blob: 1a75b9c50f46cbdd5fc219c6fc2f8cf2a35d504e [file] [log] [blame]
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
library fasta.class_hierarchy_builder;
import 'package:kernel/ast.dart';
import 'package:kernel/class_hierarchy.dart' show ClassHierarchyBase;
import 'package:kernel/core_types.dart' show CoreTypes;
import 'package:kernel/type_algebra.dart';
import 'package:kernel/src/bounds_checks.dart';
import '../../builder/declaration_builders.dart';
import '../../messages.dart'
show Message, templateMixinInferenceNoMatchingClass;
import '../../problems.dart' show unexpected, unsupported;
import '../../type_inference/type_schema.dart';
class BuilderMixinInferrer {
final CoreTypes coreTypes;
final _MixinInferenceSolution _mixinInferenceSolution;
final List<TypeParameter> typeParametersToSolveFor;
final ClassBuilder cls;
final ClassHierarchyBase classHierarchyBase;
BuilderMixinInferrer(
this.cls, this.classHierarchyBase, this.typeParametersToSolveFor)
: coreTypes = classHierarchyBase.coreTypes,
_mixinInferenceSolution =
new _MixinInferenceSolution(typeParametersToSolveFor);
void generateConstraints(
Class mixinClass, Supertype baseType, Supertype mixinSupertype) {
if (_mixinInferenceSolution.isUnsolvable) {
// The currently observed equalities are already unsolvable, and adding
// more will not change that.
return;
}
if (mixinSupertype.typeArguments.isEmpty) {
// The supertype constraint isn't generic; it doesn't constrain anything.
} else if (mixinSupertype.classNode.isAnonymousMixin) {
// We have either a mixin declaration `mixin M<X0, ..., Xn> on S0, S1` or
// a VM-style super mixin `abstract class M<X0, ..., Xn> extends S0 with
// S1` where S0 and S1 are superclass constraints that possibly have type
// arguments.
//
// It has been compiled by naming the superclass to either:
//
// abstract class S0&S1<...> extends Object implements S0, S1 {}
// abstract class M<X0, ..., Xn> extends S0&S1<...> ...
//
// for a mixin declaration, or else:
//
// abstract class S0&S1<...> = S0 with S1;
// abstract class M<X0, ..., Xn> extends S0&S1<...>
//
// for a VM-style super mixin. The type parameters of S0&S1 are the X0,
// ..., Xn that occurred free in S0 and S1. Treat S0 and S1 as separate
// supertype constraints by recursively calling this algorithm.
//
// In the Dart VM the mixin application classes themselves are all
// eliminated by translating them to normal classes. In that case, the
// mixin appears as the only interface in the introduced class. We
// support three forms for the superclass constraints:
//
// abstract class S0&S1<...> extends Object implements S0, S1 {}
// abstract class S0&S1<...> = S0 with S1;
// abstract class S0&S1<...> extends S0 implements S1 {}
Class mixinSuperclass = mixinSupertype.classNode;
if (mixinSuperclass.mixedInType == null &&
mixinSuperclass.implementedTypes.length != 1 &&
(mixinSuperclass.superclass != coreTypes.objectClass ||
mixinSuperclass.implementedTypes.length != 2)) {
unexpected(
'Compiler-generated mixin applications have a mixin or else '
'implement exactly one type',
'$mixinSuperclass implements '
'${mixinSuperclass.implementedTypes.length} types',
mixinSuperclass.fileOffset,
mixinSuperclass.fileUri);
}
Substitution substitution = Substitution.fromSupertype(mixinSupertype);
Supertype s0, s1;
if (mixinSuperclass.implementedTypes.length == 2) {
s0 = mixinSuperclass.implementedTypes[0];
s1 = mixinSuperclass.implementedTypes[1];
} else if (mixinSuperclass.implementedTypes.length == 1) {
s0 = mixinSuperclass.supertype!;
s1 = mixinSuperclass.implementedTypes.first;
} else {
s0 = mixinSuperclass.supertype!;
s1 = mixinSuperclass.mixedInType!;
}
s0 = substitution.substituteSupertype(s0);
s1 = substitution.substituteSupertype(s1);
generateConstraints(mixinClass, baseType, s0);
generateConstraints(mixinClass, baseType, s1);
} else {
// Find the type U0 which is baseType as an instance of mixinSupertype's
// class.
Supertype? supertype =
asInstantiationOf(baseType, mixinSupertype.classNode);
if (supertype == null) {
reportProblem(
templateMixinInferenceNoMatchingClass.withArguments(mixinClass.name,
baseType.classNode.name, mixinSupertype.asInterfaceType),
mixinClass);
return;
}
InterfaceType u0 = Substitution.fromSupertype(baseType)
.substituteSupertype(supertype)
.asInterfaceType;
// We want to solve U0 = S0 where S0 is mixinSupertype, but we only have
// a subtype constraints. Solve for equality by solving
// both U0 <: S0 and S0 <: U0.
InterfaceType s0 = mixinSupertype.asInterfaceType;
_mixinInferenceSolution.addSolutionFor(s0, u0,
unsupportedErrorReporter: this);
}
}
void infer(Class classNode) {
Supertype mixedInType = classNode.mixedInType!;
assert(mixedInType.typeArguments.every((t) => t == const UnknownType()));
// Note that we have no anonymous mixin applications, they have all
// been named. Note also that mixin composition has been translated
// so that we only have mixin applications of the form `S with M`.
Supertype baseType = classNode.supertype!;
Class mixinClass = mixedInType.classNode;
Supertype mixinSupertype = mixinClass.supertype!;
// Generate constraints based on the mixin's supertype.
generateConstraints(mixinClass, baseType, mixinSupertype);
if (_mixinInferenceSolution.isUnsolvable) {
reportProblem(
templateMixinInferenceNoMatchingClass.withArguments(mixinClass.name,
baseType.classNode.name, mixinSupertype.asInterfaceType),
mixinClass);
}
// Generate new type parameters with the solution as bounds.
List<TypeParameter> parameters;
if (_mixinInferenceSolution.isUnsolvable) {
parameters = [...mixinClass.typeParameters];
} else {
parameters = [
for (TypeParameter typeParameter in mixinClass.typeParameters)
new TypeParameter(
typeParameter.name,
_mixinInferenceSolution.solution[typeParameter] ??
typeParameter.bound,
typeParameter.defaultType)
];
}
// Bounds might mention the mixin class's type parameters so we have to
// substitute them before calling instantiate to bounds.
Substitution substitution = Substitution.fromPairs(
mixinClass.typeParameters,
new List<DartType>.generate(
parameters.length,
(i) => new TypeParameterType.forAlphaRenaming(
mixinClass.typeParameters[i], parameters[i])));
for (TypeParameter p in parameters) {
p.bound = substitution.substituteType(p.bound);
}
// Use instantiate to bounds.
List<DartType> bounds = calculateBounds(parameters, coreTypes.objectClass,
isNonNullableByDefault:
classNode.enclosingLibrary.isNonNullableByDefault);
for (int i = 0; i < mixedInType.typeArguments.length; ++i) {
mixedInType.typeArguments[i] = bounds[i];
}
}
Supertype? asInstantiationOf(Supertype type, Class superclass) {
List<DartType>? arguments = classHierarchyBase.getTypeArgumentsAsInstanceOf(
type.asInterfaceType, superclass);
if (arguments == null) return null;
return new Supertype(superclass, arguments);
}
void reportProblem(Message message, Class kernelClass) {
int length = cls.isMixinApplication ? 1 : cls.fullNameForErrors.length;
cls.addProblem(message, cls.charOffset, length);
}
Never reportUnsupportedProblem(String operation) {
return unsupported(operation, cls.charOffset, cls.fileUri);
}
}
class _MixinInferenceSolution {
Map<TypeParameter, DartType>? _typeParameterSolution = {};
final List<TypeParameter> typeParametersToSolveFor;
final Map<StructuralParameter, StructuralParameter>
structuralTypeParameterEqualityAssumptions =
<StructuralParameter, StructuralParameter>{};
_MixinInferenceSolution(this.typeParametersToSolveFor);
Map<TypeParameter, DartType> get solution => _typeParameterSolution!;
bool get isUnsolvable => _typeParameterSolution == null;
void addSolutionFor(DartType type1, DartType type2,
{required BuilderMixinInferrer unsupportedErrorReporter}) {
if (_typeParameterSolution == null) {
// The inference has already failed at an earlier stage, so the constraint
// gathering can stop.
return;
}
_typeParameterSolution = _mergeInferenceByUnificationResults(
_solveForEquality(type1, type2,
unsupportedErrorReporter: unsupportedErrorReporter),
_typeParameterSolution);
}
Map<TypeParameter, DartType>? _solveForEquality(
DartType type1, DartType type2,
{required BuilderMixinInferrer unsupportedErrorReporter}) {
assert(!(containsTypeVariable(type1, {...typeParametersToSolveFor}) &&
containsTypeVariable(type2, {...typeParametersToSolveFor})));
assert(type1 is! TypedefType);
assert(type2 is! TypedefType);
if (type1 is TypeParameterType &&
typeParametersToSolveFor.contains(type1.parameter)) {
return <TypeParameter, DartType>{type1.parameter: type2};
}
if (type2 is TypeParameterType &&
typeParametersToSolveFor.contains(type2.parameter)) {
return <TypeParameter, DartType>{type2.parameter: type1};
}
switch (type1) {
case AuxiliaryType():
return unsupportedErrorReporter.reportUnsupportedProblem(
"_MixinInferenceSolution._solveForEquality"
"(${type1.runtimeType}, ${type2.runtimeType})");
case InvalidType():
if (type2 is! InvalidType) {
return null;
} else {
return <TypeParameter, DartType>{};
}
case DynamicType():
if (type2 is! DynamicType) {
return null;
} else {
return <TypeParameter, DartType>{};
}
case VoidType():
if (type2 is! VoidType) {
return null;
} else {
return <TypeParameter, DartType>{};
}
case NeverType():
if (type2 is! NeverType) {
return null;
} else {
return <TypeParameter, DartType>{};
}
case NullType():
if (type2 is! NullType) {
return null;
} else {
return <TypeParameter, DartType>{};
}
case FunctionType():
if (type2 is! FunctionType) {
return null;
} else {
if (type1.positionalParameters.length !=
type2.positionalParameters.length ||
type1.requiredParameterCount != type2.requiredParameterCount ||
type1.namedParameters.length != type2.namedParameters.length ||
type1.typeParameters.length != type2.typeParameters.length ||
type1.nullability != type2.nullability) {
return null;
}
for (int i = 0; i < type1.typeParameters.length; i++) {
structuralTypeParameterEqualityAssumptions[
type1.typeParameters[i]] = type2.typeParameters[i];
structuralTypeParameterEqualityAssumptions[
type2.typeParameters[i]] = type1.typeParameters[i];
}
Map<TypeParameter, DartType>? result = _solveForEquality(
type1.returnType, type2.returnType,
unsupportedErrorReporter: unsupportedErrorReporter);
if (result == null) {
return null;
}
for (int i = 0; i < type1.typeParameters.length; i++) {
result = _mergeInferenceByUnificationResults(
_solveForEquality(type1.typeParameters[i].bound,
type2.typeParameters[i].bound,
unsupportedErrorReporter: unsupportedErrorReporter),
result);
if (result == null) {
return null;
}
}
for (int i = 0; i < type1.positionalParameters.length; i++) {
result = _mergeInferenceByUnificationResults(
_solveForEquality(type1.positionalParameters[i],
type2.positionalParameters[i],
unsupportedErrorReporter: unsupportedErrorReporter),
result);
if (result == null) {
return null;
}
}
Map<String, NamedType> namedParameterByName1 = <String, NamedType>{
for (NamedType namedType in type1.namedParameters)
namedType.name: namedType
};
for (NamedType namedType in type2.namedParameters) {
if (!namedParameterByName1.containsKey(namedType.name)) {
return null;
} else {
result = _mergeInferenceByUnificationResults(
_solveForEquality(namedParameterByName1[namedType.name]!.type,
namedType.type,
unsupportedErrorReporter: unsupportedErrorReporter),
result);
if (result == null) {
return null;
}
namedParameterByName1.remove(namedType.name);
}
}
if (namedParameterByName1.isNotEmpty) {
return null;
}
return result;
}
case TypedefType():
return unsupportedErrorReporter.reportUnsupportedProblem(
"_MixinInferenceSolution._solveForEquality"
"(${type1.runtimeType}, ${type2.runtimeType})");
case FutureOrType():
if (type2 is! FutureOrType) {
return null;
} else {
return _solveForEquality(type1.typeArgument, type2.typeArgument,
unsupportedErrorReporter: unsupportedErrorReporter);
}
case IntersectionType():
// Intersection types can't appear in supertypes.
return unsupportedErrorReporter.reportUnsupportedProblem(
"_MixinInferenceSolution._solveForEquality"
"(${type1.runtimeType}, ${type2.runtimeType})");
case TypeParameterType():
if (type2 is! TypeParameterType ||
type1.parameter != type2.parameter ||
type1.nullability != type2.nullability) {
return null;
} else {
return <TypeParameter, DartType>{};
}
case StructuralParameterType():
if (type2 is! StructuralParameterType ||
structuralTypeParameterEqualityAssumptions[type1.parameter] !=
type2.parameter ||
type1.nullability != type2.nullability) {
return null;
} else {
return <TypeParameter, DartType>{};
}
case RecordType():
if (type2 is! RecordType) {
return null;
} else {
if (type1.positional.length != type2.positional.length ||
type2.named.length != type2.named.length ||
type1.nullability != type2.nullability) {
return null;
}
Map<TypeParameter, DartType>? result = {};
for (int i = 0; i < type1.positional.length; i++) {
result = _mergeInferenceByUnificationResults(
_solveForEquality(type1.positional[i], type2.positional[i],
unsupportedErrorReporter: unsupportedErrorReporter),
result);
if (result == null) {
return result;
}
}
Map<String, NamedType> namedParameterByName1 = <String, NamedType>{
for (NamedType namedType in type1.named) namedType.name: namedType
};
for (NamedType namedType in type2.named) {
if (!namedParameterByName1.containsKey(namedType.name)) {
return null;
} else {
result = _mergeInferenceByUnificationResults(
_solveForEquality(namedParameterByName1[namedType.name]!.type,
namedType.type,
unsupportedErrorReporter: unsupportedErrorReporter),
result);
if (result == null) {
return null;
}
namedParameterByName1.remove(namedType.name);
}
}
if (namedParameterByName1.isNotEmpty) {
return null;
}
return result;
}
case InterfaceType():
if (type2 is! InterfaceType) {
return null;
} else {
if (type1.classNode != type2.classNode ||
type1.nullability != type2.nullability) {
return null;
}
assert(type1.typeArguments.length == type2.typeArguments.length);
Map<TypeParameter, DartType>? result = {};
for (int i = 0; i < type1.typeArguments.length; i++) {
result = _mergeInferenceByUnificationResults(
_solveForEquality(
type1.typeArguments[i], type2.typeArguments[i],
unsupportedErrorReporter: unsupportedErrorReporter),
result);
if (result == null) {
return null;
}
}
return result;
}
case ExtensionType():
if (type2 is! ExtensionType) {
return null;
} else {
if (type1.extensionTypeDeclaration !=
type2.extensionTypeDeclaration ||
type1.nullability != type2.nullability) {
return null;
}
assert(type1.typeArguments.length == type2.typeArguments.length);
Map<TypeParameter, DartType>? result = {};
for (int i = 0; i < type1.typeArguments.length; i++) {
result = _mergeInferenceByUnificationResults(
_solveForEquality(
type1.typeArguments[i], type2.typeArguments[i],
unsupportedErrorReporter: unsupportedErrorReporter),
result);
if (result == null) {
return null;
}
}
return result;
}
}
}
Map<TypeParameter, DartType>? _mergeInferenceByUnificationResults(
Map<TypeParameter, DartType>? result1,
Map<TypeParameter, DartType>? result2) {
if (result1 == null || result2 == null) {
return null;
} else {
for (TypeParameter typeParameter in result1.keys) {
if (result2.containsKey(typeParameter)) {
if (result1[typeParameter] != result2[typeParameter]) {
return null;
}
} else {
result2[typeParameter] = result1[typeParameter]!;
}
}
return result2;
}
}
}