blob: a735e9ec5a42358d4807e5f9a1d369cdbd75b09c [file] [log] [blame]
// 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);
});
}