[ddc] Adding support for static setters of const fields
Fixes #48717
Change-Id: I45145bc992bb129d54962b1ed9cfebbdbac41f92
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/239730
Reviewed-by: Nicholas Shahan <nshahan@google.com>
Commit-Queue: Mark Zhou <markzipan@google.com>
diff --git a/pkg/dev_compiler/lib/src/kernel/compiler.dart b/pkg/dev_compiler/lib/src/kernel/compiler.dart
index 0f4ece3..7a7f204 100644
--- a/pkg/dev_compiler/lib/src/kernel/compiler.dart
+++ b/pkg/dev_compiler/lib/src/kernel/compiler.dart
@@ -748,7 +748,7 @@
}
body = [classDef];
- _emitStaticFields(c, body);
+ _emitStaticFieldsAndAccessors(c, body);
if (finishGenericTypeTest != null) body.add(finishGenericTypeTest);
for (var peer in jsPeerNames) {
_registerExtensionType(c, peer, body);
@@ -1323,12 +1323,16 @@
/// Emits static fields for a class, and initialize them eagerly if possible,
/// otherwise define them as lazy properties.
- void _emitStaticFields(Class c, List<js_ast.Statement> body) {
+ void _emitStaticFieldsAndAccessors(Class c, List<js_ast.Statement> body) {
var fields = c.fields
.where((f) => f.isStatic && !isRedirectingFactoryField(f))
.toList();
+ var fieldNames = Set.from(fields.map((f) => f.name));
+ var staticSetters = c.procedures.where(
+ (p) => p.isStatic && p.isAccessor && fieldNames.contains(p.name));
+ var members = [...fields, ...staticSetters];
if (fields.isNotEmpty) {
- body.add(_emitLazyFields(_emitTopLevelName(c), fields,
+ body.add(_emitLazyMembers(_emitTopLevelName(c), members,
(n) => _emitStaticMemberName(n.name.text)));
}
}
@@ -1847,10 +1851,13 @@
}
Set<Member> redirectingFactories;
+ var staticFieldNames = <Name>{};
for (var m in c.fields) {
if (m.isStatic) {
if (isRedirectingFactoryField(m)) {
redirectingFactories = getRedirectingFactories(m).toSet();
+ } else {
+ staticFieldNames.add(m.name);
}
} else if (_extensionTypes.isNativeClass(c)) {
jsMethods.addAll(_emitNativeFieldAccessors(m));
@@ -1872,6 +1879,11 @@
var savedUri = _currentUri;
for (var m in c.procedures) {
+ // Static accessors on static/lazy fields are emitted earlier in
+ // `_emitStaticFieldsAndAccessors`.
+ if (m.isStatic && m.isAccessor && staticFieldNames.contains(m.name)) {
+ continue;
+ }
_staticTypeContext.enterMember(m);
// For the Dart SDK, we use the member URI because it may be different
// from the class (because of patch files). User code does not need this.
@@ -2310,36 +2322,51 @@
}
if (fields.isEmpty) return;
- moduleItems.add(_emitLazyFields(
+ moduleItems.add(_emitLazyMembers(
emitLibraryName(_currentLibrary), fields, _emitTopLevelMemberName));
}
- js_ast.Statement _emitLazyFields(
- js_ast.Expression objExpr,
- Iterable<Field> fields,
- js_ast.LiteralString Function(Field f) emitFieldName) {
+ js_ast.Statement _emitLazyMembers(
+ js_ast.Expression objExpr,
+ Iterable<Member> members,
+ js_ast.LiteralString Function(Member) emitMemberName,
+ ) {
var accessors = <js_ast.Method>[];
var savedUri = _currentUri;
- for (var field in fields) {
- _currentUri = field.fileUri;
- _staticTypeContext.enterMember(field);
- var access = emitFieldName(field);
- memberNames[field] = access.valueWithoutQuotes;
- accessors.add(js_ast.Method(access, _emitStaticFieldInitializer(field),
- isGetter: true)
- ..sourceInformation = _hoverComment(
- js_ast.PropertyAccess(objExpr, access),
- field.fileOffset,
- field.name.text.length));
+ for (var member in members) {
+ _currentUri = member.fileUri;
+ _staticTypeContext.enterMember(member);
+ var access = emitMemberName(member);
+ memberNames[member] = access.valueWithoutQuotes;
- // TODO(jmesserly): currently uses a dummy setter to indicate writable.
- if (!field.isFinal && !field.isConst) {
+ if (member is Field) {
+ accessors.add(js_ast.Method(access, _emitStaticFieldInitializer(member),
+ isGetter: true)
+ ..sourceInformation = _hoverComment(
+ js_ast.PropertyAccess(objExpr, access),
+ member.fileOffset,
+ member.name.text.length));
+ if (!member.isFinal && !member.isConst) {
+ // TODO(jmesserly): currently uses a dummy setter to indicate
+ // writable.
+ accessors.add(js_ast.Method(
+ access, js.call('function(_) {}') as js_ast.Fun,
+ isSetter: true));
+ }
+ } else if (member is Procedure) {
accessors.add(js_ast.Method(
- access, js.call('function(_) {}') as js_ast.Fun,
- isSetter: true));
+ access, _emitFunction(member.function, member.name.text),
+ isGetter: member.isGetter, isSetter: member.isSetter)
+ ..sourceInformation = _hoverComment(
+ js_ast.PropertyAccess(objExpr, access),
+ member.fileOffset,
+ member.name.text.length));
+ } else {
+ throw UnsupportedError(
+ 'Unsupported lazy member type ${member.runtimeType}: $member');
}
- _staticTypeContext.leaveMember(field);
+ _staticTypeContext.leaveMember(member);
}
_currentUri = savedUri;
@@ -4777,9 +4804,9 @@
@override
js_ast.Expression visitStaticSet(StaticSet node) {
var target = node.target;
+ var result = _emitStaticTarget(target);
var value = isJsMember(target) ? _assertInterop(node.value) : node.value;
- return _visitExpression(value)
- .toAssignExpression(_emitStaticTarget(target));
+ return _visitExpression(value).toAssignExpression(result);
}
@override
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 9c9e4b9..6a2f7b6 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
@@ -858,7 +858,8 @@
return value;
};
$desc.configurable = true;
- if ($desc.set != null) {
+ let setter = $desc.set;
+ if (setter != null) {
$desc.set = function(x) {
if (!savedLocals) {
$resetFields.push(() => {
@@ -871,6 +872,7 @@
}
init = null;
value = x;
+ setter(x);
};
}
return ${defineProperty(to, name, desc)};
@@ -914,7 +916,8 @@
}
};
$desc.configurable = true;
- if ($desc.set != null) {
+ let setter = $desc.set;
+ if (setter != null) {
$desc.set = function(x) {
if (!savedLocals) {
$resetFields.push(() => {
@@ -926,6 +929,7 @@
}
init = null;
value = x;
+ setter(x);
};
}
return ${defineProperty(to, name, desc)};
diff --git a/tests/dartdevc/hot_restart_static_test.dart b/tests/dartdevc/hot_restart_static_test.dart
index c26c298..5b62495 100644
--- a/tests/dartdevc/hot_restart_static_test.dart
+++ b/tests/dartdevc/hot_restart_static_test.dart
@@ -23,7 +23,20 @@
static int withInitializer = 3;
}
-main() {
+class StaticsSetter {
+ static int counter = 0;
+ static const field = 5;
+ static const field2 = null;
+ static set field(int value) => StaticsSetter.counter += 1;
+ static set field2(value) => 42;
+}
+
+void main() {
+ testStaticFields();
+ testStaticSetterOfConstField();
+}
+
+void testStaticFields() {
var resetFieldCount = dart.resetFields.length;
// Set static fields without explicit initializers. Avoid calling getters for
@@ -82,3 +95,49 @@
expectedResets = resetFieldCount + 6;
Expect.equals(expectedResets, dart.resetFields.length);
}
+
+void testStaticSetterOfConstField() {
+ var resetFieldCount = dart.resetFields.length;
+
+ // Static setters of const fields should behave according to spec.
+ Expect.equals(StaticsSetter.counter, 0);
+ Expect.equals(StaticsSetter.field, 5);
+ StaticsSetter.field = 100;
+ StaticsSetter.field = 100;
+ StaticsSetter.field = 100;
+ Expect.equals(StaticsSetter.field, 5);
+ Expect.equals(StaticsSetter.counter, 3);
+
+ // 2 new field resets: [StaticsSetter.counter] and [StaticsSetter.field].
+ var expectedResets = resetFieldCount + 2;
+ Expect.equals(expectedResets, dart.resetFields.length);
+
+ dart.hotRestart();
+ resetFieldCount = dart.resetFields.length;
+
+ // Static setters of const fields should be properly reset.
+ Expect.equals(StaticsSetter.counter, 0);
+ Expect.equals(StaticsSetter.field, 5);
+ StaticsSetter.field = 100;
+ StaticsSetter.field = 100;
+ StaticsSetter.field = 100;
+ Expect.equals(StaticsSetter.field, 5);
+ Expect.equals(StaticsSetter.counter, 3);
+
+ // 2 new field resets: [StaticsSetter.counter] and [StaticsSetter.field].
+ expectedResets = resetFieldCount + 2;
+ Expect.equals(expectedResets, dart.resetFields.length);
+
+ dart.hotRestart();
+ dart.hotRestart();
+ resetFieldCount = dart.resetFields.length;
+
+ // Invoke the static setter but not the getter.
+ StaticsSetter.field2 = 100;
+ StaticsSetter.field2 = 100;
+ StaticsSetter.field2 = 100;
+
+ // 1 new field reset: [StaticsSetter.field2].
+ expectedResets = resetFieldCount + 1;
+ Expect.equals(expectedResets, dart.resetFields.length);
+}
diff --git a/tests/language/static/static_setter_on_const_field_test.dart b/tests/language/static/static_setter_on_const_field_test.dart
new file mode 100644
index 0000000..48b1700
--- /dev/null
+++ b/tests/language/static/static_setter_on_const_field_test.dart
@@ -0,0 +1,29 @@
+// Copyright (c) 2022, 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.
+
+import "package:expect/expect.dart";
+
+// Tests that static setters on const fields are properly invoked on set.
+
+class A {
+ static const int field = 0;
+ static void set field(int value) {
+ Expect.equals(value, 100);
+ B.count += 1;
+ }
+}
+
+class B {
+ static int count = 0;
+}
+
+main() {
+ Expect.equals(B.count, 0);
+ A.field = 100;
+ Expect.equals(B.count, 1);
+ Expect.equals(A.field, 0);
+ A.field = 100;
+ Expect.equals(B.count, 2);
+ Expect.equals(A.field, 0);
+}
diff --git a/tests/language_2/static/static_setter_on_const_field_test.dart b/tests/language_2/static/static_setter_on_const_field_test.dart
new file mode 100644
index 0000000..354e724
--- /dev/null
+++ b/tests/language_2/static/static_setter_on_const_field_test.dart
@@ -0,0 +1,31 @@
+// Copyright (c) 2022, 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.
+
+// @dart = 2.9
+
+import "package:expect/expect.dart";
+
+// Tests that static setters of const fields are properly invoked on set.
+
+class A {
+ static const int field = 0;
+ static void set field(int value) {
+ Expect.equals(value, 100);
+ B.count += 1;
+ }
+}
+
+class B {
+ static int count = 0;
+}
+
+main() {
+ Expect.equals(B.count, 0);
+ A.field = 100;
+ Expect.equals(B.count, 1);
+ Expect.equals(A.field, 0);
+ A.field = 100;
+ Expect.equals(B.count, 2);
+ Expect.equals(A.field, 0);
+}