blob: b15e76a81dc0480c331c846a0f8a35a9e395090c [file] [log] [blame]
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
part of dart_backend;
class LocalPlaceholder {
final String identifier;
final Set<Node> nodes;
LocalPlaceholder(this.identifier) : nodes = new Set<Node>();
int get hashCode => identifier.hashCode;
String toString() =>
'local_placeholder[id($identifier), nodes($nodes)]';
}
class FunctionScope {
final Set<String> parameterIdentifiers;
final Set<LocalPlaceholder> localPlaceholders;
FunctionScope()
: parameterIdentifiers = new Set<String>(),
localPlaceholders = new Set<LocalPlaceholder>();
void registerParameter(Identifier node) {
parameterIdentifiers.add(node.source);
}
}
class ConstructorPlaceholder {
final Identifier node;
final ConstructorElement element;
ConstructorPlaceholder(this.node, this.element);
}
class DeclarationTypePlaceholder {
final TypeAnnotation typeNode;
final bool requiresVar;
DeclarationTypePlaceholder(this.typeNode, this.requiresVar);
}
class SendVisitor extends Visitor {
final TreeElements elements;
final PlaceholderCollector collector;
SendVisitor(this.collector, this.elements);
visitSend(Send node) {
Element element = elements[node];
if (elements.isTypeLiteral(node)) {
DartType type = elements.getTypeLiteralType(node);
if (!type.isDynamic) {
if (type is TypeVariableType) {
collector.makeTypeVariablePlaceholder(node.selector, type);
} else {
collector.makeTypePlaceholder(node.selector, type);
}
}
} else if (node.isSuperCall) {
if (element != null && element.isConstructor) {
collector.tryMakeConstructorPlaceholder(node, element);
} else {
collector.tryMakeMemberPlaceholder(node.selector);
}
} else if (node.isOperator) {
return;
} else if (node.isPropertyAccess) {
if (!Elements.isUnresolved(element) && element.impliesType) {
collector.makeElementPlaceholder(node, element);
} else {
visitGetterSend(node);
}
} else if (element != null && Initializers.isConstructorRedirect(node)) {
visitStaticSend(node);
} else if (Elements.isClosureSend(node, element)) {
if (element != null) {
collector.tryMakeLocalPlaceholder(element, node.selector);
}
} else {
if (Elements.isUnresolved(element)) {
if (element == null) {
// Example: f() with 'f' unbound.
// This can only happen inside an instance method.
visitDynamicSend(node);
} else {
visitStaticSend(node);
}
} else if (element.isInstanceMember) {
// Example: f() with 'f' bound to instance method.
visitDynamicSend(node);
} else if (!element.isInstanceMember) {
// Example: A.f() or f() with 'f' bound to a static function.
// Also includes new A() or new A.named() which is treated like a
// static call to a factory.
visitStaticSend(node);
}
}
}
visitDynamicSend(Send node) {
final element = elements[node];
if (element == null || !element.isErroneous) {
collector.tryMakeMemberPlaceholder(node.selector);
}
}
visitGetterSend(Send node) {
final element = elements[node];
// element == null means dynamic property access.
if (element == null) {
collector.tryMakeMemberPlaceholder(node.selector);
} else if (element.isErroneous) {
collector.makeUnresolvedPlaceholder(node);
return;
} else if (element.isPrefix) {
// Node is prefix part in case of source 'lib.somesetter = 5;'
collector.makeErasePrefixPlaceholder(node);
} else if (Elements.isStaticOrTopLevel(element)) {
// Unqualified or prefixed top level or static.
collector.makeElementPlaceholder(node.selector, element);
} else if (!element.isTopLevel) {
if (element.isInstanceMember) {
collector.tryMakeMemberPlaceholder(node.selector);
} else {
// May get FunctionExpression here in selector
// in case of A(int this.f());
if (node.selector is Identifier) {
collector.tryMakeLocalPlaceholder(element, node.selector);
} else {
assert(node.selector is FunctionExpression);
}
}
}
}
visitStaticSend(Send node) {
Element element = elements[node];
collector.mirrorRenamer.registerStaticSend(
collector.currentElement, element, node);
if (Elements.isUnresolved(element)
|| element.isDeferredLoaderGetter) {
return;
}
if (element.isConstructor || element.isFactoryConstructor) {
// Rename named constructor in redirection position:
// class C { C.named(); C.redirecting() : this.named(); }
if (node.receiver is Identifier
&& node.receiver.asIdentifier().isThis()) {
assert(node.selector is Identifier);
collector.tryMakeConstructorPlaceholder(node, element);
}
return;
}
collector.makeElementPlaceholder(node.selector, element);
// Another ugly case: <lib prefix>.<top level> is represented as
// receiver: lib prefix, selector: top level.
if (element.isTopLevel && node.receiver != null) {
assert(elements[node.receiver].isPrefix);
// Hack: putting null into map overrides receiver of original node.
collector.makeErasePrefixPlaceholder(node.receiver);
}
}
internalError(Spannable node, String reason) {
collector.internalError(reason, node: node);
}
visitNode(Node node) {
internalError(node, "Unhandled node");
}
}
class PlaceholderCollector extends Visitor {
final DiagnosticListener listener;
final MirrorRenamer mirrorRenamer;
final FunctionElement mainFunction;
final Set<String> fixedMemberNames; // member names which cannot be renamed.
final Map<Element, ElementAst> elementAsts;
final Set<Node> prefixNodesToErase = new Set<Node>();
final Set<Node> unresolvedNodes = new Set<Node>();
final Map<Element, Set<Node>> elementNodes = new Map<Element, Set<Node>>();
final Map<FunctionElement, FunctionScope> functionScopes
= new Map<FunctionElement, FunctionScope>();
final Map<LibraryElement, Set<Identifier>> privateNodes =
new Map<LibraryElement, Set<Identifier>>();
final List<DeclarationTypePlaceholder> declarationTypePlaceholders
= new List<DeclarationTypePlaceholder>();
final Map<String, Set<Identifier>> memberPlaceholders
= new Map<String, Set<Identifier>>();
final List<ConstructorPlaceholder> constructorPlaceholders
= new List<ConstructorPlaceholder>();
Map<String, LocalPlaceholder> currentLocalPlaceholders;
Element currentElement;
FunctionElement topmostEnclosingFunction;
TreeElements treeElements;
get currentFunctionScope => functionScopes.putIfAbsent(
topmostEnclosingFunction, () => new FunctionScope());
PlaceholderCollector(this.listener, this.mirrorRenamer,
this.fixedMemberNames, this.elementAsts,
this.mainFunction);
void collectFunctionDeclarationPlaceholders(
FunctionElement element, FunctionExpression node) {
if (element.isConstructor) {
ConstructorElement constructor = element;
tryMakeConstructorPlaceholder(node.name, element);
RedirectingFactoryBody bodyAsRedirectingFactoryBody =
node.body.asRedirectingFactoryBody();
if (bodyAsRedirectingFactoryBody != null) {
// Factory redirection.
FunctionElement redirectTarget = constructor.immediateRedirectionTarget;
assert(redirectTarget != null && redirectTarget != element);
tryMakeConstructorPlaceholder(
bodyAsRedirectingFactoryBody.constructorReference,
redirectTarget);
}
} else if (Elements.isStaticOrTopLevel(element)) {
// Note: this code should only rename private identifiers for class'
// fields/getters/setters/methods. Top-level identifiers are renamed
// just to escape conflicts and that should be enough as we shouldn't
// be able to resolve private identifiers for other libraries.
makeElementPlaceholder(node.name, element);
} else if (element.isClassMember) {
if (node.name is Identifier) {
tryMakeMemberPlaceholder(node.name);
} else {
assert(node.name.asSend().isOperator);
}
}
}
void collectFieldDeclarationPlaceholders(Element element, Node node) {
Identifier name = node is Identifier ? node : node.asSend().selector;
if (Elements.isStaticOrTopLevel(element)) {
makeElementPlaceholder(name, element);
} else if (Elements.isInstanceField(element)) {
tryMakeMemberPlaceholder(name);
}
}
void collect(Element element) {
this.currentElement = element;
this.topmostEnclosingFunction = null;
final ElementAst elementAst = elementAsts[element];
this.treeElements = elementAst.treeElements;
Node elementNode = elementAst.ast;
if (element is FunctionElement) {
collectFunctionDeclarationPlaceholders(element, elementNode);
} else if (element is VariableElement) {
VariableDefinitions definitions = elementNode;
Node definition = definitions.definitions.nodes.head;
collectFieldDeclarationPlaceholders(element, definition);
makeVarDeclarationTypePlaceholder(definitions);
} else {
assert(element is ClassElement || element is TypedefElement);
}
currentLocalPlaceholders = new Map<String, LocalPlaceholder>();
if (!(element is ConstructorElement && element.isRedirectingFactory)) {
// Do not visit the body of redirecting factories.
listener.withCurrentElement(element, () {
elementNode.accept(this);
});
}
}
// TODO(karlklose): should we create placeholders for these?
bool isTypedefParameter(Element element) {
return element != null &&
element.enclosingElement != null &&
element.enclosingElement.isTypedef;
}
void tryMakeLocalPlaceholder(Element element, Identifier node) {
bool isNamedOptionalParameter() {
FunctionTypedElement function = element.enclosingElement;
FunctionSignature signature = function.functionSignature;
if (!signature.optionalParametersAreNamed) return false;
for (Element parameter in signature.optionalParameters) {
if (identical(parameter, element)) return true;
}
return false;
}
if (element.isParameter && !isTypedefParameter(element) &&
isNamedOptionalParameter()) {
currentFunctionScope.registerParameter(node);
} else if (Elements.isLocal(element) && !isTypedefParameter(element)) {
makeLocalPlaceholder(node);
}
}
void tryMakeMemberPlaceholder(Identifier node) {
assert(node != null);
if (node is Operator) return;
String identifier = node.source;
if (fixedMemberNames.contains(identifier)) return;
memberPlaceholders.putIfAbsent(
identifier, () => new Set<Identifier>()).add(node);
}
void makeTypePlaceholder(Node node, DartType type) {
Send send = node.asSend();
if (send != null) {
// Prefix.
assert(send.receiver is Identifier);
assert(send.selector is Identifier);
makeErasePrefixPlaceholder(send.receiver);
node = send.selector;
}
makeElementPlaceholder(node, type.element);
}
void makeTypeVariablePlaceholder(Node node, TypeVariableType type) {
Send send = node.asSend();
if (send != null) {
// Prefix.
assert(send.receiver is Identifier);
assert(send.selector is Identifier);
makeErasePrefixPlaceholder(send.receiver);
node = send.selector;
}
tryMakeMemberPlaceholder(node);
}
void makeOmitDeclarationTypePlaceholder(TypeAnnotation type) {
if (type == null) return;
declarationTypePlaceholders.add(
new DeclarationTypePlaceholder(type, false));
}
void makeVarDeclarationTypePlaceholder(VariableDefinitions node) {
// TODO(smok): Maybe instead of calling this method and
// makeDeclaratioTypePlaceholder have type declaration placeholder
// collector logic in visitVariableDefinitions when resolver becomes better
// and/or catch syntax changes.
if (node.type == null) return;
bool requiresVar = !node.modifiers.isFinalOrConst;
declarationTypePlaceholders.add(
new DeclarationTypePlaceholder(node.type, requiresVar));
}
/// Marks [node] to be erased in the output.
/// This is done for library prefixes because they are not used in the output
/// because all imports are flattened and conflicts are renamed away.
void makeErasePrefixPlaceholder(Node node) {
assert(node is Identifier || node is Send);
prefixNodesToErase.add(node);
}
void makeElementPlaceholder(Node node, Element element) {
assert(node != null);
assert(element != null);
LibraryElement library = element.library;
if (identical(element, mainFunction)) return;
if (library.isDartCore) return;
if (library.isPlatformLibrary && !element.isTopLevel) {
return;
}
ClassElement cls = element.enclosingClass;
if (cls != null && cls.isEnumClass) {
// Enums and enum values cannot be changed, since the semantics of
// `toString` is defined by the names of the declarations.
return;
}
if (element.isAccessor) {
element = (element as AccessorElement).abstractField;
}
elementNodes.putIfAbsent(element, () => new Set<Node>()).add(node);
}
/// Marks [node] to be renamed per-library if it names an instance member
/// and has a private name.
void tryMakePrivateIdentifier(Node node, Element element) {
if (node is Identifier &&
!Elements.isStaticOrTopLevel(element) &&
!Elements.isLocal(element) &&
Name.isPrivateName(node.source)) {
privateNodes.putIfAbsent(
currentElement.library, () => new Set<Identifier>()).add(node);
}
}
void makeUnresolvedPlaceholder(Node node) {
unresolvedNodes.add(node);
}
void makeLocalPlaceholder(Identifier identifier) {
LocalPlaceholder getLocalPlaceholder() {
String name = identifier.source;
return currentLocalPlaceholders.putIfAbsent(name, () {
LocalPlaceholder localPlaceholder = new LocalPlaceholder(name);
currentFunctionScope.localPlaceholders.add(localPlaceholder);
return localPlaceholder;
});
}
getLocalPlaceholder().nodes.add(identifier);
}
/// Finds the first constructor on the chain of definingConstructor from
/// [element] that is not in a synthetic class.
Element findDefiningConstructor(ConstructorElement element) {
while (element.definingConstructor != null) {
element = element.definingConstructor;
}
return element;
}
void tryMakeConstructorPlaceholder(Node node, ConstructorElement element) {
if (Elements.isUnresolved(element)) {
makeUnresolvedPlaceholder(node);
return;
}
// A library prefix.
Node prefix;
// The name of the class with the constructor.
Node className;
// Will be null for unnamed constructors.
Identifier constructorName;
// First deconstruct the constructor, there are 4 possibilities:
// ClassName()
// prefix.ClassName()
// ClassName.constructorName()
// prefix.ClassName.constructorName()
if (node is Send) {
if (node.receiver is Send) {
Send receiver = node.receiver;
// prefix.ClassName.constructorName()
assert(treeElements[receiver.receiver] != null &&
treeElements[receiver.receiver].isPrefix);
prefix = receiver.receiver;
className = receiver.selector;
constructorName = node.selector;
} else {
Element receiverElement = treeElements[node.receiver];
if (receiverElement != null && receiverElement.isPrefix) {
// prefix.ClassName()
prefix = node.receiver;
className = node.selector;
} else {
// ClassName.constructorName()
className = node.receiver;
constructorName = node.selector;
}
}
} else {
// ClassName()
className = node;
}
if (prefix != null) {
makeErasePrefixPlaceholder(prefix);
}
if (className is TypeAnnotation) {
visitTypeAnnotation(className);
} else if (Elements.isUnresolved(element)) {
// We handle unresolved nodes elsewhere.
} else if (className.isThis() || className.isSuper()) {
// Do not rename super and this.
} else if (className is Identifier) {
makeElementPlaceholder(className, element.contextClass);
} else {
throw "Bad type of constructor name $className";
}
if (constructorName != null) {
Element definingConstructor = findDefiningConstructor(element);
constructorPlaceholders.add(new ConstructorPlaceholder(constructorName,
definingConstructor));
tryMakePrivateIdentifier(constructorName, element);
}
}
void internalError(String reason, {Node node}) {
listener.internalError(node, reason);
}
visit(Node node) => (node == null) ? null : node.accept(this);
visitNode(Node node) { node.visitChildren(this); } // We must go deeper.
visitNewExpression(NewExpression node) {
Send send = node.send;
DartType type = treeElements.getType(node);
assert(type != null);
Element constructor = treeElements[send];
assert(constructor != null);
assert(send.receiver == null);
if (!Elements.isErroneous(constructor)) {
tryMakeConstructorPlaceholder(node.send.selector, constructor);
// TODO(smok): Should this be in visitNamedArgument?
// Field names can be exposed as names of optional arguments, e.g.
// class C {
// final field;
// C([this.field]);
// }
// Do not forget to rename them as well.
FunctionElement constructorFunction = constructor;
List<Element> optionalParameters =
constructorFunction.functionSignature.optionalParameters;
for (final argument in send.argumentsNode) {
NamedArgument named = argument.asNamedArgument();
if (named == null) continue;
Identifier name = named.name;
String nameAsString = name.source;
for (final parameter in optionalParameters) {
if (parameter.isInitializingFormal) {
if (parameter.name == nameAsString) {
tryMakeMemberPlaceholder(name);
break;
}
}
}
}
} else {
makeUnresolvedPlaceholder(node.send.selector);
}
visit(node.send.argumentsNode);
}
visitSend(Send send) {
Element element = treeElements[send];
tryMakePrivateIdentifier(send.selector, element);
new SendVisitor(this, treeElements).visitSend(send);
send.visitChildren(this);
}
visitSendSet(SendSet send) {
Element element = treeElements[send];
if (Elements.isErroneous(element)) {
// Complicated case: constructs like receiver.selector++ can resolve
// to ErroneousElement. Fortunately, receiver.selector still
// can be resoved via treeElements[send.selector], that's all
// that is needed to rename the construct properly.
element = treeElements[send.selector];
}
tryMakePrivateIdentifier(send.selector, element);
if (element == null) {
if (send.receiver != null) tryMakeMemberPlaceholder(send.selector);
} else if (!element.isErroneous) {
if (Elements.isStaticOrTopLevel(element)) {
// TODO(smok): Worth investigating why sometimes we get getter/setter
// here and sometimes abstract field.
assert(element.isClass || element is VariableElement ||
element.isAccessor || element.isAbstractField ||
element.isFunction || element.isTypedef ||
element is TypeVariableElement);
makeElementPlaceholder(send.selector, element);
} else {
Identifier identifier = send.selector.asIdentifier();
if (identifier == null) {
// Handle optional function expression parameters with default values.
identifier = send.selector.asFunctionExpression().name;
}
if (Elements.isInstanceField(element)) {
tryMakeMemberPlaceholder(identifier);
} else {
tryMakeLocalPlaceholder(element, identifier);
}
}
}
send.visitChildren(this);
}
visitTypeAnnotation(TypeAnnotation node) {
final type = treeElements.getType(node);
assert(invariant(node, type != null,
message: "Missing type for type annotation: $treeElements"));
if (!type.isVoid) {
if (!type.treatAsDynamic) {
if (type is TypeVariableType) {
makeTypeVariablePlaceholder(node.typeName, type);
} else {
makeTypePlaceholder(node.typeName, type);
}
} else if (!type.isDynamic) {
makeUnresolvedPlaceholder(node.typeName);
}
}
// Visit only type arguments, otherwise in case of lib.Class type
// annotation typeName is Send and we go to visitGetterSend, as a result
// "Class" is added to member placeholders.
visit(node.typeArguments);
}
visitVariableDefinitions(VariableDefinitions node) {
// Collect only local placeholders.
for (Node definition in node.definitions.nodes) {
Element definitionElement = treeElements[definition];
// definitionElement may be null if we're inside variable definitions
// of a function that is a parameter of another function.
// TODO(smok): Fix this when resolver correctly deals with
// such cases.
if (definitionElement == null) continue;
Send send = definition.asSend();
Identifier identifier = definition is Identifier
? definition
: definition is Send
? (send.selector is Identifier
? send.selector
: null)
: null;
tryMakePrivateIdentifier(identifier, definitionElement);
if (send != null) {
// May get FunctionExpression here in definition.selector
// in case of A(int this.f());
if (send.selector is Identifier) {
if (definitionElement.isInitializingFormal) {
tryMakeMemberPlaceholder(send.selector);
} else {
tryMakeLocalPlaceholder(definitionElement, send.selector);
}
} else {
assert(send.selector is FunctionExpression);
if (definitionElement.isInitializingFormal) {
tryMakeMemberPlaceholder(
send.selector.asFunctionExpression().name);
}
}
} else if (definition is Identifier) {
tryMakeLocalPlaceholder(definitionElement, definition);
} else if (definition is FunctionExpression) {
// Skip, it will be processed in visitFunctionExpression.
} else {
internalError('Unexpected definition structure $definition');
}
}
node.visitChildren(this);
}
visitFunctionExpression(FunctionExpression node) {
bool isKeyword(Identifier id) =>
id != null && Keyword.keywords[id.source] != null;
Element element = treeElements[node];
// May get null;
if (element != null) {
tryMakePrivateIdentifier(node.name, element);
// Rename only local functions.
if (topmostEnclosingFunction == null &&
element is! LocalParameterElement &&
element is! InitializingFormalElement) {
topmostEnclosingFunction = element;
}
if (!identical(element, currentElement)) {
if (node.name != null) {
assert(node.name is Identifier);
tryMakeLocalPlaceholder(element, node.name);
}
}
}
node.visitChildren(this);
// Make sure we don't omit return type of methods which names are
// identifiers, because the following works fine:
// int interface() => 1;
// But omitting 'int' makes VM unhappy.
// TODO(smok): Remove it when http://dartbug.com/5278 is fixed.
if (node.name == null || !isKeyword(node.name.asIdentifier())) {
makeOmitDeclarationTypePlaceholder(node.returnType);
}
collectFunctionParameters(node.parameters);
}
void collectFunctionParameters(NodeList parameters) {
if (parameters == null) return;
for (Node parameter in parameters.nodes) {
if (parameter is NodeList) {
// Optional parameter list.
collectFunctionParameters(parameter);
} else {
assert(parameter is VariableDefinitions);
makeOmitDeclarationTypePlaceholder(
parameter.asVariableDefinitions().type);
}
}
}
visitClassNode(ClassNode node) {
ClassElement classElement = currentElement;
makeElementPlaceholder(node.name, classElement);
node.visitChildren(this);
}
visitNamedMixinApplication(NamedMixinApplication node) {
ClassElement classElement = currentElement;
makeElementPlaceholder(node.name, classElement);
node.visitChildren(this);
}
visitTypeVariable(TypeVariable node) {
DartType type = treeElements.getType(node);
assert(invariant(node, type != null,
message: "Missing type for type variable: $treeElements"));
makeTypeVariablePlaceholder(node.name, type);
node.visitChildren(this);
}
visitTypedef(Typedef node) {
assert(currentElement is TypedefElement);
makeElementPlaceholder(node.name, currentElement);
node.visitChildren(this);
makeOmitDeclarationTypePlaceholder(node.returnType);
collectFunctionParameters(node.formals);
}
visitBlock(Block node) {
for (Node statement in node.statements.nodes) {
if (statement is VariableDefinitions) {
makeVarDeclarationTypePlaceholder(statement);
}
}
node.visitChildren(this);
}
}