blob: 16942c727e574376d3cb9c9ba01008be51ab58f6 [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.
import 'package:kernel/ast.dart';
import 'package:kernel/class_hierarchy.dart' show ClassHierarchyBase;
import 'package:kernel/core_types.dart' show CoreTypes;
import 'package:kernel/src/bounds_checks.dart';
import 'package:kernel/type_algebra.dart';
import '../../base/messages.dart'
show Message, codeMixinInferenceNoMatchingClass;
import '../../base/problems.dart' show unexpected, unsupported;
import '../../source/source_class_builder.dart';
import '../../type_inference/type_schema.dart';
class BuilderMixinInferrer {
final CoreTypes coreTypes;
final _MixinInferenceSolution _mixinInferenceSolution;
final List<TypeParameter> typeParametersToSolveFor;
final SourceClassBuilder classBuilder;
final ClassHierarchyBase classHierarchyBase;
BuilderMixinInferrer(
this.classBuilder,
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];
}
// Coverage-ignore(suite): Not run.
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(
codeMixinInferenceNoMatchingClass.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) {
// Coverage-ignore-block(suite): Not run.
reportProblem(
codeMixinInferenceNoMatchingClass.withArguments(
mixinClass.name,
baseType.classNode.name,
mixinSupertype.asInterfaceType,
),
mixinClass,
);
}
// Generate new type parameters with the solution as bounds.
List<TypeParameter> parameters;
if (_mixinInferenceSolution.isUnsolvable) {
// Coverage-ignore-block(suite): Not run.
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, [
for (TypeParameter parameter in parameters)
new TypeParameterType.withDefaultNullability(parameter),
]);
for (TypeParameter p in parameters) {
p.bound = substitution.substituteType(p.bound);
}
// Use instantiate to bounds.
List<DartType> bounds = calculateBounds(parameters, coreTypes.objectClass);
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 = classBuilder.isMixinApplication
? 1
:
// Coverage-ignore(suite): Not run.
classBuilder.fullNameForErrors.length;
classBuilder.libraryBuilder.addProblem(
message,
classBuilder.fileOffset,
length,
classBuilder.fileUri,
);
}
// Coverage-ignore(suite): Not run.
Never reportUnsupportedProblem(String operation) {
return unsupported(
operation,
classBuilder.fileOffset,
classBuilder.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(
!(containsTypeParameter(type1, {...typeParametersToSolveFor}) &&
containsTypeParameter(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 &&
// Coverage-ignore(suite): Not run.
typeParametersToSolveFor.contains(type2.parameter)) {
// Coverage-ignore-block(suite): Not run.
return <TypeParameter, DartType>{type2.parameter: type1};
}
switch (type1) {
case AuxiliaryType():
// Coverage-ignore(suite): Not run.
return unsupportedErrorReporter.reportUnsupportedProblem(
"_MixinInferenceSolution._solveForEquality"
"(${type1.runtimeType}, ${type2.runtimeType})",
);
case InvalidType():
// Coverage-ignore(suite): Not run.
if (type2 is! InvalidType) {
return null;
} else {
return <TypeParameter, DartType>{};
}
case DynamicType():
// Coverage-ignore(suite): Not run.
if (type2 is! DynamicType) {
return null;
} else {
return <TypeParameter, DartType>{};
}
case VoidType():
if (type2 is! VoidType) {
return null;
} else {
return <TypeParameter, DartType>{};
}
case NeverType():
// Coverage-ignore(suite): Not run.
if (type2 is! NeverType) {
return null;
} else {
return <TypeParameter, DartType>{};
}
case NullType():
// Coverage-ignore(suite): Not run.
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 // Coverage-ignore(suite): Not run.
.name:
namedType,
};
for (NamedType namedType in type2.namedParameters) {
// Coverage-ignore-block(suite): Not run.
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():
// Coverage-ignore(suite): Not run.
return unsupportedErrorReporter.reportUnsupportedProblem(
"_MixinInferenceSolution._solveForEquality"
"(${type1.runtimeType}, ${type2.runtimeType})",
);
case FutureOrType():
// Coverage-ignore(suite): Not run.
if (type2 is! FutureOrType) {
return null;
} else {
return _solveForEquality(
type1.typeArgument,
type2.typeArgument,
unsupportedErrorReporter: unsupportedErrorReporter,
);
}
case IntersectionType():
// Coverage-ignore(suite): Not run.
// Intersection types can't appear in supertypes.
return unsupportedErrorReporter.reportUnsupportedProblem(
"_MixinInferenceSolution._solveForEquality"
"(${type1.runtimeType}, ${type2.runtimeType})",
);
case TypeParameterType():
// Coverage-ignore(suite): Not run.
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():
// Coverage-ignore(suite): Not run.
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;
}
// Coverage-ignore(suite): Not run.
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)) {
// Coverage-ignore-block(suite): Not run.
if (result1[typeParameter] != result2[typeParameter]) {
return null;
}
} else {
result2[typeParameter] = result1[typeParameter]!;
}
}
return result2;
}
}
}