blob: 0f56d382332ef89480e6dbde5af535c78f2e1fe4 [file] [log] [blame]
// Copyright (c) 2015, 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/common/elements.dart';
import 'package:compiler/src/common/names.dart';
import 'package:compiler/src/compiler.dart';
import 'package:compiler/src/elements/entities.dart';
import 'package:compiler/src/js_backend/no_such_method_registry.dart';
import 'package:compiler/src/js_model/elements.dart';
import 'package:compiler/src/js_model/js_world.dart' show JClosedWorld;
import 'package:expect/async_helper.dart';
import 'package:expect/expect.dart';
import 'package:compiler/src/util/memory_compiler.dart';
class NoSuchMethodInfo {
final String className;
final String? superClassName;
final bool hasThrowingSyntax;
final bool hasForwardingSyntax;
final bool isThrowing;
final bool isDefault;
final bool isOther;
final bool isComplexNoReturn;
final bool isComplexReturn;
const NoSuchMethodInfo(
this.className, {
this.superClassName,
this.hasThrowingSyntax = false,
this.hasForwardingSyntax = false,
this.isThrowing = false,
this.isDefault = false,
this.isOther = false,
this.isComplexNoReturn = false,
this.isComplexReturn = false,
});
}
class NoSuchMethodTest {
final String code;
final List<NoSuchMethodInfo> methods;
final bool isNoSuchMethodUsed;
const NoSuchMethodTest(
this.code,
this.methods, {
this.isNoSuchMethodUsed = false,
});
}
const List<NoSuchMethodTest> TESTS = const <NoSuchMethodTest>[
const NoSuchMethodTest(
"""
abstract class I {
foo();
}
class A implements I {
noSuchMethod(x) => super.noSuchMethod(x);
}
main() {
print(new A().foo());
}
""",
const <NoSuchMethodInfo>[
const NoSuchMethodInfo('A', hasForwardingSyntax: true, isDefault: true),
],
),
const NoSuchMethodTest(
"""
abstract class I {
foo();
}
class A extends B implements I {
noSuchMethod(x) => super.noSuchMethod(x);
}
class B {}
main() {
print(new A().foo());
}
""",
const <NoSuchMethodInfo>[
const NoSuchMethodInfo('A', hasForwardingSyntax: true, isDefault: true),
],
),
const NoSuchMethodTest(
"""
abstract class I {
foo();
}
class A extends B implements I {
noSuchMethod(x) {
return super.noSuchMethod(x);
}
}
class B {}
main() {
print(new A().foo());
}
""",
const <NoSuchMethodInfo>[
const NoSuchMethodInfo('A', hasForwardingSyntax: true, isDefault: true),
],
),
const NoSuchMethodTest(
"""
abstract class I {
foo();
}
class A extends B implements I {
noSuchMethod(x) => super.noSuchMethod(x);
}
class B {
noSuchMethod(x) => super.noSuchMethod(x);
}
main() {
print(new A().foo());
}
""",
const <NoSuchMethodInfo>[
const NoSuchMethodInfo(
'A',
superClassName: 'B',
hasForwardingSyntax: true,
isDefault: true,
),
const NoSuchMethodInfo('B', hasForwardingSyntax: true, isDefault: true),
],
),
const NoSuchMethodTest(
"""
abstract class I {
foo();
}
class A extends B implements I {
noSuchMethod(x) => super.noSuchMethod(x);
}
class B {
noSuchMethod(x) => throw 'foo';
}
main() {
print(new A().foo());
}
""",
const <NoSuchMethodInfo>[
const NoSuchMethodInfo(
'A',
superClassName: 'B',
hasForwardingSyntax: true,
isThrowing: true,
),
const NoSuchMethodInfo('B', hasThrowingSyntax: true, isThrowing: true),
],
isNoSuchMethodUsed: true,
),
const NoSuchMethodTest(
"""
class A {
noSuchMethod(x) => 3;
}
main() {
print((new A() as dynamic).foo());
}
""",
const <NoSuchMethodInfo>[
const NoSuchMethodInfo('A', isOther: true, isComplexReturn: true),
],
isNoSuchMethodUsed: true,
),
const NoSuchMethodTest(
"""
abstract class I {
foo();
}
class A implements I {
noSuchMethod(x, [y]) => super.noSuchMethod(x);
}
main() {
print(new A().foo());
}
""",
const <NoSuchMethodInfo>[
const NoSuchMethodInfo('A', hasForwardingSyntax: true, isDefault: true),
],
),
const NoSuchMethodTest(
"""
abstract class I {
foo();
}
class A implements I {
noSuchMethod(x, [y]) => super.noSuchMethod(y);
}
main() {
print(new A().foo());
}
""",
const <NoSuchMethodInfo>[
const NoSuchMethodInfo('A', isOther: true, isComplexNoReturn: true),
],
isNoSuchMethodUsed: true,
),
const NoSuchMethodTest(
"""
class A {
noSuchMethod(x, [y]) => super.noSuchMethod(x) + y;
}
main() {
print((new A() as dynamic).foo());
}
""",
const <NoSuchMethodInfo>[
const NoSuchMethodInfo('A', isOther: true, isComplexNoReturn: true),
],
isNoSuchMethodUsed: true,
),
const NoSuchMethodTest(
"""
class A {
noSuchMethod(Invocation x) {
throw UnsupportedError('');
}
}
main() {
print((new A() as dynamic).foo());
}
""",
const <NoSuchMethodInfo>[
const NoSuchMethodInfo('A', hasThrowingSyntax: true, isThrowing: true),
],
isNoSuchMethodUsed: true,
),
const NoSuchMethodTest(
"""
class A {
noSuchMethod(Invocation x) {
print('foo');
throw 'foo';
}
}
main() {
print((new A() as dynamic).foo());
}
""",
const <NoSuchMethodInfo>[
const NoSuchMethodInfo('A', isOther: true, isComplexNoReturn: true),
],
isNoSuchMethodUsed: true,
),
const NoSuchMethodTest(
"""
class A {
noSuchMethod(Invocation x) {
return toString();
}
}
main() {
print((new A() as dynamic).foo());
}
""",
const <NoSuchMethodInfo>[
const NoSuchMethodInfo('A', isOther: true, isComplexReturn: true),
],
isNoSuchMethodUsed: true,
),
const NoSuchMethodTest(
"""
abstract class I {
foo();
}
class A implements I {
noSuchMethod(x) => super.noSuchMethod(x) as dynamic;
}
main() {
print(new A().foo());
}
""",
const <NoSuchMethodInfo>[
const NoSuchMethodInfo('A', hasForwardingSyntax: true, isDefault: true),
],
),
const NoSuchMethodTest(
"""
abstract class I {
foo();
}
class A implements I {
noSuchMethod(x) => super.noSuchMethod(x) as int;
}
main() {
print(new A().foo());
}
""",
const <NoSuchMethodInfo>[
const NoSuchMethodInfo('A', isOther: true, isComplexNoReturn: true),
],
isNoSuchMethodUsed: true,
),
];
main() {
runTests() async {
for (NoSuchMethodTest test in TESTS) {
print('---- testing -------------------------------------------------');
print(test.code);
CompilationResult result = await runCompiler(
memorySourceFiles: {'main.dart': test.code},
);
Expect.isTrue(result.isSuccess);
Compiler compiler = result.compiler!;
checkTest(compiler, test);
}
}
asyncTest(() async {
print('--test from kernel------------------------------------------------');
await runTests();
});
}
checkTest(Compiler compiler, NoSuchMethodTest test) {
ElementEnvironment frontendEnvironment =
compiler.frontendStrategy.elementEnvironment;
NoSuchMethodRegistry registry =
compiler.frontendStrategy.noSuchMethodRegistry;
var resolver = registry.internalResolverForTesting;
final ObjectNSM =
frontendEnvironment.lookupClassMember(
compiler.frontendStrategy.commonElements.objectClass,
Names.noSuchMethod_,
)
as FunctionEntity;
JClosedWorld backendClosedWorld = compiler.backendClosedWorldForTesting!;
ElementEnvironment backendEnvironment = backendClosedWorld.elementEnvironment;
NoSuchMethodData data = backendClosedWorld.noSuchMethodData;
// Test [NoSuchMethodResolver] results for each method.
for (NoSuchMethodInfo info in test.methods) {
ClassEntity? cls = frontendEnvironment.lookupClass(
frontendEnvironment.mainLibrary!,
info.className,
);
Expect.isNotNull(cls, "Class ${info.className} not found.");
final noSuchMethod =
frontendEnvironment.lookupClassMember(cls!, Names.noSuchMethod_)
as FunctionEntity?;
Expect.isNotNull(noSuchMethod, "noSuchMethod not found in $cls.");
if (info.superClassName == null) {
Expect.equals(ObjectNSM, resolver.getSuperNoSuchMethod(noSuchMethod!));
} else {
ClassEntity? superclass = frontendEnvironment.lookupClass(
frontendEnvironment.mainLibrary!,
info.superClassName!,
);
Expect.isNotNull(
superclass,
"Superclass ${info.superClassName} not found.",
);
final superNoSuchMethod =
frontendEnvironment.lookupClassMember(
superclass!,
Names.noSuchMethod_,
)
as FunctionEntity?;
Expect.isNotNull(
superNoSuchMethod,
"noSuchMethod not found in $superclass.",
);
Expect.equals(
superNoSuchMethod,
resolver.getSuperNoSuchMethod(noSuchMethod!),
"Unexpected super noSuchMethod for $noSuchMethod.",
);
}
Expect.equals(
info.hasForwardingSyntax,
resolver.hasForwardingSyntax(noSuchMethod as JFunction),
"Unexpected hasForwardSyntax result on $noSuchMethod.",
);
Expect.equals(
info.hasThrowingSyntax,
resolver.hasThrowingSyntax(noSuchMethod),
"Unexpected hasThrowingSyntax result on $noSuchMethod.",
);
}
// Test [NoSuchMethodRegistry] results for each method. These are based on
// the [NoSuchMethodResolver] results which are therefore tested for all
// methods first.
for (NoSuchMethodInfo info in test.methods) {
ClassEntity? frontendClass = frontendEnvironment.lookupClass(
frontendEnvironment.mainLibrary!,
info.className,
);
Expect.isNotNull(frontendClass, "Class ${info.className} not found.");
final frontendNoSuchMethod =
frontendEnvironment.lookupClassMember(
frontendClass!,
Names.noSuchMethod_,
)
as FunctionEntity?;
Expect.isNotNull(
frontendNoSuchMethod,
"noSuchMethod not found in $frontendClass.",
);
Expect.equals(
info.isDefault,
registry.defaultImpls.contains(frontendNoSuchMethod),
"Unexpected isDefault result on $frontendNoSuchMethod.",
);
Expect.equals(
info.isThrowing,
registry.throwingImpls.contains(frontendNoSuchMethod),
"Unexpected isThrowing result on $frontendNoSuchMethod.",
);
Expect.equals(
info.isOther,
registry.otherImpls.contains(frontendNoSuchMethod),
"Unexpected isOther result on $frontendNoSuchMethod.",
);
ClassEntity? backendClass = backendEnvironment.lookupClass(
backendEnvironment.mainLibrary!,
info.className,
);
Expect.isNotNull(backendClass, "Class ${info.className} not found.");
final backendNoSuchMethod =
backendEnvironment.lookupClassMember(backendClass!, Names.noSuchMethod_)
as FunctionEntity?;
Expect.isNotNull(
backendNoSuchMethod,
"noSuchMethod not found in $backendClass.",
);
Expect.equals(
info.isComplexNoReturn,
data.complexNoReturnImpls.contains(backendNoSuchMethod),
"Unexpected isComplexNoReturn result on $backendNoSuchMethod.",
);
Expect.equals(
info.isComplexReturn,
data.complexReturningImpls.contains(backendNoSuchMethod),
"Unexpected isComplexReturn result on $backendNoSuchMethod.",
);
}
Expect.equals(
test.isNoSuchMethodUsed,
backendClosedWorld.backendUsage.isNoSuchMethodUsed,
"Unexpected isNoSuchMethodUsed result.",
);
}