From 2ac7957077ed778676fec9edd98d63f5a4e2756f Mon Sep 17 00:00:00 2001 From: Alexander Markov Date: Wed, 19 Mar 2025 06:30:48 -0700 Subject: [PATCH] [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 Commit-Queue: Alexander Markov --- .../extension_type/dynamic_interface.yaml | 16 +++++ .../test/data/extension_type/main.dart | 16 +++++ .../data/extension_type/modules/entry1.dart | 9 +++ .../data/extension_type/shared/shared.dart | 11 +++ .../dynamic_interface_annotator.dart | 38 ++++++++++ .../dynamic_interface.yaml | 3 + .../dynamic_interface_annotator/lib4.dart | 25 +++++++ .../lib4.dart.expect | 71 +++++++++++++++++++ .../dynamic_interface_annotator/lib5.dart | 11 +++ .../lib5.dart.expect | 32 +++++++++ .../dynamic_interface_annotator/main.dart | 1 + .../main.dart.expect | 1 + 12 files changed, 234 insertions(+) create mode 100644 pkg/dynamic_modules/test/data/extension_type/dynamic_interface.yaml create mode 100644 pkg/dynamic_modules/test/data/extension_type/main.dart create mode 100644 pkg/dynamic_modules/test/data/extension_type/modules/entry1.dart create mode 100644 pkg/dynamic_modules/test/data/extension_type/shared/shared.dart create mode 100644 pkg/vm/testcases/transformations/dynamic_interface_annotator/lib4.dart create mode 100644 pkg/vm/testcases/transformations/dynamic_interface_annotator/lib4.dart.expect create mode 100644 pkg/vm/testcases/transformations/dynamic_interface_annotator/lib5.dart create mode 100644 pkg/vm/testcases/transformations/dynamic_interface_annotator/lib5.dart.expect diff --git a/pkg/dynamic_modules/test/data/extension_type/dynamic_interface.yaml b/pkg/dynamic_modules/test/data/extension_type/dynamic_interface.yaml new file mode 100644 index 00000000000..30007df4aa1 --- /dev/null +++ b/pkg/dynamic_modules/test/data/extension_type/dynamic_interface.yaml @@ -0,0 +1,16 @@ +# Copyright (c) 2025, 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. + +callable: + - library: 'shared/shared.dart' + # TODO(sigmund): This should be included by default + - library: 'dart:core' + class: 'Object' + - library: 'dart:core' + class: 'int' + - library: 'dart:core' + class: 'String' + - library: 'dart:core' + class: 'pragma' + member: '_' diff --git a/pkg/dynamic_modules/test/data/extension_type/main.dart b/pkg/dynamic_modules/test/data/extension_type/main.dart new file mode 100644 index 00000000000..768525b2946 --- /dev/null +++ b/pkg/dynamic_modules/test/data/extension_type/main.dart @@ -0,0 +1,16 @@ +// Copyright (c) 2025, 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 '../../common/testing.dart' as helper; +import 'package:expect/expect.dart'; + +import 'shared/shared.dart'; // ignore: unused_import + +/// A dynamic module is allowed to extend a class in the dynamic interface and +/// override its members. +void main() async { + final result = (await helper.load('entry1.dart')) as String; + Expect.equals('abcdef42', result); + helper.done(); +} diff --git a/pkg/dynamic_modules/test/data/extension_type/modules/entry1.dart b/pkg/dynamic_modules/test/data/extension_type/modules/entry1.dart new file mode 100644 index 00000000000..43d37a224a8 --- /dev/null +++ b/pkg/dynamic_modules/test/data/extension_type/modules/entry1.dart @@ -0,0 +1,9 @@ +// Copyright (c) 2025, 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 '../shared/shared.dart'; + +@pragma('dyn-module:entry-point') +Object? dynamicModuleEntrypoint() => + 'abc'.addSuffix('def') + MyExtensionType.foo(42).info.toString(); diff --git a/pkg/dynamic_modules/test/data/extension_type/shared/shared.dart b/pkg/dynamic_modules/test/data/extension_type/shared/shared.dart new file mode 100644 index 00000000000..8e8a160fb3f --- /dev/null +++ b/pkg/dynamic_modules/test/data/extension_type/shared/shared.dart @@ -0,0 +1,11 @@ +// Copyright (c) 2025, 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. + +extension type MyExtensionType.foo(int _info) { + int get info => _info; +} + +extension MyExtension on String { + String addSuffix(String suffix) => this + suffix; +} diff --git a/pkg/vm/lib/transformations/dynamic_interface_annotator.dart b/pkg/vm/lib/transformations/dynamic_interface_annotator.dart index c14348751a2..c8582e94b34 100644 --- a/pkg/vm/lib/transformations/dynamic_interface_annotator.dart +++ b/pkg/vm/lib/transformations/dynamic_interface_annotator.dart @@ -152,6 +152,16 @@ class _Annotator extends RecursiveVisitor { c.accept(this); } } + for (final ext in node.extensions) { + if (ext.name[0] != '_') { + ext.accept(this); + } + } + for (final extensionType in node.extensionTypeDeclarations) { + if (extensionType.name[0] != '_') { + extensionType.accept(this); + } + } for (final exportRef in node.additionalExports) { exportRef.node!.accept(this); } @@ -207,6 +217,34 @@ class _Annotator extends RecursiveVisitor { node.addAnnotation(ConstantExpression(pragma)); } } + + @override + void visitExtension(Extension node) { + for (final md in node.memberDescriptors) { + final member = md.memberReference?.node; + if (member != null) { + annotateMember(member as Member); + } + final tearOff = md.tearOffReference?.node; + if (tearOff != null) { + annotateMember(tearOff as Member); + } + } + } + + @override + void visitExtensionTypeDeclaration(ExtensionTypeDeclaration node) { + for (final md in node.memberDescriptors) { + final member = md.memberReference?.node; + if (member != null) { + annotateMember(member as Member); + } + final tearOff = md.tearOffReference?.node; + if (tearOff != null) { + annotateMember(tearOff as Member); + } + } + } } class _ImplicitExtendableAnnotator { diff --git a/pkg/vm/testcases/transformations/dynamic_interface_annotator/dynamic_interface.yaml b/pkg/vm/testcases/transformations/dynamic_interface_annotator/dynamic_interface.yaml index fb9265bf6c8..82157218445 100644 --- a/pkg/vm/testcases/transformations/dynamic_interface_annotator/dynamic_interface.yaml +++ b/pkg/vm/testcases/transformations/dynamic_interface_annotator/dynamic_interface.yaml @@ -9,12 +9,14 @@ callable: member: sfield9 - library: 'lib3.dart' class: ['A', 'B'] + - library: 'lib4.dart' - library: 'prefix/lib*' extendable: - library: 'lib1.dart' class: ['A', 'O', 'Q', 'S', 'T', 'V'] - library: 'lib2.dart' + - library: 'lib4.dart' can-be-overridden: - library: 'lib1.dart' @@ -22,3 +24,4 @@ can-be-overridden: - library: 'lib2.dart' class: 'D' member: 'build' + - library: 'lib4.dart' diff --git a/pkg/vm/testcases/transformations/dynamic_interface_annotator/lib4.dart b/pkg/vm/testcases/transformations/dynamic_interface_annotator/lib4.dart new file mode 100644 index 00000000000..c122b978ebf --- /dev/null +++ b/pkg/vm/testcases/transformations/dynamic_interface_annotator/lib4.dart @@ -0,0 +1,25 @@ +// Copyright (c) 2025, 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. + +export 'lib5.dart'; + +extension type E3(int _info) { + int get info => _info; +} + +extension type _E4(int _info) { + int get info => _info; +} + +extension E5 on String { + String addSuffix1(String suffix) => this + suffix; +} + +extension _E6 on String { + String addSuffix2(String suffix) => this + suffix; +} + +extension on String { + String addSuffix3(String suffix) => this + suffix; +} diff --git a/pkg/vm/testcases/transformations/dynamic_interface_annotator/lib4.dart.expect b/pkg/vm/testcases/transformations/dynamic_interface_annotator/lib4.dart.expect new file mode 100644 index 00000000000..e70654723c6 --- /dev/null +++ b/pkg/vm/testcases/transformations/dynamic_interface_annotator/lib4.dart.expect @@ -0,0 +1,71 @@ +library; +import self as self; +import "dart:core" as core; +import "lib5.dart" as lib5; +additionalExports = (lib5::E1, + lib5::E2) + +export "file:pkg/vm/testcases/transformations/dynamic_interface_annotator/lib5.dart"; + +extension E5 on core::String { + method addSuffix1 = self::E5|addSuffix1; + method tearoff addSuffix1 = self::E5|get#addSuffix1; +} +extension _E6 on core::String { + method addSuffix2 = self::_E6|addSuffix2; + method tearoff addSuffix2 = self::_E6|get#addSuffix2; +} +extension /* unnamed */ _extension#2 on core::String { + method addSuffix3 = self::_extension#2|addSuffix3; + method tearoff addSuffix3 = self::_extension#2|get#addSuffix3; +} +extension type E3(core::int _info) { + abstract extension-type-member representation-field get _info() → core::int; + get info = self::E3|get#info; + constructor • = self::E3|constructor#; + constructor tearoff • = self::E3|constructor#_#new#tearOff; +} +extension type _E4(core::int _info) { + abstract extension-type-member representation-field get _info() → core::int; + get info = self::_E4|get#info; + constructor • = self::_E4|constructor#; + constructor tearoff • = self::_E4|constructor#_#new#tearOff; +} +@#C3 +static extension-type-member method E3|constructor#(core::int _info) → self::E3% /* erasure=core::int, declared=! */ { + lowered final self::E3% /* erasure=core::int, declared=! */ #this = _info; + return #this; +} +@#C3 +static extension-type-member method E3|constructor#_#new#tearOff(core::int _info) → self::E3% /* erasure=core::int, declared=! */ + return self::E3|constructor#(_info); +@#C3 +static extension-type-member method E3|get#info(lowered final self::E3% /* erasure=core::int, declared=! */ #this) → core::int + return #this as{Unchecked} core::int; +static extension-type-member method _E4|constructor#(core::int _info) → self::_E4% /* erasure=core::int, declared=! */ { + lowered final self::_E4% /* erasure=core::int, declared=! */ #this = _info; + return #this; +} +static extension-type-member method _E4|constructor#_#new#tearOff(core::int _info) → self::_E4% /* erasure=core::int, declared=! */ + return self::_E4|constructor#(_info); +static extension-type-member method _E4|get#info(lowered final self::_E4% /* erasure=core::int, declared=! */ #this) → core::int + return #this as{Unchecked} core::int; +@#C3 +static extension-member method E5|addSuffix1(lowered final core::String #this, core::String suffix) → core::String + return #this.{core::String::+}(suffix){(core::String) → core::String}; +@#C3 +static extension-member method E5|get#addSuffix1(lowered final core::String #this) → (core::String) → core::String + return (core::String suffix) → core::String => self::E5|addSuffix1(#this, suffix); +static extension-member method _E6|addSuffix2(lowered final core::String #this, core::String suffix) → core::String + return #this.{core::String::+}(suffix){(core::String) → core::String}; +static extension-member method _E6|get#addSuffix2(lowered final core::String #this) → (core::String) → core::String + return (core::String suffix) → core::String => self::_E6|addSuffix2(#this, suffix); +static extension-member method _extension#2|addSuffix3(lowered final core::String #this, core::String suffix) → core::String + return #this.{core::String::+}(suffix){(core::String) → core::String}; +static extension-member method _extension#2|get#addSuffix3(lowered final core::String #this) → (core::String) → core::String + return (core::String suffix) → core::String => self::_extension#2|addSuffix3(#this, suffix); +constants { + #C1 = "dyn-module:callable" + #C2 = null + #C3 = core::pragma {name:#C1, options:#C2} +} diff --git a/pkg/vm/testcases/transformations/dynamic_interface_annotator/lib5.dart b/pkg/vm/testcases/transformations/dynamic_interface_annotator/lib5.dart new file mode 100644 index 00000000000..260a2690c60 --- /dev/null +++ b/pkg/vm/testcases/transformations/dynamic_interface_annotator/lib5.dart @@ -0,0 +1,11 @@ +// Copyright (c) 2025, 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. + +extension type E1._(int _info) { + int get info => _info; +} + +extension E2 on String { + int get lastChar => codeUnitAt(length - 1); +} diff --git a/pkg/vm/testcases/transformations/dynamic_interface_annotator/lib5.dart.expect b/pkg/vm/testcases/transformations/dynamic_interface_annotator/lib5.dart.expect new file mode 100644 index 00000000000..14b118a4bb4 --- /dev/null +++ b/pkg/vm/testcases/transformations/dynamic_interface_annotator/lib5.dart.expect @@ -0,0 +1,32 @@ +library; +import self as self; +import "dart:core" as core; + +extension E2 on core::String { + get lastChar = self::E2|get#lastChar; +} +extension type E1(core::int _info) { + abstract extension-type-member representation-field get _info() → core::int; + get info = self::E1|get#info; + constructor _ = self::E1|constructor#_; + constructor tearoff _ = self::E1|constructor#_#_#tearOff; +} +@#C3 +static extension-type-member method E1|constructor#_(core::int _info) → self::E1% /* erasure=core::int, declared=! */ { + lowered final self::E1% /* erasure=core::int, declared=! */ #this = _info; + return #this; +} +@#C3 +static extension-type-member method E1|constructor#_#_#tearOff(core::int _info) → self::E1% /* erasure=core::int, declared=! */ + return self::E1|constructor#_(_info); +@#C3 +static extension-type-member method E1|get#info(lowered final self::E1% /* erasure=core::int, declared=! */ #this) → core::int + return #this as{Unchecked} core::int; +@#C3 +static extension-member method E2|get#lastChar(lowered final core::String #this) → core::int + return #this.{core::String::codeUnitAt}(#this.{core::String::length}{core::int}.{core::num::-}(1){(core::num) → core::int}){(core::int) → core::int}; +constants { + #C1 = "dyn-module:callable" + #C2 = null + #C3 = core::pragma {name:#C1, options:#C2} +} diff --git a/pkg/vm/testcases/transformations/dynamic_interface_annotator/main.dart b/pkg/vm/testcases/transformations/dynamic_interface_annotator/main.dart index b97c7698084..a3b8a2fe515 100644 --- a/pkg/vm/testcases/transformations/dynamic_interface_annotator/main.dart +++ b/pkg/vm/testcases/transformations/dynamic_interface_annotator/main.dart @@ -5,6 +5,7 @@ import 'lib1.dart'; import 'lib2.dart'; import 'lib3.dart'; +import 'lib4.dart'; import 'prefix/lib1.dart'; import 'prefix/lib2.dart'; diff --git a/pkg/vm/testcases/transformations/dynamic_interface_annotator/main.dart.expect b/pkg/vm/testcases/transformations/dynamic_interface_annotator/main.dart.expect index 7693a91ab36..0795e1003c3 100644 --- a/pkg/vm/testcases/transformations/dynamic_interface_annotator/main.dart.expect +++ b/pkg/vm/testcases/transformations/dynamic_interface_annotator/main.dart.expect @@ -4,6 +4,7 @@ import self as self; import "file:pkg/vm/testcases/transformations/dynamic_interface_annotator/lib1.dart"; import "file:pkg/vm/testcases/transformations/dynamic_interface_annotator/lib2.dart"; import "file:pkg/vm/testcases/transformations/dynamic_interface_annotator/lib3.dart"; +import "file:pkg/vm/testcases/transformations/dynamic_interface_annotator/lib4.dart"; import "file:pkg/vm/testcases/transformations/dynamic_interface_annotator/prefix/lib1.dart"; import "file:pkg/vm/testcases/transformations/dynamic_interface_annotator/prefix/lib2.dart";