// 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, ExpressionVisitorExperimentExclusionMixin, StatementVisitorExperimentExclusionMixin implements TreeVisitor { final Map _variables = {}; final Map labels = {}; final Map switchCases = {}; final Map typeSubstitution; final Map typeParams; bool cloneAnnotations; /// 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? typeSubstitution, Map? typeParams, Map? structuralParameters, this.cloneAnnotations = true, }) : this.typeSubstitution = ensureMutable(typeSubstitution), this.typeParams = typeParams ?? {}; static Map ensureMutable( Map? map, ) { // We need to mutate this map, so make sure we don't use a constant map. if (map == null || map.isEmpty) { return {}; } return map; } /// Returns the clone of [variable] or `null` if no clone has been created /// for variable. Variable? getVariableClone(Variable variable) { return _variables[variable]; } /// Registers [clone] as the clone for [variable]. /// /// Returns the [clone]. Variable setVariableClone(Variable variable, Variable 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 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? 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 node) { assert(_activeFileUri == null); _activeFileUri = _activeFileUriFromContext(node); final TreeNode result = clone(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(node.expressions.map(clone).toList()); } @override TreeNode visitListConcatenation(ListConcatenation node) { return new ListConcatenation( node.lists.map(clone).toList(), typeArgument: visitType(node.typeArgument), ); } @override TreeNode visitSetConcatenation(SetConcatenation node) { return new SetConcatenation( node.sets.map(clone).toList(), typeArgument: visitType(node.typeArgument), ); } @override TreeNode visitMapConcatenation(MapConcatenation node) { return new MapConcatenation( node.maps.map(clone).toList(), keyType: visitType(node.keyType), valueType: visitType(node.valueType), ); } @override TreeNode visitInstanceCreation(InstanceCreation node) { final Map fieldValues = {}; node.fieldValues.forEach((Reference fieldRef, Expression value) { fieldValues[fieldRef] = clone(value); }); return new InstanceCreation( node.classReference, node.typeArguments.map(visitType).toList(), fieldValues, node.asserts.map(clone).toList(), node.unusedArguments.map(clone).toList(), ); } @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( node.expressions.map(clone).toList(), typeArgument: visitType(node.typeArgument), isConst: node.isConst, ); } @override TreeNode visitSetLiteral(SetLiteral node) { return new SetLiteral( node.expressions.map(clone).toList(), 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( node.positional.map(clone).toList(), node.named.map(clone).toList(), 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) { Variable newVariable = clone(node.variable); return new Let(newVariable, clone(node.body)); } @override TreeNode visitBlockExpression(BlockExpression node) { return new BlockExpression(clone(node.body), clone(node.value)); } @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)); } @override TreeNode visitBlock(Block node) { return new Block(node.statements.map(clone).toList()) ..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) { return new WhileStatement(clone(node.condition), clone(node.body)); } @override TreeNode visitDoStatement(DoStatement node) { return new DoStatement(clone(node.body), clone(node.condition)); } @override TreeNode visitForStatement(ForStatement node) { List variables = node.variables.map(clone).toList(); return new ForStatement( variables, cloneOptional(node.condition), node.updates.map(clone).toList(), clone(node.body), ); } @override TreeNode visitForInStatement(ForInStatement node) { Variable newVariable = clone(node.variable); return new ForInStatement( newVariable, clone(node.iterable), clone(node.body), isAsync: node.isAsync, )..bodyOffset = node.bodyOffset; } @override TreeNode visitSwitchStatement(SwitchStatement node) { for (SwitchCase switchCase in node.cases) { switchCases[switchCase] = new SwitchCase( switchCase.expressions.map(clone).toList(), new List.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) { Variable? newException = cloneOptional(node.exception); Variable? newStackTrace = cloneOptional(node.stackTrace); return new Catch( newException, clone(node.body), stackTrace: newStackTrace, guard: visitType(node.guard), ); } @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 setVariableClone( node, new PositionalParameter( cosmeticName: node.cosmeticName, type: visitType(node.type), defaultValue: cloneOptional(node.defaultValue), ) ..flags = node.flags ..annotations = _cloneAnnotations(node) ..fileEqualsOffset = _cloneFileOffset(node.fileEqualsOffset), ); } @override TreeNode visitNamedParameter(NamedParameter node) { return 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 setVariableClone( node, new CatchVariable( name: node.catchVariableName, type: visitOptionalType(node.type), ) ..flags = node.flags ..annotations = _cloneAnnotations(node) ..fileEqualsOffset = _cloneFileOffset(node.fileEqualsOffset), ); } @override TreeNode visitLocalVariable(LocalVariable node) { return setVariableClone( node, new LocalVariable( cosmeticName: node.cosmeticName, type: visitOptionalType(node.type), initializer: cloneOptional(node.initializer), ) ..flags = node.flags ..annotations = _cloneAnnotations(node) ..fileEqualsOffset = _cloneFileOffset(node.fileEqualsOffset), ); } @override TreeNode visitLateVariable(LateVariable node) { return setVariableClone( node, new LateVariable( cosmeticName: node.cosmeticName, type: visitOptionalType(node.type), initializer: cloneOptional(node.initializer), ) ..flags = node.flags ..annotations = _cloneAnnotations(node) ..fileEqualsOffset = _cloneFileOffset(node.fileEqualsOffset), ); } @override TreeNode visitSyntheticVariable(SyntheticVariable node) { return setVariableClone( node, SyntheticVariable( cosmeticName: node.cosmeticName, type: visitType(node.type), initializer: cloneOptional(node.initializer), ) ..flags = node.flags ..annotations = _cloneAnnotations(node) ..fileEqualsOffset = _cloneFileOffset(node.fileEqualsOffset), ); } @override TreeNode visitThisVariable(ThisVariable node) { return 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)) ..fileOffset = _cloneFileOffset(node.fileOffset); } @override TreeNode visitLegacyVariable(LegacyVariable node) { return setVariableClone( node, new LegacyVariable( node.name, initializer: cloneOptional(node.initializer), type: visitType(node.type), flags: node.flags, ) ..annotations = _cloneAnnotations(node) ..fileEqualsOffset = _cloneFileOffset(node.fileEqualsOffset), ); } List _cloneAnnotations(Annotatable node) { return cloneAnnotations && !node.annotations.isEmpty ? node.annotations.map(clone).toList() : const []; } @override TreeNode visitFunctionDeclaration(FunctionDeclaration node) { Variable 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 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 [] ..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); List typeParameters = node.typeParameters .map(clone) .toList(); List positional = node.positionalParameters.map(clone).toList(); List named = node.namedParameters.map(clone).toList(); Variable? 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, )..fileEndOffset = _cloneFileOffset(node.fileEndOffset); } @override TreeNode visitArguments(Arguments node) { return new Arguments( node.positional.map(clone).toList(), types: node.types.map(visitType).toList(), named: node.named.map(clone).toList(), ); } @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), node.typeArguments.map(visitType).toList(), ); } @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) { return new LocalInitializer(clone(node.variable)); } @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)!, 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), node.typeArguments.map(visitType).toList(), ); } @override TreeNode visitAndPattern(AndPattern node) { return new AndPattern(clone(node.left), clone(node.right)); } @override TreeNode visitAssignedVariablePattern(AssignedVariablePattern node) { return new AssignedVariablePattern(getVariableClone(node.variable)!) ..matchedValueType = visitOptionalType(node.matchedValueType) ..needsCast = node.needsCast; } @override TreeNode visitCastPattern(CastPattern node) { return new CastPattern(clone(node.pattern), visitType(node.type)); } @override TreeNode visitConstantPattern(ConstantPattern node) { return new ConstantPattern(clone(node.expression)); } @override TreeNode visitInvalidPattern(InvalidPattern node) { return new InvalidPattern( clone(node.invalidExpression), declaredVariables: node.declaredVariables .map((e) => getVariableClone(e)!) .toList(), ); } @override TreeNode visitListPattern(ListPattern node) { return new ListPattern( visitOptionalType(node.typeArgument), node.patterns.map(clone).toList(), ); } @override TreeNode visitMapPattern(MapPattern node) { return new MapPattern( visitOptionalType(node.keyType), visitOptionalType(node.valueType), node.entries.map(clone).toList(), ); } @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( visitType(node.requiredType), node.fields.map(clone).toList(), ); } @override TreeNode visitOrPattern(OrPattern node) { return new OrPattern( clone(node.left), clone(node.right), orPatternJointVariables: node.orPatternJointVariables .map((e) => getVariableClone(e)!) .toList(), ); } @override TreeNode visitRecordPattern(RecordPattern node) { return new RecordPattern(node.patterns.map(clone).toList()); } @override TreeNode visitRelationalPattern(RelationalPattern node) { return new RelationalPattern(node.kind, clone(node.expression)) ..expressionType = visitOptionalType(node.expressionType); } @override TreeNode visitRestPattern(RestPattern node) { return new RestPattern(cloneOptional(node.subPattern)); } @override TreeNode visitVariablePattern(VariablePattern node) { return new VariablePattern( visitOptionalType(node.type), clone(node.variable), ); } @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.of(node.caseOffsets), node.patternGuards.map(clone).toList(), clone(node.body), isDefault: node.isDefault, hasLabel: node.hasLabel, jointVariables: node.jointVariables .map((e) => getVariableClone(e)!) .toList(), jointVariableFirstUseOffsets: node.jointVariableFirstUseOffsets == null ? null : new List.of(node.jointVariableFirstUseOffsets!), ); } @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, ); } @override TreeNode visitPatternAssignment(PatternAssignment node) { return new PatternAssignment(clone(node.pattern), clone(node.expression)); } @override TreeNode visitIfCaseStatement(IfCaseStatement node) { return new IfCaseStatement( clone(node.expression), clone(node.patternGuard), clone(node.then), cloneOptional(node.otherwise), ); } @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? typeSubstitution, Map? 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 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(), transformerFlags: node.transformerFlags, fileUri: node.fileUri, reference: reference, ) ..annotations = cloneAnnotations && !node.annotations.isEmpty ? node.annotations.map(super.clone).toList() : const [] ..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, transformerFlags: node.transformerFlags, fileUri: node.fileUri, stubKind: node.stubKind, stubTarget: node.stubTarget, ) ..annotations = cloneAnnotations && !node.annotations.isEmpty ? node.annotations.map(super.clone).toList() : const [] ..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) { result = new Field.mutable( node.name, type: visitType(node.type), initializer: cloneOptional(node.initializer), transformerFlags: node.transformerFlags, fileUri: node.fileUri, fieldReference: fieldReference, getterReference: getterReference, setterReference: setterReference, ); } else { assert( setterReference == null, "Cannot use setter reference $setterReference " "for clone of an immutable field.", ); result = new Field.immutable( node.name, type: visitType(node.type), initializer: cloneOptional(node.initializer), transformerFlags: node.transformerFlags, fileUri: node.fileUri, fieldReference: fieldReference, getterReference: getterReference, ); } result ..annotations = cloneAnnotations && !node.annotations.isEmpty ? node.annotations.map(super.clone).toList() : const [] ..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? _getterMap; Map? _setterMap; new( this.mixinApplicationClass, { Map? typeSubstitution, Map? typeParams, bool cloneAnnotations = true, }) : super( typeSubstitution: typeSubstitution, typeParams: typeParams, cloneAnnotations: cloneAnnotations, ); Member? _findSuperMember(Name name, {required bool isSetter}) { Map 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? 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? positionalParameters, List? namedParameters, }) { Procedure cloned = cloneProcedure(node, reference); if (positionalParameters != null) { cloned.function.positionalParameters = positionalParameters; setParents(positionalParameters, cloned.function); } if (namedParameters != null) { cloned.function.namedParameters = namedParameters; setParents(namedParameters, cloned.function); } return cloned; } @override Statement? cloneFunctionNodeBody(FunctionNode node) => null; }