// 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. // @dart = 2.7 import 'package:async_helper/async_helper.dart'; 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/world.dart'; import 'package:compiler/src/universe/call_structure.dart'; import 'package:compiler/src/universe/selector.dart'; import 'package:expect/expect.dart'; import '../helpers/program_lookup.dart'; import '../helpers/memory_compiler.dart'; const String code = ''' class A { // Both method1 implementations need type arguments. @pragma('dart2js:noInline') method1(T t) => t is T; // One of the method2 implementations need type arguments. @pragma('dart2js:noInline') method2(T t) => t is T; // None of the method3 implementations need type arguments. @pragma('dart2js:noInline') method3(T t) => false; } class B { @pragma('dart2js:noInline') method1(T t) => t is T; @pragma('dart2js:noInline') method2(T t) => true; @pragma('dart2js:noInline') method3(T t) => true; } // A call to either A.method1 or B.method1. @pragma('dart2js:noInline') call1(c) => c.method1(0); // A call to A.method1. @pragma('dart2js:noInline') call1a() => new A().method1(0); // A call to B.method1. @pragma('dart2js:noInline') call1b() => new B().method1(0); // A call to either A.method2 or B.method2. @pragma('dart2js:noInline') call2(c) => c.method2(0); // A call to A.method2. @pragma('dart2js:noInline') call2a() => new A().method2(0); // A call to B.method2. @pragma('dart2js:noInline') call2b() => new B().method2(0); // A call to either A.method3 or B.method3. @pragma('dart2js:noInline') call3(c) => c.method3(0); // A call to A.method3. @pragma('dart2js:noInline') call3a() => new A().method3(0); // A call to B.method3. @pragma('dart2js:noInline') call3b() => new B().method3(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 = new ProgramLookup(backendStrategy); js.Name getName(String name, int typeArguments) { return backendStrategy.namerForTesting.invocationName(new Selector.call( new PublicName(name), new CallStructure(1, const [], typeArguments))); } void checkParameters(String name, {int expectedParameterCount, bool needsTypeArguments}) { FunctionEntity function = lookupMember(elementEnvironment, name); Expect.equals( needsTypeArguments, rtiNeed.methodNeedsTypeArguments(function), "Unexpected type argument need for $function."); Method method = programLookup.getMethod(function); js.Fun fun = method.code; 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, {int expectedTypeArguments}) { FunctionEntity function = lookupMember(elementEnvironment, name); Method method = programLookup.getMethod(function); js.Fun fun = method.code; js.Name selector = getName(targetName, expectedTypeArguments); bool callFound = false; forEachNode(fun, onCall: (js.Call node) { js.Expression 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); }); }