Skip to content

Commit 2ac7957

Browse files
alexmarkovCommit Queue
authored andcommitted
[vm, dynamic_modules] Support extensions and extension types in dynamic interface annotator
TEST=pkg/vm/test/transformations/dynamic_interface_annotator_test.dart Bug: b/404399018 Change-Id: I9b4baafc0c75b62f4fe15cb1191f0e31669a8538 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/416340 Reviewed-by: Nate Biggs <[email protected]> Commit-Queue: Alexander Markov <[email protected]>
1 parent 55d5754 commit 2ac7957

File tree

12 files changed

+234
-0
lines changed

12 files changed

+234
-0
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
# for details. All rights reserved. Use of this source code is governed by a
3+
# BSD-style license that can be found in the LICENSE file.
4+
5+
callable:
6+
- library: 'shared/shared.dart'
7+
# TODO(sigmund): This should be included by default
8+
- library: 'dart:core'
9+
class: 'Object'
10+
- library: 'dart:core'
11+
class: 'int'
12+
- library: 'dart:core'
13+
class: 'String'
14+
- library: 'dart:core'
15+
class: 'pragma'
16+
member: '_'
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import '../../common/testing.dart' as helper;
6+
import 'package:expect/expect.dart';
7+
8+
import 'shared/shared.dart'; // ignore: unused_import
9+
10+
/// A dynamic module is allowed to extend a class in the dynamic interface and
11+
/// override its members.
12+
void main() async {
13+
final result = (await helper.load('entry1.dart')) as String;
14+
Expect.equals('abcdef42', result);
15+
helper.done();
16+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import '../shared/shared.dart';
6+
7+
@pragma('dyn-module:entry-point')
8+
Object? dynamicModuleEntrypoint() =>
9+
'abc'.addSuffix('def') + MyExtensionType.foo(42).info.toString();
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
extension type MyExtensionType.foo(int _info) {
6+
int get info => _info;
7+
}
8+
9+
extension MyExtension on String {
10+
String addSuffix(String suffix) => this + suffix;
11+
}

pkg/vm/lib/transformations/dynamic_interface_annotator.dart

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,16 @@ class _Annotator extends RecursiveVisitor {
152152
c.accept(this);
153153
}
154154
}
155+
for (final ext in node.extensions) {
156+
if (ext.name[0] != '_') {
157+
ext.accept(this);
158+
}
159+
}
160+
for (final extensionType in node.extensionTypeDeclarations) {
161+
if (extensionType.name[0] != '_') {
162+
extensionType.accept(this);
163+
}
164+
}
155165
for (final exportRef in node.additionalExports) {
156166
exportRef.node!.accept(this);
157167
}
@@ -207,6 +217,34 @@ class _Annotator extends RecursiveVisitor {
207217
node.addAnnotation(ConstantExpression(pragma));
208218
}
209219
}
220+
221+
@override
222+
void visitExtension(Extension node) {
223+
for (final md in node.memberDescriptors) {
224+
final member = md.memberReference?.node;
225+
if (member != null) {
226+
annotateMember(member as Member);
227+
}
228+
final tearOff = md.tearOffReference?.node;
229+
if (tearOff != null) {
230+
annotateMember(tearOff as Member);
231+
}
232+
}
233+
}
234+
235+
@override
236+
void visitExtensionTypeDeclaration(ExtensionTypeDeclaration node) {
237+
for (final md in node.memberDescriptors) {
238+
final member = md.memberReference?.node;
239+
if (member != null) {
240+
annotateMember(member as Member);
241+
}
242+
final tearOff = md.tearOffReference?.node;
243+
if (tearOff != null) {
244+
annotateMember(tearOff as Member);
245+
}
246+
}
247+
}
210248
}
211249

