[cfe] Introduce InternalSyntheticVariable internal AST node

The new node is generated in for-in loops with synthetic variables.

Part of https://github.com/dart-lang/sdk/issues/61572

Change-Id: Icaa63097a145d07cdcfe21edc0487a47f90da8dd
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/487060
Commit-Queue: Chloe Stefantsova <cstefantsova@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
This commit is contained in:
Chloe Stefantsova
2026-03-12 01:51:31 -07:00
committed by Commit Queue
parent 39870102e6
commit ea1f4d474e
13 changed files with 123 additions and 17 deletions
+17 -8
View File
@@ -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) {
@@ -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<String> modifiers = [
if (forSyntheticToken) "forSyntheticToken",
if (isImplicitlyTyped) "isImplicitlyTyped",
if (isLocalFunction) "isLocalFunction",
];
if (modifiers.isNotEmpty) {
printer.write("[${modifiers.join(",")}]");
}
}
}
mixin DelegatingVariableMixin on InternalExpressionVariableMixin
implements InternalExpressionVariable {
@override
@@ -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<String> list) {
String s = "";
for (s in list) {
if (s.isNotEmpty) {
return s;
}
}
return s;
}
@@ -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;
}
@@ -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;
}
@@ -0,0 +1,5 @@
library;
import self as self;
static method test(positional-parameter list) → dynamic
;
@@ -0,0 +1 @@
test(List<String> list) {}
+1
View File
@@ -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
+1
View File
@@ -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
+1
View File
@@ -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
+4 -8
View File
@@ -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;
+1 -1
View File
@@ -138,7 +138,7 @@ String componentToString(Component node) {
class NameSystem {
final Namer<ExpressionVariable> variables =
new NormalNamer<VariableDeclaration>('#t');
new NormalNamer<ExpressionVariable>('#t');
final Namer<Reference> libraries = new NormalNamer<Reference>('#lib');
final Namer<TypeParameter> typeParameters =
new NormalNamer<TypeParameter>('#T');