Skip to content

Commit

Permalink
Implement inferGenericFunctionOrType in front_end.
Browse files Browse the repository at this point in the history
R=jmesserly@google.com, scheglov@google.com

Review-Url: https://codereview.chromium.org/2863833002 .
  • Loading branch information
stereotype441 committed May 5, 2017
1 parent 28f277a commit a59bff3
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,45 +6,9 @@ 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';

/// Attempts to find a set of constraints for the given [typeParameters] under
/// which [subtype] is a subtype of [supertype].
///
/// If such a set can be found, it is returned (as a map from type parameter to
/// constraint). If it can't, `null` is returned.
Map<TypeParameter, TypeConstraint> subtypeMatch(
TypeSchemaEnvironment environment,
Iterable<TypeParameter> typeParameters,
DartType subtype,
DartType supertype) {
var typeConstraintGatherer =
new _TypeConstraintGatherer(environment, typeParameters);
if (typeConstraintGatherer.isSubtypeMatch(subtype, supertype)) {
return typeConstraintGatherer.computeConstraints();
} else {
return null;
}
}

/// Tracks a single constraint on a single type variable.
///
/// This is called "_ProtoConstraint" to distinglish from [Constraint], which
/// tracks the upper and lower bounds that are together implied by a set of
/// [_ProtoConstraint]s.
class _ProtoConstraint {
final TypeParameter parameter;

final DartType bound;

final bool isUpper;

_ProtoConstraint.lower(this.parameter, this.bound) : isUpper = false;

_ProtoConstraint.upper(this.parameter, this.bound) : isUpper = true;
}

