diff --git a/pkg/compiler/lib/src/compile_time_constants.dart b/pkg/compiler/lib/src/compile_time_constants.dart index 817f22f8074..521bdd66c19 100644 --- a/pkg/compiler/lib/src/compile_time_constants.dart +++ b/pkg/compiler/lib/src/compile_time_constants.dart @@ -268,10 +268,6 @@ abstract class ConstantCompilerBase implements ConstantCompiler { ErroneousElement element = elementType.element; reporter.reportErrorMessage( node, element.messageKind, element.messageArguments); - } else { - assert(elementType is MethodTypeVariableType); - // Used to `reportErrorMessage` here, but with Dart 2 upcoming - // very soon we do not emit this message any more. } } else { // We need to throw an exception at runtime. diff --git a/pkg/compiler/lib/src/diagnostics/messages.dart b/pkg/compiler/lib/src/diagnostics/messages.dart index c19fefbb0ad..7d1be45ed62 100644 --- a/pkg/compiler/lib/src/diagnostics/messages.dart +++ b/pkg/compiler/lib/src/diagnostics/messages.dart @@ -404,6 +404,8 @@ enum MessageKind { TYPE_ARGUMENT_COUNT_MISMATCH, TYPE_VARIABLE_IN_CONSTANT, TYPE_VARIABLE_WITHIN_STATIC_MEMBER, + TYPE_VARIABLE_FROM_METHOD_NOT_REIFIED, + TYPE_VARIABLE_FROM_METHOD_CONSIDERED_DYNAMIC, TYPEDEF_FORMAL_WITH_DEFAULT, UNARY_OPERATOR_BAD_ARITY, UNBOUND_LABEL, @@ -1195,6 +1197,43 @@ class C { } void main() => new C().m(null); +""" + ]), + + MessageKind.TYPE_VARIABLE_FROM_METHOD_NOT_REIFIED: const MessageTemplate( + MessageKind.TYPE_VARIABLE_FROM_METHOD_NOT_REIFIED, + "Method type variables do not have a runtime value.", + howToFix: "Try using the upper bound of the type variable, " + "or refactor the code to avoid needing this runtime value.", + examples: const [ + """ +// Method type variables are not reified, so they cannot be returned. +Type f() => T; + +main() => f(); +""", + """ +// Method type variables are not reified, so they cannot be tested dynamically. +bool f(Object o) => o is T; + +main() => f(42); +""" + ]), + + MessageKind.TYPE_VARIABLE_FROM_METHOD_CONSIDERED_DYNAMIC: + const MessageTemplate( + MessageKind.TYPE_VARIABLE_FROM_METHOD_CONSIDERED_DYNAMIC, + "Method type variables are treated as `dynamic` in `as` " + "expressions.", + howToFix: + "Try using the upper bound of the type variable, or check " + "that the blind success of the test does not introduce bugs.", + examples: const [ + """ +// Method type variables are not reified, so they cannot be tested dynamically. +bool f(Object o) => o as T; + +main() => f(42); """ ]), diff --git a/pkg/compiler/lib/src/resolution/members.dart b/pkg/compiler/lib/src/resolution/members.dart index d939a65fa74..0cc75e907f6 100644 --- a/pkg/compiler/lib/src/resolution/members.dart +++ b/pkg/compiler/lib/src/resolution/members.dart @@ -1131,6 +1131,13 @@ class ResolverVisitor extends MappingVisitor { sendStructure = new IsStructure(type); } + // GENERIC_METHODS: Method type variables are not reified so we must warn + // about the error which will occur at runtime. + if (type is MethodTypeVariableType) { + reporter.reportWarningMessage( + node, MessageKind.TYPE_VARIABLE_FROM_METHOD_NOT_REIFIED); + } + registry.registerTypeUse(new TypeUse.isCheck(type)); registry.registerSendStructure(node, sendStructure); return const NoneResult(); @@ -1145,6 +1152,13 @@ class ResolverVisitor extends MappingVisitor { ResolutionDartType type = resolveTypeAnnotation(typeNode, registerCheckedModeCheck: false); + // GENERIC_METHODS: Method type variables are not reified, so we must inform + // the developer about the potentially bug-inducing semantics. + if (type is MethodTypeVariableType) { + reporter.reportHintMessage( + node, MessageKind.TYPE_VARIABLE_FROM_METHOD_CONSIDERED_DYNAMIC); + } + registry.registerTypeUse(new TypeUse.asCast(type)); registry.registerSendStructure(node, new AsStructure(type)); return const NoneResult(); @@ -1913,6 +1927,12 @@ class ResolverVisitor extends MappingVisitor { // TODO(johnniwinther): Clean up registration of elements and selectors // for this case. } else { + // GENERIC_METHODS: Method type variables are not reified so we must warn + // about the error which will occur at runtime. + if (element.type is MethodTypeVariableType) { + reporter.reportWarningMessage( + node, MessageKind.TYPE_VARIABLE_FROM_METHOD_NOT_REIFIED); + } semantics = new StaticAccess.typeParameterTypeLiteral(element); } diff --git a/tests/compiler/dart2js/old_frontend/method_type_variable_test.dart b/tests/compiler/dart2js/old_frontend/method_type_variable_test.dart new file mode 100644 index 00000000000..b0c0d4e0ee6 --- /dev/null +++ b/tests/compiler/dart2js/old_frontend/method_type_variable_test.dart @@ -0,0 +1,141 @@ +import 'package:async_helper/async_helper.dart'; +import 'package:compiler/src/diagnostics/messages.dart'; +import 'package:expect/expect.dart'; +import '../memory_compiler.dart'; + +runTest(String code, + {List expectedWarnings: const [], + List expectedHints: const []}) async { + print('--test--------------------------------------------------------------'); + print(code); + DiagnosticCollector collector = new DiagnosticCollector(); + await runCompiler( + memorySourceFiles: {'main.dart': code}, diagnosticHandler: collector); + Expect.equals(0, collector.errors.length, "Unexpected errors."); + Expect.listEquals( + expectedWarnings, + collector.warnings.map((m) => m.messageKind).toList(), + "Unexpected warnings."); + Expect.listEquals(expectedHints, + collector.hints.map((m) => m.messageKind).toList(), "Unexpected hints."); +} + +class Test { + final String code; + final List warnings; + final List hints; + + const Test(this.code, + {this.warnings: const [], + this.hints: const []}); +} + +const List tests = const [ + /// Is-test on method type variable in unused static method. + const Test(''' +method(T t) => t is T; +main() {} +'''), + + /// Is-test on method type variable in used static method. + const Test(''' +method(T t) => t is T; +main() => method(0); +''', warnings: const [ + MessageKind.TYPE_VARIABLE_FROM_METHOD_NOT_REIFIED + ]), + + /// Is-test on method type variable in unused instance method. + const Test(''' +class C { + method(T t) => t is T; +} +main() => new C(); +'''), + + /// Is-test on method type variable in used instance method. + const Test(''' +class C { + method(T t) => t is T; +} +main() => new C().method(0); +''', warnings: const [ + MessageKind.TYPE_VARIABLE_FROM_METHOD_NOT_REIFIED + ]), + + /// As-cast on method type variable in unused static method. + const Test(''' +method(T t) => t as T; +main() {} +'''), + + /// As-cast on method type variable in used static method. + const Test(''' +method(T t) => t as T; +main() => method(0); +''', hints: const [ + MessageKind.TYPE_VARIABLE_FROM_METHOD_CONSIDERED_DYNAMIC + ]), + + /// As-cast on method type variable in unused instance method. + const Test(''' +class C { + method(T t) => t as T; +} +main() => new C(); +'''), + + /// As-cast on method type variable in used instance method. + const Test(''' +class C { + method(T t) => t as T; +} +main() => new C().method(0); +''', hints: const [ + MessageKind.TYPE_VARIABLE_FROM_METHOD_CONSIDERED_DYNAMIC + ]), + + /// Method type variable literal in unused static method. + const Test(''' +method() => T; +main() {} +'''), + + /// Method type variable literal in used static method. + const Test(''' +method() => T; +main() => method(); +''', warnings: const [ + MessageKind.TYPE_VARIABLE_FROM_METHOD_NOT_REIFIED + ]), + + /// Method type variable literal in unused instance method. + const Test(''' +class C { + method() => T; +} +main() => new C(); +'''), + + /// Method type variable literal in used instance method. + const Test(''' +class C { + method() => T; +} +main() => new C().method(); +''', warnings: const [ + MessageKind.TYPE_VARIABLE_FROM_METHOD_NOT_REIFIED + ]), +]; + +main() { + asyncTest(() async { + for (Test test in tests) { + await runTest( + test.code, + expectedWarnings: test.warnings, + expectedHints: test.hints, + ); + } + }); +}