diff --git a/pkg/front_end/lib/src/kernel/body_builder.dart b/pkg/front_end/lib/src/kernel/body_builder.dart index bbeaf8c5f3c..b651ed125de 100644 --- a/pkg/front_end/lib/src/kernel/body_builder.dart +++ b/pkg/front_end/lib/src/kernel/body_builder.dart @@ -8236,13 +8236,22 @@ class BodyBuilderImpl extends StackListenerImpl lvalue.isConst = false; } } else { - VariableDeclaration variable = elements.syntheticVariableDeclaration = - forest.createVariableDeclaration( - offsetForToken(forToken), - null, - isFinal: true, - isSynthesized: true, - ); + ExpressionVariable astVariable = isClosureContextLoweringEnabled + ? new SyntheticVariable(type: const DynamicType()) + : forest.createVariableDeclaration( + offsetForToken(forToken), + null, + isFinal: true, + isSynthesized: true, + ); + + ExpressionVariable variable = elements.syntheticVariableDeclaration = + isClosureContextLoweringEnabled + ? new InternalSyntheticVariable( + astVariable: astVariable as SyntheticVariable, + isImplicitlyTyped: false, + ) + : astVariable; if (lvalue is Generator) { /// We are in this case, where `lvalue` isn't a [VariableDeclaration]: /// @@ -8255,7 +8264,7 @@ class BodyBuilderImpl extends StackListenerImpl /// body; /// } elements.syntheticAssignment = lvalue.buildAssignment( - new VariableGet(variable)..fileOffset = inToken.offset, + new VariableGet(astVariable)..fileOffset = inToken.offset, voidContext: true, ); } else if (lvalue is Pattern) { diff --git a/pkg/front_end/lib/src/kernel/internal_ast.dart b/pkg/front_end/lib/src/kernel/internal_ast.dart index 4a22a4c9073..e5b5246dc0a 100644 --- a/pkg/front_end/lib/src/kernel/internal_ast.dart +++ b/pkg/front_end/lib/src/kernel/internal_ast.dart @@ -1334,6 +1334,48 @@ class InternalCatchVariable extends TreeNode String get catchVariableName => astVariable.catchVariableName; } +class InternalSyntheticVariable extends TreeNode + with InternalExpressionVariableMixin, DelegatingVariableMixin + implements SyntheticVariable, InternalExpressionVariable { + @override + SyntheticVariable astVariable; + + @override + final bool forSyntheticToken; + + @override + final bool isImplicitlyTyped; + + @override + final bool isLocalFunction; + + InternalSyntheticVariable({ + required this.astVariable, + required this.isImplicitlyTyped, + this.forSyntheticToken = false, + this.isLocalFunction = false, + }); + + @override + String toString() { + return "InternalSyntheticVariable(${toStringInternal()})"; + } + + @override + // Coverage-ignore(suite): Not run. + void toTextInternal(AstPrinter printer) { + printer.writeExpressionVariable(astVariable); + List modifiers = [ + if (forSyntheticToken) "forSyntheticToken", + if (isImplicitlyTyped) "isImplicitlyTyped", + if (isLocalFunction) "isLocalFunction", + ]; + if (modifiers.isNotEmpty) { + printer.write("[${modifiers.join(",")}]"); + } + } +} + mixin DelegatingVariableMixin on InternalExpressionVariableMixin implements InternalExpressionVariable { @override diff --git a/pkg/front_end/testcases/closure_context_lowering/synthetic_variables.dart b/pkg/front_end/testcases/closure_context_lowering/synthetic_variables.dart new file mode 100644 index 00000000000..64c02ec1431 --- /dev/null +++ b/pkg/front_end/testcases/closure_context_lowering/synthetic_variables.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. + +test(List list) { + String s = ""; + for (s in list) { + if (s.isNotEmpty) { + return s; + } + } + return s; +} diff --git a/pkg/front_end/testcases/closure_context_lowering/synthetic_variables.dart.strong.expect b/pkg/front_end/testcases/closure_context_lowering/synthetic_variables.dart.strong.expect new file mode 100644 index 00000000000..c4617917529 --- /dev/null +++ b/pkg/front_end/testcases/closure_context_lowering/synthetic_variables.dart.strong.expect @@ -0,0 +1,18 @@ +library; +import self as self; +import "dart:core" as core; + +static method test(positional-parameter list) → dynamic/* scope=[ + #ctx1: not-captured VariableContext([ + positional-parameter list; + ]), +] */ { + s := ""; + for (synthetic-variable #t1 in list) { + s = #t1; + if(s.{core::String::isNotEmpty}{core::bool}) { + return s; + } + } + return s; +} diff --git a/pkg/front_end/testcases/closure_context_lowering/synthetic_variables.dart.strong.modular.expect b/pkg/front_end/testcases/closure_context_lowering/synthetic_variables.dart.strong.modular.expect new file mode 100644 index 00000000000..c4617917529 --- /dev/null +++ b/pkg/front_end/testcases/closure_context_lowering/synthetic_variables.dart.strong.modular.expect @@ -0,0 +1,18 @@ +library; +import self as self; +import "dart:core" as core; + +static method test(positional-parameter list) → dynamic/* scope=[ + #ctx1: not-captured VariableContext([ + positional-parameter list; + ]), +] */ { + s := ""; + for (synthetic-variable #t1 in list) { + s = #t1; + if(s.{core::String::isNotEmpty}{core::bool}) { + return s; + } + } + return s; +} diff --git a/pkg/front_end/testcases/closure_context_lowering/synthetic_variables.dart.strong.outline.expect b/pkg/front_end/testcases/closure_context_lowering/synthetic_variables.dart.strong.outline.expect new file mode 100644 index 00000000000..8172b72c69d --- /dev/null +++ b/pkg/front_end/testcases/closure_context_lowering/synthetic_variables.dart.strong.outline.expect @@ -0,0 +1,5 @@ +library; +import self as self; + +static method test(positional-parameter list) → dynamic + ; diff --git a/pkg/front_end/testcases/closure_context_lowering/synthetic_variables.dart.textual_outline.expect b/pkg/front_end/testcases/closure_context_lowering/synthetic_variables.dart.textual_outline.expect new file mode 100644 index 00000000000..6b97f08994f --- /dev/null +++ b/pkg/front_end/testcases/closure_context_lowering/synthetic_variables.dart.textual_outline.expect @@ -0,0 +1 @@ +test(List list) {} diff --git a/pkg/front_end/testcases/closure_context_lowering/synthetic_variables.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/closure_context_lowering/synthetic_variables.dart.textual_outline_modelled.expect new file mode 100644 index 00000000000..6b97f08994f --- /dev/null +++ b/pkg/front_end/testcases/closure_context_lowering/synthetic_variables.dart.textual_outline_modelled.expect @@ -0,0 +1 @@ +test(List list) {} diff --git a/pkg/front_end/testcases/modular.status b/pkg/front_end/testcases/modular.status index 014cb003d89..5fd6639bb0c 100644 --- a/pkg/front_end/testcases/modular.status +++ b/pkg/front_end/testcases/modular.status @@ -30,3 +30,4 @@ closure_context_lowering/foo48: Crash closure_context_lowering/assert_captured_variables: Crash closure_context_lowering/late_variable_initializers: Crash closure_context_lowering/catch_variables: Crash +closure_context_lowering/synthetic_variables: Crash diff --git a/pkg/front_end/testcases/outline.status b/pkg/front_end/testcases/outline.status index 08bf04bdade..1ea1f200b68 100644 --- a/pkg/front_end/testcases/outline.status +++ b/pkg/front_end/testcases/outline.status @@ -27,3 +27,4 @@ closure_context_lowering/foo45: ExpectationFileMismatchSerialized closure_context_lowering/foo48: ExpectationFileMismatchSerialized closure_context_lowering/assert_captured_variables: ExpectationFileMismatchSerialized closure_context_lowering/late_variable_initializers: ExpectationFileMismatchSerialized +closure_context_lowering/synthetic_variables: ExpectationFileMismatchSerialized diff --git a/pkg/front_end/testcases/strong.status b/pkg/front_end/testcases/strong.status index 1da9b5351f0..2a3f2f3535b 100644 --- a/pkg/front_end/testcases/strong.status +++ b/pkg/front_end/testcases/strong.status @@ -268,3 +268,4 @@ closure_context_lowering/foo48: Crash closure_context_lowering/assert_captured_variables: Crash closure_context_lowering/late_variable_initializers: Crash closure_context_lowering/catch_variables: Crash +closure_context_lowering/synthetic_variables: Crash diff --git a/pkg/kernel/lib/src/ast/variables.dart b/pkg/kernel/lib/src/ast/variables.dart index 4eb820cb1aa..744ca61b182 100644 --- a/pkg/kernel/lib/src/ast/variables.dart +++ b/pkg/kernel/lib/src/ast/variables.dart @@ -1543,9 +1543,7 @@ class SyntheticVariable extends ExpressionVariable { } @override - bool get isSynthesized { - throw new UnsupportedError("${this.runtimeType}"); - } + bool get isSynthesized => true; @override void set isSynthesized(bool value) { @@ -1553,9 +1551,7 @@ class SyntheticVariable extends ExpressionVariable { } @override - bool get isConst { - throw new UnsupportedError("${this.runtimeType}"); - } + bool get isConst => false; @override void set isConst(bool value) { @@ -1624,7 +1620,7 @@ class SyntheticVariable extends ExpressionVariable { bool get hasIsFinal => true; @override - bool get hasIsConst => false; + bool get hasIsConst => true; @override bool get hasIsLate => false; @@ -1633,7 +1629,7 @@ class SyntheticVariable extends ExpressionVariable { bool get hasIsInitializingFormal => false; @override - bool get hasIsSynthesized => false; + bool get hasIsSynthesized => true; @override bool get hasIsHoisted => true; diff --git a/pkg/kernel/lib/text/ast_to_text.dart b/pkg/kernel/lib/text/ast_to_text.dart index 8c53c45fc67..a0f164a855f 100644 --- a/pkg/kernel/lib/text/ast_to_text.dart +++ b/pkg/kernel/lib/text/ast_to_text.dart @@ -138,7 +138,7 @@ String componentToString(Component node) { class NameSystem { final Namer variables = - new NormalNamer('#t'); + new NormalNamer('#t'); final Namer libraries = new NormalNamer('#lib'); final Namer typeParameters = new NormalNamer('#T');