blob: 03a3bf990c6e1abf770b24823f2bde62bf0ddcec [file] [log] [blame]
// Copyright (c) 2014, 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 analyzer2dart.treeShaker;
import 'dart:collection';
import 'package:analyzer/analyzer.dart';
import 'package:analyzer/src/generated/element.dart';
import 'package:compiler/implementation/universe/universe.dart';
import 'closed_world.dart';
class TreeShaker {
List<Element> _queue = <Element>[];
Set<Element> _alreadyEnqueued = new HashSet<Element>();
ClosedWorld _world = new ClosedWorld();
Set<Selector> _selectors = new HashSet<Selector>();
void addElement(Element element) {
if (_alreadyEnqueued.add(element)) {
_queue.add(element);
}
}
void addSelector(Selector selector) {
if (_selectors.add(selector)) {
// New selector, so match it against all class methods.
_world.instantiatedClasses.forEach((ClassElement element, AstNode node) {
matchClassToSelector(element, selector);
});
}
}
void matchClassToSelector(ClassElement classElement, Selector selector) {
// TODO(paulberry): walk through superclasses and mixins as well. Consider
// using InheritanceManager to do this.
for (MethodElement method in classElement.methods) {
// TODO(paulberry): account for arity and named arguments when matching
// the selector against the method.
if (selector.name == method.name) {
addElement(method);
}
}
if (selector.kind == SelectorKind.GETTER) {
for (PropertyAccessorElement accessor in classElement.accessors) {
if (accessor.isGetter && selector.name == accessor.name) {
if (accessor.isSynthetic) {
// This accessor is implied by the corresponding field declaration.
addElement(accessor.variable);
} else {
addElement(accessor);
}
}
}
}
}
ClosedWorld shake() {
while (_queue.isNotEmpty) {
Element element = _queue.removeLast();
print('Tree shaker handling $element');
if (element is FunctionElement) {
FunctionDeclaration declaration = element.node;
_world.executableElements[element] = declaration;
declaration.accept(new TreeShakingVisitor(this));
} else if (element is ClassElement) {
ClassDeclaration declaration = element.node;
_world.instantiatedClasses[element] = declaration;
for (Selector selector in _selectors) {
matchClassToSelector(element, selector);
}
} else if (element is MethodElement) {
MethodDeclaration declaration = element.node;
_world.executableElements[element] = declaration;
declaration.accept(new TreeShakingVisitor(this));
} else if (element is PropertyAccessorElement) {
if (element.isGetter) {
MethodDeclaration declaration = element.node;
_world.executableElements[element] = declaration;
declaration.accept(new TreeShakingVisitor(this));
} else {
// TODO(paulberry): handle setters.
throw new UnimplementedError();
}
} else if (element is FieldElement) {
VariableDeclaration declaration = element.node;
_world.fields[element] = declaration;
} else {
throw new Exception('Unexpected element type while tree shaking');
}
}
print('Tree shaking done');
return _world;
}
}
class TreeShakingVisitor extends RecursiveAstVisitor {
final TreeShaker treeShaker;
TreeShakingVisitor(this.treeShaker);
/**
* Handle a true method call (a MethodInvocation that represents a call to
* a non-static method).
*/
void handleMethodCall(MethodInvocation node) {
int arity = 0;
List<String> namedArguments = <String>[];
for (var x in node.argumentList.arguments) {
if (x is NamedExpression) {
namedArguments.add(x.name.label.name);
} else {
arity++;
}
}
treeShaker.addSelector(
new Selector.call(node.methodName.name, null, arity, namedArguments));
}
@override
void visitFunctionDeclaration(FunctionDeclaration node) {
super.visitFunctionDeclaration(node);
}
@override
void visitInstanceCreationExpression(InstanceCreationExpression node) {
ConstructorElement staticElement = node.staticElement;
if (staticElement != null) {
// TODO(paulberry): Really we should enqueue the constructor, and then
// when we visit it add the class to the class bucket.
ClassElement classElement = staticElement.enclosingElement;
treeShaker.addElement(classElement);
} else {
// TODO(paulberry): deal with this situation. This can happen, for
// example, in the case "main() => new Unresolved();" (which is a
// warning, not an error).
}
super.visitInstanceCreationExpression(node);
}
@override
void visitMethodInvocation(MethodInvocation node) {
super.visitMethodInvocation(node);
Element staticElement = node.methodName.staticElement;
if (staticElement == null) {
if (node.realTarget != null) {
// Calling a method that has no known element, e.g.:
// dynamic x;
// x.foo();
handleMethodCall(node);
} else {
// Calling a toplevel function which has no known element, e.g.
// main() {
// foo();
// }
// TODO(paulberry): deal with this case. May need to notify the back
// end in case this makes it want to drag in some helper code.
throw new UnimplementedError();
}
} else if (staticElement is MethodElement) {
// Invoking a method, e.g.:
// class A {
// f() {}
// }
// main() {
// new A().f();
// }
// or via implicit this, i.e.:
// class A {
// f() {}
// foo() {
// f();
// }
// }
// TODO(paulberry): if user-provided types are wrong, this may actually
// be the PropertyAccessorElement case.
// TODO(paulberry): do we need to do something different for static
// methods?
handleMethodCall(node);
} else if (staticElement is PropertyAccessorElement) {
// Invoking a callable getter, e.g.:
// typedef FunctionType();
// class A {
// FunctionType get f { ... }
// }
// main() {
// new A().f();
// }
// or via implicit this, i.e.:
// typedef FunctionType();
// class A {
// FunctionType get f { ... }
// foo() {
// f();
// }
// }
// This also covers the case where the getter is synthetic, because we
// are getting a field (TODO(paulberry): verify that this is the case).
// TODO(paulberry): deal with this case.
// TODO(paulberry): if user-provided types are wrong, this may actually
// be the MethodElement case.
throw new UnimplementedError();
} else if (staticElement is MultiplyInheritedExecutableElement) {
// TODO(paulberry): deal with this case.
throw new UnimplementedError();
} else if (staticElement is LocalElement) {
// Invoking a callable local, e.g.:
// typedef FunctionType();
// main() {
// FunctionType f = ...;
// f();
// }
// or:
// main() {
// f() { ... }
// f();
// }
// or:
// f() {}
// main() {
// f();
// }
// TODO(paulberry): for the moment we are assuming it's a toplevel
// function.
treeShaker.addElement(staticElement);
} else if (staticElement is MultiplyDefinedElement) {
// TODO(paulberry): do we have to deal with this case?
throw new UnimplementedError();
}
// TODO(paulberry): I believe all the other possibilities are errors, but
// we should double check.
}
@override
void visitPropertyAccess(PropertyAccess node) {
// Accessing a getter or setter, e.g.:
// class A {
// get g() => ...;
// }
// main() {
// new A().g;
// }
// TODO(paulberry): do setters go through this path as well?
// TODO(paulberry): handle cases where the property access is represented
// as a PrefixedIdentifier.
super.visitPropertyAccess(node);
treeShaker.addSelector(new Selector.getter(node.propertyName.name, null));
}
}