blob: 14a5143d5421820453454bc60a7039e2103d2e81 [file] [log] [blame]
// Copyright (c) 2017, 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:expect/minitest.dart";
import "package:kernel/ast.dart";
import "package:kernel/class_hierarchy.dart";
import "package:kernel/core_types.dart";
import "package:kernel/testing/mock_sdk_component.dart";
import "package:kernel/text/ast_to_text.dart";
import "package:kernel/src/text_util.dart";
void main() {
new ClosedWorldClassHierarchyTest().test_applyTreeChanges();
new ClosedWorldClassHierarchyTest().test_applyMemberChanges();
new ClosedWorldClassHierarchyTest()
.test_getSingleTargetForInterfaceInvocation();
new ClosedWorldClassHierarchyTest().test_getSubtypesOf();
new ClosedWorldClassHierarchyTest()
.test_forEachOverridePair_supertypeOverridesInterface();
new ClosedWorldClassHierarchyTest()
.test_forEachOverridePair_supertypeOverridesThis();
new ClosedWorldClassHierarchyTest()
.test_forEachOverridePair_supertypeOverridesThisAbstract();
new ClosedWorldClassHierarchyTest()
.test_forEachOverridePair_thisOverridesSupertype();
new ClosedWorldClassHierarchyTest()
.test_forEachOverridePair_thisOverridesSupertype_setter();
new ClosedWorldClassHierarchyTest()
.test_getClassAsInstanceOf_generic_extends();
new ClosedWorldClassHierarchyTest()
.test_getClassAsInstanceOf_generic_implements();
new ClosedWorldClassHierarchyTest().test_getClassAsInstanceOf_generic_with();
new ClosedWorldClassHierarchyTest()
.test_getClassAsInstanceOf_notGeneric_extends();
new ClosedWorldClassHierarchyTest()
.test_getClassAsInstanceOf_notGeneric_implements();
new ClosedWorldClassHierarchyTest()
.test_getClassAsInstanceOf_notGeneric_with();
new ClosedWorldClassHierarchyTest().test_getDeclaredMembers();
new ClosedWorldClassHierarchyTest().test_getDispatchTarget();
new ClosedWorldClassHierarchyTest().test_getDispatchTarget_abstract();
new ClosedWorldClassHierarchyTest().test_getInterfaceMember_extends();
new ClosedWorldClassHierarchyTest().test_getInterfaceMember_implements();
new ClosedWorldClassHierarchyTest().test_getInterfaceMembers_in_class();
new ClosedWorldClassHierarchyTest()
.test_getInterfaceMembers_inherited_or_mixed_in();
new ClosedWorldClassHierarchyTest().test_getInterfaceMembers_multiple();
new ClosedWorldClassHierarchyTest().test_getInterfaceMembers_shadowed();
new ClosedWorldClassHierarchyTest().test_getOrderedClasses();
new ClosedWorldClassHierarchyTest()
.test_getTypeAsInstanceOf_generic_extends();
}
class ClosedWorldClassHierarchyTest {
final Component component = createMockSdkComponent();
late CoreTypes coreTypes;
late Library library;
ClassHierarchy? _hierarchy;
ClosedWorldClassHierarchyTest() {
coreTypes = new CoreTypes(component);
Uri uri = Uri.parse('org-dartlang:///test.dart');
library = new Library(uri, fileUri: uri, name: 'test');
library.parent = component;
component.libraries.add(library);
}
ClassHierarchy createClassHierarchy(Component component) {
return new ClassHierarchy(component, coreTypes);
}
void test_applyTreeChanges() {
Class a = addClass(
new Class(name: 'A', supertype: objectSuper, fileUri: library.fileUri));
_assertLibraryText(library, '''
class A {}
''');
Uri uriB = Uri.parse('org-dartlang:///test_b.dart');
Class b = new Class(
name: 'B', supertype: a.asThisSupertype, fileUri: library.fileUri);
Library libWithB = new Library(uriB, fileUri: uriB, name: 'test_b');
libWithB.parent = component;
component.libraries.add(libWithB);
libWithB.addClass(b);
_assertLibraryText(libWithB, '''
library test_b;
import self as self;
import "test.dart" as test;
class B extends test::A { // from org-dartlang:///test.dart
}
''');
// No updated classes, the same hierarchy.
expect(hierarchy.applyTreeChanges([], [], []), same(hierarchy));
// Has updated classes, still the same hierarchy (instance). Can answer
// queries about the new classes.
var c = new Class(
name: 'C', supertype: a.asThisSupertype, fileUri: library.fileUri);
Uri uriC = Uri.parse('org-dartlang:///test2.dart');
Library libWithC = new Library(uriC, fileUri: uriC, name: 'test2');
libWithC.parent = component;
component.libraries.add(libWithC);
libWithC.addClass(c);
expect(hierarchy.applyTreeChanges([libWithB], [libWithC], []),
same(hierarchy));
expect(hierarchy.isSubclassOf(a, c), false);
expect(hierarchy.isSubclassOf(c, a), true);
// Remove so A should no longer be a super of anything.
expect(hierarchy.applyTreeChanges([libWithC], [], []), same(hierarchy));
}
void test_applyMemberChanges() {
var methodA1 = newEmptyMethod('memberA1');
var methodA2 = newEmptyMethod('memberA2');
var methodA3 = newEmptyMethod('memberA3');
var methodB1 = newEmptyMethod('memberB1');
var a = addClass(new Class(
name: 'A',
supertype: objectSuper,
procedures: [methodA1, methodA2],
fileUri: library.fileUri));
var b = addClass(new Class(
name: 'B',
supertype: a.asThisSupertype,
procedures: [methodB1],
fileUri: library.fileUri));
_assertTestLibraryText('''
class A {
method memberA1() → void {}
method memberA2() → void {}
}
class B extends self::A {
method memberB1() → void {}
}
''');
// No changes: B has memberA1, memberA2 and memberB1;
// A has memberA1 and memberA2
expect(hierarchy.getDispatchTargets(b),
unorderedEquals([methodA1, methodA2, methodB1]));
expect(
hierarchy.getDispatchTargets(a), unorderedEquals([methodA1, methodA2]));
// Add a member to A, but only update A.
a.addProcedure(methodA3);
hierarchy.applyMemberChanges([a]);
expect(hierarchy.getDispatchTargets(b),
unorderedEquals([methodA1, methodA2, methodB1]));
expect(hierarchy.getDispatchTargets(a),
unorderedEquals([methodA1, methodA2, methodA3]));
// Apply member changes again, this time telling the hierarchy to find
// descendants.
hierarchy.applyMemberChanges([a], findDescendants: true);
expect(hierarchy.getDispatchTargets(b),
unorderedEquals([methodA1, methodA2, methodA3, methodB1]));
expect(hierarchy.getDispatchTargets(a),
unorderedEquals([methodA1, methodA2, methodA3]));
}
void test_getSingleTargetForInterfaceInvocation() {
var methodInA = newEmptyMethod('foo', isAbstract: true);
var methodInB = newEmptyMethod('foo');
var methodInD = newEmptyMethod('foo');
var methodInE = newEmptyMethod('foo');
var a = addClass(new Class(
name: 'A',
supertype: objectSuper,
procedures: [methodInA],
fileUri: library.fileUri));
var b = addClass(new Class(
name: 'B',
isAbstract: true,
supertype: objectSuper,
procedures: [methodInB],
fileUri: library.fileUri));
var c = addClass(new Class(
name: 'C',
supertype: b.asThisSupertype,
implementedTypes: [a.asThisSupertype],
fileUri: library.fileUri));
addClass(new Class(
name: 'D',
supertype: b.asThisSupertype,
procedures: [methodInD],
fileUri: library.fileUri));
addClass(new Class(
name: 'E',
isAbstract: true,
supertype: objectSuper,
implementedTypes: [c.asThisSupertype],
procedures: [methodInE],
fileUri: library.fileUri));
_assertTestLibraryText('''
class A {
abstract method foo() → void;
}
abstract class B {
method foo() → void {}
}
class C extends self::B implements self::A {}
class D extends self::B {
method foo() → void {}
}
abstract class E implements self::C {
method foo() → void {}
}
''');
ClosedWorldClassHierarchy cwch = hierarchy as ClosedWorldClassHierarchy;
ClassHierarchySubtypes cwchst = cwch.computeSubtypesInformation();
expect(cwchst.getSingleTargetForInterfaceInvocation(methodInA), methodInB);
expect(cwchst.getSingleTargetForInterfaceInvocation(methodInB),
null); // B::foo and D::foo
expect(cwchst.getSingleTargetForInterfaceInvocation(methodInD), methodInD);
expect(cwchst.getSingleTargetForInterfaceInvocation(methodInE),
null); // no concrete subtypes
}
void test_getSubtypesOf() {
var a = addClass(
new Class(name: 'A', supertype: objectSuper, fileUri: library.fileUri));
var b = addClass(
new Class(name: 'B', supertype: objectSuper, fileUri: library.fileUri));
var c = addClass(
new Class(name: 'C', supertype: objectSuper, fileUri: library.fileUri));
var d = addClass(new Class(
name: 'D', supertype: a.asThisSupertype, fileUri: library.fileUri));
var e = addClass(new Class(
name: 'E',
supertype: b.asThisSupertype,
implementedTypes: [c.asThisSupertype],
fileUri: library.fileUri));
var f = addClass(new Class(
name: 'F',
supertype: e.asThisSupertype,
implementedTypes: [a.asThisSupertype],
fileUri: library.fileUri));
var g = addClass(
new Class(name: 'G', supertype: objectSuper, fileUri: library.fileUri));
var h = addClass(new Class(
name: 'H',
supertype: g.asThisSupertype,
implementedTypes: [c.asThisSupertype, a.asThisSupertype],
fileUri: library.fileUri));
_assertTestLibraryText('''
class A {}
class B {}
class C {}
class D extends self::A {}
class E extends self::B implements self::C {}
class F extends self::E implements self::A {}
class G {}
class H extends self::G implements self::C, self::A {}
''');
ClosedWorldClassHierarchy cwch = hierarchy as ClosedWorldClassHierarchy;
ClassHierarchySubtypes cwchst = cwch.computeSubtypesInformation();
expect(cwchst.getSubtypesOf(a), unorderedEquals([a, d, f, h]));
expect(cwchst.getSubtypesOf(b), unorderedEquals([b, e, f]));
expect(cwchst.getSubtypesOf(c), unorderedEquals([c, e, f, h]));
expect(cwchst.getSubtypesOf(d), unorderedEquals([d]));
expect(cwchst.getSubtypesOf(e), unorderedEquals([e, f]));
expect(cwchst.getSubtypesOf(f), unorderedEquals([f]));
expect(cwchst.getSubtypesOf(g), unorderedEquals([g, h]));
expect(cwchst.getSubtypesOf(h), unorderedEquals([h]));
}
/// Return the new or existing instance of [ClassHierarchy].
ClassHierarchy get hierarchy {
return _hierarchy ??= createClassHierarchy(component);
}
Class get objectClass => coreTypes.objectClass;
Supertype get objectSuper => coreTypes.objectClass.asThisSupertype;
Class addClass(Class c) {
if (_hierarchy != null) {
fail('The class hierarchy has already been created.');
}
library.addClass(c);
return c;
}
/// Add a new generic class with the given [name] and [typeParameterNames].
/// The [TypeParameterType]s corresponding to [typeParameterNames] are
/// passed to optional [extends_] and [implements_] callbacks.
Class addGenericClass(String name, List<String> typeParameterNames,
{Supertype Function(List<DartType> typeParameterTypes)? extends_,
List<Supertype> Function(List<DartType> typeParameterTypes)?
implements_}) {
var typeParameters = typeParameterNames
.map((name) => new TypeParameter(
name, coreTypes.objectLegacyRawType, coreTypes.objectLegacyRawType))
.toList();
var typeParameterTypes = typeParameters
.map(
(parameter) => new TypeParameterType(parameter, Nullability.legacy))
.toList();
var supertype =
extends_ != null ? extends_(typeParameterTypes) : objectSuper;
var implementedTypes =
implements_ != null ? implements_(typeParameterTypes) : <Supertype>[];
return addClass(new Class(
name: name,
typeParameters: typeParameters,
supertype: supertype,
implementedTypes: implementedTypes,
fileUri: library.fileUri));
}
Procedure newEmptyGetter(String name,
{DartType returnType: const DynamicType(), bool isAbstract: false}) {
var body =
isAbstract ? null : new Block([new ReturnStatement(new NullLiteral())]);
return new Procedure(new Name(name), ProcedureKind.Getter,
new FunctionNode(body, returnType: returnType),
fileUri: library.fileUri);
}
Procedure newEmptyMethod(String name, {bool isAbstract: false}) {
var body = isAbstract ? null : new Block([]);
return new Procedure(new Name(name), ProcedureKind.Method,
new FunctionNode(body, returnType: const VoidType()),
isAbstract: isAbstract, fileUri: library.fileUri);
}
Procedure newEmptySetter(String name,
{bool isAbstract: false, DartType type: const DynamicType()}) {
var body = isAbstract ? null : new Block([]);
return new Procedure(
new Name(name),
ProcedureKind.Setter,
new FunctionNode(body,
returnType: const VoidType(),
positionalParameters: [new VariableDeclaration('_', type: type)]),
fileUri: library.fileUri);
}
/// 2. A non-abstract member is inherited from a superclass, and in the
/// context of this class, it overrides an abstract member inheritable through
/// one of its superinterfaces.
void test_forEachOverridePair_supertypeOverridesInterface() {
var a = addClass(new Class(
name: 'A',
supertype: objectSuper,
procedures: [newEmptyMethod('foo'), newEmptyMethod('bar')],
fileUri: library.fileUri));
var b = addClass(new Class(
name: 'B',
supertype: a.asThisSupertype,
procedures: [newEmptyMethod('foo', isAbstract: true)],
fileUri: library.fileUri));
var c = addClass(new Class(
name: 'C',
supertype: a.asThisSupertype,
implementedTypes: [b.asThisSupertype],
fileUri: library.fileUri));
var d = addClass(
new Class(name: 'D', supertype: objectSuper, fileUri: library.fileUri));
var e = addClass(new Class(
name: 'E',
supertype: d.asThisSupertype,
mixedInType: a.asThisSupertype,
implementedTypes: [b.asThisSupertype],
fileUri: library.fileUri));
_assertTestLibraryText('''
class A {
method foo() → void {}
method bar() → void {}
}
class B extends self::A {
abstract method foo() → void;
}
class C extends self::A implements self::B {}
class D {}
class E = self::D with self::A implements self::B {}
''');
_assertOverridePairs(c, []);
_assertOverridePairs(e, ['test::A.foo overrides test::B.foo']);
}
/// An abstract member declared in the class is overridden by a member in
/// one of the interfaces.
void test_forEachOverridePair_supertypeOverridesThis() {
var a = addClass(new Class(
name: 'A',
supertype: objectSuper,
procedures: [newEmptyMethod('foo')],
fileUri: library.fileUri));
var b = addClass(new Class(
name: 'B',
supertype: a.asThisSupertype,
procedures: [newEmptyMethod('foo', isAbstract: true)],
fileUri: library.fileUri));
var c = addClass(new Class(
name: 'C',
supertype: a.asThisSupertype,
procedures: [newEmptyMethod('foo', isAbstract: true)],
isAbstract: true,
fileUri: library.fileUri));
var d = addClass(new Class(
name: 'D', supertype: b.asThisSupertype, fileUri: library.fileUri));
var e = addClass(new Class(
name: 'E', supertype: c.asThisSupertype, fileUri: library.fileUri));
_assertTestLibraryText('''
class A {
method foo() → void {}
}
class B extends self::A {
abstract method foo() → void;
}
abstract class C extends self::A {
abstract method foo() → void;
}
class D extends self::B {}
class E extends self::C {}
''');
_assertOverridePairs(b, ['test::B.foo overrides test::A.foo']);
_assertOverridePairs(c, ['test::C.foo overrides test::A.foo']);
_assertOverridePairs(d, []);
_assertOverridePairs(e, []);
}
/// 3. A non-abstract member is inherited from a superclass, and it overrides
/// an abstract member declared in this class.
void test_forEachOverridePair_supertypeOverridesThisAbstract() {
var a = addClass(new Class(
name: 'A',
supertype: objectSuper,
procedures: [newEmptyMethod('foo'), newEmptyMethod('bar')],
fileUri: library.fileUri));
var b = addClass(new Class(
name: 'B',
supertype: a.asThisSupertype,
procedures: [newEmptyMethod('foo', isAbstract: true)],
fileUri: library.fileUri));
_assertTestLibraryText('''
class A {
method foo() → void {}
method bar() → void {}
}
class B extends self::A {
abstract method foo() → void;
}
''');
// The documentation says:
// It is possible for two methods to override one another in both
// directions.
_assertOverridePairs(b, ['test::B.foo overrides test::A.foo']);
}
/// 1. A member declared in the class overrides a member inheritable through
/// one of the supertypes of the class.
void test_forEachOverridePair_thisOverridesSupertype() {
var a = addClass(new Class(
name: 'A',
supertype: objectSuper,
procedures: [newEmptyMethod('foo'), newEmptyMethod('bar')],
fileUri: library.fileUri));
var b = addClass(new Class(
name: 'B',
supertype: a.asThisSupertype,
procedures: [newEmptyMethod('foo')],
fileUri: library.fileUri));
var c = addClass(new Class(
name: 'C',
supertype: b.asThisSupertype,
procedures: [newEmptyMethod('bar')],
fileUri: library.fileUri));
_assertTestLibraryText('''
class A {
method foo() → void {}
method bar() → void {}
}
class B extends self::A {
method foo() → void {}
}
class C extends self::B {
method bar() → void {}
}
''');
_assertOverridePairs(b, ['test::B.foo overrides test::A.foo']);
_assertOverridePairs(c, ['test::C.bar overrides test::A.bar']);
}
/// 1. A member declared in the class overrides a member inheritable through
/// one of the supertypes of the class.
void test_forEachOverridePair_thisOverridesSupertype_setter() {
var a = addClass(new Class(
name: 'A',
supertype: objectSuper,
procedures: [newEmptySetter('foo'), newEmptySetter('bar')],
fileUri: library.fileUri));
var b = addClass(new Class(
name: 'B',
supertype: a.asThisSupertype,
procedures: [newEmptySetter('foo')],
fileUri: library.fileUri));
var c = addClass(new Class(
name: 'C',
supertype: b.asThisSupertype,
procedures: [newEmptySetter('bar')],
fileUri: library.fileUri));
_assertTestLibraryText('''
class A {
set foo(dynamic _) → void {}
set bar(dynamic _) → void {}
}
class B extends self::A {
set foo(dynamic _) → void {}
}
class C extends self::B {
set bar(dynamic _) → void {}
}
''');
_assertOverridePairs(b, ['test::B.foo= overrides test::A.foo=']);
_assertOverridePairs(c, ['test::C.bar= overrides test::A.bar=']);
}
void test_getClassAsInstanceOf_generic_extends() {
var int = coreTypes.intLegacyRawType;
var bool = coreTypes.boolLegacyRawType;
var a = addGenericClass('A', ['T', 'U']);
var bT = new TypeParameter(
'T', coreTypes.objectLegacyRawType, coreTypes.objectLegacyRawType);
var bTT = new TypeParameterType(bT, Nullability.legacy);
var b = addClass(new Class(
name: 'B',
typeParameters: [bT],
supertype: new Supertype(a, [bTT, bool]),
fileUri: library.fileUri));
var c = addClass(new Class(
name: 'C',
supertype: new Supertype(b, [int]),
fileUri: library.fileUri));
_assertTestLibraryText('''
class A<T*, U*> {}
class B<T*> extends self::A<self::B::T*, core::bool*> {}
class C extends self::B<core::int*> {}
''');
expect(hierarchy.getClassAsInstanceOf(a, objectClass), objectSuper);
expect(hierarchy.getClassAsInstanceOf(a, a), a.asThisSupertype);
expect(hierarchy.getClassAsInstanceOf(b, a), new Supertype(a, [bTT, bool]));
expect(hierarchy.getClassAsInstanceOf(c, b), new Supertype(b, [int]));
expect(hierarchy.getClassAsInstanceOf(c, a), new Supertype(a, [int, bool]));
}
void test_getClassAsInstanceOf_generic_implements() {
var int = coreTypes.intLegacyRawType;
var bool = coreTypes.boolLegacyRawType;
var a = addGenericClass('A', ['T', 'U']);
var bT = new TypeParameter(
'T', coreTypes.objectLegacyRawType, coreTypes.objectLegacyRawType);
var bTT = new TypeParameterType(bT, Nullability.legacy);
var b = addClass(new Class(
name: 'B',
typeParameters: [bT],
supertype: objectSuper,
implementedTypes: [
new Supertype(a, [bTT, bool])
],
fileUri: library.fileUri));
var c = addClass(new Class(
name: 'C',
supertype: objectSuper,
implementedTypes: [
new Supertype(b, [int])
],
fileUri: library.fileUri));
_assertTestLibraryText('''
class A<T*, U*> {}
class B<T*> implements self::A<self::B::T*, core::bool*> {}
class C implements self::B<core::int*> {}
''');
expect(hierarchy.getClassAsInstanceOf(a, objectClass), objectSuper);
expect(hierarchy.getClassAsInstanceOf(a, a), a.asThisSupertype);
expect(hierarchy.getClassAsInstanceOf(b, a), new Supertype(a, [bTT, bool]));
expect(hierarchy.getClassAsInstanceOf(c, b), new Supertype(b, [int]));
expect(hierarchy.getClassAsInstanceOf(c, a), new Supertype(a, [int, bool]));
}
void test_getClassAsInstanceOf_generic_with() {
var int = coreTypes.intLegacyRawType;
var bool = coreTypes.boolLegacyRawType;
var a = addGenericClass('A', ['T', 'U']);
var bT = new TypeParameter(
'T', coreTypes.objectLegacyRawType, coreTypes.objectLegacyRawType);
var bTT = new TypeParameterType(bT, Nullability.legacy);
var b = addClass(new Class(
name: 'B',
typeParameters: [bT],
supertype: objectSuper,
mixedInType: new Supertype(a, [bTT, bool]),
fileUri: library.fileUri));
var c = addClass(new Class(
name: 'C',
supertype: objectSuper,
mixedInType: new Supertype(b, [int]),
fileUri: library.fileUri));
_assertTestLibraryText('''
class A<T*, U*> {}
class B<T*> = core::Object with self::A<self::B::T*, core::bool*> {}
class C = core::Object with self::B<core::int*> {}
''');
expect(hierarchy.getClassAsInstanceOf(a, objectClass), objectSuper);
expect(hierarchy.getClassAsInstanceOf(a, a), a.asThisSupertype);
expect(hierarchy.getClassAsInstanceOf(b, a), new Supertype(a, [bTT, bool]));
expect(hierarchy.getClassAsInstanceOf(c, b), new Supertype(b, [int]));
expect(hierarchy.getClassAsInstanceOf(c, a), new Supertype(a, [int, bool]));
}
void test_getClassAsInstanceOf_notGeneric_extends() {
var a = addClass(
new Class(name: 'A', supertype: objectSuper, fileUri: library.fileUri));
var b = addClass(new Class(
name: 'B', supertype: a.asThisSupertype, fileUri: library.fileUri));
var c = addClass(new Class(
name: 'C', supertype: b.asThisSupertype, fileUri: library.fileUri));
var z = addClass(
new Class(name: 'Z', supertype: objectSuper, fileUri: library.fileUri));
_assertTestLibraryText('''
class A {}
class B extends self::A {}
class C extends self::B {}
class Z {}
''');
expect(hierarchy.getClassAsInstanceOf(a, objectClass), objectSuper);
expect(hierarchy.getClassAsInstanceOf(a, a), a.asThisSupertype);
expect(hierarchy.getClassAsInstanceOf(b, a), a.asThisSupertype);
expect(hierarchy.getClassAsInstanceOf(c, a), a.asThisSupertype);
expect(hierarchy.getClassAsInstanceOf(c, b), b.asThisSupertype);
expect(hierarchy.getClassAsInstanceOf(z, a), null);
expect(hierarchy.getClassAsInstanceOf(z, objectClass), objectSuper);
}
void test_getClassAsInstanceOf_notGeneric_implements() {
var a = addClass(
new Class(name: 'A', supertype: objectSuper, fileUri: library.fileUri));
var b = addClass(
new Class(name: 'B', supertype: objectSuper, fileUri: library.fileUri));
var c = addClass(new Class(
name: 'C',
supertype: objectSuper,
implementedTypes: [a.asThisSupertype],
fileUri: library.fileUri));
var d = addClass(new Class(
name: 'D',
supertype: objectSuper,
implementedTypes: [c.asThisSupertype],
fileUri: library.fileUri));
var e = addClass(new Class(
name: 'D',
supertype: a.asThisSupertype,
implementedTypes: [b.asThisSupertype],
fileUri: library.fileUri));
var z = addClass(
new Class(name: 'Z', supertype: objectSuper, fileUri: library.fileUri));
_assertTestLibraryText('''
class A {}
class B {}
class C implements self::A {}
class D implements self::C {}
class D extends self::A implements self::B {}
class Z {}
''');
expect(hierarchy.getClassAsInstanceOf(c, a), a.asThisSupertype);
expect(hierarchy.getClassAsInstanceOf(d, a), a.asThisSupertype);
expect(hierarchy.getClassAsInstanceOf(d, c), c.asThisSupertype);
expect(hierarchy.getClassAsInstanceOf(e, a), a.asThisSupertype);
expect(hierarchy.getClassAsInstanceOf(e, b), b.asThisSupertype);
expect(hierarchy.getClassAsInstanceOf(z, a), null);
}
void test_getClassAsInstanceOf_notGeneric_with() {
var a = addClass(
new Class(name: 'A', supertype: objectSuper, fileUri: library.fileUri));
var b = addClass(new Class(
name: 'B',
supertype: objectSuper,
mixedInType: a.asThisSupertype,
fileUri: library.fileUri));
var z = addClass(
new Class(name: 'Z', supertype: objectSuper, fileUri: library.fileUri));
_assertTestLibraryText('''
class A {}
class B = core::Object with self::A {}
class Z {}
''');
expect(hierarchy.getClassAsInstanceOf(b, objectClass), objectSuper);
expect(hierarchy.getClassAsInstanceOf(b, a), a.asThisSupertype);
expect(hierarchy.getClassAsInstanceOf(z, a), null);
}
void test_getDeclaredMembers() {
var method = newEmptyMethod('method');
var getter = newEmptyGetter('getter');
var setter = newEmptySetter('setter');
var abstractMethod = newEmptyMethod('abstractMethod', isAbstract: true);
var abstractGetter = newEmptyGetter('abstractGetter', isAbstract: true);
var abstractSetter = newEmptySetter('abstractSetter', isAbstract: true);
var nonFinalField =
new Field.mutable(new Name('nonFinalField'), fileUri: library.fileUri);
var finalField = new Field.immutable(new Name('finalField'),
isFinal: true, fileUri: library.fileUri);
var a = addClass(new Class(
isAbstract: true,
name: 'A',
supertype: objectSuper,
fields: [nonFinalField, finalField],
procedures: [
method,
getter,
setter,
abstractMethod,
abstractGetter,
abstractSetter
],
fileUri: library.fileUri));
var b = addClass(new Class(
isAbstract: true,
name: 'B',
supertype: a.asThisSupertype,
fileUri: library.fileUri));
_assertTestLibraryText('''
abstract class A {
field dynamic nonFinalField;
final field dynamic finalField;
method method() → void {}
get getter() → dynamic {
return null;
}
set setter(dynamic _) → void {}
abstract method abstractMethod() → void;
get abstractGetter() → dynamic;
set abstractSetter(dynamic _) → void;
}
abstract class B extends self::A {}
''');
expect(
hierarchy.getDeclaredMembers(a),
unorderedEquals([
method,
getter,
abstractMethod,
abstractGetter,
nonFinalField,
finalField
]));
expect(hierarchy.getDeclaredMembers(a, setters: true),
unorderedEquals([setter, abstractSetter, nonFinalField]));
expect(hierarchy.getDeclaredMembers(b).isEmpty, isTrue);
expect(hierarchy.getDeclaredMembers(b, setters: true).isEmpty, isTrue);
}
void test_getDispatchTarget() {
var aMethod = newEmptyMethod('aMethod');
var aSetter = newEmptySetter('aSetter');
var bMethod = newEmptyMethod('bMethod');
var bSetter = newEmptySetter('bSetter');
var a = addClass(new Class(
name: 'A',
supertype: objectSuper,
procedures: [aMethod, aSetter],
fileUri: library.fileUri));
var b = addClass(new Class(
name: 'B',
supertype: a.asThisSupertype,
procedures: [bMethod, bSetter],
fileUri: library.fileUri));
var c = addClass(new Class(
name: 'C', supertype: b.asThisSupertype, fileUri: library.fileUri));
_assertTestLibraryText('''
class A {
method aMethod() → void {}
set aSetter(dynamic _) → void {}
}
class B extends self::A {
method bMethod() → void {}
set bSetter(dynamic _) → void {}
}
class C extends self::B {}
''');
var aMethodName = new Name('aMethod');
var aSetterName = new Name('aSetter');
var bMethodName = new Name('bMethod');
var bSetterName = new Name('bSetter');
expect(hierarchy.getDispatchTarget(a, aMethodName), aMethod);
expect(hierarchy.getDispatchTarget(a, bMethodName), isNull);
expect(hierarchy.getDispatchTarget(a, aSetterName, setter: true), aSetter);
expect(hierarchy.getDispatchTarget(a, bSetterName, setter: true), isNull);
expect(hierarchy.getDispatchTarget(b, aMethodName), aMethod);
expect(hierarchy.getDispatchTarget(b, bMethodName), bMethod);
expect(hierarchy.getDispatchTarget(b, aSetterName, setter: true), aSetter);
expect(hierarchy.getDispatchTarget(b, bSetterName, setter: true), bSetter);
expect(hierarchy.getDispatchTarget(c, aMethodName), aMethod);
expect(hierarchy.getDispatchTarget(c, bMethodName), bMethod);
expect(hierarchy.getDispatchTarget(c, aSetterName, setter: true), aSetter);
expect(hierarchy.getDispatchTarget(c, bSetterName, setter: true), bSetter);
}
void test_getDispatchTarget_abstract() {
var aFoo = newEmptyMethod('foo', isAbstract: true);
var aBar = newEmptyMethod('bar');
var bFoo = newEmptyMethod('foo');
var bBar = newEmptyMethod('bar', isAbstract: true);
var a = addClass(new Class(
isAbstract: true,
name: 'A',
supertype: objectSuper,
procedures: [aFoo, aBar],
fileUri: library.fileUri));
var b = addClass(new Class(
isAbstract: true,
name: 'B',
supertype: a.asThisSupertype,
procedures: [bFoo, bBar],
fileUri: library.fileUri));
var c = addClass(new Class(
name: 'C', supertype: b.asThisSupertype, fileUri: library.fileUri));
_assertTestLibraryText('''
abstract class A {
abstract method foo() → void;
method bar() → void {}
}
abstract class B extends self::A {
method foo() → void {}
abstract method bar() → void;
}
class C extends self::B {}
''');
expect(hierarchy.getDispatchTarget(a, new Name('foo')), isNull);
expect(hierarchy.getDispatchTarget(b, new Name('foo')), bFoo);
expect(hierarchy.getDispatchTarget(c, new Name('foo')), bFoo);
expect(hierarchy.getDispatchTarget(a, new Name('bar')), aBar);
expect(hierarchy.getDispatchTarget(b, new Name('bar')), aBar);
expect(hierarchy.getDispatchTarget(c, new Name('bar')), aBar);
}
void test_getInterfaceMember_extends() {
var aMethod = newEmptyMethod('aMethod');
var aSetter = newEmptySetter('aSetter');
var bMethod = newEmptyMethod('bMethod');
var bSetter = newEmptySetter('bSetter');
var a = addClass(new Class(
name: 'A',
supertype: objectSuper,
procedures: [aMethod, aSetter],
fileUri: library.fileUri));
var b = addClass(new Class(
name: 'B',
supertype: a.asThisSupertype,
procedures: [bMethod, bSetter],
fileUri: library.fileUri));
var c = addClass(new Class(
name: 'C', supertype: b.asThisSupertype, fileUri: library.fileUri));
_assertTestLibraryText('''
class A {
method aMethod() → void {}
set aSetter(dynamic _) → void {}
}
class B extends self::A {
method bMethod() → void {}
set bSetter(dynamic _) → void {}
}
class C extends self::B {}
''');
var aMethodName = new Name('aMethod');
var aSetterName = new Name('aSetter');
var bMethodName = new Name('bMethod');
var bSetterName = new Name('bSetter');
expect(hierarchy.getInterfaceMember(a, aMethodName), aMethod);
expect(hierarchy.getInterfaceMember(a, bMethodName), isNull);
expect(hierarchy.getInterfaceMember(a, aSetterName, setter: true), aSetter);
expect(hierarchy.getInterfaceMember(a, bSetterName, setter: true), isNull);
expect(hierarchy.getInterfaceMember(b, aMethodName), aMethod);
expect(hierarchy.getInterfaceMember(b, bMethodName), bMethod);
expect(hierarchy.getInterfaceMember(b, aSetterName, setter: true), aSetter);
expect(hierarchy.getInterfaceMember(b, bSetterName, setter: true), bSetter);
expect(hierarchy.getInterfaceMember(c, aMethodName), aMethod);
expect(hierarchy.getInterfaceMember(c, bMethodName), bMethod);
expect(hierarchy.getInterfaceMember(c, aSetterName, setter: true), aSetter);
expect(hierarchy.getInterfaceMember(c, bSetterName, setter: true), bSetter);
}
void test_getInterfaceMember_implements() {
var aMethod = newEmptyMethod('aMethod');
var aSetter = newEmptySetter('aSetter');
var bMethod = newEmptyMethod('bMethod');
var bSetter = newEmptySetter('bSetter');
var a = addClass(new Class(
name: 'A',
supertype: objectSuper,
procedures: [aMethod, aSetter],
fileUri: library.fileUri));
var b = addClass(new Class(
name: 'B',
supertype: objectSuper,
implementedTypes: [a.asThisSupertype],
procedures: [bMethod, bSetter],
fileUri: library.fileUri));
var c = addClass(new Class(
name: 'C',
supertype: objectSuper,
implementedTypes: [b.asThisSupertype],
fileUri: library.fileUri));
_assertTestLibraryText('''
class A {
method aMethod() → void {}
set aSetter(dynamic _) → void {}
}
class B implements self::A {
method bMethod() → void {}
set bSetter(dynamic _) → void {}
}
class C implements self::B {}
''');
var aMethodName = new Name('aMethod');
var aSetterName = new Name('aSetter');
var bMethodName = new Name('bMethod');
var bSetterName = new Name('bSetter');
expect(hierarchy.getInterfaceMember(a, aMethodName), aMethod);
expect(hierarchy.getInterfaceMember(a, bMethodName), isNull);
expect(hierarchy.getInterfaceMember(a, aSetterName, setter: true), aSetter);
expect(hierarchy.getInterfaceMember(a, bSetterName, setter: true), isNull);
expect(hierarchy.getInterfaceMember(b, aMethodName), aMethod);
expect(hierarchy.getInterfaceMember(b, bMethodName), bMethod);
expect(hierarchy.getInterfaceMember(b, aSetterName, setter: true), aSetter);
expect(hierarchy.getInterfaceMember(b, bSetterName, setter: true), bSetter);
expect(hierarchy.getInterfaceMember(c, aMethodName), aMethod);
expect(hierarchy.getInterfaceMember(c, bMethodName), bMethod);
expect(hierarchy.getInterfaceMember(c, aSetterName, setter: true), aSetter);
expect(hierarchy.getInterfaceMember(c, bSetterName, setter: true), bSetter);
}
void test_getInterfaceMembers_in_class() {
var method = newEmptyMethod('method');
var getter = newEmptyGetter('getter');
var setter = newEmptySetter('setter');
var abstractMethod = newEmptyMethod('abstractMethod', isAbstract: true);
var abstractGetter = newEmptyGetter('abstractGetter', isAbstract: true);
var abstractSetter = newEmptySetter('abstractSetter', isAbstract: true);
var nonFinalField =
new Field.mutable(new Name('nonFinalField'), fileUri: library.fileUri);
var finalField = new Field.immutable(new Name('finalField'),
isFinal: true, fileUri: library.fileUri);
var a = addClass(new Class(
isAbstract: true,
name: 'A',
supertype: objectSuper,
fields: [nonFinalField, finalField],
procedures: [
method,
getter,
setter,
abstractMethod,
abstractGetter,
abstractSetter
],
fileUri: library.fileUri));
_assertTestLibraryText('''
abstract class A {
field dynamic nonFinalField;
final field dynamic finalField;
method method() → void {}
get getter() → dynamic {
return null;
}
set setter(dynamic _) → void {}
abstract method abstractMethod() → void;
get abstractGetter() → dynamic;
set abstractSetter(dynamic _) → void;
}
''');
expect(
hierarchy.getInterfaceMembers(a),
unorderedEquals([
method,
getter,
abstractMethod,
abstractGetter,
nonFinalField,
finalField
]));
expect(hierarchy.getInterfaceMembers(a, setters: true),
unorderedEquals([setter, abstractSetter, nonFinalField]));
}
void test_getInterfaceMembers_inherited_or_mixed_in() {
var method = newEmptyMethod('method');
var getter = newEmptyGetter('getter');
var setter = newEmptySetter('setter');
var abstractMethod = newEmptyMethod('abstractMethod', isAbstract: true);
var abstractGetter = newEmptyGetter('abstractGetter', isAbstract: true);
var abstractSetter = newEmptySetter('abstractSetter', isAbstract: true);
var nonFinalField =
new Field.mutable(new Name('nonFinalField'), fileUri: library.fileUri);
var finalField = new Field.immutable(new Name('finalField'),
isFinal: true, fileUri: library.fileUri);
var a = addClass(new Class(
name: 'A',
supertype: objectSuper,
fields: [nonFinalField, finalField],
procedures: [
method,
getter,
setter,
abstractMethod,
abstractGetter,
abstractSetter
],
fileUri: library.fileUri));
var b = addClass(new Class(
name: 'B', supertype: a.asThisSupertype, fileUri: library.fileUri));
var c = addClass(new Class(
isAbstract: true,
name: 'C',
supertype: objectSuper,
implementedTypes: [a.asThisSupertype],
fileUri: library.fileUri));
var d = addClass(new Class(
name: 'D',
supertype: objectSuper,
mixedInType: a.asThisSupertype,
fileUri: library.fileUri));
_assertTestLibraryText('''
class A {
field dynamic nonFinalField;
final field dynamic finalField;
method method() → void {}
get getter() → dynamic {
return null;
}
set setter(dynamic _) → void {}
abstract method abstractMethod() → void;
get abstractGetter() → dynamic;
set abstractSetter(dynamic _) → void;
}
class B extends self::A {}
abstract class C implements self::A {}
class D = core::Object with self::A {}
''');
var expectedGetters = [
method,
getter,
abstractMethod,
abstractGetter,
nonFinalField,
finalField
];
expect(hierarchy.getInterfaceMembers(b), unorderedEquals(expectedGetters));
var expectedSetters = [setter, abstractSetter, nonFinalField];
expect(hierarchy.getInterfaceMembers(b, setters: true),
unorderedEquals(expectedSetters));
expect(hierarchy.getInterfaceMembers(c), unorderedEquals(expectedGetters));
expect(hierarchy.getInterfaceMembers(c, setters: true),
unorderedEquals(expectedSetters));
expect(hierarchy.getInterfaceMembers(d), unorderedEquals(expectedGetters));
expect(hierarchy.getInterfaceMembers(d, setters: true),
unorderedEquals(expectedSetters));
}
void test_getInterfaceMembers_multiple() {
var method_a = newEmptyMethod('method');
var getter_a = newEmptyGetter('getter');
var setter_a = newEmptySetter('setter');
var nonFinalField_a =
new Field.mutable(new Name('nonFinalField'), fileUri: library.fileUri);
var finalField_a = new Field.immutable(new Name('finalField'),
isFinal: true, fileUri: library.fileUri);
var method_b = newEmptyMethod('method');
var getter_b = newEmptyGetter('getter');
var setter_b = newEmptySetter('setter');
var nonFinalField_b =
new Field.mutable(new Name('nonFinalField'), fileUri: library.fileUri);
var finalField_b = new Field.immutable(new Name('finalField'),
isFinal: true, fileUri: library.fileUri);
var a = addClass(new Class(
name: 'A',
supertype: objectSuper,
fields: [nonFinalField_a, finalField_a],
procedures: [method_a, getter_a, setter_a],
fileUri: library.fileUri));
var b = addClass(new Class(
name: 'B',
supertype: objectSuper,
fields: [nonFinalField_b, finalField_b],
procedures: [method_b, getter_b, setter_b],
fileUri: library.fileUri));
var c = addClass(new Class(
isAbstract: true,
name: 'C',
supertype: objectSuper,
implementedTypes: [a.asThisSupertype, b.asThisSupertype],
fileUri: library.fileUri));
_assertTestLibraryText('''
class A {
field dynamic nonFinalField;
final field dynamic finalField;
method method() → void {}
get getter() → dynamic {
return null;
}
set setter(dynamic _) → void {}
}
class B {
field dynamic nonFinalField;
final field dynamic finalField;
method method() → void {}
get getter() → dynamic {
return null;
}
set setter(dynamic _) → void {}
}
abstract class C implements self::A, self::B {}
''');
expect(
hierarchy.getInterfaceMembers(c),
unorderedEquals([
method_a,
getter_a,
nonFinalField_a,
finalField_a,
method_b,
getter_b,
nonFinalField_b,
finalField_b
]));
expect(
hierarchy.getInterfaceMembers(c, setters: true),
unorderedEquals(
[setter_a, nonFinalField_a, setter_b, nonFinalField_b]));
}
void test_getInterfaceMembers_shadowed() {
var method_a = newEmptyMethod('method');
var nonShadowedMethod_a = newEmptyMethod('nonShadowedMethod');
var getter_a = newEmptyGetter('getter');
var setter_a = newEmptySetter('setter');
var nonShadowedSetter_a = newEmptySetter('nonShadowedSetter');
var nonFinalField_a =
new Field.mutable(new Name('nonFinalField'), fileUri: library.fileUri);
var finalField_a = new Field.immutable(new Name('finalField'),
isFinal: true, fileUri: library.fileUri);
var method_b = newEmptyMethod('method');
var getter_b = newEmptyGetter('getter');
var setter_b = newEmptySetter('setter');
var nonFinalField_b =
new Field.mutable(new Name('nonFinalField'), fileUri: library.fileUri);
var finalField_b = new Field.immutable(new Name('finalField'),
isFinal: true, fileUri: library.fileUri);
var a = addClass(new Class(
name: 'A',
supertype: objectSuper,
fields: [nonFinalField_a, finalField_a],
procedures: [
method_a,
nonShadowedMethod_a,
getter_a,
setter_a,
nonShadowedSetter_a
],
fileUri: library.fileUri));
var b = addClass(new Class(
name: 'B',
supertype: a.asThisSupertype,
fields: [nonFinalField_b, finalField_b],
procedures: [method_b, getter_b, setter_b],
fileUri: library.fileUri));
_assertTestLibraryText('''
class A {
field dynamic nonFinalField;
final field dynamic finalField;
method method() → void {}
method nonShadowedMethod() → void {}
get getter() → dynamic {
return null;
}
set setter(dynamic _) → void {}
set nonShadowedSetter(dynamic _) → void {}
}
class B extends self::A {
field dynamic nonFinalField;
final field dynamic finalField;
method method() → void {}
get getter() → dynamic {
return null;
}
set setter(dynamic _) → void {}
}
''');
expect(
hierarchy.getInterfaceMembers(b),
unorderedEquals([
nonShadowedMethod_a,
method_b,
getter_b,
nonFinalField_b,
finalField_b
]));
expect(hierarchy.getInterfaceMembers(b, setters: true),
unorderedEquals([nonShadowedSetter_a, setter_b, nonFinalField_b]));
}
void test_getOrderedClasses() {
var a = addClass(
new Class(name: 'A', supertype: objectSuper, fileUri: library.fileUri));
var b = addClass(new Class(
name: 'B', supertype: a.asThisSupertype, fileUri: library.fileUri));
var c = addClass(new Class(
name: 'C', supertype: b.asThisSupertype, fileUri: library.fileUri));
void assertOrderOfClasses(List<Class> unordered, List<Class> expected) {
var ordered = hierarchy.getOrderedClasses(unordered);
expect(ordered, expected);
}
assertOrderOfClasses([a, b, c], [a, b, c]);
assertOrderOfClasses([b, a, c], [a, b, c]);
assertOrderOfClasses([a, c, b], [a, b, c]);
assertOrderOfClasses([b, c, a], [a, b, c]);
assertOrderOfClasses([c, a, b], [a, b, c]);
assertOrderOfClasses([c, b, a], [a, b, c]);
assertOrderOfClasses([c, b], [b, c]);
}
void test_getTypeAsInstanceOf_generic_extends() {
var int = coreTypes.intLegacyRawType;
var bool = coreTypes.boolLegacyRawType;
var a = addGenericClass('A', ['T', 'U']);
var bT = new TypeParameter(
'T', coreTypes.objectLegacyRawType, coreTypes.objectLegacyRawType);
var bTT = new TypeParameterType(bT, Nullability.legacy);
var b = addClass(new Class(
name: 'B',
typeParameters: [bT],
supertype: new Supertype(a, [bTT, bool]),
fileUri: library.fileUri));
_assertTestLibraryText('''
class A<T*, U*> {}
class B<T*> extends self::A<self::B::T*, core::bool*> {}
''');
var b_int = new InterfaceType(b, Nullability.legacy, [int]);
expect(hierarchy.getTypeAsInstanceOf(b_int, a, library),
new InterfaceType(a, Nullability.legacy, [int, bool]));
expect(hierarchy.getTypeAsInstanceOf(b_int, objectClass, library),
new InterfaceType(objectClass, Nullability.legacy));
}
void _assertOverridePairs(Class class_, List<String> expected) {
List<String> overrideDescriptions = [];
void callback(
Member declaredMember, Member interfaceMember, bool isSetter) {
var suffix = isSetter ? '=' : '';
String declaredMemberName =
qualifiedMemberNameToString(declaredMember, includeLibraryName: true);
String declaredName = '${declaredMemberName}$suffix';
String interfaceMemberName = qualifiedMemberNameToString(interfaceMember,
includeLibraryName: true);
String interfaceName = '${interfaceMemberName}$suffix';
var desc = '$declaredName overrides $interfaceName';
overrideDescriptions.add(desc);
}
hierarchy.forEachOverridePair(class_, callback);
expect(overrideDescriptions, unorderedEquals(expected));
}
/// Assert that the test [library] has the [expectedText] presentation.
/// The presentation is close, but not identical to the normal Kernel one.
void _assertTestLibraryText(String expectedText) {
_assertLibraryText(library, expectedText);
}
void _assertLibraryText(Library lib, String expectedText) {
StringBuffer sb = new StringBuffer();
Printer printer = new Printer(sb);
printer.writeLibraryFile(lib);
String actualText = sb.toString();
// Clean up the text a bit.
const oftenUsedPrefix = '''
library test;
import self as self;
import "dart:core" as core;
''';
if (actualText.startsWith(oftenUsedPrefix)) {
actualText = actualText.substring(oftenUsedPrefix.length);
}
actualText = actualText.replaceAll('{\n}', '{}');
actualText = actualText.replaceAll(' extends core::Object', '');
if (actualText != expectedText) {
print('-------- Actual --------');
print(actualText + '------------------------');
}
expect(actualText, expectedText);
}
}