| // Copyright (c) 2016, 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. | |
| library kernel.clone; | |
| import 'ast.dart'; | |
| import 'type_algebra.dart'; | |
| /// Visitor that return a clone of a tree, maintaining references to cloned | |
| /// objects. | |
| /// | |
| /// This class does not clone members. For that, use the | |
| /// [CloneVisitorWithMembers] and setup references properly. | |
| class CloneVisitorNotMembers | |
| with | |
| TreeVisitorExperimentExclusionMixin<TreeNode>, | |
| ExpressionVisitorExperimentExclusionMixin<TreeNode>, | |
| StatementVisitorExperimentExclusionMixin<TreeNode> | |
| implements TreeVisitor<TreeNode> { | |
| final Map<VariableBase, VariableBase> _variables = | |
| <VariableBase, VariableBase>{}; | |
| final Map<VariableContext, VariableContext> _variableContexts = | |
| <VariableContext, VariableContext>{}; | |
| final Map<LabeledStatement, LabeledStatement> labels = | |
| <LabeledStatement, LabeledStatement>{}; | |
| final Map<SwitchCase, SwitchCase> switchCases = <SwitchCase, SwitchCase>{}; | |
| final Map<TypeParameter, DartType> typeSubstitution; | |
| final Map<TypeParameter, TypeParameter> typeParams; | |
| bool cloneAnnotations; | |
| bool inScope = false; | |
| /// Creates an instance of the cloning visitor for Kernel ASTs. | |
| /// | |
| /// The boolean value of [cloneAnnotations] tells if the annotations on the | |
| /// outline elements in the source AST should be cloned to the target AST. The | |
| /// annotations in procedure bodies are cloned unconditionally. | |
| new({ | |
| Map<TypeParameter, DartType>? typeSubstitution, | |
| Map<TypeParameter, TypeParameter>? typeParams, | |
| Map<StructuralParameter, StructuralParameter>? structuralParameters, | |
| this.cloneAnnotations = true, | |
| }) : this.typeSubstitution = ensureMutable(typeSubstitution), | |
| this.typeParams = typeParams ?? <TypeParameter, TypeParameter>{}; | |
| static Map<TypeParameter, DartType> ensureMutable( | |
| Map<TypeParameter, DartType>? map, | |
| ) { | |
| // We need to mutate this map, so make sure we don't use a constant map. | |
| if (map == null || map.isEmpty) { | |
| return <TypeParameter, DartType>{}; | |
| } | |
| return map; | |
| } | |
| /// Returns the clone of [variable] or `null` if no clone has been created | |
| /// for variable. | |
| Variable? getVariableClone(Variable variable) { | |
| return _variables[variable] as Variable?; | |
| } | |
| /// Registers [clone] as the clone for [variable]. | |
| /// | |
| /// Returns the [clone]. | |
| V setVariableClone<V extends VariableBase>(V variable, V clone) { | |
| return _variables[variable] = clone; | |
| } | |
| @override | |
| TreeNode visitLibrary(Library node) { | |
| throw 'Cloning of libraries is not implemented'; | |
| } | |
| @override | |
| TreeNode visitClass(Class node) { | |
| throw 'Cloning of classes is not implemented'; | |
| } | |
| @override | |
| TreeNode visitExtension(Extension node) { | |
| throw 'Cloning of extensions is not implemented'; | |
| } | |
| @override | |
| TreeNode visitExtensionTypeDeclaration(ExtensionTypeDeclaration node) { | |
| throw 'Cloning of extension type declarations is not implemented'; | |
| } | |
| @override | |
| TreeNode visitConstructor(Constructor node) { | |
| throw 'Cloning of constructors is not implemented here'; | |
| } | |
| @override | |
| TreeNode visitProcedure(Procedure node) { | |
| throw 'Cloning of procedures is not implemented here'; | |
| } | |
| @override | |
| TreeNode visitField(Field node) { | |
| throw 'Cloning of fields is not implemented here'; | |
| } | |
| // The currently active file uri where we are cloning [TreeNode]s from. If | |
| // this is set to `null` we cannot clone file offsets to newly created nodes. | |
| // The [_cloneFileOffset] helper function will ensure this. | |
| Uri? _activeFileUri; | |
| // If we don't know the file uri we are cloning elements from, it's not safe | |
| // to clone file offsets either. | |
| int _cloneFileOffset(int fileOffset) { | |
| return _activeFileUri == null ? TreeNode.noOffset : fileOffset; | |
| } | |
| bool _assertFileUriTarget(TreeNode node, TreeNode clone) { | |
| if (node is FileUriNode && clone is FileUriNode) { | |
| if (node.fileUri != clone.fileUri) { | |
| assert( | |
| false, | |
| "Original and clone disagrees on file uri: " | |
| "${node.fileUri} vs ${clone.fileUri}", | |
| ); | |
| return false; | |
| } | |
| return true; | |
| } else if (node is! FileUriNode && clone is! FileUriNode) { | |
| return true; | |
| } | |
| assert(false, "Original and clone disagrees on being a file uri node."); | |
| return false; | |
| } | |
| T clone<T extends TreeNode>(T node) { | |
| final Uri? activeFileUriSaved = _activeFileUri; | |
| if (node is FileUriNode) { | |
| _activeFileUri = node.fileUri; | |
| } | |
| final TreeNode result = node.accept(this) | |
| ..fileOffset = _cloneFileOffset(node.fileOffset); | |
| assert(_assertFileUriTarget(node, result)); | |
| _activeFileUri = activeFileUriSaved; | |
| return result as T; | |
| } | |
| T? cloneOptional<T extends TreeNode>(T? node) { | |
| if (node == null) return null; | |
| final Uri? activeFileUriSaved = _activeFileUri; | |
| if (node is FileUriNode) { | |
| _activeFileUri = node.fileUri; | |
| } | |
| TreeNode? result = node.accept(this); | |
| if (result != null) { | |
| result.fileOffset = _cloneFileOffset(node.fileOffset); | |
| assert(_assertFileUriTarget(node, result)); | |
| } | |
| _activeFileUri = activeFileUriSaved; | |
| return result as T?; | |
| } | |
| /// Root entry point for cloning a subtree within the same context where the | |
| /// file offsets are valid. | |
| T cloneInContext<T extends TreeNode>(T node) { | |
| assert(_activeFileUri == null); | |
| _activeFileUri = _activeFileUriFromContext(node); | |
| final TreeNode result = clone<T>(node); | |
| _activeFileUri = null; | |
| return result as T; | |
| } | |
| Uri? _activeFileUriFromContext(TreeNode? node) { | |
| while (node != null) { | |
| if (node is FileUriNode) { | |
| return node.fileUri; | |
| } | |
| node = node.parent; | |
| } | |
| return null; | |
| } | |
| DartType visitType(DartType type) { | |
| return substitute(type, typeSubstitution); | |
| } | |
| Constant visitConstant(Constant constant) { | |
| return constant; | |
| } | |
| DartType? visitOptionalType(DartType? type) { | |
| return type == null ? null : substitute(type, typeSubstitution); | |
| } | |
| @override | |
| TreeNode visitInvalidExpression(InvalidExpression node) { | |
| return new InvalidExpression( | |
| node.message, | |
| node.expression != null ? clone(node.expression!) : null, | |
| ); | |
| } | |
| @override | |
| TreeNode visitVariableGet(VariableGet node) { | |
| Variable? variable = getVariableClone(node.variable); | |
| assert(variable != null, "Missing clone for variable ${node.variable}."); | |
| return new VariableGet(variable!, visitOptionalType(node.promotedType)); | |
| } | |
| @override | |
| TreeNode visitVariableSet(VariableSet node) { | |
| Variable? variable = getVariableClone(node.variable); | |
| assert(variable != null, "Missing clone for variable ${node.variable}."); | |
| return new VariableSet(variable!, clone(node.value)); | |
| } | |
| @override | |
| TreeNode visitAbstractSuperPropertyGet(AbstractSuperPropertyGet node) { | |
| return new AbstractSuperPropertyGet.byReference( | |
| clone(node.receiver), | |
| node.name, | |
| node.interfaceTargetReference, | |
| ); | |
| } | |
| @override | |
| TreeNode visitAbstractSuperPropertySet(AbstractSuperPropertySet node) { | |
| return new AbstractSuperPropertySet.byReference( | |
| clone(node.receiver), | |
| node.name, | |
| clone(node.value), | |
| node.interfaceTargetReference, | |
| ); | |
| } | |
| @override | |
| TreeNode visitSuperPropertyGet(SuperPropertyGet node) { | |
| return new SuperPropertyGet.byReference( | |
| clone(node.receiver), | |
| node.name, | |
| node.interfaceTargetReference, | |
| ); | |
| } | |
| @override | |
| TreeNode visitSuperPropertySet(SuperPropertySet node) { | |
| return new SuperPropertySet.byReference( | |
| clone(node.receiver), | |
| node.name, | |
| clone(node.value), | |
| node.interfaceTargetReference, | |
| ); | |
| } | |
| @override | |
| TreeNode visitStaticGet(StaticGet node) { | |
| return new StaticGet.byReference(node.targetReference); | |
| } | |
| @override | |
| TreeNode visitStaticSet(StaticSet node) { | |
| return new StaticSet.byReference(node.targetReference, clone(node.value)); | |
| } | |
| @override | |
| TreeNode visitAbstractSuperMethodInvocation( | |
| AbstractSuperMethodInvocation node, | |
| ) { | |
| return new AbstractSuperMethodInvocation.byReference( | |
| clone(node.receiver), | |
| node.name, | |
| clone(node.arguments), | |
| node.interfaceTargetReference, | |
| ); | |
| } | |
| @override | |
| TreeNode visitSuperMethodInvocation(SuperMethodInvocation node) { | |
| return new SuperMethodInvocation.byReference( | |
| clone(node.receiver), | |
| node.name, | |
| clone(node.arguments), | |
| node.interfaceTargetReference, | |
| ); | |
| } | |
| @override | |
| TreeNode visitStaticInvocation(StaticInvocation node) { | |
| return new StaticInvocation.byReference( | |
| node.targetReference, | |
| clone(node.arguments), | |
| isConst: node.isConst, | |
| ); | |
| } | |
| @override | |
| TreeNode visitConstructorInvocation(ConstructorInvocation node) { | |
| return new ConstructorInvocation.byReference( | |
| node.targetReference, | |
| clone(node.arguments), | |
| isConst: node.isConst, | |
| ); | |
| } | |
| @override | |
| TreeNode visitRedirectingFactoryInvocation( | |
| RedirectingFactoryInvocation node, | |
| ) { | |
| return new RedirectingFactoryInvocation.byReference( | |
| node.redirectingFactoryTargetReference, | |
| clone(node.expression), | |
| ); | |
| } | |
| @override | |
| TreeNode visitNot(Not node) { | |
| return new Not(clone(node.operand)); | |
| } | |
| @override | |
| TreeNode visitNullCheck(NullCheck node) { | |
| return new NullCheck(clone(node.operand)); | |
| } | |
| @override | |
| TreeNode visitLogicalExpression(LogicalExpression node) { | |
| return new LogicalExpression( | |
| clone(node.left), | |
| node.operatorEnum, | |
| clone(node.right), | |
| ); | |
| } | |
| @override | |
| TreeNode visitConditionalExpression(ConditionalExpression node) { | |
| return new ConditionalExpression( | |
| clone(node.condition), | |
| clone(node.then), | |
| clone(node.otherwise), | |
| visitType(node.staticType), | |
| ); | |
| } | |
| @override | |
| TreeNode visitStringConcatenation(StringConcatenation node) { | |
| return new StringConcatenation( | |
| ExpressionList.generate( | |
| node.expressions.length, | |
| (i) => clone(node.expressions[i]), | |
| ), | |
| ); | |
| } | |
| @override | |
| TreeNode visitListConcatenation(ListConcatenation node) { | |
| return new ListConcatenation( | |
| ExpressionList.generate(node.lists.length, (i) => clone(node.lists[i])), | |
| typeArgument: visitType(node.typeArgument), | |
| ); | |
| } | |
| @override | |
| TreeNode visitSetConcatenation(SetConcatenation node) { | |
| return new SetConcatenation( | |
| ExpressionList.generate(node.sets.length, (i) => clone(node.sets[i])), | |
| typeArgument: visitType(node.typeArgument), | |
| ); | |
| } | |
| @override | |
| TreeNode visitMapConcatenation(MapConcatenation node) { | |
| return new MapConcatenation( | |
| ExpressionList.generate(node.maps.length, (i) => clone(node.maps[i])), | |
| keyType: visitType(node.keyType), | |
| valueType: visitType(node.valueType), | |
| ); | |
| } | |
| @override | |
| TreeNode visitInstanceCreation(InstanceCreation node) { | |
| final Map<Reference, Expression> fieldValues = <Reference, Expression>{}; | |
| node.fieldValues.forEach((Reference fieldRef, Expression value) { | |
| fieldValues[fieldRef] = clone(value); | |
| }); | |
| return new InstanceCreation( | |
| node.classReference, | |
| DartTypeList.generate( | |
| node.typeArguments.length, | |
| (i) => visitType(node.typeArguments[i]), | |
| ), | |
| fieldValues, | |
| node.asserts.map(clone).toList(), | |
| ExpressionList.generate( | |
| node.unusedArguments.length, | |
| (i) => clone(node.unusedArguments[i]), | |
| ), | |
| ); | |
| } | |
| @override | |
| TreeNode visitFileUriExpression(FileUriExpression node) { | |
| return new FileUriExpression(clone(node.expression), _activeFileUri!); | |
| } | |
| @override | |
| TreeNode visitIsExpression(IsExpression node) { | |
| return new IsExpression(clone(node.operand), visitType(node.type)); | |
| } | |
| @override | |
| TreeNode visitAsExpression(AsExpression node) { | |
| return new AsExpression(clone(node.operand), visitType(node.type)) | |
| ..flags = node.flags; | |
| } | |
| @override | |
| TreeNode visitSymbolLiteral(SymbolLiteral node) { | |
| return new SymbolLiteral(node.value); | |
| } | |
| @override | |
| TreeNode visitTypeLiteral(TypeLiteral node) { | |
| return new TypeLiteral(visitType(node.type)); | |
| } | |
| @override | |
| TreeNode visitThisExpression(ThisExpression node) { | |
| return new ThisExpression(); | |
| } | |
| @override | |
| TreeNode visitRethrow(Rethrow node) { | |
| return new Rethrow(); | |
| } | |
| @override | |
| TreeNode visitThrow(Throw node) { | |
| return new Throw(clone(node.expression))..flags = node.flags; | |
| } | |
| @override | |
| TreeNode visitListLiteral(ListLiteral node) { | |
| return new ListLiteral( | |
| ExpressionList.generate( | |
| node.expressions.length, | |
| (i) => clone(node.expressions[i]), | |
| ), | |
| typeArgument: visitType(node.typeArgument), | |
| isConst: node.isConst, | |
| ); | |
| } | |
| @override | |
| TreeNode visitSetLiteral(SetLiteral node) { | |
| return new SetLiteral( | |
| ExpressionList.generate( | |
| node.expressions.length, | |
| (i) => clone(node.expressions[i]), | |
| ), | |
| typeArgument: visitType(node.typeArgument), | |
| isConst: node.isConst, | |
| ); | |
| } | |
| @override | |
| TreeNode visitMapLiteral(MapLiteral node) { | |
| return new MapLiteral( | |
| node.entries.map(clone).toList(), | |
| keyType: visitType(node.keyType), | |
| valueType: visitType(node.valueType), | |
| isConst: node.isConst, | |
| ); | |
| } | |
| @override | |
| TreeNode visitMapLiteralEntry(MapLiteralEntry node) { | |
| return new MapLiteralEntry(clone(node.key), clone(node.value)); | |
| } | |
| @override | |
| TreeNode visitRecordLiteral(RecordLiteral node) { | |
| return new RecordLiteral( | |
| ExpressionList.generate( | |
| node.positional.length, | |
| (i) => clone(node.positional[i]), | |
| ), | |
| NamedExpressionList.generate( | |
| node.named.length, | |
| (i) => clone(node.named[i]), | |
| ), | |
| visitType(node.recordType) as RecordType, | |
| isConst: node.isConst, | |
| ); | |
| } | |
| @override | |
| TreeNode visitAwaitExpression(AwaitExpression node) { | |
| return new AwaitExpression(clone(node.operand)) | |
| ..runtimeCheckType = node.runtimeCheckType != null | |
| ? visitType(node.runtimeCheckType!) | |
| : null; | |
| } | |
| @override | |
| TreeNode visitFunctionExpression(FunctionExpression node) { | |
| return new FunctionExpression(clone(node.function))..id = node.id; | |
| } | |
| @override | |
| TreeNode visitConstantExpression(ConstantExpression node) { | |
| if (node is FileUriConstantExpression) { | |
| return new FileUriConstantExpression( | |
| visitConstant(node.constant), | |
| type: visitType(node.type), | |
| fileUri: node.fileUri, | |
| ); | |
| } | |
| return new ConstantExpression( | |
| visitConstant(node.constant), | |
| visitType(node.type), | |
| ); | |
| } | |
| @override | |
| TreeNode visitStringLiteral(StringLiteral node) { | |
| return new StringLiteral(node.value); | |
| } | |
| @override | |
| TreeNode visitIntLiteral(IntLiteral node) { | |
| return new IntLiteral(node.value); | |
| } | |
| @override | |
| TreeNode visitDoubleLiteral(DoubleLiteral node) { | |
| return new DoubleLiteral(node.value); | |
| } | |
| @override | |
| TreeNode visitBoolLiteral(BoolLiteral node) { | |
| return new BoolLiteral(node.value); | |
| } | |
| @override | |
| TreeNode visitNullLiteral(NullLiteral node) { | |
| return new NullLiteral(); | |
| } | |
| @override | |
| TreeNode visitLet(Let node) { | |
| SyntheticVariable newVariable = clone(node.variable); | |
| return new Let( | |
| variable: newVariable, | |
| // ignore: deprecated_member_use_from_same_package | |
| value: newVariable.initializer!, | |
| body: clone(node.body), | |
| ); | |
| } | |
| @override | |
| TreeNode visitBlockExpression(BlockExpression node) { | |
| // The scope should be cloned before the rest of the node, since the | |
| // variables declared in the scope can appear in the node. | |
| Scope? clonedScope = _cloneScope(node.scope); | |
| return new BlockExpression(clone(node.body), clone(node.value)) | |
| ..scope = clonedScope; | |
| } | |
| @override | |
| TreeNode visitRecordIndexGet(RecordIndexGet node) { | |
| return new RecordIndexGet( | |
| clone(node.receiver), | |
| visitType(node.receiverType) as RecordType, | |
| node.index, | |
| ); | |
| } | |
| @override | |
| TreeNode visitRecordNameGet(RecordNameGet node) { | |
| return new RecordNameGet( | |
| clone(node.receiver), | |
| visitType(node.receiverType) as RecordType, | |
| node.name, | |
| ); | |
| } | |
| @override | |
| TreeNode visitExpressionStatement(ExpressionStatement node) { | |
| return new ExpressionStatement(clone(node.expression)); | |
| } | |
| Scope? _cloneScope(Scope? scope) { | |
| bool savedInScope = inScope; | |
| inScope = true; | |
| Scope? result; | |
| if (scope != null) { | |
| List<VariableContext> clonedContexts = []; | |
| for (VariableContext context in scope.contexts) { | |
| VariableContext clonedContext = new VariableContext( | |
| captureKind: context.captureKind, | |
| variables: [], | |
| ); | |
| for (VariableBase variable in context.variables) { | |
| // TODO(cstefantsova): Support [TypeVariable]s. | |
| variable as Variable; | |
| // If a variable appears in a context, it must be the point of | |
| // declaration of that variable, and it shouldn't be previously | |
| // cloned. | |
| assert(getVariableClone(variable) == null); | |
| Variable clonedVariable = clone(variable); | |
| clonedContext.addVariable(clonedVariable); | |
| } | |
| clonedContexts.add(clonedContext); | |
| _variableContexts[context] = clonedContext; | |
| } | |
| result = new Scope(contexts: clonedContexts); | |
| } | |
| inScope = savedInScope; | |
| return result; | |
| } | |
| List<VariableContext>? _cloneCapturedContexts( | |
| List<VariableContext>? capturedContexts, | |
| ) { | |
| return capturedContexts == null | |
| ? null | |
| : [ | |
| for (VariableContext context in capturedContexts) | |
| _variableContexts[context]!, | |
| ]; | |
| } | |
| @override | |
| TreeNode visitBlock(Block node) { | |
| // The scope should be cloned before the rest of the node, since the | |
| // variables declared in the scope can appear in the node. | |
| Scope? clonedScope = _cloneScope(node.scope); | |
| return new Block(node.statements.map(clone).toList()) | |
| ..scope = clonedScope | |
| ..fileEndOffset = _cloneFileOffset(node.fileEndOffset); | |
| } | |
| @override | |
| TreeNode visitAssertBlock(AssertBlock node) { | |
| return new AssertBlock(node.statements.map(clone).toList()); | |
| } | |
| @override | |
| TreeNode visitEmptyStatement(EmptyStatement node) { | |
| return new EmptyStatement(); | |
| } | |
| @override | |
| TreeNode visitAssertStatement(AssertStatement node) { | |
| return new AssertStatement( | |
| clone(node.condition), | |
| conditionStartOffset: node.conditionStartOffset, | |
| conditionEndOffset: node.conditionEndOffset, | |
| message: cloneOptional(node.message), | |
| ); | |
| } | |
| @override | |
| TreeNode visitLabeledStatement(LabeledStatement node) { | |
| LabeledStatement newNode = new LabeledStatement(null); | |
| labels[node] = newNode; | |
| newNode.body = clone(node.body)..parent = newNode; | |
| return newNode; | |
| } | |
| @override | |
| TreeNode visitBreakStatement(BreakStatement node) { | |
| return new BreakStatement(labels[node.target]!); | |
| } | |
| @override | |
| TreeNode visitWhileStatement(WhileStatement node) { | |
| // The scope should be cloned before the rest of the node, since the | |
| // variables declared in the scope can appear in the node. | |
| Scope? clonedScope = _cloneScope(node.scope); | |
| return new WhileStatement(clone(node.condition), clone(node.body)) | |
| ..scope = clonedScope; | |
| } | |
| @override | |
| TreeNode visitDoStatement(DoStatement node) { | |
| return new DoStatement(clone(node.body), clone(node.condition)); | |
| } | |
| @override | |
| TreeNode visitForStatement(ForStatement node) { | |
| // The scope should be cloned before the rest of the node, since the | |
| // variables declared in the scope can appear in the node. | |
| Scope? clonedScope = _cloneScope(node.scope); | |
| List<VariableDeclaration> variables = node.variables.map(clone).toList(); | |
| return new ForStatement( | |
| variables, | |
| cloneOptional(node.condition), | |
| ExpressionList.generate( | |
| node.updates.length, | |
| (i) => clone(node.updates[i]), | |
| ), | |
| clone(node.body), | |
| )..scope = clonedScope; | |
| } | |
| @override | |
| TreeNode visitForInStatement(ForInStatement node) { | |
| // The scope should be cloned before the rest of the node, since the | |
| // variables declared in the scope can appear in the node. | |
| Scope? clonedScope = _cloneScope(node.scope); | |
| DeclaredVariable newVariable = clone(node.variable); | |
| return new ForInStatement( | |
| newVariable, | |
| clone(node.iterable), | |
| clone(node.body), | |
| isAsync: node.isAsync, | |
| ) | |
| ..scope = clonedScope | |
| ..bodyOffset = node.bodyOffset; | |
| } | |
| @override | |
| TreeNode visitSwitchStatement(SwitchStatement node) { | |
| for (SwitchCase switchCase in node.cases) { | |
| switchCases[switchCase] = new SwitchCase( | |
| new ExpressionList.generate( | |
| switchCase.expressions.length, | |
| (int i) => clone(switchCase.expressions[i]), | |
| ), | |
| new List<int>.of(switchCase.expressionOffsets), | |
| dummyStatement, | |
| isDefault: switchCase.isDefault, | |
| ); | |
| } | |
| return new SwitchStatement( | |
| clone(node.expression), | |
| node.cases.map(clone).toList(), | |
| isExplicitlyExhaustive: node.isExplicitlyExhaustive, | |
| )..expressionTypeInternal = visitOptionalType(node.expressionTypeInternal); | |
| } | |
| @override | |
| TreeNode visitSwitchCase(SwitchCase node) { | |
| SwitchCase switchCase = switchCases[node]!; | |
| switchCase.body = clone(node.body)..parent = switchCase; | |
| return switchCase; | |
| } | |
| @override | |
| TreeNode visitContinueSwitchStatement(ContinueSwitchStatement node) { | |
| return new ContinueSwitchStatement(switchCases[node.target]!); | |
| } | |
| @override | |
| TreeNode visitIfStatement(IfStatement node) { | |
| return new IfStatement( | |
| clone(node.condition), | |
| clone(node.then), | |
| cloneOptional(node.otherwise), | |
| ); | |
| } | |
| @override | |
| TreeNode visitReturnStatement(ReturnStatement node) { | |
| return new ReturnStatement(cloneOptional(node.expression)); | |
| } | |
| @override | |
| TreeNode visitTryCatch(TryCatch node) { | |
| return new TryCatch( | |
| clone(node.body), | |
| node.catches.map(clone).toList(), | |
| isSynthetic: node.isSynthetic, | |
| ); | |
| } | |
| @override | |
| TreeNode visitCatch(Catch node) { | |
| // The scope should be cloned before the rest of the node, since the | |
| // variables declared in the scope can appear in the node. | |
| Scope? clonedScope = _cloneScope(node.scope); | |
| CatchVariable? newException = cloneOptional(node.exception); | |
| CatchVariable? newStackTrace = cloneOptional(node.stackTrace); | |
| return new Catch( | |
| newException, | |
| clone(node.body), | |
| stackTrace: newStackTrace, | |
| guard: visitType(node.guard), | |
| )..scope = clonedScope; | |
| } | |
| @override | |
| TreeNode visitTryFinally(TryFinally node) { | |
| return new TryFinally(clone(node.body), clone(node.finalizer)); | |
| } | |
| @override | |
| TreeNode visitYieldStatement(YieldStatement node) { | |
| return new YieldStatement(clone(node.expression))..flags = node.flags; | |
| } | |
| @override | |
| TreeNode visitVariable(Variable node) { | |
| throw new UnimplementedError( | |
| "${this.runtimeType}.visitVariableDeclaration", | |
| ); | |
| } | |
| @override | |
| TreeNode visitPositionalParameter(PositionalParameter node) { | |
| return _variables[node] ?? | |
| setVariableClone( | |
| node, | |
| new PositionalParameter( | |
| parameterName: node.parameterName, | |
| type: visitType(node.type), | |
| defaultValue: cloneOptional(node.defaultValue), | |
| ) | |
| ..flags = node.flags | |
| ..annotations = _cloneAnnotations(node) | |
| ..fileEqualsOffset = _cloneFileOffset(node.fileEqualsOffset), | |
| ); | |
| } | |
| @override | |
| TreeNode visitNamedParameter(NamedParameter node) { | |
| return _variables[node] ?? | |
| setVariableClone( | |
| node, | |
| new NamedParameter( | |
| parameterName: node.parameterName, | |
| type: visitType(node.type), | |
| defaultValue: cloneOptional(node.defaultValue), | |
| ) | |
| ..flags = node.flags | |
| ..annotations = _cloneAnnotations(node) | |
| ..fileEqualsOffset = _cloneFileOffset(node.fileEqualsOffset), | |
| ); | |
| } | |
| @override | |
| TreeNode visitCatchVariable(CatchVariable node) { | |
| return _variables[node] ?? | |
| setVariableClone( | |
| node, | |
| new CatchVariable( | |
| name: node.catchVariableName, | |
| type: visitOptionalType(node.type), | |
| ) | |
| ..flags = node.flags | |
| ..annotations = _cloneAnnotations(node) | |
| ..fileEqualsOffset = _cloneFileOffset(node.fileEqualsOffset), | |
| ); | |
| } | |
| TreeNode _cloneVariable<V extends Variable>( | |
| V node, | |
| TreeNode Function(V, {Expression? initializer}) clone, | |
| ) { | |
| V? clonedVariable = _variables[node] as V?; | |
| if (clonedVariable == null) { | |
| return clone( | |
| node, | |
| // Don't clone initializers of variables during scope cloning. The | |
| // variables in the scope may refer to each other through their | |
| // initializers, and the current layout of variable contexts within | |
| // scopes doesn't allow for easy following of the variable | |
| // declaration order. Therefore, cloning of initializers is | |
| // postponed until the points of variable declarations further in | |
| // the code. | |
| initializer: inScope ? null : cloneOptional(node.initializer), | |
| ); | |
| } else { | |
| // The variables cloned in [_cloneScope] don't have initializers until | |
| // the point of the variable declaration, where this visit method is | |
| // called the second time. At that point, the initializer should be | |
| // cloned. | |
| assert(!inScope && clonedVariable.initializer == null); | |
| clonedVariable.initializer = cloneOptional(node.initializer) | |
| ?..parent = clonedVariable; | |
| return clonedVariable; | |
| } | |
| } | |
| @override | |
| TreeNode visitLocalVariable(LocalVariable node) { | |
| return _cloneVariable(node, ( | |
| LocalVariable node, { | |
| Expression? initializer, | |
| }) { | |
| return setVariableClone( | |
| node, | |
| new LocalVariable( | |
| name: node.name, | |
| type: visitOptionalType(node.type), | |
| initializer: initializer, | |
| ) | |
| ..flags = node.flags | |
| ..annotations = _cloneAnnotations(node) | |
| ..fileEqualsOffset = _cloneFileOffset(node.fileEqualsOffset), | |
| ); | |
| }); | |
| } | |
| @override | |
| TreeNode visitLocalFunctionVariable(LocalFunctionVariable node) { | |
| return _variables[node] ?? | |
| setVariableClone( | |
| node, | |
| new LocalFunctionVariable( | |
| name: node.name, | |
| type: visitOptionalType(node.type), | |
| ) | |
| ..flags = node.flags | |
| ..annotations = _cloneAnnotations(node) | |
| ..fileEqualsOffset = _cloneFileOffset(node.fileEqualsOffset), | |
| ); | |
| } | |
| @override | |
| TreeNode visitConstVariable(ConstVariable node) { | |
| return _variables[node] ?? | |
| setVariableClone( | |
| node, | |
| new ConstVariable( | |
| name: node.name, | |
| type: visitOptionalType(node.type), | |
| value: cloneOptional(node.value), | |
| ) | |
| ..flags = node.flags | |
| ..annotations = _cloneAnnotations(node) | |
| ..fileEqualsOffset = _cloneFileOffset(node.fileEqualsOffset), | |
| ); | |
| } | |
| @override | |
| TreeNode visitLateVariable(LateVariable node) { | |
| return _cloneVariable(node, (LateVariable node, {Expression? initializer}) { | |
| return setVariableClone( | |
| node, | |
| new LateVariable( | |
| name: node.name, | |
| type: visitOptionalType(node.type), | |
| initialValue: initializer, | |
| ) | |
| ..flags = node.flags | |
| ..annotations = _cloneAnnotations(node) | |
| ..fileEqualsOffset = _cloneFileOffset(node.fileEqualsOffset), | |
| ); | |
| }); | |
| } | |
| @override | |
| TreeNode visitSyntheticVariable(SyntheticVariable node) { | |
| return _cloneVariable(node, ( | |
| SyntheticVariable node, { | |
| Expression? initializer, | |
| }) { | |
| return setVariableClone( | |
| node, | |
| SyntheticVariable( | |
| cosmeticName: node.cosmeticName, | |
| type: visitType(node.type), | |
| initializer: initializer, | |
| ) | |
| ..flags = node.flags | |
| ..annotations = _cloneAnnotations(node) | |
| ..fileEqualsOffset = _cloneFileOffset(node.fileEqualsOffset), | |
| ); | |
| }); | |
| } | |
| @override | |
| TreeNode visitThisVariable(ThisVariable node) { | |
| return _variables[node] ?? | |
| setVariableClone( | |
| node, | |
| new ThisVariable(type: visitType(node.type)) | |
| ..flags = node.flags | |
| ..annotations = _cloneAnnotations(node) | |
| ..fileEqualsOffset = _cloneFileOffset(node.fileEqualsOffset), | |
| ); | |
| } | |
| @override | |
| TreeNode visitVariableStatement(VariableStatement node) { | |
| return new VariableStatement(clone(node.declaration)) | |
| ..fileOffset = _cloneFileOffset(node.fileOffset); | |
| } | |
| @override | |
| TreeNode visitVariableDeclaration(VariableDeclaration node) { | |
| return new VariableDeclaration(clone(node.variable)) | |
| ..capturedContexts = _cloneCapturedContexts(node.capturedContexts) | |
| ..fileOffset = _cloneFileOffset(node.fileOffset); | |
| } | |
| List<Expression> _cloneAnnotations(Annotatable node) { | |
| return cloneAnnotations && !node.annotations.isEmpty | |
| ? node.annotations.map(clone).toList() | |
| : const <Expression>[]; | |
| } | |
| @override | |
| TreeNode visitFunctionDeclaration(FunctionDeclaration node) { | |
| LocalFunctionVariable newVariable = clone(node.variable); | |
| // Create the declaration before cloning the body to support recursive | |
| // [LocalFunctionInvocation] nodes. | |
| FunctionDeclaration declaration = new FunctionDeclaration( | |
| newVariable, | |
| dummyFunctionNode, | |
| )..id = node.id; | |
| FunctionNode functionNode = clone(node.function); | |
| declaration.function = functionNode..parent = declaration; | |
| return declaration; | |
| } | |
| void prepareTypeParameters(List<TypeParameter> typeParameters) { | |
| for (int i = 0; i < typeParameters.length; i++) { | |
| TypeParameter node = typeParameters[i]; | |
| TypeParameter? newNode = typeParams[node]; | |
| if (newNode == null) { | |
| newNode = new TypeParameter(node.name); | |
| typeParams[node] = newNode; | |
| typeSubstitution[node] = new TypeParameterType( | |
| newNode, | |
| node.computeNullabilityFromBound(), | |
| ); | |
| } | |
| } | |
| } | |
| @override | |
| TypeParameter visitTypeParameter(TypeParameter node) { | |
| TypeParameter newNode = typeParams[node]!; | |
| newNode.bound = visitType(node.bound); | |
| newNode.defaultType = visitType(node.defaultType); | |
| return newNode | |
| ..annotations = cloneAnnotations && !node.annotations.isEmpty | |
| ? node.annotations.map(clone).toList() | |
| : const <Expression>[] | |
| ..flags = node.flags; | |
| } | |
| Statement? cloneFunctionNodeBody(FunctionNode node) { | |
| bool savedCloneAnnotations = this.cloneAnnotations; | |
| try { | |
| this.cloneAnnotations = true; | |
| return cloneOptional(node.body); | |
| } finally { | |
| this.cloneAnnotations = savedCloneAnnotations; | |
| } | |
| } | |
| @override | |
| TreeNode visitFunctionNode(FunctionNode node) { | |
| prepareTypeParameters(node.typeParameters); | |
| // The scope should be cloned before the rest of the node, since the | |
| // variables declared in the scope can appear in the node. | |
| Scope? clonedScope = _cloneScope(node.scope); | |
| TypeParameterList typeParameters = TypeParameterList.generate( | |
| node.typeParameters.length, | |
| (i) => clone(node.typeParameters[i]), | |
| ); | |
| PositionalParameterList positional = PositionalParameterList.generate( | |
| node.positionalParameters.length, | |
| (i) => clone(node.positionalParameters[i]), | |
| ); | |
| NamedParameterList named = NamedParameterList.generate( | |
| node.namedParameters.length, | |
| (i) => clone(node.namedParameters[i]), | |
| ); | |
| ThisVariable? thisVariable = cloneOptional(node.thisVariable); | |
| final DartType? futureValueType = node.emittedValueType != null | |
| ? visitType(node.emittedValueType!) | |
| : null; | |
| return new FunctionNode( | |
| cloneFunctionNodeBody(node), | |
| typeParameters: typeParameters, | |
| positionalParameters: positional, | |
| namedParameters: named, | |
| thisVariable: thisVariable, | |
| requiredParameterCount: node.requiredParameterCount, | |
| returnType: visitType(node.returnType), | |
| asyncMarker: node.asyncMarker, | |
| dartAsyncMarker: node.dartAsyncMarker, | |
| emittedValueType: futureValueType, | |
| ) | |
| ..scope = clonedScope | |
| ..capturedContexts = _cloneCapturedContexts(node.capturedContexts) | |
| ..fileEndOffset = _cloneFileOffset(node.fileEndOffset); | |
| } | |
| @override | |
| TreeNode visitArguments(Arguments node) { | |
| return new Arguments( | |
| ExpressionList.generate( | |
| node.positional.length, | |
| (i) => clone(node.positional[i]), | |
| ), | |
| types: DartTypeList.generate( | |
| node.types.length, | |
| (i) => visitType(node.types[i]), | |
| ), | |
| named: NamedExpressionList.generate( | |
| node.named.length, | |
| (i) => clone(node.named[i]), | |
| ), | |
| ); | |
| } | |
| @override | |
| TreeNode visitNamedExpression(NamedExpression node) { | |
| return new NamedExpression(node.name, clone(node.value)); | |
| } | |
| TreeNode _unsupportedNode(TreeNode node) { | |
| throw 'Cloning Kernel non-members is not supported. ' | |
| 'Tried cloning $node'; | |
| } | |
| @override | |
| TreeNode visitAssertInitializer(AssertInitializer node) { | |
| return new AssertInitializer(clone(node.statement)); | |
| } | |
| @override | |
| TreeNode visitCheckLibraryIsLoaded(CheckLibraryIsLoaded node) { | |
| return new CheckLibraryIsLoaded(node.import); | |
| } | |
| @override | |
| TreeNode visitCombinator(Combinator node) { | |
| return _unsupportedNode(node); | |
| } | |
| @override | |
| TreeNode visitFieldInitializer(FieldInitializer node) { | |
| return new FieldInitializer.byReference( | |
| node.fieldReference, | |
| clone(node.value), | |
| ); | |
| } | |
| @override | |
| TreeNode visitInstantiation(Instantiation node) { | |
| return new Instantiation( | |
| clone(node.expression), | |
| DartTypeList.generate( | |
| node.typeArguments.length, | |
| (i) => visitType(node.typeArguments[i]), | |
| ), | |
| ); | |
| } | |
| @override | |
| TreeNode visitInvalidInitializer(InvalidInitializer node) { | |
| return new InvalidInitializer(node.message)..flags = node.flags; | |
| } | |
| @override | |
| TreeNode visitLibraryDependency(LibraryDependency node) { | |
| return _unsupportedNode(node); | |
| } | |
| @override | |
| TreeNode visitLibraryPart(LibraryPart node) { | |
| return _unsupportedNode(node); | |
| } | |
| @override | |
| TreeNode visitLoadLibrary(LoadLibrary node) { | |
| return new LoadLibrary(node.import); | |
| } | |
| @override | |
| TreeNode visitLocalInitializer(LocalInitializer node) { | |
| SyntheticVariable variable = clone(node.variable); | |
| // ignore: deprecated_member_use_from_same_package | |
| return new LocalInitializer(variable, variable.initializer!); | |
| } | |
| @override | |
| TreeNode visitComponent(Component node) { | |
| return _unsupportedNode(node); | |
| } | |
| @override | |
| TreeNode visitRedirectingInitializer(RedirectingInitializer node) { | |
| return new RedirectingInitializer.byReference( | |
| node.targetReference, | |
| clone(node.arguments), | |
| ); | |
| } | |
| @override | |
| TreeNode visitSuperInitializer(SuperInitializer node) { | |
| return new SuperInitializer.byReference( | |
| node.targetReference, | |
| clone(node.arguments), | |
| ); | |
| } | |
| @override | |
| TreeNode visitTypedef(Typedef node) { | |
| return _unsupportedNode(node); | |
| } | |
| @override | |
| TreeNode visitDynamicGet(DynamicGet node) { | |
| return new DynamicGet(node.kind, clone(node.receiver), node.name); | |
| } | |
| @override | |
| TreeNode visitDynamicInvocation(DynamicInvocation node) { | |
| return new DynamicInvocation( | |
| node.kind, | |
| clone(node.receiver), | |
| node.name, | |
| clone(node.arguments), | |
| )..flags = node.flags; | |
| } | |
| @override | |
| TreeNode visitDynamicSet(DynamicSet node) { | |
| return new DynamicSet( | |
| node.kind, | |
| clone(node.receiver), | |
| node.name, | |
| clone(node.value), | |
| ); | |
| } | |
| @override | |
| TreeNode visitEqualsCall(EqualsCall node) { | |
| return new EqualsCall.byReference( | |
| clone(node.left), | |
| clone(node.right), | |
| functionType: visitType(node.functionType) as FunctionType, | |
| interfaceTargetReference: node.interfaceTargetReference, | |
| ); | |
| } | |
| @override | |
| TreeNode visitEqualsNull(EqualsNull node) { | |
| return new EqualsNull(clone(node.expression)); | |
| } | |
| @override | |
| TreeNode visitFunctionInvocation(FunctionInvocation node) { | |
| return new FunctionInvocation( | |
| node.kind, | |
| clone(node.receiver), | |
| clone(node.arguments), | |
| functionType: visitOptionalType(node.functionType) as FunctionType?, | |
| ); | |
| } | |
| @override | |
| TreeNode visitInstanceGet(InstanceGet node) { | |
| return new InstanceGet.byReference( | |
| node.kind, | |
| clone(node.receiver), | |
| node.name, | |
| resultType: visitType(node.resultType), | |
| interfaceTargetReference: node.interfaceTargetReference, | |
| ); | |
| } | |
| @override | |
| TreeNode visitInstanceInvocation(InstanceInvocation node) { | |
| return new InstanceInvocation.byReference( | |
| node.kind, | |
| clone(node.receiver), | |
| node.name, | |
| clone(node.arguments), | |
| functionType: visitType(node.functionType) as FunctionType, | |
| interfaceTargetReference: node.interfaceTargetReference, | |
| )..flags = node.flags; | |
| } | |
| @override | |
| TreeNode visitInstanceGetterInvocation(InstanceGetterInvocation node) { | |
| return new InstanceGetterInvocation.byReference( | |
| node.kind, | |
| clone(node.receiver), | |
| node.name, | |
| clone(node.arguments), | |
| functionType: visitOptionalType(node.functionType) as FunctionType?, | |
| interfaceTargetReference: node.interfaceTargetReference, | |
| ); | |
| } | |
| @override | |
| TreeNode visitInstanceSet(InstanceSet node) { | |
| return new InstanceSet.byReference( | |
| node.kind, | |
| clone(node.receiver), | |
| node.name, | |
| clone(node.value), | |
| interfaceTargetReference: node.interfaceTargetReference, | |
| ); | |
| } | |
| @override | |
| TreeNode visitInstanceTearOff(InstanceTearOff node) { | |
| return new InstanceTearOff.byReference( | |
| node.kind, | |
| clone(node.receiver), | |
| node.name, | |
| resultType: visitType(node.resultType), | |
| interfaceTargetReference: node.interfaceTargetReference, | |
| ); | |
| } | |
| @override | |
| TreeNode visitLocalFunctionInvocation(LocalFunctionInvocation node) { | |
| return new LocalFunctionInvocation( | |
| getVariableClone(node.variable) as LocalFunctionVariable, | |
| clone(node.arguments), | |
| functionType: visitType(node.functionType) as FunctionType, | |
| ); | |
| } | |
| @override | |
| TreeNode visitStaticTearOff(StaticTearOff node) { | |
| return new StaticTearOff.byReference(node.targetReference); | |
| } | |
| @override | |
| TreeNode visitFunctionTearOff(FunctionTearOff node) { | |
| return new FunctionTearOff(clone(node.receiver)); | |
| } | |
| @override | |
| TreeNode visitConstructorTearOff(ConstructorTearOff node) { | |
| return new ConstructorTearOff.byReference(node.targetReference); | |
| } | |
| @override | |
| TreeNode visitRedirectingFactoryTearOff(RedirectingFactoryTearOff node) { | |
| return new RedirectingFactoryTearOff.byReference(node.targetReference); | |
| } | |
| @override | |
| TreeNode visitTypedefTearOff(TypedefTearOff node) { | |
| return new TypedefTearOff( | |
| node.structuralParameters, | |
| clone(node.expression), | |
| DartTypeList.generate( | |
| node.typeArguments.length, | |
| (i) => visitType(node.typeArguments[i]), | |
| ), | |
| ); | |
| } | |
| @override | |
| TreeNode visitAndPattern(AndPattern node) { | |
| return new AndPattern(clone(node.left), clone(node.right)); | |
| } | |
| @override | |
| TreeNode visitAssignedVariablePattern(AssignedVariablePattern node) { | |
| return new AssignedVariablePattern( | |
| variableName: node.variableName, | |
| variableType: visitType(node.variableType), | |
| writeVariable: getVariableClone(node.writeVariable)!, | |
| matchedValueType: visitType(node.matchedValueType), | |
| needsCast: node.needsCast, | |
| hasObservableEffect: node.hasObservableEffect, | |
| ); | |
| } | |
| @override | |
| TreeNode visitCastPattern(CastPattern node) { | |
| return new CastPattern(clone(node.pattern), visitType(node.type)); | |
| } | |
| @override | |
| TreeNode visitConstantPattern(ConstantPattern node) { | |
| return new ConstantPattern.byReference( | |
| expression: clone(node.expression), | |
| expressionType: visitType(node.expressionType), | |
| equalsTargetReference: node.equalsTargetReference, | |
| equalsType: visitType(node.equalsType) as FunctionType, | |
| ); | |
| } | |
| @override | |
| TreeNode visitInvalidPattern(InvalidPattern node) { | |
| return new InvalidPattern( | |
| clone(node.invalidExpression), | |
| declaredVariables: node.declaredVariables | |
| .map((e) => getVariableClone(e) as DeclaredVariable) | |
| .toList(), | |
| ); | |
| } | |
| @override | |
| TreeNode visitListPattern(ListPattern node) { | |
| return new ListPattern.byReference( | |
| typeArgument: visitOptionalType(node.typeArgument), | |
| patterns: node.patterns.map(clone).toList(), | |
| requiredType: visitType(node.requiredType), | |
| matchedValueType: visitType(node.matchedValueType), | |
| lookupType: visitType(node.lookupType), | |
| lengthTargetReference: node.lengthTargetReference, | |
| lengthType: visitType(node.lengthType), | |
| lengthCheckTargetReference: node.lengthCheckTargetReference, | |
| lengthCheckType: visitType(node.lengthCheckType) as FunctionType, | |
| sublistTargetReference: node.sublistTargetReference, | |
| sublistType: visitType(node.sublistType) as FunctionType, | |
| minusTargetReference: node.minusTargetReference, | |
| minusType: visitType(node.minusType) as FunctionType, | |
| indexGetTargetReference: node.indexGetTargetReference, | |
| indexGetType: visitType(node.indexGetType) as FunctionType, | |
| flags: node.flags, | |
| ); | |
| } | |
| @override | |
| TreeNode visitMapPattern(MapPattern node) { | |
| return new MapPattern.byReference( | |
| keyType: visitOptionalType(node.keyType), | |
| valueType: visitOptionalType(node.valueType), | |
| entries: node.entries.map(clone).toList(), | |
| requiredType: visitType(node.requiredType), | |
| matchedValueType: visitType(node.matchedValueType), | |
| lookupType: visitType(node.lookupType), | |
| containsKeyTargetReference: node.containsKeyTargetReference, | |
| containsKeyType: visitType(node.containsKeyType) as FunctionType, | |
| indexGetTargetReference: node.indexGetTargetReference, | |
| indexGetType: visitType(node.indexGetType) as FunctionType, | |
| flags: node.flags, | |
| ); | |
| } | |
| @override | |
| TreeNode visitMapPatternEntry(MapPatternEntry node) { | |
| return new MapPatternEntry(clone(node.key), clone(node.value)); | |
| } | |
| @override | |
| TreeNode visitMapPatternRestEntry(MapPatternRestEntry node) { | |
| return new MapPatternRestEntry(); | |
| } | |
| @override | |
| TreeNode visitNamedPattern(NamedPattern node) { | |
| return new NamedPattern(node.name, clone(node.pattern)); | |
| } | |
| @override | |
| TreeNode visitNullAssertPattern(NullAssertPattern node) { | |
| return new NullAssertPattern(clone(node.pattern)); | |
| } | |
| @override | |
| TreeNode visitNullCheckPattern(NullCheckPattern node) { | |
| return new NullCheckPattern(clone(node.pattern)); | |
| } | |
| @override | |
| TreeNode visitObjectPattern(ObjectPattern node) { | |
| return new ObjectPattern( | |
| requiredType: visitType(node.requiredType), | |
| fields: node.fields.map(clone).toList(), | |
| matchedValueType: visitType(node.matchedValueType), | |
| needsCheck: node.needsCheck, | |
| ); | |
| } | |
| @override | |
| TreeNode visitOrPattern(OrPattern node) { | |
| return new OrPattern( | |
| clone(node.left), | |
| clone(node.right), | |
| orPatternJointVariables: node.orPatternJointVariables | |
| .map((e) => getVariableClone(e) as DeclaredVariable) | |
| .toList(), | |
| ); | |
| } | |
| @override | |
| TreeNode visitRecordPattern(RecordPattern node) { | |
| return new RecordPattern( | |
| patterns: node.patterns.map(clone).toList(), | |
| requiredType: visitType(node.requiredType) as RecordType, | |
| matchedValueType: visitType(node.matchedValueType), | |
| needsCheck: node.needsCheck, | |
| lookupType: visitType(node.lookupType) as RecordType, | |
| ); | |
| } | |
| @override | |
| TreeNode visitRelationalPattern(RelationalPattern node) { | |
| final DartTypeList? typeArguments = node.typeArguments; | |
| return new RelationalPattern( | |
| kind: node.kind, | |
| expression: clone(node.expression), | |
| expressionType: visitType(node.expressionType), | |
| matchedValueType: visitType(node.matchedValueType), | |
| accessKind: node.accessKind, | |
| name: node.name, | |
| target: node.target, | |
| typeArguments: typeArguments == null | |
| ? null | |
| : DartTypeList.generate( | |
| typeArguments.length, | |
| (i) => visitType(typeArguments[i]), | |
| ), | |
| functionType: visitOptionalType(node.functionType) as FunctionType?, | |
| ); | |
| } | |
| @override | |
| TreeNode visitRestPattern(RestPattern node) { | |
| return new RestPattern(cloneOptional(node.subPattern)); | |
| } | |
| @override | |
| TreeNode visitVariablePattern(VariablePattern node) { | |
| return new VariablePattern( | |
| type: visitOptionalType(node.type), | |
| variable: clone(node.variable), | |
| matchedValueType: visitType(node.matchedValueType), | |
| ); | |
| } | |
| @override | |
| TreeNode visitWildcardPattern(WildcardPattern node) { | |
| return new WildcardPattern(visitOptionalType(node.type)); | |
| } | |
| @override | |
| TreeNode visitPatternGuard(PatternGuard node) { | |
| return new PatternGuard(node.pattern, node.guard); | |
| } | |
| @override | |
| TreeNode visitPatternSwitchCase(PatternSwitchCase node) { | |
| return new PatternSwitchCase( | |
| new List<int>.of(node.caseOffsets), | |
| node.patternGuards.map(clone).toList(), | |
| clone(node.body), | |
| isDefault: node.isDefault, | |
| hasLabel: node.hasLabel, | |
| jointVariableDeclarations: node.jointVariableDeclarations | |
| .map(clone) | |
| .toList(), | |
| jointVariableFirstUseOffsets: node.jointVariableFirstUseOffsets == null | |
| ? null | |
| : new List<int>.of(node.jointVariableFirstUseOffsets!), | |
| jointVariableScope: _cloneScope(node.scope), | |
| ); | |
| } | |
| @override | |
| TreeNode visitPatternSwitchStatement(PatternSwitchStatement node) { | |
| return new PatternSwitchStatement( | |
| clone(node.expression), | |
| node.cases.map(clone).toList(), | |
| )..expressionTypeInternal = visitOptionalType(node.expressionTypeInternal); | |
| } | |
| @override | |
| TreeNode visitSwitchExpression(SwitchExpression node) { | |
| return new SwitchExpression( | |
| clone(node.expression), | |
| node.cases.map(clone).toList(), | |
| ) | |
| ..expressionType = visitOptionalType(node.expressionType) | |
| ..staticType = visitOptionalType(node.staticType); | |
| } | |
| @override | |
| TreeNode visitSwitchExpressionCase(SwitchExpressionCase node) { | |
| return new SwitchExpressionCase( | |
| clone(node.patternGuard), | |
| clone(node.expression), | |
| ); | |
| } | |
| @override | |
| TreeNode visitPatternVariableDeclaration(PatternVariableDeclaration node) { | |
| return new PatternVariableDeclaration( | |
| clone(node.pattern), | |
| clone(node.initializer), | |
| isFinal: node.isFinal, | |
| matchedValueType: visitType(node.matchedValueType), | |
| ); | |
| } | |
| @override | |
| TreeNode visitPatternAssignment(PatternAssignment node) { | |
| return new PatternAssignment( | |
| pattern: clone(node.pattern), | |
| expression: clone(node.expression), | |
| matchedValueType: visitType(node.matchedValueType), | |
| ); | |
| } | |
| @override | |
| TreeNode visitIfCaseStatement(IfCaseStatement node) { | |
| return new IfCaseStatement( | |
| expression: clone(node.expression), | |
| patternGuard: clone(node.patternGuard), | |
| then: clone(node.then), | |
| otherwise: cloneOptional(node.otherwise), | |
| matchedValueType: visitType(node.matchedValueType), | |
| ); | |
| } | |
| @override | |
| TreeNode visitAuxiliaryExpression(AuxiliaryExpression node) { | |
| throw new UnsupportedError( | |
| "Unsupported auxiliary expression ${node} (${node.runtimeType}).", | |
| ); | |
| } | |
| @override | |
| TreeNode visitAuxiliaryStatement(AuxiliaryStatement node) { | |
| throw new UnsupportedError( | |
| "Unsupported auxiliary statement ${node} (${node.runtimeType}).", | |
| ); | |
| } | |
| @override | |
| TreeNode visitAuxiliaryInitializer(AuxiliaryInitializer node) { | |
| throw new UnsupportedError( | |
| "Unsupported auxiliary initializer ${node} (${node.runtimeType}).", | |
| ); | |
| } | |
| @override | |
| TreeNode visitAuxiliaryPattern(AuxiliaryPattern node) { | |
| throw new UnsupportedError( | |
| "Unsupported auxiliary pattern ${node} (${node.runtimeType}).", | |
| ); | |
| } | |
| } | |
| /// Visitor that return a clone of a tree, maintaining references to cloned | |
| /// objects. | |
| /// | |
| /// It is safe to clone members, but cloning a class or library is not | |
| /// supported. | |
| class CloneVisitorWithMembers extends CloneVisitorNotMembers { | |
| new({ | |
| Map<TypeParameter, DartType>? typeSubstitution, | |
| Map<TypeParameter, TypeParameter>? typeParams, | |
| bool cloneAnnotations = true, | |
| }) : super( | |
| typeSubstitution: typeSubstitution, | |
| typeParams: typeParams, | |
| cloneAnnotations: cloneAnnotations, | |
| ); | |
| @override | |
| @Deprecated("When cloning with members one should use the specific cloneX") | |
| T clone<T extends TreeNode>(T node) { | |
| return super.clone(node); | |
| } | |
| Constructor cloneConstructor(Constructor node, Reference? reference) { | |
| final Uri? activeFileUriSaved = _activeFileUri; | |
| _activeFileUri = node.fileUri; | |
| Constructor result = | |
| new Constructor( | |
| super.clone(node.function), | |
| name: node.name, | |
| isConst: node.isConst, | |
| isExternal: node.isExternal, | |
| isSynthetic: node.isSynthetic, | |
| initializers: node.initializers.map(super.clone).toList(), | |
| containsSuperCalls: node.containsSuperCalls, | |
| fileUri: node.fileUri, | |
| reference: reference, | |
| ) | |
| ..annotations = cloneAnnotations && !node.annotations.isEmpty | |
| ? node.annotations.map(super.clone).toList() | |
| : const <Expression>[] | |
| ..fileOffset = _cloneFileOffset(node.fileOffset) | |
| ..fileEndOffset = _cloneFileOffset(node.fileEndOffset); | |
| setParents(result.annotations, result); | |
| assert(_assertFileUriTarget(node, result)); | |
| _activeFileUri = activeFileUriSaved; | |
| return result; | |
| } | |
| Procedure cloneProcedure(Procedure node, Reference? reference) { | |
| final Uri? activeFileUriSaved = _activeFileUri; | |
| _activeFileUri = node.fileUri; | |
| Procedure result = | |
| new Procedure( | |
| node.name, | |
| node.kind, | |
| super.clone(node.function), | |
| reference: reference, | |
| containsSuperCalls: node.containsSuperCalls, | |
| fileUri: node.fileUri, | |
| stubKind: node.stubKind, | |
| stubTarget: node.stubTarget, | |
| ) | |
| ..annotations = cloneAnnotations && !node.annotations.isEmpty | |
| ? node.annotations.map(super.clone).toList() | |
| : const <Expression>[] | |
| ..fileStartOffset = _cloneFileOffset(node.fileStartOffset) | |
| ..fileOffset = _cloneFileOffset(node.fileOffset) | |
| ..fileEndOffset = _cloneFileOffset(node.fileEndOffset) | |
| ..flags = node.flags; | |
| setParents(result.annotations, result); | |
| assert(_assertFileUriTarget(node, result)); | |
| _activeFileUri = activeFileUriSaved; | |
| return result; | |
| } | |
| Field cloneField( | |
| Field node, | |
| Reference? fieldReference, | |
| Reference? getterReference, | |
| Reference? setterReference, | |
| ) { | |
| final Uri? activeFileUriSaved = _activeFileUri; | |
| _activeFileUri = node.fileUri; | |
| Field result; | |
| if (node.hasSetter) { | |
| ThisVariable? clonedThisVariable = | |
| node.thisVariable?.accept<TreeNode>(this) as ThisVariable?; | |
| result = new Field.mutable( | |
| node.name, | |
| type: visitType(node.type), | |
| initializer: cloneOptional(node.initializer), | |
| containsSuperCalls: node.containsSuperCalls, | |
| fileUri: node.fileUri, | |
| fieldReference: fieldReference, | |
| getterReference: getterReference, | |
| setterReference: setterReference, | |
| )..thisVariable = clonedThisVariable; | |
| clonedThisVariable?.parent = result; | |
| } else { | |
| assert( | |
| setterReference == null, | |
| "Cannot use setter reference $setterReference " | |
| "for clone of an immutable field.", | |
| ); | |
| ThisVariable? clonedThisVariable = | |
| node.thisVariable?.accept<TreeNode>(this) as ThisVariable?; | |
| result = new Field.immutable( | |
| node.name, | |
| type: visitType(node.type), | |
| initializer: cloneOptional(node.initializer), | |
| containsSuperCalls: node.containsSuperCalls, | |
| fileUri: node.fileUri, | |
| fieldReference: fieldReference, | |
| getterReference: getterReference, | |
| )..thisVariable = clonedThisVariable; | |
| clonedThisVariable?.parent = result; | |
| } | |
| result | |
| ..annotations = cloneAnnotations && !node.annotations.isEmpty | |
| ? node.annotations.map(super.clone).toList() | |
| : const <Expression>[] | |
| ..fileOffset = _cloneFileOffset(node.fileOffset) | |
| ..fileEndOffset = _cloneFileOffset(node.fileEndOffset) | |
| ..flags = node.flags; | |
| setParents(result.annotations, result); | |
| assert(_assertFileUriTarget(node, result)); | |
| _activeFileUri = activeFileUriSaved; | |
| return result; | |
| } | |
| } | |
| /// Cloner that resolves super calls in mixin declarations. | |
| class MixinApplicationCloner extends CloneVisitorWithMembers { | |
| final Class mixinApplicationClass; | |
| Map<Name, Member>? _getterMap; | |
| Map<Name, Member>? _setterMap; | |
| new( | |
| this.mixinApplicationClass, { | |
| Map<TypeParameter, DartType>? typeSubstitution, | |
| Map<TypeParameter, TypeParameter>? typeParams, | |
| bool cloneAnnotations = true, | |
| }) : super( | |
| typeSubstitution: typeSubstitution, | |
| typeParams: typeParams, | |
| cloneAnnotations: cloneAnnotations, | |
| ); | |
| Member? _findSuperMember(Name name, {required bool isSetter}) { | |
| Map<Name, Member> cache; | |
| if (isSetter) { | |
| cache = _setterMap ??= {}; | |
| } else { | |
| cache = _getterMap ??= {}; | |
| } | |
| Member? member = cache[name]; | |
| if (member != null) { | |
| return member; | |
| } | |
| Class? superClass = mixinApplicationClass.superclass; | |
| while (superClass != null) { | |
| for (Procedure procedure in superClass.procedures) { | |
| if (procedure.name == name) { | |
| if (isSetter) { | |
| if (procedure.kind == ProcedureKind.Setter && | |
| !procedure.isAbstract) { | |
| return cache[name] = procedure; | |
| } | |
| } else { | |
| if (procedure.kind != ProcedureKind.Setter && | |
| !procedure.isAbstract) { | |
| return cache[name] = procedure; | |
| } | |
| } | |
| } | |
| } | |
| for (Field field in superClass.fields) { | |
| if (field.name == name) { | |
| if (isSetter) { | |
| if (field.hasSetter) { | |
| return cache[name] = field; | |
| } | |
| } else { | |
| return cache[name] = field; | |
| } | |
| } | |
| } | |
| superClass = superClass.superclass; | |
| } | |
| // TODO(johnniwinther): Throw instead when the CFE reports missing concrete | |
| // super members. | |
| // throw new StateError( | |
| // 'No super member found for $name in $mixinApplicationClass'); | |
| return null; | |
| } | |
| @override | |
| SuperMethodInvocation visitSuperMethodInvocation(SuperMethodInvocation node) { | |
| SuperMethodInvocation cloned = | |
| super.visitSuperMethodInvocation(node) as SuperMethodInvocation; | |
| cloned.interfaceTarget = | |
| _findSuperMember(node.name, isSetter: false) as Procedure? ?? | |
| // TODO(johnniwinther): Remove this when an error is reported instead. | |
| cloned.interfaceTarget; | |
| return cloned; | |
| } | |
| @override | |
| SuperPropertyGet visitSuperPropertyGet(SuperPropertyGet node) { | |
| SuperPropertyGet cloned = | |
| super.visitSuperPropertyGet(node) as SuperPropertyGet; | |
| cloned.interfaceTarget = | |
| _findSuperMember(node.name, isSetter: false) ?? | |
| // TODO(johnniwinther): Remove this when an error is reported instead. | |
| cloned.interfaceTarget; | |
| return cloned; | |
| } | |
| @override | |
| SuperPropertySet visitSuperPropertySet(SuperPropertySet node) { | |
| SuperPropertySet cloned = | |
| super.visitSuperPropertySet(node) as SuperPropertySet; | |
| cloned.interfaceTarget = | |
| _findSuperMember(node.name, isSetter: true) ?? | |
| // TODO(johnniwinther): Remove this when an error is reported instead. | |
| cloned.interfaceTarget; | |
| return cloned; | |
| } | |
| } | |
| class CloneProcedureWithoutBody extends CloneVisitorWithMembers { | |
| new({ | |
| Map<TypeParameter, DartType>? typeSubstitution, | |
| bool cloneAnnotations = true, | |
| }) : super( | |
| typeSubstitution: typeSubstitution, | |
| cloneAnnotations: cloneAnnotations, | |
| ); | |
| /// Clones procedure and replaces its parts with those passed as arguments | |
| /// | |
| /// [cloneProcedureWith] is a shortcut that can be helpful, for example, for | |
| /// transforming external procedures. | |
| /// | |
| /// Since this cloner clones procedures without the body, it's safe to replace | |
| /// the parameters of the cloned procedure, since they aren't referenced | |
| /// anywhere. If either [positionalParameters] or [namedParameters] are | |
| /// passed in, they are used in place of the freshly cloned | |
| /// [FunctionNode.positionalParameters] and [FunctionNode.namedParameters]. | |
| Procedure cloneProcedureWith( | |
| Procedure node, | |
| Reference? reference, { | |
| List<PositionalParameter>? positionalParameters, | |
| List<NamedParameter>? namedParameters, | |
| }) { | |
| Procedure cloned = cloneProcedure(node, reference); | |
| if (positionalParameters != null) { | |
| cloned.function.positionalParameters = new PositionalParameterList.from( | |
| positionalParameters, | |
| ); | |
| setParents(positionalParameters, cloned.function); | |
| } | |
| if (namedParameters != null) { | |
| cloned.function.namedParameters = new NamedParameterList.from( | |
| namedParameters, | |
| ); | |
| setParents(namedParameters, cloned.function); | |
| } | |
| return cloned; | |
| } | |
| @override | |
| Statement? cloneFunctionNodeBody(FunctionNode node) => null; | |
| } |