Do an extra round of type inference before resolving deferred closures.

The order of operations for type inference of a generic invocation is
now:

1. Create some constraints on type parameters by trying to match the
   return type of the invocation target as a subtype of the incoming
   type context.  (For a constructor invocation, the return type of
   the invocation target is considered the raw uninstantiated type of
   the class enclosing the constructor declaration.)

2. Downwards inference: partially solve the set of type constraints
   accumulated in step 1, to produce a preliminary mapping of type
   parameters to type schemas.

3. Recursively infer all arguments to the invocation, except that if
   experimental feature `inference-update-1` is enabled, skip any
   arguments that are function literals (a.k.a. "closures").  Obtain
   the type contexts for the recursive inference by substituting the
   preliminary mapping (from step 2) into the corresponding parameter
   types of the invocation target.  For each argument that is
   recursively inferred, create additional constraints on type
   parameters using the resulting static type.

4. If no arguments were skipped during step 3, go to step 7 (this
   always happens if `inference-update-1` is disabled).

5. Horizontal inference: partially solve the set of type constraints
   accumulated so far, to produce an updated preliminary mapping of
   type parameters to type schemas.

6. Recursively infer all of the invocation arguments that were
   previously skipped.  As in step 3, obtain the type contexts for the
   recursive inference by substituting the preliminary mapping (this
   time from step 5) into the corresponding parameter types of the
   invocation target.  Again, for each argument that is recursively
   inferred, create additional constraints on type parameters using
   the resulting static type.

7. Upwards inference: solve the set of type constraints accumulated so
   far, to produce a final mapping of type parameters to types.  Check
   that each type is a subtype of the bound of its corresponding type
   parameter.

8. Check that the static type of each argument is assignable to the
   type obtained by substituting the final mapping (from step 7) into
   the corresponding parameter type of the invocation target.

9. Finally, obtain the static type of the invocation by substituting
   the final mapping (from step 7) into the return type of the
   invocation target.

This addresses simpler cases of
https://github.com/dart-lang/language/issues/731.  Note that if
experimental flag `inference-update-1` is disabled, the behavior is
unchanged.

Note that steps 2 and 5 use the same algorithm as each other (they
only differ in how many type constraints have been accumulated so
far), so I've renamed the function that performs it from
`downwardsInfer` to `partialInfer`.

