| // 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. |
| |
| library world_test; |
| |
| import 'package:compiler/src/elements/names.dart'; |
| import 'package:expect/async_helper.dart'; |
| import 'package:expect/expect.dart'; |
| import 'package:compiler/src/common/elements.dart'; |
| import 'package:compiler/src/common/names.dart'; |
| import 'package:compiler/src/elements/entities.dart'; |
| import 'package:compiler/src/js_model/js_world.dart' show JClosedWorld; |
| import 'package:compiler/src/universe/class_hierarchy.dart'; |
| import 'package:compiler/src/universe/class_set.dart'; |
| import '../helpers/type_test_helper.dart'; |
| |
| void main() { |
| runTests() async { |
| await testClassSets(); |
| await testProperties(); |
| await testNativeClasses(); |
| await testCommonSubclasses(); |
| await testLiveMembers(); |
| } |
| |
| asyncTest(() async { |
| print('--test from kernel------------------------------------------------'); |
| await runTests(); |
| }); |
| } |
| |
| testClassSets() async { |
| var env = await TypeEnvironment.create(r""" |
| import 'dart:html' as html; |
| |
| mixin class A implements X {} |
| mixin class B {} |
| class C_Super extends A {} |
| class C extends C_Super {} |
| class D implements A {} |
| class E extends B implements A {} |
| class F extends Object with A implements B {} |
| class G extends Object with B, A {} |
| class X {} |
| |
| main() { |
| A(); |
| B(); |
| C(); |
| D(); |
| E(); |
| F(); |
| G(); |
| html.window; |
| html.Worker(''); |
| } |
| """, testBackendWorld: true); |
| JClosedWorld closedWorld = env.jClosedWorld; |
| ElementEnvironment elementEnvironment = closedWorld.elementEnvironment; |
| |
| final Object_ = env.getElement("Object") as ClassEntity; |
| final A = env.getElement("A") as ClassEntity; |
| final B = env.getElement("B") as ClassEntity; |
| final C = env.getElement("C") as ClassEntity; |
| final D = env.getElement("D") as ClassEntity; |
| final E = env.getElement("E") as ClassEntity; |
| final F = env.getElement("F") as ClassEntity; |
| final G = env.getElement("G") as ClassEntity; |
| final X = env.getElement("X") as ClassEntity; |
| |
| void checkClasses( |
| String property, |
| ClassEntity cls, |
| Iterable<ClassEntity> foundClasses, |
| List<ClassEntity> expectedClasses, { |
| bool exact = true, |
| }) { |
| for (ClassEntity expectedClass in expectedClasses) { |
| Expect.isTrue( |
| foundClasses.contains(expectedClass), |
| "Expect $expectedClass in '$property' on $cls. " |
| "Found:\n ${foundClasses.join('\n ')}\n" |
| "${closedWorld.classHierarchy.dump(cls)}", |
| ); |
| } |
| if (exact) { |
| Expect.equals( |
| expectedClasses.length, |
| foundClasses.length, |
| "Unexpected classes " |
| "${foundClasses.where((c) => !expectedClasses.contains(c))} " |
| "in '$property' on $cls.\n" |
| "${closedWorld.classHierarchy.dump(cls)}", |
| ); |
| } |
| } |
| |
| void check( |
| String property, |
| ClassEntity cls, |
| Iterable<ClassEntity> foundClasses, |
| List<ClassEntity> expectedClasses, { |
| bool exact = true, |
| void forEach(ClassEntity cls, ForEachFunction f)?, |
| int getCount(ClassEntity cls)?, |
| }) { |
| checkClasses(property, cls, foundClasses, expectedClasses, exact: exact); |
| |
| if (forEach != null) { |
| List<ClassEntity> visited = <ClassEntity>[]; |
| forEach(cls, (ClassEntity c) { |
| visited.add(c); |
| return IterationStep.continue_; |
| }); |
| checkClasses( |
| 'forEach($property)', |
| cls, |
| visited, |
| expectedClasses, |
| exact: exact, |
| ); |
| } |
| |
| if (getCount != null && exact) { |
| int count = getCount(cls); |
| Expect.equals( |
| expectedClasses.length, |
| count, |
| "Unexpected class count in '$property' on $cls.\n" |
| "${closedWorld.classHierarchy.dump(cls)}", |
| ); |
| } |
| } |
| |
| void testSubclasses( |
| ClassEntity cls, |
| List<ClassEntity> expectedClasses, { |
| bool exact = true, |
| }) { |
| check( |
| 'subclassesOf', |
| cls, |
| closedWorld.classHierarchy.subclassesOf(cls), |
| expectedClasses, |
| exact: exact, |
| ); |
| } |
| |
| void testStrictSubclasses( |
| ClassEntity cls, |
| List<ClassEntity> expectedClasses, { |
| bool exact = true, |
| }) { |
| check( |
| 'strictSubclassesOf', |
| cls, |
| closedWorld.classHierarchy.strictSubclassesOf(cls), |
| expectedClasses, |
| exact: exact, |
| forEach: closedWorld.classHierarchy.forEachStrictSubclassOf, |
| getCount: closedWorld.classHierarchy.strictSubclassCount, |
| ); |
| } |
| |
| void testStrictSubtypes( |
| ClassEntity cls, |
| List<ClassEntity> expectedClasses, { |
| bool exact = true, |
| }) { |
| check( |
| 'strictSubtypesOf', |
| cls, |
| closedWorld.classHierarchy.strictSubtypesOf(cls), |
| expectedClasses, |
| exact: exact, |
| forEach: closedWorld.classHierarchy.forEachStrictSubtypeOf, |
| getCount: closedWorld.classHierarchy.strictSubtypeCount, |
| ); |
| } |
| |
| void testMixinUses( |
| ClassEntity cls, |
| List<ClassEntity> expectedClasses, { |
| bool exact = true, |
| }) { |
| check( |
| 'mixinUsesOf', |
| cls, |
| closedWorld.mixinUsesOf(cls), |
| expectedClasses, |
| exact: exact, |
| ); |
| } |
| |
| testSubclasses(Object_, [A, B, C, D, E, F, G], exact: false); |
| testSubclasses(A, [A, C]); |
| testSubclasses(B, [B, E]); |
| testSubclasses(C, [C]); |
| testSubclasses(D, [D]); |
| testSubclasses(E, [E]); |
| testSubclasses(F, [F]); |
| testSubclasses(G, [G]); |
| testSubclasses(X, []); |
| |
| testStrictSubclasses(Object_, [A, B, C, D, E, F, G], exact: false); |
| testStrictSubclasses(A, [C]); |
| testStrictSubclasses(B, [E]); |
| testStrictSubclasses(C, []); |
| testStrictSubclasses(D, []); |
| testStrictSubclasses(E, []); |
| testStrictSubclasses(F, []); |
| testStrictSubclasses(G, []); |
| testStrictSubclasses(X, []); |
| |
| testStrictSubtypes(Object_, [A, B, C, D, E, F, G], exact: false); |
| testStrictSubtypes(A, [C, D, E, F, G]); |
| testStrictSubtypes(B, [E, F, G]); |
| testStrictSubtypes(C, []); |
| testStrictSubtypes(D, []); |
| testStrictSubtypes(E, []); |
| testStrictSubtypes(F, []); |
| testStrictSubtypes(G, []); |
| testStrictSubtypes(X, [A, C, D, E, F, G]); |
| |
| testMixinUses(Object_, []); |
| testMixinUses(A, [ |
| elementEnvironment.getSuperClass(F)!, |
| elementEnvironment.getSuperClass(G)!, |
| ]); |
| testMixinUses(B, [ |
| elementEnvironment.getSuperClass(elementEnvironment.getSuperClass(G)!)!, |
| ]); |
| testMixinUses(C, []); |
| testMixinUses(D, []); |
| testMixinUses(E, []); |
| testMixinUses(F, []); |
| testMixinUses(G, []); |
| testMixinUses(X, []); |
| } |
| |
| testProperties() async { |
| var env = await TypeEnvironment.create(r""" |
| mixin class A {} |
| class A1 extends A {} |
| class A2 implements A {} |
| class A3 extends Object with A {} |
| |
| mixin class B {} |
| class B1 extends B {} |
| class B2 implements B {} |
| class B3 extends Object with B {} |
| |
| mixin class C {} |
| class C1 extends C {} |
| class C2 implements C {} |
| class C3 extends Object with C {} |
| |
| mixin class D {} |
| class D1 extends D {} |
| class D2 implements D {} |
| class D3 extends Object with D {} |
| |
| mixin class E {} |
| class E1 extends E {} |
| class E2 implements E {} |
| class E3 extends Object with E {} |
| |
| mixin class F {} |
| class F1 extends F {} |
| class F2 implements F {} |
| class F3 extends Object with F {} |
| |
| mixin class G {} |
| class G1 extends G {} |
| class G2 extends G1 {} |
| class G3 extends G2 implements G {} |
| class G4 extends G2 with G {} |
| |
| mixin class H {} |
| class H1 extends H {} |
| class H2 extends H1 {} |
| class H3 extends H2 implements H {} |
| class H4 extends H2 with H {} |
| |
| main() { |
| B(); |
| C1(); |
| D2(); |
| E3(); |
| F1(); |
| F2(); |
| G2(); |
| G3(); |
| H4(); |
| } |
| """, testBackendWorld: true); |
| JClosedWorld closedWorld = env.jClosedWorld; |
| |
| check(String name, {bool? hasStrictSubtype, bool? hasOnlySubclasses}) { |
| final cls = env.getElement(name) as ClassEntity; |
| Expect.equals( |
| hasStrictSubtype, |
| closedWorld.classHierarchy.hasAnyStrictSubtype(cls), |
| "Unexpected hasAnyStrictSubtype property on $cls.", |
| ); |
| Expect.equals( |
| hasOnlySubclasses, |
| closedWorld.classHierarchy.hasOnlySubclasses(cls), |
| "Unexpected hasOnlySubclasses property on $cls.", |
| ); |
| } |
| |
| check("Object", hasStrictSubtype: true, hasOnlySubclasses: true); |
| |
| // No instantiated Ax classes. |
| check("A", hasStrictSubtype: false, hasOnlySubclasses: true); |
| check("A1", hasStrictSubtype: false, hasOnlySubclasses: true); |
| check("A2", hasStrictSubtype: false, hasOnlySubclasses: true); |
| check("A3", hasStrictSubtype: false, hasOnlySubclasses: true); |
| |
| // class B instantiated |
| check("B", hasStrictSubtype: false, hasOnlySubclasses: true); |
| check("B1", hasStrictSubtype: false, hasOnlySubclasses: true); |
| check("B2", hasStrictSubtype: false, hasOnlySubclasses: true); |
| check("B3", hasStrictSubtype: false, hasOnlySubclasses: true); |
| |
| // class C1 extends C instantiated |
| check("C", hasStrictSubtype: true, hasOnlySubclasses: true); |
| check("C1", hasStrictSubtype: false, hasOnlySubclasses: true); |
| check("C2", hasStrictSubtype: false, hasOnlySubclasses: true); |
| check("C3", hasStrictSubtype: false, hasOnlySubclasses: true); |
| |
| // class D2 implements D instantiated |
| check("D", hasStrictSubtype: true, hasOnlySubclasses: false); |
| check("D1", hasStrictSubtype: false, hasOnlySubclasses: true); |
| check("D2", hasStrictSubtype: false, hasOnlySubclasses: true); |
| check("D3", hasStrictSubtype: false, hasOnlySubclasses: true); |
| |
| // class E2 extends Object with E instantiated |
| check("E", hasStrictSubtype: true, hasOnlySubclasses: false); |
| check("E1", hasStrictSubtype: false, hasOnlySubclasses: true); |
| check("E2", hasStrictSubtype: false, hasOnlySubclasses: true); |
| check("E3", hasStrictSubtype: false, hasOnlySubclasses: true); |
| |
| // class F1 extends F instantiated |
| // class F2 implements F instantiated |
| check("F", hasStrictSubtype: true, hasOnlySubclasses: false); |
| check("F1", hasStrictSubtype: false, hasOnlySubclasses: true); |
| check("F2", hasStrictSubtype: false, hasOnlySubclasses: true); |
| check("F3", hasStrictSubtype: false, hasOnlySubclasses: true); |
| |
| // class G2 extends G1 extends G instantiated |
| // class G3 extends G2 extends G1 extends G instantiated |
| check("G", hasStrictSubtype: true, hasOnlySubclasses: true); |
| check("G1", hasStrictSubtype: true, hasOnlySubclasses: true); |
| check("G2", hasStrictSubtype: true, hasOnlySubclasses: true); |
| check("G3", hasStrictSubtype: false, hasOnlySubclasses: true); |
| check("G4", hasStrictSubtype: false, hasOnlySubclasses: true); |
| |
| // class H4 extends H2 with H extends H1 extends H instantiated |
| check("H", hasStrictSubtype: true, hasOnlySubclasses: true); |
| check("H1", hasStrictSubtype: true, hasOnlySubclasses: true); |
| check("H2", hasStrictSubtype: true, hasOnlySubclasses: true); |
| check("H3", hasStrictSubtype: false, hasOnlySubclasses: true); |
| check("H4", hasStrictSubtype: false, hasOnlySubclasses: true); |
| } |
| |
| testNativeClasses() async { |
| var env = await TypeEnvironment.create(r""" |
| import 'dart:html' as html; |
| main() { |
| html.window; // Creates 'Window'. |
| html.Worker(''); // Creates 'Worker'. |
| html.CanvasElement() // Creates CanvasElement |
| ..getContext(''); // Creates CanvasRenderingContext2D |
| } |
| """, testBackendWorld: true); |
| JClosedWorld closedWorld = env.jClosedWorld; |
| ElementEnvironment elementEnvironment = closedWorld.elementEnvironment; |
| LibraryEntity dart_html = elementEnvironment.lookupLibrary(Uris.dartHtml)!; |
| |
| ClassEntity clsEventTarget = elementEnvironment.lookupClass( |
| dart_html, |
| 'EventTarget', |
| )!; |
| ClassEntity clsWindow = elementEnvironment.lookupClass(dart_html, 'Window')!; |
| ClassEntity clsAbstractWorker = elementEnvironment.lookupClass( |
| dart_html, |
| 'AbstractWorker', |
| )!; |
| ClassEntity clsWorker = elementEnvironment.lookupClass(dart_html, 'Worker')!; |
| ClassEntity clsCanvasElement = elementEnvironment.lookupClass( |
| dart_html, |
| 'CanvasElement', |
| )!; |
| ClassEntity clsCanvasRenderingContext = elementEnvironment.lookupClass( |
| dart_html, |
| 'CanvasRenderingContext', |
| )!; |
| ClassEntity clsCanvasRenderingContext2D = elementEnvironment.lookupClass( |
| dart_html, |
| 'CanvasRenderingContext2D', |
| )!; |
| |
| List<ClassEntity> allClasses = [ |
| clsEventTarget, |
| clsWindow, |
| clsAbstractWorker, |
| clsWorker, |
| clsCanvasElement, |
| clsCanvasRenderingContext, |
| clsCanvasRenderingContext2D, |
| ]; |
| |
| check( |
| ClassEntity cls, { |
| required bool isDirectlyInstantiated, |
| required bool isAbstractlyInstantiated, |
| required bool isIndirectlyInstantiated, |
| required bool hasStrictSubtype, |
| required bool hasOnlySubclasses, |
| ClassEntity? lubOfInstantiatedSubclasses, |
| ClassEntity? lubOfInstantiatedSubtypes, |
| int? instantiatedSubclassCount, |
| int? instantiatedSubtypeCount, |
| List<ClassEntity> subclasses = const <ClassEntity>[], |
| List<ClassEntity> subtypes = const <ClassEntity>[], |
| }) { |
| ClassSet classSet = closedWorld.classHierarchy.getClassSet(cls); |
| ClassHierarchyNode node = classSet.node; |
| |
| String dumpText = '\n${closedWorld.classHierarchy.dump(cls)}'; |
| |
| Expect.equals( |
| isDirectlyInstantiated, |
| closedWorld.classHierarchy.isDirectlyInstantiated(cls), |
| "Unexpected isDirectlyInstantiated property on $cls.$dumpText", |
| ); |
| Expect.equals( |
| isAbstractlyInstantiated, |
| closedWorld.classHierarchy.isAbstractlyInstantiated(cls), |
| "Unexpected isAbstractlyInstantiated property on $cls.$dumpText", |
| ); |
| Expect.equals( |
| isIndirectlyInstantiated, |
| closedWorld.classHierarchy.isIndirectlyInstantiated(cls), |
| "Unexpected isIndirectlyInstantiated property on $cls.$dumpText", |
| ); |
| Expect.equals( |
| hasStrictSubtype, |
| closedWorld.classHierarchy.hasAnyStrictSubtype(cls), |
| "Unexpected hasAnyStrictSubtype property on $cls.$dumpText", |
| ); |
| Expect.equals( |
| hasOnlySubclasses, |
| closedWorld.classHierarchy.hasOnlySubclasses(cls), |
| "Unexpected hasOnlySubclasses property on $cls.$dumpText", |
| ); |
| Expect.equals( |
| lubOfInstantiatedSubclasses, |
| node.getLubOfInstantiatedSubclasses(), |
| "Unexpected getLubOfInstantiatedSubclasses() result on $cls.$dumpText", |
| ); |
| Expect.equals( |
| lubOfInstantiatedSubtypes, |
| classSet.getLubOfInstantiatedSubtypes(), |
| "Unexpected getLubOfInstantiatedSubtypes() result on $cls.$dumpText", |
| ); |
| if (instantiatedSubclassCount != null) { |
| Expect.equals( |
| instantiatedSubclassCount, |
| node.instantiatedSubclassCount, |
| "Unexpected instantiatedSubclassCount property on $cls.$dumpText", |
| ); |
| } |
| if (instantiatedSubtypeCount != null) { |
| Expect.equals( |
| instantiatedSubtypeCount, |
| classSet.instantiatedSubtypeCount, |
| "Unexpected instantiatedSubtypeCount property on $cls.$dumpText", |
| ); |
| } |
| for (ClassEntity other in allClasses) { |
| if (other == cls) continue; |
| if (!closedWorld.classHierarchy.isExplicitlyInstantiated(other)) continue; |
| Expect.equals( |
| subclasses.contains(other), |
| closedWorld.classHierarchy.isSubclassOf(other, cls), |
| "Unexpected subclass relation between $other and $cls.", |
| ); |
| Expect.equals( |
| subtypes.contains(other), |
| closedWorld.classHierarchy.isSubtypeOf(other, cls), |
| "Unexpected subtype relation between $other and $cls.", |
| ); |
| } |
| |
| Set<ClassEntity> strictSubclasses = Set<ClassEntity>(); |
| closedWorld.classHierarchy.forEachStrictSubclassOf(cls, ( |
| ClassEntity other, |
| ) { |
| if (allClasses.contains(other)) { |
| strictSubclasses.add(other); |
| } |
| return IterationStep.continue_; |
| }); |
| Expect.setEquals( |
| subclasses, |
| strictSubclasses, |
| "Unexpected strict subclasses of $cls: ${strictSubclasses}.", |
| ); |
| |
| Set<ClassEntity> strictSubtypes = Set<ClassEntity>(); |
| closedWorld.classHierarchy.forEachStrictSubtypeOf(cls, (ClassEntity other) { |
| if (allClasses.contains(other)) { |
| strictSubtypes.add(other); |
| } |
| return IterationStep.continue_; |
| }); |
| Expect.setEquals( |
| subtypes, |
| strictSubtypes, |
| "Unexpected strict subtypes of $cls: $strictSubtypes.", |
| ); |
| } |
| |
| // Extended by Window. |
| check( |
| clsEventTarget, |
| isDirectlyInstantiated: false, |
| isAbstractlyInstantiated: false, |
| isIndirectlyInstantiated: true, |
| hasStrictSubtype: true, |
| hasOnlySubclasses: true, |
| lubOfInstantiatedSubclasses: clsEventTarget, |
| lubOfInstantiatedSubtypes: clsEventTarget, |
| // May vary with implementation, do no test. |
| instantiatedSubclassCount: null, |
| instantiatedSubtypeCount: null, |
| subclasses: [clsWindow, clsCanvasElement, clsWorker], |
| subtypes: [clsWindow, clsCanvasElement, clsWorker], |
| ); |
| |
| // Created by 'html.window'. |
| check( |
| clsWindow, |
| isDirectlyInstantiated: false, |
| isAbstractlyInstantiated: true, |
| isIndirectlyInstantiated: false, |
| hasStrictSubtype: false, |
| hasOnlySubclasses: true, |
| lubOfInstantiatedSubclasses: clsWindow, |
| lubOfInstantiatedSubtypes: clsWindow, |
| instantiatedSubclassCount: 0, |
| instantiatedSubtypeCount: 0, |
| ); |
| |
| // Implemented by 'Worker'. |
| check( |
| clsAbstractWorker, |
| isDirectlyInstantiated: false, |
| isAbstractlyInstantiated: false, |
| isIndirectlyInstantiated: false, |
| hasStrictSubtype: true, |
| hasOnlySubclasses: false, |
| lubOfInstantiatedSubclasses: null, |
| lubOfInstantiatedSubtypes: clsWorker, |
| instantiatedSubclassCount: 0, |
| instantiatedSubtypeCount: 1, |
| subtypes: [clsWorker], |
| ); |
| |
| // Created by 'new html.Worker'. |
| check( |
| clsWorker, |
| isDirectlyInstantiated: false, |
| isAbstractlyInstantiated: true, |
| isIndirectlyInstantiated: false, |
| hasStrictSubtype: false, |
| hasOnlySubclasses: true, |
| lubOfInstantiatedSubclasses: clsWorker, |
| lubOfInstantiatedSubtypes: clsWorker, |
| instantiatedSubclassCount: 0, |
| instantiatedSubtypeCount: 0, |
| ); |
| |
| // Created by 'new html.CanvasElement'. |
| check( |
| clsCanvasElement, |
| isDirectlyInstantiated: false, |
| isAbstractlyInstantiated: true, |
| isIndirectlyInstantiated: false, |
| hasStrictSubtype: false, |
| hasOnlySubclasses: true, |
| lubOfInstantiatedSubclasses: clsCanvasElement, |
| lubOfInstantiatedSubtypes: clsCanvasElement, |
| instantiatedSubclassCount: 0, |
| instantiatedSubtypeCount: 0, |
| ); |
| |
| // Implemented by CanvasRenderingContext2D and RenderingContext. |
| check( |
| clsCanvasRenderingContext, |
| isDirectlyInstantiated: false, |
| isAbstractlyInstantiated: false, |
| isIndirectlyInstantiated: false, |
| hasStrictSubtype: true, |
| hasOnlySubclasses: false, |
| lubOfInstantiatedSubclasses: null, |
| lubOfInstantiatedSubtypes: clsCanvasRenderingContext, |
| instantiatedSubclassCount: 0, |
| instantiatedSubtypeCount: 2, |
| subtypes: [clsCanvasRenderingContext2D], |
| ); |
| |
| // Created by 'html.CanvasElement.getContext'. |
| check( |
| clsCanvasRenderingContext2D, |
| isDirectlyInstantiated: false, |
| isAbstractlyInstantiated: true, |
| isIndirectlyInstantiated: false, |
| hasStrictSubtype: false, |
| hasOnlySubclasses: true, |
| lubOfInstantiatedSubclasses: clsCanvasRenderingContext2D, |
| lubOfInstantiatedSubtypes: clsCanvasRenderingContext2D, |
| instantiatedSubclassCount: 0, |
| instantiatedSubtypeCount: 0, |
| ); |
| } |
| |
| testCommonSubclasses() async { |
| var env = await TypeEnvironment.create(r""" |
| class A {} |
| class B {} |
| class C extends A {} |
| class D implements A {} |
| class E extends B {} |
| class F implements C, E {} |
| class G extends C implements E {} |
| class H implements C {} |
| class I extends D implements E {} |
| class J extends E implements D {} |
| main() { |
| A(); |
| B(); |
| C(); |
| D(); |
| E(); |
| F(); |
| G(); |
| H(); |
| I(); |
| J(); |
| } |
| """, testBackendWorld: true); |
| JClosedWorld closedWorld = env.jClosedWorld; |
| |
| final A = env.getElement("A") as ClassEntity; |
| final B = env.getElement("B") as ClassEntity; |
| final C = env.getElement("C") as ClassEntity; |
| final F = env.getElement("F") as ClassEntity; |
| final G = env.getElement("G") as ClassEntity; |
| final I = env.getElement("I") as ClassEntity; |
| final J = env.getElement("J") as ClassEntity; |
| |
| ClassQuery? toClassQuery( |
| SubclassResult result, |
| ClassEntity cls1, |
| ClassQuery query1, |
| ClassEntity cls2, |
| ClassQuery query2, |
| ) { |
| switch (result) { |
| case SimpleSubclassResult.empty: |
| return null; |
| case SimpleSubclassResult.exact1: |
| return ClassQuery.exact; |
| case SimpleSubclassResult.exact2: |
| return ClassQuery.exact; |
| case SimpleSubclassResult.subclass1: |
| return ClassQuery.subclass; |
| case SimpleSubclassResult.subclass2: |
| return ClassQuery.subclass; |
| case SimpleSubclassResult.subtype1: |
| return ClassQuery.subtype; |
| case SimpleSubclassResult.subtype2: |
| return ClassQuery.subtype; |
| case SetSubclassResult(): |
| return null; |
| } |
| } |
| |
| ClassEntity? toClassEntity( |
| SubclassResult result, |
| ClassEntity cls1, |
| ClassQuery query1, |
| ClassEntity cls2, |
| ClassQuery query2, |
| ) { |
| switch (result) { |
| case SimpleSubclassResult.empty: |
| return null; |
| case SimpleSubclassResult.exact1: |
| return cls1; |
| case SimpleSubclassResult.exact2: |
| return cls2; |
| case SimpleSubclassResult.subclass1: |
| return cls1; |
| case SimpleSubclassResult.subclass2: |
| return cls2; |
| case SimpleSubclassResult.subtype1: |
| return cls1; |
| case SimpleSubclassResult.subtype2: |
| return cls2; |
| case SetSubclassResult(): |
| return null; |
| } |
| } |
| |
| void check( |
| ClassEntity cls1, |
| ClassQuery query1, |
| ClassEntity cls2, |
| ClassQuery query2, |
| SubclassResult expectedResult, |
| ) { |
| SubclassResult result1 = closedWorld.classHierarchy.commonSubclasses( |
| cls1, |
| query1, |
| cls2, |
| query2, |
| ); |
| SubclassResult result2 = closedWorld.classHierarchy.commonSubclasses( |
| cls2, |
| query2, |
| cls1, |
| query1, |
| ); |
| Expect.equals( |
| toClassQuery(result1, cls1, query1, cls2, query2), |
| toClassQuery(result2, cls2, query2, cls1, query1), |
| "Asymmetric results for ($cls1,$query1) vs ($cls2,$query2):" |
| "\n a vs b: $result1\n b vs a: $result2", |
| ); |
| Expect.equals( |
| toClassEntity(result1, cls1, query1, cls2, query2), |
| toClassEntity(result2, cls2, query2, cls1, query1), |
| "Asymmetric results for ($cls1,$query1) vs ($cls2,$query2):" |
| "\n a vs b: $result1\n b vs a: $result2", |
| ); |
| switch (expectedResult) { |
| case SimpleSubclassResult(): |
| Expect.equals( |
| expectedResult, |
| result1, |
| "Unexpected results for ($cls1,$query1) vs ($cls2,$query2):" |
| "\n expected: $expectedResult\n actual: $result1", |
| ); |
| case SetSubclassResult(): |
| Expect.type<SetSubclassResult>(result1); |
| Expect.type<SetSubclassResult>(result2); |
| result1 as SetSubclassResult; |
| result2 as SetSubclassResult; |
| Expect.setEquals( |
| result1.classes, |
| result2.classes, |
| "Asymmetric results for ($cls1,$query1) vs ($cls2,$query2):" |
| "\n a vs b: $result1\n b vs a: $result2", |
| ); |
| Expect.setEquals( |
| expectedResult.classes, |
| result1.classes, |
| "Unexpected results for ($cls1,$query1) vs ($cls2,$query2):" |
| "\n expected: $expectedResult\n actual: $result1", |
| ); |
| } |
| } |
| |
| check(A, ClassQuery.exact, A, ClassQuery.exact, SimpleSubclassResult.exact1); |
| check( |
| A, |
| ClassQuery.exact, |
| A, |
| ClassQuery.subclass, |
| SimpleSubclassResult.exact1, |
| ); |
| check( |
| A, |
| ClassQuery.exact, |
| A, |
| ClassQuery.subtype, |
| SimpleSubclassResult.exact1, |
| ); |
| check( |
| A, |
| ClassQuery.subclass, |
| A, |
| ClassQuery.subclass, |
| SimpleSubclassResult.subclass1, |
| ); |
| check( |
| A, |
| ClassQuery.subclass, |
| A, |
| ClassQuery.subtype, |
| SimpleSubclassResult.subclass1, |
| ); |
| check( |
| A, |
| ClassQuery.subtype, |
| A, |
| ClassQuery.subtype, |
| SimpleSubclassResult.subtype1, |
| ); |
| |
| check(A, ClassQuery.exact, B, ClassQuery.exact, SimpleSubclassResult.empty); |
| check( |
| A, |
| ClassQuery.exact, |
| B, |
| ClassQuery.subclass, |
| SimpleSubclassResult.empty, |
| ); |
| check( |
| A, |
| ClassQuery.subclass, |
| B, |
| ClassQuery.exact, |
| SimpleSubclassResult.empty, |
| ); |
| check(A, ClassQuery.exact, B, ClassQuery.subtype, SimpleSubclassResult.empty); |
| check(A, ClassQuery.subtype, B, ClassQuery.exact, SimpleSubclassResult.empty); |
| check( |
| A, |
| ClassQuery.subclass, |
| B, |
| ClassQuery.subclass, |
| SimpleSubclassResult.empty, |
| ); |
| check(A, ClassQuery.subclass, B, ClassQuery.subtype, SetSubclassResult([G])); |
| check(A, ClassQuery.subtype, B, ClassQuery.subclass, SetSubclassResult([J])); |
| check( |
| A, |
| ClassQuery.subtype, |
| B, |
| ClassQuery.subtype, |
| SetSubclassResult([F, G, I, J]), |
| ); |
| |
| check(A, ClassQuery.exact, C, ClassQuery.exact, SimpleSubclassResult.empty); |
| check( |
| A, |
| ClassQuery.exact, |
| C, |
| ClassQuery.subclass, |
| SimpleSubclassResult.empty, |
| ); |
| check( |
| A, |
| ClassQuery.subclass, |
| C, |
| ClassQuery.exact, |
| SimpleSubclassResult.exact2, |
| ); |
| check(A, ClassQuery.exact, C, ClassQuery.subtype, SimpleSubclassResult.empty); |
| check( |
| A, |
| ClassQuery.subtype, |
| C, |
| ClassQuery.exact, |
| SimpleSubclassResult.exact2, |
| ); |
| check( |
| A, |
| ClassQuery.subclass, |
| C, |
| ClassQuery.subclass, |
| SimpleSubclassResult.subclass2, |
| ); |
| check(A, ClassQuery.subclass, C, ClassQuery.subtype, SetSubclassResult([C])); |
| check( |
| A, |
| ClassQuery.subtype, |
| C, |
| ClassQuery.subclass, |
| SimpleSubclassResult.subclass2, |
| ); |
| check( |
| A, |
| ClassQuery.subtype, |
| C, |
| ClassQuery.subtype, |
| SimpleSubclassResult.subtype2, |
| ); |
| |
| check(B, ClassQuery.exact, C, ClassQuery.exact, SimpleSubclassResult.empty); |
| check( |
| B, |
| ClassQuery.exact, |
| C, |
| ClassQuery.subclass, |
| SimpleSubclassResult.empty, |
| ); |
| check( |
| B, |
| ClassQuery.subclass, |
| C, |
| ClassQuery.exact, |
| SimpleSubclassResult.empty, |
| ); |
| check(B, ClassQuery.exact, C, ClassQuery.subtype, SimpleSubclassResult.empty); |
| check(B, ClassQuery.subtype, C, ClassQuery.exact, SimpleSubclassResult.empty); |
| check( |
| B, |
| ClassQuery.subclass, |
| C, |
| ClassQuery.subclass, |
| SimpleSubclassResult.empty, |
| ); |
| check(B, ClassQuery.subclass, C, ClassQuery.subtype, SetSubclassResult([])); |
| check(B, ClassQuery.subtype, C, ClassQuery.subclass, SetSubclassResult([G])); |
| check( |
| B, |
| ClassQuery.subtype, |
| C, |
| ClassQuery.subtype, |
| SetSubclassResult([F, G]), |
| ); |
| } |
| |
| testLiveMembers() async { |
| final env = await TypeEnvironment.create(r""" |
| class A { int a() => 1; } |
| |
| mixin B { int b(); } |
| class C with B { int b() => 2; } |
| |
| mixin D { int d(); } |
| mixin E on D { int e() => d(); } |
| class F implements D { int d() => 3; } |
| class G extends F with E {} |
| |
| abstract class H { int h(); } |
| class I implements H { int h() => 4; } |
| |
| abstract class J { int j(); } |
| |
| abstract class K { int k(); } |
| class L extends K { int k() => 5; } |
| |
| abstract class M { int m(); } |
| class N extends M { int m() => 6; } |
| |
| main() { |
| A().a(); |
| C().b(); |
| G().e(); |
| I().h(); |
| L().k(); |
| N(); |
| } |
| """, testBackendWorld: true); |
| |
| JClosedWorld closedWorld = env.jClosedWorld; |
| |
| void check( |
| String clsName, |
| String memberName, { |
| bool expectExists = true, |
| bool expectAbstract = false, |
| bool expectConcrete = false, |
| }) { |
| final cls = env.getClass(clsName); |
| final member = closedWorld.elementEnvironment.lookupLocalClassMember( |
| cls, |
| Name(memberName, Uri()), |
| ); |
| if (expectExists) { |
| Expect.isNotNull( |
| member, |
| "Expected $clsName to contain member $memberName.", |
| ); |
| } else { |
| Expect.isNull( |
| member, |
| "Expected $clsName not to contain member $memberName.", |
| ); |
| return; |
| } |
| Expect.isTrue( |
| expectAbstract ^ expectConcrete, |
| "Can only expect to be in one of liveAbstractInstanceMembers and " |
| "liveInstanceMembers.", |
| ); |
| if (expectAbstract) { |
| Expect.isTrue( |
| closedWorld.liveAbstractInstanceMembers.contains(member), |
| "Expected $member to exist in liveAbstractInstanceMembers.", |
| ); |
| Expect.isFalse( |
| closedWorld.liveInstanceMembers.contains(member), |
| "Expected $member to not exist in liveInstanceMembers.", |
| ); |
| } else { |
| Expect.isTrue( |
| closedWorld.liveInstanceMembers.contains(member), |
| "Expected $member to exist in liveInstanceMembers.", |
| ); |
| Expect.isFalse( |
| closedWorld.liveAbstractInstanceMembers.contains(member), |
| "Expected $member to not exist in liveAbstractInstanceMembers.", |
| ); |
| } |
| } |
| |
| check('A', 'a', expectConcrete: true); |
| check('B', 'b', expectAbstract: true); |
| check('C', 'b', expectConcrete: true); |
| check('D', 'd', expectAbstract: true); |
| check('E', 'e', expectConcrete: true); |
| check('F', 'd', expectConcrete: true); |
| check('H', 'h', expectAbstract: true); |
| check('I', 'h', expectConcrete: true); |
| check('J', 'j', expectExists: false); |
| check('K', 'k', expectAbstract: true); |
| check('L', 'k', expectConcrete: true); |
| check('M', 'm', expectExists: false); |
| check('N', 'm', expectExists: false); |
| } |