| Index: pkg/front_end/lib/src/fasta/type_inference/type_constraint_gatherer.dart
|
| diff --git a/pkg/front_end/lib/src/fasta/type_inference/type_constraint_gatherer.dart b/pkg/front_end/lib/src/fasta/type_inference/type_constraint_gatherer.dart
|
| index 2d01953f34c89a34608e978582a715323adeda3c..96fcea43a65c6834635f85b45b3783485d88c269 100644
|
| --- a/pkg/front_end/lib/src/fasta/type_inference/type_constraint_gatherer.dart
|
| +++ b/pkg/front_end/lib/src/fasta/type_inference/type_constraint_gatherer.dart
|
| @@ -5,6 +5,7 @@
|
| import 'package:front_end/src/fasta/type_inference/type_schema.dart';
|
| import 'package:front_end/src/fasta/type_inference/type_schema_environment.dart';
|
| import 'package:kernel/ast.dart';
|
| +import 'package:kernel/type_algebra.dart';
|
|
|
| /// Creates a collection of [TypeConstraint]s corresponding to type parameters,
|
| /// based on an attempt to make one type schema a subtype of another.
|
| @@ -53,6 +54,143 @@ class TypeConstraintGatherer {
|
| return isMatch;
|
| }
|
|
|
| + void _constrainLower(TypeParameter parameter, DartType lower) {
|
| + _protoConstraints.add(new _ProtoConstraint.lower(parameter, lower));
|
| + }
|
| +
|
| + void _constrainUpper(TypeParameter parameter, DartType upper) {
|
| + _protoConstraints.add(new _ProtoConstraint.upper(parameter, upper));
|
| + }
|
| +
|
| + bool _isFunctionSubtypeMatch(FunctionType subtype, FunctionType supertype) {
|
| + // A function type `(M0,..., Mn, [M{n+1}, ..., Mm]) -> R0` is a subtype
|
| + // match for a function type `(N0,..., Nk, [N{k+1}, ..., Nr]) -> R1` with
|
| + // respect to `L` under constraints `C0 + ... + Cr + C`
|
| + // - If `R0` is a subtype match for a type `R1` with respect to `L` under
|
| + // constraints `C`:
|
| + // - If `n <= k` and `r <= m`.
|
| + // - And for `i` in `0...r`, `Ni` is a subtype match for `Mi` with respect
|
| + // to `L` under constraints `Ci`.
|
| + // Function types with named parameters are treated analogously to the
|
| + // positional parameter case above.
|
| + // A generic function type `<T0 extends B0, ..., Tn extends Bn>F0` is a
|
| + // subtype match for a generic function type `<S0 extends B0, ..., Sn
|
| + // extends Bn>F1` with respect to `L` under constraints `Cl`:
|
| + // - If `F0[Z0/T0, ..., Zn/Tn]` is a subtype match for `F0[Z0/S0, ...,
|
| + // Zn/Sn]` with respect to `L` under constraints `C`, where each `Zi` is a
|
| + // fresh type variable with bound `Bi`.
|
| + // - And `Cl` is `C` with each constraint replaced with its closure with
|
| + // respect to `[Z0, ..., Zn]`.
|
| + if (subtype.requiredParameterCount > supertype.requiredParameterCount) {
|
| + return false;
|
| + }
|
| + if (subtype.positionalParameters.length <
|
| + supertype.positionalParameters.length) {
|
| + return false;
|
| + }
|
| + if (subtype.typeParameters.isNotEmpty ||
|
| + supertype.typeParameters.isNotEmpty) {
|
| + var subtypeSubstitution = <TypeParameter, DartType>{};
|
| + var supertypeSubstitution = <TypeParameter, DartType>{};
|
| + if (!_matchTypeFormals(subtype.typeParameters, supertype.typeParameters,
|
| + subtypeSubstitution, supertypeSubstitution)) {
|
| + return false;
|
| + }
|
| +
|
| + // TODO(paulberry): try to push this functionality into kernel.
|
| + FunctionType substituteTypeParams(
|
| + FunctionType type, Map<TypeParameter, DartType> substitutionMap) {
|
| + var substitution = Substitution.fromMap(substitutionMap);
|
| + return new FunctionType(
|
| + type.positionalParameters.map(substitution.substituteType).toList(),
|
| + substitution.substituteType(type.returnType),
|
| + namedParameters: type.namedParameters
|
| + .map((named) => new NamedType(
|
| + named.name, substitution.substituteType(named.type)))
|
| + .toList(),
|
| + typeParameters: substitutionMap.keys.toList(),
|
| + requiredParameterCount: type.requiredParameterCount);
|
| + }
|
| +
|
| + subtype = substituteTypeParams(subtype, subtypeSubstitution);
|
| + supertype = substituteTypeParams(supertype, supertypeSubstitution);
|
| + }
|
| +
|
| + // Test the return types.
|
| + if (supertype.returnType is! VoidType &&
|
| + !_isSubtypeMatch(subtype.returnType, supertype.returnType)) {
|
| + return false;
|
| + }
|
| +
|
| + // Test the parameter types.
|
| + for (int i = 0; i < supertype.positionalParameters.length; ++i) {
|
| + var supertypeParameter = supertype.positionalParameters[i];
|
| + var subtypeParameter = subtype.positionalParameters[i];
|
| + // Termination: Both types shrink in size.
|
| + if (!_isSubtypeMatch(supertypeParameter, subtypeParameter)) {
|
| + return false;
|
| + }
|
| + }
|
| + int subtypeNameIndex = 0;
|
| + for (NamedType supertypeParameter in supertype.namedParameters) {
|
| + while (subtypeNameIndex < subtype.namedParameters.length &&
|
| + subtype.namedParameters[subtypeNameIndex].name !=
|
| + supertypeParameter.name) {
|
| + ++subtypeNameIndex;
|
| + }
|
| + if (subtypeNameIndex == subtype.namedParameters.length) return false;
|
| + NamedType subtypeParameter = subtype.namedParameters[subtypeNameIndex];
|
| + // Termination: Both types shrink in size.
|
| + if (!_isSubtypeMatch(supertypeParameter.type, subtypeParameter.type)) {
|
| + return false;
|
| + }
|
| + }
|
| + return true;
|
| + }
|
| +
|
| + bool _isInterfaceSubtypeMatch(
|
| + InterfaceType subtype, InterfaceType supertype) {
|
| + // A type `P<M0, ..., Mk>` is a subtype match for `P<N0, ..., Nk>` with
|
| + // respect to `L` under constraints `C0 + ... + Ck`:
|
| + // - If `Mi` is a subtype match for `Ni` with respect to `L` under
|
| + // constraints `Ci`.
|
| + // A type `P<M0, ..., Mk>` is a subtype match for `Q<N0, ..., Nj>` with
|
| + // respect to `L` under constraints `C`:
|
| + // - If `R<B0, ..., Bj>` is the superclass of `P<M0, ..., Mk>` and `R<B0,
|
| + // ..., Bj>` is a subtype match for `Q<N0, ..., Nj>` with respect to `L`
|
| + // under constraints `C`.
|
| + // - Or `R<B0, ..., Bj>` is one of the interfaces implemented by `P<M0, ...,
|
| + // Mk>` (considered in lexical order) and `R<B0, ..., Bj>` is a subtype
|
| + // match for `Q<N0, ..., Nj>` with respect to `L` under constraints `C`.
|
| + // - Or `R<B0, ..., Bj>` is a mixin into `P<M0, ..., Mk>` (considered in
|
| + // lexical order) and `R<B0, ..., Bj>` is a subtype match for `Q<N0, ...,
|
| + // Nj>` with respect to `L` under constraints `C`.
|
| +
|
| + // Note that since kernel requires that no class may only appear in the set
|
| + // of supertypes of a given type more than once, the order of the checks
|
| + // above is irrelevant; we just need to find the matched superclass,
|
| + // substitute, and then iterate through type variables.
|
| + var matchingSupertypeOfSubtype =
|
| + environment.hierarchy.getTypeAsInstanceOf(subtype, supertype.classNode);
|
| + if (matchingSupertypeOfSubtype == null) return false;
|
| + for (int i = 0; i < supertype.classNode.typeParameters.length; i++) {
|
| + if (!_isSubtypeMatch(matchingSupertypeOfSubtype.typeArguments[i],
|
| + supertype.typeArguments[i])) {
|
| + return false;
|
| + }
|
| + }
|
| + return true;
|
| + }
|
| +
|
| + bool _isNull(DartType type) {
|
| + // TODO(paulberry): would it be better to call this "_isBottom", and to have
|
| + // it return `true` for both Null and bottom types? Revisit this once
|
| + // enough functionality is implemented that we can compare the behavior with
|
| + // the old analyzer-based implementation.
|
| + return type is InterfaceType &&
|
| + identical(type.classNode, environment.coreTypes.nullClass);
|
| + }
|
| +
|
| /// Attempts to match [subtype] as a subtype of [supertype], gathering any
|
| /// constraints discovered in the process.
|
| ///
|
| @@ -150,78 +288,21 @@ class TypeConstraintGatherer {
|
| // and `F` is a subtype match for a type `Q` with respect to `L` under
|
| // constraints `C`.
|
| // TODO(paulberry): implement this case.
|
| - // A function type `(M0,..., Mn, [M{n+1}, ..., Mm]) -> R0` is a subtype
|
| - // match for a function type `(N0,..., Nk, [N{k+1}, ..., Nr]) -> R1` with
|
| - // respect to `L` under constraints `C0 + ... + Cr + C`
|
| - // - If `R0` is a subtype match for a type `R1` with respect to `L` under
|
| - // constraints `C`:
|
| - // - If `n <= k` and `r <= m`.
|
| - // - And for `i` in `0...r`, `Ni` is a subtype match for `Mi` with respect
|
| - // to `L` under constraints `Ci`.
|
| - // Function types with named parameters are treated analogously to the
|
| - // positional parameter case above.
|
| - // TODO(paulberry): implement this case.
|
| - // A generic function type `<T0 extends B0, ..., Tn extends Bn>F0` is a
|
| - // subtype match for a generic function type `<S0 extends B0, ..., Sn
|
| - // extends Bn>F1` with respect to `L` under constraints `Cl`:
|
| - // - If `F0[Z0/T0, ..., Zn/Tn]` is a subtype match for `F0[Z0/S0, ...,
|
| - // Zn/Sn]` with respect to `L` under constraints `C`, where each `Zi` is a
|
| - // fresh type variable with bound `Bi`.
|
| - // - And `Cl` is `C` with each constraint replaced with its closure with
|
| - // respect to `[Z0, ..., Zn]`.
|
| - // TODO(paulberry): implement this case.
|
| - return false;
|
| - }
|
| -
|
| - void _constrainLower(TypeParameter parameter, DartType lower) {
|
| - _protoConstraints.add(new _ProtoConstraint.lower(parameter, lower));
|
| - }
|
| -
|
| - void _constrainUpper(TypeParameter parameter, DartType upper) {
|
| - _protoConstraints.add(new _ProtoConstraint.upper(parameter, upper));
|
| - }
|
| -
|
| - bool _isInterfaceSubtypeMatch(
|
| - InterfaceType subtype, InterfaceType supertype) {
|
| - // A type `P<M0, ..., Mk>` is a subtype match for `P<N0, ..., Nk>` with
|
| - // respect to `L` under constraints `C0 + ... + Ck`:
|
| - // - If `Mi` is a subtype match for `Ni` with respect to `L` under
|
| - // constraints `Ci`.
|
| - // A type `P<M0, ..., Mk>` is a subtype match for `Q<N0, ..., Nj>` with
|
| - // respect to `L` under constraints `C`:
|
| - // - If `R<B0, ..., Bj>` is the superclass of `P<M0, ..., Mk>` and `R<B0,
|
| - // ..., Bj>` is a subtype match for `Q<N0, ..., Nj>` with respect to `L`
|
| - // under constraints `C`.
|
| - // - Or `R<B0, ..., Bj>` is one of the interfaces implemented by `P<M0, ...,
|
| - // Mk>` (considered in lexical order) and `R<B0, ..., Bj>` is a subtype
|
| - // match for `Q<N0, ..., Nj>` with respect to `L` under constraints `C`.
|
| - // - Or `R<B0, ..., Bj>` is a mixin into `P<M0, ..., Mk>` (considered in
|
| - // lexical order) and `R<B0, ..., Bj>` is a subtype match for `Q<N0, ...,
|
| - // Nj>` with respect to `L` under constraints `C`.
|
| -
|
| - // Note that since kernel requires that no class may only appear in the set
|
| - // of supertypes of a given type more than once, the order of the checks
|
| - // above is irrelevant; we just need to find the matched superclass,
|
| - // substitute, and then iterate through type variables.
|
| - var matchingSupertypeOfSubtype =
|
| - environment.hierarchy.getTypeAsInstanceOf(subtype, supertype.classNode);
|
| - if (matchingSupertypeOfSubtype == null) return false;
|
| - for (int i = 0; i < supertype.classNode.typeParameters.length; i++) {
|
| - if (!_isSubtypeMatch(matchingSupertypeOfSubtype.typeArguments[i],
|
| - supertype.typeArguments[i])) {
|
| - return false;
|
| + if (subtype is FunctionType) {
|
| + if (supertype is InterfaceType) {
|
| + if (identical(
|
| + supertype.classNode, environment.coreTypes.functionClass) ||
|
| + identical(supertype.classNode, environment.coreTypes.objectClass)) {
|
| + return true;
|
| + } else {
|
| + return false;
|
| + }
|
| + }
|
| + if (supertype is FunctionType) {
|
| + return _isFunctionSubtypeMatch(subtype, supertype);
|
| }
|
| }
|
| - return true;
|
| - }
|
| -
|
| - bool _isNull(DartType type) {
|
| - // TODO(paulberry): would it be better to call this "_isBottom", and to have
|
| - // it return `true` for both Null and bottom types? Revisit this once
|
| - // enough functionality is implemented that we can compare the behavior with
|
| - // the old analyzer-based implementation.
|
| - return type is InterfaceType &&
|
| - identical(type.classNode, environment.coreTypes.nullClass);
|
| + return false;
|
| }
|
|
|
| bool _isTop(DartType type) =>
|
| @@ -229,6 +310,37 @@ class TypeConstraintGatherer {
|
| type is VoidType ||
|
| (type is InterfaceType &&
|
| identical(type.classNode, environment.coreTypes.objectClass));
|
| +
|
| + /// Given two lists of function type formal parameters, checks that their
|
| + /// bounds are compatible.
|
| + ///
|
| + /// The return value indicates whether a match was found. If it was, entries
|
| + /// are added to [substitution1] and [substitution2] which substitute a fresh
|
| + /// set of type variables for the type parameters [params1] and [params2],
|
| + /// respectively, allowing further comparison.
|
| + bool _matchTypeFormals(
|
| + List<TypeParameter> params1,
|
| + List<TypeParameter> params2,
|
| + Map<TypeParameter, DartType> substitution1,
|
| + Map<TypeParameter, DartType> substitution2) {
|
| + int count = params1.length;
|
| + if (count != params2.length) return false;
|
| + // TODO(paulberry): in imitation of analyzer, we're checking the bounds as
|
| + // we build up the substitutions. But I don't think that's correct--I think
|
| + // we should build up both substitutions completely before checking any
|
| + // bounds.
|
| + for (int i = 0; i < count; i++) {
|
| + TypeParameter pFresh = new TypeParameter(params2[i].name);
|
| + DartType variableFresh = new TypeParameterType(pFresh);
|
| + substitution1[params1[i]] = variableFresh;
|
| + substitution2[params2[i]] = variableFresh;
|
| + DartType bound1 = substitute(params1[i].bound, substitution1);
|
| + DartType bound2 = substitute(params2[i].bound, substitution2);
|
| + pFresh.bound = bound2;
|
| + if (!_isSubtypeMatch(bound2, bound1)) return false;
|
| + }
|
| + return true;
|
| + }
|
| }
|
|
|
| /// Tracks a single constraint on a single type variable.
|
|
|