Change-Id: I10d3288d4f4ba9e2b6bc18409186ddc67ca2ee9d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/238881
Reviewed-by: Samuel Rawlins <srawlins@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
This commit is contained in:
Paul Berry
2022-03-25 22:07:50 +00:00
committed by Commit Bot
parent 2a1dba465c
commit 83bea5cfbb
6 changed files with 196 additions and 11 deletions
@@ -178,15 +178,15 @@ class GenericInferrer {
_tryMatchSubtypeOf(declaredType, contextType, origin, covariant: true);
}
/// Performs downwards inference, producing a set of inferred types that may
/// contain references to the "unknown type".
List<DartType> downwardsInfer() => _chooseTypes(downwardsInferPhase: true);
/// Performs partial (either downwards or horizontal) inference, producing a
/// set of inferred types that may contain references to the "unknown type".
List<DartType> partialInfer() => _chooseTypes(partial: true);
/// Same as [upwardsInfer], but if [failAtError] is `true` (the default) and
/// inference fails, returns `null` rather than trying to perform error
/// recovery.
List<DartType>? tryUpwardsInfer({bool failAtError = true}) {
var inferredTypes = _chooseTypes(downwardsInferPhase: false);
var inferredTypes = _chooseTypes(partial: false);
// Check the inferred types against all of the constraints.
var knownTypes = <TypeParameterElement, DartType>{};
var hasErrorReported = false;
@@ -424,7 +424,7 @@ class GenericInferrer {
/// Computes (or recomputes) a set of [inferredTypes] based on the constraints
/// that have been recorded so far.
List<DartType> _chooseTypes({required bool downwardsInferPhase}) {
List<DartType> _chooseTypes({required bool partial}) {
var inferredTypes = List<DartType>.filled(
_typeFormals.length, UnknownInferredType.instance);
for (int i = 0; i < _typeFormals.length; i++) {
@@ -443,7 +443,7 @@ class GenericInferrer {
}
var constraints = _constraints[typeParam]!;
if (downwardsInferPhase) {
if (partial) {
var inferredType = _inferTypeParameterFromContext(
constraints, extendsClause,
isContravariant: typeParam.variance.isContravariant);
@@ -157,8 +157,8 @@ abstract class FullInvocationInferrer<Node extends AstNodeImpl>
genericMetadataIsEnabled: resolver.genericMetadataIsEnabled,
);
substitution = Substitution.fromPairs(
rawType.typeFormals, inferrer.downwardsInfer());
substitution =
Substitution.fromPairs(rawType.typeFormals, inferrer.partialInfer());
}
List<EqualityInfo<PromotableElement, DartType>?>? identicalInfo =
@@ -174,6 +174,10 @@ abstract class FullInvocationInferrer<Node extends AstNodeImpl>
substitution: substitution,
inferrer: inferrer);
if (deferredClosures != null) {
if (inferrer != null) {
substitution = Substitution.fromPairs(
rawType!.typeFormals, inferrer.partialInfer());
}
_resolveDeferredClosures(
resolver: resolver,
node: node,
@@ -112,7 +112,7 @@ class TypedLiteralResolver {
} else {
inferrer = _inferListTypeDownwards(node, contextType: contextType);
if (contextType != null) {
var typeArguments = inferrer.downwardsInfer();
var typeArguments = inferrer.partialInfer();
listType = _typeProvider.listElement.instantiate(
typeArguments: typeArguments, nullabilitySuffix: _noneOrStarSuffix);
}
@@ -146,7 +146,7 @@ class TypedLiteralResolver {
} else {
inferrer = _inferSetTypeDownwards(node, literalResolution.contextType);
if (literalResolution.contextType != null) {
var typeArguments = inferrer.downwardsInfer();
var typeArguments = inferrer.partialInfer();
literalType = _typeProvider.setElement.instantiate(
typeArguments: typeArguments,
nullabilitySuffix: _noneOrStarSuffix);
@@ -160,7 +160,7 @@ class TypedLiteralResolver {
} else {
inferrer = _inferMapTypeDownwards(node, literalResolution.contextType);
if (literalResolution.contextType != null) {
var typeArguments = inferrer.downwardsInfer();
var typeArguments = inferrer.partialInfer();
literalType = _typeProvider.mapElement.instantiate(
typeArguments: typeArguments,
nullabilitySuffix: _noneOrStarSuffix);
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
import 'package:analyzer/src/dart/analysis/experiments.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../context_collection_resolution.dart';
@@ -41,6 +43,82 @@ test() => identical(() {}, () {});
// lead to a crash.
}
test_fold_inference() async {
var code = '''
example(List<int> list) {
var a = list.fold(0, (x, y) => x + y);
}
''';
if (_isEnabled) {
await assertErrorsInCode(code, [
error(HintCode.UNUSED_LOCAL_VARIABLE, 32, 1),
]);
assertType(findElement.localVar('a').type, 'int');
assertType(findElement.parameter('x').type, 'int');
assertType(findElement.parameter('y').type, 'int');
expect(
findNode.binary('x + y').staticElement!.enclosingElement.name, 'num');
} else {
await assertErrorsInCode(code, [
error(HintCode.UNUSED_LOCAL_VARIABLE, 32, 1),
error(
CompileTimeErrorCode
.UNCHECKED_OPERATOR_INVOCATION_OF_NULLABLE_VALUE,
61,
1),
]);
}
}
test_horizontal_inference_propagate_to_return_type() async {
await assertErrorsInCode('''
U f<T, U>(T t, U Function(T) g) => throw '';
test() {
var a = f(0, (x) => [x]);
}
''', [
error(HintCode.UNUSED_LOCAL_VARIABLE, 60, 1),
]);
assertType(findNode.methodInvocation('f(').typeArgumentTypes![0], 'int');
assertType(findNode.methodInvocation('f(').typeArgumentTypes![1],
_isEnabled ? 'List<int>' : 'List<Object?>');
assertType(
findNode.methodInvocation('f(').staticInvokeType,
_isEnabled
? 'List<int> Function(int, List<int> Function(int))'
: 'List<Object?> Function(int, List<Object?> Function(int))');
assertType(findNode.simpleParameter('x)').declaredElement!.type,
_isEnabled ? 'int' : 'Object?');
assertType(findNode.variableDeclaration('a =').declaredElement!.type,
_isEnabled ? 'List<int>' : 'List<Object?>');
}
test_horizontal_inference_simple() async {
await assertNoErrorsInCode('''
void f<T>(T t, void Function(T) g) {}
test() => f(0, (x) {});
''');
assertType(
findNode.methodInvocation('f(').typeArgumentTypes!.single, 'int');
assertType(findNode.methodInvocation('f(').staticInvokeType,
'void Function(int, void Function(int))');
assertType(findNode.simpleParameter('x').declaredElement!.type,
_isEnabled ? 'int' : 'Object?');
}
test_horizontal_inference_simple_named() async {
await assertNoErrorsInCode('''
void f<T>({required T t, required void Function(T) g}) {}
test() => f(t: 0, g: (x) {});
''');
assertType(
findNode.methodInvocation('f(').typeArgumentTypes!.single, 'int');
assertType(findNode.methodInvocation('f(').staticInvokeType,
'void Function({required void Function(int) g, required int t})');
assertType(findNode.simpleParameter('x').declaredElement!.type,
_isEnabled ? 'int' : 'Object?');
}
test_write_capture_deferred() async {
await assertNoErrorsInCode('''
test(int? i) {
@@ -0,0 +1,47 @@
// Copyright (c) 2022, 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.
// Tests that when the feature is disabled, inferred types do not flow
// "horizontally" from a non-closure argument of an invocation to a closure
// argument.
// @dart=2.17
import '../static_type_helper.dart';
testLaterUnnamedParameter(void Function<T>(T, void Function(T)) f) {
f(0, (x) {
x.expectStaticType<Exactly<Object?>>();
});
}
testEarlierUnnamedParameter(void Function<T>(void Function(T), T) f) {
f((x) {
x.expectStaticType<Exactly<Object?>>();
}, 0);
}
testLaterNamedParameter(
void Function<T>({required T a, required void Function(T) b}) f) {
f(
a: 0,
b: (x) {
x.expectStaticType<Exactly<Object?>>();
});
}
testEarlierNamedParameter(
void Function<T>({required void Function(T) a, required T b}) f) {
f(
a: (x) {
x.expectStaticType<Exactly<Object?>>();
},
b: 0);
}
testPropagateToReturnType(U Function<T, U>(T, U Function(T)) f) {
f(0, (x) => [x]).expectStaticType<Exactly<List<Object?>>>();
}
main() {}
@@ -0,0 +1,56 @@
// Copyright (c) 2022, 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.
// Tests that when the feature is enabled, inferred types can flow
// "horizontally" from a non-closure argument of an invocation to a closure
// argument.
// SharedOptions=--enable-experiment=inference-update-1
import '../static_type_helper.dart';
testLaterUnnamedParameter(void Function<T>(T, void Function(T)) f) {
f(0, (x) {
x.expectStaticType<Exactly<int>>();
});
}
testEarlierUnnamedParameter(void Function<T>(void Function(T), T) f) {
f((x) {
x.expectStaticType<Exactly<int>>();
}, 0);
}
testLaterNamedParameter(
void Function<T>({required T a, required void Function(T) b}) f) {
f(
a: 0,
b: (x) {
x.expectStaticType<Exactly<int>>();
});
}
testEarlierNamedParameter(
void Function<T>({required void Function(T) a, required T b}) f) {
f(
a: (x) {
x.expectStaticType<Exactly<int>>();
},
b: 0);
}
testPropagateToReturnType(U Function<T, U>(T, U Function(T)) f) {
f(0, (x) => [x]).expectStaticType<Exactly<List<int>>>();
}
testFold(List<int> list) {
var a = list.fold(
0,
(x, y) =>
(x..expectStaticType<Exactly<int>>()) +
(y..expectStaticType<Exactly<int>>()));
a.expectStaticType<Exactly<int>>();
}
main() {}