|  | // Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS file | 
|  | // for details. All rights reserved. Use of this source code is governed by a | 
|  | // BSD-style license that can be found in the LICENSE file. | 
|  |  | 
|  | import 'package:compiler/src/commandline_options.dart'; | 
|  | import 'package:compiler/src/common/elements.dart'; | 
|  | import 'package:compiler/src/compiler.dart'; | 
|  | import 'package:compiler/src/elements/entities.dart'; | 
|  | import 'package:compiler/src/elements/names.dart'; | 
|  | import 'package:compiler/src/js_backend/runtime_types_resolution.dart'; | 
|  | import 'package:compiler/src/js_emitter/model.dart'; | 
|  | import 'package:compiler/src/js_model/js_strategy.dart'; | 
|  | import 'package:compiler/src/js/js.dart' as js; | 
|  | import 'package:compiler/src/js_model/js_world.dart' show JClosedWorld; | 
|  | import 'package:compiler/src/universe/call_structure.dart'; | 
|  | import 'package:compiler/src/universe/selector.dart'; | 
|  | import 'package:expect/async_helper.dart'; | 
|  | import 'package:expect/expect.dart'; | 
|  | import '../helpers/program_lookup.dart'; | 
|  | import 'package:compiler/src/util/memory_compiler.dart'; | 
|  |  | 
|  | const String code = ''' | 
|  | class A { | 
|  | // Both method1 implementations need type arguments. | 
|  | @pragma('dart2js:noInline') | 
|  | method1<T>(T t) => t is T; | 
|  |  | 
|  | // One of the method2 implementations need type arguments. | 
|  | @pragma('dart2js:noInline') | 
|  | method2<T>(T t) => t is T; | 
|  |  | 
|  | // None of the method3 implementations need type arguments. | 
|  | @pragma('dart2js:noInline') | 
|  | method3<T>(T t) => false; | 
|  | } | 
|  |  | 
|  | class B { | 
|  | @pragma('dart2js:noInline') | 
|  | method1<T>(T t) => t is T; | 
|  | @pragma('dart2js:noInline') | 
|  | method2<T>(T t) => true; | 
|  | @pragma('dart2js:noInline') | 
|  | method3<T>(T t) => true; | 
|  | } | 
|  |  | 
|  | // A call to either A.method1 or B.method1. | 
|  | @pragma('dart2js:noInline') | 
|  | call1(c) => c.method1<int>(0); | 
|  |  | 
|  | // A call to A.method1. | 
|  | @pragma('dart2js:noInline') | 
|  | call1a() => A().method1<int>(0); | 
|  |  | 
|  | // A call to B.method1. | 
|  | @pragma('dart2js:noInline') | 
|  | call1b() => B().method1<int>(0); | 
|  |  | 
|  | // A call to either A.method2 or B.method2. | 
|  | @pragma('dart2js:noInline') | 
|  | call2(c) => c.method2<int>(0); | 
|  |  | 
|  | // A call to A.method2. | 
|  | @pragma('dart2js:noInline') | 
|  | call2a() => A().method2<int>(0); | 
|  |  | 
|  | // A call to B.method2. | 
|  | @pragma('dart2js:noInline') | 
|  | call2b() => B().method2<int>(0); | 
|  |  | 
|  | // A call to either A.method3 or B.method3. | 
|  | @pragma('dart2js:noInline') | 
|  | call3(c) => c.method3<int>(0); | 
|  |  | 
|  | // A call to A.method3. | 
|  | @pragma('dart2js:noInline') | 
|  | call3a() => A().method3<int>(0); | 
|  |  | 
|  | // A call to B.method3. | 
|  | @pragma('dart2js:noInline') | 
|  | call3b() => B().method3<int>(0); | 
|  |  | 
|  | main() { | 
|  | call1(new A()); | 
|  | call1(new B()); | 
|  | call1a(); | 
|  | call1b(); | 
|  | call2(new A()); | 
|  | call2(new B()); | 
|  | call2a(); | 
|  | call2b(); | 
|  | call3(new A()); | 
|  | call3(new B()); | 
|  | call3a(); | 
|  | call3b(); | 
|  | } | 
|  | '''; | 
|  |  | 
|  | main() { | 
|  | asyncTest(() async { | 
|  | CompilationResult result = await runCompiler( | 
|  | memorySourceFiles: {'main.dart': code}, | 
|  | options: [Flags.omitImplicitChecks], | 
|  | ); | 
|  | Expect.isTrue(result.isSuccess); | 
|  | Compiler compiler = result.compiler!; | 
|  | JsBackendStrategy backendStrategy = compiler.backendStrategy; | 
|  | JClosedWorld closedWorld = compiler.backendClosedWorldForTesting!; | 
|  | RuntimeTypesNeed rtiNeed = closedWorld.rtiNeed; | 
|  | ElementEnvironment elementEnvironment = closedWorld.elementEnvironment; | 
|  | ProgramLookup programLookup = ProgramLookup(backendStrategy); | 
|  |  | 
|  | js.Name getName(String name, int typeArguments) { | 
|  | return backendStrategy.namerForTesting.invocationName( | 
|  | new Selector.call( | 
|  | PublicName(name), | 
|  | CallStructure(1, const <String>[], typeArguments), | 
|  | ), | 
|  | ); | 
|  | } | 
|  |  | 
|  | void checkParameters( | 
|  | String name, { | 
|  | required int expectedParameterCount, | 
|  | required bool needsTypeArguments, | 
|  | }) { | 
|  | final function = lookupMember(elementEnvironment, name) as FunctionEntity; | 
|  |  | 
|  | Expect.equals( | 
|  | needsTypeArguments, | 
|  | rtiNeed.methodNeedsTypeArguments(function), | 
|  | "Unexpected type argument need for $function.", | 
|  | ); | 
|  | Method method = programLookup.getMethod(function)!; | 
|  |  | 
|  | final fun = method.code as js.Fun; | 
|  | Expect.equals( | 
|  | expectedParameterCount, | 
|  | fun.params.length, | 
|  | "Unexpected parameter count on $function: ${js.nodeToString(fun)}", | 
|  | ); | 
|  | } | 
|  |  | 
|  | // The declarations should have type parameters only when needed. | 
|  | checkParameters( | 
|  | 'A.method1', | 
|  | expectedParameterCount: 2, | 
|  | needsTypeArguments: true, | 
|  | ); | 
|  | checkParameters( | 
|  | 'B.method1', | 
|  | expectedParameterCount: 2, | 
|  | needsTypeArguments: true, | 
|  | ); | 
|  | checkParameters( | 
|  | 'A.method2', | 
|  | expectedParameterCount: 2, | 
|  | needsTypeArguments: true, | 
|  | ); | 
|  | checkParameters( | 
|  | 'B.method2', | 
|  | expectedParameterCount: 1, | 
|  | needsTypeArguments: false, | 
|  | ); | 
|  | checkParameters( | 
|  | 'A.method3', | 
|  | expectedParameterCount: 1, | 
|  | needsTypeArguments: false, | 
|  | ); | 
|  | checkParameters( | 
|  | 'B.method3', | 
|  | expectedParameterCount: 1, | 
|  | needsTypeArguments: false, | 
|  | ); | 
|  |  | 
|  | checkArguments( | 
|  | String name, | 
|  | String targetName, { | 
|  | required int expectedTypeArguments, | 
|  | }) { | 
|  | final function = lookupMember(elementEnvironment, name) as FunctionEntity; | 
|  | Method method = programLookup.getMethod(function)!; | 
|  |  | 
|  | final fun = method.code as js.Fun; | 
|  |  | 
|  | js.Name selector = getName(targetName, expectedTypeArguments); | 
|  | bool callFound = false; | 
|  | forEachNode( | 
|  | fun, | 
|  | onCall: (js.Call node) { | 
|  | js.Node target = js.undefer(node.target); | 
|  | if (target is js.PropertyAccess) { | 
|  | js.Node targetSelector = js.undefer(target.selector); | 
|  | if (targetSelector is js.Name && | 
|  | targetSelector.key == selector.key) { | 
|  | callFound = true; | 
|  | Expect.equals( | 
|  | 1 + expectedTypeArguments, | 
|  | node.arguments.length, | 
|  | "Unexpected argument count in $function call to $targetName: " | 
|  | "${js.nodeToString(fun)}", | 
|  | ); | 
|  | } | 
|  | } | 
|  | }, | 
|  | ); | 
|  | Expect.isTrue( | 
|  | callFound, | 
|  | "No call to $targetName as '${selector.key}' in $function found.", | 
|  | ); | 
|  | } | 
|  |  | 
|  | // The declarations should have type parameters only when needed by the | 
|  | // selector. | 
|  | checkArguments('call1', 'method1', expectedTypeArguments: 1); | 
|  | checkArguments('call1a', 'method1', expectedTypeArguments: 1); | 
|  | checkArguments('call1b', 'method1', expectedTypeArguments: 1); | 
|  | checkArguments('call2', 'method2', expectedTypeArguments: 1); | 
|  | checkArguments('call2a', 'method2', expectedTypeArguments: 1); | 
|  | checkArguments('call2b', 'method2', expectedTypeArguments: 1); | 
|  | checkArguments('call3', 'method3', expectedTypeArguments: 0); | 
|  | checkArguments('call3a', 'method3', expectedTypeArguments: 0); | 
|  | checkArguments('call3b', 'method3', expectedTypeArguments: 0); | 
|  | }); | 
|  | } |