From 3d2d6492c1f49124eefdff7dc945ed0fa09565ce Mon Sep 17 00:00:00 2001 From: Nate Biggs Date: Mon, 27 Apr 2026 09:42:38 -0700 Subject: [PATCH] Add 'external-effect' pragma support to all the backends. Call sites targeting a procedure annotated with `external-effect` will not produce any code, including the argument which will not be evaluated. However, the single parameter will be treated as 'live' for the purposes of any global analysis the backends do. This is useful for things like protobuf shaking where a user may want to retain certain protobuf messages without actually emitting the code that retains those messages. Today this functionality is available internally in the vm and wasm SDK libraries. dart2js has similar functionality represented via the opaqueTrue and opaqueFalse booleans (which will cause conditional branches to get shaken after analysis). This will replace dart2js's opaque(True/False). This also adds validation to the frontend to ensure a method annotated with 'external-effect' is well-formed. Change-Id: If1c4096673e655c58fe7638840a16125003e7809 Tested: Backend tests for codegen were added. A frontend test was added for the validation. A language test was added to confirm the behavior. Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/476020 Reviewed-by: Alexander Markov Commit-Queue: Nate Biggs --- .../lib/js_interop_checks.dart | 3 +- pkg/compiler/doc/pragmas.md | 9 ++ .../lib/src/kernel/element_map_impl.dart | 3 +- pkg/compiler/lib/src/ssa/builder.dart | 8 ++ .../test/codegen/data/external_effect.dart | 23 ++++ pkg/dart2bytecode/lib/bytecode_generator.dart | 16 +-- pkg/dart2wasm/lib/code_generator.dart | 5 + pkg/dart2wasm/lib/intrinsics.dart | 4 - .../test/ir_tests/external_effect.dart | 24 ++++ .../test/ir_tests/external_effect.wat | 20 +++ pkg/dev_compiler/lib/src/kernel/compiler.dart | 18 ++- .../lib/src/kernel/compiler_new.dart | 18 ++- .../lib/src/kernel/kernel_helpers.dart | 16 +++ .../src/api_prototype/external_effect.dart | 5 + .../lib/src/builder/metadata_builder.dart | 22 +++ pkg/front_end/lib/src/codes/diagnostic.g.dart | 41 ++++++ .../lib/src/kernel/constant_evaluator.dart | 13 ++ .../lib/src/kernel/external_effect.dart | 101 ++++++++++++++ .../src/kernel/static_weak_references.dart | 23 +--- pkg/front_end/lib/src/kernel/utils.dart | 49 +++++++ pkg/front_end/messages.yaml | 43 ++++++ pkg/front_end/test/lint_test.status | 1 + .../test/spell_checking_list_messages.txt | 1 + .../external_effect/invalid_annotations.dart | 51 +++++++ .../invalid_annotations.dart.strong.expect | 128 +++++++++++++++++ ...lid_annotations.dart.strong.modular.expect | 128 +++++++++++++++++ ...lid_annotations.dart.strong.outline.expect | 129 ++++++++++++++++++ ...annotations.dart.strong.transformed.expect | 128 +++++++++++++++++ ...id_annotations.dart.textual_outline.expect | 40 ++++++ ...tions.dart.textual_outline_modelled.expect | 40 ++++++ .../external_effect/valid_annotations.dart | 13 ++ .../valid_annotations.dart.strong.expect | 26 ++++ ...lid_annotations.dart.strong.modular.expect | 26 ++++ ...lid_annotations.dart.strong.outline.expect | 20 +++ ...annotations.dart.strong.transformed.expect | 26 ++++ ...id_annotations.dart.textual_outline.expect | 9 ++ ...tions.dart.textual_outline_modelled.expect | 9 ++ pkg/kernel/binary.md | 2 +- pkg/kernel/lib/src/ast/members.dart | 9 ++ pkg/kernel/lib/text/ast_to_text.dart | 1 + .../pragmas_recognized_by_compiler.md | 9 ++ runtime/docs/pragmas.md | 1 + runtime/lib/object.cc | 4 - runtime/vm/bootstrap_natives.h | 1 - .../frontend/kernel_binary_flowgraph.cc | 9 +- .../frontend/kernel_binary_flowgraph.h | 4 +- runtime/vm/compiler/recognized_methods_list.h | 1 - runtime/vm/object.cc | 7 + runtime/vm/object.h | 4 + runtime/vm/symbol_list.h | 1 + sdk/lib/_internal/vm/lib/internal_patch.dart | 5 +- .../_internal/wasm/lib/internal_patch.dart | 4 +- .../language/method/external_effect_test.dart | 35 +++++ 53 files changed, 1263 insertions(+), 73 deletions(-) create mode 100644 pkg/compiler/test/codegen/data/external_effect.dart create mode 100644 pkg/dart2wasm/test/ir_tests/external_effect.dart create mode 100644 pkg/dart2wasm/test/ir_tests/external_effect.wat create mode 100644 pkg/front_end/lib/src/api_prototype/external_effect.dart create mode 100644 pkg/front_end/lib/src/kernel/external_effect.dart create mode 100644 pkg/front_end/testcases/external_effect/invalid_annotations.dart create mode 100644 pkg/front_end/testcases/external_effect/invalid_annotations.dart.strong.expect create mode 100644 pkg/front_end/testcases/external_effect/invalid_annotations.dart.strong.modular.expect create mode 100644 pkg/front_end/testcases/external_effect/invalid_annotations.dart.strong.outline.expect create mode 100644 pkg/front_end/testcases/external_effect/invalid_annotations.dart.strong.transformed.expect create mode 100644 pkg/front_end/testcases/external_effect/invalid_annotations.dart.textual_outline.expect create mode 100644 pkg/front_end/testcases/external_effect/invalid_annotations.dart.textual_outline_modelled.expect create mode 100644 pkg/front_end/testcases/external_effect/valid_annotations.dart create mode 100644 pkg/front_end/testcases/external_effect/valid_annotations.dart.strong.expect create mode 100644 pkg/front_end/testcases/external_effect/valid_annotations.dart.strong.modular.expect create mode 100644 pkg/front_end/testcases/external_effect/valid_annotations.dart.strong.outline.expect create mode 100644 pkg/front_end/testcases/external_effect/valid_annotations.dart.strong.transformed.expect create mode 100644 pkg/front_end/testcases/external_effect/valid_annotations.dart.textual_outline.expect create mode 100644 pkg/front_end/testcases/external_effect/valid_annotations.dart.textual_outline_modelled.expect create mode 100644 tests/language/method/external_effect_test.dart diff --git a/pkg/_js_interop_checks/lib/js_interop_checks.dart b/pkg/_js_interop_checks/lib/js_interop_checks.dart index 76c08d297d1..6b238d10f93 100644 --- a/pkg/_js_interop_checks/lib/js_interop_checks.dart +++ b/pkg/_js_interop_checks/lib/js_interop_checks.dart @@ -608,7 +608,8 @@ class JsInteropChecks extends RecursiveVisitor { final uri = member.enclosingLibrary.importUri; return uri.isScheme('dart') && _pathsWithAllowedDartExternalUsage.contains(uri.path) || - _allowedNativeTestPatterns.any(uri.path.contains); + _allowedNativeTestPatterns.any(uri.path.contains) || + (member is Procedure && member.hasExternalEffectPragma); } /// Assumes given [member] is not JS interop, and reports an error if diff --git a/pkg/compiler/doc/pragmas.md b/pkg/compiler/doc/pragmas.md index 3cf822b6d73..74210ec69f0 100644 --- a/pkg/compiler/doc/pragmas.md +++ b/pkg/compiler/doc/pragmas.md @@ -318,3 +318,12 @@ argument type. It should be either `external` or return its argument (for backwa Compiler replaces `weakRef(foo)` expression with either `foo` if method `foo()` is used and retained during tree shaking, or `null` if `foo()` is only used through weak references. Target `foo` should be a constant tearoff of a static method without arguments. + +### Declaring an external effect method + +```dart +@pragma('external-effect') +external void effect(Object? o); +``` + +Declares a special static external method `effect` which the compiler will treat as live code when performing any analysis of the program. For example, a type referenced from this call that would otherwise be tree-shaken will no longer be tree-shaken. To reduce code size the call itself (and its arguments) are dropped in the compiled output though. diff --git a/pkg/compiler/lib/src/kernel/element_map_impl.dart b/pkg/compiler/lib/src/kernel/element_map_impl.dart index 8bed4ef0a0f..2796186c722 100644 --- a/pkg/compiler/lib/src/kernel/element_map_impl.dart +++ b/pkg/compiler/lib/src/kernel/element_map_impl.dart @@ -2407,7 +2407,8 @@ class KernelNativeMemberResolver { // js_interop_checks when `native` and `external` can be disambiguated. if (!hasNativeBody && node.isExternal && - !_nativeBasicData.isJsInteropMember(_elementMap.getMember(node))) { + !_nativeBasicData.isJsInteropMember(_elementMap.getMember(node)) && + !(node is ir.Procedure && node.hasExternalEffectPragma)) { // TODO(johnniwinther): Should we change dart:html and friends to use // `external` instead of the native body syntax? _elementMap.reporter.reportErrorMessage( diff --git a/pkg/compiler/lib/src/ssa/builder.dart b/pkg/compiler/lib/src/ssa/builder.dart index c46ef37b332..ccc6bfefbfb 100644 --- a/pkg/compiler/lib/src/ssa/builder.dart +++ b/pkg/compiler/lib/src/ssa/builder.dart @@ -6,6 +6,10 @@ import 'package:_js_interop_checks/src/js_interop.dart' show getDartJSInteropJSName; // ignore: implementation_imports +import 'package:front_end/src/api_prototype/external_effect.dart' + as ir + show ExternalEffect; +// ignore: implementation_imports import 'package:front_end/src/api_prototype/static_weak_references.dart' as ir show StaticWeakReferences; @@ -5063,6 +5067,10 @@ class KernelSsaGraphBuilder extends ir.VisitorDefault stack.add(graph.addConstantNull(closedWorld)); return; } + if (ir.ExternalEffect.isExternalEffect(node)) { + stack.add(graph.addConstantNull(closedWorld)); + return; + } ir.Procedure target = node.target; final sourceInformation = _sourceInformationBuilder.buildCall(node, node); final function = _elementMap.getMember(target) as FunctionEntity; diff --git a/pkg/compiler/test/codegen/data/external_effect.dart b/pkg/compiler/test/codegen/data/external_effect.dart new file mode 100644 index 00000000000..8498ce8c1ed --- /dev/null +++ b/pkg/compiler/test/codegen/data/external_effect.dart @@ -0,0 +1,23 @@ +// Copyright (c) 2026, 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. + +@pragma('external-effect') +external void externalEffect(Object? o); + +void nonExternalEffect(Object? o) {} + +int use(int i) { + print(i); + return i; +} + +/*member: main:function() { + A.print(1); + A.print(2); +}*/ +void main() { + externalEffect(use(0)); + print(1); + nonExternalEffect(use(2)); +} diff --git a/pkg/dart2bytecode/lib/bytecode_generator.dart b/pkg/dart2bytecode/lib/bytecode_generator.dart index 34a20f5b592..c28cf8da3bf 100644 --- a/pkg/dart2bytecode/lib/bytecode_generator.dart +++ b/pkg/dart2bytecode/lib/bytecode_generator.dart @@ -6,6 +6,8 @@ import 'dart:convert'; import 'dart:developer'; import 'dart:math' as math; +import 'package:front_end/src/api_prototype/external_effect.dart' + show ExternalEffect; import 'package:kernel/ast.dart' hide Component, FunctionDeclaration; import 'package:kernel/ast.dart' as ast show Component, FunctionDeclaration; import 'package:kernel/class_hierarchy.dart' show ClassHierarchy; @@ -1089,11 +1091,6 @@ class BytecodeGenerator extends RecursiveVisitor { 'reachabilityFence', ); - late Procedure nativeEffect = libraryIndex.getTopLevelProcedure( - 'dart:_internal', - '_nativeEffect', - ); - late Procedure iterableIterator = libraryIndex.getProcedure( 'dart:core', 'Iterable', @@ -4006,6 +4003,11 @@ class BytecodeGenerator extends RecursiveVisitor { @override void visitStaticInvocation(StaticInvocation node) { + if (ExternalEffect.isExternalEffect(node)) { + // Skip over AST of the argument, return null. + asm.emitPushNull(); + return; + } if (node.isConst) { _genPushConstExpr(node); return; @@ -4018,10 +4020,6 @@ class BytecodeGenerator extends RecursiveVisitor { assert(args.named.isEmpty); _generateNode(args.positional.single); return; - } else if (target == nativeEffect) { - // Skip over AST of the argument, return null. - asm.emitPushNull(); - return; } else if (target == ffiCall) { assert(args.named.isEmpty); _generateFfiCall(args.positional.single); diff --git a/pkg/dart2wasm/lib/code_generator.dart b/pkg/dart2wasm/lib/code_generator.dart index 2a1798f8286..00e5d45f618 100644 --- a/pkg/dart2wasm/lib/code_generator.dart +++ b/pkg/dart2wasm/lib/code_generator.dart @@ -5,6 +5,8 @@ import 'dart:collection' show LinkedHashMap; import 'package:collection/collection.dart'; +import 'package:front_end/src/api_prototype/external_effect.dart' + show ExternalEffect; import 'package:kernel/ast.dart'; import 'package:kernel/type_environment.dart'; import 'package:wasm_builder/wasm_builder.dart' as w; @@ -1625,6 +1627,9 @@ abstract class AstCodeGenerator StaticInvocation node, w.ValueType expectedType, ) { + if (ExternalEffect.isExternalEffect(node)) { + return voidMarker; + } w.ValueType? intrinsicResult = intrinsifier.generateStaticIntrinsic(node); if (intrinsicResult != null) return intrinsicResult; diff --git a/pkg/dart2wasm/lib/intrinsics.dart b/pkg/dart2wasm/lib/intrinsics.dart index bafb49b4bff..fe20cac65f1 100644 --- a/pkg/dart2wasm/lib/intrinsics.dart +++ b/pkg/dart2wasm/lib/intrinsics.dart @@ -286,7 +286,6 @@ enum StaticIntrinsic { setIdentityHashField('dart:_object_helper', null, 'setIdentityHashField'), unsafeCast('dart:_internal', null, 'unsafeCast'), unsafeCastOpaque('dart:_internal', null, 'unsafeCastOpaque'), - nativeEffect('dart:_internal', null, '_nativeEffect'), floatToIntBits('dart:_internal', null, 'floatToIntBits'), intBitsToFloat('dart:_internal', null, 'intBitsToFloat'), doubleToIntBits('dart:_internal', null, 'doubleToIntBits'), @@ -1440,9 +1439,6 @@ class Intrinsifier { // Just evaluate the operand and let the context convert it to the // expected type. return codeGen.translateExpression(operand, typeOfExp(operand)); - case StaticIntrinsic.nativeEffect: - // Ignore argument - return translator.voidMarker; case StaticIntrinsic.floatToIntBits: codeGen.translateExpression( node.arguments.positional.single, diff --git a/pkg/dart2wasm/test/ir_tests/external_effect.dart b/pkg/dart2wasm/test/ir_tests/external_effect.dart new file mode 100644 index 00000000000..4e4a40462ef --- /dev/null +++ b/pkg/dart2wasm/test/ir_tests/external_effect.dart @@ -0,0 +1,24 @@ +// Copyright (c) 2026, 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. + +// functionFilter=main +// compilerOption=--no-inlining +// compilerOption=--no-minify + +@pragma('external-effect') +external void externalEffect(Object? o); + +void nonExternalEffect(Object? o) {} + +int use(int i) { + print(i); + return i; +} + +@pragma('wasm:never-inline') +void main() { + externalEffect(use(0)); + print(1); + nonExternalEffect(use(2)); +} diff --git a/pkg/dart2wasm/test/ir_tests/external_effect.wat b/pkg/dart2wasm/test/ir_tests/external_effect.wat new file mode 100644 index 00000000000..603b4c8f424 --- /dev/null +++ b/pkg/dart2wasm/test/ir_tests/external_effect.wat @@ -0,0 +1,20 @@ +(module $module0 + (type $#Top (struct + (field $field0 i32))) + (type $BoxedInt (sub final $#Top (struct + (field $field0 i32) + (field $value i64)))) + (global $1 (ref $BoxedInt) + (i32.const 59) + (i64.const 1) + (struct.new $BoxedInt)) + (func $"main " + global.get $1 + call $print + i32.const 59 + i64.const 2 + struct.new $BoxedInt + call $print + ) + (func $print (param $var0 (ref $#Top)) <...>) +) \ No newline at end of file diff --git a/pkg/dev_compiler/lib/src/kernel/compiler.dart b/pkg/dev_compiler/lib/src/kernel/compiler.dart index c5904a580a0..721f433a732 100644 --- a/pkg/dev_compiler/lib/src/kernel/compiler.dart +++ b/pkg/dev_compiler/lib/src/kernel/compiler.dart @@ -11,6 +11,8 @@ import 'package:_js_interop_checks/src/js_interop.dart' show getDartJSInteropJSName, hasDartJSInteropAnnotation; import 'package:_js_interop_checks/src/transformations/js_util_optimizer.dart' show ExtensionIndex; +import 'package:front_end/src/api_prototype/external_effect.dart' + show ExternalEffect; import 'package:front_end/src/api_unstable/ddc.dart'; import 'package:js_shared/synced/embedded_names.dart' show JsGetName, JsBuiltin; import 'package:kernel/class_hierarchy.dart'; @@ -3644,8 +3646,7 @@ class ProgramCompiler extends ComputeOnceConstantVisitor _currentUri = savedUri; _staticTypeContext.leaveMember(p); - if (_options.dynamicModule && - p.annotations.any((a) => _isEntrypointPragma(a, _coreTypes))) { + if (_options.dynamicModule && _isDynamicModuleEntryPoint(p, _coreTypes)) { if (_dynamicEntrypoint == null) { if (p.function.requiredParameterCount > 0) { // TODO(sigmund): this error should be caught by a kernel checker that @@ -6684,6 +6685,9 @@ class ProgramCompiler extends ComputeOnceConstantVisitor @override js_ast.Expression visitStaticInvocation(StaticInvocation node) { var target = node.target; + if (ExternalEffect.isExternalEffect(node)) { + return js_ast.LiteralNull(); + } if (isInlineJS(target)) return _emitInlineJSCode(node) as js_ast.Expression; if (target.isFactory) return _emitFactoryInvocation(node); @@ -9275,12 +9279,6 @@ class _SwitchLabelState { /// /// Used to denote the entrypoint method of a dynamic module. // TODO(sigmund): move to package:kernel. -bool _isEntrypointPragma(Expression expression, CoreTypes coreTypes) { - if (expression is! ConstantExpression) return false; - final value = expression.constant; - if (value is! InstanceConstant) return false; - if (value.classReference != coreTypes.pragmaClass.reference) return false; - final name = value.fieldValues[coreTypes.pragmaName.fieldReference]; - if (name is! StringConstant) return false; - return name.value == 'dyn-module:entry-point'; +bool _isDynamicModuleEntryPoint(Procedure p, CoreTypes coreTypes) { + return hasPragma(p, 'dyn-module:entry-point', coreTypes); } diff --git a/pkg/dev_compiler/lib/src/kernel/compiler_new.dart b/pkg/dev_compiler/lib/src/kernel/compiler_new.dart index a7a5d63ebe6..a19b2c5c526 100644 --- a/pkg/dev_compiler/lib/src/kernel/compiler_new.dart +++ b/pkg/dev_compiler/lib/src/kernel/compiler_new.dart @@ -11,6 +11,8 @@ import 'package:_js_interop_checks/src/js_interop.dart' show getDartJSInteropJSName, hasDartJSInteropAnnotation; import 'package:_js_interop_checks/src/transformations/js_util_optimizer.dart' show ExtensionIndex; +import 'package:front_end/src/api_prototype/external_effect.dart' + show ExternalEffect; import 'package:front_end/src/api_unstable/ddc.dart'; import 'package:js_shared/synced/embedded_names.dart' show JsGetName, JsBuiltin; import 'package:kernel/class_hierarchy.dart'; @@ -4249,8 +4251,7 @@ class LibraryCompiler extends ComputeOnceConstantVisitor _currentUri = savedUri; _staticTypeContext.leaveMember(p); - if (_options.dynamicModule && - p.annotations.any((a) => _isEntrypointPragma(a, _coreTypes))) { + if (_options.dynamicModule && _isDynamicModuleEntryPoint(p, _coreTypes)) { if (_dynamicEntrypoint == null) { if (p.function.requiredParameterCount > 0) { // TODO(sigmund): this error should be caught by a kernel checker that @@ -7377,6 +7378,9 @@ class LibraryCompiler extends ComputeOnceConstantVisitor @override js_ast.Expression visitStaticInvocation(StaticInvocation node) { var target = node.target; + if (ExternalEffect.isExternalEffect(node)) { + return js_ast.LiteralNull(); + } if (isInlineJS(target)) return _emitInlineJSCode(node) as js_ast.Expression; if (target.isFactory) return _emitFactoryInvocation(node); @@ -10098,12 +10102,6 @@ class _SwitchLabelState { /// `const pragma('dyn-module:entry-point')`. /// /// Used to denote the entrypoint method of a dynamic module. -bool _isEntrypointPragma(Expression expression, CoreTypes coreTypes) { - if (expression is! ConstantExpression) return false; - final value = expression.constant; - if (value is! InstanceConstant) return false; - if (value.classReference != coreTypes.pragmaClass.reference) return false; - final name = value.fieldValues[coreTypes.pragmaName.fieldReference]; - if (name is! StringConstant) return false; - return name.value == 'dyn-module:entry-point'; +bool _isDynamicModuleEntryPoint(Procedure p, CoreTypes coreTypes) { + return hasPragma(p, 'dyn-module:entry-point', coreTypes); } diff --git a/pkg/dev_compiler/lib/src/kernel/kernel_helpers.dart b/pkg/dev_compiler/lib/src/kernel/kernel_helpers.dart index da6f42c5058..efdbc43dffa 100644 --- a/pkg/dev_compiler/lib/src/kernel/kernel_helpers.dart +++ b/pkg/dev_compiler/lib/src/kernel/kernel_helpers.dart @@ -243,6 +243,22 @@ bool isUnsupportedFactoryConstructor(Procedure node) { return false; } +bool hasPragma(Annotatable node, String name, CoreTypes coreTypes) { + for (var a in node.annotations) { + if (a is! ConstantExpression) continue; + final value = a.constant; + if (value is! InstanceConstant) continue; + if (value.classReference != coreTypes.pragmaClass.reference) { + continue; + } + final nameValue = value.fieldValues[coreTypes.pragmaName.fieldReference]; + if (nameValue is StringConstant && nameValue.value == name) { + return true; + } + } + return false; +} + /// Gets the real supertype of [c] and the list of [mixins] in reverse /// application order (mixins will appear before ones they override). /// diff --git a/pkg/front_end/lib/src/api_prototype/external_effect.dart b/pkg/front_end/lib/src/api_prototype/external_effect.dart new file mode 100644 index 00000000000..8ca3a9ddf2a --- /dev/null +++ b/pkg/front_end/lib/src/api_prototype/external_effect.dart @@ -0,0 +1,5 @@ +// Copyright (c) 2026, 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 '../kernel/external_effect.dart' show ExternalEffect; diff --git a/pkg/front_end/lib/src/builder/metadata_builder.dart b/pkg/front_end/lib/src/builder/metadata_builder.dart index 74e7d8b8872..8bc36d6d6bf 100644 --- a/pkg/front_end/lib/src/builder/metadata_builder.dart +++ b/pkg/front_end/lib/src/builder/metadata_builder.dart @@ -4,10 +4,13 @@ import 'package:_fe_analyzer_shared/src/metadata/expressions.dart' as shared; import 'package:_fe_analyzer_shared/src/scanner/scanner.dart' show Token; +import 'package:front_end/src/kernel/kernel_constants.dart' + show KernelConstantErrorReporter; import 'package:kernel/ast.dart'; import 'package:kernel/clone.dart'; import '../api_prototype/experimental_flags.dart'; +import '../api_prototype/external_effect.dart' show ExternalEffect; import '../base/extension_scope.dart'; import '../base/loader.dart'; import '../base/scope.dart' show LookupScope; @@ -166,6 +169,25 @@ class MetadataBuilder { for (Annotation annotation in annotations) { annotation.metadataBuilder._expression = annotation.expression; } + + validateAnnotations(annotatable, libraryBuilder); + } + + static void validateAnnotations( + Annotatable annotatable, + SourceLibraryBuilder libraryBuilder, + ) { + if (ExternalEffect.isOutlineAnnotatedWithExternalEffect( + annotatable, + libraryBuilder.loader.coreTypes, + )) { + ExternalEffect.validatePragma( + annotatable, + libraryBuilder.loader.coreTypes, + new KernelConstantErrorReporter(libraryBuilder.loader), + checkHasFlag: false, + ); + } } } diff --git a/pkg/front_end/lib/src/codes/diagnostic.g.dart b/pkg/front_end/lib/src/codes/diagnostic.g.dart index ded27c0654d..b7242d670f1 100644 --- a/pkg/front_end/lib/src/codes/diagnostic.g.dart +++ b/pkg/front_end/lib/src/codes/diagnostic.g.dart @@ -2046,6 +2046,47 @@ Message _withArgumentsCyclicTypedef({required String name}) { ); } +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +const MessageCode dartExternalEffectIncorrectType = const MessageCode( + "DartExternalEffectIncorrectType", + problemMessage: + """A function annotated with the 'external-effect' pragma must have the type 'void Function(Object?)'""", + correctionMessage: """Try correcting the type of the function.""", +); + +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +const MessageCode dartExternalEffectMalformedPragma = const MessageCode( + "DartExternalEffectMalformedPragma", + problemMessage: + """The 'external-effect' pragma must be applied as a String literal.""", + correctionMessage: + """Try inlining the 'external-string' argument to the pragma.""", +); + +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +const MessageCode dartExternalEffectNotExternal = const MessageCode( + "DartExternalEffectNotExternal", + problemMessage: + """A function annotated with the 'external-effect' pragma must be external.""", + correctionMessage: """Try making the function external.""", +); + +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +const MessageCode dartExternalEffectNotMethod = const MessageCode( + "DartExternalEffectNotMethod", + problemMessage: + """The 'external-effect' pragma can only be applied to methods.""", + correctionMessage: """Try removing the pragma or applying it to a method.""", +); + +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +const MessageCode dartExternalEffectNotStatic = const MessageCode( + "DartExternalEffectNotStatic", + problemMessage: + """A function annotated with the 'external-effect' pragma must be static.""", + correctionMessage: """Try making the function static.""", +); + // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. const MessageCode dartFfiLibraryInDart2Wasm = const MessageCode( "DartFfiLibraryInDart2Wasm", diff --git a/pkg/front_end/lib/src/kernel/constant_evaluator.dart b/pkg/front_end/lib/src/kernel/constant_evaluator.dart index 1dcab3ae759..e08b353a7ce 100644 --- a/pkg/front_end/lib/src/kernel/constant_evaluator.dart +++ b/pkg/front_end/lib/src/kernel/constant_evaluator.dart @@ -24,6 +24,7 @@ import 'package:_fe_analyzer_shared/src/exhaustiveness/exhaustive.dart'; import 'package:_fe_analyzer_shared/src/exhaustiveness/space.dart'; import 'package:_fe_analyzer_shared/src/exhaustiveness/static_type.dart'; import 'package:front_end/src/codes/diagnostic.dart' as diag; +import 'package:front_end/src/kernel/external_effect.dart' show ExternalEffect; import 'package:kernel/ast.dart'; import 'package:kernel/core_types.dart'; import 'package:kernel/src/find_type_visitor.dart'; @@ -365,6 +366,18 @@ class ConstantsTransformer extends RemovingTransformer { constantEvaluator.errorReporter, ); } + + if (ExternalEffect.isAnnotatedWithExternalEffect( + parent, + typeEnvironment.coreTypes, + )) { + ExternalEffect.validatePragma( + parent, + typeEnvironment.coreTypes, + constantEvaluator.errorReporter, + checkHasFlag: true, + ); + } } RecordUse.validateAnnotations( nodes, diff --git a/pkg/front_end/lib/src/kernel/external_effect.dart b/pkg/front_end/lib/src/kernel/external_effect.dart new file mode 100644 index 00000000000..711d9413f2e --- /dev/null +++ b/pkg/front_end/lib/src/kernel/external_effect.dart @@ -0,0 +1,101 @@ +// Copyright (c) 2026, 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:front_end/src/codes/diagnostic.dart' as diag; +import 'package:front_end/src/api_prototype/constant_evaluator.dart'; +import 'package:front_end/src/kernel/utils.dart'; +import 'package:kernel/core_types.dart'; +import 'package:kernel/kernel.dart'; + +class ExternalEffect { + static const String pragmaName = 'external-effect'; + + // Coverage-ignore(suite): Not run. + static bool isExternalEffect(StaticInvocation node) { + return node.target.hasExternalEffectPragma; + } + + static bool isOutlineAnnotatedWithExternalEffect( + Annotatable node, + CoreTypes coreTypes, + ) { + return isOutlineAnnotatedWithPragma(node, coreTypes, pragmaName); + } + + static bool isAnnotatedWithExternalEffect( + Annotatable node, + CoreTypes coreTypes, + ) { + return isAnnotatedWithPragma(node, coreTypes, pragmaName); + } + + static void validatePragma( + Annotatable node, + CoreTypes coreTypes, + ErrorReporter errorReporter, { + required bool checkHasFlag, + }) { + if (node is! Procedure || node.kind != ProcedureKind.Method) { + errorReporter.report( + diag.dartExternalEffectNotMethod.withLocation( + node.location!.file, + node.fileOffset, + 1, + ), + ); + return; + } + + if (node.isInstanceMember) { + errorReporter.report( + diag.dartExternalEffectNotStatic.withLocation( + node.location!.file, + node.fileOffset, + 1, + ), + ); + return; + } + + if (!node.isExternal) { + errorReporter.report( + diag.dartExternalEffectNotExternal.withLocation( + node.location!.file, + node.fileOffset, + 1, + ), + ); + return; + } + + FunctionNode function = node.function; + + if (function.computeFunctionType(Nullability.nonNullable) != + new FunctionType( + [coreTypes.objectNullableRawType], + const VoidType(), + Nullability.nonNullable, + )) { + errorReporter.report( + diag.dartExternalEffectIncorrectType.withLocation( + node.location!.file, + node.fileOffset, + 1, + ), + ); + return; + } + + if (checkHasFlag && !node.hasExternalEffectPragma) { + errorReporter.report( + diag.dartExternalEffectMalformedPragma.withLocation( + node.location!.file, + node.fileOffset, + 1, + ), + ); + } + node.hasExternalEffectPragma = true; + } +} diff --git a/pkg/front_end/lib/src/kernel/static_weak_references.dart b/pkg/front_end/lib/src/kernel/static_weak_references.dart index 0574f439326..97b5c4e2520 100644 --- a/pkg/front_end/lib/src/kernel/static_weak_references.dart +++ b/pkg/front_end/lib/src/kernel/static_weak_references.dart @@ -5,6 +5,7 @@ /// Handling of static weak references. import 'package:front_end/src/codes/diagnostic.dart' as diag; +import 'package:front_end/src/kernel/utils.dart' show isAnnotatedWithPragma; import 'package:kernel/ast.dart'; import 'package:kernel/core_types.dart' show CoreTypes; @@ -23,27 +24,7 @@ class StaticWeakReferences { static bool isAnnotatedWithWeakReferencePragma( Annotatable node, CoreTypes coreTypes, - ) { - List annotations = node.annotations; - for (int i = 0; i < annotations.length; i++) { - Expression annotation = annotations[i]; - if (annotation is ConstantExpression) { - Constant constant = annotation.constant; - if (constant is InstanceConstant) { - if (constant.classNode == coreTypes.pragmaClass) { - Constant? name = - constant.fieldValues[coreTypes.pragmaName.fieldReference]; - if (name is StringConstant) { - if (name.value == weakTearoffReferencePragma) { - return true; - } - } - } - } - } - } - return false; - } + ) => isAnnotatedWithPragma(node, coreTypes, weakTearoffReferencePragma); static void validateWeakReferenceUse( StaticInvocation node, diff --git a/pkg/front_end/lib/src/kernel/utils.dart b/pkg/front_end/lib/src/kernel/utils.dart index d02a830ca2a..aa5c944fb7f 100644 --- a/pkg/front_end/lib/src/kernel/utils.dart +++ b/pkg/front_end/lib/src/kernel/utils.dart @@ -14,6 +14,7 @@ import 'package:front_end/src/base/scope.dart'; import 'package:kernel/ast.dart'; import 'package:kernel/binary/ast_to_binary.dart'; import 'package:kernel/clone.dart'; +import 'package:kernel/core_types.dart' show CoreTypes; import 'package:kernel/text/ast_to_text.dart'; import 'package:kernel/src/printer.dart'; @@ -379,3 +380,51 @@ class _DummyExtensionScope implements ExtensionScope { } final Argument dummyArgument = new PositionalArgument(dummyExpression); + +bool isOutlineAnnotatedWithPragma( + Annotatable node, + CoreTypes coreTypes, + String pragmaName, +) { + List annotations = node.annotations; + for (int i = 0; i < annotations.length; i++) { + Expression annotation = annotations[i]; + if (annotation is RedirectingFactoryInvocation && + annotation + .redirectingFactoryTarget + .function + .redirectingFactoryTarget! + .target == + coreTypes.pragmaConstructor) { + Expression name = annotation.expression.arguments.positional[0]; + if (name is StringLiteral && name.value == pragmaName) { + return true; + } + } + } + return false; +} + +bool isAnnotatedWithPragma( + Annotatable node, + CoreTypes coreTypes, + String pragmaName, +) { + List annotations = node.annotations; + for (int i = 0; i < annotations.length; i++) { + Expression annotation = annotations[i]; + if (annotation is ConstantExpression) { + Constant constant = annotation.constant; + if (constant is InstanceConstant) { + if (constant.classNode == coreTypes.pragmaClass) { + Constant? name = + constant.fieldValues[coreTypes.pragmaName.fieldReference]; + if (name is StringConstant && name.value == pragmaName) { + return true; + } + } + } + } + } + return false; +} diff --git a/pkg/front_end/messages.yaml b/pkg/front_end/messages.yaml index febb0c9a683..f1acfea1d1a 100644 --- a/pkg/front_end/messages.yaml +++ b/pkg/front_end/messages.yaml @@ -7757,3 +7757,46 @@ anonymousMethodWrongParameterTypeCfe: experiments: anonymous-methods statement: - '"".(int i) => 1;' + +dartExternalEffectNotMethod: + parameters: none + problemMessage: "The 'external-effect' pragma can only be applied to methods." + correctionMessage: "Try removing the pragma or applying it to a method." + script: | + @pragma('external-effect') + external int get foo; + +dartExternalEffectIncorrectType: + parameters: none + problemMessage: "A function annotated with the 'external-effect' pragma must have the type 'void Function(Object?)'" + correctionMessage: "Try correcting the type of the function." + script: | + @pragma('external-effect') + external void foo(int x); + +dartExternalEffectNotStatic: + parameters: none + problemMessage: "A function annotated with the 'external-effect' pragma must be static." + correctionMessage: "Try making the function static." + script: | + class C { + @pragma('external-effect') + external void foo(Object? x); + } + +dartExternalEffectNotExternal: + parameters: none + problemMessage: "A function annotated with the 'external-effect' pragma must be external." + correctionMessage: "Try making the function external." + script: | + @pragma('external-effect') + void foo(Object? x) {} + +dartExternalEffectMalformedPragma: + parameters: none + problemMessage: "The 'external-effect' pragma must be applied as a String literal." + correctionMessage: "Try inlining the 'external-string' argument to the pragma." + script: | + const bar = 'external-effect'; + @pragma(bar) + external void foo(Object? x); diff --git a/pkg/front_end/test/lint_test.status b/pkg/front_end/test/lint_test.status index ddf5b535114..079b5e3f092 100644 --- a/pkg/front_end/test/lint_test.status +++ b/pkg/front_end/test/lint_test.status @@ -11,6 +11,7 @@ front_end/lib/src/api_prototype/compiler_options/Exports: Fail front_end/lib/src/api_prototype/const_conditional_simplifier/Exports: Fail front_end/lib/src/api_prototype/constant_evaluator/Exports: Fail front_end/lib/src/api_prototype/dynamic_module_validator/Exports: Fail +front_end/lib/src/api_prototype/external_effect/Exports: Fail front_end/lib/src/api_prototype/front_end/Exports: Fail front_end/lib/src/api_prototype/incremental_kernel_generator/Exports: Fail front_end/lib/src/api_prototype/lowering_predicates/Exports: Fail diff --git a/pkg/front_end/test/spell_checking_list_messages.txt b/pkg/front_end/test/spell_checking_list_messages.txt index a39de4d4569..0a5e0393b8d 100644 --- a/pkg/front_end/test/spell_checking_list_messages.txt +++ b/pkg/front_end/test/spell_checking_list_messages.txt @@ -66,6 +66,7 @@ finality float32x float64x flutter_runner +function(object function.tojs futureor github.com diff --git a/pkg/front_end/testcases/external_effect/invalid_annotations.dart b/pkg/front_end/testcases/external_effect/invalid_annotations.dart new file mode 100644 index 00000000000..0972162cab1 --- /dev/null +++ b/pkg/front_end/testcases/external_effect/invalid_annotations.dart @@ -0,0 +1,51 @@ +// Copyright (c) 2026, 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. + +@pragma('external-effect') +external int a; + +@pragma('external-effect') +external int get b; + +@pragma('external-effect') +external set c(int value); + +@pragma('external-effect') +void d(Object? o) {} + +@pragma('external-effect') +external void e(Object o); + +const z = 'external-effect'; + +@pragma(z) +external void f(Object? o); + +class A { + @pragma('external-effect') + external void a(Object? o); + + @pragma('external-effect') + external static int b(Object? o); + + @pragma('external-effect') + external static void c(Object? o, Object? x); + + @pragma('external-effect') + external static void d(int i); + + @pragma('external-effect') + external static void e([Object? o = const Object()]); + + @pragma('external-effect') + external static void f({required Object? o}); + + @pragma('external-effect') + external static void g({Object? o = 3}); + + @pragma('external-effect') + external static void h(T? t); +} + +void main() {} diff --git a/pkg/front_end/testcases/external_effect/invalid_annotations.dart.strong.expect b/pkg/front_end/testcases/external_effect/invalid_annotations.dart.strong.expect new file mode 100644 index 00000000000..20080188c76 --- /dev/null +++ b/pkg/front_end/testcases/external_effect/invalid_annotations.dart.strong.expect @@ -0,0 +1,128 @@ +library; +// +// Problems in library: +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:6:14: Error: The 'external-effect' pragma can only be applied to methods. +// Try removing the pragma or applying it to a method. +// external int a; +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:9:18: Error: The 'external-effect' pragma can only be applied to methods. +// Try removing the pragma or applying it to a method. +// external int get b; +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:12:14: Error: The 'external-effect' pragma can only be applied to methods. +// Try removing the pragma or applying it to a method. +// external set c(int value); +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:15:6: Error: A function annotated with the 'external-effect' pragma must be external. +// Try making the function external. +// void d(Object? o) {} +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:18:15: Error: A function annotated with the 'external-effect' pragma must have the type 'void Function(Object?)' +// Try correcting the type of the function. +// external void e(Object o); +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:27:17: Error: A function annotated with the 'external-effect' pragma must be static. +// Try making the function static. +// external void a(Object? o); +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:30:23: Error: A function annotated with the 'external-effect' pragma must have the type 'void Function(Object?)' +// Try correcting the type of the function. +// external static int b(Object? o); +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:33:24: Error: A function annotated with the 'external-effect' pragma must have the type 'void Function(Object?)' +// Try correcting the type of the function. +// external static void c(Object? o, Object? x); +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:36:24: Error: A function annotated with the 'external-effect' pragma must have the type 'void Function(Object?)' +// Try correcting the type of the function. +// external static void d(int i); +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:39:24: Error: A function annotated with the 'external-effect' pragma must have the type 'void Function(Object?)' +// Try correcting the type of the function. +// external static void e([Object? o = const Object()]); +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:42:24: Error: A function annotated with the 'external-effect' pragma must have the type 'void Function(Object?)' +// Try correcting the type of the function. +// external static void f({required Object? o}); +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:45:24: Error: A function annotated with the 'external-effect' pragma must have the type 'void Function(Object?)' +// Try correcting the type of the function. +// external static void g({Object? o = 3}); +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:48:24: Error: A function annotated with the 'external-effect' pragma must have the type 'void Function(Object?)' +// Try correcting the type of the function. +// external static void h(T? t); +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:23:15: Error: The 'external-effect' pragma must be applied as a String literal. +// Try inlining the 'external-string' argument to the pragma. +// external void f(Object? o); +// ^ +// +import self as self; +import "dart:core" as core; + +class A extends core::Object { + synthetic constructor •() → self::A + : super core::Object::•() + ; + @#C3 + external method a(core::Object? o) → void; + @#C3 + external static method b(core::Object? o) → core::int; + @#C3 + external static method c(core::Object? o, core::Object? x) → void; + @#C3 + external static method d(core::int i) → void; + @#C3 + external static method e([core::Object? o = #C4]) → void; + @#C3 + external static method f({required core::Object? o}) → void; + @#C3 + external static method g({core::Object? o = #C5}) → void; + @#C3 + external static method h(self::A::h::T? t) → void; +} +static const field core::String z = #C1; +@#C3 +external static get a() → core::int; +@#C3 +external static set a(synthesized core::int #externalFieldValue) → void; +@#C3 +external static get b() → core::int; +@#C3 +external static set c(core::int value) → void; +@#C3 +static method d(core::Object? o) → void {} +@#C3 +external static method e(core::Object o) → void; +@#C3 +external static external-effect method f(core::Object? o) → void; +static method main() → void {} + +constants { + #C1 = "external-effect" + #C2 = null + #C3 = core::pragma {name:#C1, options:#C2} + #C4 = core::Object {} + #C5 = 3 +} + + +Constructor coverage from constants: +org-dartlang-testcase:///invalid_annotations.dart: +- pragma._ (from org-dartlang-sdk:///sdk/lib/core/annotations.dart) +- Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart) diff --git a/pkg/front_end/testcases/external_effect/invalid_annotations.dart.strong.modular.expect b/pkg/front_end/testcases/external_effect/invalid_annotations.dart.strong.modular.expect new file mode 100644 index 00000000000..20080188c76 --- /dev/null +++ b/pkg/front_end/testcases/external_effect/invalid_annotations.dart.strong.modular.expect @@ -0,0 +1,128 @@ +library; +// +// Problems in library: +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:6:14: Error: The 'external-effect' pragma can only be applied to methods. +// Try removing the pragma or applying it to a method. +// external int a; +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:9:18: Error: The 'external-effect' pragma can only be applied to methods. +// Try removing the pragma or applying it to a method. +// external int get b; +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:12:14: Error: The 'external-effect' pragma can only be applied to methods. +// Try removing the pragma or applying it to a method. +// external set c(int value); +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:15:6: Error: A function annotated with the 'external-effect' pragma must be external. +// Try making the function external. +// void d(Object? o) {} +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:18:15: Error: A function annotated with the 'external-effect' pragma must have the type 'void Function(Object?)' +// Try correcting the type of the function. +// external void e(Object o); +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:27:17: Error: A function annotated with the 'external-effect' pragma must be static. +// Try making the function static. +// external void a(Object? o); +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:30:23: Error: A function annotated with the 'external-effect' pragma must have the type 'void Function(Object?)' +// Try correcting the type of the function. +// external static int b(Object? o); +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:33:24: Error: A function annotated with the 'external-effect' pragma must have the type 'void Function(Object?)' +// Try correcting the type of the function. +// external static void c(Object? o, Object? x); +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:36:24: Error: A function annotated with the 'external-effect' pragma must have the type 'void Function(Object?)' +// Try correcting the type of the function. +// external static void d(int i); +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:39:24: Error: A function annotated with the 'external-effect' pragma must have the type 'void Function(Object?)' +// Try correcting the type of the function. +// external static void e([Object? o = const Object()]); +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:42:24: Error: A function annotated with the 'external-effect' pragma must have the type 'void Function(Object?)' +// Try correcting the type of the function. +// external static void f({required Object? o}); +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:45:24: Error: A function annotated with the 'external-effect' pragma must have the type 'void Function(Object?)' +// Try correcting the type of the function. +// external static void g({Object? o = 3}); +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:48:24: Error: A function annotated with the 'external-effect' pragma must have the type 'void Function(Object?)' +// Try correcting the type of the function. +// external static void h(T? t); +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:23:15: Error: The 'external-effect' pragma must be applied as a String literal. +// Try inlining the 'external-string' argument to the pragma. +// external void f(Object? o); +// ^ +// +import self as self; +import "dart:core" as core; + +class A extends core::Object { + synthetic constructor •() → self::A + : super core::Object::•() + ; + @#C3 + external method a(core::Object? o) → void; + @#C3 + external static method b(core::Object? o) → core::int; + @#C3 + external static method c(core::Object? o, core::Object? x) → void; + @#C3 + external static method d(core::int i) → void; + @#C3 + external static method e([core::Object? o = #C4]) → void; + @#C3 + external static method f({required core::Object? o}) → void; + @#C3 + external static method g({core::Object? o = #C5}) → void; + @#C3 + external static method h(self::A::h::T? t) → void; +} +static const field core::String z = #C1; +@#C3 +external static get a() → core::int; +@#C3 +external static set a(synthesized core::int #externalFieldValue) → void; +@#C3 +external static get b() → core::int; +@#C3 +external static set c(core::int value) → void; +@#C3 +static method d(core::Object? o) → void {} +@#C3 +external static method e(core::Object o) → void; +@#C3 +external static external-effect method f(core::Object? o) → void; +static method main() → void {} + +constants { + #C1 = "external-effect" + #C2 = null + #C3 = core::pragma {name:#C1, options:#C2} + #C4 = core::Object {} + #C5 = 3 +} + + +Constructor coverage from constants: +org-dartlang-testcase:///invalid_annotations.dart: +- pragma._ (from org-dartlang-sdk:///sdk/lib/core/annotations.dart) +- Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart) diff --git a/pkg/front_end/testcases/external_effect/invalid_annotations.dart.strong.outline.expect b/pkg/front_end/testcases/external_effect/invalid_annotations.dart.strong.outline.expect new file mode 100644 index 00000000000..10d4ef766fd --- /dev/null +++ b/pkg/front_end/testcases/external_effect/invalid_annotations.dart.strong.outline.expect @@ -0,0 +1,129 @@ +library; +// +// Problems in library: +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:6:14: Error: The 'external-effect' pragma can only be applied to methods. +// Try removing the pragma or applying it to a method. +// external int a; +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:9:18: Error: The 'external-effect' pragma can only be applied to methods. +// Try removing the pragma or applying it to a method. +// external int get b; +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:12:14: Error: The 'external-effect' pragma can only be applied to methods. +// Try removing the pragma or applying it to a method. +// external set c(int value); +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:15:6: Error: A function annotated with the 'external-effect' pragma must be external. +// Try making the function external. +// void d(Object? o) {} +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:18:15: Error: A function annotated with the 'external-effect' pragma must have the type 'void Function(Object?)' +// Try correcting the type of the function. +// external void e(Object o); +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:27:17: Error: A function annotated with the 'external-effect' pragma must be static. +// Try making the function static. +// external void a(Object? o); +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:30:23: Error: A function annotated with the 'external-effect' pragma must have the type 'void Function(Object?)' +// Try correcting the type of the function. +// external static int b(Object? o); +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:33:24: Error: A function annotated with the 'external-effect' pragma must have the type 'void Function(Object?)' +// Try correcting the type of the function. +// external static void c(Object? o, Object? x); +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:36:24: Error: A function annotated with the 'external-effect' pragma must have the type 'void Function(Object?)' +// Try correcting the type of the function. +// external static void d(int i); +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:39:24: Error: A function annotated with the 'external-effect' pragma must have the type 'void Function(Object?)' +// Try correcting the type of the function. +// external static void e([Object? o = const Object()]); +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:42:24: Error: A function annotated with the 'external-effect' pragma must have the type 'void Function(Object?)' +// Try correcting the type of the function. +// external static void f({required Object? o}); +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:45:24: Error: A function annotated with the 'external-effect' pragma must have the type 'void Function(Object?)' +// Try correcting the type of the function. +// external static void g({Object? o = 3}); +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:48:24: Error: A function annotated with the 'external-effect' pragma must have the type 'void Function(Object?)' +// Try correcting the type of the function. +// external static void h(T? t); +// ^ +// +import self as self; +import "dart:core" as core; + +class A extends core::Object { + synthetic constructor •() → self::A + ; + @/*original=core::pragma::•*/ const core::pragma::_("external-effect") + external method a(core::Object? o) → void; + @/*original=core::pragma::•*/ const core::pragma::_("external-effect") + external static method b(core::Object? o) → core::int; + @/*original=core::pragma::•*/ const core::pragma::_("external-effect") + external static method c(core::Object? o, core::Object? x) → void; + @/*original=core::pragma::•*/ const core::pragma::_("external-effect") + external static method d(core::int i) → void; + @/*original=core::pragma::•*/ const core::pragma::_("external-effect") + external static method e([has-declared-initializer core::Object? o]) → void; + @/*original=core::pragma::•*/ const core::pragma::_("external-effect") + external static method f({required core::Object? o}) → void; + @/*original=core::pragma::•*/ const core::pragma::_("external-effect") + external static method g({has-declared-initializer core::Object? o}) → void; + @/*original=core::pragma::•*/ const core::pragma::_("external-effect") + external static method h(self::A::h::T? t) → void; +} +static const field core::String z = "external-effect"; +@/*original=core::pragma::•*/ const core::pragma::_("external-effect") +external static get a() → core::int; +@/*original=core::pragma::•*/ const core::pragma::_("external-effect") +external static set a(synthesized core::int #externalFieldValue) → void; +@/*original=core::pragma::•*/ const core::pragma::_("external-effect") +external static get b() → core::int; +@/*original=core::pragma::•*/ const core::pragma::_("external-effect") +external static set c(core::int value) → void; +@/*original=core::pragma::•*/ const core::pragma::_("external-effect") +static method d(core::Object? o) → void + ; +@/*original=core::pragma::•*/ const core::pragma::_("external-effect") +external static method e(core::Object o) → void; +@/*original=core::pragma::•*/ const core::pragma::_(self::z) +external static method f(core::Object? o) → void; +static method main() → void + ; + + +Extra constant evaluation status: +Evaluated: RedirectingFactoryInvocation @ org-dartlang-testcase:///invalid_annotations.dart:26:4 -> InstanceConstant(const pragma{pragma.name: "external-effect", pragma.options: null}) +Evaluated: RedirectingFactoryInvocation @ org-dartlang-testcase:///invalid_annotations.dart:29:4 -> InstanceConstant(const pragma{pragma.name: "external-effect", pragma.options: null}) +Evaluated: RedirectingFactoryInvocation @ org-dartlang-testcase:///invalid_annotations.dart:32:4 -> InstanceConstant(const pragma{pragma.name: "external-effect", pragma.options: null}) +Evaluated: RedirectingFactoryInvocation @ org-dartlang-testcase:///invalid_annotations.dart:35:4 -> InstanceConstant(const pragma{pragma.name: "external-effect", pragma.options: null}) +Evaluated: RedirectingFactoryInvocation @ org-dartlang-testcase:///invalid_annotations.dart:38:4 -> InstanceConstant(const pragma{pragma.name: "external-effect", pragma.options: null}) +Evaluated: RedirectingFactoryInvocation @ org-dartlang-testcase:///invalid_annotations.dart:41:4 -> InstanceConstant(const pragma{pragma.name: "external-effect", pragma.options: null}) +Evaluated: RedirectingFactoryInvocation @ org-dartlang-testcase:///invalid_annotations.dart:44:4 -> InstanceConstant(const pragma{pragma.name: "external-effect", pragma.options: null}) +Evaluated: RedirectingFactoryInvocation @ org-dartlang-testcase:///invalid_annotations.dart:47:4 -> InstanceConstant(const pragma{pragma.name: "external-effect", pragma.options: null}) +Evaluated: RedirectingFactoryInvocation @ org-dartlang-testcase:///invalid_annotations.dart:5:2 -> InstanceConstant(const pragma{pragma.name: "external-effect", pragma.options: null}) +Evaluated: RedirectingFactoryInvocation @ org-dartlang-testcase:///invalid_annotations.dart:5:2 -> InstanceConstant(const pragma{pragma.name: "external-effect", pragma.options: null}) +Evaluated: RedirectingFactoryInvocation @ org-dartlang-testcase:///invalid_annotations.dart:8:2 -> InstanceConstant(const pragma{pragma.name: "external-effect", pragma.options: null}) +Evaluated: RedirectingFactoryInvocation @ org-dartlang-testcase:///invalid_annotations.dart:11:2 -> InstanceConstant(const pragma{pragma.name: "external-effect", pragma.options: null}) +Evaluated: RedirectingFactoryInvocation @ org-dartlang-testcase:///invalid_annotations.dart:14:2 -> InstanceConstant(const pragma{pragma.name: "external-effect", pragma.options: null}) +Evaluated: RedirectingFactoryInvocation @ org-dartlang-testcase:///invalid_annotations.dart:17:2 -> InstanceConstant(const pragma{pragma.name: "external-effect", pragma.options: null}) +Evaluated: RedirectingFactoryInvocation @ org-dartlang-testcase:///invalid_annotations.dart:22:2 -> InstanceConstant(const pragma{pragma.name: "external-effect", pragma.options: null}) +Extra constant evaluation: evaluated: 15, effectively constant: 15 diff --git a/pkg/front_end/testcases/external_effect/invalid_annotations.dart.strong.transformed.expect b/pkg/front_end/testcases/external_effect/invalid_annotations.dart.strong.transformed.expect new file mode 100644 index 00000000000..20080188c76 --- /dev/null +++ b/pkg/front_end/testcases/external_effect/invalid_annotations.dart.strong.transformed.expect @@ -0,0 +1,128 @@ +library; +// +// Problems in library: +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:6:14: Error: The 'external-effect' pragma can only be applied to methods. +// Try removing the pragma or applying it to a method. +// external int a; +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:9:18: Error: The 'external-effect' pragma can only be applied to methods. +// Try removing the pragma or applying it to a method. +// external int get b; +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:12:14: Error: The 'external-effect' pragma can only be applied to methods. +// Try removing the pragma or applying it to a method. +// external set c(int value); +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:15:6: Error: A function annotated with the 'external-effect' pragma must be external. +// Try making the function external. +// void d(Object? o) {} +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:18:15: Error: A function annotated with the 'external-effect' pragma must have the type 'void Function(Object?)' +// Try correcting the type of the function. +// external void e(Object o); +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:27:17: Error: A function annotated with the 'external-effect' pragma must be static. +// Try making the function static. +// external void a(Object? o); +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:30:23: Error: A function annotated with the 'external-effect' pragma must have the type 'void Function(Object?)' +// Try correcting the type of the function. +// external static int b(Object? o); +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:33:24: Error: A function annotated with the 'external-effect' pragma must have the type 'void Function(Object?)' +// Try correcting the type of the function. +// external static void c(Object? o, Object? x); +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:36:24: Error: A function annotated with the 'external-effect' pragma must have the type 'void Function(Object?)' +// Try correcting the type of the function. +// external static void d(int i); +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:39:24: Error: A function annotated with the 'external-effect' pragma must have the type 'void Function(Object?)' +// Try correcting the type of the function. +// external static void e([Object? o = const Object()]); +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:42:24: Error: A function annotated with the 'external-effect' pragma must have the type 'void Function(Object?)' +// Try correcting the type of the function. +// external static void f({required Object? o}); +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:45:24: Error: A function annotated with the 'external-effect' pragma must have the type 'void Function(Object?)' +// Try correcting the type of the function. +// external static void g({Object? o = 3}); +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:48:24: Error: A function annotated with the 'external-effect' pragma must have the type 'void Function(Object?)' +// Try correcting the type of the function. +// external static void h(T? t); +// ^ +// +// pkg/front_end/testcases/external_effect/invalid_annotations.dart:23:15: Error: The 'external-effect' pragma must be applied as a String literal. +// Try inlining the 'external-string' argument to the pragma. +// external void f(Object? o); +// ^ +// +import self as self; +import "dart:core" as core; + +class A extends core::Object { + synthetic constructor •() → self::A + : super core::Object::•() + ; + @#C3 + external method a(core::Object? o) → void; + @#C3 + external static method b(core::Object? o) → core::int; + @#C3 + external static method c(core::Object? o, core::Object? x) → void; + @#C3 + external static method d(core::int i) → void; + @#C3 + external static method e([core::Object? o = #C4]) → void; + @#C3 + external static method f({required core::Object? o}) → void; + @#C3 + external static method g({core::Object? o = #C5}) → void; + @#C3 + external static method h(self::A::h::T? t) → void; +} +static const field core::String z = #C1; +@#C3 +external static get a() → core::int; +@#C3 +external static set a(synthesized core::int #externalFieldValue) → void; +@#C3 +external static get b() → core::int; +@#C3 +external static set c(core::int value) → void; +@#C3 +static method d(core::Object? o) → void {} +@#C3 +external static method e(core::Object o) → void; +@#C3 +external static external-effect method f(core::Object? o) → void; +static method main() → void {} + +constants { + #C1 = "external-effect" + #C2 = null + #C3 = core::pragma {name:#C1, options:#C2} + #C4 = core::Object {} + #C5 = 3 +} + + +Constructor coverage from constants: +org-dartlang-testcase:///invalid_annotations.dart: +- pragma._ (from org-dartlang-sdk:///sdk/lib/core/annotations.dart) +- Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart) diff --git a/pkg/front_end/testcases/external_effect/invalid_annotations.dart.textual_outline.expect b/pkg/front_end/testcases/external_effect/invalid_annotations.dart.textual_outline.expect new file mode 100644 index 00000000000..dd8310ad3c9 --- /dev/null +++ b/pkg/front_end/testcases/external_effect/invalid_annotations.dart.textual_outline.expect @@ -0,0 +1,40 @@ +@pragma('external-effect') +external int a; + +@pragma('external-effect') +external int get b; + +@pragma('external-effect') +external set c(int value); + +@pragma('external-effect') +void d(Object? o) {} + +@pragma('external-effect') +external void e(Object o); + +const z = 'external-effect'; + +@pragma(z) +external void f(Object? o); + +class A { + @pragma('external-effect') + external void a(Object? o); + @pragma('external-effect') + external static int b(Object? o); + @pragma('external-effect') + external static void c(Object? o, Object? x); + @pragma('external-effect') + external static void d(int i); + @pragma('external-effect') + external static void e([Object? o = const Object()]); + @pragma('external-effect') + external static void f({required Object? o}); + @pragma('external-effect') + external static void g({Object? o = 3}); + @pragma('external-effect') + external static void h(T? t); +} + +void main() {} diff --git a/pkg/front_end/testcases/external_effect/invalid_annotations.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/external_effect/invalid_annotations.dart.textual_outline_modelled.expect new file mode 100644 index 00000000000..47487dcc2aa --- /dev/null +++ b/pkg/front_end/testcases/external_effect/invalid_annotations.dart.textual_outline_modelled.expect @@ -0,0 +1,40 @@ +class A { + @pragma('external-effect') + external static int b(Object? o); + @pragma('external-effect') + external static void c(Object? o, Object? x); + @pragma('external-effect') + external static void d(int i); + @pragma('external-effect') + external static void e([Object? o = const Object()]); + @pragma('external-effect') + external static void f({required Object? o}); + @pragma('external-effect') + external static void g({Object? o = 3}); + @pragma('external-effect') + external static void h(T? t); + @pragma('external-effect') + external void a(Object? o); +} + +const z = 'external-effect'; + +@pragma('external-effect') +external int a; + +@pragma('external-effect') +external int get b; + +@pragma('external-effect') +external set c(int value); + +@pragma('external-effect') +external void e(Object o); + +@pragma(z) +external void f(Object? o); + +@pragma('external-effect') +void d(Object? o) {} + +void main() {} diff --git a/pkg/front_end/testcases/external_effect/valid_annotations.dart b/pkg/front_end/testcases/external_effect/valid_annotations.dart new file mode 100644 index 00000000000..8568f7e2ac1 --- /dev/null +++ b/pkg/front_end/testcases/external_effect/valid_annotations.dart @@ -0,0 +1,13 @@ +// Copyright (c) 2026, 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. + +@pragma('external-effect') +external void foo(Object? o); + +class A { + @pragma('external-effect') + external static void foo(Object? o); +} + +void main() {} diff --git a/pkg/front_end/testcases/external_effect/valid_annotations.dart.strong.expect b/pkg/front_end/testcases/external_effect/valid_annotations.dart.strong.expect new file mode 100644 index 00000000000..ec2ac16bc96 --- /dev/null +++ b/pkg/front_end/testcases/external_effect/valid_annotations.dart.strong.expect @@ -0,0 +1,26 @@ +library; +import self as self; +import "dart:core" as core; + +class A extends core::Object { + synthetic constructor •() → self::A + : super core::Object::•() + ; + @#C3 + external static external-effect method foo(core::Object? o) → void; +} +@#C3 +external static external-effect method foo(core::Object? o) → void; +static method main() → void {} + +constants { + #C1 = "external-effect" + #C2 = null + #C3 = core::pragma {name:#C1, options:#C2} +} + + +Constructor coverage from constants: +org-dartlang-testcase:///valid_annotations.dart: +- pragma._ (from org-dartlang-sdk:///sdk/lib/core/annotations.dart) +- Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart) diff --git a/pkg/front_end/testcases/external_effect/valid_annotations.dart.strong.modular.expect b/pkg/front_end/testcases/external_effect/valid_annotations.dart.strong.modular.expect new file mode 100644 index 00000000000..ec2ac16bc96 --- /dev/null +++ b/pkg/front_end/testcases/external_effect/valid_annotations.dart.strong.modular.expect @@ -0,0 +1,26 @@ +library; +import self as self; +import "dart:core" as core; + +class A extends core::Object { + synthetic constructor •() → self::A + : super core::Object::•() + ; + @#C3 + external static external-effect method foo(core::Object? o) → void; +} +@#C3 +external static external-effect method foo(core::Object? o) → void; +static method main() → void {} + +constants { + #C1 = "external-effect" + #C2 = null + #C3 = core::pragma {name:#C1, options:#C2} +} + + +Constructor coverage from constants: +org-dartlang-testcase:///valid_annotations.dart: +- pragma._ (from org-dartlang-sdk:///sdk/lib/core/annotations.dart) +- Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart) diff --git a/pkg/front_end/testcases/external_effect/valid_annotations.dart.strong.outline.expect b/pkg/front_end/testcases/external_effect/valid_annotations.dart.strong.outline.expect new file mode 100644 index 00000000000..0d575aa21c8 --- /dev/null +++ b/pkg/front_end/testcases/external_effect/valid_annotations.dart.strong.outline.expect @@ -0,0 +1,20 @@ +library; +import self as self; +import "dart:core" as core; + +class A extends core::Object { + synthetic constructor •() → self::A + ; + @/*original=core::pragma::•*/ const core::pragma::_("external-effect") + external static external-effect method foo(core::Object? o) → void; +} +@/*original=core::pragma::•*/ const core::pragma::_("external-effect") +external static external-effect method foo(core::Object? o) → void; +static method main() → void + ; + + +Extra constant evaluation status: +Evaluated: RedirectingFactoryInvocation @ org-dartlang-testcase:///valid_annotations.dart:9:4 -> InstanceConstant(const pragma{pragma.name: "external-effect", pragma.options: null}) +Evaluated: RedirectingFactoryInvocation @ org-dartlang-testcase:///valid_annotations.dart:5:2 -> InstanceConstant(const pragma{pragma.name: "external-effect", pragma.options: null}) +Extra constant evaluation: evaluated: 2, effectively constant: 2 diff --git a/pkg/front_end/testcases/external_effect/valid_annotations.dart.strong.transformed.expect b/pkg/front_end/testcases/external_effect/valid_annotations.dart.strong.transformed.expect new file mode 100644 index 00000000000..ec2ac16bc96 --- /dev/null +++ b/pkg/front_end/testcases/external_effect/valid_annotations.dart.strong.transformed.expect @@ -0,0 +1,26 @@ +library; +import self as self; +import "dart:core" as core; + +class A extends core::Object { + synthetic constructor •() → self::A + : super core::Object::•() + ; + @#C3 + external static external-effect method foo(core::Object? o) → void; +} +@#C3 +external static external-effect method foo(core::Object? o) → void; +static method main() → void {} + +constants { + #C1 = "external-effect" + #C2 = null + #C3 = core::pragma {name:#C1, options:#C2} +} + + +Constructor coverage from constants: +org-dartlang-testcase:///valid_annotations.dart: +- pragma._ (from org-dartlang-sdk:///sdk/lib/core/annotations.dart) +- Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart) diff --git a/pkg/front_end/testcases/external_effect/valid_annotations.dart.textual_outline.expect b/pkg/front_end/testcases/external_effect/valid_annotations.dart.textual_outline.expect new file mode 100644 index 00000000000..fb348535d87 --- /dev/null +++ b/pkg/front_end/testcases/external_effect/valid_annotations.dart.textual_outline.expect @@ -0,0 +1,9 @@ +@pragma('external-effect') +external void foo(Object? o); + +class A { + @pragma('external-effect') + external static void foo(Object? o); +} + +void main() {} diff --git a/pkg/front_end/testcases/external_effect/valid_annotations.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/external_effect/valid_annotations.dart.textual_outline_modelled.expect new file mode 100644 index 00000000000..8d5f1a93643 --- /dev/null +++ b/pkg/front_end/testcases/external_effect/valid_annotations.dart.textual_outline_modelled.expect @@ -0,0 +1,9 @@ +class A { + @pragma('external-effect') + external static void foo(Object? o); +} + +@pragma('external-effect') +external void foo(Object? o); + +void main() {} diff --git a/pkg/kernel/binary.md b/pkg/kernel/binary.md index eb02bc4dc82..41a934e8273 100644 --- a/pkg/kernel/binary.md +++ b/pkg/kernel/binary.md @@ -457,7 +457,7 @@ type Procedure extends Member { UInt flags (isStatic, isAbstract, isExternal, isConst, isExtensionMember, isSynthetic, isInternalImplementation, isExtensionTypeMember, hasWeakTearoffReferencePragma, IsLoweredLateField, - isErroneous); + isErroneous, isExternalEffect); Name name; List annotations; MemberReference stubTarget; // May be NullReference. diff --git a/pkg/kernel/lib/src/ast/members.dart b/pkg/kernel/lib/src/ast/members.dart index 6315e2cac37..76d401ef48a 100644 --- a/pkg/kernel/lib/src/ast/members.dart +++ b/pkg/kernel/lib/src/ast/members.dart @@ -1092,6 +1092,7 @@ class Procedure extends Member implements GenericFunction { static const int FlagExtensionTypeMember = 1 << 7; static const int FlagHasWeakTearoffReferencePragma = 1 << 8; static const int FlagErroneous = 1 << 9; + static const int FlagHasExternalEffectPragma = 1 << 10; bool get isStatic => flags & FlagStatic != 0; @@ -1243,6 +1244,14 @@ class Procedure extends Member implements GenericFunction { : (flags & ~FlagHasWeakTearoffReferencePragma); } + bool get hasExternalEffectPragma => flags & FlagHasExternalEffectPragma != 0; + + void set hasExternalEffectPragma(bool value) { + flags = value + ? (flags | FlagHasExternalEffectPragma) + : (flags & ~FlagHasExternalEffectPragma); + } + @override bool get isErroneous => flags & FlagErroneous != 0; diff --git a/pkg/kernel/lib/text/ast_to_text.dart b/pkg/kernel/lib/text/ast_to_text.dart index e0010ae8762..44a73736366 100644 --- a/pkg/kernel/lib/text/ast_to_text.dart +++ b/pkg/kernel/lib/text/ast_to_text.dart @@ -1380,6 +1380,7 @@ class Printer extends VisitorDefault with VisitorVoidMixin { writeModifier(node.isSynthetic, 'synthetic'); writeModifier(node.isConst, 'const'); writeModifier(node.isErroneous, 'erroneous'); + writeModifier(node.hasExternalEffectPragma, 'external-effect'); switch (node.stubKind) { case ProcedureStubKind.Regular: case ProcedureStubKind.AbstractForwardingStub: diff --git a/runtime/docs/compiler/pragmas_recognized_by_compiler.md b/runtime/docs/compiler/pragmas_recognized_by_compiler.md index 72bacff43c4..81339c1327a 100644 --- a/runtime/docs/compiler/pragmas_recognized_by_compiler.md +++ b/runtime/docs/compiler/pragmas_recognized_by_compiler.md @@ -148,3 +148,12 @@ corresponding to the category the recognized method belongs to, as defined in The pragmas must match exactly the set of recognized methods. This enables kernel-level analyses and optimizations to query whether a method is recognized by the VM. The correspondence is checked when running in debug mode. + +### Declaring an external effect method + +```dart +@pragma('external-effect') +external void effect(Object? o); +``` + +Declares a special static external method `effect` which the compiler will treat as live code when performing any analysis of the program. For example, a type referenced from this call that would otherwise be tree-shaken will no longer be tree-shaken. To reduce code size the call itself (and its arguments) are dropped in the compiled output though. diff --git a/runtime/docs/pragmas.md b/runtime/docs/pragmas.md index f0da7fd323e..6260b46f0e1 100644 --- a/runtime/docs/pragmas.md +++ b/runtime/docs/pragmas.md @@ -22,6 +22,7 @@ These pragmas are part of the VM's API and are safe for use in external code. | `vm:deeply-immutable` | [Specifying a class and all its subtypes are deeply immutable](deeply_immutable.md) | | `vm:align-loops` | Tells compiler to align all loop headers inside the function to an architecture specific boundary: currently 32 bytes on X64 and ARM64 (except Apple Silicon, which explicitly discourages aligning branch targets) | | `vm:no-sanitize-thread` | Disable ThreadSanitizer instrumentation | +| `external-effect` | Declares a static method which will be treated as live code when performing any analysis of the program. The call itself (and its arguments) are then dropped from the running program. ## Unsafe pragmas for general use diff --git a/runtime/lib/object.cc b/runtime/lib/object.cc index 39d530beeb9..13a0bd3b677 100644 --- a/runtime/lib/object.cc +++ b/runtime/lib/object.cc @@ -305,10 +305,6 @@ DEFINE_NATIVE_ENTRY(Internal_unsafeCast, 0, 1) { return arguments->NativeArgAt(0); } -DEFINE_NATIVE_ENTRY(Internal_nativeEffect, 0, 1) { - UNREACHABLE(); -} - DEFINE_NATIVE_ENTRY(Internal_collectAllGarbage, 0, 0) { auto isolate_group = thread->isolate_group(); isolate_group->heap()->CollectAllGarbage(GCReason::kDebugging, diff --git a/runtime/vm/bootstrap_natives.h b/runtime/vm/bootstrap_natives.h index c6bfb48c430..ab38f4db492 100644 --- a/runtime/vm/bootstrap_natives.h +++ b/runtime/vm/bootstrap_natives.h @@ -265,7 +265,6 @@ namespace dart { V(GrowableList_setLength, 2) \ V(GrowableList_setData, 2) \ V(Internal_unsafeCast, 1) \ - V(Internal_nativeEffect, 1) \ V(Internal_collectAllGarbage, 0) \ V(Internal_makeListFixedLength, 1) \ V(Internal_makeFixedListUnmodifiable, 1) \ diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc index 5d8221ccb54..c259aacfb5f 100644 --- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc +++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc @@ -3426,10 +3426,13 @@ Fragment StreamingFlowGraphBuilder::BuildStaticInvocation(TokenPosition* p) { return BuildCachableIdempotentCall(position, target); } + if (target.IsExternalEffect()) { + // AOT kernels will already have external effect calls removed by TFA. + return BuildExternalEffect(); + } + const auto recognized_kind = target.recognized_kind(); switch (recognized_kind) { - case MethodRecognizer::kNativeEffect: - return BuildNativeEffect(); case MethodRecognizer::kReachabilityFence: return BuildReachabilityFence(); case MethodRecognizer::kFfiCall: @@ -6003,7 +6006,7 @@ Fragment StreamingFlowGraphBuilder::BuildFunctionNode( return instructions; } -Fragment StreamingFlowGraphBuilder::BuildNativeEffect() { +Fragment StreamingFlowGraphBuilder::BuildExternalEffect() { const intptr_t argc = ReadUInt(); // Read argument count. ASSERT(argc == 1); // Native side effect to ignore. const intptr_t list_length = ReadListLength(); // Read types list length. diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h index 9e903a9b7a0..871563e2d3f 100644 --- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h +++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h @@ -375,8 +375,8 @@ class StreamingFlowGraphBuilder : public KernelReaderHelper { Fragment BuildFunctionNode(intptr_t local_function_id, intptr_t func_decl_offset); - // Build flow graph for '_nativeEffect'. - Fragment BuildNativeEffect(); + // Build flow graph for 'external-effect' methods. + Fragment BuildExternalEffect(); // Build the call-site manually, to avoid doing initialization checks // for late fields. diff --git a/runtime/vm/compiler/recognized_methods_list.h b/runtime/vm/compiler/recognized_methods_list.h index d530b7f56d3..7e0e86116f3 100644 --- a/runtime/vm/compiler/recognized_methods_list.h +++ b/runtime/vm/compiler/recognized_methods_list.h @@ -191,7 +191,6 @@ namespace dart { V(FfiLibrary, ::, _checkNotDeeplyImmutable, CheckNotDeeplyImmutable, \ 0x34e4da90) \ V(InternalLibrary, ClassID, getID, ClassIDgetID, 0xdc6e70ca) \ - V(InternalLibrary, ::, _nativeEffect, NativeEffect, 0x61c2f399) \ V(InternalLibrary, ::, reachabilityFence, ReachabilityFence, 0x72f213bf) \ V(InternalLibrary, ::, get:has63BitSmis, Has63BitSmis, 0xf5fe3f31) \ V(InternalLibrary, ::, copyRangeFromUint8ListToOneByteString, \ diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc index 99d308beb32..b39e22e576d 100644 --- a/runtime/vm/object.cc +++ b/runtime/vm/object.cc @@ -9601,6 +9601,13 @@ bool Function::IsCachableIdempotent() const { return InVmTests(*this); } +bool Function::IsExternalEffect() const { + if (!has_pragma()) return false; + + return Library::FindPragma(dart::Thread::Current(), /*only_core=*/false, + *this, Symbols::external_effect()); +} + bool Function::IsFfiCallClosure() const { if (!IsNonImplicitClosureFunction()) return false; if (!has_pragma()) return false; diff --git a/runtime/vm/object.h b/runtime/vm/object.h index c4c131400ad..bc58ae9a9ee 100644 --- a/runtime/vm/object.h +++ b/runtime/vm/object.h @@ -3699,6 +3699,10 @@ class Function : public Object { bool IsCachableIdempotent() const; + // Whether this function represents an external effect and should be dropped + // from codegen. + bool IsExternalEffect() const; + // Whether this function's |recognized_kind| requires optimization. bool RecognizedKindForceOptimize() const; diff --git a/runtime/vm/symbol_list.h b/runtime/vm/symbol_list.h index d610bea7702..2391059bf74 100644 --- a/runtime/vm/symbol_list.h +++ b/runtime/vm/symbol_list.h @@ -525,6 +525,7 @@ namespace dart { V(dyn_module_implicitly_callable, "dyn-module:implicitly-callable") \ V(dyn_module_can_be_used_as_type, "dyn-module:can-be-used-as-type") \ V(executable, "executable") \ + V(external_effect, "external-effect") \ V(get, "get") \ V(isLeaf, "isLeaf") \ V(isPaused, "isPaused") \ diff --git a/sdk/lib/_internal/vm/lib/internal_patch.dart b/sdk/lib/_internal/vm/lib/internal_patch.dart index d13d7f8ed31..3454107fc96 100644 --- a/sdk/lib/_internal/vm/lib/internal_patch.dart +++ b/sdk/lib/_internal/vm/lib/internal_patch.dart @@ -171,9 +171,8 @@ external void reachabilityFence(Object? object); // This function can be used to encode native side effects. // // The function call and it's argument are removed in flow graph construction. -@pragma("vm:recognized", "other") -@pragma("vm:external-name", "Internal_nativeEffect") -external void _nativeEffect(Object object); +@pragma("external-effect") +external void _nativeEffect(Object? object); // Collection of functions which should only be used for testing purposes. abstract class VMInternalsForTesting { diff --git a/sdk/lib/_internal/wasm/lib/internal_patch.dart b/sdk/lib/_internal/wasm/lib/internal_patch.dart index eaddf9ada36..36ba55157f2 100644 --- a/sdk/lib/_internal/wasm/lib/internal_patch.dart +++ b/sdk/lib/_internal/wasm/lib/internal_patch.dart @@ -66,8 +66,8 @@ void reachabilityFence(Object? object) {} external void exportWasmFunction(Function object); // This function can be used to encode native side effects. -@pragma("wasm:intrinsic") -external void _nativeEffect(Object object); +@pragma("external-effect") +external void _nativeEffect(Object? object); // Thomas Wang 64-bit mix. // https://gist.github.com/badboy/6267743 diff --git a/tests/language/method/external_effect_test.dart b/tests/language/method/external_effect_test.dart new file mode 100644 index 00000000000..71d72f94eac --- /dev/null +++ b/tests/language/method/external_effect_test.dart @@ -0,0 +1,35 @@ +// Copyright (c) 2026, 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"; + +@pragma('external-effect') +external void externalEffect(Object? o); + +void noExternalEffect(Object? o) {} + +List used = []; + +Null use(int o) { + used.add(o); + return null; +} + +const Object constObj = Object(); + +Null useConstObject() { + used.add(constObj); + return null; +} + +void main() { + externalEffect(use(3)); + externalEffect(useConstObject()); + Expect.isTrue(used.isEmpty); + noExternalEffect(use(4)); + noExternalEffect(useConstObject()); + Expect.equals(used.length, 2); + Expect.equals(used[0], 4); + Expect.equals(used[1], constObj); +}