Skip to content

Commit 9bdf248

Browse files
Kevin Millikincommit-bot@chromium.org
Kevin Millikin
authored andcommitted
Signal errors and warnings for invalid returns
Implement the feature spec for checking returns against inferred function return types: https://github.com/dart-lang/sdk/blob/master/docs/language/informal/invalid_returns.md with the change that an invalid return without a subexpression is a warning, not an error. This is because it is a warning in the analyzer and it would be a breaking change to turn it into an error. The check for valid returns is moved to exactly where we handle returns, rather than in ensureAssignable which is used as a helper in a lot of places. A bug in type inference was fixed: we would use `void` for the type of return without a subexpression instead of `null`. To accommodate that we would use a non-standard subtyping relation for returns. This could lead to us inferring a return type of `void` in cases where we should not. Change-Id: Iee9ece9c722f47efa305f49490d3022d0bbb9f44 Reviewed-on: https://dart-review.googlesource.com/72403 Commit-Queue: Kevin Millikin <[email protected]> Reviewed-by: Aske Simon Christensen <[email protected]>
1 parent 9a1f783 commit 9bdf248

18 files changed

+170
-479
lines changed

pkg/front_end/lib/src/fasta/fasta_codes_generated.dart

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6284,7 +6284,7 @@ const Code<Null> codeReturnFromVoidFunction = messageReturnFromVoidFunction;
62846284
const MessageCode messageReturnFromVoidFunction = const MessageCode(
62856285
"ReturnFromVoidFunction",
62866286
analyzerCode: "RETURN_OF_INVALID_TYPE",
6287-
severity: Severity.warning,
6287+
severity: Severity.error,
62886288
message: r"""Can't return a value from a void function.""");
62896289

62906290
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
@@ -6297,6 +6297,16 @@ const MessageCode messageReturnTypeFunctionExpression = const MessageCode(
62976297
severity: Severity.errorLegacyWarning,
62986298
message: r"""A function expression can't have a return type.""");
62996299

