[ddc] Updating representation of enums to support hot reload semantics.
Enums are now split between pre and post canonicalization members. A
separate operation during link-time is emitted for every const
enum field. These are required during link time since enhanced enums can have non-trivial type hierarchies.
Example for snippet:
```
enum E {
e1(1), ...
const E(this.i);
final int i;
}
```
Used to emit enum fields as:
```
dart.defineLazy(CT, {
get C1() {
return C[1] = dart.const(Object.setPrototypeOf({
[_Enum__name]: "e1",
[_Enum_index]: 0
i: 1
}, E.prototype));
},
}
```
Now emits them as:
```
// Declaration-time
dart.defineLazy(CT, {
get C1() {
return C[1] = dart.const(Object.setPrototypeOf({
[_Enum__name]: "e1",
}, E.prototype));
},
}
// Link-time
dart.extendEnum(dart.const(Object.setPrototypeOf({
[_Enum__name]: "e1"
}, E.prototype)), {
i: 1,
get index() {
return E.values.indexOf(this);
}
});
```
Change-Id: Id5ce2ec117e59d8daa28df7fe6051e4a7e1a5bc1
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/404723
Commit-Queue: Mark Zhou <markzipan@google.com>
Reviewed-by: Nicholas Shahan <nshahan@google.com>
diff --git a/pkg/dev_compiler/lib/src/kernel/compiler_new.dart b/pkg/dev_compiler/lib/src/kernel/compiler_new.dart
index 24018c8..9002e0a 100644
--- a/pkg/dev_compiler/lib/src/kernel/compiler_new.dart
+++ b/pkg/dev_compiler/lib/src/kernel/compiler_new.dart
@@ -348,6 +348,9 @@
/// Library link method statements that create type rules.
final List<js_ast.Statement> _typeRuleLinks = [];
+ /// Holds additional initialization logic for enum fields.
+ final List<js_ast.Statement> _enumExtensions = [];
+
/// Whether the current function needs to insert parameter checks.
///
/// Used to avoid adding checks for formal parameters inside a synthetic
@@ -1011,6 +1014,8 @@
..._defineExtensionMemberLinks,
..._nativeExtensionLinks,
..._typeRuleLinks,
+ // Enum extensions must be emitted after type hierachies have stabilized.
+ ..._enumExtensions
]);
var function =
js_ast.NamedFunction(functionName, js_ast.Fun(parameters, body));
@@ -7942,6 +7947,72 @@
]);
}
+ js_ast.Expression visitEnum(InstanceConstant node) {
+ // Non-nullable is forced here because the type of an instance constant
+ // should never appear as legacy "*" at runtime but the library where the
+ // constant is defined can cause those types to appear here.
+ var type = node
+ .getType(_staticTypeContext)
+ .withDeclaredNullability(Nullability.nonNullable);
+ var classRef = _emitClassRef(type as InterfaceType);
+ var prototype = js.call('#.prototype', [classRef]);
+ var enumAccessor = _emitTopLevelName(node.classNode);
+
+ // Enums are canonicalized based on their 'name' member alone. We
+ // append other members (such as 'index' and those introduced via enhanced
+ // enums) after canonicalization so they can be updated across hot reloads.
+ var constantProperties = <js_ast.Property>[];
+ var additionalProperties = <js_ast.Property>[];
+ if (type.typeArguments.isNotEmpty) {
+ // Generic interface type instances require a type information tag.
+ var property = js_ast.Property(
+ _propertyName(js_ast.FixedNames.rtiName), _emitType(type));
+ constantProperties.add(property);
+ }
+ node.fieldValues.forEach((k, v) {
+ var constant = visitConstant(v);
+ var member = k.asField;
+ var memberClass = member.enclosingClass!;
+ if (!memberClass.isEnum) {
+ if (member.name.text == 'index') {
+ // We transform the 'index' field of Enum fields into a special
+ // getter so that their indices are consistent across hot reloads.
+ var value = js.call('#.values.indexOf(this)', enumAccessor);
+ var jsMember = _getSymbol(_emitClassPrivateNameSymbol(
+ memberClass.enclosingLibrary,
+ getLocalClassName(memberClass),
+ member));
+ additionalProperties.add(js_ast.Method(
+ jsMember, js.fun('function() { return #; }', [value]),
+ isGetter: true));
+ } else {
+ var jsMember = _getSymbol(_emitClassPrivateNameSymbol(
+ memberClass.enclosingLibrary,
+ getLocalClassName(memberClass),
+ member));
+ constantProperties.add(js_ast.Property(jsMember, constant));
+ }
+ } else {
+ additionalProperties.add(js_ast.Property(
+ _emitMemberName(member.name.text, member: member), constant));
+ }
+ });
+
+ var canonicalizedEnum = _canonicalizeConstObject(
+ _emitJSObjectSetPrototypeOf(
+ js_ast.ObjectInitializer(constantProperties, multiline: true),
+ prototype,
+ fullyQualifiedName: false));
+
+ var enumExtension = _runtimeStatement('extendEnum(#, #)', [
+ canonicalizedEnum,
+ js_ast.ObjectInitializer(additionalProperties, multiline: true)
+ ]);
+ _enumExtensions.add(enumExtension);
+
+ return canonicalizedEnum;
+ }
+
@override
js_ast.Expression visitInstanceConstant(InstanceConstant node) {
var savedTypeEnvironment = _currentTypeEnvironment;
@@ -7950,17 +8021,18 @@
ClassTypeEnvironment(node.classNode.typeParameters);
}
+ if (node.classNode.isEnum) {
+ var constant = visitEnum(node);
+ _currentTypeEnvironment = savedTypeEnvironment;
+ return constant;
+ }
+
js_ast.Property entryToProperty(MapEntry<Reference, Constant> entry) {
var constant = visitConstant(entry.value);
var member = entry.key.asField;
var cls = member.enclosingClass!;
- // Enums cannot be overridden, so we can safely use the field name
- // directly. Otherwise, use a private symbol in case the field
- // was overridden.
- var symbol = cls.isEnum
- ? _emitMemberName(member.name.text, member: member)
- : _getSymbol(_emitClassPrivateNameSymbol(
- cls.enclosingLibrary, getLocalClassName(cls), member));
+ var symbol = _getSymbol(_emitClassPrivateNameSymbol(
+ cls.enclosingLibrary, getLocalClassName(cls), member));
return js_ast.Property(symbol, constant);
}
diff --git a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart
index cf55281..bd7c9a7 100644
--- a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart
+++ b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart
@@ -1375,7 +1375,8 @@
property,
);
-/// Attempts to assign class [classDeclaration] as [classIdentifier] on [library].
+/// Attempts to assign class [classDeclaration] as [classIdentifier] on
+/// [library].
///
/// During a hot reload, should [library.classIdentifier] already exist, this
/// copies the members of [classDeclaration] and its prototype's properties to
@@ -1428,3 +1429,13 @@
copyProperties(topLevelContainer, propertiesObject, copyWhen: copyWhen);
return topLevelContainer;
}
+
+/// Appends const members in [additionalFieldsObject] to [canonicalizedEnum].
+///
+/// [additionalFieldsObject] is a JS object containing fields that should not
+/// be considered for enum identity/equality but may be updated after a hot
+/// reload.
+extendEnum(canonicalizedEnum, additionalFieldsObject) {
+ copyProperties(canonicalizedEnum, additionalFieldsObject);
+ return canonicalizedEnum;
+}