Skip to content

Commit 4f9fff9

Browse files
author
Jennifer Messerly
committed
fix #29108, improve inference error messages
[email protected] Review-Url: https://codereview.chromium.org/2757233004 .
1 parent 9f2668c commit 4f9fff9

File tree

12 files changed

+322
-87
lines changed

12 files changed

+322
-87
lines changed

pkg/analyzer/lib/src/generated/type_system.dart

Lines changed: 70 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -236,12 +236,7 @@ class StrongTypeSystemImpl extends TypeSystem {
236236
// subtypes (or supertypes) as necessary, and track the constraints that
237237
// are implied by this.
238238
var inferrer = new _GenericInferrer(typeProvider, this, fnType.typeFormals);
239-
240-
// Since we're trying to infer the instantiation, we want to ignore type
241-
// formals as we check the parameters and return type.
242-
var inferFnType =
243-
fnType.instantiate(TypeParameterTypeImpl.getTypes(fnType.typeFormals));
244-
inferrer.constrainReturnType(inferFnType, contextType);
239+
inferrer.constrainGenericFunctionInContext(fnType, contextType);
245240

246241
// Infer and instantiate the resulting type.
247242
return inferrer.infer(fnType, fnType.typeFormals,
@@ -1603,6 +1598,19 @@ class _GenericInferrer {
16031598
_matchSubtypeOf(declaredType, contextType, null, origin, covariant: true);
16041599
}
16051600

1601+
/// Constrain a universal function type [fnType] used in a context
1602+
/// [contextType].
1603+
void constrainGenericFunctionInContext(
1604+
FunctionType fnType, DartType contextType) {
1605+
var origin = new _TypeConstraintFromFunctionContext(fnType, contextType);
1606+
1607+
// Since we're trying to infer the instantiation, we want to ignore type
1608+
// formals as we check the parameters and return type.
1609+
var inferFnType =
1610+
fnType.instantiate(TypeParameterTypeImpl.getTypes(fnType.typeFormals));
1611+
_matchSubtypeOf(inferFnType, contextType, null, origin, covariant: true);
1612+
}
1613+
16061614
/// Apply an argument constraint, which asserts that the [argument] staticType
16071615
/// is a subtype of the [parameterType].
16081616
void constrainArgument(
@@ -1851,20 +1859,31 @@ class _GenericInferrer {
18511859
var constraints = _constraints[typeParam.element];
18521860
var typeParamBound =
18531861
typeParam.bound.substitute2(inferredTypes, fnTypeParams);
1854-
if (!typeParamBound.isDynamic) {
1855-
constraints
1856-
.add(new _TypeConstraint.fromExtends(typeParam, typeParamBound));
1857-
}
1862+
18581863
var inferred = inferredTypes[i];
1859-
if (constraints.any((c) => !c.isSatisifedBy(_typeSystem, inferred))) {
1860-
// Heuristic: keep the erroneous type, it should satisfy at least some
1861-
// of the constraints (e.g. the return context). If we fall back to
1862-
// instantiateToBounds, we'll typically get more errors (e.g. because
1863-
// `dynamic` is the most common bound).
1864-
knownTypes[typeParam] = inferred;
1865-
errorReporter?.reportErrorForNode(StrongModeCode.COULD_NOT_INFER,
1866-
errorNode, [typeParam, _formatError(inferred, constraints)]);
1867-
} else if (UnknownInferredType.isKnown(inferred)) {
1864+
bool success =
1865+
constraints.every((c) => c.isSatisifedBy(_typeSystem, inferred));
1866+
if (success && !typeParamBound.isDynamic) {
1867+
// If everything else succeeded, check the `extends` constraint.
1868+
var extendsConstraint =
1869+
new _TypeConstraint.fromExtends(typeParam, typeParamBound);
1870+
constraints.add(extendsConstraint);
1871+
success = extendsConstraint.isSatisifedBy(_typeSystem, inferred);
1872+
}
1873+
1874+
if (!success) {
1875+
errorReporter?.reportErrorForNode(
1876+
StrongModeCode.COULD_NOT_INFER,
1877+
errorNode,
1878+
[typeParam, _formatError(typeParam, inferred, constraints)]);
1879+
1880+
// Heuristic: even if we failed, keep the erroneous type.
1881+
// It should satisfy at least some of the constraints (e.g. the return
1882+
// context). If we fall back to instantiateToBounds, we'll typically get
1883+
// more errors (e.g. because `dynamic` is the most common bound).
1884+
}
1885+
1886+
if (UnknownInferredType.isKnown(inferred)) {
18681887
knownTypes[typeParam] = inferred;
18691888
}
18701889
}
@@ -2039,22 +2058,23 @@ class _GenericInferrer {
20392058
return result;
20402059
}
20412060

2042-
String _formatError(
2043-
DartType inferred, Iterable<_TypeConstraint> constraints) {
2044-
var intro = "Inferred type '$inferred' does not work with constraints:";
2061+
String _formatError(TypeParameterType typeParam, DartType inferred,
2062+
Iterable<_TypeConstraint> constraints) {
2063+
var intro = "Tried to infer '$inferred' for '$typeParam'"
2064+
" which doesn't work:";
20452065

20462066
var constraintsByOrigin = <_TypeConstraintOrigin, List<_TypeConstraint>>{};
20472067
for (var c in constraints) {
20482068
constraintsByOrigin.putIfAbsent(c.origin, () => []).add(c);
20492069
}
20502070

2071+
// Only report unique constraint origins.
20512072
Iterable<_TypeConstraint> isSatisified(bool expected) => constraintsByOrigin
20522073
.values
20532074
.where((l) =>
20542075
l.every((c) => c.isSatisifedBy(_typeSystem, inferred)) == expected)
20552076
.expand((i) => i);
20562077

2057-
// Only report unique constraint origins.
20582078
String unsatisified = _formatConstraints(isSatisified(false));
20592079
String satisified = _formatConstraints(isSatisified(true));
20602080

@@ -2074,16 +2094,19 @@ class _GenericInferrer {
20742094
.toList();
20752095

20762096
int prefixMax = lineParts.map((p) => p[0].length).fold(0, math.max);
2077-
int middleMax = lineParts.map((p) => p[1].length).fold(0, math.max);
20782097

20792098
// Use a set to prevent identical message lines.
20802099
// (It's not uncommon for the same constraint to show up in a few places.)
20812100
var messageLines = new Set<String>.from(lineParts.map((parts) {
20822101
var prefix = parts[0];
20832102
var middle = parts[1];
20842103
var prefixPad = ' ' * (prefixMax - prefix.length);
2085-
var middlePad = ' ' * (middleMax - middle.length);
2086-
return ' $prefix$prefixPad $middle$middlePad ${parts[2]}'.trimRight();
2104+
var middlePad = ' ' * (prefixMax);
2105+
var end = "";
2106+
if (parts.length > 2) {
2107+
end = '\n $middlePad ${parts[2]}';
2108+
}
2109+
return ' $prefix$prefixPad $middle$end';
20872110
}));
20882111

20892112
return messageLines.join('\n');
@@ -2121,13 +2144,13 @@ class _TypeConstraintFromArgument extends _TypeConstraintOrigin {
21212144
// "Map value"
21222145
prefix = "${genericType.name} $parameterName";
21232146
} else {
2124-
prefix = "Argument '$parameterName'";
2147+
prefix = "Parameter '$parameterName'";
21252148
}
21262149

21272150
return [
21282151
prefix,
2129-
"inferred as '$argumentType'",
2130-
"must be a '$parameterType'."
2152+
"declared as '$parameterType'",
2153+
"but argument is '$argumentType'."
21312154
];
21322155
}
21332156
}
@@ -2143,7 +2166,23 @@ class _TypeConstraintFromReturnType extends _TypeConstraintOrigin {
21432166
return [
21442167
"Return type",
21452168
"declared as '$declaredType'",
2146-
"used where a '$contextType' is required."
2169+
"used where '$contextType' is required."
2170+
];
2171+
}
2172+
}
2173+
2174+
class _TypeConstraintFromFunctionContext extends _TypeConstraintOrigin {
2175+
final DartType contextType;
2176+
final DartType functionType;
2177+
2178+
_TypeConstraintFromFunctionContext(this.functionType, this.contextType);
2179+
2180+
@override
2181+
formatError() {
2182+
return [
2183+
"Function type",
2184+
"declared as '$functionType'",
2185+
"used where '$contextType' is required."
21472186
];
21482187
}
21492188
}
@@ -2158,8 +2197,7 @@ class _TypeConstraintFromExtendsClause extends _TypeConstraintOrigin {
21582197
formatError() {
21592198
return [
21602199
"Type parameter '$typeParam'",
2161-
"declared to extend '$extendsType'.",
2162-
""
2200+
"declared to extend '$extendsType'."
21632201
];
21642202
}
21652203
}

pkg/analyzer/test/generated/strong_mode_test.dart

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1098,6 +1098,176 @@ class StrongModeLocalInferenceTest extends ResolverTestCase {
10981098
_isInstantiationOf(_hasElement(elementC))([_isDynamic])(cType);
10991099
}
11001100

1101+
test_inference_error_arguments() async {
1102+
Source source = addSource(r'''
1103+
typedef R F<T, R>(T t);
1104+
1105+
F<T, T> g<T>(F<T, T> f) => (x) => f(f(x));
1106+
1107+
test() {
1108+
var h = g((int x) => 42.0);
1109+
}
1110+
''');
1111+
await computeAnalysisResult(source);
1112+
_expectInferenceError(
1113+
source,
1114+
[
1115+
StrongModeCode.COULD_NOT_INFER,
1116+
StaticWarningCode.ARGUMENT_TYPE_NOT_ASSIGNABLE
1117+
],
1118+
r'''
1119+
Couldn't infer type parameter 'T'.
1120+
1121+
Tried to infer 'double' for 'T' which doesn't work:
1122+
Parameter 'f' declared as '(T) → T'
1123+
but argument is '(int) → double'.
1124+
1125+
Consider passing explicit type argument(s) to the generic.
1126+
1127+
''');
1128+
}
1129+
1130+
test_inference_error_arguments2() async {
1131+
Source source = addSource(r'''
1132+
typedef R F<T, R>(T t);
1133+
1134+
F<T, T> g<T>(F<T, T> a, F<T, T> b) => (x) => a(b(x));
1135+
1136+
test() {
1137+
var h = g((int x) => 42.0, (double x) => 42);
1138+
}
1139+
''');
1140+
await computeAnalysisResult(source);
1141+
_expectInferenceError(
1142+
source,
1143+
[
1144+
StrongModeCode.COULD_NOT_INFER,
1145+
StaticWarningCode.ARGUMENT_TYPE_NOT_ASSIGNABLE,
1146+
StaticWarningCode.ARGUMENT_TYPE_NOT_ASSIGNABLE
1147+
],
1148+
r'''
1149+
Couldn't infer type parameter 'T'.
1150+
1151+
Tried to infer 'num' for 'T' which doesn't work:
1152+
Parameter 'a' declared as '(T) → T'
1153+
but argument is '(int) → double'.
1154+
Parameter 'b' declared as '(T) → T'
1155+
but argument is '(double) → int'.
1156+
1157+
Consider passing explicit type argument(s) to the generic.
1158+
1159+
''');
1160+
}
1161+
1162+
test_inference_error_extendsFromReturn() async {
1163+
// This is not an inference error because we successfully infer Null.
1164+
Source source = addSource(r'''
1165+
T max<T extends num>(T x, T y) => x;
1166+
1167+
test() {
1168+
String hello = max(1, 2);
1169+
}
1170+
''');
1171+
var analysisResult = await computeAnalysisResult(source);
1172+
assertErrors(source, [
1173+
StrongModeCode.INVALID_CAST_LITERAL,
1174+
StrongModeCode.INVALID_CAST_LITERAL
1175+
]);
1176+
var unit = analysisResult.unit;
1177+
var h = (AstFinder.getStatementsInTopLevelFunction(unit, "test")[0]
1178+
as VariableDeclarationStatement)
1179+
.variables
1180+
.variables[0];
1181+
var call = h.initializer as MethodInvocation;
1182+
expect(call.staticInvokeType.toString(), '(Null, Null) → Null');
1183+
}
1184+
1185+
test_inference_error_extendsFromReturn2() async {
1186+
Source source = addSource(r'''
1187+
typedef R F<T, R>(T t);
1188+
F<T, T> g<T extends num>() => (y) => y;
1189+
1190+
test() {
1191+
F<String, String> hello = g();
1192+
}
1193+
''');
1194+
await computeAnalysisResult(source);
1195+
_expectInferenceError(
1196+
source,
1197+
[
1198+
StrongModeCode.COULD_NOT_INFER,
1199+
],
1200+
r'''
1201+
Couldn't infer type parameter 'T'.
1202+
1203+
Tried to infer 'String' for 'T' which doesn't work:
1204+
Type parameter 'T' declared to extend 'num'.
1205+
The type 'String' was inferred from:
1206+
Return type declared as '(T) → T'
1207+
used where '(String) → String' is required.
1208+
1209+
Consider passing explicit type argument(s) to the generic.
1210+
1211+
''');
1212+
}
1213+
1214+
1215+
test_inference_error_genericFunction() async {
1216+
Source source = addSource(r'''
1217+
T max<T extends num>(T x, T y) => x < y ? y : x;
1218+
abstract class Iterable<T> {
1219+
T get first;
1220+
S fold<S>(S s, S f(S s, T t));
1221+
}
1222+
test(Iterable values) {
1223+
num n = values.fold(values.first as num, max);
1224+
}
1225+
''');
1226+
await computeAnalysisResult(source);
1227+
_expectInferenceError(
1228+
source,
1229+
[
1230+
StrongModeCode.COULD_NOT_INFER,
1231+
StaticWarningCode.ARGUMENT_TYPE_NOT_ASSIGNABLE
1232+
],
1233+
r'''
1234+
Couldn't infer type parameter 'T'.
1235+
1236+
Tried to infer 'dynamic' for 'T' which doesn't work:
1237+
Function type declared as '<T extends num>(T, T) → T'
1238+
used where '(num, dynamic) → num' is required.
1239+
1240+
Consider passing explicit type argument(s) to the generic.
1241+
1242+
''');
1243+
}
1244+
1245+
test_inference_error_returnContext() async {
1246+
Source source = addSource(r'''
1247+
typedef R F<T, R>(T t);
1248+
1249+
F<T, T> g<T>(T t) => (x) => t;
1250+
1251+
test() {
1252+
F<num, int> h = g(42);
1253+
}
1254+
''');
1255+
await computeAnalysisResult(source);
1256+
_expectInferenceError(
1257+
source,
1258+
[StrongModeCode.COULD_NOT_INFER],
1259+
r'''
1260+
Couldn't infer type parameter 'T'.
1261+
1262+
Tried to infer 'num' for 'T' which doesn't work:
1263+
Return type declared as '(T) → T'
1264+
used where '(num) → int' is required.
1265+
1266+
Consider passing explicit type argument(s) to the generic.
1267+
1268+
''');
1269+
}
1270+
11011271
test_inference_hints() async {
11021272
Source source = addSource(r'''
11031273
void main () {
@@ -2137,6 +2307,23 @@ num test(Iterable values) => values.fold(values.first as num, max);
21372307
check("f3", _isListOf((DartType type) => _isListOf(_isInt)(type)));
21382308
}
21392309

2310+
/// Verifies the source has the expected [errorCodes] as well as the
2311+
/// expected [errorMessage].
2312+
void _expectInferenceError(
2313+
Source source, List<ErrorCode> errorCodes, String errorMessage) {
2314+
assertErrors(source, errorCodes);
2315+
var errors = analysisResults[source]
2316+
.errors
2317+
.where((e) => e.errorCode == StrongModeCode.COULD_NOT_INFER)
2318+
.map((e) => e.message)
2319+
.toList();
2320+
expect(errors.length, 1);
2321+
var actual = errors[0];
2322+
expect(actual,
2323+
errorMessage, // Print the literal error message for easy copy+paste:
2324+
reason: 'Actual error did not match expected error:\n$actual');
2325+
}
2326+
21402327
/// Helper method for testing `FutureOr<T>`.
21412328
///
21422329
/// Validates that [code] produces [errors]. It should define a function

0 commit comments

Comments
 (0)