mirror of
https://github.com/dart-lang/sdk
synced 2024-10-14 19:10:05 +00:00
Implement inferTypeFromConstraints in front_end.
This is an intermediate algorithm used in type inference. It corresponds to the method _GenericInferrer.infer() in analyzer. R=jmesserly@google.com, scheglov@google.com Note: error reporting will be added in a follow-up CL. Review-Url: https://codereview.chromium.org/2863823002 .
This commit is contained in:
parent
c376a45f91
commit
28f277a7e2
|
@ -20,6 +20,5 @@
|
|||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="Dart SDK" level="application" />
|
||||
<orderEntry type="library" name="Dart SDK" level="project" />
|
||||
<orderEntry type="library" name="Dart Packages" level="project" />
|
||||
</component>
|
||||
</module>
|
|
@ -16,11 +16,19 @@ import 'package:kernel/type_environment.dart';
|
|||
class TypeConstraint {
|
||||
/// The lower bound of the type being constrained. This bound must be a
|
||||
/// subtype of the type being constrained.
|
||||
DartType lower = const UnknownType();
|
||||
DartType lower;
|
||||
|
||||
/// The upper bound of the type being constrained. The type being constrained
|
||||
/// must be a subtype of this bound.
|
||||
DartType upper = const UnknownType();
|
||||
DartType upper;
|
||||
|
||||
TypeConstraint()
|
||||
: lower = const UnknownType(),
|
||||
upper = const UnknownType();
|
||||
|
||||
TypeConstraint._(this.lower, this.upper);
|
||||
|
||||
TypeConstraint clone() => new TypeConstraint._(lower, upper);
|
||||
|
||||
String toString() =>
|
||||
'${typeSchemaToString(lower)} <: <type> <: ${typeSchemaToString(upper)}';
|
||||
|
@ -149,6 +157,104 @@ class TypeSchemaEnvironment extends TypeEnvironment {
|
|||
return const DynamicType();
|
||||
}
|
||||
|
||||
/// Use the given [constraints] to substitute for type variables in
|
||||
/// [genericType].
|
||||
///
|
||||
/// [typeParametersToInfer] is the set of type parameters that should be
|
||||
/// substituted for. [typesFromDownwardsInference] should be a list of the
|
||||
/// same length, initially filled with `null`.
|
||||
///
|
||||
/// If [downwardsInferPhase] is `true`, then we are in the first pass of
|
||||
/// inference, pushing context types down. This means we are allowed to push
|
||||
/// down `?` to precisely represent an unknown type. Also, any types that are
|
||||
/// inferred during this stage will be stored in [typesFromDownwardsInference]
|
||||
/// for later use.
|
||||
///
|
||||
/// If [downwardsInferPhase] is `false`, then we are in the second pass of
|
||||
/// inference, and must not conclude `?` for any type formal. In this pass,
|
||||
/// values will be read from [typesFromDownwardsInference] to use as a
|
||||
/// starting point for inference.
|
||||
DartType inferTypeFromConstraints(
|
||||
Map<TypeParameter, TypeConstraint> constraints,
|
||||
DartType genericType,
|
||||
List<TypeParameter> typeParametersToInfer,
|
||||
List<DartType> typesFromDownwardsInference,
|
||||
{bool downwardsInferPhase: false}) {
|
||||
// Initialize the inferred type array.
|
||||
//
|
||||
// In the downwards phase, they all start as `?` to offer reasonable
|
||||
// degradation for f-bounded type parameters.
|
||||
var inferredTypes = new List<DartType>.filled(
|
||||
typeParametersToInfer.length, const UnknownType());
|
||||
|
||||
for (int i = 0; i < typeParametersToInfer.length; i++) {
|
||||
TypeParameter typeParam = typeParametersToInfer[i];
|
||||
|
||||
var typeParamBound = typeParam.bound;
|
||||
DartType extendsConstraint;
|
||||
if (!_isObjectOrDynamic(typeParamBound)) {
|
||||
extendsConstraint = Substitution
|
||||
.fromPairs(typeParametersToInfer, inferredTypes)
|
||||
.substituteType(typeParamBound);
|
||||
}
|
||||
|
||||
var constraint = constraints[typeParam];
|
||||
if (downwardsInferPhase) {
|
||||
typesFromDownwardsInference[i] = inferredTypes[i] =
|
||||
_inferTypeParameterFromContext(constraint, extendsConstraint);
|
||||
} else {
|
||||
inferredTypes[i] = _inferTypeParameterFromAll(
|
||||
typesFromDownwardsInference[i], constraint, extendsConstraint);
|
||||
}
|
||||
}
|
||||
|
||||
// If the downwards infer phase has failed, we'll catch this in the upwards
|
||||
// phase later on.
|
||||
if (downwardsInferPhase) {
|
||||
return Substitution
|
||||
.fromPairs(typeParametersToInfer, inferredTypes)
|
||||
.substituteType(genericType);
|
||||
}
|
||||
|
||||
// Check the inferred types against all of the constraints.
|
||||
var knownTypes = <TypeParameter, DartType>{};
|
||||
for (int i = 0; i < typeParametersToInfer.length; i++) {
|
||||
TypeParameter typeParam = typeParametersToInfer[i];
|
||||
var constraint = constraints[typeParam];
|
||||
var typeParamBound = Substitution
|
||||
.fromPairs(typeParametersToInfer, inferredTypes)
|
||||
.substituteType(typeParam.bound);
|
||||
|
||||
var inferred = inferredTypes[i];
|
||||
bool success = typeSatisfiesConstraint(inferred, constraint);
|
||||
if (success && !_isObjectOrDynamic(typeParamBound)) {
|
||||
// If everything else succeeded, check the `extends` constraint.
|
||||
var extendsConstraint = typeParamBound;
|
||||
success = isSubtypeOf(inferred, extendsConstraint);
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
// TODO(paulberry): report error.
|
||||
|
||||
// Heuristic: even if we failed, keep the erroneous type.
|
||||
// It should satisfy at least some of the constraints (e.g. the return
|
||||
// context). If we fall back to instantiateToBounds, we'll typically get
|
||||
// more errors (e.g. because `dynamic` is the most common bound).
|
||||
}
|
||||
|
||||
if (isKnown(inferred)) {
|
||||
knownTypes[typeParam] = inferred;
|
||||
}
|
||||
}
|
||||
|
||||
// Use instantiate to bounds to finish things off.
|
||||
var result = instantiateToBounds(genericType, knownTypes: knownTypes);
|
||||
|
||||
// TODO(paulberry): report any errors from instantiateToBounds.
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Given a [DartType] [type], if [type] is an uninstantiated
|
||||
/// parameterized type then instantiate the parameters to their
|
||||
/// bounds. See the issue for the algorithm description.
|
||||
|
@ -401,6 +507,44 @@ class TypeSchemaEnvironment extends TypeEnvironment {
|
|||
requiredParameterCount: requiredParameterCount);
|
||||
}
|
||||
|
||||
DartType _inferTypeParameterFromAll(DartType typeFromContextInference,
|
||||
TypeConstraint constraint, DartType extendsConstraint) {
|
||||
// See if we already fixed this type from downwards inference.
|
||||
// If so, then we aren't allowed to change it based on argument types.
|
||||
if (isKnown(typeFromContextInference)) {
|
||||
return typeFromContextInference;
|
||||
}
|
||||
|
||||
if (extendsConstraint != null) {
|
||||
constraint = constraint.clone();
|
||||
addUpperBound(constraint, extendsConstraint);
|
||||
}
|
||||
|
||||
return solveTypeConstraint(constraint, grounded: true);
|
||||
}
|
||||
|
||||
DartType _inferTypeParameterFromContext(
|
||||
TypeConstraint constraint, DartType extendsConstraint) {
|
||||
DartType t = solveTypeConstraint(constraint);
|
||||
if (!isKnown(t)) {
|
||||
return t;
|
||||
}
|
||||
|
||||
// If we're about to make our final choice, apply the extends clause.
|
||||
// This gives us a chance to refine the choice, in case it would violate
|
||||
// the `extends` clause. For example:
|
||||
//
|
||||
// Object obj = math.min/*<infer Object, error>*/(1, 2);
|
||||
//
|
||||
// If we consider the `T extends num` we conclude `<num>`, which works.
|
||||
if (extendsConstraint != null) {
|
||||
constraint = constraint.clone();
|
||||
addUpperBound(constraint, extendsConstraint);
|
||||
return solveTypeConstraint(constraint);
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
DartType _interfaceLeastUpperBound(InterfaceType type1, InterfaceType type2) {
|
||||
// This currently does not implement a very complete least upper bound
|
||||
// algorithm, but handles a couple of the very common cases that are
|
||||
|
|
|
@ -259,6 +259,75 @@ class TypeSchemaEnvironmentTest {
|
|||
expect(env.getGreatestLowerBound(A, B), same(bottomType));
|
||||
}
|
||||
|
||||
void test_inferTypeFromConstraints_applyBound() {
|
||||
// class A<T extends num> {}
|
||||
var T = new TypeParameter('T', numType);
|
||||
var A = _addClass(_class('A', typeParameters: [T])).thisType;
|
||||
var env = _makeEnv();
|
||||
{
|
||||
// With no constraints:
|
||||
var constraints = {T: new TypeConstraint()};
|
||||
// Downward inference should infer A<?>
|
||||
var typesFromDownwardsInference = <DartType>[null];
|
||||
expect(
|
||||
env.inferTypeFromConstraints(
|
||||
constraints, A, [T], typesFromDownwardsInference,
|
||||
downwardsInferPhase: true),
|
||||
new InterfaceType(A.classNode, [unknownType]));
|
||||
expect(typesFromDownwardsInference[0], unknownType);
|
||||
// Upward inference should infer A<num>
|
||||
expect(
|
||||
env.inferTypeFromConstraints(
|
||||
constraints, A, [T], typesFromDownwardsInference),
|
||||
new InterfaceType(A.classNode, [numType]));
|
||||
}
|
||||
{
|
||||
// With an upper bound of Object:
|
||||
var constraints = {T: _makeConstraint(upper: objectType)};
|
||||
// Downward inference should infer A<num>
|
||||
var typesFromDownwardsInference = <DartType>[null];
|
||||
expect(
|
||||
env.inferTypeFromConstraints(
|
||||
constraints, A, [T], typesFromDownwardsInference,
|
||||
downwardsInferPhase: true),
|
||||
new InterfaceType(A.classNode, [numType]));
|
||||
expect(typesFromDownwardsInference[0], numType);
|
||||
// Upward inference should infer A<num>
|
||||
expect(
|
||||
env.inferTypeFromConstraints(
|
||||
constraints, A, [T], typesFromDownwardsInference),
|
||||
new InterfaceType(A.classNode, [numType]));
|
||||
// Upward inference should still infer A<num> even if there are more
|
||||
// constraints now, because num was finalized during downward inference.
|
||||
constraints = {T: _makeConstraint(lower: intType, upper: intType)};
|
||||
expect(
|
||||
env.inferTypeFromConstraints(
|
||||
constraints, A, [T], typesFromDownwardsInference),
|
||||
new InterfaceType(A.classNode, [numType]));
|
||||
}
|
||||
}
|
||||
|
||||
void test_inferTypeFromConstraints_simple() {
|
||||
var env = _makeEnv();
|
||||
var T = listClass.typeParameters[0];
|
||||
// With an upper bound of List<?>:
|
||||
var constraints = {T: _makeConstraint(upper: _list(unknownType))};
|
||||
// Downwards inference should infer List<List<?>>
|
||||
var typesFromDownwardsInference = <DartType>[null];
|
||||
expect(
|
||||
env.inferTypeFromConstraints(
|
||||
constraints, listClass.thisType, [T], typesFromDownwardsInference,
|
||||
downwardsInferPhase: true),
|
||||
_list(_list(unknownType)));
|
||||
// And it should have recorded List<?> as the type inferred for T.
|
||||
expect(typesFromDownwardsInference[0], _list(unknownType));
|
||||
// Upwards inference should refine that to List<List<Null>>
|
||||
expect(
|
||||
env.inferTypeFromConstraints(
|
||||
constraints, listClass.thisType, [T], typesFromDownwardsInference),
|
||||
_list(_list(nullType)));
|
||||
}
|
||||
|
||||
void test_instantiateToBounds_noTypesKnown() {
|
||||
// class A {}
|
||||
var A = _addClass(_class('A')).rawType;
|
||||
|
|
Loading…
Reference in a new issue