/// Creates a collection of [TypeConstraint]s corresponding to type parameters,
/// based on an attempt to make one type schema a subtype of another.
class _TypeConstraintGatherer {
class TypeConstraintGatherer {
final TypeSchemaEnvironment environment;

final _protoConstraints = <_ProtoConstraint>[];
Expand All @@ -53,7 +17,7 @@ class _TypeConstraintGatherer {

/// Creates a [TypeConstraintGatherer] which is prepared to gather type
/// constraints for the given [typeParameters].
_TypeConstraintGatherer(
TypeConstraintGatherer(
this.environment, Iterable<TypeParameter> typeParameters)
: _parametersToConstrain = typeParameters.toList();

Expand All @@ -75,12 +39,30 @@ class _TypeConstraintGatherer {
return result;
}

/// Tries to match [subtype] against [supertype].
///
/// If the match suceeds, the resulting type constraints are recorded for
/// later use by [computeConstraints]. If the match fails, the set of type
/// constraints is unchanged.
bool trySubtypeMatch(DartType subtype, DartType supertype) {
int oldProtoConstraintsLength = _protoConstraints.length;
bool isMatch = _isSubtypeMatch(subtype, supertype);
if (!isMatch) {
_protoConstraints.length = oldProtoConstraintsLength;
}
return isMatch;
}

/// Attempts to match [subtype] as a subtype of [supertype], gathering any
/// constraints discovered in the process. If a set of constraints was found,
/// `true` is returned and the caller may proceed to call
/// [computeConstraints]. Otherwise, `false` is returned and the set of
/// gathered constraints is undefined.
bool isSubtypeMatch(DartType subtype, DartType supertype) {
/// constraints discovered in the process.
///
/// If a set of constraints was found, `true` is returned and the caller
/// may proceed to call [computeConstraints]. Otherwise, `false` is returned.
///
/// In the case where `false` is returned, some bogus constraints may have
/// been added to [_protoConstraints]. It is the caller's responsibility to
/// discard them if necessary.
bool _isSubtypeMatch(DartType subtype, DartType supertype) {
// The unknown type `?` is a subtype match for any type `Q` with no
// constraints.
if (subtype is UnknownType) return true;
Expand Down Expand Up @@ -152,7 +134,7 @@ class _TypeConstraintGatherer {
// type `Q` with respect to `L` under constraints `C`:
// - If `P` is a subtype match for `Q` with respect to `L` under
// constraints `C`.
return isSubtypeMatch(subtype.parameter.bound, supertype);
return _isSubtypeMatch(subtype.parameter.bound, supertype);
}
if (subtype is InterfaceType && supertype is InterfaceType) {
return _isInterfaceSubtypeMatch(subtype, supertype);
Expand Down Expand Up @@ -225,7 +207,7 @@ class _TypeConstraintGatherer {
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],
if (!_isSubtypeMatch(matchingSupertypeOfSubtype.typeArguments[i],
supertype.typeArguments[i])) {
return false;
}
Expand All @@ -248,3 +230,20 @@ class _TypeConstraintGatherer {
(type is InterfaceType &&
identical(type.classNode, environment.coreTypes.objectClass));
}

/// Tracks a single constraint on a single type variable.
///
/// This is called "_ProtoConstraint" to distinguish from [TypeConstraint],
/// which tracks the upper and lower bounds that are together implied by a set
/// of [_ProtoConstraint]s.
class _ProtoConstraint {
final TypeParameter parameter;

final DartType bound;

final bool isUpper;

_ProtoConstraint.lower(this.parameter, this.bound) : isUpper = false;

_ProtoConstraint.upper(this.parameter, this.bound) : isUpper = true;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import 'dart:math' as math;

import 'package:front_end/src/fasta/type_inference/type_constraint_gatherer.dart';
import 'package:front_end/src/fasta/type_inference/type_schema.dart';
import 'package:front_end/src/fasta/type_inference/type_schema_elimination.dart';
import 'package:kernel/ast.dart';
Expand Down Expand Up @@ -157,6 +158,68 @@ class TypeSchemaEnvironment extends TypeEnvironment {
return const DynamicType();
}

/// Infers a generic type, function, method, or list/map literal
/// instantiation, using the downward context type as well as the argument
/// types if available.
///
/// For example, given a function type with generic type parameters, this
/// infers the type parameters from the actual argument types, and returns the
/// instantiated function type.
///
/// Concretely, given a function type with parameter types P0, P1, ... Pn,
/// result type R, and generic type parameters T0, T1, ... Tm, use the
/// argument types A0, A1, ... An to solve for the type parameters.
///
/// For each parameter Pi, we want to ensure that Ai <: Pi. We can do this by
/// running the subtype algorithm, and when we reach a type parameter Tj,
/// recording the lower or upper bound it must satisfy. At the end, all
/// constraints can be combined to determine the type.
///
/// All constraints on each type parameter Tj are tracked, as well as where
/// they originated, so we can issue an error message tracing back to the
/// argument values, type parameter "extends" clause, or the return type
/// context.
///
/// TODO(paulberry): I think [formalTypes] and [actualTypes] might only be
/// used for upwards inference. If this is the case, consider having the
/// caller to pass `null` for these to signal downwards inference, rather than
/// use an optional boolean parameter that might easily be missed.
DartType inferGenericFunctionOrType(
List<TypeParameter> typeParametersToInfer,
DartType genericType,
List<DartType> formalTypes,
List<DartType> actualTypes,
DartType returnContextType,
List<DartType> typesFromDownwardsInference,
{bool downwards: false}) {
if (typeParametersToInfer.isEmpty) {
return genericType;
}

// Create a TypeConstraintGatherer that will allow certain type parameters
// to be inferred. It will optimistically assume these type parameters can
// be subtypes (or supertypes) as necessary, and track the constraints that
// are implied by this.
var gatherer = new TypeConstraintGatherer(this, typeParametersToInfer);

DartType declaredReturnType =
genericType is FunctionType ? genericType.returnType : genericType;

if (returnContextType != null) {
gatherer.trySubtypeMatch(declaredReturnType, returnContextType);
}

for (int i = 0; i < actualTypes.length; i++) {
// Try to pass each argument to each parameter, recording any type
// parameter bounds that were implied by this assignment.
gatherer.trySubtypeMatch(actualTypes[i], formalTypes[i]);
}

return inferTypeFromConstraints(gatherer.computeConstraints(), genericType,
typeParametersToInfer, typesFromDownwardsInference,
downwardsInferPhase: downwards);
}

/// Use the given [constraints] to substitute for type variables in
/// [genericType].
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,11 @@ class TypeConstraintGathererTest {
DartType a, DartType b, List<String> expectedConstraints) {
var typeSchemaEnvironment =
new TypeSchemaEnvironment(coreTypes, new ClassHierarchy(program));
var constraints =
subtypeMatch(typeSchemaEnvironment, [T1.parameter, T2.parameter], a, b);
var typeConstraintGatherer = new TypeConstraintGatherer(
typeSchemaEnvironment, [T1.parameter, T2.parameter]);
var constraints = typeConstraintGatherer.trySubtypeMatch(a, b)
? typeConstraintGatherer.computeConstraints()
: null;
if (expectedConstraints == null) {
expect(constraints, isNull);
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,47 @@ class TypeSchemaEnvironmentTest {
expect(env.getGreatestLowerBound(A, B), same(bottomType));
}

void test_inferGenericFunctionOrType() {
var env = _makeEnv();
{
// Test an instantiation of [1, 2.0] with no context. This should infer
// as List<?> during downwards inference.
var typesFromDownwardsInference = <DartType>[null];
TypeParameterType T = listClass.thisType.typeArguments[0];
expect(
env.inferGenericFunctionOrType([T.parameter], listClass.thisType, [],
[], null, typesFromDownwardsInference,
downwards: true),
_list(unknownType));
// And upwards inference should refine it to List<num>.
expect(
env.inferGenericFunctionOrType([T.parameter], listClass.thisType,
[T, T], [intType, doubleType], null, typesFromDownwardsInference),
_list(numType));
}
{
// Test an instantiation of [1, 2.0] with a context of List<Object>. This
// should infer as List<Object> during downwards inference.
var typesFromDownwardsInference = <DartType>[null];
TypeParameterType T = listClass.thisType.typeArguments[0];
expect(
env.inferGenericFunctionOrType([T.parameter], listClass.thisType, [],
[], _list(objectType), typesFromDownwardsInference,
downwards: true),
_list(objectType));
// And upwards inference should preserve the type.
expect(
env.inferGenericFunctionOrType(
[T.parameter],
listClass.thisType,
[T, T],
[intType, doubleType],
_list(objectType),
typesFromDownwardsInference),
_list(objectType));
}
}

void test_inferTypeFromConstraints_applyBound() {
// class A<T extends num> {}
var T = new TypeParameter('T', numType);
Expand Down

0 comments on commit a59bff3

Please sign in to comment.