/*
* Copyright (c) 2012, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.eclipse.org/legal/epl-v10.html
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.dart.engine.internal.resolver;
import com.google.dart.engine.ast.Annotation;
import com.google.dart.engine.ast.AstNode;
import com.google.dart.engine.ast.BinaryExpression;
import com.google.dart.engine.ast.CommentReference;
import com.google.dart.engine.ast.CompilationUnit;
import com.google.dart.engine.ast.ExportDirective;
import com.google.dart.engine.ast.Expression;
import com.google.dart.engine.ast.FunctionDeclaration;
import com.google.dart.engine.ast.FunctionExpressionInvocation;
import com.google.dart.engine.ast.ImportDirective;
import com.google.dart.engine.ast.IndexExpression;
import com.google.dart.engine.ast.LibraryDirective;
import com.google.dart.engine.ast.MethodInvocation;
import com.google.dart.engine.ast.NamedExpression;
import com.google.dart.engine.ast.PartDirective;
import com.google.dart.engine.ast.PartOfDirective;
import com.google.dart.engine.ast.PostfixExpression;
import com.google.dart.engine.ast.PrefixExpression;
import com.google.dart.engine.ast.PrefixedIdentifier;
import com.google.dart.engine.ast.PropertyAccess;
import com.google.dart.engine.ast.SimpleIdentifier;
import com.google.dart.engine.ast.visitor.RecursiveAstVisitor;
import com.google.dart.engine.element.CompilationUnitElement;
import com.google.dart.engine.element.Element;
import com.google.dart.engine.element.ElementAnnotation;
import com.google.dart.engine.element.ExportElement;
import com.google.dart.engine.element.ImportElement;
import com.google.dart.engine.element.LibraryElement;
import com.google.dart.engine.element.MethodElement;
import com.google.dart.engine.element.PrefixElement;
import com.google.dart.engine.type.Type;
import com.google.dart.engine.utilities.io.PrintStringWriter;
import junit.framework.Assert;
import java.util.ArrayList;
import java.util.Set;
/**
* Instances of the class {@code ResolutionVerifier} verify that all of the nodes in an AST
* structure that should have been resolved were resolved.
*/
public class ResolutionVerifier extends RecursiveAstVisitor<Void> {
/**
* A set containing nodes that are known to not be resolvable and should therefore not cause the
* test to fail.
*/
private Set<AstNode> knownExceptions;
/**
* A list containing all of the AST nodes that were not resolved.
*/
private ArrayList<AstNode> unresolvedNodes = new ArrayList<AstNode>();
/**
* A list containing all of the AST nodes that were resolved to an element of the wrong type.
*/
private ArrayList<AstNode> wrongTypedNodes = new ArrayList<AstNode>();
/**
* Initialize a newly created verifier to verify that all of the nodes in the visited AST
* structures that are expected to have been resolved have an element associated with them.
*/
public ResolutionVerifier() {
this(null);
}
/**
* Initialize a newly created verifier to verify that all of the identifiers in the visited AST
* structures that are expected to have been resolved have an element associated with them. Nodes
* in the set of known exceptions are not expected to have been resolved, even if they normally
* would have been expected to have been resolved.
*
* @param knownExceptions a set containing nodes that are known to not be resolvable and should
* therefore not cause the test to fail
**/
public ResolutionVerifier(Set<AstNode> knownExceptions) {
this.knownExceptions = knownExceptions;
}
/**
* Assert that all of the visited identifiers were resolved.
*/
public void assertResolved() {
if (!unresolvedNodes.isEmpty() || !wrongTypedNodes.isEmpty()) {
PrintStringWriter writer = new PrintStringWriter();
if (!unresolvedNodes.isEmpty()) {
writer.print("Failed to resolve ");
writer.print(unresolvedNodes.size());
writer.println(" nodes:");
printNodes(writer, unresolvedNodes);
}
if (!wrongTypedNodes.isEmpty()) {
writer.print("Resolved ");
writer.print(wrongTypedNodes.size());
writer.println(" to the wrong type of element:");
printNodes(writer, wrongTypedNodes);
}
Assert.fail(writer.toString());
}
}
@Override
public Void visitAnnotation(Annotation node) {
node.visitChildren(this);
ElementAnnotation elementAnnotation = node.getElementAnnotation();
if (elementAnnotation == null) {
if (knownExceptions == null || !knownExceptions.contains(node)) {
unresolvedNodes.add(node);
}
} else if (!(elementAnnotation instanceof ElementAnnotation)) {
wrongTypedNodes.add(node);
}
return null;
}
@Override
public Void visitBinaryExpression(BinaryExpression node) {
node.visitChildren(this);
if (!node.getOperator().isUserDefinableOperator()) {
return null;
}
Type operandType = node.getLeftOperand().getStaticType();
if (operandType == null || operandType.isDynamic()) {
return null;
}
return checkResolved(node, node.getStaticElement(), MethodElement.class);
}
@Override
public Void visitCommentReference(CommentReference node) {
// We don't care whether we were able to resolve references in comments.
return null;
}
@Override
public Void visitCompilationUnit(CompilationUnit node) {
node.visitChildren(this);
return checkResolved(node, node.getElement(), CompilationUnitElement.class);
}
@Override
public Void visitExportDirective(ExportDirective node) {
// Not sure how to test the combinators given that it isn't an error if the names are not defined.
return checkResolved(node, node.getElement(), ExportElement.class);
}
@Override
public Void visitFunctionDeclaration(FunctionDeclaration node) {
node.visitChildren(this);
if (node.getElement() instanceof LibraryElement) {
wrongTypedNodes.add(node);
}
return null;
}
@Override
public Void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) {
node.visitChildren(this);
// TODO(brianwilkerson) If we start resolving function expressions, then conditionally check to
// see whether the node was resolved correctly.
return null; //checkResolved(node, node.getElement(), FunctionElement.class);
}
@Override
public Void visitImportDirective(ImportDirective node) {
// Not sure how to test the combinators given that it isn't an error if the names are not defined.
checkResolved(node, node.getElement(), ImportElement.class);
SimpleIdentifier prefix = node.getPrefix();
if (prefix == null) {
return null;
}
return checkResolved(prefix, prefix.getStaticElement(), PrefixElement.class);
}
@Override
public Void visitIndexExpression(IndexExpression node) {
node.visitChildren(this);
Type targetType = node.getRealTarget().getStaticType();
if (targetType == null || targetType.isDynamic()) {
return null;
}
return checkResolved(node, node.getStaticElement(), MethodElement.class);
}
@Override
public Void visitLibraryDirective(LibraryDirective node) {
return checkResolved(node, node.getElement(), LibraryElement.class);
}
@Override
public Void visitNamedExpression(NamedExpression node) {
// We don't resolve the names of named expressions
return node.getExpression().accept(this);
}
@Override
public Void visitPartDirective(PartDirective node) {
return checkResolved(node, node.getElement(), CompilationUnitElement.class);
}
@Override
public Void visitPartOfDirective(PartOfDirective node) {
return checkResolved(node, node.getElement(), LibraryElement.class);
}
@Override
public Void visitPostfixExpression(PostfixExpression node) {
node.visitChildren(this);
if (!node.getOperator().isUserDefinableOperator()) {
return null;
}
Type operandType = node.getOperand().getStaticType();
if (operandType == null || operandType.isDynamic()) {
return null;
}
return checkResolved(node, node.getStaticElement(), MethodElement.class);
}
@Override
public Void visitPrefixedIdentifier(PrefixedIdentifier node) {
SimpleIdentifier prefix = node.getPrefix();
prefix.accept(this);
Type prefixType = prefix.getStaticType();
if (prefixType == null || prefixType.isDynamic()) {
return null;
}
return checkResolved(node, node.getStaticElement(), null);
}
@Override
public Void visitPrefixExpression(PrefixExpression node) {
node.visitChildren(this);
if (!node.getOperator().isUserDefinableOperator()) {
return null;
}
Type operandType = node.getOperand().getStaticType();
if (operandType == null || operandType.isDynamic()) {
return null;
}
return checkResolved(node, node.getStaticElement(), MethodElement.class);
}
@Override
public Void visitPropertyAccess(PropertyAccess node) {
Expression target = node.getRealTarget();
target.accept(this);
Type targetType = target.getStaticType();
if (targetType == null || targetType.isDynamic()) {
return null;
}
return node.getPropertyName().accept(this);
}
@Override
public Void visitSimpleIdentifier(SimpleIdentifier node) {
if (node.getName().equals("void")) {
return null;
}
AstNode parent = node.getParent();
if (parent instanceof MethodInvocation) {
MethodInvocation invocation = ((MethodInvocation) parent);
if (invocation.getMethodName() == node) {
Expression target = invocation.getRealTarget();
Type targetType = target == null ? null : target.getStaticType();
if (targetType == null || targetType.isDynamic()) {
return null;
}
}
}
return checkResolved(node, node.getStaticElement(), null);
}
private Void checkResolved(AstNode node, Element element, Class<? extends Element> expectedClass) {
if (element == null) {
if (knownExceptions == null || !knownExceptions.contains(node)) {
unresolvedNodes.add(node);
}
} else if (expectedClass != null) {
if (!expectedClass.isInstance(element)) {
wrongTypedNodes.add(node);
}
}
return null;
}
private String getFileName(AstNode node) {
// TODO (jwren) there are two copies of this method, one here and one in StaticTypeVerifier,
// they should be resolved into a single method
if (node != null) {
AstNode root = node.getRoot();
if (root instanceof CompilationUnit) {
CompilationUnit rootCU = ((CompilationUnit) root);
if (rootCU.getElement() != null) {
return rootCU.getElement().getSource().getFullName();
} else {
return "<unknown file- CompilationUnit.getElement() returned null>";
}
} else {
return "<unknown file- CompilationUnit.getRoot() is not a CompilationUnit>";
}
}
return "<unknown file- ASTNode is null>";
}
private void printNodes(PrintStringWriter writer, ArrayList<AstNode> nodes) {
for (AstNode identifier : nodes) {
writer.print(" ");
writer.print(identifier.toString());
writer.print(" (");
writer.print(getFileName(identifier));
writer.print(" : ");
writer.print(identifier.getOffset());
writer.println(")");
}
}
}