/* * 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.AstNode; import com.google.dart.engine.ast.BreakStatement; import com.google.dart.engine.ast.CommentReference; import com.google.dart.engine.ast.CompilationUnit; import com.google.dart.engine.ast.ConstructorFieldInitializer; import com.google.dart.engine.ast.ConstructorName; import com.google.dart.engine.ast.ContinueStatement; import com.google.dart.engine.ast.ExportDirective; import com.google.dart.engine.ast.Expression; import com.google.dart.engine.ast.ImportDirective; import com.google.dart.engine.ast.Label; import com.google.dart.engine.ast.LibraryIdentifier; import com.google.dart.engine.ast.MethodInvocation; import com.google.dart.engine.ast.PrefixedIdentifier; import com.google.dart.engine.ast.RedirectingConstructorInvocation; import com.google.dart.engine.ast.SimpleIdentifier; import com.google.dart.engine.ast.SuperConstructorInvocation; import com.google.dart.engine.ast.TypeName; import com.google.dart.engine.ast.visitor.GeneralizingAstVisitor; import com.google.dart.engine.element.PrefixElement; import com.google.dart.engine.internal.type.DynamicTypeImpl; import com.google.dart.engine.type.Type; import com.google.dart.engine.utilities.io.PrintStringWriter; import junit.framework.Assert; import java.util.ArrayList; /** * Instances of the class {@code StaticTypeVerifier} verify that all of the nodes in an AST * structure that should have a static type associated with them do have a static type. */ public class StaticTypeVerifier extends GeneralizingAstVisitor<Void> { /** * A list containing all of the AST Expression nodes that were not resolved. */ private ArrayList<Expression> unresolvedExpressions = new ArrayList<Expression>(); /** * A list containing all of the AST Expression nodes for which a propagated type was computed but * where that type was not more specific than the static type. */ private ArrayList<Expression> invalidlyPropagatedExpressions = new ArrayList<Expression>(); /** * A list containing all of the AST TypeName nodes that were not resolved. */ private ArrayList<TypeName> unresolvedTypes = new ArrayList<TypeName>(); /** * Counter for the number of Expression nodes visited that are resolved. */ int resolvedExpressionCount = 0; /** * Counter for the number of Expression nodes visited that have propagated type information. */ int propagatedExpressionCount = 0; /** * Counter for the number of TypeName nodes visited that are resolved. */ int resolvedTypeCount = 0; /** * Initialize a newly created verifier to verify that all of the nodes in an AST structure that * should have a static type associated with them do have a static type. */ public StaticTypeVerifier() { super(); } /** * Assert that all of the visited nodes have a static type associated with them. */ public void assertResolved() { if (!unresolvedExpressions.isEmpty() /*|| !invalidlyPropagatedExpressions.isEmpty()*/ || !unresolvedTypes.isEmpty()) { @SuppressWarnings("resource") PrintStringWriter writer = new PrintStringWriter(); int unresolvedTypeCount = unresolvedTypes.size(); if (unresolvedTypeCount > 0) { writer.print("Failed to resolve "); writer.print(unresolvedTypeCount); writer.print(" of "); writer.print(resolvedTypeCount + unresolvedTypeCount); writer.println(" type names:"); for (TypeName identifier : unresolvedTypes) { writer.print(" "); writer.print(identifier.toString()); writer.print(" ("); writer.print(getFileName(identifier)); writer.print(" : "); writer.print(identifier.getOffset()); writer.println(")"); } } int unresolvedExpressionCount = unresolvedExpressions.size(); if (unresolvedExpressionCount > 0) { writer.println("Failed to resolve "); writer.print(unresolvedExpressionCount); writer.print(" of "); writer.print(resolvedExpressionCount + unresolvedExpressionCount); writer.println(" expressions:"); for (Expression expression : unresolvedExpressions) { writer.print(" "); writer.print(expression.toString()); writer.print(" ("); writer.print(getFileName(expression)); writer.print(" : "); writer.print(expression.getOffset()); writer.println(")"); } } int invalidlyPropagatedExpressionCount = invalidlyPropagatedExpressions.size(); if (invalidlyPropagatedExpressionCount > 0) { writer.println("Incorrectly propagated "); writer.print(invalidlyPropagatedExpressionCount); writer.print(" of "); writer.print(propagatedExpressionCount); writer.println(" expressions:"); for (Expression expression : invalidlyPropagatedExpressions) { writer.print(" "); writer.print(expression.toString()); writer.print(" ["); writer.print(expression.getStaticType().getDisplayName()); writer.print(", "); writer.print(expression.getPropagatedType().getDisplayName()); writer.println("]"); writer.print(" "); writer.print(getFileName(expression)); writer.print(" : "); writer.print(expression.getOffset()); writer.println(")"); } } Assert.fail(writer.toString()); } } @Override public Void visitBreakStatement(BreakStatement node) { // Don't visit the children because the identifier (if it exists) is not expected to have a type. return null; } @Override public Void visitCommentReference(CommentReference node) { // Do nothing. return null; } @Override public Void visitContinueStatement(ContinueStatement node) { // Don't visit the children because the identifier (if it exists) is not expected to have a type. return null; } @Override public Void visitExportDirective(ExportDirective node) { // Don't visit the children because they are not expected to have a type. return null; } @Override public Void visitExpression(Expression node) { node.visitChildren(this); Type staticType = node.getStaticType(); if (staticType == null) { unresolvedExpressions.add(node); } else { resolvedExpressionCount++; Type propagatedType = node.getPropagatedType(); if (propagatedType != null) { propagatedExpressionCount++; if (!propagatedType.isMoreSpecificThan(staticType)) { invalidlyPropagatedExpressions.add(node); } } } return null; } @Override public Void visitImportDirective(ImportDirective node) { // Don't visit the children because they are not expected to have a type. return null; } @Override public Void visitLabel(Label node) { // Don't visit the children because the identifier is not expected to have a type. return null; } @Override public Void visitLibraryIdentifier(LibraryIdentifier node) { // Do nothing, LibraryIdentifiers and children don't have an associated static type. return null; } @Override public Void visitPrefixedIdentifier(PrefixedIdentifier node) { // In cases where we have a prefixed identifier where the prefix is dynamic, we don't want to // assert that the node will have a type. if (node.getStaticType() == null && node.getPrefix().getStaticType() == DynamicTypeImpl.getInstance()) { return null; } return super.visitPrefixedIdentifier(node); } @Override public Void visitSimpleIdentifier(SimpleIdentifier node) { // In cases where identifiers are being used for something other than an expressions, // then they can be ignored. AstNode parent = node.getParent(); if (parent instanceof MethodInvocation && node == ((MethodInvocation) parent).getMethodName()) { return null; } else if (parent instanceof RedirectingConstructorInvocation && node == ((RedirectingConstructorInvocation) parent).getConstructorName()) { return null; } else if (parent instanceof SuperConstructorInvocation && node == ((SuperConstructorInvocation) parent).getConstructorName()) { return null; } else if (parent instanceof ConstructorName && node == ((ConstructorName) parent).getName()) { return null; } else if (parent instanceof ConstructorFieldInitializer && node == ((ConstructorFieldInitializer) parent).getFieldName()) { return null; } else if (node.getStaticElement() instanceof PrefixElement) { // Prefixes don't have a type. return null; } return super.visitSimpleIdentifier(node); } @Override public Void visitTypeName(TypeName node) { // Note: do not visit children from this node, the child SimpleIdentifier in TypeName // (i.e. "String") does not have a static type defined. if (node.getType() == null) { unresolvedTypes.add(node); } else { resolvedTypeCount++; } return null; } private String getFileName(AstNode node) { // TODO (jwren) there are two copies of this method, one here and one in ResolutionVerifier, // 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>"; } }