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); +}