212250
class _ImplicitExtendableAnnotator {

pkg/vm/testcases/transformations/dynamic_interface_annotator/dynamic_interface.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,19 @@ callable:
99
member: sfield9
1010
- library: 'lib3.dart'
1111
class: ['A', 'B']
12+
- library: 'lib4.dart'
1213
- library: 'prefix/lib*'
1314

1415
extendable:
1516
- library: 'lib1.dart'
1617
class: ['A', 'O', 'Q', 'S', 'T', 'V']
1718
- library: 'lib2.dart'
19+
- library: 'lib4.dart'
1820

1921
can-be-overridden:
2022
- library: 'lib1.dart'
2123
class: ['A', 'Q', 'U']
2224
- library: 'lib2.dart'
2325
class: 'D'
2426
member: 'build'
27+
- library: 'lib4.dart'
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
export 'lib5.dart';
6+
7+
extension type E3(int _info) {
8+
int get info => _info;
9+
}
10+
11+
extension type _E4(int _info) {
12+
int get info => _info;
13+
}
14+
15+
extension E5 on String {
16+
String addSuffix1(String suffix) => this + suffix;
17+
}
18+
19+
extension _E6 on String {
20+
String addSuffix2(String suffix) => this + suffix;
21+
}
22+
23+
extension on String {
24+
String addSuffix3(String suffix) => this + suffix;
25+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
library;
2+
import self as self;
3+
import "dart:core" as core;
4+
import "lib5.dart" as lib5;
5+
additionalExports = (lib5::E1,
6+
lib5::E2)
7+
8+
export "file:pkg/vm/testcases/transformations/dynamic_interface_annotator/lib5.dart";
9+
10+
extension E5 on core::String {
11+
method addSuffix1 = self::E5|addSuffix1;
12+
method tearoff addSuffix1 = self::E5|get#addSuffix1;
13+
}
14+
extension _E6 on core::String {
15+
method addSuffix2 = self::_E6|addSuffix2;
16+
method tearoff addSuffix2 = self::_E6|get#addSuffix2;
17+
}
18+
extension /* unnamed */ _extension#2 on core::String {
19+
method addSuffix3 = self::_extension#2|addSuffix3;
20+
method tearoff addSuffix3 = self::_extension#2|get#addSuffix3;
21+
}
22+
extension type E3(core::int _info) {
23+
abstract extension-type-member representation-field get _info() → core::int;
24+
get info = self::E3|get#info;
25+
constructor • = self::E3|constructor#;
26+
constructor tearoff • = self::E3|constructor#_#new#tearOff;
27+
}
28+
extension type _E4(core::int _info) {
29+
abstract extension-type-member representation-field get _info() → core::int;
30+
get info = self::_E4|get#info;
31+
constructor • = self::_E4|constructor#;
32+
constructor tearoff • = self::_E4|constructor#_#new#tearOff;
33+
}
34+
@#C3
35+
static extension-type-member method E3|constructor#(core::int _info) → self::E3% /* erasure=core::int, declared=! */ {
36+
lowered final self::E3% /* erasure=core::int, declared=! */ #this = _info;
37+
return #this;
38+
}
39+
@#C3
40+
static extension-type-member method E3|constructor#_#new#tearOff(core::int _info) → self::E3% /* erasure=core::int, declared=! */
41+
return self::E3|constructor#(_info);
42+
@#C3
43+
static extension-type-member method E3|get#info(lowered final self::E3% /* erasure=core::int, declared=! */ #this) → core::int
44+
return #this as{Unchecked} core::int;
45+
static extension-type-member method _E4|constructor#(core::int _info) → self::_E4% /* erasure=core::int, declared=! */ {
46+
lowered final self::_E4% /* erasure=core::int, declared=! */ #this = _info;
47+
return #this;
48+
}
49+
static extension-type-member method _E4|constructor#_#new#tearOff(core::int _info) → self::_E4% /* erasure=core::int, declared=! */
50+
return self::_E4|constructor#(_info);
51+
static extension-type-member method _E4|get#info(lowered final self::_E4% /* erasure=core::int, declared=! */ #this) → core::int
52+
return #this as{Unchecked} core::int;
53+
@#C3
54+
static extension-member method E5|addSuffix1(lowered final core::String #this, core::String suffix) → core::String
55+
return #this.{core::String::+}(suffix){(core::String) → core::String};
56+
@#C3
57+
static extension-member method E5|get#addSuffix1(lowered final core::String #this) → (core::String) → core::String
58+
return (core::String suffix) → core::String => self::E5|addSuffix1(#this, suffix);
59+
static extension-member method _E6|addSuffix2(lowered final core::String #this, core::String suffix) → core::String
60+
return #this.{core::String::+}(suffix){(core::String) → core::String};
61+
static extension-member method _E6|get#addSuffix2(lowered final core::String #this) → (core::String) → core::String
62+
return (core::String suffix) → core::String => self::_E6|addSuffix2(#this, suffix);
63+
static extension-member method _extension#2|addSuffix3(lowered final core::String #this, core::String suffix) → core::String
64+
return #this.{core::String::+}(suffix){(core::String) → core::String};
65+
static extension-member method _extension#2|get#addSuffix3(lowered final core::String #this) → (core::String) → core::String
66+
return (core::String suffix) → core::String => self::_extension#2|addSuffix3(#this, suffix);
67+
constants {
68+
#C1 = "dyn-module:callable"
69+
#C2 = null
70+
#C3 = core::pragma {name:#C1, options:#C2}
71+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
extension type E1._(int _info) {
6+
int get info => _info;
7+
}
8+
9+
extension E2 on String {
10+
int get lastChar => codeUnitAt(length - 1);
11+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
library;
2+
import self as self;
3+
import "dart:core" as core;
4+
5+
extension E2 on core::String {
6+
get lastChar = self::E2|get#lastChar;
7+
}
8+
extension type E1(core::int _info) {
9+
abstract extension-type-member representation-field get _info() → core::int;
10+
get info = self::E1|get#info;
11+
constructor _ = self::E1|constructor#_;
12+
constructor tearoff _ = self::E1|constructor#_#_#tearOff;
13+
}
14+
@#C3
15+
static extension-type-member method E1|constructor#_(core::int _info) → self::E1% /* erasure=core::int, declared=! */ {
16+
lowered final self::E1% /* erasure=core::int, declared=! */ #this = _info;
17+
return #this;
18+
}
19+
@#C3
20+
static extension-type-member method E1|constructor#_#_#tearOff(core::int _info) → self::E1% /* erasure=core::int, declared=! */
21+
return self::E1|constructor#_(_info);
22+
@#C3
23+
static extension-type-member method E1|get#info(lowered final self::E1% /* erasure=core::int, declared=! */ #this) → core::int
24+
return #this as{Unchecked} core::int;
25+
@#C3
26+
static extension-member method E2|get#lastChar(lowered final core::String #this) → core::int
27+
return #this.{core::String::codeUnitAt}(#this.{core::String::length}{core::int}.{core::num::-}(1){(core::num) → core::int}){(core::int) → core::int};
28+
constants {
29+
#C1 = "dyn-module:callable"
30+
#C2 = null
31+
#C3 = core::pragma {name:#C1, options:#C2}
32+
}

0 commit comments

Comments
 (0)