Skip to content

Commit 6edbfd9

Browse files
MarkzipanCommit Queue
authored andcommitted
[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 <[email protected]> Reviewed-by: Nicholas Shahan <[email protected]>
1 parent 048b4ac commit 6edbfd9

File tree

2 files changed

+91
-8
lines changed

2 files changed

+91
-8
lines changed

pkg/dev_compiler/lib/src/kernel/compiler_new.dart

Lines changed: 79 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,9 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
348348
/// Library link method statements that create type rules.
349349
final List<js_ast.Statement> _typeRuleLinks = [];
350350

351+
/// Holds additional initialization logic for enum fields.
352+
final List<js_ast.Statement> _enumExtensions = [];
353+
351354
/// Whether the current function needs to insert parameter checks.
352355
///
353356
/// Used to avoid adding checks for formal parameters inside a synthetic
@@ -1011,6 +1014,8 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
10111014
..._defineExtensionMemberLinks,
10121015
..._nativeExtensionLinks,
10131016
..._typeRuleLinks,
1017+
// Enum extensions must be emitted after type hierachies have stabilized.
1018+
..._enumExtensions
10141019
]);
10151020
var function =
10161021
js_ast.NamedFunction(functionName, js_ast.Fun(parameters, body));
@@ -7942,6 +7947,72 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
79427947
]);
79437948
}
79447949

7950+
js_ast.Expression visitEnum(InstanceConstant node) {
7951+
// Non-nullable is forced here because the type of an instance constant
7952+
// should never appear as legacy "*" at runtime but the library where the
7953+
// constant is defined can cause those types to appear here.
7954+
var type = node
7955+
.getType(_staticTypeContext)
7956+
.withDeclaredNullability(Nullability.nonNullable);
7957+
var classRef = _emitClassRef(type as InterfaceType);
7958+
var prototype = js.call('#.prototype', [classRef]);
7959+
var enumAccessor = _emitTopLevelName(node.classNode);
7960+
7961+
// Enums are canonicalized based on their 'name' member alone. We
7962+
// append other members (such as 'index' and those introduced via enhanced
7963+
// enums) after canonicalization so they can be updated across hot reloads.
7964+
var constantProperties = <js_ast.Property>[];
7965+
var additionalProperties = <js_ast.Property>[];
7966+
if (type.typeArguments.isNotEmpty) {
7967+
// Generic interface type instances require a type information tag.
7968+
var property = js_ast.Property(
7969+
_propertyName(js_ast.FixedNames.rtiName), _emitType(type));
7970+
constantProperties.add(property);
7971+
}
7972+
node.fieldValues.forEach((k, v) {
7973+
var constant = visitConstant(v);
7974+
var member = k.asField;
7975+
var memberClass = member.enclosingClass!;
7976+
if (!memberClass.isEnum) {
7977+
if (member.name.text == 'index') {
7978+
// We transform the 'index' field of Enum fields into a special
7979+
// getter so that their indices are consistent across hot reloads.
7980+
var value = js.call('#.values.indexOf(this)', enumAccessor);
7981+
var jsMember = _getSymbol(_emitClassPrivateNameSymbol(
7982+
memberClass.enclosingLibrary,
7983+
getLocalClassName(memberClass),
7984+
member));
7985+
additionalProperties.add(js_ast.Method(
7986+
jsMember, js.fun('function() { return #; }', [value]),
7987+
isGetter: true));
7988+
} else {
7989+
var jsMember = _getSymbol(_emitClassPrivateNameSymbol(
7990+
memberClass.enclosingLibrary,
7991+
getLocalClassName(memberClass),
7992+
member));
7993+
constantProperties.add(js_ast.Property(jsMember, constant));
7994+
}
7995+
} else {
7996+
additionalProperties.add(js_ast.Property(
7997+
_emitMemberName(member.name.text, member: member), constant));
7998+
}
7999+
});
8000+
8001+
var canonicalizedEnum = _canonicalizeConstObject(
8002+
_emitJSObjectSetPrototypeOf(
8003+
js_ast.ObjectInitializer(constantProperties, multiline: true),
8004+
prototype,
8005+
fullyQualifiedName: false));
8006+
8007+
var enumExtension = _runtimeStatement('extendEnum(#, #)', [
8008+
canonicalizedEnum,
8009+
js_ast.ObjectInitializer(additionalProperties, multiline: true)
8010+
]);
8011+
_enumExtensions.add(enumExtension);
8012+
8013+
return canonicalizedEnum;
8014+
}
8015+
79458016
@override
79468017
js_ast.Expression visitInstanceConstant(InstanceConstant node) {
79478018
var savedTypeEnvironment = _currentTypeEnvironment;
@@ -7950,17 +8021,18 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
79508021
ClassTypeEnvironment(node.classNode.typeParameters);
79518022
}
79528023

8024+
if (node.classNode.isEnum) {
8025+
var constant = visitEnum(node);
8026+
_currentTypeEnvironment = savedTypeEnvironment;
8027+
return constant;
8028+
}
8029+
79538030
js_ast.Property entryToProperty(MapEntry<Reference, Constant> entry) {
79548031
var constant = visitConstant(entry.value);
79558032
var member = entry.key.asField;
79568033
var cls = member.enclosingClass!;
7957-
// Enums cannot be overridden, so we can safely use the field name
7958-
// directly. Otherwise, use a private symbol in case the field
7959-
// was overridden.
7960-
var symbol = cls.isEnum
7961-
? _emitMemberName(member.name.text, member: member)
7962-
: _getSymbol(_emitClassPrivateNameSymbol(
7963-
cls.enclosingLibrary, getLocalClassName(cls), member));
8034+
var symbol = _getSymbol(_emitClassPrivateNameSymbol(
8035+
cls.enclosingLibrary, getLocalClassName(cls), member));
79648036
return js_ast.Property(symbol, constant);
79658037
}
79668038

sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1375,7 +1375,8 @@ bool isStateBearingSymbol(property) => JS<bool>(
13751375
property,
13761376
);
13771377

1378-
/// Attempts to assign class [classDeclaration] as [classIdentifier] on [library].
1378+
/// Attempts to assign class [classDeclaration] as [classIdentifier] on
1379+
/// [library].
13791380
///
13801381
/// During a hot reload, should [library.classIdentifier] already exist, this
13811382
/// copies the members of [classDeclaration] and its prototype's properties to
@@ -1428,3 +1429,13 @@ declareTopLevelProperties(topLevelContainer, propertiesObject) {
14281429
copyProperties(topLevelContainer, propertiesObject, copyWhen: copyWhen);
14291430
return topLevelContainer;
14301431
}
1432+
1433+
/// Appends const members in [additionalFieldsObject] to [canonicalizedEnum].
1434+
///
1435+
/// [additionalFieldsObject] is a JS object containing fields that should not
1436+
/// be considered for enum identity/equality but may be updated after a hot
1437+
/// reload.
1438+
extendEnum(canonicalizedEnum, additionalFieldsObject) {
1439+
copyProperties(canonicalizedEnum, additionalFieldsObject);
1440+
return canonicalizedEnum;
1441+
}

0 commit comments

Comments
 (0)