Skip to content

Commit 1ffba32

Browse files
stereotype441Commit Bot
authored and
Commit Bot
committed
Rework type inference logic to avoid redundant constraint gathering.
Previously, the analyzer would perform downwards and upwards inference using separate GenericInferrer objects. This meant that any constraint gathering work performed during downwards inference had to be repeated during upwards inference. This change avoids the extra work by using a single GenericInferrer object for both downwards and upwards inference. It also cleans up the API for GenericInferrer a bit. Change-Id: Idc5deed96c18ffc89aeb8ba4e5e95942843dae11 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/236660 Reviewed-by: Samuel Rawlins <[email protected]> Commit-Queue: Paul Berry <[email protected]>
1 parent d0a1404 commit 1ffba32

14 files changed

+308
-336
lines changed

pkg/analyzer/lib/src/dart/element/generic_inferrer.dart

+122-101
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,34 @@ class GenericInferrer {
5656
final Set<TypeParameterElement> _typeParameters = Set.identity();
5757
final Map<TypeParameterElement, List<_TypeConstraint>> _constraints = {};
5858

59-
GenericInferrer(
60-
this._typeSystem,
61-
Iterable<TypeParameterElement> typeFormals,
62-
) {
63-
_typeParameters.addAll(typeFormals);
64-
for (var formal in typeFormals) {
59+
/// The list of type parameters being inferred.
60+
final List<TypeParameterElement> _typeFormals;
61+
62+
/// Indicates whether type parameter bounds should be included in constraints.
63+
final bool considerExtendsClause;
64+
65+
/// The [ErrorReporter] to which inference errors should be reported, or
66+
/// `null` if errors shouldn't be reported.
67+
final ErrorReporter? errorReporter;
68+
69+
/// The [AstNode] to which errors should be attached. May be `null` if errors
70+
/// are not being reported (that is, if [errorReporter] is also `null`).
71+
final AstNode? errorNode;
72+
73+
/// Indicates whether the "generic metadata" feature is enabled. When it is,
74+
/// type arguments are allowed to be instantiated with generic function types.
75+
final bool genericMetadataIsEnabled;
76+
77+
GenericInferrer(this._typeSystem, this._typeFormals,
78+
{this.considerExtendsClause = true,
79+
this.errorReporter,
80+
this.errorNode,
81+
required this.genericMetadataIsEnabled}) {
82+
if (errorReporter != null) {
83+
assert(errorNode != null);
84+
}
85+
_typeParameters.addAll(_typeFormals);
86+
for (var formal in _typeFormals) {
6587
_constraints[formal] = [];
6688
}
6789
}
@@ -85,6 +107,24 @@ class GenericInferrer {
85107
tryMatchSubtypeOf(argumentType, parameterType, origin, covariant: false);
86108
}
87109

110+
/// Applies all the argument constraints implied by [parameters] and
111+
/// [argumentTypes].
112+
void constrainArguments(
113+
{ClassElement? genericClass,
114+
required List<ParameterElement> parameters,
115+
required List<DartType> argumentTypes}) {
116+
for (int i = 0; i < argumentTypes.length; i++) {
117+
// Try to pass each argument to each parameter, recording any type
118+
// parameter bounds that were implied by this assignment.
119+
constrainArgument(
120+
argumentTypes[i],
121+
parameters[i].type,
122+
parameters[i].name,
123+
genericClass: genericClass,
124+
);
125+
}
126+
}
127+
88128
/// Constrain a universal function type [fnType] used in a context
89129
/// [contextType].
90130
void constrainGenericFunctionInContext(
@@ -117,67 +157,46 @@ class GenericInferrer {
117157
tryMatchSubtypeOf(declaredType, contextType, origin, covariant: true);
118158
}
119159

120-
/// Given the constraints that were given by calling [constrainArgument] and
121-
/// [constrainReturnType], find the type arguments for the [typeFormals] that
122-
/// satisfies these constraints.
123-
///
124-
/// If [downwardsInferPhase] is set, we are in the first pass of inference,
125-
/// pushing context types down. At that point we are allowed to push down
126-
/// `_` to precisely represent an unknown type. If [downwardsInferPhase] is
127-
/// false, we are on our final inference pass, have all available information
128-
/// including argument types, and must not conclude `_` for any type formal.
129-
List<DartType>? infer(
130-
List<TypeParameterElement> typeFormals, {
131-
bool considerExtendsClause = true,
132-
ErrorReporter? errorReporter,
133-
AstNode? errorNode,
134-
bool failAtError = false,
135-
bool downwardsInferPhase = false,
136-
required bool genericMetadataIsEnabled,
137-
}) {
138-
// Initialize the inferred type array.
139-
//
140-
// In the downwards phase, they all start as `_` to offer reasonable
141-
// degradation for f-bounded type parameters.
142-
var inferredTypes =
143-
List<DartType>.filled(typeFormals.length, UnknownInferredType.instance);
160+
/// Performs downwards inference, producing a set of inferred types that may
161+
/// contain references to the "unknown type".
162+
List<DartType> downwardsInfer() => _chooseTypes(downwardsInferPhase: true);
144163

145-
for (int i = 0; i < typeFormals.length; i++) {
146-
// TODO (kallentu) : Clean up TypeParameterElementImpl casting once
147-
// variance is added to the interface.
148-
var typeParam = typeFormals[i] as TypeParameterElementImpl;
149-
_TypeConstraint? extendsClause;
150-
var bound = typeParam.bound;
151-
if (considerExtendsClause && bound != null) {
152-
extendsClause = _TypeConstraint.fromExtends(
153-
typeParam,
154-
bound,
155-
Substitution.fromPairs(typeFormals, inferredTypes)
156-
.substituteType(bound),
157-
isNonNullableByDefault: isNonNullableByDefault,
158-
);
164+
/// Tries to make [i1] a subtype of [i2] and accumulate constraints as needed.
165+
///
166+
/// The return value indicates whether the match was successful. If it was
167+
/// unsuccessful, any constraints that were accumulated during the match
168+
/// attempt have been rewound (see [_rewindConstraints]).
169+
bool tryMatchSubtypeOf(DartType t1, DartType t2, _TypeConstraintOrigin origin,
170+
{required bool covariant}) {
171+
var gatherer = TypeConstraintGatherer(
172+
typeSystem: _typeSystem, typeParameters: _typeParameters);
173+
var success = gatherer.trySubtypeMatch(t1, t2, !covariant);
174+
if (success) {
175+
var constraints = gatherer.computeConstraints();
176+
for (var entry in constraints.entries) {
177+
if (!entry.value.isEmpty) {
178+
var constraint = _constraints[entry.key]!;
179+
constraint.add(
180+
_TypeConstraint(origin, entry.key,
181+
lower: entry.value.lower, upper: entry.value.upper),
182+
);
183+
}
159184
}
160-
161-
var constraints = _constraints[typeParam]!;
162-
inferredTypes[i] = downwardsInferPhase
163-
? _inferTypeParameterFromContext(constraints, extendsClause,
164-
isContravariant: typeParam.variance.isContravariant)
165-
: _inferTypeParameterFromAll(constraints, extendsClause,
166-
isContravariant: typeParam.variance.isContravariant,
167-
preferUpwardsInference: !typeParam.isLegacyCovariant);
168185
}
169186

170-
// If the downwards infer phase has failed, we'll catch this in the upwards
171-
// phase later on.
172-
if (downwardsInferPhase) {
173-
return inferredTypes;
174-
}
187+
return success;
188+
}
175189

190+
/// Same as [upwardsInfer], but if [failAtError] is `true` (the default) and
191+
/// inference fails, returns `null` rather than trying to perform error
192+
/// recovery.
193+
List<DartType>? tryUpwardsInfer({bool failAtError = true}) {
194+
var inferredTypes = _chooseTypes(downwardsInferPhase: false);
176195
// Check the inferred types against all of the constraints.
177196
var knownTypes = <TypeParameterElement, DartType>{};
178197
var hasErrorReported = false;
179-
for (int i = 0; i < typeFormals.length; i++) {
180-
TypeParameterElement parameter = typeFormals[i];
198+
for (int i = 0; i < _typeFormals.length; i++) {
199+
TypeParameterElement parameter = _typeFormals[i];
181200
var constraints = _constraints[parameter]!;
182201

183202
var inferred = inferredTypes[i];
@@ -189,7 +208,7 @@ class GenericInferrer {
189208
var parameterBoundRaw = parameter.bound;
190209
if (parameterBoundRaw != null) {
191210
var parameterBound =
192-
Substitution.fromPairs(typeFormals, inferredTypes)
211+
Substitution.fromPairs(_typeFormals, inferredTypes)
193212
.substituteType(parameterBoundRaw);
194213
parameterBound = _toLegacyElementIfOptOut(parameterBound);
195214
var extendsConstraint = _TypeConstraint.fromExtends(
@@ -225,7 +244,7 @@ class GenericInferrer {
225244
hasErrorReported = true;
226245
var typeFormals = inferred.typeFormals;
227246
var typeFormalsStr = typeFormals.map(_elementStr).join(', ');
228-
errorReporter.reportErrorForNode(
247+
errorReporter!.reportErrorForNode(
229248
CompileTimeErrorCode.COULD_NOT_INFER, errorNode!, [
230249
parameter.name,
231250
' Inferred candidate type ${_typeStr(inferred)} has type parameters'
@@ -250,17 +269,17 @@ class GenericInferrer {
250269
}
251270

252271
// Use instantiate to bounds to finish things off.
253-
var hasError = List<bool>.filled(typeFormals.length, false);
254-
var result = _typeSystem.instantiateTypeFormalsToBounds(typeFormals,
272+
var hasError = List<bool>.filled(_typeFormals.length, false);
273+
var result = _typeSystem.instantiateTypeFormalsToBounds(_typeFormals,
255274
hasError: hasError, knownTypes: knownTypes);
256275

257276
// Report any errors from instantiateToBounds.
258277
for (int i = 0; i < hasError.length; i++) {
259278
if (hasError[i]) {
260279
if (failAtError) return null;
261280
hasErrorReported = true;
262-
TypeParameterElement typeParam = typeFormals[i];
263-
var typeParamBound = Substitution.fromPairs(typeFormals, inferredTypes)
281+
TypeParameterElement typeParam = _typeFormals[i];
282+
var typeParamBound = Substitution.fromPairs(_typeFormals, inferredTypes)
264283
.substituteType(typeParam.bound ?? typeProvider.objectType);
265284
// TODO(jmesserly): improve this error message.
266285
errorReporter?.reportErrorForNode(
@@ -277,7 +296,6 @@ class GenericInferrer {
277296
_checkArgumentsNotMatchingBounds(
278297
errorNode: errorNode,
279298
errorReporter: errorReporter,
280-
typeParameters: typeFormals,
281299
typeArguments: result,
282300
);
283301
}
@@ -286,47 +304,18 @@ class GenericInferrer {
286304
return result;
287305
}
288306

289-
/// Tries to make [i1] a subtype of [i2] and accumulate constraints as needed.
290-
///
291-
/// The return value indicates whether the match was successful. If it was
292-
/// unsuccessful, any constraints that were accumulated during the match
293-
/// attempt have been rewound (see [_rewindConstraints]).
294-
bool tryMatchSubtypeOf(DartType t1, DartType t2, _TypeConstraintOrigin origin,
295-
{required bool covariant}) {
296-
var gatherer = TypeConstraintGatherer(
297-
typeSystem: _typeSystem,
298-
typeParameters: _typeParameters,
299-
);
300-
var success = gatherer.trySubtypeMatch(t1, t2, !covariant);
301-
if (success) {
302-
var constraints = gatherer.computeConstraints();
303-
for (var entry in constraints.entries) {
304-
if (!entry.value.isEmpty) {
305-
var constraint = _constraints[entry.key]!;
306-
constraint.add(
307-
_TypeConstraint(
308-
origin,
309-
entry.key,
310-
lower: entry.value.lower,
311-
upper: entry.value.upper,
312-
),
313-
);
314-
}
315-
}
316-
}
317-
318-
return success;
319-
}
307+
/// Performs upwards inference, producing a final set of inferred types that
308+
/// does not contain references to the "unknown type".
309+
List<DartType> upwardsInfer() => tryUpwardsInfer(failAtError: false)!;
320310

321311
/// Check that inferred [typeArguments] satisfy the [typeParameters] bounds.
322312
void _checkArgumentsNotMatchingBounds({
323313
required AstNode? errorNode,
324314
required ErrorReporter? errorReporter,
325-
required List<TypeParameterElement> typeParameters,
326315
required List<DartType> typeArguments,
327316
}) {
328-
for (int i = 0; i < typeParameters.length; i++) {
329-
var parameter = typeParameters[i];
317+
for (int i = 0; i < _typeFormals.length; i++) {
318+
var parameter = _typeFormals[i];
330319
var argument = typeArguments[i];
331320

332321
var rawBound = parameter.bound;
@@ -335,7 +324,7 @@ class GenericInferrer {
335324
}
336325
rawBound = _typeSystem.toLegacyTypeIfOptOut(rawBound);
337326

338-
var substitution = Substitution.fromPairs(typeParameters, typeArguments);
327+
var substitution = Substitution.fromPairs(_typeFormals, typeArguments);
339328
var bound = substitution.substituteType(rawBound);
340329
if (!_typeSystem.isSubtypeOf(argument, bound)) {
341330
errorReporter?.reportErrorForNode(
@@ -438,6 +427,38 @@ class GenericInferrer {
438427
}
439428
}
440429

430+
/// Computes (or recomputes) a set of [inferredTypes] based on the constraints
431+
/// that have been recorded so far.
432+
List<DartType> _chooseTypes({required bool downwardsInferPhase}) {
433+
var inferredTypes = List<DartType>.filled(
434+
_typeFormals.length, UnknownInferredType.instance);
435+
for (int i = 0; i < _typeFormals.length; i++) {
436+
// TODO (kallentu) : Clean up TypeParameterElementImpl casting once
437+
// variance is added to the interface.
438+
var typeParam = _typeFormals[i] as TypeParameterElementImpl;
439+
_TypeConstraint? extendsClause;
440+
var bound = typeParam.bound;
441+
if (considerExtendsClause && bound != null) {
442+
extendsClause = _TypeConstraint.fromExtends(
443+
typeParam,
444+
bound,
445+
Substitution.fromPairs(_typeFormals, inferredTypes)
446+
.substituteType(bound),
447+
isNonNullableByDefault: isNonNullableByDefault);
448+
}
449+
450+
var constraints = _constraints[typeParam]!;
451+
inferredTypes[i] = downwardsInferPhase
452+
? _inferTypeParameterFromContext(constraints, extendsClause,
453+
isContravariant: typeParam.variance.isContravariant)
454+
: _inferTypeParameterFromAll(constraints, extendsClause,
455+
isContravariant: typeParam.variance.isContravariant,
456+
preferUpwardsInference: !typeParam.isLegacyCovariant);
457+
}
458+
459+
return inferredTypes;
460+
}
461+
441462
String _elementStr(Element element) {
442463
return element.getDisplayString(withNullability: isNonNullableByDefault);
443464
}

0 commit comments

Comments
 (0)