blob: 2771bb4ee855965e79570a36fc8a3a05114e59ff [file] [log] [blame]
// 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';
typedef String Renamer(Class node);
String _idRenamer(Class node) => node.name;
class S extends DartTypeVisitor<DartType> {
final Map<TreeNode, TreeNode> mapping;
S(this.mapping);
DartType defaultDartType(DartType node) => node;
int changed = 0;
Class visitClass(Class node) {
if (mapping.containsKey(node)) {
changed++;
return mapping[node];
}
return node;
}
DartType visit(DartType node) => node.accept(this);
DartType visitInterfaceType(InterfaceType node) {
final oldChanged = changed;
final typeArguments = node.typeArguments.map(visit).toList();
final classNode = visitClass(node.classNode);
if (changed != oldChanged) {
changed++;
return new InterfaceType(classNode, typeArguments);
}
return node;
}
NamedType visitNamedType(NamedType type) {
final oldChanged = changed;
final DartType t = visit(type.type);
if (oldChanged != changed) {
return new NamedType(type.name, t);
} else {
return type;
}
}
Supertype visitSupertype(Supertype node) {
final oldChanged = changed;
final Class classNode = visitClass(node.classNode);
final List<DartType> typeArguments = node.typeArguments.map(visit).toList();
if (oldChanged != changed) {
return new Supertype(classNode, typeArguments);
}
return node;
}
DartType visitFunctionType(FunctionType node) {
final oldChanged = changed;
final List<TypeParameter> typeParameters = node.typeParameters;
final int requiredParameterCount = node.requiredParameterCount;
final List<DartType> positionalParameters = node.positionalParameters.map(visit).toList();
final List<NamedType> namedParameters = node.namedParameters.map(visitNamedType).toList();
final DartType returnType = visit(node.returnType);
if (oldChanged != changed) {
return new FunctionType(positionalParameters, returnType, requiredParameterCount: requiredParameterCount, namedParameters: namedParameters, typeParameters: typeParameters);
}
return node;
}
}
/// 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 CloneVisitor extends TreeVisitor {
final Map<VariableDeclaration, VariableDeclaration> variables =
<VariableDeclaration, VariableDeclaration>{};
final Map<LabeledStatement, LabeledStatement> labels =
<LabeledStatement, LabeledStatement>{};
final Map<SwitchCase, SwitchCase> switchCases = <SwitchCase, SwitchCase>{};
final Map<TypeParameter, DartType> typeSubstitution;
final renamer;
CloneVisitor({Map<TypeParameter, DartType> typeSubstitution,
this.renamer: _idRenamer})
: this.typeSubstitution = ensureMutable(typeSubstitution);
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;
}
TreeNode defaultTreeNode(TreeNode node) => throw "Cloning of ${node.runtimeType} is not implemented";
TreeNode visitLibrary(Library node) {
throw 'Cloning of libraries is not implemented';
}
final Map<TreeNode, TreeNode> mapping = <TreeNode, TreeNode>{};
TreeNode visitClass(Class node) {
final cloned = new Class(name: renamer(node),
isAbstract: node.isAbstract, fileUri: node.fileUri);
mapping[node] = cloned;
substituteSupertype(Supertype type) {
if (type != null) {
type = Substitution.fromMap(typeSubstitution)
.substituteSupertype(type);
type = new S(mapping).visitSupertype(type);
}
return type;
}
cloned.typeParameters.addAll(node.typeParameters.map((TypeParameter param) {
if (typeSubstitution.containsKey(param)) {
return null;
}
return visitTypeParameter(param);
}).where((p) => p != null));
cloned.supertype = substituteSupertype(node.supertype);
cloned.mixedInType = substituteSupertype(node.mixedInType);
cloned.implementedTypes.addAll(node.implementedTypes.map(substituteSupertype));
cloneConstructor(Constructor node) {
return mapping[node] = new Constructor(null,
name: node.name,
isConst: node.isConst,
isExternal: node.isExternal,
transformerFlags: node.transformerFlags)
..fileEndOffset = node.fileEndOffset;
}
fillConstructor(Constructor node) {
final Constructor nodeClone = mapping[node];
nodeClone.function = clone(node.function);
nodeClone.function?.parent = nodeClone;
nodeClone.initializers = node.initializers.map(clone).toList();
setParents(nodeClone.initializers, nodeClone);
}
cloneProcedure(Procedure node) {
return mapping[node] = new Procedure(node.name, node.kind, null /* clone(node.function) */,
isAbstract: node.isAbstract,
isStatic: node.isStatic,
isExternal: node.isExternal,
isConst: node.isConst,
transformerFlags: node.transformerFlags,
fileUri: node.fileUri)..fileEndOffset = node.fileEndOffset;
}
fillProcedure(Procedure node) {
final Procedure nodeClone = mapping[node];
nodeClone.function = clone(node.function);
nodeClone.function?.parent = nodeClone;
}
cloneField(Field node) {
return mapping[node] = new Field(node.name,
type: visitType(node.type),
initializer: null/* cloneOptional(node.initializer) */,
isFinal: node.isFinal,
isConst: node.isConst,
isStatic: node.isStatic,
hasImplicitGetter: node.hasImplicitGetter,
hasImplicitSetter: node.hasImplicitSetter,
transformerFlags: node.transformerFlags,
fileUri: node.fileUri)..fileEndOffset = node.fileEndOffset;
}
fillField(Field node) {
final Field nodeClone = mapping[node];
nodeClone.initializer = cloneOptional(node.initializer);
nodeClone.initializer?.parent = nodeClone;
}
cloned.constructors.addAll(node.constructors.map(cloneConstructor));
setParents(cloned.constructors, cloned);
cloned.procedures.addAll(node.procedures.map(cloneProcedure));
setParents(cloned.procedures, cloned);
cloned.fields.addAll(node.fields.map(cloneField));
setParents(cloned.fields, cloned);
node.constructors.forEach(fillConstructor);
node.procedures.forEach(fillProcedure);
node.fields.forEach(fillField);
return cloned;
}
TreeNode clone(TreeNode node) =>
node.accept(this)..fileOffset = node.fileOffset;
TreeNode cloneOptional(TreeNode node) {
TreeNode result = node?.accept(this);
if (result != null) result.fileOffset = node.fileOffset;
return result;
}
DartType visitType(DartType type) {
return new S(mapping).visit(substitute(type, typeSubstitution));
}
DartType visitOptionalType(DartType type) {
return type == null ? null : visitType(type);
}
visitInvalidExpression(InvalidExpression node) => new InvalidExpression();
visitVariableGet(VariableGet node) {
return new VariableGet(
variables[node.variable], visitOptionalType(node.promotedType));
}
visitVariableSet(VariableSet node) {
return new VariableSet(variables[node.variable], clone(node.value));
}
Member redirect(Member m) => m == null ? null : (mapping[m] ?? m);
visitPropertyGet(PropertyGet node) {
return new PropertyGet(
clone(node.receiver), node.name, redirect(node.interfaceTarget));
}
visitPropertySet(PropertySet node) {
return new PropertySet(clone(node.receiver), node.name,
clone(node.value), redirect(node.interfaceTarget));
}
visitDirectPropertyGet(DirectPropertyGet node) {
return new DirectPropertyGet(
clone(node.receiver), redirect(node.target));
}
visitDirectPropertySet(DirectPropertySet node) {
return new DirectPropertySet(
clone(node.receiver), redirect(node.target), clone(node.value));
}
visitSuperPropertyGet(SuperPropertyGet node) {
return new SuperPropertyGet(
node.name, redirect(node.interfaceTarget));
}
visitSuperPropertySet(SuperPropertySet node) {
return new SuperPropertySet(
node.name, clone(node.value), redirect(node.interfaceTarget));
}
visitStaticGet(StaticGet node) {
return new StaticGet(redirect(node.target));
}
visitStaticSet(StaticSet node) {
return new StaticSet(redirect(node.target), clone(node.value));
}
visitMethodInvocation(MethodInvocation node) {
return new MethodInvocation(clone(node.receiver), node.name,
clone(node.arguments), redirect(node.interfaceTarget));
}
visitDirectMethodInvocation(DirectMethodInvocation node) {
return new DirectMethodInvocation(
clone(node.receiver), redirect(node.target), clone(node.arguments));
}
visitSuperMethodInvocation(SuperMethodInvocation node) {
return new SuperMethodInvocation(
node.name, clone(node.arguments), redirect(node.interfaceTarget));
}
visitStaticInvocation(StaticInvocation node) {
return new StaticInvocation(
redirect(node.target), clone(node.arguments),
isConst: node.isConst);
}
visitConstructorInvocation(ConstructorInvocation node) {
final target = redirect(node.target);
final Arguments arguments = clone(node.arguments);
final klass = node.target.enclosingClass;
if (target.enclosingClass.typeParameters.length != klass.typeParameters.length) {
var j = 0;
for (var i = 0; i < arguments.types.length; i++) {
if (!typeSubstitution.containsKey(klass.typeParameters[i])) {
arguments.types[j++] = arguments.types[i];
}
}
arguments.types.length = j;
}
return new ConstructorInvocation(
target, arguments,
isConst: node.isConst);
}
visitNot(Not node) {
return new Not(clone(node.operand));
}
visitLogicalExpression(LogicalExpression node) {
return new LogicalExpression(
clone(node.left), node.operator, clone(node.right));
}
visitConditionalExpression(ConditionalExpression node) {
return new ConditionalExpression(clone(node.condition), clone(node.then),
clone(node.otherwise), visitOptionalType(node.staticType));
}
visitStringConcatenation(StringConcatenation node) {
return new StringConcatenation(node.expressions.map(clone).toList());
}
visitIsExpression(IsExpression node) {
return new IsExpression(clone(node.operand), visitType(node.type));
}
visitAsExpression(AsExpression node) {
return new AsExpression(clone(node.operand), visitType(node.type));
}
visitSymbolLiteral(SymbolLiteral node) {
return new SymbolLiteral(node.value);
}
visitTypeLiteral(TypeLiteral node) {
return new TypeLiteral(visitType(node.type));
}
visitThisExpression(ThisExpression node) {
return new ThisExpression();
}
visitRethrow(Rethrow node) {
return new Rethrow();
}
visitThrow(Throw node) {
return new Throw(cloneOptional(node.expression));
}
visitListLiteral(ListLiteral node) {
return new ListLiteral(node.expressions.map(clone).toList(),
typeArgument: visitType(node.typeArgument), isConst: node.isConst);
}
visitMapLiteral(MapLiteral node) {
return new MapLiteral(node.entries.map(clone).toList(),
keyType: visitType(node.keyType),
valueType: visitType(node.valueType),
isConst: node.isConst);
}
visitMapEntry(MapEntry node) {
return new MapEntry(clone(node.key), clone(node.value));
}
visitAwaitExpression(AwaitExpression node) {
return new AwaitExpression(clone(node.operand));
}
visitFunctionExpression(FunctionExpression node) {
return new FunctionExpression(clone(node.function));
}
visitStringLiteral(StringLiteral node) {
return new StringLiteral(node.value);
}
visitIntLiteral(IntLiteral node) {
return new IntLiteral(node.value);
}
visitDoubleLiteral(DoubleLiteral node) {
return new DoubleLiteral(node.value);
}
visitBoolLiteral(BoolLiteral node) {
return new BoolLiteral(node.value);
}
visitNullLiteral(NullLiteral node) {
return new NullLiteral();
}
visitLet(Let node) {
var newVariable = clone(node.variable);
return new Let(newVariable, clone(node.body));
}
// Statements
visitInvalidStatement(InvalidStatement node) {
return new InvalidStatement();
}
visitExpressionStatement(ExpressionStatement node) {
return new ExpressionStatement(clone(node.expression));
}
visitBlock(Block node) {
return new Block(node.statements.map(clone).toList());
}
visitEmptyStatement(EmptyStatement node) {
return new EmptyStatement();
}
visitAssertStatement(AssertStatement node) {
return new AssertStatement(
clone(node.condition), cloneOptional(node.message));
}
visitLabeledStatement(LabeledStatement node) {
LabeledStatement newNode = new LabeledStatement(null);
labels[node] = newNode;
newNode.body = clone(node.body)..parent = newNode;
return newNode;
}
visitBreakStatement(BreakStatement node) {
return new BreakStatement(labels[node.target]);
}
visitWhileStatement(WhileStatement node) {
return new WhileStatement(clone(node.condition), clone(node.body));
}
visitDoStatement(DoStatement node) {
return new DoStatement(clone(node.body), clone(node.condition));
}
visitForStatement(ForStatement node) {
var variables = node.variables.map(clone).toList();
return new ForStatement(variables, cloneOptional(node.condition),
node.updates.map(clone).toList(), clone(node.body));
}
visitForInStatement(ForInStatement node) {
var newVariable = clone(node.variable);
return new ForInStatement(
newVariable, clone(node.iterable), clone(node.body));
}
visitSwitchStatement(SwitchStatement node) {
for (SwitchCase switchCase in node.cases) {
switchCases[switchCase] = new SwitchCase(
switchCase.expressions.map(clone).toList(),
new List<int>.from(switchCase.expressionOffsets),
null);
}
return new SwitchStatement(
clone(node.expression), node.cases.map(clone).toList());
}
visitSwitchCase(SwitchCase node) {
var switchCase = switchCases[node];
switchCase.body = clone(node.body)..parent = switchCase;
return switchCase;
}
visitContinueSwitchStatement(ContinueSwitchStatement node) {
return new ContinueSwitchStatement(switchCases[node.target]);
}
visitIfStatement(IfStatement node) {
return new IfStatement(
clone(node.condition), clone(node.then), cloneOptional(node.otherwise));
}
visitReturnStatement(ReturnStatement node) {
return new ReturnStatement(cloneOptional(node.expression));
}
visitTryCatch(TryCatch node) {
return new TryCatch(clone(node.body), node.catches.map(clone).toList());
}
visitCatch(Catch node) {
var newException = cloneOptional(node.exception);
var newStackTrace = cloneOptional(node.stackTrace);
return new Catch(newException, clone(node.body),
stackTrace: newStackTrace, guard: visitType(node.guard));
}
visitTryFinally(TryFinally node) {
return new TryFinally(clone(node.body), clone(node.finalizer));
}
visitYieldStatement(YieldStatement node) {
return new YieldStatement(clone(node.expression));
}
visitVariableDeclaration(VariableDeclaration node) {
return variables[node] = new VariableDeclaration(node.name,
initializer: cloneOptional(node.initializer),
type: visitType(node.type),
isFinal: node.isFinal,
isConst: node.isConst);
}
visitFunctionDeclaration(FunctionDeclaration node) {
var newVariable = clone(node.variable);
return new FunctionDeclaration(newVariable, clone(node.function));
}
// Members
visitConstructor(Constructor node) {
return new Constructor(clone(node.function),
name: node.name,
isConst: node.isConst,
isExternal: node.isExternal,
initializers: node.initializers.map(clone).toList(),
transformerFlags: node.transformerFlags)
..fileEndOffset = node.fileEndOffset;
}
visitProcedure(Procedure node) {
return new Procedure(node.name, node.kind, clone(node.function),
isAbstract: node.isAbstract,
isStatic: node.isStatic,
isExternal: node.isExternal,
isConst: node.isConst,
transformerFlags: node.transformerFlags,
fileUri: node.fileUri)..fileEndOffset = node.fileEndOffset;
}
visitField(Field node) {
return new Field(node.name,
type: visitType(node.type),
initializer: cloneOptional(node.initializer),
isFinal: node.isFinal,
isConst: node.isConst,
isStatic: node.isStatic,
hasImplicitGetter: node.hasImplicitGetter,
hasImplicitSetter: node.hasImplicitSetter,
transformerFlags: node.transformerFlags,
fileUri: node.fileUri)..fileEndOffset = node.fileEndOffset;
}
visitTypeParameter(TypeParameter node) {
var newNode = new TypeParameter(node.name);
typeSubstitution[node] = new TypeParameterType(newNode);
newNode.bound = visitType(node.bound);
return newNode;
}
TreeNode cloneFunctionNodeBody(FunctionNode node) => cloneOptional(node.body);
visitFunctionNode(FunctionNode node) {
var typeParameters = node.typeParameters.map(clone).toList();
var positional = node.positionalParameters.map(clone).toList();
var named = node.namedParameters.map(clone).toList();
return new FunctionNode(cloneFunctionNodeBody(node),
typeParameters: typeParameters,
positionalParameters: positional,
namedParameters: named,
requiredParameterCount: node.requiredParameterCount,
returnType: visitType(node.returnType),
asyncMarker: node.asyncMarker,
dartAsyncMarker: node.dartAsyncMarker)
..fileEndOffset = node.fileEndOffset;
}
visitArguments(Arguments node) {
return new Arguments(node.positional.map(clone).toList(),
types: node.types.map(visitType).toList(),
named: node.named.map(clone).toList());
}
visitNamedExpression(NamedExpression node) {
return new NamedExpression(node.name, clone(node.value));
}
visitFieldInitializer(FieldInitializer node) {
return new FieldInitializer(redirect(node.field), clone(node.value));
}
visitSuperInitializer(SuperInitializer node) {
return new SuperInitializer(redirect(node.target), visitArguments(node.arguments));
}
}