/*
* 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.AstNode;
import com.google.dart.engine.ast.Block;
import com.google.dart.engine.ast.CatchClause;
import com.google.dart.engine.ast.ClassDeclaration;
import com.google.dart.engine.ast.ClassTypeAlias;
import com.google.dart.engine.ast.ConstructorDeclaration;
import com.google.dart.engine.ast.Declaration;
import com.google.dart.engine.ast.DeclaredIdentifier;
import com.google.dart.engine.ast.DoStatement;
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.FormalParameterList;
import com.google.dart.engine.ast.FunctionDeclaration;
import com.google.dart.engine.ast.FunctionDeclarationStatement;
import com.google.dart.engine.ast.FunctionExpression;
import com.google.dart.engine.ast.FunctionTypeAlias;
import com.google.dart.engine.ast.IfStatement;
import com.google.dart.engine.ast.Label;
import com.google.dart.engine.ast.LabeledStatement;
import com.google.dart.engine.ast.MethodDeclaration;
import com.google.dart.engine.ast.NodeList;
import com.google.dart.engine.ast.SimpleIdentifier;
import com.google.dart.engine.ast.Statement;
import com.google.dart.engine.ast.SwitchCase;
import com.google.dart.engine.ast.SwitchDefault;
import com.google.dart.engine.ast.SwitchMember;
import com.google.dart.engine.ast.SwitchStatement;
import com.google.dart.engine.ast.TopLevelVariableDeclaration;
import com.google.dart.engine.ast.VariableDeclaration;
import com.google.dart.engine.ast.VariableDeclarationStatement;
import com.google.dart.engine.ast.WhileStatement;
import com.google.dart.engine.ast.visitor.UnifyingAstVisitor;
import com.google.dart.engine.element.ClassElement;
import com.google.dart.engine.element.CompilationUnitElement;
import com.google.dart.engine.element.ConstructorElement;
import com.google.dart.engine.element.Element;
import com.google.dart.engine.element.ExecutableElement;
import com.google.dart.engine.element.LabelElement;
import com.google.dart.engine.element.LibraryElement;
import com.google.dart.engine.element.VariableElement;
import com.google.dart.engine.error.AnalysisError;
import com.google.dart.engine.error.AnalysisErrorListener;
import com.google.dart.engine.error.ErrorCode;
import com.google.dart.engine.internal.scope.ClassScope;
import com.google.dart.engine.internal.scope.EnclosedScope;
import com.google.dart.engine.internal.scope.FunctionScope;
import com.google.dart.engine.internal.scope.FunctionTypeScope;
import com.google.dart.engine.internal.scope.LabelScope;
import com.google.dart.engine.internal.scope.LibraryScope;
import com.google.dart.engine.internal.scope.Scope;
import com.google.dart.engine.internal.scope.TypeParameterScope;
import com.google.dart.engine.scanner.Token;
import com.google.dart.engine.source.Source;
/**
* The abstract class {@code ScopedVisitor} maintains name and label scopes as an AST structure is
* being visited.
*
* @coverage dart.engine.resolver
*/
public abstract class ScopedVisitor extends UnifyingAstVisitor<Void> {
/**
* The element for the library containing the compilation unit being visited.
*/
private LibraryElement definingLibrary;
/**
* The source representing the compilation unit being visited.
*/
private Source source;
/**
* The error listener that will be informed of any errors that are found during resolution.
*/
private AnalysisErrorListener errorListener;
/**
* The scope used to resolve identifiers.
*/
private Scope nameScope;
/**
* The object used to access the types from the core library.
*/
private TypeProvider typeProvider;
/**
* The scope used to resolve labels for {@code break} and {@code continue} statements, or
* {@code null} if no labels have been defined in the current context.
*/
private LabelScope labelScope;
/**
* 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 ScopedVisitor(Library library, Source source, TypeProvider typeProvider) {
this.definingLibrary = library.getLibraryElement();
this.source = source;
LibraryScope libraryScope = library.getLibraryScope();
this.errorListener = libraryScope.getErrorListener();
this.nameScope = libraryScope;
this.typeProvider = typeProvider;
}
/**
* 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 ScopedVisitor(LibraryElement definingLibrary, Source source, TypeProvider typeProvider,
AnalysisErrorListener errorListener) {
this.definingLibrary = definingLibrary;
this.source = source;
this.errorListener = errorListener;
this.nameScope = new LibraryScope(definingLibrary, errorListener);
this.typeProvider = typeProvider;
}
/**
* 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 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 ScopedVisitor(LibraryElement definingLibrary, Source source, TypeProvider typeProvider,
Scope nameScope, AnalysisErrorListener errorListener) {
this.definingLibrary = definingLibrary;
this.source = source;
this.errorListener = errorListener;
this.nameScope = nameScope;
this.typeProvider = typeProvider;
}
/**
* 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 ScopedVisitor(ResolvableLibrary library, Source source, TypeProvider typeProvider) {
this.definingLibrary = library.getLibraryElement();
this.source = source;
LibraryScope libraryScope = library.getLibraryScope();
this.errorListener = libraryScope.getErrorListener();
this.nameScope = libraryScope;
this.typeProvider = typeProvider;
}
/**
* Return the library element for the library containing the compilation unit being resolved.
*
* @return the library element for the library containing the compilation unit being resolved
*/
public LibraryElement getDefiningLibrary() {
return definingLibrary;
}
/**
* Return the object used to access the types from the core library.
*
* @return the object used to access the types from the core library
*/
public TypeProvider getTypeProvider() {
return typeProvider;
}
/**
* Replaces the current {@link Scope} with the enclosing {@link Scope}.
*
* @return the enclosing {@link Scope}.
*/
public Scope popNameScope() {
nameScope = nameScope.getEnclosingScope();
return nameScope;
}
/**
* Pushes a new {@link Scope} into the visitor.
*
* @return the new {@link Scope}.
*/
public Scope pushNameScope() {
Scope newScope = new EnclosedScope(nameScope);
nameScope = newScope;
return nameScope;
}
@Override
public Void visitBlock(Block node) {
Scope outerScope = nameScope;
try {
EnclosedScope enclosedScope = new EnclosedScope(nameScope);
hideNamesDefinedInBlock(enclosedScope, node);
nameScope = enclosedScope;
super.visitBlock(node);
} finally {
nameScope = outerScope;
}
return null;
}
@Override
public Void visitCatchClause(CatchClause node) {
SimpleIdentifier exception = node.getExceptionParameter();
if (exception != null) {
Scope outerScope = nameScope;
try {
nameScope = new EnclosedScope(nameScope);
nameScope.define(exception.getStaticElement());
SimpleIdentifier stackTrace = node.getStackTraceParameter();
if (stackTrace != null) {
nameScope.define(stackTrace.getStaticElement());
}
super.visitCatchClause(node);
} finally {
nameScope = outerScope;
}
} else {
super.visitCatchClause(node);
}
return null;
}
@Override
public Void visitClassDeclaration(ClassDeclaration node) {
ClassElement classElement = node.getElement();
Scope outerScope = nameScope;
try {
if (classElement == null) {
AnalysisEngine.getInstance().getLogger().logInformation(
"Missing element for class declaration " + node.getName().getName() + " in "
+ getDefiningLibrary().getSource().getFullName(),
new Exception());
super.visitClassDeclaration(node);
} else {
nameScope = new TypeParameterScope(nameScope, classElement);
visitClassDeclarationInScope(node);
nameScope = new ClassScope(nameScope, classElement);
visitClassMembersInScope(node);
}
} finally {
nameScope = outerScope;
}
return null;
}
@Override
public Void visitClassTypeAlias(ClassTypeAlias node) {
Scope outerScope = nameScope;
try {
ClassElement element = node.getElement();
nameScope = new ClassScope(new TypeParameterScope(nameScope, element), element);
super.visitClassTypeAlias(node);
} finally {
nameScope = outerScope;
}
return null;
}
@Override
public Void visitConstructorDeclaration(ConstructorDeclaration node) {
ConstructorElement constructorElement = node.getElement();
Scope outerScope = nameScope;
try {
if (constructorElement == null) {
StringBuilder builder = new StringBuilder();
builder.append("Missing element for constructor ");
builder.append(node.getReturnType().getName());
if (node.getName() != null) {
builder.append(".");
builder.append(node.getName().getName());
}
builder.append(" in ");
builder.append(getDefiningLibrary().getSource().getFullName());
AnalysisEngine.getInstance().getLogger().logInformation(builder.toString(), new Exception());
} else {
nameScope = new FunctionScope(nameScope, constructorElement);
}
super.visitConstructorDeclaration(node);
} finally {
nameScope = outerScope;
}
return null;
}
@Override
public Void visitDeclaredIdentifier(DeclaredIdentifier node) {
VariableElement element = node.getElement();
if (element != null) {
nameScope.define(element);
}
super.visitDeclaredIdentifier(node);
return null;
}
@Override
public Void visitDoStatement(DoStatement node) {
LabelScope outerLabelScope = labelScope;
try {
labelScope = new LabelScope(labelScope, false, false);
visitStatementInScope(node.getBody());
safelyVisit(node.getCondition());
} finally {
labelScope = outerLabelScope;
}
return null;
}
@Override
public Void visitForEachStatement(ForEachStatement node) {
Scope outerNameScope = nameScope;
LabelScope outerLabelScope = labelScope;
try {
nameScope = new EnclosedScope(nameScope);
labelScope = new LabelScope(outerLabelScope, false, false);
visitForEachStatementInScope(node);
} finally {
labelScope = outerLabelScope;
nameScope = outerNameScope;
}
return null;
}
@Override
public Void visitFormalParameterList(FormalParameterList node) {
super.visitFormalParameterList(node);
// We finished resolving function signature, now include formal parameters scope.
if (nameScope instanceof FunctionScope) {
((FunctionScope) nameScope).defineParameters();
}
if (nameScope instanceof FunctionTypeScope) {
((FunctionTypeScope) nameScope).defineParameters();
}
return null;
}
@Override
public Void visitForStatement(ForStatement node) {
Scope outerNameScope = nameScope;
LabelScope outerLabelScope = labelScope;
try {
nameScope = new EnclosedScope(nameScope);
labelScope = new LabelScope(outerLabelScope, false, false);
visitForStatementInScope(node);
} finally {
labelScope = outerLabelScope;
nameScope = outerNameScope;
}
return null;
}
@Override
public Void visitFunctionDeclaration(FunctionDeclaration node) {
ExecutableElement functionElement = node.getElement();
Scope outerScope = nameScope;
try {
if (functionElement == null) {
AnalysisEngine.getInstance().getLogger().logInformation(
"Missing element for top-level function " + node.getName().getName() + " in "
+ getDefiningLibrary().getSource().getFullName(),
new Exception());
} else {
nameScope = new FunctionScope(nameScope, functionElement);
}
super.visitFunctionDeclaration(node);
} finally {
nameScope = outerScope;
}
if (functionElement != null
&& !(functionElement.getEnclosingElement() instanceof CompilationUnitElement)) {
nameScope.define(functionElement);
}
return null;
}
@Override
public Void visitFunctionExpression(FunctionExpression node) {
if (node.getParent() instanceof FunctionDeclaration) {
// We have already created a function scope and don't need to do so again.
super.visitFunctionExpression(node);
} else {
Scope outerScope = nameScope;
try {
ExecutableElement functionElement = node.getElement();
if (functionElement == null) {
StringBuilder builder = new StringBuilder();
builder.append("Missing element for function ");
AstNode parent = node.getParent();
while (parent != null) {
if (parent instanceof Declaration) {
Element parentElement = ((Declaration) parent).getElement();
builder.append(parentElement == null ? "<unknown> " : (parentElement.getName() + " "));
}
parent = parent.getParent();
}
builder.append("in ");
builder.append(getDefiningLibrary().getSource().getFullName());
AnalysisEngine.getInstance().getLogger().logInformation(
builder.toString(),
new Exception());
} else {
nameScope = new FunctionScope(nameScope, functionElement);
}
super.visitFunctionExpression(node);
} finally {
nameScope = outerScope;
}
}
return null;
}
@Override
public Void visitFunctionTypeAlias(FunctionTypeAlias node) {
Scope outerScope = nameScope;
try {
nameScope = new FunctionTypeScope(nameScope, node.getElement());
super.visitFunctionTypeAlias(node);
} finally {
nameScope = outerScope;
}
return null;
}
@Override
public Void visitIfStatement(IfStatement node) {
safelyVisit(node.getCondition());
visitStatementInScope(node.getThenStatement());
visitStatementInScope(node.getElseStatement());
return null;
}
@Override
public Void visitLabeledStatement(LabeledStatement node) {
LabelScope outerScope = addScopesFor(node.getLabels());
try {
super.visitLabeledStatement(node);
} finally {
labelScope = outerScope;
}
return null;
}
@Override
public Void visitMethodDeclaration(MethodDeclaration node) {
Scope outerScope = nameScope;
try {
ExecutableElement methodElement = node.getElement();
if (methodElement == null) {
AnalysisEngine.getInstance().getLogger().logInformation(
"Missing element for method " + node.getName().getName() + " in "
+ getDefiningLibrary().getSource().getFullName(),
new Exception());
} else {
nameScope = new FunctionScope(nameScope, methodElement);
}
super.visitMethodDeclaration(node);
} finally {
nameScope = outerScope;
}
return null;
}
@Override
public Void visitSwitchCase(SwitchCase node) {
node.getExpression().accept(this);
Scope outerNameScope = nameScope;
try {
nameScope = new EnclosedScope(nameScope);
node.getStatements().accept(this);
} finally {
nameScope = outerNameScope;
}
return null;
}
@Override
public Void visitSwitchDefault(SwitchDefault node) {
Scope outerNameScope = nameScope;
try {
nameScope = new EnclosedScope(nameScope);
node.getStatements().accept(this);
} finally {
nameScope = outerNameScope;
}
return null;
}
@Override
public Void visitSwitchStatement(SwitchStatement node) {
LabelScope outerScope = labelScope;
try {
labelScope = new LabelScope(outerScope, true, false);
for (SwitchMember member : node.getMembers()) {
for (Label label : member.getLabels()) {
SimpleIdentifier labelName = label.getLabel();
LabelElement labelElement = (LabelElement) labelName.getStaticElement();
labelScope = new LabelScope(labelScope, labelName.getName(), labelElement);
}
}
super.visitSwitchStatement(node);
} finally {
labelScope = outerScope;
}
return null;
}
@Override
public Void visitVariableDeclaration(VariableDeclaration node) {
super.visitVariableDeclaration(node);
if (!(node.getParent().getParent() instanceof TopLevelVariableDeclaration)
&& !(node.getParent().getParent() instanceof FieldDeclaration)) {
VariableElement element = node.getElement();
if (element != null) {
nameScope.define(element);
}
}
return null;
}
@Override
public Void visitWhileStatement(WhileStatement node) {
LabelScope outerScope = labelScope;
try {
labelScope = new LabelScope(outerScope, false, false);
safelyVisit(node.getCondition());
visitStatementInScope(node.getBody());
} finally {
labelScope = outerScope;
}
return null;
}
/**
* Return the label scope in which the current node is being resolved.
*
* @return the label scope in which the current node is being resolved
*/
protected LabelScope getLabelScope() {
return labelScope;
}
/**
* Return the name scope in which the current node is being resolved.
*
* @return the name scope in which the current node is being resolved
*/
protected Scope getNameScope() {
return nameScope;
}
/**
* Return the source.
*
* @return the source
*/
protected Source getSource() {
return source;
}
/**
* Report an error with the given error code and arguments.
*
* @param errorCode the error code of the error to be reported
* @param node the node specifying the location of the error
* @param arguments the arguments to the error, used to compose the error message
*/
protected void reportErrorForNode(ErrorCode errorCode, AstNode node, Object... arguments) {
errorListener.onError(new AnalysisError(
source,
node.getOffset(),
node.getLength(),
errorCode,
arguments));
}
/**
* Report an error with the given error code and arguments.
*
* @param errorCode the error code of the error to be reported
* @param offset the offset of the location of the error
* @param length the length of the location of the error
* @param arguments the arguments to the error, used to compose the error message
*/
protected void reportErrorForOffset(ErrorCode errorCode, int offset, int length,
Object... arguments) {
errorListener.onError(new AnalysisError(source, offset, length, errorCode, arguments));
}
/**
* Report an error with the given error code and arguments.
*
* @param errorCode the error code of the error to be reported
* @param token the token specifying the location of the error
* @param arguments the arguments to the error, used to compose the error message
*/
protected void reportErrorForToken(ErrorCode errorCode, Token token, Object... arguments) {
errorListener.onError(new AnalysisError(
source,
token.getOffset(),
token.getLength(),
errorCode,
arguments));
}
/**
* Visit the given AST node if it is not null.
*
* @param node the node to be visited
*/
protected void safelyVisit(AstNode node) {
if (node != null) {
node.accept(this);
}
}
protected void visitClassDeclarationInScope(ClassDeclaration node) {
safelyVisit(node.getName());
safelyVisit(node.getTypeParameters());
safelyVisit(node.getExtendsClause());
safelyVisit(node.getWithClause());
safelyVisit(node.getImplementsClause());
safelyVisit(node.getNativeClause());
}
protected void visitClassMembersInScope(ClassDeclaration node) {
safelyVisit(node.getDocumentationComment());
node.getMetadata().accept(this);
node.getMembers().accept(this);
}
/**
* Visit the given statement after it's scope has been created. This replaces the normal call to
* the inherited visit method so that ResolverVisitor can intervene when type propagation is
* enabled.
*
* @param node the statement to be visited
*/
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.
//
safelyVisit(node.getIdentifier());
safelyVisit(node.getIterator());
safelyVisit(node.getLoopVariable());
visitStatementInScope(node.getBody());
}
/**
* Visit the given statement after it's scope has been created. This replaces the normal call to
* the inherited visit method so that ResolverVisitor can intervene when type propagation is
* enabled.
*
* @param node the statement to be visited
*/
protected void visitForStatementInScope(ForStatement node) {
safelyVisit(node.getVariables());
safelyVisit(node.getInitialization());
safelyVisit(node.getCondition());
node.getUpdaters().accept(this);
visitStatementInScope(node.getBody());
}
/**
* Visit the given statement after it's scope has been created. This is used by ResolverVisitor to
* correctly visit the 'then' and 'else' statements of an 'if' statement.
*
* @param node the statement to be visited
*/
protected void visitStatementInScope(Statement node) {
if (node instanceof Block) {
// Don't create a scope around a block because the block will create it's own scope.
visitBlock((Block) node);
} else if (node != null) {
Scope outerNameScope = nameScope;
try {
nameScope = new EnclosedScope(nameScope);
node.accept(this);
} finally {
nameScope = outerNameScope;
}
}
}
/**
* Add scopes for each of the given labels.
*
* @param labels the labels for which new scopes are to be added
* @return the scope that was in effect before the new scopes were added
*/
private LabelScope addScopesFor(NodeList<Label> labels) {
LabelScope outerScope = labelScope;
for (Label label : labels) {
SimpleIdentifier labelNameNode = label.getLabel();
String labelName = labelNameNode.getName();
LabelElement labelElement = (LabelElement) labelNameNode.getStaticElement();
labelScope = new LabelScope(labelScope, labelName, labelElement);
}
return outerScope;
}
/**
* Marks the local declarations of the given {@link Block} hidden in the enclosing scope.
* According to the scoping rules name is hidden if block defines it, but name is defined after
* its declaration statement.
*/
private void hideNamesDefinedInBlock(EnclosedScope scope, Block block) {
NodeList<Statement> statements = block.getStatements();
int statementCount = statements.size();
for (int i = 0; i < statementCount; i++) {
Statement statement = statements.get(i);
if (statement instanceof VariableDeclarationStatement) {
VariableDeclarationStatement vds = (VariableDeclarationStatement) statement;
NodeList<VariableDeclaration> variables = vds.getVariables().getVariables();
int variableCount = variables.size();
for (int j = 0; j < variableCount; j++) {
scope.hide(variables.get(j).getElement());
}
} else if (statement instanceof FunctionDeclarationStatement) {
FunctionDeclarationStatement fds = (FunctionDeclarationStatement) statement;
scope.hide(fds.getFunctionDeclaration().getElement());
}
}
}
}