6300+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
6301+
const Code<Null> codeReturnWithoutExpression = messageReturnWithoutExpression;
6302+
6303+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
6304+
const MessageCode messageReturnWithoutExpression = const MessageCode(
6305+
"ReturnWithoutExpression",
6306+
analyzerCode: "RETURN_WITHOUT_VALUE",
6307+
severity: Severity.warning,
6308+
message: r"""Must explicitly return a value from a non-void function.""");
6309+
63006310
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
63016311
const Template<Message Function(Uri uri_)> templateSdkRootNotFound =
63026312
const Template<Message Function(Uri uri_)>(
@@ -7421,7 +7431,7 @@ const Code<Null> codeVoidExpression = messageVoidExpression;
74217431
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
74227432
const MessageCode messageVoidExpression = const MessageCode("VoidExpression",
74237433
analyzerCode: "USE_OF_VOID_RESULT",
7424-
severity: Severity.warning,
7434+
severity: Severity.error,
74257435
message: r"""This expression has type 'void' and can't be used.""");
74267436

74277437
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.

pkg/front_end/lib/src/fasta/kernel/kernel_shadow_ast.dart

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2377,15 +2377,10 @@ class ReturnJudgment extends ReturnStatement implements StatementJudgment {
23772377
isVoidAllowed: true);
23782378
inferredType = judgment.inferredType;
23792379
} else {
2380-
inferredType = const VoidType();
2381-
}
2382-
// Analyzer treats bare `return` statements as having no effect on the
2383-
// inferred type of the closure. TODO(paulberry): is this what we want
2384-
// for Fasta?
2385-
if (judgment != null) {
2386-
closureContext.handleReturn(inferrer, inferredType, expression,
2387-
fileOffset, !identical(returnKeywordLexeme, "return"));
2380+
inferredType = inferrer.coreTypes.nullClass.rawType;
23882381
}
2382+
closureContext.handleReturn(inferrer, this, inferredType,
2383+
!identical(returnKeywordLexeme, "return"));
23892384
inferrer.listener.returnStatement(this, fileOffset, tokens, null);
23902385
}
23912386
}
@@ -3134,7 +3129,8 @@ class ShadowTypeInferrer extends TypeInferrerImpl {
31343129
DartType inferFieldTopLevel<Expression, Statement, Initializer, Type>(
31353130
ShadowField field, bool typeNeeded) {
31363131
if (field.initializer == null) return const DynamicType();
3137-
return inferExpression(field.initializer, const UnknownType(), typeNeeded);
3132+
return inferExpression(field.initializer, const UnknownType(), typeNeeded,
3133+
isVoidAllowed: true);
31383134
}
31393135

31403136
@override

pkg/front_end/lib/src/fasta/type_inference/type_inferrer.dart

Lines changed: 115 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import 'package:kernel/ast.dart'
3636
ProcedureKind,
3737
PropertyGet,
3838
PropertySet,
39+
ReturnStatement,
3940
StaticGet,
4041
SuperMethodInvocation,
4142
SuperPropertyGet,
@@ -74,6 +75,7 @@ import '../fasta_codes.dart'
7475
Message,
7576
Template,
7677
messageReturnFromVoidFunction,
78+
messageReturnWithoutExpression,
7779
messageVoidExpression,
7880
noLength,
7981
templateArgumentTypeNotAssignable,
@@ -203,10 +205,24 @@ class ClosureContext {
203205
/// wrapping this type in `Stream` or `Iterator`, as appropriate.
204206
DartType _inferredUnwrappedReturnOrYieldType;
205207

208+
/// Whether the function is an arrow function.
209+
bool isArrow;
210+
211+
/// A list of return statements in functions whose return type is being
212+
/// inferred.
213+
///
214+
/// The returns are checked for validity after the return type is inferred.
215+
List<ReturnStatement> returnStatements;
216+
217+
/// A list of return expression types in functions whose return type is
218+
/// being inferred.
219+
List<DartType> returnExpressionTypes;
220+
206221
factory ClosureContext(TypeInferrerImpl inferrer, AsyncMarker asyncMarker,
207222
DartType returnContext, bool needToInferReturnType) {
208223
assert(returnContext != null);
209-
DartType declaredReturnType = returnContext;
224+
DartType declaredReturnType =
225+
greatestClosure(inferrer.coreTypes, returnContext);
210226
bool isAsync = asyncMarker == AsyncMarker.Async ||
211227
asyncMarker == AsyncMarker.AsyncStar;
212228
bool isGenerator = asyncMarker == AsyncMarker.SyncStar ||
@@ -228,23 +244,86 @@ class ClosureContext {
228244
}
229245

230246
ClosureContext._(this.isAsync, this.isGenerator, this.returnOrYieldContext,
231-
this.declaredReturnType, this._needToInferReturnType) {}
247+
this.declaredReturnType, this._needToInferReturnType) {
248+
if (_needToInferReturnType) {
249+
returnStatements = [];
250+
returnExpressionTypes = [];
251+
}
252+
}
253+
254+
bool checkValidReturn(TypeInferrerImpl inferrer, DartType returnType,
255+
ReturnStatement statement, DartType expressionType) {
256+
if (statement.expression == null) {
257+
if (isAsync) {
258+
returnType = inferrer.typeSchemaEnvironment.unfutureType(returnType);
259+
}
260+
if (returnType is! VoidType &&
261+
returnType is! DynamicType &&
262+
returnType != inferrer.coreTypes.nullClass.rawType) {
263+
statement.expression = inferrer.helper.wrapInProblem(
264+
new NullLiteral()..fileOffset = statement.fileOffset,
265+
messageReturnWithoutExpression,
266+
noLength)
267+
..parent = statement;
268+
return false;
269+
}
270+
} else {
271+
if (isAsync) {
272+
returnType = inferrer.typeSchemaEnvironment.unfutureType(returnType);
273+
expressionType =
274+
inferrer.typeSchemaEnvironment.unfutureType(expressionType);
275+
}
276+
if (!isArrow && returnType is VoidType) {
277+
if (expressionType is! VoidType &&
278+
expressionType is! DynamicType &&
279+
expressionType != inferrer.coreTypes.nullClass.rawType) {
280+
statement.expression = inferrer.helper.wrapInProblem(
281+
statement.expression, messageReturnFromVoidFunction, noLength)
282+
..parent = statement;
283+
return false;
284+
}
285+
} else if (expressionType is VoidType) {
286+
if (returnType is! VoidType &&
287+
returnType is! DynamicType &&
288+
returnType != inferrer.coreTypes.nullClass.rawType) {
289+
statement.expression = inferrer.helper.wrapInProblem(
290+
statement.expression, messageVoidExpression, noLength)
291+
..parent = statement;
292+
return false;
293+
}
294+
}
295+
}
296+
return true;
297+
}
232298

233299
/// Updates the inferred return type based on the presence of a return
234300
/// statement returning the given [type].
235-
void handleReturn(TypeInferrerImpl inferrer, DartType type,
236-
Expression expression, int fileOffset, bool isArrow) {
301+
void handleReturn(TypeInferrerImpl inferrer, ReturnStatement statement,
302+
DartType type, bool isArrow) {
237303
if (isGenerator) return;
238-
if (inferrer.ensureAssignable(
239-
returnOrYieldContext, type, expression, fileOffset,
240-
isReturnFromAsync: isAsync,
241-
isReturn: true,
242-
declaredReturnType: declaredReturnType,
243-
isArrow: isArrow) !=
244-
null) {
245-
type = greatestClosure(inferrer.coreTypes, returnOrYieldContext);
304+
// The first return we see tells us if we have an arrow function.
305+
if (this.isArrow == null) {
306+
this.isArrow = isArrow;
307+
} else {
308+
assert(this.isArrow == isArrow);
246309
}
310+
247311
if (_needToInferReturnType) {
312+
// Add the return to a list to be checked for validity after we've
313+
// inferred the return type.
314+
returnStatements.add(statement);
315+
returnExpressionTypes.add(type);
316+
317+
// The return expression has to be assignable to the return type
318+
// expectation from the downwards inference context.
319+
if (statement.expression != null &&
320+
inferrer.ensureAssignable(returnOrYieldContext, type,
321+
statement.expression, statement.fileOffset,
322+
isReturnFromAsync: isAsync, isVoidAllowed: true) !=
323+
null) {
324+
// Not assignable, use the expectation.
325+
type = greatestClosure(inferrer.coreTypes, returnOrYieldContext);
326+
}
248327
var unwrappedType = type;
249328
if (isAsync) {
250329
unwrappedType = inferrer.typeSchemaEnvironment.unfutureType(type);
@@ -256,6 +335,16 @@ class ClosureContext {
256335
.getStandardUpperBound(
257336
_inferredUnwrappedReturnOrYieldType, unwrappedType);
258337
}
338+
return;
339+
}
340+
341+
// If we are not inferring a type we can immediately check that the return
342+
// is valid.
343+
if (checkValidReturn(inferrer, declaredReturnType, statement, type) &&
344+
statement.expression != null) {
345+
inferrer.ensureAssignable(returnOrYieldContext, type,
346+
statement.expression, statement.fileOffset,
347+
isReturnFromAsync: isAsync, isVoidAllowed: true);
259348
}
260349
}
261350

@@ -294,13 +383,20 @@ class ClosureContext {
294383
assert(_needToInferReturnType);
295384
DartType inferredType =
296385
inferrer.inferReturnType(_inferredUnwrappedReturnOrYieldType);
297-
if (!_analyzerSubtypeOf(inferrer, inferredType, returnOrYieldContext)) {
386+
if (!inferrer.typeSchemaEnvironment
387+
.isSubtypeOf(inferredType, returnOrYieldContext)) {
298388
// If the inferred return type isn't a subtype of the context, we use the
299389
// context.
300390
inferredType = greatestClosure(inferrer.coreTypes, returnOrYieldContext);
301391
}
302392

303-
return _wrapAsyncOrGenerator(inferrer, inferredType);
393+
inferredType = _wrapAsyncOrGenerator(inferrer, inferredType);
394+
for (int i = 0; i < returnStatements.length; ++i) {
395+
checkValidReturn(inferrer, inferredType, returnStatements[i],
396+
returnExpressionTypes[i]);
397+
}
398+
399+
return inferredType;
304400
}
305401

306402
DartType _wrapAsyncOrGenerator(TypeInferrerImpl inferrer, DartType type) {
@@ -316,19 +412,6 @@ class ClosureContext {
316412
return type;
317413
}
318414
}
319-
320-
static bool _analyzerSubtypeOf(
321-
TypeInferrerImpl inferrer, DartType subtype, DartType supertype) {
322-
if (supertype is VoidType) {
323-
if (subtype is VoidType) return true;
324-
if (subtype is InterfaceType &&
325-
identical(subtype.classNode, inferrer.coreTypes.nullClass)) {
326-
return true;
327-
}
328-
return false;
329-
}
330-
return inferrer.typeSchemaEnvironment.isSubtypeOf(subtype, supertype);
331-
}
332415
}
333416

334417
/// Enum denoting the kinds of contravariance check that might need to be
@@ -585,22 +668,9 @@ abstract class TypeInferrerImpl extends TypeInferrer {
585668
Expression ensureAssignable(DartType expectedType, DartType actualType,
586669
Expression expression, int fileOffset,
587670
{bool isReturnFromAsync: false,
588-
bool isReturn: false,
589-
bool isVoidAllowed,
590-
bool isArrow: false,
591-
DartType declaredReturnType,
671+
bool isVoidAllowed: false,
592672
Template<Message Function(DartType, DartType)> template}) {
593-
isVoidAllowed ??= isArrow;
594673
assert(expectedType != null);
595-
if (isReturn &&
596-
!isArrow &&
597-
!isValidReturn(declaredReturnType, actualType, isReturnFromAsync)) {
598-
TreeNode parent = expression.parent;
599-
Expression errorNode = helper.wrapInProblem(
600-
expression, messageReturnFromVoidFunction, noLength);
601-
parent?.replaceChild(expression, errorNode);
602-
return errorNode;
603-
}
604674
expectedType = greatestClosure(coreTypes, expectedType);
605675

606676
DartType initialExpectedType = expectedType;
@@ -617,28 +687,6 @@ abstract class TypeInferrerImpl extends TypeInferrer {
617687
expectedType = futuredExpectedType;
618688
}
619689
}
620-
if (isReturn && !isArrow) {
621-
if (expectedType is VoidType) {
622-
isVoidAllowed = true;
623-
if (actualType is! VoidType &&
624-
actualType is! DynamicType &&
625-
!isNull(actualType)) {
626-
// Error: not assignable. Perform error recovery.
627-
TreeNode parent = expression.parent;
628-
Expression errorNode = helper.wrapInProblem(
629-
expression, messageReturnFromVoidFunction, noLength);
630-
parent?.replaceChild(expression, errorNode);
631-
return errorNode;
632-
}
633-
} else {
634-
DartType flattened = typeSchemaEnvironment.unfutureType(expectedType);
635-
if (flattened is VoidType) {
636-
isVoidAllowed = true;
637-
} else {
638-
isVoidAllowed = expectedType is DynamicType;
639-
}
640-
}
641-
}
642690

643691
// We don't need to insert assignability checks when doing top level type
644692
// inference since top level type inference only cares about the type that
@@ -721,63 +769,6 @@ abstract class TypeInferrerImpl extends TypeInferrer {
721769
}
722770
}
723771

724-
bool isValidReturn(
725-
DartType returnType, DartType expressionType, bool isAsync) {
726-
final DartType t = returnType;
727-
final DartType s = expressionType;
728-
if (!isAsync) {
729-
if (t is DynamicType) {
730-
// * `return exp;` where `exp` has static type `S` is a valid return if:
731-
// * `T` is `dynamic`
732-
return true;
733-
}
734-
735-
if (t is VoidType) {
736-
// * `return exp;` where `exp` has static type `S` is a valid return if:
737-
// * `T` is `void`
738-
// * and `S` is `void` or `dynamic` or `Null`
739-
return s is VoidType || s is DynamicType || isNull(s);
740-
} else {
741-
// * `return exp;` where `exp` has static type `S` is a valid return if:
742-
// * `T` is not `void`
743-
// * and `S` is not `void`
744-
// * and `S` is assignable to `T`
745-
return s is! VoidType;
746-
}
747-
}
748-
final DartType flattenT = typeSchemaEnvironment.unfutureType(t);
749-
750-
// * `return exp;` where `exp` has static type `S` is a valid return if:
751-
// * `flatten(T)` is `dynamic` or `Null`
752-
if (flattenT is DynamicType || isNull(flattenT)) return true;
753-
754-
// * `return exp;` where `exp` has static type `S` is a valid return if:
755-
// * `T` is `void`
756-
// * and `S` is `void`, `dynamic` or `Null`
757-
if (t is VoidType) {
758-
if (s is VoidType || s is DynamicType || isNull(s)) return true;
759-
} else {
760-
final DartType flattenS = typeSchemaEnvironment.unfutureType(s);
761-
// * `return exp;` where `exp` has static type `S` is a valid return if:
762-
// * `T` is not `void`
763-
// * `flatten(T)` is `void`
764-
// * and `flatten(S)` is `void`, `dynamic` or `Null`
765-
if (flattenT is VoidType) {
766-
if (flattenS is VoidType ||
767-
flattenS is DynamicType ||
768-
isNull(flattenS)) {
769-
return true;
770-
}
771-
}
772-
773-
// * `return exp;` where `exp` has static type `S` is a valid return if:
774-
// * `T` is not `void`
775-
// * and `flatten(S)` is not `void`
776-
if (flattenS is! VoidType) return true;
777-
}
778-
return false;
779-
}
780-
781772
bool isNull(DartType type) {
782773
return type is InterfaceType && type.classNode == coreTypes.nullClass;
783774
}
@@ -1215,10 +1206,12 @@ abstract class TypeInferrerImpl extends TypeInferrer {
12151206
assert(closureContext == null);
12161207
this.helper = helper;
12171208
var actualType = inferExpression(
1218-
initializer, declaredType ?? const UnknownType(), declaredType != null);
1209+
initializer, declaredType ?? const UnknownType(), declaredType != null,
1210+
isVoidAllowed: true);
12191211
if (declaredType != null) {
12201212
ensureAssignable(
1221-
declaredType, actualType, initializer, initializer.fileOffset);
1213+
declaredType, actualType, initializer, initializer.fileOffset,
1214+
isVoidAllowed: declaredType is VoidType);
12221215
}
12231216
this.helper = null;
12241217
}

0 commit comments

Comments
 (0)