/*
* Copyright (c) 2013, 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.AnalysisEngine;
import com.google.dart.engine.ast.Annotation;
import com.google.dart.engine.ast.ArgumentList;
import com.google.dart.engine.ast.AsExpression;
import com.google.dart.engine.ast.AssertStatement;
import com.google.dart.engine.ast.AstNode;
import com.google.dart.engine.ast.BinaryExpression;
import com.google.dart.engine.ast.Block;
import com.google.dart.engine.ast.BlockFunctionBody;
import com.google.dart.engine.ast.BreakStatement;
import com.google.dart.engine.ast.ClassDeclaration;
import com.google.dart.engine.ast.Comment;
import com.google.dart.engine.ast.CommentReference;
import com.google.dart.engine.ast.CompilationUnit;
import com.google.dart.engine.ast.CompilationUnitMember;
import com.google.dart.engine.ast.ConditionalExpression;
import com.google.dart.engine.ast.ConstructorDeclaration;
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.DeclaredIdentifier;
import com.google.dart.engine.ast.Directive;
import com.google.dart.engine.ast.DoStatement;
import com.google.dart.engine.ast.EmptyFunctionBody;
import com.google.dart.engine.ast.EnumDeclaration;
import com.google.dart.engine.ast.Expression;
import com.google.dart.engine.ast.ExpressionFunctionBody;
import com.google.dart.engine.ast.ExpressionStatement;
import com.google.dart.engine.ast.FieldDeclaration;
import com.google.dart.engine.ast.ForEachStatement;
import com.google.dart.engine.ast.ForStatement;
import com.google.dart.engine.ast.FormalParameter;
import com.google.dart.engine.ast.FunctionDeclaration;
import com.google.dart.engine.ast.FunctionExpression;
import com.google.dart.engine.ast.FunctionExpressionInvocation;
import com.google.dart.engine.ast.FunctionTypeAlias;
import com.google.dart.engine.ast.HideCombinator;
import com.google.dart.engine.ast.IfStatement;
import com.google.dart.engine.ast.IsExpression;
import com.google.dart.engine.ast.Label;
import com.google.dart.engine.ast.LibraryIdentifier;
import com.google.dart.engine.ast.MethodDeclaration;
import com.google.dart.engine.ast.MethodInvocation;
import com.google.dart.engine.ast.NodeList;
import com.google.dart.engine.ast.ParenthesizedExpression;
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.RedirectingConstructorInvocation;
import com.google.dart.engine.ast.RethrowExpression;
import com.google.dart.engine.ast.ReturnStatement;
import com.google.dart.engine.ast.ShowCombinator;
import com.google.dart.engine.ast.SimpleIdentifier;
import com.google.dart.engine.ast.Statement;
import com.google.dart.engine.ast.SuperConstructorInvocation;
import com.google.dart.engine.ast.SwitchCase;
import com.google.dart.engine.ast.SwitchDefault;
import com.google.dart.engine.ast.ThrowExpression;
import com.google.dart.engine.ast.TopLevelVariableDeclaration;
import com.google.dart.engine.ast.TypeName;
import com.google.dart.engine.ast.WhileStatement;
import com.google.dart.engine.ast.visitor.RecursiveAstVisitor;
import com.google.dart.engine.element.ClassElement;
import com.google.dart.engine.element.Element;
import com.google.dart.engine.element.ElementKind;
import com.google.dart.engine.element.ExecutableElement;
import com.google.dart.engine.element.LibraryElement;
import com.google.dart.engine.element.LocalVariableElement;
import com.google.dart.engine.element.ParameterElement;
import com.google.dart.engine.element.PropertyInducingElement;
import com.google.dart.engine.element.VariableElement;
import com.google.dart.engine.error.AnalysisErrorListener;
import com.google.dart.engine.internal.element.PropertyInducingElementImpl;
import com.google.dart.engine.internal.element.VariableElementImpl;
import com.google.dart.engine.internal.scope.Scope;
import com.google.dart.engine.scanner.TokenType;
import com.google.dart.engine.source.Source;
import com.google.dart.engine.type.FunctionType;
import com.google.dart.engine.type.InterfaceType;
import com.google.dart.engine.type.Type;
import java.util.Map;
/**
* Instances of the class {@code ResolverVisitor} are used to resolve the nodes within a single
* compilation unit.
*
* @coverage dart.engine.resolver
*/
public class ResolverVisitor extends ScopedVisitor {
/**
* The manager for the inheritance mappings.
*/
private final InheritanceManager inheritanceManager;
/**
* The object used to resolve the element associated with the current node.
*/
private ElementResolver elementResolver;
/**
* The object used to compute the type associated with the current node.
*/
private StaticTypeAnalyzer typeAnalyzer;
/**
* The class element representing the class containing the current node, or {@code null} if the
* current node is not contained in a class.
*/
private ClassElement enclosingClass = null;
/**
* The class declaration representing the class containing the current node, or {@code null} if
* the current node is not contained in a class.
*/
private ClassDeclaration enclosingClassDeclaration = null;
/**
* The function type alias representing the function type containing the current node, or
* {@code null} if the current node is not contained in a function type alias.
*/
private FunctionTypeAlias enclosingFunctionTypeAlias = null;
/**
* The element representing the function containing the current node, or {@code null} if the
* current node is not contained in a function.
*/
private ExecutableElement enclosingFunction = null;
/**
* The {@link Comment} before a {@link FunctionDeclaration} or a {@link MethodDeclaration} that
* cannot be resolved where we visited it, because it should be resolved in the scope of the body.
*/
private Comment commentBeforeFunction = null;
/**
* The object keeping track of which elements have had their types overridden.
*/
private TypeOverrideManager overrideManager = new TypeOverrideManager();
/**
* The object keeping track of which elements have had their types promoted.
*/
private TypePromotionManager promoteManager = new TypePromotionManager();
/**
* Initialize a newly created visitor to resolve the nodes in a compilation unit.
*
* @param library the library containing the compilation unit being resolved
* @param source the source representing the compilation unit being visited
* @param typeProvider the object used to access the types from the core library
*/
public ResolverVisitor(Library library, Source source, TypeProvider typeProvider) {
super(library, source, typeProvider);
this.inheritanceManager = library.getInheritanceManager();
this.elementResolver = new ElementResolver(this);
this.typeAnalyzer = new StaticTypeAnalyzer(this);
}
/**
* Initialize a newly created visitor to resolve the nodes in a compilation unit.
*
* @param definingLibrary the element for the library containing the compilation unit being
* visited
* @param source the source representing the compilation unit being visited
* @param typeProvider the object used to access the types from the core library
* @param errorListener the error listener that will be informed of any errors that are found
* during resolution
*/
public ResolverVisitor(LibraryElement definingLibrary, Source source, TypeProvider typeProvider,
InheritanceManager inheritanceManager, AnalysisErrorListener errorListener) {
super(definingLibrary, source, typeProvider, errorListener);
this.inheritanceManager = inheritanceManager;
this.elementResolver = new ElementResolver(this);
this.typeAnalyzer = new StaticTypeAnalyzer(this);
}
/**
* Initialize a newly created visitor to resolve the nodes in an AST node.
*
* @param definingLibrary the element for the library containing the node being visited
* @param source the source representing the compilation unit containing the node being visited
* @param typeProvider the object used to access the types from the core library
* @param nameScope the scope used to resolve identifiers in the node that will first be visited
* @param errorListener the error listener that will be informed of any errors that are found
* during resolution
*/
public ResolverVisitor(LibraryElement definingLibrary, Source source, TypeProvider typeProvider,
Scope nameScope, AnalysisErrorListener errorListener) {
super(definingLibrary, source, typeProvider, nameScope, errorListener);
this.inheritanceManager = new InheritanceManager(definingLibrary);
this.elementResolver = new ElementResolver(this);
this.typeAnalyzer = new StaticTypeAnalyzer(this);
}
/**
* Initialize a newly created visitor to resolve the nodes in a compilation unit.
*
* @param library the library containing the compilation unit being resolved
* @param source the source representing the compilation unit being visited
* @param typeProvider the object used to access the types from the core library
*/
public ResolverVisitor(ResolvableLibrary library, Source source, TypeProvider typeProvider) {
super(library, source, typeProvider);
this.inheritanceManager = library.getInheritanceManager();
this.elementResolver = new ElementResolver(this);
this.typeAnalyzer = new StaticTypeAnalyzer(this);
}
/**
* Return the object keeping track of which elements have had their types overridden.
*
* @return the object keeping track of which elements have had their types overridden
*/
public TypeOverrideManager getOverrideManager() {
return overrideManager;
}
/**
* Return the object keeping track of which elements have had their types promoted.
*
* @return the object keeping track of which elements have had their types promoted
*/
public TypePromotionManager getPromoteManager() {
return promoteManager;
}
@Override
public Void visitAnnotation(Annotation node) {
AstNode parent = node.getParent();
if (parent == enclosingClassDeclaration || parent == enclosingFunctionTypeAlias) {
return null;
}
return super.visitAnnotation(node);
}
@Override
public Void visitAsExpression(AsExpression node) {
super.visitAsExpression(node);
// Since an as-statement doesn't actually change the type, we don't
// let it affect the propagated type when it would result in a loss
// of precision.
overrideExpression(node.getExpression(), node.getType().getType(), false);
return null;
}
@Override
public Void visitAssertStatement(AssertStatement node) {
super.visitAssertStatement(node);
propagateTrueState(node.getCondition());
return null;
}
@Override
public Void visitBinaryExpression(BinaryExpression node) {
TokenType operatorType = node.getOperator().getType();
Expression leftOperand = node.getLeftOperand();
Expression rightOperand = node.getRightOperand();
if (operatorType == TokenType.AMPERSAND_AMPERSAND) {
safelyVisit(leftOperand);
if (rightOperand != null) {
overrideManager.enterScope();
try {
promoteManager.enterScope();
try {
propagateTrueState(leftOperand);
// Type promotion.
promoteTypes(leftOperand);
clearTypePromotionsIfPotentiallyMutatedIn(leftOperand);
clearTypePromotionsIfPotentiallyMutatedIn(rightOperand);
clearTypePromotionsIfAccessedInClosureAndProtentiallyMutated(rightOperand);
// Visit right operand.
rightOperand.accept(this);
} finally {
promoteManager.exitScope();
}
} finally {
overrideManager.exitScope();
}
}
} else if (operatorType == TokenType.BAR_BAR) {
safelyVisit(leftOperand);
if (rightOperand != null) {
overrideManager.enterScope();
try {
propagateFalseState(leftOperand);
rightOperand.accept(this);
} finally {
overrideManager.exitScope();
}
}
} else {
safelyVisit(leftOperand);
safelyVisit(rightOperand);
}
node.accept(elementResolver);
node.accept(typeAnalyzer);
return null;
}
@Override
public Void visitBlockFunctionBody(BlockFunctionBody node) {
safelyVisit(commentBeforeFunction);
overrideManager.enterScope();
try {
super.visitBlockFunctionBody(node);
} finally {
overrideManager.exitScope();
}
return null;
}
@Override
public Void visitBreakStatement(BreakStatement node) {
//
// We do not visit the label because it needs to be visited in the context of the statement.
//
node.accept(elementResolver);
node.accept(typeAnalyzer);
return null;
}
@Override
public Void visitClassDeclaration(ClassDeclaration node) {
//
// Resolve the metadata in the library scope.
//
if (node.getMetadata() != null) {
node.getMetadata().accept(this);
}
enclosingClassDeclaration = node;
//
// Continue the class resolution.
//
ClassElement outerType = enclosingClass;
try {
enclosingClass = node.getElement();
typeAnalyzer.setThisType(enclosingClass == null ? null : enclosingClass.getType());
super.visitClassDeclaration(node);
node.accept(elementResolver);
node.accept(typeAnalyzer);
} finally {
typeAnalyzer.setThisType(outerType == null ? null : outerType.getType());
enclosingClass = outerType;
enclosingClassDeclaration = null;
}
return null;
}
@Override
public Void visitComment(Comment node) {
if (node.getParent() instanceof FunctionDeclaration
|| node.getParent() instanceof ConstructorDeclaration
|| node.getParent() instanceof MethodDeclaration) {
if (node != commentBeforeFunction) {
commentBeforeFunction = node;
return null;
}
}
super.visitComment(node);
commentBeforeFunction = null;
return null;
}
@Override
public Void visitCommentReference(CommentReference node) {
//
// We do not visit the identifier because it needs to be visited in the context of the reference.
//
node.accept(elementResolver);
node.accept(typeAnalyzer);
return null;
}
@Override
public Void visitCompilationUnit(CompilationUnit node) {
//
// TODO(brianwilkerson) The goal of the code below is to visit the declarations in such an
// order that we can infer type information for top-level variables before we visit references
// to them. This is better than making no effort, but still doesn't completely satisfy that
// goal (consider for example "final var a = b; final var b = 0;"; we'll infer a type of 'int'
// for 'b', but not for 'a' because of the order of the visits). Ideally we would create a
// dependency graph, but that would require references to be resolved, which they are not.
//
overrideManager.enterScope();
try {
NodeList<Directive> directives = node.getDirectives();
int directiveCount = directives.size();
for (int i = 0; i < directiveCount; i++) {
directives.get(i).accept(this);
}
NodeList<CompilationUnitMember> declarations = node.getDeclarations();
int declarationCount = declarations.size();
for (int i = 0; i < declarationCount; i++) {
CompilationUnitMember declaration = declarations.get(i);
if (!(declaration instanceof ClassDeclaration)) {
declaration.accept(this);
}
}
for (int i = 0; i < declarationCount; i++) {
CompilationUnitMember declaration = declarations.get(i);
if (declaration instanceof ClassDeclaration) {
declaration.accept(this);
}
}
} finally {
overrideManager.exitScope();
}
node.accept(elementResolver);
node.accept(typeAnalyzer);
return null;
}
@Override
public Void visitConditionalExpression(ConditionalExpression node) {
Expression condition = node.getCondition();
safelyVisit(condition);
Expression thenExpression = node.getThenExpression();
if (thenExpression != null) {
overrideManager.enterScope();
try {
promoteManager.enterScope();
try {
propagateTrueState(condition);
// Type promotion.
promoteTypes(condition);
clearTypePromotionsIfPotentiallyMutatedIn(thenExpression);
clearTypePromotionsIfAccessedInClosureAndProtentiallyMutated(thenExpression);
// Visit "then" expression.
thenExpression.accept(this);
} finally {
promoteManager.exitScope();
}
} finally {
overrideManager.exitScope();
}
}
Expression elseExpression = node.getElseExpression();
if (elseExpression != null) {
overrideManager.enterScope();
try {
propagateFalseState(condition);
elseExpression.accept(this);
} finally {
overrideManager.exitScope();
}
}
node.accept(elementResolver);
node.accept(typeAnalyzer);
boolean thenIsAbrupt = isAbruptTerminationExpression(thenExpression);
boolean elseIsAbrupt = isAbruptTerminationExpression(elseExpression);
if (elseIsAbrupt && !thenIsAbrupt) {
propagateTrueState(condition);
propagateState(thenExpression);
} else if (thenIsAbrupt && !elseIsAbrupt) {
propagateFalseState(condition);
propagateState(elseExpression);
}
return null;
}
@Override
public Void visitConstructorDeclaration(ConstructorDeclaration node) {
ExecutableElement outerFunction = enclosingFunction;
try {
enclosingFunction = node.getElement();
super.visitConstructorDeclaration(node);
} finally {
enclosingFunction = outerFunction;
}
return null;
}
@Override
public Void visitConstructorFieldInitializer(ConstructorFieldInitializer node) {
//
// We visit the expression, but do not visit the field name because it needs to be visited in
// the context of the constructor field initializer node.
//
safelyVisit(node.getExpression());
node.accept(elementResolver);
node.accept(typeAnalyzer);
return null;
}
@Override
public Void visitConstructorName(ConstructorName node) {
//
// We do not visit either the type name, because it won't be visited anyway, or the name,
// because it needs to be visited in the context of the constructor name.
//
node.accept(elementResolver);
node.accept(typeAnalyzer);
return null;
}
@Override
public Void visitContinueStatement(ContinueStatement node) {
//
// We do not visit the label because it needs to be visited in the context of the statement.
//
node.accept(elementResolver);
node.accept(typeAnalyzer);
return null;
}
@Override
public Void visitDoStatement(DoStatement node) {
overrideManager.enterScope();
try {
super.visitDoStatement(node);
} finally {
overrideManager.exitScope();
}
// TODO(brianwilkerson) If the loop can only be exited because the condition is false, then
// propagateFalseState(node.getCondition());
return null;
}
@Override
public Void visitEmptyFunctionBody(EmptyFunctionBody node) {
safelyVisit(commentBeforeFunction);
return super.visitEmptyFunctionBody(node);
}
@Override
public Void visitEnumDeclaration(EnumDeclaration node) {
//
// Resolve the metadata in the library scope.
//
if (node.getMetadata() != null) {
node.getMetadata().accept(this);
}
//
// There is nothing else to do because everything else was resolved by the element builder.
//
return null;
}
@Override
public Void visitExpressionFunctionBody(ExpressionFunctionBody node) {
safelyVisit(commentBeforeFunction);
overrideManager.enterScope();
try {
super.visitExpressionFunctionBody(node);
} finally {
overrideManager.exitScope();
}
return null;
}
// @Override
// public Void visitEmptyFunctionBody(EmptyFunctionBody node) {
// overrideManager.enterScope();
// try {
// super.visitEmptyFunctionBody(node);
// } finally {
// overrideManager.exitScope();
// }
// return null;
// }
@Override
public Void visitFieldDeclaration(FieldDeclaration node) {
overrideManager.enterScope();
try {
super.visitFieldDeclaration(node);
} finally {
Map<Element, Type> overrides = overrideManager.captureOverrides(node.getFields());
overrideManager.exitScope();
overrideManager.applyOverrides(overrides);
}
return null;
}
@Override
public Void visitForEachStatement(ForEachStatement node) {
overrideManager.enterScope();
try {
super.visitForEachStatement(node);
} finally {
overrideManager.exitScope();
}
return null;
}
@Override
public Void visitForStatement(ForStatement node) {
overrideManager.enterScope();
try {
super.visitForStatement(node);
} finally {
overrideManager.exitScope();
}
return null;
}
@Override
public Void visitFunctionDeclaration(FunctionDeclaration node) {
ExecutableElement outerFunction = enclosingFunction;
try {
SimpleIdentifier functionName = node.getName();
enclosingFunction = (ExecutableElement) functionName.getStaticElement();
super.visitFunctionDeclaration(node);
} finally {
enclosingFunction = outerFunction;
}
return null;
}
@Override
public Void visitFunctionExpression(FunctionExpression node) {
ExecutableElement outerFunction = enclosingFunction;
try {
enclosingFunction = node.getElement();
overrideManager.enterScope();
try {
super.visitFunctionExpression(node);
} finally {
overrideManager.exitScope();
}
} finally {
enclosingFunction = outerFunction;
}
return null;
}
@Override
public Void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) {
safelyVisit(node.getFunction());
node.accept(elementResolver);
inferFunctionExpressionsParametersTypes(node.getArgumentList());
safelyVisit(node.getArgumentList());
node.accept(typeAnalyzer);
return null;
}
@Override
public Void visitFunctionTypeAlias(FunctionTypeAlias node) {
// Resolve the metadata in the library scope.
if (node.getMetadata() != null) {
node.getMetadata().accept(this);
}
FunctionTypeAlias outerAlias = enclosingFunctionTypeAlias;
enclosingFunctionTypeAlias = node;
try {
super.visitFunctionTypeAlias(node);
} finally {
enclosingFunctionTypeAlias = outerAlias;
}
return null;
}
@Override
public Void visitHideCombinator(HideCombinator node) {
//
// Combinators aren't visited by this visitor, the LibraryResolver has already resolved the
// identifiers and there is no type analysis to be done.
//
return null;
}
@Override
public Void visitIfStatement(IfStatement node) {
Expression condition = node.getCondition();
safelyVisit(condition);
Map<Element, Type> thenOverrides = null;
Statement thenStatement = node.getThenStatement();
if (thenStatement != null) {
overrideManager.enterScope();
try {
promoteManager.enterScope();
try {
propagateTrueState(condition);
// Type promotion.
promoteTypes(condition);
clearTypePromotionsIfPotentiallyMutatedIn(thenStatement);
clearTypePromotionsIfAccessedInClosureAndProtentiallyMutated(thenStatement);
// Visit "then".
visitStatementInScope(thenStatement);
} finally {
promoteManager.exitScope();
}
} finally {
thenOverrides = overrideManager.captureLocalOverrides();
overrideManager.exitScope();
}
}
Map<Element, Type> elseOverrides = null;
Statement elseStatement = node.getElseStatement();
if (elseStatement != null) {
overrideManager.enterScope();
try {
propagateFalseState(condition);
visitStatementInScope(elseStatement);
} finally {
elseOverrides = overrideManager.captureLocalOverrides();
overrideManager.exitScope();
}
}
node.accept(elementResolver);
node.accept(typeAnalyzer);
boolean thenIsAbrupt = isAbruptTerminationStatement(thenStatement);
boolean elseIsAbrupt = isAbruptTerminationStatement(elseStatement);
if (elseIsAbrupt && !thenIsAbrupt) {
propagateTrueState(condition);
if (thenOverrides != null) {
overrideManager.applyOverrides(thenOverrides);
}
} else if (thenIsAbrupt && !elseIsAbrupt) {
propagateFalseState(condition);
if (elseOverrides != null) {
overrideManager.applyOverrides(elseOverrides);
}
} else if (!thenIsAbrupt && !elseIsAbrupt) {
if (AnalysisEngine.getInstance().getEnableUnionTypes()) {
// It would be more precise to ignore the existing override for any variable that
// is overridden in both branches.
if (thenOverrides != null) {
overrideManager.mergeOverrides(thenOverrides);
}
if (elseOverrides != null) {
overrideManager.mergeOverrides(elseOverrides);
}
}
}
return null;
}
@Override
public Void visitLabel(Label node) {
//
// We don't visit labels or their children because they don't have a type. Instead, the element
// resolver is responsible for resolving the labels in the context of their parent (either a
// BreakStatement, ContinueStatement, or NamedExpression).
//
return null;
}
@Override
public Void visitLibraryIdentifier(LibraryIdentifier node) {
//
// We don't visit library identifiers or their children because they have already been resolved.
//
return null;
}
@Override
public Void visitMethodDeclaration(MethodDeclaration node) {
ExecutableElement outerFunction = enclosingFunction;
try {
enclosingFunction = node.getElement();
super.visitMethodDeclaration(node);
} finally {
enclosingFunction = outerFunction;
}
return null;
}
@Override
public Void visitMethodInvocation(MethodInvocation node) {
//
// We visit the target and argument list, but do not visit the method name because it needs to
// be visited in the context of the invocation.
//
safelyVisit(node.getTarget());
node.accept(elementResolver);
inferFunctionExpressionsParametersTypes(node.getArgumentList());
safelyVisit(node.getArgumentList());
node.accept(typeAnalyzer);
return null;
}
@Override
public Void visitNode(AstNode node) {
node.visitChildren(this);
node.accept(elementResolver);
node.accept(typeAnalyzer);
return null;
}
@Override
public Void visitPrefixedIdentifier(PrefixedIdentifier node) {
//
// We visit the prefix, but do not visit the identifier because it needs to be visited in the
// context of the prefix.
//
safelyVisit(node.getPrefix());
node.accept(elementResolver);
node.accept(typeAnalyzer);
return null;
}
@Override
public Void visitPropertyAccess(PropertyAccess node) {
//
// We visit the target, but do not visit the property name because it needs to be visited in the
// context of the property access node.
//
safelyVisit(node.getTarget());
node.accept(elementResolver);
node.accept(typeAnalyzer);
return null;
}
@Override
public Void visitRedirectingConstructorInvocation(RedirectingConstructorInvocation node) {
//
// We visit the argument list, but do not visit the optional identifier because it needs to be
// visited in the context of the constructor invocation.
//
safelyVisit(node.getArgumentList());
node.accept(elementResolver);
node.accept(typeAnalyzer);
return null;
}
@Override
public Void visitShowCombinator(ShowCombinator node) {
//
// Combinators aren't visited by this visitor, the LibraryResolver has already resolved the
// identifiers and there is no type analysis to be done.
//
return null;
}
@Override
public Void visitSuperConstructorInvocation(SuperConstructorInvocation node) {
//
// We visit the argument list, but do not visit the optional identifier because it needs to be
// visited in the context of the constructor invocation.
//
safelyVisit(node.getArgumentList());
node.accept(elementResolver);
node.accept(typeAnalyzer);
return null;
}
@Override
public Void visitSwitchCase(SwitchCase node) {
overrideManager.enterScope();
try {
super.visitSwitchCase(node);
} finally {
overrideManager.exitScope();
}
return null;
}
@Override
public Void visitSwitchDefault(SwitchDefault node) {
overrideManager.enterScope();
try {
super.visitSwitchDefault(node);
} finally {
overrideManager.exitScope();
}
return null;
}
@Override
public Void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
overrideManager.enterScope();
try {
super.visitTopLevelVariableDeclaration(node);
} finally {
Map<Element, Type> overrides = overrideManager.captureOverrides(node.getVariables());
overrideManager.exitScope();
overrideManager.applyOverrides(overrides);
}
return null;
}
@Override
public Void visitTypeName(TypeName node) {
//
// We don't visit type names or their children because they have already been resolved.
//
return null;
}
@Override
public Void visitWhileStatement(WhileStatement node) {
Expression condition = node.getCondition();
safelyVisit(condition);
Statement body = node.getBody();
if (body != null) {
overrideManager.enterScope();
try {
propagateTrueState(condition);
visitStatementInScope(body);
} finally {
overrideManager.exitScope();
}
}
// TODO(brianwilkerson) If the loop can only be exited because the condition is false, then
// propagateFalseState(condition);
node.accept(elementResolver);
node.accept(typeAnalyzer);
return null;
}
/**
* Return the class element representing the class containing the current node, or {@code null} if
* the current node is not contained in a class.
*
* @return the class element representing the class containing the current node
*/
protected ClassElement getEnclosingClass() {
return enclosingClass;
}
/**
* Return the element representing the function containing the current node, or {@code null} if
* the current node is not contained in a function.
*
* @return the element representing the function containing the current node
*/
protected ExecutableElement getEnclosingFunction() {
return enclosingFunction;
}
/**
* Return the propagated element associated with the given expression whose type can be
* overridden, or {@code null} if there is no element whose type can be overridden.
*
* @param expression the expression with which the element is associated
* @return the element associated with the given expression
*/
protected VariableElement getOverridablePropagatedElement(Expression expression) {
Element element = null;
if (expression instanceof SimpleIdentifier) {
element = ((SimpleIdentifier) expression).getPropagatedElement();
} else if (expression instanceof PrefixedIdentifier) {
element = ((PrefixedIdentifier) expression).getPropagatedElement();
} else if (expression instanceof PropertyAccess) {
element = ((PropertyAccess) expression).getPropertyName().getPropagatedElement();
}
if (element instanceof VariableElement) {
return (VariableElement) element;
}
return null;
}
/**
* Return the static element associated with the given expression whose type can be overridden, or
* {@code null} if there is no element whose type can be overridden.
*
* @param expression the expression with which the element is associated
* @return the element associated with the given expression
*/
protected VariableElement getOverridableStaticElement(Expression expression) {
Element element = null;
if (expression instanceof SimpleIdentifier) {
element = ((SimpleIdentifier) expression).getStaticElement();
} else if (expression instanceof PrefixedIdentifier) {
element = ((PrefixedIdentifier) expression).getStaticElement();
} else if (expression instanceof PropertyAccess) {
element = ((PropertyAccess) expression).getPropertyName().getStaticElement();
}
if (element instanceof VariableElement) {
return (VariableElement) element;
}
return null;
}
/**
* Return the static element associated with the given expression whose type can be promoted, or
* {@code null} if there is no element whose type can be promoted.
*
* @param expression the expression with which the element is associated
* @return the element associated with the given expression
*/
protected VariableElement getPromotionStaticElement(Expression expression) {
while (expression instanceof ParenthesizedExpression) {
expression = ((ParenthesizedExpression) expression).getExpression();
}
if (!(expression instanceof SimpleIdentifier)) {
return null;
}
SimpleIdentifier identifier = (SimpleIdentifier) expression;
Element element = identifier.getStaticElement();
if (!(element instanceof VariableElement)) {
return null;
}
ElementKind kind = element.getKind();
if (kind == ElementKind.LOCAL_VARIABLE) {
return (VariableElement) element;
}
if (kind == ElementKind.PARAMETER) {
return (VariableElement) element;
}
return null;
}
/**
* If it is appropriate to do so, override the current type of the static and propagated elements
* associated with the given expression with the given type. Generally speaking, it is appropriate
* if the given type is more specific than the current type.
*
* @param expression the expression used to access the static and propagated elements whose types
* might be overridden
* @param potentialType the potential type of the elements
* @param allowPrecisionLoss see @{code overrideVariable} docs
*/
protected void overrideExpression(Expression expression, Type potentialType,
boolean allowPrecisionLoss) {
VariableElement element = getOverridableStaticElement(expression);
if (element != null) {
overrideVariable(element, potentialType, allowPrecisionLoss);
}
element = getOverridablePropagatedElement(expression);
if (element != null) {
overrideVariable(element, potentialType, allowPrecisionLoss);
}
}
/**
* If it is appropriate to do so, override the current type of the given element with the given
* type.
*
* @param element the element whose type might be overridden
* @param potentialType the potential type of the element
* @param allowPrecisionLoss true if {@code potentialType} is allowed to be less precise than the
* current best type
*/
protected void overrideVariable(VariableElement element, Type potentialType,
boolean allowPrecisionLoss) {
if (potentialType == null || potentialType.isBottom()) {
return;
}
Type currentType = getBestType(element);
// If we aren't allowing precision loss then the third and fourth conditions check that we
// aren't losing precision.
//
// Let [C] be the current type and [P] be the potential type. When we aren't allowing
// precision loss -- which is the case for is-checks -- we check that [! (C << P)] or [P << C].
// The second check, that [P << C], is analogous to part of the Dart Language Spec rule
// for type promotion under is-checks (in the analogy [T] is [P] and [S] is [C]):
//
// An is-expression of the form [v is T] shows that [v] has type [T] iff [T] is more
// specific than the type [S] of the expression [v] and both [T != dynamic] and
// [S != dynamic].
//
// It also covers an important case that is not applicable in the spec: for union types, we
// want an is-check to promote from an union type to (a subtype of) any of its members.
//
// The first check, that [! (C << P)], covers the case where [P] and [C] are unrelated types;
// This case is not addressed in the spec for static types.
if (currentType == null || allowPrecisionLoss || !currentType.isMoreSpecificThan(potentialType)
|| potentialType.isMoreSpecificThan(currentType)) {
if (element instanceof PropertyInducingElement) {
PropertyInducingElement variable = (PropertyInducingElement) element;
if (!variable.isConst() && !variable.isFinal()) {
return;
}
((PropertyInducingElementImpl) variable).setPropagatedType(potentialType);
}
overrideManager.setType(element, potentialType);
}
}
@Override
protected void visitForEachStatementInScope(ForEachStatement node) {
//
// We visit the iterator before the loop variable because the loop variable cannot be in scope
// while visiting the iterator.
//
Expression iterator = node.getIterator();
safelyVisit(iterator);
DeclaredIdentifier loopVariable = node.getLoopVariable();
SimpleIdentifier identifier = node.getIdentifier();
safelyVisit(loopVariable);
safelyVisit(identifier);
Statement body = node.getBody();
if (body != null) {
overrideManager.enterScope();
try {
if (loopVariable != null && iterator != null) {
LocalVariableElement loopElement = loopVariable.getElement();
if (loopElement != null) {
Type iteratorElementType = getIteratorElementType(iterator);
overrideVariable(loopElement, iteratorElementType, true);
recordPropagatedType(loopVariable.getIdentifier(), iteratorElementType);
}
} else if (identifier != null && iterator != null) {
Element identifierElement = identifier.getStaticElement();
if (identifierElement instanceof VariableElement) {
Type iteratorElementType = getIteratorElementType(iterator);
overrideVariable((VariableElement) identifierElement, iteratorElementType, true);
recordPropagatedType(identifier, iteratorElementType);
}
}
visitStatementInScope(body);
} finally {
overrideManager.exitScope();
}
}
node.accept(elementResolver);
node.accept(typeAnalyzer);
}
@Override
protected void visitForStatementInScope(ForStatement node) {
safelyVisit(node.getVariables());
safelyVisit(node.getInitialization());
safelyVisit(node.getCondition());
overrideManager.enterScope();
try {
propagateTrueState(node.getCondition());
visitStatementInScope(node.getBody());
node.getUpdaters().accept(this);
} finally {
overrideManager.exitScope();
}
// TODO(brianwilkerson) If the loop can only be exited because the condition is false, then
// propagateFalseState(condition);
}
/**
* Checks each promoted variable in the current scope for compliance with the following
* specification statement:
* <p>
* If the variable <i>v</i> is accessed by a closure in <i>s<sub>1</sub></i> then the variable
* <i>v</i> is not potentially mutated anywhere in the scope of <i>v</i>.
*/
private void clearTypePromotionsIfAccessedInClosureAndProtentiallyMutated(AstNode target) {
for (Element element : promoteManager.getPromotedElements()) {
if (((VariableElementImpl) element).isPotentiallyMutatedInScope()) {
if (isVariableAccessedInClosure(element, target)) {
promoteManager.setType(element, null);
}
}
}
}
/**
* Checks each promoted variable in the current scope for compliance with the following
* specification statement:
* <p>
* <i>v</i> is not potentially mutated in <i>s<sub>1</sub></i> or within a closure.
*/
private void clearTypePromotionsIfPotentiallyMutatedIn(AstNode target) {
for (Element element : promoteManager.getPromotedElements()) {
if (isVariablePotentiallyMutatedIn(element, target)) {
promoteManager.setType(element, null);
}
}
}
/**
* Return the best type information available for the given element. If the type of the element
* has been overridden, then return the overriding type. Otherwise, return the static type.
*
* @param element the element for which type information is to be returned
* @return the best type information available for the given element
*/
private Type getBestType(Element element) {
Type bestType = overrideManager.getType(element);
if (bestType == null) {
if (element instanceof LocalVariableElement) {
bestType = ((LocalVariableElement) element).getType();
} else if (element instanceof ParameterElement) {
bestType = ((ParameterElement) element).getType();
}
}
return bestType;
}
/**
* The given expression is the expression used to compute the iterator for a for-each statement.
* Attempt to compute the type of objects that will be assigned to the loop variable and return
* that type. Return {@code null} if the type could not be determined.
*
* @param iterator the iterator for a for-each statement
* @return the type of objects that will be assigned to the loop variable
*/
private Type getIteratorElementType(Expression iteratorExpression) {
Type expressionType = iteratorExpression.getBestType();
if (expressionType instanceof InterfaceType) {
InterfaceType interfaceType = (InterfaceType) expressionType;
FunctionType iteratorFunction = inheritanceManager.lookupMemberType(interfaceType, "iterator");
if (iteratorFunction == null) {
// TODO(brianwilkerson) Should we report this error?
return null;
}
Type iteratorType = iteratorFunction.getReturnType();
if (iteratorType instanceof InterfaceType) {
InterfaceType iteratorInterfaceType = (InterfaceType) iteratorType;
FunctionType currentFunction = inheritanceManager.lookupMemberType(
iteratorInterfaceType,
"current");
if (currentFunction == null) {
// TODO(brianwilkerson) Should we report this error?
return null;
}
return currentFunction.getReturnType();
}
}
return null;
}
/**
* If given "mayBeClosure" is {@link FunctionExpression} without explicit parameters types and its
* required type is {@link FunctionType}, then infer parameters types from {@link FunctionType}.
*/
private void inferFunctionExpressionParametersTypes(Expression mayBeClosure,
Type mayByFunctionType) {
// prepare closure
if (!(mayBeClosure instanceof FunctionExpression)) {
return;
}
FunctionExpression closure = (FunctionExpression) mayBeClosure;
// prepare expected closure type
if (!(mayByFunctionType instanceof FunctionType)) {
return;
}
FunctionType expectedClosureType = (FunctionType) mayByFunctionType;
// If the expectedClosureType is not more specific than the static type, return.
Type staticClosureType = closure.getElement() != null ? closure.getElement().getType() : null;
if (staticClosureType != null && !expectedClosureType.isMoreSpecificThan(staticClosureType)) {
return;
}
// set propagated type for the closure
closure.setPropagatedType(expectedClosureType);
// set inferred types for parameters
NodeList<FormalParameter> parameters = closure.getParameters().getParameters();
ParameterElement[] expectedParameters = expectedClosureType.getParameters();
for (int i = 0; i < parameters.size() && i < expectedParameters.length; i++) {
FormalParameter parameter = parameters.get(i);
ParameterElement element = parameter.getElement();
Type currentType = getBestType(element);
// may be override the type
Type expectedType = expectedParameters[i].getType();
if (currentType == null || expectedType.isMoreSpecificThan(currentType)) {
overrideManager.setType(element, expectedType);
}
}
}
/**
* Try to infer types of parameters of the {@link FunctionExpression} arguments.
*/
private void inferFunctionExpressionsParametersTypes(ArgumentList argumentList) {
for (Expression argument : argumentList.getArguments()) {
ParameterElement parameter = argument.getPropagatedParameterElement();
if (parameter == null) {
parameter = argument.getStaticParameterElement();
}
if (parameter != null) {
inferFunctionExpressionParametersTypes(argument, parameter.getType());
}
}
}
/**
* Return {@code true} if the given expression terminates abruptly (that is, if any expression
* following the given expression will not be reached).
*
* @param expression the expression being tested
* @return {@code true} if the given expression terminates abruptly
*/
private boolean isAbruptTerminationExpression(Expression expression) {
// TODO(brianwilkerson) This needs to be significantly improved. Ideally we would eventually
// turn this into a method on Expression that returns a termination indication (normal, abrupt
// with no exception, abrupt with an exception).
while (expression instanceof ParenthesizedExpression) {
expression = ((ParenthesizedExpression) expression).getExpression();
}
return expression instanceof ThrowExpression || expression instanceof RethrowExpression;
}
/**
* Return {@code true} if the given statement terminates abruptly (that is, if any statement
* following the given statement will not be reached).
*
* @param statement the statement being tested
* @return {@code true} if the given statement terminates abruptly
*/
private boolean isAbruptTerminationStatement(Statement statement) {
// TODO(brianwilkerson) This needs to be significantly improved. Ideally we would eventually
// turn this into a method on Statement that returns a termination indication (normal, abrupt
// with no exception, abrupt with an exception).
//
// collinsn: it is unsound to assume that [break] and [continue] are "abrupt".
// See: https://code.google.com/p/dart/issues/detail?id=19929#c4 (tests are
// included in TypePropagationTest.java).
// In general, the difficulty is loopy control flow.
//
// In the presence of exceptions things become much more complicated, but while
// we only use this to propagate at [if]-statement join points, checking for [return]
// may work well enough in the common case.
if (statement instanceof ReturnStatement) {
return true;
} else if (statement instanceof ExpressionStatement) {
return isAbruptTerminationExpression(((ExpressionStatement) statement).getExpression());
} else if (statement instanceof Block) {
NodeList<Statement> statements = ((Block) statement).getStatements();
int size = statements.size();
if (size == 0) {
return false;
}
// This last-statement-is-return heuristic is unsound for adversarial code,
// but probably works well in the common case:
//
// var x = 123;
// var c = true;
// L: if (c) {
// x = "hello";
// c = false;
// break L;
// return;
// }
// print(x);
//
// Unsound to assume that [x = "hello";] never executed after the if-statement.
// Of course, a dead-code analysis could point out that [return] here is dead.
return isAbruptTerminationStatement(statements.get(size - 1));
}
return false;
}
/**
* Return {@code true} if the given variable is accessed within a closure in the given
* {@link AstNode} and also mutated somewhere in variable scope. This information is only
* available for local variables (including parameters).
*
* @param variable the variable to check
* @param target the {@link AstNode} to check within
* @return {@code true} if this variable is potentially mutated somewhere in the given ASTNode
*/
private boolean isVariableAccessedInClosure(final Element variable, AstNode target) {
final boolean[] result = {false};
target.accept(new RecursiveAstVisitor<Void>() {
private boolean inClosure = false;
@Override
public Void visitFunctionExpression(FunctionExpression node) {
boolean inClosure = this.inClosure;
try {
this.inClosure = true;
return super.visitFunctionExpression(node);
} finally {
this.inClosure = inClosure;
}
}
@Override
public Void visitSimpleIdentifier(SimpleIdentifier node) {
if (result[0]) {
return null;
}
if (inClosure && node.getStaticElement() == variable) {
result[0] |= true;
}
return null;
}
});
return result[0];
}
/**
* Return {@code true} if the given variable is potentially mutated somewhere in the given
* {@link AstNode}. This information is only available for local variables (including parameters).
*
* @param variable the variable to check
* @param target the {@link AstNode} to check within
* @return {@code true} if this variable is potentially mutated somewhere in the given ASTNode
*/
private boolean isVariablePotentiallyMutatedIn(final Element variable, AstNode target) {
final boolean[] result = {false};
target.accept(new RecursiveAstVisitor<Void>() {
@Override
public Void visitSimpleIdentifier(SimpleIdentifier node) {
if (result[0]) {
return null;
}
if (node.getStaticElement() == variable) {
if (node.inSetterContext()) {
result[0] |= true;
}
}
return null;
}
});
return result[0];
}
/**
* If it is appropriate to do so, promotes the current type of the static element associated with
* the given expression with the given type. Generally speaking, it is appropriate if the given
* type is more specific than the current type.
*
* @param expression the expression used to access the static element whose types might be
* promoted
* @param potentialType the potential type of the elements
*/
private void promote(Expression expression, Type potentialType) {
VariableElement element = getPromotionStaticElement(expression);
if (element != null) {
// may be mutated somewhere in closure
if (((VariableElementImpl) element).isPotentiallyMutatedInClosure()) {
return;
}
// prepare current variable type
Type type = promoteManager.getType(element);
if (type == null) {
type = expression.getStaticType();
}
// Declared type should not be "dynamic".
if (type == null || type.isDynamic()) {
return;
}
// Promoted type should not be "dynamic".
if (potentialType == null || potentialType.isDynamic()) {
return;
}
// Promoted type should be more specific than declared.
if (!potentialType.isMoreSpecificThan(type)) {
return;
}
// Do promote type of variable.
promoteManager.setType(element, potentialType);
}
}
/**
* Promotes type information using given condition.
*/
private void promoteTypes(Expression condition) {
if (condition instanceof BinaryExpression) {
BinaryExpression binary = (BinaryExpression) condition;
if (binary.getOperator().getType() == TokenType.AMPERSAND_AMPERSAND) {
Expression left = binary.getLeftOperand();
Expression right = binary.getRightOperand();
promoteTypes(left);
promoteTypes(right);
clearTypePromotionsIfPotentiallyMutatedIn(right);
}
} else if (condition instanceof IsExpression) {
IsExpression is = (IsExpression) condition;
if (is.getNotOperator() == null) {
promote(is.getExpression(), is.getType().getType());
}
} else if (condition instanceof ParenthesizedExpression) {
promoteTypes(((ParenthesizedExpression) condition).getExpression());
}
}
/**
* Propagate any type information that results from knowing that the given condition will have
* been evaluated to 'false'.
*
* @param condition the condition that will have evaluated to 'false'
*/
private void propagateFalseState(Expression condition) {
if (condition instanceof BinaryExpression) {
BinaryExpression binary = (BinaryExpression) condition;
if (binary.getOperator().getType() == TokenType.BAR_BAR) {
propagateFalseState(binary.getLeftOperand());
propagateFalseState(binary.getRightOperand());
}
} else if (condition instanceof IsExpression) {
IsExpression is = (IsExpression) condition;
if (is.getNotOperator() != null) {
// Since an is-statement doesn't actually change the type, we don't
// let it affect the propagated type when it would result in a loss
// of precision.
overrideExpression(is.getExpression(), is.getType().getType(), false);
}
} else if (condition instanceof PrefixExpression) {
PrefixExpression prefix = (PrefixExpression) condition;
if (prefix.getOperator().getType() == TokenType.BANG) {
propagateTrueState(prefix.getOperand());
}
} else if (condition instanceof ParenthesizedExpression) {
propagateFalseState(((ParenthesizedExpression) condition).getExpression());
}
}
/**
* Propagate any type information that results from knowing that the given expression will have
* been evaluated without altering the flow of execution.
*
* @param expression the expression that will have been evaluated
*/
private void propagateState(Expression expression) {
// TODO(brianwilkerson) Implement this.
}
/**
* Propagate any type information that results from knowing that the given condition will have
* been evaluated to 'true'.
*
* @param condition the condition that will have evaluated to 'true'
*/
private void propagateTrueState(Expression condition) {
if (condition instanceof BinaryExpression) {
BinaryExpression binary = (BinaryExpression) condition;
if (binary.getOperator().getType() == TokenType.AMPERSAND_AMPERSAND) {
propagateTrueState(binary.getLeftOperand());
propagateTrueState(binary.getRightOperand());
}
} else if (condition instanceof IsExpression) {
IsExpression is = (IsExpression) condition;
if (is.getNotOperator() == null) {
// Since an is-statement doesn't actually change the type, we don't
// let it affect the propagated type when it would result in a loss
// of precision.
overrideExpression(is.getExpression(), is.getType().getType(), false);
}
} else if (condition instanceof PrefixExpression) {
PrefixExpression prefix = (PrefixExpression) condition;
if (prefix.getOperator().getType() == TokenType.BANG) {
propagateFalseState(prefix.getOperand());
}
} else if (condition instanceof ParenthesizedExpression) {
propagateTrueState(((ParenthesizedExpression) condition).getExpression());
}
}
/**
* Record that the propagated type of the given node is the given type.
*
* @param expression the node whose type is to be recorded
* @param type the propagated type of the node
*/
private void recordPropagatedType(Expression expression, Type type) {
if (type != null && !type.isDynamic()) {
expression.setPropagatedType(type);
}
}
}