mirror of
https://github.com/dart-lang/sdk
synced 2024-10-03 00:29:50 +00:00
fix #26992, inference failures are now an error
R=leafp@google.com Review URL: https://codereview.chromium.org/2295853002 .
This commit is contained in:
parent
4024fe154a
commit
f57ed4d894
18
CHANGELOG.md
18
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<T> {
|
||||
Cup(T t);
|
||||
}
|
||||
main() {
|
||||
// Error because:
|
||||
// - if we choose Cup<num> it is not assignable to `cOfInt`,
|
||||
// - if we choose Cup<int> then `n` is not assignable to int.
|
||||
num n;
|
||||
C<int> cOfInt = new C(n);
|
||||
}
|
||||
```
|
||||
|
||||
## 1.19.0
|
||||
|
||||
### Language changes
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -1905,8 +1905,8 @@ class StaticTypeAnalyzer extends SimpleAstVisitor<Object> {
|
|||
*/
|
||||
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<Object> {
|
|||
* 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<Object> {
|
|||
}
|
||||
}
|
||||
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<Object> {
|
|||
_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.
|
||||
|
|
|
@ -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<DartType> correspondingParameterTypes,
|
||||
List<DartType> 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<TypeParameterType> 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.
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
|
|
@ -625,9 +625,8 @@ class C<T> {
|
|||
|
||||
var x = /*info:INFERRED_TYPE_ALLOCATION*/new C(42);
|
||||
|
||||
// Don't infer if we had a context type.
|
||||
num y;
|
||||
C<int> c_int = /*info:INFERRED_TYPE_ALLOCATION*/new C(/*info:DOWN_CAST_IMPLICIT*/y);
|
||||
C<int> 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<num> c_num = /*pass should be info:INFERRED_TYPE_ALLOCATION*/new C(123);
|
||||
|
@ -1225,10 +1224,10 @@ void main() {
|
|||
A<int, String> a5 = /*error:STATIC_TYPE_ERROR*/new A<dynamic, dynamic>.named(3, "hello");
|
||||
}
|
||||
{
|
||||
A<int, String> a0 = /*info:INFERRED_TYPE_ALLOCATION*/new A(
|
||||
A<int, String> 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<int, String> a1 = /*info:INFERRED_TYPE_ALLOCATION*/new A.named(
|
||||
A<int, String> 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<int, String> a5 = /*error:INVALID_ASSIGNMENT*/new B<dynamic, dynamic>.named("hello", 3);
|
||||
}
|
||||
{
|
||||
A<int, String> a0 = /*info:INFERRED_TYPE_ALLOCATION*/new B(
|
||||
A<int, String> 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<int, String> a1 = /*info:INFERRED_TYPE_ALLOCATION*/new B.named(
|
||||
A<int, String> 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<int, int> a5 = /*error:INVALID_ASSIGNMENT*/new C<dynamic>.named(3);
|
||||
}
|
||||
{
|
||||
A<int, int> a0 = /*info:INFERRED_TYPE_ALLOCATION*/new C(
|
||||
A<int, int> a0 = /*info:INFERRED_TYPE_ALLOCATION*/new /*error:COULD_NOT_INFER*/C(
|
||||
/*error:ARGUMENT_TYPE_NOT_ASSIGNABLE*/"hello");
|
||||
A<int, int> a1 = /*info:INFERRED_TYPE_ALLOCATION*/new C.named(
|
||||
A<int, int> 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<int, String> a5 = /*error:INVALID_ASSIGNMENT*/new D<dynamic, dynamic>.named("hello");
|
||||
}
|
||||
{
|
||||
A<int, String> a0 = /*info:INFERRED_TYPE_ALLOCATION*/new D(
|
||||
A<int, String> a0 = /*info:INFERRED_TYPE_ALLOCATION*/new /*error:COULD_NOT_INFER*/D(
|
||||
/*error:ARGUMENT_TYPE_NOT_ASSIGNABLE*/3);
|
||||
A<int, String> a1 = /*info:INFERRED_TYPE_ALLOCATION*/new D.named(
|
||||
A<int, String> 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<int, String> a2 = /*info:INFERRED_TYPE_ALLOCATION*/new F.named(3, "hello", 3, "hello");
|
||||
A<int, String> a3 = /*info:INFERRED_TYPE_ALLOCATION*/new F.named(3, "hello");
|
||||
A<int, String> a4 = /*info:INFERRED_TYPE_ALLOCATION*/new F.named(3, "hello",
|
||||
A<int, String> 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<int, String> a5 = /*info:INFERRED_TYPE_ALLOCATION*/new F.named(3, "hello",
|
||||
A<int, String> 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<T> implements Future<T> {
|
|||
$declared f;
|
||||
// Instantiates Future<int>
|
||||
$downwards<int> t1 = f.then((_) =>
|
||||
${allocInfo}new $upwards.value(
|
||||
${allocInfo}new /*error:COULD_NOT_INFER*/$upwards.value(
|
||||
/*error:ARGUMENT_TYPE_NOT_ASSIGNABLE*/'hi'));
|
||||
|
||||
// Instantiates List<int>
|
||||
|
@ -1774,7 +1773,7 @@ main() {
|
|||
var c = new Foo().method("str");
|
||||
s = c;
|
||||
|
||||
new Foo<String>().method(/*error:ARGUMENT_TYPE_NOT_ASSIGNABLE*/42);
|
||||
new Foo<String>()./*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<String> y;
|
||||
Iterable<String> 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<T> extends D<T> {
|
||||
|
|
Loading…
Reference in a new issue