Skip to content

Commit afa127b

Browse files
simolus3Commit Queue
authored andcommitted
Support renaming fields for JS object literals
This respects the `@JS()` annotation on parameters of external factories in JS extension types to customize the keys of created object literals. Closes #55138 Change-Id: Idebeee922d2080356fd7449f639ecd2ae9ee5fc0 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/445840 Reviewed-by: Nicholas Shahan <[email protected]> Reviewed-by: Nate Biggs <[email protected]> Reviewed-by: Srujan Gaddam <[email protected]> Reviewed-by: Stephen Adams <[email protected]> Commit-Queue: Nate Biggs <[email protected]> Commit-Queue: Srujan Gaddam <[email protected]>
1 parent 83ecfc3 commit afa127b

File tree

7 files changed

+96
-17
lines changed

7 files changed

+96
-17
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,12 @@ To learn more about the feature, check out the
7777
for more details.
7878
- `isA<JSBoxedDartObject>` now checks that the value was the result of a
7979
`toJSBox` operation instead of returning true for all objects.
80+
- For object literals created from extension type factories, the `@JS()`
81+
annotation can now be used to change the name of keys in JavaScript. See
82+
[#55138][] for more details.
8083

8184
[#59830]: https://github.com/dart-lang/sdk/issues/59830
85+
[#55138]: https://github.com/dart-lang/sdk/issues/55138
8286

8387
## 3.9.0
8488

pkg/_js_interop_checks/lib/src/js_interop.dart

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,19 @@ bool hasPatchAnnotation(Annotatable a) => a.annotations.any(_isPatchAnnotation);
5353
/// If [a] has a `@JS('...')` annotation, returns the value inside the
5454
/// parentheses.
5555
///
56+
/// This function only considers `JS` annotations defined in [interopLibraries]
57+
/// or, when that argument is not set, from both `package:js` and
58+
/// `dart:js_interop`.
59+
///
5660
/// If there is none or the class does not have a `@JS()` annotation, returns
5761
/// an empty String.
58-
String getJSName(Annotatable a) {
62+
String getJSName(Annotatable a, {Set<Uri>? interopLibraries}) {
5963
String jsClass = '';
6064
for (var annotation in a.annotations) {
61-
if (_isJSInteropAnnotation(annotation)) {
65+
if (_isJSInteropAnnotation(
66+
annotation,
67+
interopLibraries: interopLibraries,
68+
)) {
6269
var jsClasses = stringAnnotationValues(annotation);
6370
if (jsClasses.isNotEmpty) {
6471
jsClass = jsClasses[0];
@@ -68,6 +75,14 @@ String getJSName(Annotatable a) {
6875
return jsClass;
6976
}
7077

78+
/// If [a] has a `JS('...')` annotation from `dart:js_interop`, returns the
79+
/// value inside the parentheses.
80+
///
81+
/// If no such annotation exists, returns an empty string.
82+
String getDartJSInteropJSName(Annotatable a) {
83+
return getJSName(a, interopLibraries: {_jsInterop});
84+
}
85+
7186
/// If [a] has a `@Native('...')` annotation, returns the values inside the
7287
/// parentheses.
7388
///
@@ -130,8 +145,8 @@ bool _isInteropAnnotation(
130145
return interopLibraries.contains(importUri);
131146
}
132147

133-
bool _isJSInteropAnnotation(Expression value) =>
134-
_isInteropAnnotation(value, 'JS');
148+
bool _isJSInteropAnnotation(Expression value, {Set<Uri>? interopLibraries}) =>
149+
_isInteropAnnotation(value, 'JS', interopLibraries: interopLibraries);
135150

136151
bool _isPackageJSAnnotation(Expression value) => _isInteropAnnotation(
137152
value,

pkg/compiler/lib/src/ssa/builder.dart

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5+
// ignore: implementation_imports
6+
import 'package:_js_interop_checks/src/js_interop.dart'
7+
show getDartJSInteropJSName;
58
// ignore: implementation_imports
69
import 'package:front_end/src/api_prototype/static_weak_references.dart'
710
as ir
@@ -7016,7 +7019,11 @@ class KernelSsaGraphBuilder extends ir.VisitorDefault<void>
70167019
final argument = arguments[i];
70177020
if (argument != null) {
70187021
filteredArguments.add(argument);
7019-
var jsName = _nativeData.computeUnescapedJSInteropName(parameterName);
7022+
var customName = getDartJSInteropJSName(variable);
7023+
7024+
var jsName = (customName.isNotEmpty && isObjectLiteralConstructor)
7025+
? customName
7026+
: _nativeData.computeUnescapedJSInteropName(parameterName);
70207027
parameterNameMap[jsName] = js.InterpolatedExpression(positions++);
70217028
}
70227029
i++;

pkg/dart2wasm/lib/js/interop_specializer.dart

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@
33
// BSD-style license that can be found in the LICENSE file.
44

55
import 'package:_js_interop_checks/src/js_interop.dart'
6-
show getJSName, hasAnonymousAnnotation, hasJSInteropAnnotation;
6+
show
7+
getDartJSInteropJSName,
8+
getJSName,
9+
hasAnonymousAnnotation,
10+
hasJSInteropAnnotation;
711
import 'package:_js_interop_checks/src/transformations/js_util_optimizer.dart'
812
show ExtensionIndex;
913
import 'package:kernel/ast.dart';
@@ -343,9 +347,22 @@ class _ObjectLiteralSpecializer extends _InvocationSpecializer {
343347
.toList();
344348
}
345349

350+
/// The name to use in JavaScript for the Dart parameter [variable].
351+
///
352+
/// This defaults to the name of the [variable], but can be changed with a
353+
/// `@JS()` annotation.
354+
String _jsKey(VariableDeclaration variable) {
355+
// Only support `@JS` renaming on extension type object literal
356+
// constructors.
357+
final changedName = interopMethod.isExtensionTypeMember
358+
? getDartJSInteropJSName(variable)
359+
: '';
360+
return changedName.isEmpty ? variable.name! : changedName;
361+
}
362+
346363
@override
347364
String bodyString(String object, List<String> callArguments) {
348-
final keys = parameters.map((named) => named.name!).toList();
365+
final keys = parameters.map(_jsKey).toList();
349366
final keyValuePairs = <String>[];
350367
for (int i = 0; i < callArguments.length; i++) {
351368
keyValuePairs.add('${keys[i]}: ${callArguments[i]}');
@@ -362,8 +379,7 @@ class _ObjectLiteralSpecializer extends _InvocationSpecializer {
362379
// `Cons(a: 0)`, and `Cons(a: 1, b: 1)` only create two shapes:
363380
// `{a: value, b: value}` and `{a: value}`. Therefore, we only need two
364381
// methods to handle the `Cons` invocations.
365-
final shape =
366-
parameters.map((VariableDeclaration decl) => decl.name).join('|');
382+
final shape = parameters.map(_jsKey).join('|');
367383
final interopProcedure = _jsObjectLiteralMethods
368384
.putIfAbsent(interopMethod, () => {})
369385
.putIfAbsent(shape, () => _getRawInteropProcedure());

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

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import 'dart:io' as io;
88
import 'dart:math' show max, min;
99

1010
import 'package:_js_interop_checks/src/js_interop.dart'
11-
show hasDartJSInteropAnnotation;
11+
show getDartJSInteropJSName, hasDartJSInteropAnnotation;
1212
import 'package:_js_interop_checks/src/transformations/js_util_optimizer.dart'
1313
show ExtensionIndex;
1414
import 'package:front_end/src/api_unstable/ddc.dart';
@@ -6937,9 +6937,20 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
69376937
// JS interop checks assert that only external extension type constructors
69386938
// and factories have named parameters.
69396939
assert(target.function.positionalParameters.isEmpty);
6940-
var namedProperties = {
6941-
for (final named in node.arguments.named) named.name: named.value,
6942-
};
6940+
var namedProperties = <String, Expression>{};
6941+
for (var named in node.arguments.named) {
6942+
var name = named.name;
6943+
for (var parameter in target.function.namedParameters) {
6944+
if (parameter.name == named.name) {
6945+
var customName = getDartJSInteropJSName(parameter);
6946+
if (customName.isNotEmpty) {
6947+
name = customName;
6948+
}
6949+
break;
6950+
}
6951+
}
6952+
namedProperties[name] = named.value;
6953+
}
69436954
return _emitObjectLiteral(namedProperties, isDartJsInterop: true);
69446955
}
69456956
if (target == _coreTypes.identicalProcedure) {

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

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import 'dart:io' as io;
88
import 'dart:math' show max, min;
99

1010
import 'package:_js_interop_checks/src/js_interop.dart'
11-
show hasDartJSInteropAnnotation;
11+
show getDartJSInteropJSName, hasDartJSInteropAnnotation;
1212
import 'package:_js_interop_checks/src/transformations/js_util_optimizer.dart'
1313
show ExtensionIndex;
1414
import 'package:front_end/src/api_unstable/ddc.dart';
@@ -7620,9 +7620,21 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
76207620
// JS interop checks assert that only external extension type constructors
76217621
// and factories have named parameters.
76227622
assert(target.function.positionalParameters.isEmpty);
7623-
var namedProperties = {
7624-
for (final named in node.arguments.named) named.name: named.value,
7625-
};
7623+
var namedProperties = <String, Expression>{};
7624+
for (var named in node.arguments.named) {
7625+
var name = named.name;
7626+
for (var parameter in target.function.namedParameters) {
7627+
if (parameter.name == named.name) {
7628+
var customName = getDartJSInteropJSName(parameter);
7629+
if (customName.isNotEmpty) {
7630+
name = customName;
7631+
}
7632+
break;
7633+
}
7634+
}
7635+
namedProperties[name] = named.value;
7636+
}
7637+
76267638
return _emitObjectLiteral(namedProperties, isDartJsInterop: true);
76277639
}
76287640
if (target == _coreTypes.identicalProcedure) {

tests/lib/js/static_interop_test/extension_type/object_literal_constructor_test.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@ extension type Literal._(JSObject _) implements JSObject {
1313
external factory Literal.fact({double? a, String b, bool? c});
1414
}
1515

16+
extension type NamedKeysLiteral._(JSObject _) implements JSObject {
17+
external NamedKeysLiteral({@JS('a') double? namedA, @JS('b') String namedB});
18+
}
19+
20+
extension type OriginalNamesLiteral._(JSObject _) implements JSObject {
21+
external OriginalNamesLiteral({@JS() double? a, @JS('') String b});
22+
}
23+
1624
// Test that the properties we assumed to exist in `literal` actually exist and
1725
// that their values are as expected. If we assumed they don't exist, check that
1826
// they do not. Note that we don't check the order of the keys in the literal.
@@ -53,4 +61,10 @@ void main() {
5361
testProperties(Literal(a: 0.0, b: '', c: true), a: 0.0, b: '', c: true);
5462
// Test that passing in a different order doesn't change the values.
5563
testProperties(Literal.fact(c: true, a: 0.0, b: ''), a: 0.0, b: '', c: true);
64+
65+
testProperties(NamedKeysLiteral(namedA: 0.0), a: 0.0);
66+
testProperties(NamedKeysLiteral(namedA: 1.0, namedB: 'b'), a: 1.0, b: 'b');
67+
68+
testProperties(OriginalNamesLiteral(a: 0.0), a: 0.0);
69+
testProperties(OriginalNamesLiteral(a: 0.0, b: 'b'), a: 0.0, b: 'b');
5670
}

0 commit comments

Comments
 (0)