| // Copyright (c) 2024, 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. |
| |
| part of '../../ast.dart'; |
| |
| // ------------------------------------------------------------------------ |
| // STATEMENTS |
| // ------------------------------------------------------------------------ |
| |
| sealed class Statement extends TreeNode { |
| @override |
| R accept<R>(StatementVisitor<R> v); |
| |
| @override |
| R accept1<R, A>(StatementVisitor1<R, A> v, A arg); |
| |
| @override |
| String toText(AstTextStrategy strategy) { |
| AstPrinter printer = new AstPrinter(strategy); |
| printer.writeStatement(this); |
| return printer.getText(); |
| } |
| } |
| |
| abstract class AuxiliaryStatement extends Statement { |
| @override |
| R accept<R>(StatementVisitor<R> v) => v.visitAuxiliaryStatement(this); |
| |
| @override |
| R accept1<R, A>(StatementVisitor1<R, A> v, A arg) => |
| v.visitAuxiliaryStatement(this, arg); |
| } |
| |
| class ExpressionStatement extends Statement { |
| Expression expression; |
| |
| // TODO(johnniwinther): Fix this so set value is not lost. We include this |
| // getter so offset is consistent before and after serialization. |
| // ExpressionStatements are common so serializing the offset could |
| // increase serialized size. |
| @override |
| int get fileOffset => expression.fileOffset; |
| |
| new(this.expression) { |
| expression.parent = this; |
| } |
| |
| @override |
| R accept<R>(StatementVisitor<R> v) => v.visitExpressionStatement(this); |
| |
| @override |
| R accept1<R, A>(StatementVisitor1<R, A> v, A arg) => |
| v.visitExpressionStatement(this, arg); |
| |
| @override |
| void visitChildren(Visitor v) { |
| expression.accept(v); |
| } |
| |
| @override |
| void transformChildren(Transformer v) { |
| expression = v.transform(expression); |
| expression.parent = this; |
| } |
| |
| @override |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| expression = v.transform(expression); |
| expression.parent = this; |
| } |
| |
| @override |
| String toString() { |
| return "ExpressionStatement(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.writeExpression(expression); |
| printer.write(';'); |
| } |
| } |
| |
| class Block extends Statement implements ScopeProvider { |
| final List<Statement> statements; |
| |
| /// End offset in the source file it comes from. Valid values are from 0 and |
| /// up, or -1 ([TreeNode.noOffset]) if the file end offset is not available |
| /// (this is the default if none is specifically set). |
| int fileEndOffset = TreeNode.noOffset; |
| |
| @override |
| List<int>? get fileOffsetsIfMultiple => [fileOffset, fileEndOffset]; |
| |
| @override |
| Scope? scope; |
| |
| new(this.statements) { |
| // Ensure statements is mutable. |
| assert(checkListIsMutable(statements, dummyStatement)); |
| setParents(statements, this); |
| } |
| |
| @override |
| R accept<R>(StatementVisitor<R> v) => v.visitBlock(this); |
| |
| @override |
| R accept1<R, A>(StatementVisitor1<R, A> v, A arg) => v.visitBlock(this, arg); |
| |
| @override |
| void visitChildren(Visitor v) { |
| visitList(statements, v); |
| } |
| |
| @override |
| void transformChildren(Transformer v) { |
| v.transformList(statements, this); |
| } |
| |
| @override |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| v.transformStatementList(statements, this); |
| } |
| |
| void addStatement(Statement node) { |
| statements.add(node); |
| node.parent = this; |
| } |
| |
| @override |
| String toString() { |
| return "Block(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.writeBlock(statements); |
| } |
| } |
| |
| /// A block that is only executed when asserts are enabled. |
| /// |
| /// Sometimes arbitrary statements must be guarded by whether asserts are |
| /// enabled. For example, when a subexpression of an assert in async code is |
| /// linearized and named, it can produce such a block of statements. |
| class AssertBlock extends Statement { |
| final List<Statement> statements; |
| |
| new(this.statements) { |
| // Ensure statements is mutable. |
| assert(checkListIsMutable(statements, dummyStatement)); |
| setParents(statements, this); |
| } |
| |
| @override |
| R accept<R>(StatementVisitor<R> v) => v.visitAssertBlock(this); |
| |
| @override |
| R accept1<R, A>(StatementVisitor1<R, A> v, A arg) => |
| v.visitAssertBlock(this, arg); |
| |
| @override |
| void transformChildren(Transformer v) { |
| v.transformList(statements, this); |
| } |
| |
| @override |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| v.transformStatementList(statements, this); |
| } |
| |
| @override |
| void visitChildren(Visitor v) { |
| visitList(statements, v); |
| } |
| |
| @override |
| String toString() { |
| return "AssertBlock(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.write('assert '); |
| printer.writeBlock(statements); |
| } |
| } |
| |
| class EmptyStatement extends Statement { |
| @override |
| R accept<R>(StatementVisitor<R> v) => v.visitEmptyStatement(this); |
| |
| @override |
| R accept1<R, A>(StatementVisitor1<R, A> v, A arg) => |
| v.visitEmptyStatement(this, arg); |
| |
| @override |
| void visitChildren(Visitor v) {} |
| |
| @override |
| void transformChildren(Transformer v) {} |
| |
| @override |
| void transformOrRemoveChildren(RemovingTransformer v) {} |
| |
| @override |
| String toString() { |
| return "EmptyStatement(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.write(';'); |
| } |
| } |
| |
| class AssertStatement extends Statement { |
| Expression condition; |
| Expression? message; // May be null. |
| |
| /// Character offset in the source where the assertion condition begins. |
| /// |
| /// This is an index into [Source.text]. |
| int conditionStartOffset; |
| |
| /// Character offset in the source where the assertion condition ends. |
| /// |
| /// This is an index into [Source.text]. |
| int conditionEndOffset; |
| |
| @override |
| List<int>? get fileOffsetsIfMultiple => [ |
| fileOffset, |
| conditionStartOffset, |
| conditionEndOffset, |
| ]; |
| |
| new( |
| this.condition, { |
| this.message, |
| required this.conditionStartOffset, |
| required this.conditionEndOffset, |
| }) { |
| condition.parent = this; |
| message?.parent = this; |
| } |
| |
| @override |
| R accept<R>(StatementVisitor<R> v) => v.visitAssertStatement(this); |
| |
| @override |
| R accept1<R, A>(StatementVisitor1<R, A> v, A arg) => |
| v.visitAssertStatement(this, arg); |
| |
| @override |
| void visitChildren(Visitor v) { |
| condition.accept(v); |
| message?.accept(v); |
| } |
| |
| @override |
| void transformChildren(Transformer v) { |
| condition = v.transform(condition); |
| condition.parent = this; |
| if (message != null) { |
| message = v.transform(message!); |
| message?.parent = this; |
| } |
| } |
| |
| @override |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| condition = v.transform(condition); |
| condition.parent = this; |
| if (message != null) { |
| message = v.transformOrRemoveExpression(message!); |
| message?.parent = this; |
| } |
| } |
| |
| @override |
| String toString() { |
| return "AssertStatement(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.write('assert('); |
| printer.writeExpression(condition); |
| if (message != null) { |
| printer.write(', '); |
| printer.writeExpression(message!); |
| } |
| printer.write(');'); |
| } |
| } |
| |
| /// A target of a [Break] statement. |
| /// |
| /// The label itself has no name; breaks reference the statement directly. |
| /// |
| /// The frontend does not generate labeled statements without uses. |
| class LabeledStatement extends Statement { |
| late Statement body; |
| |
| new(Statement? body) { |
| if (body != null) { |
| this.body = body..parent = this; |
| } |
| } |
| |
| @override |
| R accept<R>(StatementVisitor<R> v) => v.visitLabeledStatement(this); |
| |
| @override |
| R accept1<R, A>(StatementVisitor1<R, A> v, A arg) => |
| v.visitLabeledStatement(this, arg); |
| |
| @override |
| void visitChildren(Visitor v) { |
| body.accept(v); |
| } |
| |
| @override |
| void transformChildren(Transformer v) { |
| body = v.transform(body); |
| body.parent = this; |
| } |
| |
| @override |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| body = v.transform(body); |
| body.parent = this; |
| } |
| |
| @override |
| String toString() { |
| return "LabeledStatement(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.write(printer.getLabelName(this)); |
| printer.write(':'); |
| printer.newLine(); |
| printer.writeStatement(body); |
| } |
| } |
| |
| /// Breaks out of an enclosing [LabeledStatement]. |
| /// |
| /// Both `break` and `continue` statements are translated into this node. |
| /// |
| /// Example `break` desugaring: |
| /// |
| /// while (x) { |
| /// if (y) break; |
| /// BODY |
| /// } |
| /// |
| /// ==> |
| /// |
| /// L: while (x) { |
| /// if (y) break L; |
| /// BODY |
| /// } |
| /// |
| /// Example `continue` desugaring: |
| /// |
| /// while (x) { |
| /// if (y) continue; |
| /// BODY |
| /// } |
| /// |
| /// ==> |
| /// |
| /// while (x) { |
| /// L: { |
| /// if (y) break L; |
| /// BODY |
| /// } |
| /// } |
| /// |
| /// Note: Compiler-generated [LabeledStatement]s for [WhileStatement]s and |
| /// [ForStatement]s are only generated when needed. If there isn't a `break` or |
| /// `continue` in a loop, the kernel for the loop won't have a generated |
| /// [LabeledStatement]. |
| class BreakStatement extends Statement { |
| LabeledStatement target; |
| |
| new(this.target); |
| |
| @override |
| R accept<R>(StatementVisitor<R> v) => v.visitBreakStatement(this); |
| |
| @override |
| R accept1<R, A>(StatementVisitor1<R, A> v, A arg) => |
| v.visitBreakStatement(this, arg); |
| |
| @override |
| void visitChildren(Visitor v) {} |
| |
| @override |
| void transformChildren(Transformer v) {} |
| |
| @override |
| void transformOrRemoveChildren(RemovingTransformer v) {} |
| |
| @override |
| String toString() { |
| return "BreakStatement(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.write('break '); |
| printer.write(printer.getLabelName(target)); |
| printer.write(';'); |
| } |
| } |
| |
| /// Common interface for loop statements. |
| abstract interface class LoopStatement implements Statement { |
| abstract Statement body; |
| } |
| |
| class WhileStatement extends Statement implements LoopStatement, ScopeProvider { |
| Expression condition; |
| |
| @override |
| Statement body; |
| |
| @override |
| Scope? scope; |
| |
| new(this.condition, this.body) { |
| condition.parent = this; |
| body.parent = this; |
| } |
| |
| @override |
| R accept<R>(StatementVisitor<R> v) => v.visitWhileStatement(this); |
| |
| @override |
| R accept1<R, A>(StatementVisitor1<R, A> v, A arg) => |
| v.visitWhileStatement(this, arg); |
| |
| @override |
| void visitChildren(Visitor v) { |
| condition.accept(v); |
| body.accept(v); |
| } |
| |
| @override |
| void transformChildren(Transformer v) { |
| condition = v.transform(condition); |
| condition.parent = this; |
| body = v.transform(body); |
| body.parent = this; |
| } |
| |
| @override |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| condition = v.transform(condition); |
| condition.parent = this; |
| body = v.transform(body); |
| body.parent = this; |
| } |
| |
| @override |
| String toString() { |
| return "WhileStatement(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.write('while ('); |
| printer.writeExpression(condition); |
| printer.write(') '); |
| printer.writeStatement(body); |
| } |
| } |
| |
| class DoStatement extends Statement implements LoopStatement { |
| @override |
| Statement body; |
| |
| Expression condition; |
| |
| new(this.body, this.condition) { |
| body.parent = this; |
| condition.parent = this; |
| } |
| |
| @override |
| R accept<R>(StatementVisitor<R> v) => v.visitDoStatement(this); |
| |
| @override |
| R accept1<R, A>(StatementVisitor1<R, A> v, A arg) => |
| v.visitDoStatement(this, arg); |
| |
| @override |
| void visitChildren(Visitor v) { |
| body.accept(v); |
| condition.accept(v); |
| } |
| |
| @override |
| void transformChildren(Transformer v) { |
| body = v.transform(body); |
| body.parent = this; |
| condition = v.transform(condition); |
| condition.parent = this; |
| } |
| |
| @override |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| body = v.transform(body); |
| body.parent = this; |
| condition = v.transform(condition); |
| condition.parent = this; |
| } |
| |
| @override |
| String toString() { |
| return "DoStatement(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.write('do '); |
| printer.writeStatement(body); |
| printer.write(' while ('); |
| printer.writeExpression(condition); |
| printer.write(');'); |
| } |
| } |
| |
| class ForStatement extends Statement implements LoopStatement, ScopeProvider { |
| // May be empty, but not null. |
| final List<VariableDeclaration> variables; |
| |
| // TODO(61572): Remove this. |
| List<VariableDeclaration> get variableInitializations => variables; |
| |
| Expression? condition; // May be null. |
| final List<Expression> updates; // May be empty, but not null. |
| |
| @override |
| Statement body; |
| |
| @override |
| Scope? scope; |
| |
| new(this.variables, this.condition, this.updates, this.body) { |
| setParents(variables, this); |
| condition?.parent = this; |
| setParents(updates, this); |
| body.parent = this; |
| } |
| |
| @override |
| R accept<R>(StatementVisitor<R> v) => v.visitForStatement(this); |
| |
| @override |
| R accept1<R, A>(StatementVisitor1<R, A> v, A arg) => |
| v.visitForStatement(this, arg); |
| |
| @override |
| void visitChildren(Visitor v) { |
| visitList(variables, v); |
| condition?.accept(v); |
| visitList(updates, v); |
| body.accept(v); |
| } |
| |
| @override |
| void transformChildren(Transformer v) { |
| v.transformList(variables, this); |
| if (condition != null) { |
| condition = v.transform(condition!); |
| condition?.parent = this; |
| } |
| v.transformList(updates, this); |
| body = v.transform(body); |
| body.parent = this; |
| } |
| |
| @override |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| v.transformVariableDeclarationList(variables, this); |
| if (condition != null) { |
| condition = v.transformOrRemoveExpression(condition!); |
| condition?.parent = this; |
| } |
| v.transformExpressionList(updates, this); |
| body = v.transform(body); |
| body.parent = this; |
| } |
| |
| @override |
| String toString() { |
| return "ForStatement(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.write('for ('); |
| for (int index = 0; index < variables.length; index++) { |
| if (index > 0) { |
| printer.write(', '); |
| } |
| printer.writeVariableInitialization( |
| variables[index].variable, |
| includeModifiersAndType: index == 0, |
| ); |
| } |
| printer.write('; '); |
| if (condition != null) { |
| printer.writeExpression(condition!); |
| } |
| printer.write('; '); |
| printer.writeExpressions(updates); |
| printer.write(') '); |
| printer.writeStatement(body); |
| } |
| } |
| |
| class ForInStatement extends Statement implements LoopStatement, ScopeProvider { |
| /// Offset in the source file it comes from. |
| /// |
| /// Valid values are from 0 and up, or -1 ([TreeNode.noOffset]) if the file |
| /// offset is not available (this is the default if none is specifically set). |
| int bodyOffset = TreeNode.noOffset; |
| |
| @override |
| List<int>? get fileOffsetsIfMultiple => [fileOffset, bodyOffset]; |
| |
| // Has no initializer. |
| DeclaredVariable variable; |
| |
| Expression iterable; |
| |
| @override |
| Statement body; |
| |
| bool isAsync; // True if this is an 'await for' loop. |
| |
| @override |
| Scope? scope; |
| |
| new(this.variable, this.iterable, this.body, {this.isAsync = false}) { |
| variable.parent = this; |
| iterable.parent = this; |
| body.parent = this; |
| } |
| |
| @override |
| R accept<R>(StatementVisitor<R> v) => v.visitForInStatement(this); |
| |
| @override |
| R accept1<R, A>(StatementVisitor1<R, A> v, A arg) => |
| v.visitForInStatement(this, arg); |
| |
| @override |
| void visitChildren(Visitor v) { |
| variable.accept(v); |
| iterable.accept(v); |
| body.accept(v); |
| } |
| |
| @override |
| void transformChildren(Transformer v) { |
| variable = v.transform(variable); |
| variable.parent = this; |
| iterable = v.transform(iterable); |
| iterable.parent = this; |
| body = v.transform(body); |
| body.parent = this; |
| } |
| |
| @override |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| variable = v.transform(variable); |
| variable.parent = this; |
| iterable = v.transform(iterable); |
| iterable.parent = this; |
| body = v.transform(body); |
| body.parent = this; |
| } |
| |
| /// Returns the type of the iterator in this for-in statement. |
| /// |
| /// This calls `StaticTypeContext.getForInIteratorType` which calls |
| /// [getStaticTypeInternal] to compute the type of not already cached in |
| /// [context]. |
| DartType getIteratorType(StaticTypeContext context) => |
| context.getForInIteratorType(this); |
| |
| /// Computes the type of the iterator in this for-in statement. |
| /// |
| /// This is called by `StaticTypeContext.getForInIteratorType` if the iterator |
| /// type of this for-in statement is not already cached in [context]. |
| DartType getIteratorTypeInternal(StaticTypeContext context) { |
| DartType? iteratorType; |
| if (isAsync) { |
| InterfaceType streamType = iterable.getStaticTypeAsInstanceOf( |
| context.typeEnvironment.coreTypes.streamClass, |
| context, |
| ); |
| iteratorType = new InterfaceType( |
| context.typeEnvironment.coreTypes.streamIteratorClass, |
| context.nonNullable, |
| streamType.typeArguments, |
| ); |
| } else { |
| InterfaceType iterableType = iterable.getStaticTypeAsInstanceOf( |
| context.typeEnvironment.coreTypes.iterableClass, |
| context, |
| ); |
| Member? member = context.typeEnvironment.hierarchy.getInterfaceMember( |
| iterableType.classNode, |
| new Name('iterator'), |
| ); |
| if (member != null) { |
| iteratorType = Substitution.fromInterfaceType(iterableType) |
| .substituteType(member.getterType); |
| } |
| } |
| return iteratorType ?? const DynamicType(); |
| } |
| |
| /// Returns the type of the element in this for-in statement. |
| /// |
| /// This calls `StaticTypeContext.getForInElementType` which calls |
| /// [getStaticTypeInternal] to compute the type of not already cached in |
| /// [context]. |
| DartType getElementType(StaticTypeContext context) => |
| context.getForInElementType(this); |
| |
| /// Computes the type of the element in this for-in statement. |
| /// |
| /// This is called by `StaticTypeContext.getForInElementType` if the element |
| /// type of this for-in statement is not already cached in [context]. |
| DartType getElementTypeInternal(StaticTypeContext context) { |
| DartType iterableType = iterable |
| .getStaticType(context) |
| .nonTypeParameterBound; |
| // TODO(johnniwinther): Update this to use the type of |
| // `iterable.iterator.current` if inference is updated accordingly. |
| while (iterableType is TypeParameterType) { |
| TypeParameterType typeParameterType = iterableType; |
| iterableType = typeParameterType.bound; |
| } |
| if (iterableType is NeverType) { |
| return iterableType; |
| } |
| if (iterableType is InvalidType) { |
| return iterableType; |
| } |
| if (iterableType is! TypeDeclarationType) { |
| // TODO(johnniwinther): Change this to an assert once the CFE correctly |
| // inserts casts for all invalid iterable types. |
| return const InvalidType(); |
| } |
| if (isAsync) { |
| List<DartType> typeArguments = context.typeEnvironment |
| .getTypeArgumentsAsInstanceOf( |
| iterableType, |
| context.typeEnvironment.coreTypes.streamClass, |
| )!; |
| return typeArguments.single; |
| } else { |
| List<DartType> typeArguments = context.typeEnvironment |
| .getTypeArgumentsAsInstanceOf( |
| iterableType, |
| context.typeEnvironment.coreTypes.iterableClass, |
| )!; |
| return typeArguments.single; |
| } |
| } |
| |
| @override |
| String toString() { |
| return "ForInStatement(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.write('for ('); |
| printer.writeExpressionVariable(variable); |
| |
| printer.write(' in '); |
| printer.writeExpression(iterable); |
| printer.write(') '); |
| printer.writeStatement(body); |
| } |
| } |
| |
| /// Statement of form `switch (e) { case x: ... }`. |
| /// |
| /// Adjacent case clauses have been merged into a single [SwitchCase]. A runtime |
| /// exception must be thrown if one [SwitchCase] falls through to another case. |
| class SwitchStatement extends Statement { |
| Expression expression; |
| final List<SwitchCase> cases; |
| |
| /// For switches without a default clause, whether all possible values are |
| /// covered by a switch case. For switches with a default clause, always |
| /// `false`. |
| /// Initialized during type inference. |
| bool isExplicitlyExhaustive; |
| |
| /// The static type of the [expression] |
| /// |
| /// This is set during inference. |
| DartType? expressionTypeInternal; |
| |
| new(this.expression, this.cases, {this.isExplicitlyExhaustive = false}) { |
| expression.parent = this; |
| setParents(cases, this); |
| } |
| |
| /// The static type of the [expression] |
| /// |
| /// This is set during inference. |
| DartType get expressionType { |
| assert( |
| expressionTypeInternal != null, |
| "Expression type hasn't been computed for $this.", |
| ); |
| return expressionTypeInternal!; |
| } |
| |
| void set expressionType(DartType value) { |
| expressionTypeInternal = value; |
| } |
| |
| /// Whether the switch has a `default` case. |
| bool get hasDefault { |
| assert(cases.every((c) => c == cases.last || !c.isDefault)); |
| return cases.isNotEmpty && cases.last.isDefault; |
| } |
| |
| /// Whether the switch is guaranteed to hit one of the cases (including the |
| /// default case, if present). |
| bool get isExhaustive => isExplicitlyExhaustive || hasDefault; |
| |
| @override |
| R accept<R>(StatementVisitor<R> v) => v.visitSwitchStatement(this); |
| |
| @override |
| R accept1<R, A>(StatementVisitor1<R, A> v, A arg) => |
| v.visitSwitchStatement(this, arg); |
| |
| @override |
| void visitChildren(Visitor v) { |
| expression.accept(v); |
| visitList(cases, v); |
| expressionTypeInternal?.accept(v); |
| } |
| |
| @override |
| void transformChildren(Transformer v) { |
| expression = v.transform(expression); |
| expression.parent = this; |
| v.transformList(cases, this); |
| if (expressionTypeInternal != null) { |
| expressionTypeInternal = v.visitDartType(expressionTypeInternal!); |
| } |
| } |
| |
| @override |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| expression = v.transform(expression); |
| expression.parent = this; |
| v.transformSwitchCaseList(cases, this); |
| if (expressionTypeInternal != null) { |
| expressionTypeInternal = v.visitDartType( |
| expressionTypeInternal!, |
| cannotRemoveSentinel, |
| ); |
| } |
| } |
| |
| @override |
| String toString() { |
| return "SwitchStatement(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.write('switch ('); |
| printer.writeExpression(expression); |
| printer.write(') {'); |
| printer.incIndentation(); |
| for (SwitchCase switchCase in cases) { |
| printer.newLine(); |
| printer.writeSwitchCase(switchCase); |
| } |
| printer.decIndentation(); |
| printer.newLine(); |
| printer.write('}'); |
| } |
| } |
| |
| /// A group of `case` clauses and/or a `default` clause. |
| /// |
| /// This is a potential target of [ContinueSwitchStatement]. |
| class SwitchCase extends TreeNode { |
| final List<Expression> expressions; |
| final List<int> expressionOffsets; |
| late Statement body; |
| bool isDefault; |
| |
| new( |
| this.expressions, |
| this.expressionOffsets, |
| Statement? body, { |
| this.isDefault = false, |
| }) { |
| setParents(expressions, this); |
| if (body != null) { |
| this.body = body..parent = this; |
| } |
| } |
| |
| new defaultCase(Statement? body) |
| : isDefault = true, |
| expressions = <Expression>[], |
| expressionOffsets = <int>[] { |
| if (body != null) { |
| this.body = body..parent = this; |
| } |
| } |
| |
| @override |
| List<int>? get fileOffsetsIfMultiple => [fileOffset, ...expressionOffsets]; |
| |
| @override |
| R accept<R>(TreeVisitor<R> v) => v.visitSwitchCase(this); |
| |
| @override |
| R accept1<R, A>(TreeVisitor1<R, A> v, A arg) => v.visitSwitchCase(this, arg); |
| |
| @override |
| void visitChildren(Visitor v) { |
| visitList(expressions, v); |
| body.accept(v); |
| } |
| |
| @override |
| void transformChildren(Transformer v) { |
| v.transformList(expressions, this); |
| body = v.transform(body); |
| body.parent = this; |
| } |
| |
| @override |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| v.transformExpressionList(expressions, this); |
| body = v.transform(body); |
| body.parent = this; |
| } |
| |
| @override |
| String toString() { |
| return "SwitchCase(${toStringInternal()})"; |
| } |
| |
| @override |
| String toText(AstTextStrategy strategy) { |
| AstPrinter printer = new AstPrinter(strategy); |
| toTextInternal(printer); |
| return printer.getText(); |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| for (int index = 0; index < expressions.length; index++) { |
| if (index > 0) { |
| printer.newLine(); |
| } |
| printer.write('case '); |
| printer.writeExpression(expressions[index]); |
| printer.write(':'); |
| } |
| if (isDefault) { |
| if (expressions.isNotEmpty) { |
| printer.newLine(); |
| } |
| printer.write('default:'); |
| } |
| printer.incIndentation(); |
| Statement? block = body; |
| if (block is Block) { |
| for (Statement statement in block.statements) { |
| printer.newLine(); |
| printer.writeStatement(statement); |
| } |
| } else { |
| printer.write(' '); |
| printer.writeStatement(body); |
| } |
| printer.decIndentation(); |
| } |
| } |
| |
| /// Jump to a case in an enclosing switch. |
| class ContinueSwitchStatement extends Statement { |
| SwitchCase target; |
| |
| new(this.target); |
| |
| @override |
| R accept<R>(StatementVisitor<R> v) => v.visitContinueSwitchStatement(this); |
| |
| @override |
| R accept1<R, A>(StatementVisitor1<R, A> v, A arg) => |
| v.visitContinueSwitchStatement(this, arg); |
| |
| @override |
| void visitChildren(Visitor v) {} |
| |
| @override |
| void transformChildren(Transformer v) {} |
| |
| @override |
| void transformOrRemoveChildren(RemovingTransformer v) {} |
| |
| @override |
| String toString() { |
| return "ContinueSwitchStatement(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.write('continue '); |
| printer.write(printer.getSwitchCaseName(target)); |
| printer.write(';'); |
| } |
| } |
| |
| class IfStatement extends Statement { |
| Expression condition; |
| Statement then; |
| Statement? otherwise; |
| |
| new(this.condition, this.then, this.otherwise) { |
| condition.parent = this; |
| then.parent = this; |
| otherwise?.parent = this; |
| } |
| |
| @override |
| R accept<R>(StatementVisitor<R> v) => v.visitIfStatement(this); |
| |
| @override |
| R accept1<R, A>(StatementVisitor1<R, A> v, A arg) => |
| v.visitIfStatement(this, arg); |
| |
| @override |
| void visitChildren(Visitor v) { |
| condition.accept(v); |
| then.accept(v); |
| otherwise?.accept(v); |
| } |
| |
| @override |
| void transformChildren(Transformer v) { |
| condition = v.transform(condition); |
| condition.parent = this; |
| then = v.transform(then); |
| then.parent = this; |
| if (otherwise != null) { |
| otherwise = v.transform(otherwise!); |
| otherwise?.parent = this; |
| } |
| } |
| |
| @override |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| condition = v.transform(condition); |
| condition.parent = this; |
| then = v.transform(then); |
| then.parent = this; |
| if (otherwise != null) { |
| otherwise = v.transformOrRemoveStatement(otherwise!); |
| otherwise?.parent = this; |
| } |
| } |
| |
| @override |
| String toString() { |
| return "IfStatement(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.write('if ('); |
| printer.writeExpression(condition); |
| printer.write(') '); |
| printer.writeStatement(then); |
| if (otherwise != null) { |
| printer.write(' else '); |
| printer.writeStatement(otherwise!); |
| } |
| } |
| } |
| |
| class ReturnStatement extends Statement { |
| Expression? expression; // May be null. |
| |
| new([this.expression]) { |
| expression?.parent = this; |
| } |
| |
| @override |
| R accept<R>(StatementVisitor<R> v) => v.visitReturnStatement(this); |
| |
| @override |
| R accept1<R, A>(StatementVisitor1<R, A> v, A arg) => |
| v.visitReturnStatement(this, arg); |
| |
| @override |
| void visitChildren(Visitor v) { |
| expression?.accept(v); |
| } |
| |
| @override |
| void transformChildren(Transformer v) { |
| if (expression != null) { |
| expression = v.transform(expression!); |
| expression?.parent = this; |
| } |
| } |
| |
| @override |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| if (expression != null) { |
| expression = v.transformOrRemoveExpression(expression!); |
| expression?.parent = this; |
| } |
| } |
| |
| @override |
| String toString() { |
| return "ReturnStatement(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.write('return'); |
| if (expression != null) { |
| printer.write(' '); |
| printer.writeExpression(expression!); |
| } |
| printer.write(';'); |
| } |
| } |
| |
| class TryCatch extends Statement { |
| Statement body; |
| List<Catch> catches; |
| bool isSynthetic; |
| |
| new(this.body, this.catches, {this.isSynthetic = false}) { |
| body.parent = this; |
| setParents(catches, this); |
| } |
| |
| @override |
| R accept<R>(StatementVisitor<R> v) => v.visitTryCatch(this); |
| |
| @override |
| R accept1<R, A>(StatementVisitor1<R, A> v, A arg) => |
| v.visitTryCatch(this, arg); |
| |
| @override |
| void visitChildren(Visitor v) { |
| body.accept(v); |
| visitList(catches, v); |
| } |
| |
| @override |
| void transformChildren(Transformer v) { |
| body = v.transform(body); |
| body.parent = this; |
| v.transformList(catches, this); |
| } |
| |
| @override |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| body = v.transform(body); |
| body.parent = this; |
| v.transformCatchList(catches, this); |
| } |
| |
| @override |
| String toString() { |
| return "TryCatch(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.write('try '); |
| printer.writeStatement(body); |
| for (Catch catchClause in catches) { |
| printer.write(' '); |
| printer.writeCatch(catchClause); |
| } |
| } |
| } |
| |
| class Catch extends TreeNode implements ScopeProvider { |
| DartType guard; // Not null, defaults to dynamic. |
| CatchVariable? exception; |
| CatchVariable? stackTrace; |
| Statement body; |
| |
| @override |
| Scope? scope; |
| |
| new( |
| this.exception, |
| this.body, { |
| this.guard = const DynamicType(), |
| this.stackTrace, |
| }) { |
| exception?.parent = this; |
| stackTrace?.parent = this; |
| body.parent = this; |
| } |
| |
| @override |
| R accept<R>(TreeVisitor<R> v) => v.visitCatch(this); |
| |
| @override |
| R accept1<R, A>(TreeVisitor1<R, A> v, A arg) => v.visitCatch(this, arg); |
| |
| @override |
| void visitChildren(Visitor v) { |
| guard.accept(v); |
| exception?.accept(v); |
| stackTrace?.accept(v); |
| body.accept(v); |
| } |
| |
| @override |
| void transformChildren(Transformer v) { |
| guard = v.visitDartType(guard); |
| if (exception != null) { |
| exception = v.transform(exception!); |
| exception?.parent = this; |
| } |
| if (stackTrace != null) { |
| stackTrace = v.transform(stackTrace!); |
| stackTrace?.parent = this; |
| } |
| body = v.transform(body); |
| body.parent = this; |
| } |
| |
| @override |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| guard = v.visitDartType(guard, cannotRemoveSentinel); |
| if (exception != null) { |
| exception = v.transformOrRemoveVariable(exception!) as CatchVariable; |
| exception?.parent = this; |
| } |
| if (stackTrace != null) { |
| stackTrace = v.transformOrRemoveVariable(stackTrace!) as CatchVariable; |
| stackTrace?.parent = this; |
| } |
| body = v.transform(body); |
| body.parent = this; |
| } |
| |
| @override |
| String toString() { |
| return "Catch(${toStringInternal()})"; |
| } |
| |
| @override |
| String toText(AstTextStrategy strategy) { |
| AstPrinter printer = new AstPrinter(strategy); |
| toTextInternal(printer); |
| return printer.getText(); |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| bool isImplicitType(DartType type) { |
| if (type is DynamicType) { |
| return true; |
| } |
| if (type is InterfaceType && |
| type.classReference.node != null && |
| type.classNode.name == 'Object') { |
| Uri uri = type.classNode.enclosingLibrary.importUri; |
| return uri.isScheme('dart') && |
| uri.path == 'core' && |
| type.nullability == Nullability.nonNullable; |
| } |
| return false; |
| } |
| |
| if (exception != null) { |
| if (!isImplicitType(guard)) { |
| printer.write('on '); |
| printer.writeType(guard); |
| printer.write(' '); |
| } |
| printer.write('catch ('); |
| printer.writeVariableInitialization( |
| exception!, |
| includeModifiersAndType: false, |
| includeInitializer: false, |
| ); |
| if (stackTrace != null) { |
| printer.write(', '); |
| printer.writeVariableInitialization( |
| stackTrace!, |
| includeModifiersAndType: false, |
| ); |
| } |
| printer.write(') '); |
| } else { |
| printer.write('on '); |
| printer.writeType(guard); |
| printer.write(' '); |
| } |
| printer.writeStatement(body); |
| } |
| } |
| |
| class TryFinally extends Statement { |
| Statement body; |
| Statement finalizer; |
| |
| new(this.body, this.finalizer) { |
| body.parent = this; |
| finalizer.parent = this; |
| } |
| |
| @override |
| R accept<R>(StatementVisitor<R> v) => v.visitTryFinally(this); |
| |
| @override |
| R accept1<R, A>(StatementVisitor1<R, A> v, A arg) => |
| v.visitTryFinally(this, arg); |
| |
| @override |
| void visitChildren(Visitor v) { |
| body.accept(v); |
| finalizer.accept(v); |
| } |
| |
| @override |
| void transformChildren(Transformer v) { |
| body = v.transform(body); |
| body.parent = this; |
| finalizer = v.transform(finalizer); |
| finalizer.parent = this; |
| } |
| |
| @override |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| body = v.transform(body); |
| body.parent = this; |
| finalizer = v.transform(finalizer); |
| finalizer.parent = this; |
| } |
| |
| @override |
| String toString() { |
| return "TryFinally(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| if (body is! TryCatch) { |
| // This is a `try {} catch (e) {} finally {}`. Avoid repeating `try`. |
| printer.write('try '); |
| } |
| printer.writeStatement(body); |
| printer.write(' finally '); |
| printer.writeStatement(finalizer); |
| } |
| } |
| |
| /// Statement of form `yield x` or `yield* x`. |
| class YieldStatement extends Statement { |
| Expression expression; |
| int flags = 0; |
| |
| new(this.expression, {bool isYieldStar = false}) { |
| expression.parent = this; |
| this.isYieldStar = isYieldStar; |
| } |
| |
| static const int FlagYieldStar = 1 << 0; |
| |
| bool get isYieldStar => flags & FlagYieldStar != 0; |
| |
| void set isYieldStar(bool value) { |
| flags = value ? (flags | FlagYieldStar) : (flags & ~FlagYieldStar); |
| } |
| |
| @override |
| R accept<R>(StatementVisitor<R> v) => v.visitYieldStatement(this); |
| |
| @override |
| R accept1<R, A>(StatementVisitor1<R, A> v, A arg) => |
| v.visitYieldStatement(this, arg); |
| |
| @override |
| void visitChildren(Visitor v) { |
| expression.accept(v); |
| } |
| |
| @override |
| void transformChildren(Transformer v) { |
| expression = v.transform(expression); |
| expression.parent = this; |
| } |
| |
| @override |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| expression = v.transform(expression); |
| expression.parent = this; |
| } |
| |
| @override |
| String toString() { |
| return "YieldStatement(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.write('yield'); |
| if (isYieldStar) { |
| printer.write('*'); |
| } |
| printer.write(' '); |
| printer.writeExpression(expression); |
| printer.write(';'); |
| } |
| } |
| |
| /// Declaration of a local variable. |
| class VariableStatement extends Statement { |
| /// The declared variable. |
| VariableDeclaration declaration; |
| |
| new(this.declaration) { |
| declaration.parent = this; |
| } |
| |
| @override |
| R accept<R>(StatementVisitor<R> v) => v.visitVariableStatement(this); |
| |
| @override |
| R accept1<R, A>(StatementVisitor1<R, A> v, A arg) => |
| v.visitVariableStatement(this, arg); |
| |
| @override |
| void visitChildren(Visitor v) { |
| declaration.accept(v); |
| } |
| |
| @override |
| void transformChildren(Transformer v) { |
| declaration = v.transform(declaration)..parent = this; |
| } |
| |
| @override |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| declaration = v.transformOrRemove(declaration, cannotRemoveSentinel)! |
| ..parent = this; |
| } |
| |
| @override |
| String toString() { |
| return "VariableStatement(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.writeVariableDeclaration(declaration); |
| printer.write(';'); |
| } |
| } |
| |
| /// Declaration a local function. |
| /// |
| /// The body of the function may use [variable] as its self-reference. |
| class FunctionDeclaration extends Statement implements LocalFunction { |
| LocalFunctionVariable variable; // Is final and has no initializer. |
| |
| @override |
| FunctionNode function; |
| |
| @override |
| LocalFunctionId id = LocalFunctionId.invalid; |
| |
| new(this.variable, this.function) { |
| variable.parent = this; |
| function.parent = this; |
| } |
| |
| @override |
| List<TypeParameter> get typeParameters => function.typeParameters; |
| |
| @override |
| R accept<R>(StatementVisitor<R> v) => v.visitFunctionDeclaration(this); |
| |
| @override |
| R accept1<R, A>(StatementVisitor1<R, A> v, A arg) => |
| v.visitFunctionDeclaration(this, arg); |
| |
| @override |
| void visitChildren(Visitor v) { |
| variable.accept(v); |
| function.accept(v); |
| } |
| |
| @override |
| void transformChildren(Transformer v) { |
| variable = v.transform(variable); |
| variable.parent = this; |
| function = v.transform(function); |
| function.parent = this; |
| } |
| |
| @override |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| variable = v.transform(variable); |
| variable.parent = this; |
| function = v.transform(function); |
| function.parent = this; |
| } |
| |
| @override |
| String toString() { |
| return "FunctionDeclaration(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.writeFunctionNode(function, printer.getVariableName(variable)); |
| if (function.body is ReturnStatement) { |
| printer.write(';'); |
| } |
| } |
| } |