diff --git a/pkg/dev_compiler/lib/src/kernel/compiler.dart b/pkg/dev_compiler/lib/src/kernel/compiler.dart index b85602b2e3d..8bf9eeefc2b 100644 --- a/pkg/dev_compiler/lib/src/kernel/compiler.dart +++ b/pkg/dev_compiler/lib/src/kernel/compiler.dart @@ -350,7 +350,7 @@ class ProgramCompiler extends ComputeOnceConstantVisitor _assertInteropMethod = sdk.getTopLevelMember( 'dart:_runtime', 'assertInterop') as Procedure, _futureOrNormalizer = FutureOrNormalizer(_coreTypes), - _typeRecipeGenerator = TypeRecipeGenerator(_coreTypes); + _typeRecipeGenerator = TypeRecipeGenerator(_coreTypes, _hierarchy); @override Library? get currentLibrary => _currentLibrary; @@ -484,6 +484,20 @@ class ProgramCompiler extends ComputeOnceConstantVisitor moduleItems.addAll(afterClassDefItems); afterClassDefItems.clear(); + // Add all type hierarchy rules for the interface types used in this module. + if (_options.newRuntimeTypes) { + var universeClass = + rtiLibrary.classes.firstWhere((cls) => cls.name == '_Universe'); + var typeRules = _typeRecipeGenerator.visitedInterfaceTypeRules; + var template = "#._Universe.#(#, JSON.parse('${jsonEncode(typeRules)}'))"; + var addRulesStatement = js.call(template, [ + emitLibraryName(rtiLibrary), + _emitMemberName('addRules', memberClass: universeClass), + runtimeCall('typeUniverse') + ]).toStatement(); + moduleItems.add(addRulesStatement); + } + // Visit directives (for exports) libraries.forEach(_emitExports); @@ -763,15 +777,15 @@ class ProgramCompiler extends ComputeOnceConstantVisitor }); var jsCtors = _defineConstructors(c, className); + + // TODO(nshahan) Use ClassTypeEnvironment when the representation of generic + // classes is no longer a closure that defines the class and captures the + // type arguments. // Emitting class members in a class type environment results in a more // succinct type representation when referencing class type arguments from - // instance members. - var savedTypeEnvironment = _currentTypeEnvironment; - if (c.typeParameters.isNotEmpty) { - _currentTypeEnvironment = ClassTypeEnvironment(c.typeParameters); - } + // instance members but the type rules must include mappings of all type + // arguments throughout the hierarchy. var jsMethods = _emitClassMethods(c); - _currentTypeEnvironment = savedTypeEnvironment; _emitSuperHelperSymbols(body); // Deferred supertypes must be evaluated lazily while emitting classes to @@ -795,11 +809,34 @@ class ProgramCompiler extends ComputeOnceConstantVisitor var finishGenericTypeTest = _emitClassTypeTests(c, className, body); + /// Collects all implemented types in the ancestry of [cls]. + Iterable transitiveImplementedTypes(Class cls) { + var allImplementedTypes = {}; + var toVisit = ListQueue()..addAll(cls.implementedTypes); + while (toVisit.isNotEmpty) { + var supertype = toVisit.removeFirst(); + var superclass = supertype.classNode; + if (allImplementedTypes.contains(supertype) || + superclass == _coreTypes.objectClass) continue; + toVisit.addAll(superclass.supers); + // Skip encoding the synthetic classes in the type rules because they + // will never be instantiated or appear in type tests. + if (superclass.isAnonymousMixin) continue; + allImplementedTypes.add(supertype); + } + return allImplementedTypes; + } + // Attach caches on all canonicalized types. if (_options.newRuntimeTypes) { var name = _typeRecipeGenerator.interfaceTypeRecipe(c); - body.add(runtimeStatement( - 'addRtiResources(#, #)', [className, js.string(name)])); + var implementedRecipes = [ + name, + for (var type in transitiveImplementedTypes(c)) + _typeRecipeGenerator.interfaceTypeRecipe(type.classNode) + ]; + body.add(runtimeStatement('addRtiResources(#, #)', + [className, js_ast.stringArray(implementedRecipes)])); } body.add(runtimeStatement('addTypeCaches(#)', [className])); @@ -981,8 +1018,13 @@ class ProgramCompiler extends ComputeOnceConstantVisitor if (t is InterfaceType) { _declareBeforeUse(t.classNode); if (t.typeArguments.isNotEmpty) { - var typeRep = - _emitGenericClassType(t, t.typeArguments.map(emitDeferredType)); + var typeRep = _emitGenericClassType( + t, + _options.newRuntimeTypes + // No reason to defer type arguments in the new type + // representation. + ? t.typeArguments.map(_emitType) + : t.typeArguments.map(emitDeferredType)); return emitNullability ? _emitNullabilityWrapper(typeRep, t.declaredNullability) : typeRep; @@ -3041,11 +3083,12 @@ class ProgramCompiler extends ComputeOnceConstantVisitor } else if (environment is ClassTypeEnvironment) { // Class type environments are already constructed and attached to the // instance of a generic class. - return js.call('this.${js_ast.FixedNames.rtiName}'); + var env = runtimeCall('getReifiedType(this)'); + return emitRtiEval(env, recipe); } else if (environment is ExtendedClassTypeEnvironment) { // A generic class instance already stores a reference to a type // containing all of its type arguments. - var env = js.call('this.${js_ast.FixedNames.rtiName}'); + var env = runtimeCall('getReifiedType(this)'); // Bind extra type parameters. for (var parameter in environment.extendedParameters) { env = emitRtiBind(env, parameter); @@ -3203,6 +3246,44 @@ class ProgramCompiler extends ComputeOnceConstantVisitor return _typeTable.nameType(type, typeRep); } + /// Emits the a reference to the class described by [type]. + /// + /// The nullability of [type] is not considered because it is meaningless when + /// describing a reference to the class itself. + /// + /// In the case of a generic type, this reference will be a call to the + /// function that defines the class and will pass the type parameters as + /// arguments. The nullability of the type parameters does have meaning so it + /// is encoded. + js_ast.Expression _emitClassRef(InterfaceType type) { + var cls = type.classNode; + _declareBeforeUse(cls); + // Type parameters don't matter as JS interop types cannot be reified. + // package:js types fall under `@JS`, `@anonymous`, or `@staticInterop` + // types. `@JS` types are used to correspond to JS types that exist, but we + // do not use the underlying type for type checks, so they operate virtually + // the same as `@anonymous` types. `@staticInterop` types, however, can be + // casted to other `package:js` types as well as any type that inherits + // `JavaScriptObject`. This is to match the behavior across the other + // backends that use erasure. We represent `@JS` and `@anonymous` types with + // a NonStaticInteropType and `@staticInterop` with a StaticInteropType to + // make this distinction at runtime. + var jsName = isJSAnonymousType(cls) + ? getLocalClassName(cls) + : _emitJsNameWithoutGlobal(cls); + if (jsName != null) { + return runtimeCall('packageJSType(#, #)', + [js.escapedString(jsName), js.boolean(isStaticInteropType(cls))]); + } + var args = type.typeArguments; + Iterable? jsArgs; + if (args.any((a) => a != const DynamicType())) { + jsArgs = args.map(_emitType); + } + if (jsArgs != null) return _emitGenericClassType(type, jsArgs); + return _emitTopLevelNameNoInterop(type.classNode); + } + /// Emits the representation of a FutureOr [type]. js_ast.Expression _emitFutureOrType(FutureOrType type) { _declareBeforeUse(_coreTypes.deprecatedFutureOrClass); @@ -3420,7 +3501,9 @@ class ProgramCompiler extends ComputeOnceConstantVisitor /// Emits an expression that lets you access statics on a [type] from code. js_ast.Expression _emitConstructorAccess(InterfaceType type) { return _emitJSInterop(type.classNode) ?? - _emitInterfaceType(type, emitNullability: false); + (_options.newRuntimeTypes + ? _emitClassRef(type) + : _emitInterfaceType(type, emitNullability: false)); } js_ast.Expression _emitConstructorName(InterfaceType type, Member c) { diff --git a/pkg/dev_compiler/lib/src/kernel/type_recipe_generator.dart b/pkg/dev_compiler/lib/src/kernel/type_recipe_generator.dart index aeff27a85c6..5d52235e117 100644 --- a/pkg/dev_compiler/lib/src/kernel/type_recipe_generator.dart +++ b/pkg/dev_compiler/lib/src/kernel/type_recipe_generator.dart @@ -2,8 +2,11 @@ // 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 'dart:collection'; + import 'package:js_shared/synced/recipe_syntax.dart' show Recipe; import 'package:kernel/ast.dart'; +import 'package:kernel/class_hierarchy.dart'; import 'package:kernel/core_types.dart'; import 'package:path/path.dart' as p; @@ -18,10 +21,13 @@ import 'type_environment.dart'; /// `ProgramCompiler`. It provides the API to interact with the DartType visitor /// that generates the type recipes. class TypeRecipeGenerator { + final CoreTypes _coreTypes; + final ClassHierarchy _hierarchy; final _TypeRecipeVisitor _recipeVisitor; - TypeRecipeGenerator(CoreTypes coreTypes) - : _recipeVisitor = + TypeRecipeGenerator(CoreTypes coreTypes, this._hierarchy) + : _coreTypes = coreTypes, + _recipeVisitor = _TypeRecipeVisitor(const EmptyTypeEnvironment(), coreTypes); /// Returns a recipe for the provided [type] packaged with an environment with @@ -36,11 +42,66 @@ class TypeRecipeGenerator { var minimalEnvironment = environment.prune(parametersInType); // Set the visitor state, generate the recipe, and package it with the // environment required to evaluate it. - _recipeVisitor.setState(environment: minimalEnvironment); + _recipeVisitor.setState( + environment: minimalEnvironment, recordInterfaceTypes: true); var recipe = type.accept(_recipeVisitor); return GeneratedRecipe(recipe, minimalEnvironment); } + /// Returns a mapping of type hierarchies for all [InterfaceType]s that have + /// appeared in type recipes. + /// + /// This mapping is intended to satisfy the requirements for the + /// `_Universe.addRules()` static method in the runtime rti library. + /// + /// The format is compatible to directly encode as the JSON string expected + /// by the runtime library: + /// + /// ``` + /// '{"type 0": {"supertype 0": ["type argument 0"... "type argument n"], + /// ... + /// "supertype n": ["type argument 0"... "type argument n"]}, + /// ... + /// "type n": {...}}' + /// ``` + Map>> get visitedInterfaceTypeRules { + var rules = >>{}; + for (var type in _recipeVisitor.visitedInterfaceTypes) { + var recipe = interfaceTypeRecipe(type.classNode); + var cls = type.classNode; + // Create a class type environment for calculating type argument indices. + // Avoid recording the types while iterating the visited types. + _recipeVisitor.setState( + environment: ClassTypeEnvironment(cls.typeParameters), + recordInterfaceTypes: false); + var supertypeEntries = >{}; + // Encode type rules for all supers. + var toVisit = ListQueue.from(cls.supers); + var visited = {}; + while (toVisit.isNotEmpty) { + var currentClass = toVisit.removeFirst().classNode; + if (currentClass == _coreTypes.objectClass) continue; + var currentType = _hierarchy.getClassAsInstanceOf(cls, currentClass)!; + if (visited.contains(currentType)) continue; + // Add all supers to the visit queue. + toVisit.addAll(currentClass.supers); + // Skip encoding the synthetic classes in the type rules because they + // will never be instantiated or appear in type tests. + if (currentClass.isAnonymousMixin) continue; + // Encode this type rule. + var recipe = interfaceTypeRecipe(currentClass); + var typeArgumentRecipes = [ + for (var typeArgument in currentType.typeArguments) + typeArgument.accept(_recipeVisitor) + ]; + supertypeEntries[recipe] = typeArgumentRecipes; + visited.add(currentType); + } + if (supertypeEntries.isNotEmpty) rules[recipe] = supertypeEntries; + } + return rules; + } + String interfaceTypeRecipe(Class node) => _recipeVisitor.interfaceTypeRecipe(node); } @@ -60,7 +121,21 @@ class _TypeRecipeVisitor extends DartTypeVisitor { /// Part of the state that should be set before visiting a type. /// Used to determine the indices for type variables. DDCTypeEnvironment _typeEnvironment; + + /// Type parameters introduced to the environment while visiting generic + /// function types nested within other types. var _unboundTypeParameters = []; + + /// When `true` this visitor will record all of the [InterfaceType]s it + /// visits. + /// + /// Part of the state that should be set before visiting a type. + /// These types can be used later to produce runtime type hierarchy rules for + /// all of the visited interface types. + bool _recordInterfaceTypes = true; + + /// All of the [InterfaceType]s visited. + final _visitedInterfaceTypes = {}; final CoreTypes _coreTypes; _TypeRecipeVisitor(this._typeEnvironment, this._coreTypes); @@ -70,10 +145,17 @@ class _TypeRecipeVisitor extends DartTypeVisitor { /// Generally this should be called before visiting a type, but the visitor /// does not modify the state so if many types need to be evaluated in the /// same state it can be set once before visiting all of them. - void setState({DDCTypeEnvironment? environment}) { + void setState({DDCTypeEnvironment? environment, bool? recordInterfaceTypes}) { if (environment != null) _typeEnvironment = environment; + if (recordInterfaceTypes != null) { + _recordInterfaceTypes = recordInterfaceTypes; + } } + /// The [InterfaceType]s that have been visited. + Iterable get visitedInterfaceTypes => + Set.unmodifiable(_visitedInterfaceTypes); + @override String defaultDartType(DartType node) { throw UnimplementedError('Unknown DartType: $node'); @@ -87,6 +169,7 @@ class _TypeRecipeVisitor extends DartTypeVisitor { @override String visitInterfaceType(InterfaceType node) { + if (_recordInterfaceTypes) _visitedInterfaceTypes.add(node); // Generate the interface type recipe. var recipeBuffer = StringBuffer(interfaceTypeRecipe(node.classNode)); // Generate the recipes for all type arguments. diff --git a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/classes.dart b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/classes.dart index 7eddb65e480..9b19cc06157 100644 --- a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/classes.dart +++ b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/classes.dart @@ -654,23 +654,27 @@ Object typeTagSymbol(String recipe) { return tagSymbol; } -/// Attaches the type [recipe] and the type tag for the class to to [classRef]. +/// Attaches the class type recipe and the type tags for all implemented +/// [interfaceRecipes] to [classRef]. /// /// The tags are used for simple identification of instances in the dart:rti /// library. /// -/// The first element of interface [interfaceRecipes] must always be the recipe -/// for the type represented by [classRef]. -void addRtiResources(Object classRef, String recipe) { - // Attach the classes own interface type recipe. +/// The first element of [interfaceRecipes] must always be the type recipe for +/// the type represented by [classRef]. +void addRtiResources(Object classRef, JSArray interfaceRecipes) { + // Attach the [classRef]'s own interface type recipe. // The recipe is used in dart:_rti to create an [rti.Rti] instance when // needed. - JS('', r'#.# = #', classRef, rti.interfaceTypeRecipePropertyName, recipe); + JS('', r'#.# = #[0]', classRef, rti.interfaceTypeRecipePropertyName, + interfaceRecipes); // Add specialized test resources used for fast interface type checks in // dart:_rti. var prototype = JS('!', '#.prototype', classRef); - var tagSymbol = typeTagSymbol(recipe); - JS('', '#.# = #', prototype, tagSymbol, true); + for (var recipe in interfaceRecipes) { + var tagSymbol = typeTagSymbol(recipe); + JS('', '#.# = #', prototype, tagSymbol, true); + } } /// Pre-initializes types with empty type caches.