diff --git a/pkg/analyzer/lib/src/dart/element/generic_inferrer.dart b/pkg/analyzer/lib/src/dart/element/generic_inferrer.dart index 34aee9e21be..47ca3ea8deb 100644 --- a/pkg/analyzer/lib/src/dart/element/generic_inferrer.dart +++ b/pkg/analyzer/lib/src/dart/element/generic_inferrer.dart @@ -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 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 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? 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 = {}; 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 _chooseTypes({required bool downwardsInferPhase}) { + List _chooseTypes({required bool partial}) { var inferredTypes = List.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); diff --git a/pkg/analyzer/lib/src/dart/resolver/invocation_inferrer.dart b/pkg/analyzer/lib/src/dart/resolver/invocation_inferrer.dart index 52fb27d4196..128c1cf09bc 100644 --- a/pkg/analyzer/lib/src/dart/resolver/invocation_inferrer.dart +++ b/pkg/analyzer/lib/src/dart/resolver/invocation_inferrer.dart @@ -157,8 +157,8 @@ abstract class FullInvocationInferrer genericMetadataIsEnabled: resolver.genericMetadataIsEnabled, ); - substitution = Substitution.fromPairs( - rawType.typeFormals, inferrer.downwardsInfer()); + substitution = + Substitution.fromPairs(rawType.typeFormals, inferrer.partialInfer()); } List?>? identicalInfo = @@ -174,6 +174,10 @@ abstract class FullInvocationInferrer substitution: substitution, inferrer: inferrer); if (deferredClosures != null) { + if (inferrer != null) { + substitution = Substitution.fromPairs( + rawType!.typeFormals, inferrer.partialInfer()); + } _resolveDeferredClosures( resolver: resolver, node: node, diff --git a/pkg/analyzer/lib/src/dart/resolver/typed_literal_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/typed_literal_resolver.dart index 747b7f0545a..ad94d27810d 100644 --- a/pkg/analyzer/lib/src/dart/resolver/typed_literal_resolver.dart +++ b/pkg/analyzer/lib/src/dart/resolver/typed_literal_resolver.dart @@ -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); diff --git a/pkg/analyzer/test/src/dart/resolution/type_inference/inference_update_1_test.dart b/pkg/analyzer/test/src/dart/resolution/type_inference/inference_update_1_test.dart index 209ee0fce63..be669581c53 100644 --- a/pkg/analyzer/test/src/dart/resolution/type_inference/inference_update_1_test.dart +++ b/pkg/analyzer/test/src/dart/resolution/type_inference/inference_update_1_test.dart @@ -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 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 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' : 'List'); + assertType( + findNode.methodInvocation('f(').staticInvokeType, + _isEnabled + ? 'List Function(int, List Function(int))' + : 'List Function(int, List Function(int))'); + assertType(findNode.simpleParameter('x)').declaredElement!.type, + _isEnabled ? 'int' : 'Object?'); + assertType(findNode.variableDeclaration('a =').declaredElement!.type, + _isEnabled ? 'List' : 'List'); + } + + test_horizontal_inference_simple() async { + await assertNoErrorsInCode(''' +void f(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({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) { diff --git a/tests/language/inference_update_1/horizontal_inference_disabled_test.dart b/tests/language/inference_update_1/horizontal_inference_disabled_test.dart new file mode 100644 index 00000000000..e335aee22c3 --- /dev/null +++ b/tests/language/inference_update_1/horizontal_inference_disabled_test.dart @@ -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, void Function(T)) f) { + f(0, (x) { + x.expectStaticType>(); + }); +} + +testEarlierUnnamedParameter(void Function(void Function(T), T) f) { + f((x) { + x.expectStaticType>(); + }, 0); +} + +testLaterNamedParameter( + void Function({required T a, required void Function(T) b}) f) { + f( + a: 0, + b: (x) { + x.expectStaticType>(); + }); +} + +testEarlierNamedParameter( + void Function({required void Function(T) a, required T b}) f) { + f( + a: (x) { + x.expectStaticType>(); + }, + b: 0); +} + +testPropagateToReturnType(U Function(T, U Function(T)) f) { + f(0, (x) => [x]).expectStaticType>>(); +} + +main() {} diff --git a/tests/language/inference_update_1/horizontal_inference_enabled_test.dart b/tests/language/inference_update_1/horizontal_inference_enabled_test.dart new file mode 100644 index 00000000000..fc18f0ff15a --- /dev/null +++ b/tests/language/inference_update_1/horizontal_inference_enabled_test.dart @@ -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, void Function(T)) f) { + f(0, (x) { + x.expectStaticType>(); + }); +} + +testEarlierUnnamedParameter(void Function(void Function(T), T) f) { + f((x) { + x.expectStaticType>(); + }, 0); +} + +testLaterNamedParameter( + void Function({required T a, required void Function(T) b}) f) { + f( + a: 0, + b: (x) { + x.expectStaticType>(); + }); +} + +testEarlierNamedParameter( + void Function({required void Function(T) a, required T b}) f) { + f( + a: (x) { + x.expectStaticType>(); + }, + b: 0); +} + +testPropagateToReturnType(U Function(T, U Function(T)) f) { + f(0, (x) => [x]).expectStaticType>>(); +} + +testFold(List list) { + var a = list.fold( + 0, + (x, y) => + (x..expectStaticType>()) + + (y..expectStaticType>())); + a.expectStaticType>(); +} + +main() {}