mirror of
https://github.com/dart-lang/sdk
synced 2024-10-14 10:33:28 +00:00
[ddc] Support more complicated subtype checks
Generate type hierarchy rules for all interface types used in a module. These rules are utilized by the `isSubtype()` to determine interface subtypes. Issue: https://github.com/dart-lang/sdk/issues/48585 Change-Id: I63f64075d4947f234eca5e730ad15588e60b9e1e Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/266542 Reviewed-by: Anna Gringauze <annagrin@google.com> Reviewed-by: Mark Zhou <markzipan@google.com> Commit-Queue: Nicholas Shahan <nshahan@google.com>
This commit is contained in:
parent
bf548e788a
commit
a464fc354b
|
@ -350,7 +350,7 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
|
|||
_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<js_ast.Expression>
|
|||
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<js_ast.Expression>
|
|||
});
|
||||
|
||||
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<js_ast.Expression>
|
|||
|
||||
var finishGenericTypeTest = _emitClassTypeTests(c, className, body);
|
||||
|
||||
/// Collects all implemented types in the ancestry of [cls].
|
||||
Iterable<Supertype> transitiveImplementedTypes(Class cls) {
|
||||
var allImplementedTypes = <Supertype>{};
|
||||
var toVisit = ListQueue<Supertype>()..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<js_ast.Expression>
|
|||
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<js_ast.Expression>
|
|||
} 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<js_ast.Expression>
|
|||
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<js_ast.Expression>? 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<js_ast.Expression>
|
|||
/// 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) {
|
||||
|
|
|
@ -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<String, Map<String, List<String>>> get visitedInterfaceTypeRules {
|
||||
var rules = <String, Map<String, List<String>>>{};
|
||||
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 = <String, List<String>>{};
|
||||
// Encode type rules for all supers.
|
||||
var toVisit = ListQueue<Supertype>.from(cls.supers);
|
||||
var visited = <Supertype>{};
|
||||
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<String> {
|
|||
/// 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 = <String>[];
|
||||
|
||||
/// 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 = <InterfaceType>{};
|
||||
final CoreTypes _coreTypes;
|
||||
|
||||
_TypeRecipeVisitor(this._typeEnvironment, this._coreTypes);
|
||||
|
@ -70,10 +145,17 @@ class _TypeRecipeVisitor extends DartTypeVisitor<String> {
|
|||
/// 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<InterfaceType> get visitedInterfaceTypes =>
|
||||
Set.unmodifiable(_visitedInterfaceTypes);
|
||||
|
||||
@override
|
||||
String defaultDartType(DartType node) {
|
||||
throw UnimplementedError('Unknown DartType: $node');
|
||||
|
@ -87,6 +169,7 @@ class _TypeRecipeVisitor extends DartTypeVisitor<String> {
|
|||
|
||||
@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.
|
||||
|
|
|
@ -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<String> 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<Object>('!', '#.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.
|
||||
|
|
Loading…
Reference in a new issue