[cfe][Contexts] Flatten variables hierarchy

This removes the LegacyVariableDeclaration and VariableInitializationBase classes to create a simpler hierarchy. This is done preparation for splitting variables from statements.

TEST=existing

Change-Id: If29c9eee1e3d8bed819ce53ed178c3efa3bffaea
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/501961
Reviewed-by: Alexander Markov <alexmarkov@google.com>
Commit-Queue: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Chloe Stefantsova <cstefantsova@google.com>
This commit is contained in:
Johnni Winther
2026-05-11 00:38:41 -07:00
committed by dart-scoped@luci-project-accounts.iam.gserviceaccount.com
parent 4736248967
commit c2787e1cf4
21 changed files with 171 additions and 312 deletions
+80 -229
View File
@@ -589,7 +589,7 @@ class ForStatement extends Statement implements LoopStatement, ScopeProvider {
@override
void transformOrRemoveChildren(RemovingTransformer v) {
v.transformVariableInitializationList(variables, this);
v.transformVariableDeclarationList(variables, this);
if (condition != null) {
condition = v.transformOrRemoveExpression(condition!);
condition?.parent = this;
@@ -1458,188 +1458,6 @@ class YieldStatement extends Statement {
}
}
abstract interface class LegacyVariableDeclaration
implements
Annotatable,
Statement,
VariableDeclaration,
VariableInitializationBase {
/// The name of the variable as provided in the source code.
///
/// The name of a variable can only be omitted if the variable is synthesized.
/// Otherwise, its name is as provided in the source code.
@override
abstract String? name;
/// The declared or inferred type of the variable.
@override
abstract DartType type;
/// For locals, this is the initial value.
/// For parameters, this is the default value.
///
/// Should be null in other cases.
@override
abstract Expression? initializer;
@override
abstract int flags;
/// Whether the parameter is declared with the `covariant` keyword.
@override
abstract bool isCovariantByDeclaration;
/// If this [LegacyVariableDeclaration] is a parameter of a method, indicates
/// whether the method implementation needs to contain a runtime type check to
/// deal with generic covariance.
///
/// When `true`, runtime checks may need to be performed.
@override
abstract bool isCovariantByClass;
/// Whether the variable is declared with the `const` keyword.
@override
abstract bool isConst;
/// Whether the variable is declared with the `late` keyword.
///
/// The `late` modifier is only supported on local variables and not on
/// parameters.
@override
abstract bool isLate;
/// Whether the variable is declared with the `final` keyword.
@override
abstract bool isFinal;
/// Whether the parameter is declared with the `required` keyword.
///
/// The `required` modifier is only supported on named parameters and not on
/// positional parameters and local variables.
@override
abstract bool isRequired;
/// Whether the variable is part of a lowering.
///
/// If a variable is part of a lowering its name may be synthesized so that it
/// doesn't reflect the name used in the source code and might not have a
/// one-to-one correspondence with the variable in the source.
///
/// Lowering is used for instance of encoding of 'this' in extension instance
/// members and encoding of late locals.
@override
abstract bool isLowered;
/// Whether the declaration of this variable is has been moved to an earlier
/// source location.
///
/// This is for instance the case for variables declared in a pattern, where
/// the lowering requires the variable to be declared before the expression
/// that performs that matching in which its initialization occurs.
@override
abstract bool isHoisted;
/// Whether this variable is synthesized, that is, it is _not_ declared in
/// the source code.
///
/// The name of a variable can only be omitted if the variable is synthesized.
/// Otherwise, its name is as provided in the source code.
@override
abstract bool isSynthesized;
/// Whether the variable is assignable.
///
/// This is `true` if the variable is neither constant nor final, or if it
/// is late final without an initializer.
@override
bool get isAssignable;
/// Whether the variable is declared as an initializing formal parameter of
/// a constructor.
@informative
@override
abstract bool isInitializingFormal;
/// Whether the variable is declared as a super initializing formal parameter
/// of a constructor.
@informative
@override
abstract bool isSuperInitializingFormal;
@informative
@override
abstract bool isErroneouslyInitialized;
/// Whether the variable has an initializer, either by declaration or copied
/// from an original declaration.
///
/// Note that the variable might have a synthesized initializer expression,
/// so `hasDeclaredInitializer == false` doesn't imply `initializer == null`.
/// For instance, for duplicate variable names, an invalid expression is set
/// as the initializer of the second variable.
@override
abstract bool hasDeclaredInitializer;
/// Whether this variable is a wildcard variable.
///
/// Wildcard variables have the name `_`.
@override
abstract bool isWildcard;
/// Offset of the equals sign in the source file it comes from.
///
/// Valid values are from 0 and up, or -1 ([TreeNode.noOffset])
/// if the equals sign offset is not available (e.g. if not initialized)
/// (this is the default if none is specifically set).
@override
abstract int fileEqualsOffset;
/// Offset of the declaration, set and used when writing the binary.
@override
abstract int binaryOffsetNoTag;
/// List of metadata annotations on the variable declaration.
///
/// This defaults to an immutable empty list. Use [addAnnotation] to add
/// annotations if needed.
@override
abstract List<Expression> annotations;
@override
void clearAnnotations();
factory LegacyVariableDeclaration(
String? name, {
Expression? initializer,
DartType type,
int flags,
bool isFinal,
bool isConst,
bool isInitializingFormal,
bool isSuperInitializingFormal,
bool isCovariantByDeclaration,
bool isLate,
bool isRequired,
bool isLowered,
bool isSynthesized,
bool isHoisted,
bool hasDeclaredInitializer,
bool isWildcard,
}) = VariableStatement;
factory LegacyVariableDeclaration.forValue(
Expression? initializer, {
bool isFinal,
bool isConst,
bool isInitializingFormal,
bool isSuperInitializingFormal,
bool isLate,
bool isRequired,
bool isLowered,
DartType type,
}) = VariableStatement.forValue;
}
/// Declaration of a local variable.
///
/// This may occur as a statement, but is also used in several non-statement
@@ -1648,13 +1466,23 @@ abstract interface class LegacyVariableDeclaration
/// When this occurs as a statement, it must be a direct child of a [Block].
//
// DESIGN TODO: Should we remove the 'final' modifier from variables?
class VariableStatement extends Statement implements LegacyVariableDeclaration {
class VariableStatement extends Statement
implements Annotatable, VariableDeclaration {
/// Offset of the equals sign in the source file it comes from.
///
/// Valid values are from 0 and up, or -1 ([TreeNode.noOffset])
/// if the equals sign offset is not available (e.g. if not initialized)
/// (this is the default if none is specifically set).
@override
int fileEqualsOffset = TreeNode.noOffset;
@override
List<int>? get fileOffsetsIfMultiple => [fileOffset, fileEqualsOffset];
/// List of metadata annotations on the variable declaration.
///
/// This defaults to an immutable empty list. Use [addAnnotation] to add
/// annotations if needed.
@override
List<Expression> annotations = const <Expression>[];
@@ -1667,12 +1495,18 @@ class VariableStatement extends Statement implements LegacyVariableDeclaration {
@override
int flags = 0;
/// The declared or inferred type of the variable.
@override
DartType type; // Not null, defaults to dynamic.
/// Offset of the declaration, set and used when writing the binary.
@override
int binaryOffsetNoTag = -1;
/// For locals, this is the initial value.
/// For parameters, this is the default value.
///
/// Should be null in other cases.
@override
Expression? initializer; // May be null.
@@ -1741,6 +1575,10 @@ class VariableStatement extends Statement implements LegacyVariableDeclaration {
this.isSynthesized = true;
}
/// The name of the variable as provided in the source code.
///
/// The name of a variable can only be omitted if the variable is synthesized.
/// Otherwise, its name is as provided in the source code.
@override
String? get name => _name;
@@ -1780,48 +1618,102 @@ class VariableStatement extends Statement implements LegacyVariableDeclaration {
static const int FlagSuperInitializingFormal = 1 << 12;
static const int FlagErroneouslyInitialized = 1 << 13;
/// Whether the variable is declared with the `final` keyword.
@override
bool get isFinal => flags & FlagFinal != 0;
/// Whether the variable is declared with the `const` keyword.
@override
bool get isConst => flags & FlagConst != 0;
/// Whether the parameter is declared with the `covariant` keyword.
@override
bool get isCovariantByDeclaration => flags & FlagCovariantByDeclaration != 0;
/// Whether the variable is declared as an initializing formal parameter of
/// a constructor.
@informative
@override
bool get isInitializingFormal => flags & FlagInitializingFormal != 0;
/// Whether the variable is declared as a super initializing formal parameter
/// of a constructor.
@informative
@override
bool get isSuperInitializingFormal =>
flags & FlagSuperInitializingFormal != 0;
@informative
@override
bool get isErroneouslyInitialized => flags & FlagErroneouslyInitialized != 0;
/// If this [LegacyVariableDeclaration] is a parameter of a method, indicates
/// whether the method implementation needs to contain a runtime type check to
/// deal with generic covariance.
///
/// When `true`, runtime checks may need to be performed.
@override
bool get isCovariantByClass => flags & FlagCovariantByClass != 0;
/// Whether the variable is declared with the `late` keyword.
///
/// The `late` modifier is only supported on local variables and not on
/// parameters.
@override
bool get isLate => flags & FlagLate != 0;
/// Whether the parameter is declared with the `required` keyword.
///
/// The `required` modifier is only supported on named parameters and not on
/// positional parameters and local variables.
@override
bool get isRequired => flags & FlagRequired != 0;
/// Whether the variable is part of a lowering.
///
/// If a variable is part of a lowering its name may be synthesized so that it
/// doesn't reflect the name used in the source code and might not have a
/// one-to-one correspondence with the variable in the source.
///
/// Lowering is used for instance of encoding of 'this' in extension instance
/// members and encoding of late locals.
@override
bool get isLowered => flags & FlagLowered != 0;
/// Whether this variable is synthesized, that is, it is _not_ declared in
/// the source code.
///
/// The name of a variable can only be omitted if the variable is synthesized.
/// Otherwise, its name is as provided in the source code.
@override
bool get isSynthesized => flags & FlagSynthesized != 0;
/// Whether the declaration of this variable is has been moved to an earlier
/// source location.
///
/// This is for instance the case for variables declared in a pattern, where
/// the lowering requires the variable to be declared before the expression
/// that performs that matching in which its initialization occurs.
@override
bool get isHoisted => flags & FlagHoisted != 0;
/// Whether the variable has an initializer, either by declaration or copied
/// from an original declaration.
///
/// Note that the variable might have a synthesized initializer expression,
/// so `hasDeclaredInitializer == false` doesn't imply `initializer == null`.
/// For instance, for duplicate variable names, an invalid expression is set
/// as the initializer of the second variable.
@override
bool get hasDeclaredInitializer => flags & FlagHasDeclaredInitializer != 0;
@override
bool get isWildcard => flags & FlagWildcard != 0;
/// Whether the variable is assignable.
///
/// This is `true` if the variable is neither constant nor final, or if it
/// is late final without an initializer.
@override
bool get isAssignable {
if (isConst) return false;
@@ -1996,12 +1888,12 @@ class VariableStatement extends Statement implements LegacyVariableDeclaration {
}
@override
VariableInitializationBase? get variableInitialization {
VariableDeclaration? get variableInitialization {
throw new UnsupportedError("${this.runtimeType}");
}
@override
void set variableInitialization(VariableInitializationBase? value) {
void set variableInitialization(VariableDeclaration? value) {
throw new UnsupportedError("${this.runtimeType}");
}
@@ -2127,47 +2019,6 @@ class FunctionDeclaration extends Statement implements LocalFunction {
}
}
/// The statement that marks the declaration of the variable in the source Dart
/// program. If the [initializer] is `null`, the variable was declared without
/// an initializer.
abstract class VariableInitializationBase
implements Statement, Annotatable, ContextConsumer {
abstract VariableDeclaration variable;
abstract Expression? initializer;
abstract bool hasDeclaredInitializer;
abstract int flags;
abstract bool isErroneouslyInitialized;
abstract bool isConst;
abstract bool isCovariantByClass;
abstract bool isCovariantByDeclaration;
abstract bool isFinal;
abstract bool isHoisted;
abstract bool isInitializingFormal;
abstract bool isLate;
abstract bool isLowered;
abstract bool isRequired;
abstract bool isSuperInitializingFormal;
abstract bool isSynthesized;
abstract bool isWildcard;
abstract int binaryOffsetNoTag;
abstract int fileEqualsOffset;
abstract String? name;
abstract DartType type;
abstract String? cosmeticName;
abstract VariableInitializationBase? variableInitialization;
factory VariableInitializationBase({
required VariableDeclaration variable,
required Expression? initializer,
bool hasDeclaredInitializer,
}) = VariableInitialization;
void clearAnnotations();
bool get isAssignable;
VariableContext get context;
VariableDeclaration get asExpressionVariable;
}
class VariableInitialization extends Statement implements VariableDeclaration {
@override
VariableDeclaration variable;
@@ -2414,10 +2265,10 @@ class VariableInitialization extends Statement implements VariableDeclaration {
}
@override
VariableInitializationBase? get variableInitialization => this;
VariableDeclaration? get variableInitialization => this;
@override
void set variableInitialization(VariableInitializationBase? value) {
void set variableInitialization(VariableDeclaration? value) {
throw new UnsupportedError("${this.runtimeType}");
}
+12 -13
View File
@@ -28,7 +28,7 @@ sealed class VariableBase extends TreeNode implements Annotatable {
abstract interface class IVariable implements TreeNode {
abstract DartType type;
abstract String? cosmeticName;
abstract VariableInitializationBase? variableInitialization;
abstract VariableDeclaration? variableInitialization;
abstract Expression? initializer;
abstract bool isFinal;
abstract bool isConst;
@@ -73,14 +73,14 @@ abstract interface class IVariable implements TreeNode {
/// The root of the sealed hierarchy of non-type variables.
sealed class VariableDeclaration extends VariableBase
implements IVariable, Statement, VariableInitializationBase {
implements IVariable, Statement, ContextConsumer {
/// Static type of the variable.
@override
abstract DartType type;
/// Initialization node for the variable, if available.
@override
abstract VariableInitializationBase? variableInitialization;
abstract VariableDeclaration? variableInitialization;
/// Derived from [variableInitialization], if available.
@override
@@ -186,8 +186,7 @@ sealed class VariableDeclaration extends VariableBase
@override
VariableDeclaration get asExpressionVariable => this;
@override
String? get name;
abstract String? name;
}
/// Local variables. They aren't Statements. A [LocalVariable] is "declared" in
@@ -202,7 +201,7 @@ class LocalVariable extends VariableDeclaration {
DartType type;
@override
VariableInitializationBase? variableInitialization;
VariableDeclaration? variableInitialization;
@override
List<Expression> annotations = const <Expression>[];
@@ -532,12 +531,12 @@ class CatchVariable extends VariableDeclaration {
}
@override
VariableInitializationBase? get variableInitialization {
VariableDeclaration? get variableInitialization {
throw new UnsupportedError("${this.runtimeType}.variableInitialization");
}
@override
void set variableInitialization(VariableInitializationBase? value) {
void set variableInitialization(VariableDeclaration? value) {
throw new UnsupportedError("${this.runtimeType}.variableInitialization=");
}
@@ -836,10 +835,10 @@ sealed class FunctionParameter extends VariableDeclaration {
/// Function parameters don't have initializers, only default values.
@override
VariableInitializationBase? get variableInitialization => null;
VariableDeclaration? get variableInitialization => null;
@override
void set variableInitialization(VariableInitializationBase? value) {}
void set variableInitialization(VariableDeclaration? value) {}
@override
Expression? get initializer => defaultValue;
@@ -1292,10 +1291,10 @@ class ThisVariable extends VariableDeclaration {
void set cosmeticName(String? value) {}
@override
VariableInitializationBase? get variableInitialization => null;
VariableDeclaration? get variableInitialization => null;
@override
void set variableInitialization(VariableInitializationBase? value) {}
void set variableInitialization(VariableDeclaration? value) {}
@override
DartType type;
@@ -1573,7 +1572,7 @@ class SyntheticVariable extends VariableDeclaration {
DartType type;
@override
VariableInitializationBase? variableInitialization;
VariableDeclaration? variableInitialization;
// TODO(cstefantsova): Consider a throwing implementation instead.
@override
+6
View File
@@ -1316,6 +1316,9 @@ class EquivalenceVisitor implements Visitor1<bool, Node> {
if (a is LabeledStatement) {
return b is LabeledStatement;
}
if (a is VariableStatement) {
return b is VariableStatement && a.name == b.name;
}
if (a is StructuralParameter) {
return b is StructuralParameter && a.name == b.name;
}
@@ -5890,6 +5893,9 @@ class EquivalenceStrategy {
if (identical(node, other)) return true;
if (node is! VariableStatement) return false;
if (other is! VariableStatement) return false;
if (!visitor.checkDeclarations(node, other, '')) {
return false;
}
visitor.pushNodeState(node, other);
bool result = true;
if (!checkVariableStatement_fileEqualsOffset(visitor, node, other)) {
+2 -2
View File
@@ -260,7 +260,7 @@ class AstPrinter {
return _variableNames[node] ??= '#${_variableNames.length}';
case CatchVariable(catchVariableName: var name):
return name;
case LegacyVariableDeclaration(:var name):
case VariableStatement(:var name):
case VariableInitialization(:var name):
if (name != null) {
return name;
@@ -519,7 +519,7 @@ class AstPrinter {
/// If [isLate] and [type] are provided, these values are used instead of
/// the corresponding properties on [node].
void writeVariableInitialization(
VariableInitializationBase node, {
VariableDeclaration node, {
bool includeModifiersAndType = true,
bool? isLate,
DartType? type,
+8 -7
View File
@@ -243,7 +243,7 @@ class NameSystem {
}
abstract class Annotator {
String annotateVariable(Printer printer, VariableInitializationBase node);
String annotateVariable(Printer printer, VariableDeclaration node);
String annotateReturn(Printer printer, FunctionNode node);
String annotateField(Printer printer, Field node);
}
@@ -1249,7 +1249,8 @@ class Printer extends VisitorDefault<void> with VisitorVoidMixin {
}
void writeExpressionVariable(VariableDeclaration node) {
if (node is LegacyVariableDeclaration && node is! FunctionParameter) {
// TODO(cstefantsova): Printer of the new variables is broken.
if (node is VariableStatement && node is! FunctionParameter) {
writeVariableDeclaration(node);
} else {
if (showOffsets) writeWord("[${node.fileOffset}]");
@@ -1266,7 +1267,7 @@ class Printer extends VisitorDefault<void> with VisitorVoidMixin {
writeWord('this-variable');
case SyntheticVariable():
writeWord('synthetic-variable');
case LegacyVariableDeclaration():
case VariableStatement():
writeWord('variable-declaration');
case CatchVariable():
writeWord('catch-variable');
@@ -2603,7 +2604,7 @@ class Printer extends VisitorDefault<void> with VisitorVoidMixin {
ensureSpace();
}
writeSymbol('(');
if (node.variable case LegacyVariableDeclaration variable) {
if (node.variable case VariableStatement variable) {
writeVariableDeclaration(variable, useVarKeyword: true);
} else {
writeExpressionVariable(node.variable);
@@ -2757,7 +2758,7 @@ class Printer extends VisitorDefault<void> with VisitorVoidMixin {
}
@override
void visitVariableInitialization(VariableInitializationBase node) {
void visitVariableInitialization(VariableInitialization node) {
writeIndentation();
writeVariableInitialization(node);
_writeContexts(node);
@@ -2825,8 +2826,8 @@ class Printer extends VisitorDefault<void> with VisitorVoidMixin {
}
}
void writeVariableInitialization(VariableInitializationBase node) {
if (node is VariableDeclaration) {
void writeVariableInitialization(VariableDeclaration node) {
if (node is VariableStatement) {
writeVariableDeclaration(node);
} else {
if (showOffsets) writeWord("[${node.fileOffset}]");
+7 -3
View File
@@ -1183,7 +1183,7 @@ class TypeCheckingVisitor
@override
void visitForStatement(ForStatement node) {
node.variables.forEach(visitVariableInitialization);
node.variables.forEach(_handleVariableInitialization);
if (node.condition != null) {
node.condition = checkExpressionAndAssignability(
node.condition!,
@@ -1257,11 +1257,15 @@ class TypeCheckingVisitor
@override
void visitVariableDeclaration(VariableDeclaration node) {
visitVariableInitialization(node);
_handleVariableInitialization(node);
}
@override
void visitVariableInitialization(VariableInitializationBase node) {
void visitVariableInitialization(VariableInitialization node) {
_handleVariableInitialization(node);
}
void _handleVariableInitialization(VariableDeclaration node) {
if (node.initializer != null) {
node.initializer = checkExpressionAndAssignability(
node.initializer!,
+3 -3
View File
@@ -270,7 +270,7 @@ class VerifyingVisitor extends RecursiveResultVisitor<void> {
// TODO(cstefantsova): Remove this method when the new variable model is
// supported.
bool _isNewModelVariable(TreeNode node) {
return node is VariableDeclaration && node is! LegacyVariableDeclaration ||
return node is VariableDeclaration && node is! VariableStatement ||
node is FunctionParameter;
}
@@ -1142,11 +1142,11 @@ class VerifyingVisitor extends RecursiveResultVisitor<void> {
}
@override
void visitVariableInitialization(VariableInitializationBase node) {
void visitVariableInitialization(VariableDeclaration node) {
return _verifyVariableInitialization(node);
}
void _verifyVariableInitialization(VariableInitializationBase node) {
void _verifyVariableInitialization(VariableDeclaration node) {
enterTreeNode(node);
TreeNode? parent = node.parent;
if (parent is! Block &&
-12
View File
@@ -2425,18 +2425,6 @@ class RemovingTransformer extends TreeVisitor1Default<TreeNode, TreeNode?> {
transformList(nodes, parent, dummyVariableDeclaration);
}
/// Transforms or removes [VariableInitializationBase] nodes in [nodes] as
/// children of [parent].
///
/// This is convenience method for calling [transformList] with removal
/// sentinel for [VariableInitializationBase] nodes.
void transformVariableInitializationList(
List<VariableInitializationBase> nodes,
TreeNode parent,
) {
transformList(nodes, parent, dummyVariableDeclaration);
}
/// Transforms or removes [T] nodes in [nodes] as children of [parent] by
/// calling [transformOrRemove] using [removalSentinel] as the removal
/// sentinel.