/**
* Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the Eclipse Public License (EPL).
* Please see the license.txt included with this distribution for details.
* Any modifications to this file must keep this entire header intact.
*/
/*
* Created on 27/07/2005
*/
package com.python.pydev.analysis.visitors;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.python.pydev.core.ICompletionCache;
import org.python.pydev.core.IPythonNature;
import org.python.pydev.core.IToken;
import org.python.pydev.core.structure.FastStack;
import org.python.pydev.editor.codecompletion.revisited.modules.SourceToken;
import org.python.pydev.parser.jython.ast.TryExcept;
import com.aptana.shared_core.string.FastStringBuffer;
import com.aptana.shared_core.structure.Tuple;
import com.python.pydev.analysis.scopeanalysis.AbstractScopeAnalyzerVisitor;
import com.python.pydev.analysis.visitors.ImportChecker.ImportInfo;
/**
* Class used to handle scopes while we're walking through the AST.
*
* @author Fabio
*/
public final class Scope implements Iterable<ScopeItems> {
/**
* the scope type is a method
*/
public static final int SCOPE_TYPE_GLOBAL = 1;
/**
* the scope type is a method
*/
public static final int SCOPE_TYPE_METHOD = 2;
/**
* the scope type is a class
*/
public static final int SCOPE_TYPE_CLASS = 4;
/**
* the scope type is a list comprehension
*/
public static final int SCOPE_TYPE_LIST_COMP = 8;
/**
* the scope type is a lambda
*/
public static final int SCOPE_TYPE_LAMBDA = 16;
/**
* when we are at method definition, not always is as expected...
*/
public boolean isInMethodDefinition = false;
/**
* Constant defining the scopes that should be considered when we're in a method
*/
public static final int ACCEPTED_METHOD_SCOPES = SCOPE_TYPE_GLOBAL | SCOPE_TYPE_METHOD | SCOPE_TYPE_LAMBDA
| SCOPE_TYPE_LIST_COMP;
/**
* Constant defining all the available scopes
*/
public static final int ACCEPTED_ALL_SCOPES = SCOPE_TYPE_GLOBAL | SCOPE_TYPE_METHOD | SCOPE_TYPE_LAMBDA
| SCOPE_TYPE_CLASS | SCOPE_TYPE_LIST_COMP;
/**
* Constant defining that method and lambda are accepted.
*/
public static final int ACCEPTED_METHOD_AND_LAMBDA = SCOPE_TYPE_METHOD | SCOPE_TYPE_LAMBDA;
/**
* used to check for invalid imports
*/
public ImportChecker importChecker;
/**
* @param scopeType
* @return a string representing the scope type
*/
public static String getScopeTypeStr(int scopeType) {
switch (scopeType) {
case Scope.SCOPE_TYPE_GLOBAL:
return "Global Scope";
case Scope.SCOPE_TYPE_CLASS:
return "Class Scope";
case Scope.SCOPE_TYPE_METHOD:
return "Method Scope";
case Scope.SCOPE_TYPE_LAMBDA:
return "Lambda Scope";
case Scope.SCOPE_TYPE_LIST_COMP:
return "List Comp Scope";
}
return null;
}
/**
* this stack is used to hold the scope. when we enter a scope, an item is added, and when we
* exit, it is removed (and the analysis of unused tokens should happen at this time).
*/
private FastStack<ScopeItems> scope = new FastStack<ScopeItems>(10);
private FastStack<Integer> scopeId = new FastStack<Integer>(10);
private int scopeUnique = 0;
private AbstractScopeAnalyzerVisitor visitor;
private int getNewId() {
scopeUnique++;
return scopeUnique;
}
public Scope(AbstractScopeAnalyzerVisitor visitor, IPythonNature nature, String moduleName) {
this.visitor = visitor;
this.importChecker = new ImportChecker(visitor, nature, moduleName);
}
/**
* Adds many tokens at once. (created by the same token)
* Adding more than one ONLY happens for:
* - wild imports (kind of obvious)
* - imports such as import os.path (one token is created for os and one for os.path)
*/
public void addImportTokens(List<IToken> list, IToken generator, ICompletionCache completionCache) {
ScopeItems.TryExceptInfo withinExceptNode = scope.peek().getTryExceptImportError();
//only report undefined imports if we're not inside a try..except ImportError.
boolean reportUndefinedImports = withinExceptNode == null;
boolean requireTokensToBeImports = false;
ImportInfo importInfo = null;
if (generator != null) {
//it will only enter here if it is a wild import (for other imports, the generator is equal to the
//import)
if (!generator.isImport()) {
throw new RuntimeException(
"Only imports should generate multiple tokens "
+ "(it may be null for imports in the form import foo.bar, but then all its tokens must be imports).");
}
importInfo = importChecker.visitImportToken(generator, reportUndefinedImports, completionCache);
} else {
requireTokensToBeImports = true;
}
ScopeItems m = scope.peek();
for (Iterator<IToken> iter = list.iterator(); iter.hasNext();) {
IToken o = iter.next();
//System.out.println("adding: "+o.getRepresentation());
Found found = addToken(generator, m, o, o.getRepresentation());
if (withinExceptNode != null) {
withinExceptNode.addFoundImportToTryExcept(found); //may mark previous as used...
}
//the token that we find here is either an import (in the case of some from xxx import yyy or import aa.bb)
//or a Name, ClassDef, MethodDef, etc. (in the case of wild imports)
if (requireTokensToBeImports) {
if (!o.isImport()) {
throw new RuntimeException("Expecting import token");
}
importInfo = importChecker.visitImportToken(o, reportUndefinedImports, completionCache);
}
//can be either the one resolved in the wild import or in this token (if it is not a wild import)
found.importInfo = importInfo;
visitor.onImportInfoSetOnFound(found);
}
}
public Found addToken(IToken generator, IToken o) {
return addToken(generator, o, o.getRepresentation());
}
public Found addToken(IToken generator, IToken o, String rep) {
ScopeItems m = scope.peek();
return addToken(generator, m, o, rep);
}
/**
* Adds a token to the global scope
*/
public Found addTokenToGlobalScope(IToken generator) {
ScopeItems globalScope = getGlobalScope();
return addToken(generator, globalScope, generator, generator.getRepresentation());
}
/**
* when adding a token, we also have to check if there is not a token with the same representation
* added, because if there is, the previous token might not be used at all...
*
* @param generator that's the token that generated this representation
* @param m the current scope items
* @param o the generator token
* @param rep the representation of the token (o)
* @return
*/
public Found addToken(IToken generator, ScopeItems m, IToken o, String rep) {
if (generator == null) {
generator = o;
}
Found found = findFirst(rep, false);
boolean isReimport = false;
if (!isInMethodDefinition && found != null) { //it will be removed from the scope
if (found.isImport() && generator.isImport()) {
isReimport = true;
//keep on going, as it still might be used or unused
} else {
if (!found.isUsed() && !m.getIsInSubSubScope()) { // it was not used, and we're not in an if scope...
//this kind of unused message should only happen if we are at the same scope...
if (found.getSingle().scopeFound.getScopeId() == getCurrScopeId()) {
//we don't get unused at the global scope or class definition scope unless it's an import
if ((found.getSingle().scopeFound.getScopeType() & Scope.ACCEPTED_METHOD_AND_LAMBDA) != 0
|| found.isImport()) {
visitor.onAddUnusedMessage(null, found);
}
}
} else if (!((m.getScopeType() & Scope.ACCEPTED_METHOD_AND_LAMBDA) != 0 && found.getSingle().scopeFound
.getScopeType() == Scope.SCOPE_TYPE_CLASS)) {
//if it was found but in a class scope (and we're now in a method scope), we will have to create a new Found.
//found... may have been or not used, (if we're in an if scope, that does not matter, because
//we have to group things together for generating messages for all the occurrences in the if)
found.addGeneratorToFound(generator, o, getCurrScopeId(), getCurrScopeItems());
//ok, it was added, so, let's call this over because we've appended it to another found,
//no reason to re-add it again.
return found;
}
}
}
Found newFound = new Found(o, (SourceToken) generator, m.getScopeId(), m);
if (isReimport) {
if (m.getTryExceptImportError() == null) {
//we don't want to add reimport messages if we're within a try..except
visitor.onAddReimportMessage(newFound);
}
}
m.put(rep, newFound);
return newFound;
}
public ScopeItems getCurrScopeItems() {
return scope.peek();
}
/**
* initializes a new scope
*/
public void startScope(int scopeType) {
int newId = getNewId();
scope.push(new ScopeItems(newId, scopeType));
scopeId.push(newId);
}
public int getCurrScopeId() {
return scopeId.peek();
}
public ScopeItems endScope() {
scopeId.pop();
return scope.pop();
}
public int size() {
return scope.size();
}
/**
*
* @param name the name to search for
* @param setUsed indicates if the found tokens should be marked used
* @return true if a given name was found in any of the scopes we have so far
*/
public boolean find(String name, boolean setUsed) {
return findInScopes(name, setUsed).size() > 0;
}
public List<Found> findInScopes(String name, boolean setUsed) {
List<Found> ret = new ArrayList<Found>();
for (ScopeItems m : scope) {
Found f = m.getLastAppearance(name);
if (f != null) {
if (setUsed) {
f.setUsed(true);
}
ret.add(f);
}
}
return ret;
}
public Found findFirst(String name, boolean setUsed) {
return findFirst(name, setUsed, ACCEPTED_ALL_SCOPES);
}
public Found findFirst(String name, boolean setUsed, int acceptedScopes) {
Iterator<ScopeItems> topDown = scope.topDownIterator();
while (topDown.hasNext()) {
ScopeItems m = topDown.next();
if ((m.getScopeType() & acceptedScopes) != 0) {
Found f = m.getLastAppearance(name);
if (f != null) {
if (setUsed) {
f.setUsed(true);
}
return f;
}
}
}
return null;
}
public void addIfSubScope() {
scope.peek().addIfSubScope();
}
public boolean getIsInIfSubScope() {
return scope.peek().getIsInIfSubScope();
}
public void removeIfSubScope() {
scope.peek().removeIfSubScope();
}
public void addTryExceptSubScope(TryExcept node) {
scope.peek().addTryExceptSubScope(node);
}
public void removeTryExceptSubScope() {
scope.peek().removeTryExceptSubScope();
}
public ScopeItems currentScope() {
if (scope.size() == 0) {
return null;
}
return scope.peek();
}
@Override
public String toString() {
FastStringBuffer buffer = new FastStringBuffer();
buffer.append("Scope: ");
for (ScopeItems item : scope) {
buffer.append("\n");
buffer.appendObject(item);
}
return buffer.toString();
}
public ScopeItems getGlobalScope() {
return scope.getFirst();
}
public Iterator<ScopeItems> iterator() {
return this.scope.iterator();
}
/**
* find out if an item is in the names to ignore given its full representation
*/
public Tuple<IToken, Found> findInNamesToIgnore(String fullRep, Map<String, Tuple<IToken, Found>> lastInStack) {
int i = fullRep.indexOf('.', 0);
while (i >= 0) {
String sub = fullRep.substring(0, i);
i = fullRep.indexOf('.', i + 1);
if (lastInStack.containsKey(sub)) {
return lastInStack.get(sub);
}
}
return lastInStack.get(fullRep);
}
/**
* checks if there is some token in the names that are defined (but should be ignored)
*/
public Tuple<IToken, Found> findInNamesToIgnore(String rep) {
int currScopeType = getCurrScopeItems().getScopeType();
for (ScopeItems s : this.scope) {
//ok, if we are in a scope method, we may not get things that were defined in a class scope.
if ((currScopeType & ACCEPTED_METHOD_AND_LAMBDA) != 0 && s.getScopeType() == SCOPE_TYPE_CLASS) {
continue;
}
Map<String, Tuple<IToken, Found>> m = s.namesToIgnore;
Tuple<IToken, Found> found = findInNamesToIgnore(rep, m);
if (found != null) {
return found;
}
}
return null;
}
public ScopeItems getPrevScopeItems() {
if (scope.size() <= 1) {
return null;
}
return scope.get(scope.size() - 2);
}
}