// 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. // @dart = 2.9 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"; 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(); CoreTypes coreTypes; 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 typeParameterNames, {Supertype extends_(List typeParameterTypes), List implements_(List typeParameterTypes)}) { 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) : []; 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 {} class B extends self::A {} class C extends self::B {} '''); 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 {} class B implements self::A {} class C implements self::B {} '''); 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 {} class B = core::Object with self::A {} class C = core::Object with self::B {} '''); 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 unordered, List 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 {} class B extends self::A {} '''); 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 expected) { List 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); } }