diff --git a/CHANGELOG.md b/CHANGELOG.md index d76e779f1b8..315e870b91f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,24 @@ * Added `WebSocket.addUtf8Text` to allow sending a pre-encoded text message without a round-trip UTF-8 conversion. +## Strong Mode + +* Breaking change - it is an error if a generic type parameter cannot be + inferred (SDK issue [26992](https://github.com/dart-lang/sdk/issues/26992)). + + ```dart + class Cup { + Cup(T t); + } + main() { + // Error because: + // - if we choose Cup it is not assignable to `cOfInt`, + // - if we choose Cup then `n` is not assignable to int. + num n; + C cOfInt = new C(n); + } + ``` + ## 1.19.0 ### Language changes diff --git a/pkg/analyzer/lib/src/generated/error.dart b/pkg/analyzer/lib/src/generated/error.dart index fd71772e0a2..83ab38ac719 100644 --- a/pkg/analyzer/lib/src/generated/error.dart +++ b/pkg/analyzer/lib/src/generated/error.dart @@ -5969,6 +5969,11 @@ class StrongModeCode extends ErrorCode { const StrongModeCode(ErrorType.COMPILE_TIME_ERROR, 'INVALID_PARAMETER_DECLARATION', _typeCheckMessage); + static const StrongModeCode COULD_NOT_INFER = const StrongModeCode( + ErrorType.COMPILE_TIME_ERROR, + 'COULD_NOT_INFER', + "Could not infer type parameter {0}, {1} must be of type {2}."); + static const StrongModeCode INFERRED_TYPE = const StrongModeCode( ErrorType.HINT, 'INFERRED_TYPE', _inferredTypeMessage); diff --git a/pkg/analyzer/lib/src/generated/static_type_analyzer.dart b/pkg/analyzer/lib/src/generated/static_type_analyzer.dart index d6787516f05..b7d45f75057 100644 --- a/pkg/analyzer/lib/src/generated/static_type_analyzer.dart +++ b/pkg/analyzer/lib/src/generated/static_type_analyzer.dart @@ -1905,8 +1905,8 @@ class StaticTypeAnalyzer extends SimpleAstVisitor { */ void _inferGenericInvocationExpression(InvocationExpression node) { ArgumentList arguments = node.argumentList; - FunctionType inferred = _inferGenericInvoke( - node, node.function.staticType, node.typeArguments, arguments); + FunctionType inferred = _inferGenericInvoke(node, node.function.staticType, + node.typeArguments, arguments, node.function); if (inferred != null && inferred != node.staticInvokeType) { // Fix up the parameter elements based on inferred method. arguments.correspondingStaticParameters = ResolverVisitor @@ -1923,8 +1923,12 @@ class StaticTypeAnalyzer extends SimpleAstVisitor { * This takes into account both the context type, as well as information from * the argument types. */ - FunctionType _inferGenericInvoke(Expression node, DartType fnType, - TypeArgumentList typeArguments, ArgumentList argumentList) { + FunctionType _inferGenericInvoke( + Expression node, + DartType fnType, + TypeArgumentList typeArguments, + ArgumentList argumentList, + AstNode errorNode) { TypeSystem ts = _typeSystem; if (typeArguments == null && fnType is FunctionType && @@ -1982,7 +1986,8 @@ class StaticTypeAnalyzer extends SimpleAstVisitor { } } return ts.inferGenericFunctionCall(_typeProvider, fnType, paramTypes, - argTypes, InferenceContext.getContext(node)); + argTypes, InferenceContext.getContext(node), + errorReporter: _resolver.errorReporter, errorNode: errorNode); } return null; } @@ -2020,8 +2025,8 @@ class StaticTypeAnalyzer extends SimpleAstVisitor { _constructorToGenericFunctionType(rawElement); ArgumentList arguments = node.argumentList; - FunctionType inferred = _inferGenericInvoke( - node, constructorType, constructor.type.typeArguments, arguments); + FunctionType inferred = _inferGenericInvoke(node, constructorType, + constructor.type.typeArguments, arguments, node.constructorName); if (inferred != null && inferred != originalElement.type) { // Fix up the parameter elements based on inferred method. diff --git a/pkg/analyzer/lib/src/generated/type_system.dart b/pkg/analyzer/lib/src/generated/type_system.dart index e7d747e1f52..b19ae14f90e 100644 --- a/pkg/analyzer/lib/src/generated/type_system.dart +++ b/pkg/analyzer/lib/src/generated/type_system.dart @@ -7,6 +7,7 @@ library analyzer.src.generated.type_system; import 'dart:collection'; import 'dart:math' as math; +import 'package:analyzer/dart/ast/ast.dart' show AstNode; import 'package:analyzer/dart/ast/token.dart' show TokenType; import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/type.dart'; @@ -14,6 +15,8 @@ import 'package:analyzer/src/dart/element/element.dart'; import 'package:analyzer/src/dart/element/type.dart'; import 'package:analyzer/src/generated/engine.dart' show AnalysisContext, AnalysisOptionsImpl; +import 'package:analyzer/src/generated/error.dart' + show ErrorCode, ErrorReporter, StrongModeCode; import 'package:analyzer/src/generated/resolver.dart' show TypeProvider; import 'package:analyzer/src/generated/utilities_dart.dart' show ParameterKind; import 'package:analyzer/src/generated/utilities_general.dart' @@ -248,6 +251,9 @@ class StrongTypeSystemImpl extends TypeSystem { // // It would be safe to return a partial solution here, but the user // experience may be better if we simply do not infer in this case. + // + // TODO(jmesserly): this heuristic is old. Maybe we should we issue the + // inference error? return resultType ?? fnType; } @@ -272,7 +278,9 @@ class StrongTypeSystemImpl extends TypeSystem { FunctionType fnType, List correspondingParameterTypes, List argumentTypes, - DartType returnContextType) { + DartType returnContextType, + {ErrorReporter errorReporter, + AstNode errorNode}) { if (fnType.typeFormals.isEmpty) { return fnType; } @@ -305,7 +313,7 @@ class StrongTypeSystemImpl extends TypeSystem { argumentTypes[i], correspondingParameterTypes[i]); } - return inferringTypeSystem._infer(fnType); + return inferringTypeSystem._infer(fnType, errorReporter, errorNode); } /** @@ -1371,7 +1379,8 @@ class _StrongInferenceTypeSystem extends StrongTypeSystemImpl { /// Given the constraints that were given by calling [isSubtypeOf], find the /// instantiation of the generic function that satisfies these constraints. - FunctionType _infer(FunctionType fnType) { + FunctionType _infer(FunctionType fnType, + [ErrorReporter errorReporter, AstNode errorNode]) { List fnTypeParams = TypeParameterTypeImpl.getTypes(fnType.typeFormals); @@ -1423,15 +1432,39 @@ class _StrongInferenceTypeSystem extends StrongTypeSystemImpl { new _TypeParameterVariance.from(typeParam, fnType.returnType); _TypeParameterBound bound = _bounds[typeParam]; - inferredTypes[i] = - variance.passedIn || bound.lower.isBottom ? bound.upper : bound.lower; + DartType lowerBound = bound.lower; + DartType upperBound = bound.upper; // See if the bounds can be satisfied. - if (bound.upper.isBottom || - !_typeSystem.isSubtypeOf(bound.lower, bound.upper)) { - // Inference failed. Bail. - return null; + // TODO(jmesserly): also we should have an error for unconstrained type + // parameters, rather than silently inferring dynamic. + if (upperBound.isBottom || + !_typeSystem.isSubtypeOf(lowerBound, upperBound)) { + // Inference failed. + if (errorReporter == null) { + return null; + } + errorReporter.reportErrorForNode(StrongModeCode.COULD_NOT_INFER, + errorNode, [typeParam, lowerBound, upperBound]); + + // To make the errors more useful, we swap the normal heuristic. + // + // The normal heuristic prefers using the argument types (upwards + // inference, lower bound) to choose a tighter type. + // + // Here we want to prefer the return context type, so we can put the + // blame on the arguments to the function. That will result in narrow + // error spans. But ultimately it's just a heuristic, as the code is + // already erroneous. + // + // (we may adjust the normal heuristic too, once upwards+downwards + // inference are fully integrated, to prefer downwards info). + lowerBound = bound.upper; + upperBound = bound.lower; } + + inferredTypes[i] = + variance.passedIn || lowerBound.isBottom ? upperBound : lowerBound; } // Return the instantiated type. diff --git a/pkg/analyzer/test/src/task/strong/checker_test.dart b/pkg/analyzer/test/src/task/strong/checker_test.dart index 3d11558a05f..aecebc6a639 100644 --- a/pkg/analyzer/test/src/task/strong/checker_test.dart +++ b/pkg/analyzer/test/src/task/strong/checker_test.dart @@ -1857,16 +1857,16 @@ main() { x = foo/*error:EXTRA_POSITIONAL_ARGUMENTS*/('1', '2', '3'); foo/*error:NOT_ENOUGH_REQUIRED_ARGUMENTS*/(1); x = foo/*error:NOT_ENOUGH_REQUIRED_ARGUMENTS*/('1'); - x = /*info:DYNAMIC_CAST*/foo/*error:EXTRA_POSITIONAL_ARGUMENTS*/(1, 2, 3); - x = /*info:DYNAMIC_CAST*/foo/*error:NOT_ENOUGH_REQUIRED_ARGUMENTS*/(1); + x = /*error:COULD_NOT_INFER*/foo/*error:EXTRA_POSITIONAL_ARGUMENTS*/(/*error:ARGUMENT_TYPE_NOT_ASSIGNABLE*/1, /*error:ARGUMENT_TYPE_NOT_ASSIGNABLE*/2, 3); + x = /*error:COULD_NOT_INFER*/foo/*error:NOT_ENOUGH_REQUIRED_ARGUMENTS*/(/*error:ARGUMENT_TYPE_NOT_ASSIGNABLE*/1); // named arguments bar(y: 1, x: 2, /*error:UNDEFINED_NAMED_PARAMETER*/z: 3); x = bar(/*error:UNDEFINED_NAMED_PARAMETER*/z: '1', x: '2', y: '3'); bar(y: 1); x = bar(x: '1', /*error:UNDEFINED_NAMED_PARAMETER*/z: 42); - x = /*info:DYNAMIC_CAST*/bar(y: 1, x: 2, /*error:UNDEFINED_NAMED_PARAMETER*/z: 3); - x = /*info:DYNAMIC_CAST*/bar(x: 1); + x = /*error:COULD_NOT_INFER*/bar(/*error:ARGUMENT_TYPE_NOT_ASSIGNABLE*/y: 1, /*error:ARGUMENT_TYPE_NOT_ASSIGNABLE*/x: 2, /*error:UNDEFINED_NAMED_PARAMETER*/z: 3); + x = /*error:COULD_NOT_INFER*/bar(/*error:ARGUMENT_TYPE_NOT_ASSIGNABLE*/x: 1); } '''); } diff --git a/pkg/analyzer/test/src/task/strong/inferred_type_test.dart b/pkg/analyzer/test/src/task/strong/inferred_type_test.dart index f4a6c9d1636..0490b744239 100644 --- a/pkg/analyzer/test/src/task/strong/inferred_type_test.dart +++ b/pkg/analyzer/test/src/task/strong/inferred_type_test.dart @@ -625,9 +625,8 @@ class C { var x = /*info:INFERRED_TYPE_ALLOCATION*/new C(42); -// Don't infer if we had a context type. num y; -C c_int = /*info:INFERRED_TYPE_ALLOCATION*/new C(/*info:DOWN_CAST_IMPLICIT*/y); +C c_int = /*info:INFERRED_TYPE_ALLOCATION*/new /*error:COULD_NOT_INFER*/C(/*info:DOWN_CAST_IMPLICIT*/y); // These hints are not reported because we resolve with a null error listener. C c_num = /*pass should be info:INFERRED_TYPE_ALLOCATION*/new C(123); @@ -1225,10 +1224,10 @@ void main() { A a5 = /*error:STATIC_TYPE_ERROR*/new A.named(3, "hello"); } { - A a0 = /*info:INFERRED_TYPE_ALLOCATION*/new A( + A a0 = /*info:INFERRED_TYPE_ALLOCATION*/new /*error:COULD_NOT_INFER,error:COULD_NOT_INFER*/A( /*error:ARGUMENT_TYPE_NOT_ASSIGNABLE*/"hello", /*error:ARGUMENT_TYPE_NOT_ASSIGNABLE*/3); - A a1 = /*info:INFERRED_TYPE_ALLOCATION*/new A.named( + A a1 = /*info:INFERRED_TYPE_ALLOCATION*/new /*error:COULD_NOT_INFER,error:COULD_NOT_INFER*/A.named( /*error:ARGUMENT_TYPE_NOT_ASSIGNABLE*/"hello", /*error:ARGUMENT_TYPE_NOT_ASSIGNABLE*/3); } @@ -1241,10 +1240,10 @@ void main() { A a5 = /*error:INVALID_ASSIGNMENT*/new B.named("hello", 3); } { - A a0 = /*info:INFERRED_TYPE_ALLOCATION*/new B( + A a0 = /*info:INFERRED_TYPE_ALLOCATION*/new /*error:COULD_NOT_INFER,error:COULD_NOT_INFER*/B( /*error:ARGUMENT_TYPE_NOT_ASSIGNABLE*/3, /*error:ARGUMENT_TYPE_NOT_ASSIGNABLE*/"hello"); - A a1 = /*info:INFERRED_TYPE_ALLOCATION*/new B.named( + A a1 = /*info:INFERRED_TYPE_ALLOCATION*/new /*error:COULD_NOT_INFER,error:COULD_NOT_INFER*/B.named( /*error:ARGUMENT_TYPE_NOT_ASSIGNABLE*/3, /*error:ARGUMENT_TYPE_NOT_ASSIGNABLE*/"hello"); } @@ -1257,9 +1256,9 @@ void main() { A a5 = /*error:INVALID_ASSIGNMENT*/new C.named(3); } { - A a0 = /*info:INFERRED_TYPE_ALLOCATION*/new C( + A a0 = /*info:INFERRED_TYPE_ALLOCATION*/new /*error:COULD_NOT_INFER*/C( /*error:ARGUMENT_TYPE_NOT_ASSIGNABLE*/"hello"); - A a1 = /*info:INFERRED_TYPE_ALLOCATION*/new C.named( + A a1 = /*info:INFERRED_TYPE_ALLOCATION*/new /*error:COULD_NOT_INFER*/C.named( /*error:ARGUMENT_TYPE_NOT_ASSIGNABLE*/"hello"); } { @@ -1271,9 +1270,9 @@ void main() { A a5 = /*error:INVALID_ASSIGNMENT*/new D.named("hello"); } { - A a0 = /*info:INFERRED_TYPE_ALLOCATION*/new D( + A a0 = /*info:INFERRED_TYPE_ALLOCATION*/new /*error:COULD_NOT_INFER*/D( /*error:ARGUMENT_TYPE_NOT_ASSIGNABLE*/3); - A a1 = /*info:INFERRED_TYPE_ALLOCATION*/new D.named( + A a1 = /*info:INFERRED_TYPE_ALLOCATION*/new /*error:COULD_NOT_INFER*/D.named( /*error:ARGUMENT_TYPE_NOT_ASSIGNABLE*/3); } { @@ -1288,9 +1287,9 @@ void main() { b: /*info:INFERRED_TYPE_LITERAL*/[/*error:LIST_ELEMENT_TYPE_NOT_ASSIGNABLE*/3]); A a2 = /*info:INFERRED_TYPE_ALLOCATION*/new F.named(3, "hello", 3, "hello"); A a3 = /*info:INFERRED_TYPE_ALLOCATION*/new F.named(3, "hello"); - A a4 = /*info:INFERRED_TYPE_ALLOCATION*/new F.named(3, "hello", + A a4 = /*info:INFERRED_TYPE_ALLOCATION*/new /*error:COULD_NOT_INFER,error:COULD_NOT_INFER*/F.named(3, "hello", /*error:ARGUMENT_TYPE_NOT_ASSIGNABLE*/"hello", /*error:ARGUMENT_TYPE_NOT_ASSIGNABLE*/3); - A a5 = /*info:INFERRED_TYPE_ALLOCATION*/new F.named(3, "hello", + A a5 = /*info:INFERRED_TYPE_ALLOCATION*/new /*error:COULD_NOT_INFER*/F.named(3, "hello", /*error:ARGUMENT_TYPE_NOT_ASSIGNABLE*/"hello"); } } @@ -1706,7 +1705,7 @@ class MyFuture implements Future { $declared f; // Instantiates Future $downwards t1 = f.then((_) => - ${allocInfo}new $upwards.value( + ${allocInfo}new /*error:COULD_NOT_INFER*/$upwards.value( /*error:ARGUMENT_TYPE_NOT_ASSIGNABLE*/'hi')); // Instantiates List @@ -1774,7 +1773,7 @@ main() { var c = new Foo().method("str"); s = c; - new Foo().method(/*error:ARGUMENT_TYPE_NOT_ASSIGNABLE*/42); + new Foo()./*error:COULD_NOT_INFER*/method(/*error:ARGUMENT_TYPE_NOT_ASSIGNABLE*/42); } '''); } @@ -1800,14 +1799,14 @@ main() { printInt(myMax(1, 2) as int); // Mixing int and double means return type is num. - printInt(/*info:DOWN_CAST_IMPLICIT*/max(1, 2.0)); - printInt(/*info:DOWN_CAST_IMPLICIT*/min(1, 2.0)); - printDouble(/*info:DOWN_CAST_IMPLICIT*/max(1, 2.0)); - printDouble(/*info:DOWN_CAST_IMPLICIT*/min(1, 2.0)); + printInt(/*error:COULD_NOT_INFER*/max(1, /*error:ARGUMENT_TYPE_NOT_ASSIGNABLE*/2.0)); + printInt(/*error:COULD_NOT_INFER*/min(1, /*error:ARGUMENT_TYPE_NOT_ASSIGNABLE*/2.0)); + printDouble(/*error:COULD_NOT_INFER*/max(/*error:ARGUMENT_TYPE_NOT_ASSIGNABLE*/1, 2.0)); + printDouble(/*error:COULD_NOT_INFER*/min(/*error:ARGUMENT_TYPE_NOT_ASSIGNABLE*/1, 2.0)); // Types other than int and double are not accepted. printInt( - /*info:DOWN_CAST_IMPLICIT*/min( + /*error:COULD_NOT_INFER*/min( /*error:ARGUMENT_TYPE_NOT_ASSIGNABLE*/"hi", /*error:ARGUMENT_TYPE_NOT_ASSIGNABLE*/"there")); } @@ -1877,6 +1876,15 @@ main() { '''); } + void test_genericMethods_inferenceError() { + checkFile(r''' +main() { + List y; + Iterable x = y./*error:COULD_NOT_INFER*/map(/*error:ARGUMENT_TYPE_NOT_ASSIGNABLE*/(String z) => 1.0); +} + '''); + } + void test_genericMethods_inferGenericFunctionParameterType() { var mainUnit = checkFile(''' class C extends D {