Reland fix for #28233: add hint for missing returns to function expressions
The bulk of this change is actually correcting missing returns in the Dart SDK.
Bug: https://github.com/dart-lang/sdk/issues/28233
Change-Id: I52bcbc6c6f4a129d3fc22003f4448a7c3d4487ad
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/100301
Commit-Queue: Samuel Rawlins <srawlins@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
diff --git a/pkg/analyzer/lib/src/generated/resolver.dart b/pkg/analyzer/lib/src/generated/resolver.dart
index 0382710..91657b9 100644
--- a/pkg/analyzer/lib/src/generated/resolver.dart
+++ b/pkg/analyzer/lib/src/generated/resolver.dart
@@ -473,6 +473,14 @@
}
@override
+ void visitFunctionExpression(FunctionExpression node) {
+ if (node.parent is! FunctionDeclaration) {
+ _checkForMissingReturn(null, node.body, node.element, node);
+ }
+ super.visitFunctionExpression(node);
+ }
+
+ @override
void visitImportDirective(ImportDirective node) {
_checkForDeprecatedMemberUse(node.uriElement, node);
ImportElement importElement = node.element;
@@ -1034,7 +1042,27 @@
var flattenedType =
body.isAsynchronous ? _typeSystem.flatten(returnType) : returnType;
- // dynamic/Null/void are allowed to omit a return.
+ // Function expressions without a return will have their return type set
+ // to `Null` regardless of their context type. So we need to figure out
+ // if a return type was expected from the original downwards context.
+ //
+ // This helps detect hint cases like `int Function() f = () {}`.
+ // See https://github.com/dart-lang/sdk/issues/28233 for context.
+ if (flattenedType.isDartCoreNull && functionNode is FunctionExpression) {
+ var contextType = InferenceContext.getContext(functionNode);
+ if (contextType is FunctionType) {
+ returnType = contextType.returnType;
+ flattenedType = body.isAsynchronous
+ ? returnType.flattenFutures(_typeSystem)
+ : returnType;
+ }
+ }
+
+ // dynamic, Null, void, and FutureOr<T> where T is (dynamic, Null, void)
+ // are allowed to omit a return.
+ if (flattenedType.isDartAsyncFutureOr) {
+ flattenedType = (flattenedType as InterfaceType).typeArguments[0];
+ }
if (flattenedType.isDynamic ||
flattenedType.isDartCoreNull ||
flattenedType.isVoid) {
diff --git a/pkg/analyzer/test/src/diagnostics/missing_return_test.dart b/pkg/analyzer/test/src/diagnostics/missing_return_test.dart
index 41042fb..902c4f3 100644
--- a/pkg/analyzer/test/src/diagnostics/missing_return_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/missing_return_test.dart
@@ -79,11 +79,96 @@
''', [HintCode.MISSING_RETURN]);
}
+ test_functionExpression_declared() async {
+ await assertNoErrorsInCode(r'''
+main() {
+ f() {} // no hint
+}
+''');
+ }
+
+ test_functionExpression_expression() async {
+ await assertNoErrorsInCode(r'''
+main() {
+ int Function() f = () => null; // no hint
+}
+''');
+ }
+
+ test_functionExpression_futureOrDynamic() async {
+ await assertNoErrorsInCode(r'''
+import 'dart:async';
+main() {
+ FutureOr<dynamic> Function() f = () { print(42); };
+}
+''');
+ }
+
+ test_functionExpression_futureOrInt() async {
+ await assertErrorCodesInCode(r'''
+import 'dart:async';
+main() {
+ FutureOr<int> Function() f = () { print(42); };
+}
+''', [HintCode.MISSING_RETURN]);
+ }
+
+ test_functionExpression_inferred() async {
+ await assertErrorCodesInCode(r'''
+main() {
+ int Function() f = () { print(42); };
+}
+''', [HintCode.MISSING_RETURN]);
+ }
+
+ test_functionExpression_inferred_dynamic() async {
+ await assertNoErrorsInCode(r'''
+main() {
+ Function() f = () { print(42); }; // no hint
+}
+''');
+ }
+
+ test_functionExpressionAsync_inferred() async {
+ await assertErrorCodesInCode(r'''
+main() {
+ Future<int> Function() f = () async { print(42); };
+}
+''', [HintCode.MISSING_RETURN]);
+ }
+
+ test_functionExpressionAsync_inferred_dynamic() async {
+ await assertNoErrorsInCode(r'''
+main() {
+ Future Function() f = () async { print(42); }; // no hint
+}
+''');
+ }
+
test_method() async {
await assertErrorCodesInCode(r'''
class A {
int m() {}
-}''', [HintCode.MISSING_RETURN]);
+}
+''', [HintCode.MISSING_RETURN]);
+ }
+
+ test_method_futureOrDynamic() async {
+ await assertNoErrorsInCode(r'''
+import 'dart:async';
+class A {
+ FutureOr<dynamic> m() {}
+}
+''');
+ }
+
+ test_method_futureOrInt() async {
+ await assertErrorCodesInCode(r'''
+import 'dart:async';
+class A {
+ FutureOr<int> m() {}
+}
+''', [HintCode.MISSING_RETURN]);
}
test_method_inferred() async {
diff --git a/pkg/compiler/lib/src/js/rewrite_async.dart b/pkg/compiler/lib/src/js/rewrite_async.dart
index b3768c4..01cad9d 100644
--- a/pkg/compiler/lib/src/js/rewrite_async.dart
+++ b/pkg/compiler/lib/src/js/rewrite_async.dart
@@ -1385,8 +1385,9 @@
if (clause is js.Case) {
return new js.Case(
clause.expression, translateToBlock(clause.body));
- } else if (clause is js.Default) {
- return new js.Default(translateToBlock(clause.body));
+ } else {
+ return new js.Default(
+ translateToBlock((clause as js.Default).body));
}
}).toList();
addStatement(new js.Switch(key, cases));
diff --git a/pkg/compiler/lib/src/js_backend/interceptor_data.dart b/pkg/compiler/lib/src/js_backend/interceptor_data.dart
index cc38bab..ee38c00 100644
--- a/pkg/compiler/lib/src/js_backend/interceptor_data.dart
+++ b/pkg/compiler/lib/src/js_backend/interceptor_data.dart
@@ -12,6 +12,7 @@
import '../inferrer/abstract_value_domain.dart';
import '../js/js.dart' as jsAst;
import '../serialization/serialization.dart';
+import '../universe/class_set.dart';
import '../universe/selector.dart';
import '../world.dart' show JClosedWorld;
import 'namer.dart' show ModularNamer, suffixForGetInterceptor;
@@ -252,6 +253,7 @@
if (result == null) result = new Set<ClassEntity>();
result.add(subclass);
}
+ return IterationStep.CONTINUE;
});
}
return result;
diff --git a/pkg/compiler/lib/src/js_backend/runtime_types.dart b/pkg/compiler/lib/src/js_backend/runtime_types.dart
index 3dd3633..fa7a7d1 100644
--- a/pkg/compiler/lib/src/js_backend/runtime_types.dart
+++ b/pkg/compiler/lib/src/js_backend/runtime_types.dart
@@ -22,6 +22,7 @@
import '../options.dart';
import '../serialization/serialization.dart';
import '../universe/class_hierarchy.dart';
+import '../universe/class_set.dart';
import '../universe/codegen_world_builder.dart';
import '../universe/feature.dart';
import '../universe/selector.dart';
@@ -1525,6 +1526,7 @@
closedWorld.classHierarchy.forEachStrictSubtypeOf(cls,
(ClassEntity sub) {
potentiallyNeedTypeArguments(sub);
+ return IterationStep.CONTINUE;
});
} else if (entity is FunctionEntity) {
methodsNeedingTypeArguments.add(entity);
diff --git a/pkg/compiler/lib/src/js_model/js_world_builder.dart b/pkg/compiler/lib/src/js_model/js_world_builder.dart
index fa4c4d9..3785a41 100644
--- a/pkg/compiler/lib/src/js_model/js_world_builder.dart
+++ b/pkg/compiler/lib/src/js_model/js_world_builder.dart
@@ -108,6 +108,7 @@
.getClassHierarchyNode(closedWorld.commonElements.objectClass)
.forEachSubclass((ClassEntity cls) {
convertClassSet(closedWorld.classHierarchy.getClassSet(cls));
+ return IterationStep.CONTINUE;
}, ClassHierarchyNode.ALL);
Set<MemberEntity> liveInstanceMembers =
diff --git a/pkg/compiler/lib/src/old_to_new_api.dart b/pkg/compiler/lib/src/old_to_new_api.dart
index 808e242..a5d7c45 100644
--- a/pkg/compiler/lib/src/old_to_new_api.dart
+++ b/pkg/compiler/lib/src/old_to_new_api.dart
@@ -23,6 +23,8 @@
@override
Future<Input> readFromUri(Uri uri, {InputKind inputKind: InputKind.UTF8}) {
+ // The switch handles all enum values, but not null.
+ // ignore: missing_return
return _inputProvider(uri).then((/*String|List<int>*/ data) {
switch (inputKind) {
case InputKind.UTF8:
diff --git a/pkg/compiler/lib/src/universe/class_hierarchy.dart b/pkg/compiler/lib/src/universe/class_hierarchy.dart
index 776b4f1..49a899e 100644
--- a/pkg/compiler/lib/src/universe/class_hierarchy.dart
+++ b/pkg/compiler/lib/src/universe/class_hierarchy.dart
@@ -195,10 +195,12 @@
getClassHierarchyNode(_commonElements.objectClass);
node.forEachSubclass((ClassEntity cls) {
getClassHierarchyNode(cls).writeToDataSink(sink);
+ return null;
}, ClassHierarchyNode.ALL);
ClassSet set = getClassSet(_commonElements.objectClass);
set.forEachSubclass((ClassEntity cls) {
getClassSet(cls).writeToDataSink(sink);
+ return null;
}, ClassHierarchyNode.ALL);
sink.end(tag);
}
@@ -836,6 +838,7 @@
if (builder._isSubtypeOf(z, y)) {
classes.add(z);
}
+ return null;
}, ClassHierarchyNode.ALL, strict: strict);
}
diff --git a/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart b/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart
index 51d5070..b97c08b 100644
--- a/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart
@@ -1591,6 +1591,7 @@
} else {
reportInvalid(
node, 'No support for ${target.runtimeType} in a static-get.');
+ return null;
}
});
}
diff --git a/pkg/front_end/test/fasta/generator_to_string_test.dart b/pkg/front_end/test/fasta/generator_to_string_test.dart
index 6987266..7f59d4b 100644
--- a/pkg/front_end/test/fasta/generator_to_string_test.dart
+++ b/pkg/front_end/test/fasta/generator_to_string_test.dart
@@ -241,5 +241,6 @@
"offset: 4, prefixGenerator: , isInitializer: false, isSuper: false)",
new KernelUnexpectedQualifiedUseGenerator(
helper, token, generator, false));
+ return Future<void>.value();
});
}
diff --git a/pkg/front_end/test/fasta/unlinked_scope_test.dart b/pkg/front_end/test/fasta/unlinked_scope_test.dart
index e58294c..fb933f8 100644
--- a/pkg/front_end/test/fasta/unlinked_scope_test.dart
+++ b/pkg/front_end/test/fasta/unlinked_scope_test.dart
@@ -108,5 +108,6 @@
testExpression("unresolved");
testExpression("a + b", "a.+(b)");
testExpression("a = b");
+ return Future<void>.value();
});
}
diff --git a/pkg/front_end/test/src/base/processed_options_test.dart b/pkg/front_end/test/src/base/processed_options_test.dart
index 605b5ac..0abc4cb 100644
--- a/pkg/front_end/test/src/base/processed_options_test.dart
+++ b/pkg/front_end/test/src/base/processed_options_test.dart
@@ -22,6 +22,7 @@
defineReflectiveSuite(() {
defineReflectiveTests(ProcessedOptionsTest);
});
+ return Future<void>.value();
});
}
diff --git a/pkg/front_end/tool/_fasta/entry_points.dart b/pkg/front_end/tool/_fasta/entry_points.dart
index a3cd069..18b7445 100644
--- a/pkg/front_end/tool/_fasta/entry_points.dart
+++ b/pkg/front_end/tool/_fasta/entry_points.dart
@@ -192,6 +192,7 @@
// TODO(ahe): Extend this entry point so it can replace
// batchEntryPoint.
new IncrementalCompiler(c);
+ return Future<void>.value();
});
}
diff --git a/pkg/vm/test/frontend_server_test.dart b/pkg/vm/test/frontend_server_test.dart
index 5310517..a56d500 100644
--- a/pkg/vm/test/frontend_server_test.dart
+++ b/pkg/vm/test/frontend_server_test.dart
@@ -83,11 +83,12 @@
new StreamController<List<int>>();
final ReceivePort compileCalled = new ReceivePort();
when(compiler.compile(any, any, generator: anyNamed('generator')))
- .thenAnswer((Invocation invocation) {
+ .thenAnswer((Invocation invocation) async {
expect(invocation.positionalArguments[0], equals('server.dart'));
expect(
invocation.positionalArguments[1]['sdk-root'], equals('sdkroot'));
compileCalled.sendPort.send(true);
+ return true;
});
Future<int> result = starter(
@@ -116,11 +117,12 @@
new StreamController<List<int>>();
final ReceivePort compileCalled = new ReceivePort();
when(compiler.compile(any, any, generator: anyNamed('generator')))
- .thenAnswer((Invocation invocation) {
+ .thenAnswer((Invocation invocation) async {
expect(invocation.positionalArguments[0], equals('server.dart'));
expect(
invocation.positionalArguments[1]['sdk-root'], equals('sdkroot'));
compileCalled.sendPort.send(true);
+ return true;
});
Future<int> result = starter(
@@ -141,12 +143,13 @@
final ReceivePort compileCalled = new ReceivePort();
int counter = 1;
when(compiler.compile(any, any, generator: anyNamed('generator')))
- .thenAnswer((Invocation invocation) {
+ .thenAnswer((Invocation invocation) async {
expect(invocation.positionalArguments[0],
equals('server${counter++}.dart'));
expect(
invocation.positionalArguments[1]['sdk-root'], equals('sdkroot'));
compileCalled.sendPort.send(true);
+ return true;
});
Future<int> result = starter(
@@ -180,7 +183,7 @@
final ReceivePort recompileCalled = new ReceivePort();
when(compiler.recompileDelta(entryPoint: null))
- .thenAnswer((Invocation invocation) {
+ .thenAnswer((Invocation invocation) async {
recompileCalled.sendPort.send(true);
});
Future<int> result = starter(
@@ -208,7 +211,7 @@
final ReceivePort recompileCalled = new ReceivePort();
when(compiler.recompileDelta(entryPoint: 'file2.dart'))
- .thenAnswer((Invocation invocation) {
+ .thenAnswer((Invocation invocation) async {
recompileCalled.sendPort.send(true);
});
Future<int> result = starter(
@@ -275,7 +278,7 @@
final ReceivePort recompileCalled = new ReceivePort();
when(compiler.recompileDelta(entryPoint: null))
- .thenAnswer((Invocation invocation) {
+ .thenAnswer((Invocation invocation) async {
recompileCalled.sendPort.send(true);
});
Future<int> result = starter(
diff --git a/tests/compiler/dart2js/model/class_set_test.dart b/tests/compiler/dart2js/model/class_set_test.dart
index 5ceb1e8..ea78222 100644
--- a/tests/compiler/dart2js/model/class_set_test.dart
+++ b/tests/compiler/dart2js/model/class_set_test.dart
@@ -344,6 +344,7 @@
List<ClassEntity> visited = <ClassEntity>[];
classSet.forEachSubclass((cls) {
visited.add(cls);
+ return IterationStep.CONTINUE;
}, ClassHierarchyNode.ALL);
Expect.listEquals(
@@ -381,6 +382,7 @@
List<ClassEntity> visited = <ClassEntity>[];
classSet.forEachSubtype((cls) {
visited.add(cls);
+ return IterationStep.CONTINUE;
}, ClassHierarchyNode.ALL);
Expect.listEquals(
diff --git a/tests/compiler/dart2js/model/receiver_type_test.dart b/tests/compiler/dart2js/model/receiver_type_test.dart
index 663c6e3..3ad5042 100644
--- a/tests/compiler/dart2js/model/receiver_type_test.dart
+++ b/tests/compiler/dart2js/model/receiver_type_test.dart
@@ -6,6 +6,7 @@
import 'package:async_helper/async_helper.dart';
import 'package:compiler/src/elements/entities.dart';
import 'package:compiler/src/inferrer/typemasks/masks.dart';
+import 'package:compiler/src/universe/class_set.dart';
import 'package:compiler/src/universe/selector.dart';
import 'package:compiler/src/world.dart';
import 'package:expect/expect.dart';
@@ -49,7 +50,8 @@
Selector callSelector = new Selector.callClosure(0);
closedWorld.classHierarchy.forEachStrictSubclassOf(
closedWorld.commonElements.objectClass, (ClassEntity cls) {
- if (cls.library.canonicalUri.scheme != 'memory') return;
+ if (cls.library.canonicalUri.scheme != 'memory')
+ return IterationStep.CONTINUE;
TypeMask mask = new TypeMask.nonNullSubclass(cls, closedWorld);
TypeMask receiverType = closedWorld.computeReceiverType(callSelector, mask);
@@ -65,6 +67,7 @@
Expect.equals(expected, '$receiverType',
"Unexpected receiver type for $callSelector on $mask");
}
+ return IterationStep.CONTINUE;
});
Expect.equals(2, closureCount);
diff --git a/tests/compiler/dart2js/model/world_test.dart b/tests/compiler/dart2js/model/world_test.dart
index c49ff3d..b2106a8 100644
--- a/tests/compiler/dart2js/model/world_test.dart
+++ b/tests/compiler/dart2js/model/world_test.dart
@@ -99,6 +99,7 @@
List<ClassEntity> visited = <ClassEntity>[];
forEach(cls, (ClassEntity c) {
visited.add(c);
+ return IterationStep.CONTINUE;
});
checkClasses('forEach($property)', cls, visited, expectedClasses,
exact: exact);
@@ -424,6 +425,7 @@
if (allClasses.contains(other)) {
strictSubclasses.add(other);
}
+ return IterationStep.CONTINUE;
});
Expect.setEquals(subclasses, strictSubclasses,
"Unexpected strict subclasses of $cls: ${strictSubclasses}.");
@@ -433,6 +435,7 @@
if (allClasses.contains(other)) {
strictSubtypes.add(other);
}
+ return IterationStep.CONTINUE;
});
Expect.setEquals(subtypes, strictSubtypes,
"Unexpected strict subtypes of $cls: $strictSubtypes.");
diff --git a/tests/compiler/dart2js/sourcemaps/helpers/sourcemap_helper.dart b/tests/compiler/dart2js/sourcemaps/helpers/sourcemap_helper.dart
index f38c7a6..9e4273d 100644
--- a/tests/compiler/dart2js/sourcemaps/helpers/sourcemap_helper.dart
+++ b/tests/compiler/dart2js/sourcemaps/helpers/sourcemap_helper.dart
@@ -262,6 +262,7 @@
}
return null;
}
+ return null;
});
}
}