/**
* Copyright (c) 2005-2013 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 Jan 20, 2005
*
* @author Fabio Zadrozny
*/
package org.python.pydev.editor.codecompletion.revisited.visitors;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.python.pydev.core.FullRepIterable;
import org.python.pydev.core.ILocalScope;
import org.python.pydev.core.IPythonNature;
import org.python.pydev.core.IToken;
import org.python.pydev.core.ITypeInfo;
import org.python.pydev.core.log.Log;
import org.python.pydev.editor.codecompletion.revisited.modules.SourceToken;
import org.python.pydev.parser.jython.SimpleNode;
import org.python.pydev.parser.jython.ast.Assert;
import org.python.pydev.parser.jython.ast.Attribute;
import org.python.pydev.parser.jython.ast.Call;
import org.python.pydev.parser.jython.ast.ClassDef;
import org.python.pydev.parser.jython.ast.Expr;
import org.python.pydev.parser.jython.ast.FunctionDef;
import org.python.pydev.parser.jython.ast.Name;
import org.python.pydev.parser.jython.ast.Str;
import org.python.pydev.parser.jython.ast.Tuple;
import org.python.pydev.parser.jython.ast.argumentsType;
import org.python.pydev.parser.jython.ast.commentType;
import org.python.pydev.parser.jython.ast.exprType;
import org.python.pydev.parser.jython.ast.stmtType;
import org.python.pydev.parser.visitors.NodeUtils;
import org.python.pydev.parser.visitors.TypeInfo;
import org.python.pydev.parser.visitors.scope.ASTEntry;
import org.python.pydev.parser.visitors.scope.SequencialASTIteratorVisitor;
import org.python.pydev.shared_core.model.ISimpleNode;
import org.python.pydev.shared_core.structure.FastStack;
/**
* @author Fabio Zadrozny
*/
public class LocalScope implements ILocalScope {
//the first node from the stack is always the module itself (if it's not there, it means it is a compiled module scope)
public FastStack<SimpleNode> scope = new FastStack<SimpleNode>(20);
public int scopeEndLine = -1;
public int ifMainLine = -1;
public SimpleNode foundAtASTNode;
private final IPythonNature nature;
@Override
public void setFoundAtASTNode(ISimpleNode node) {
this.foundAtASTNode = (SimpleNode) node;
}
@Override
public SimpleNode getFoundAtASTNode() {
return foundAtASTNode;
}
/**
* Used to create without an initial scope. It may be changed later by using the getScopeStack() and
* adding tokens.
*/
public LocalScope(IPythonNature nature) {
this.nature = nature;
}
public LocalScope(IPythonNature nature, FastStack<SimpleNode> scope) {
this.nature = nature;
this.scope.addAll(scope);
}
@Override
public FastStack<SimpleNode> getScopeStack() {
return scope;
}
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (!(obj instanceof LocalScope)) {
return false;
}
LocalScope s = (LocalScope) obj;
if (this.scope.size() != s.scope.size()) {
return false;
}
return checkIfScopesMatch(s);
}
@Override
public int hashCode() {
assert false : "hashCode not designed";
return 42; // any arbitrary constant will do
}
/**
* @see org.python.pydev.core.ILocalScope#isOuterOrSameScope(org.python.pydev.editor.codecompletion.revisited.visitors.LocalScope)
*/
@Override
public boolean isOuterOrSameScope(ILocalScope s) {
if (this.scope.size() > s.getScopeStack().size()) {
return false;
}
return checkIfScopesMatch(s);
}
/**
* @param s the scope we're checking for
* @return if the scope passed as a parameter starts with the same scope we have here. It should not be
* called if the size of the scope we're checking is bigger than the size of 'this' scope.
*/
@SuppressWarnings("unchecked")
private boolean checkIfScopesMatch(ILocalScope s) {
Iterator<SimpleNode> otIt = s.getScopeStack().iterator();
for (Iterator<SimpleNode> iter = this.scope.iterator(); iter.hasNext();) {
SimpleNode element = iter.next();
SimpleNode otElement = otIt.next();
if (element.beginColumn != otElement.beginColumn) {
return false;
}
if (element.beginLine != otElement.beginLine) {
return false;
}
if (!element.getClass().equals(otElement.getClass())) {
return false;
}
String rep1 = NodeUtils.getFullRepresentationString(element);
String rep2 = NodeUtils.getFullRepresentationString(otElement);
if (rep1 == null || rep2 == null) {
if (rep1 != rep2) {
return false;
}
} else if (!rep1.equals(rep2)) {
return false;
}
}
return true;
}
/**
* @see org.python.pydev.core.ILocalScope#getAllLocalTokens()
*/
@Override
public IToken[] getAllLocalTokens() {
return getLocalTokens(Integer.MAX_VALUE, Integer.MAX_VALUE, false);
}
/**
* @see org.python.pydev.core.ILocalScope#getLocalTokens(int, int, boolean)
*/
@Override
public IToken[] getLocalTokens(int endLine, int col, boolean onlyArgs) {
Set<SourceToken> comps = new HashSet<SourceToken>();
for (Iterator<SimpleNode> iter = this.scope.iterator(); iter.hasNext();) {
SimpleNode element = iter.next();
stmtType[] body = null;
if (element instanceof FunctionDef) {
FunctionDef f = (FunctionDef) element;
final argumentsType args = f.args;
for (int i = 0; i < args.args.length; i++) {
String s = NodeUtils.getRepresentationString(args.args[i]);
comps.add(new SourceToken(args.args[i], s, "", "", "", IToken.TYPE_PARAM, nature));
}
if (args.vararg != null) {
String s = NodeUtils.getRepresentationString(args.vararg);
comps.add(new SourceToken(args.vararg, s, "", "", "", IToken.TYPE_PARAM, nature));
}
if (args.kwarg != null) {
String s = NodeUtils.getRepresentationString(args.kwarg);
comps.add(new SourceToken(args.kwarg, s, "", "", "", IToken.TYPE_PARAM, nature));
}
if (args.kwonlyargs != null) {
for (int i = 0; i < args.kwonlyargs.length; i++) {
String s = NodeUtils.getRepresentationString(args.kwonlyargs[i]);
comps.add(new SourceToken(args.kwonlyargs[i], s, "", "", "", IToken.TYPE_PARAM, nature));
}
}
if (onlyArgs) {
continue;
}
body = f.body;
}
else if (element instanceof ClassDef && !iter.hasNext()) {
ClassDef classDef = (ClassDef) element;
body = classDef.body;
}
if (body != null) {
try {
for (int i = 0; i < body.length; i++) {
GlobalModelVisitor visitor = new GlobalModelVisitor(GlobalModelVisitor.GLOBAL_TOKENS, "",
false, true, this.nature);
stmtType stmt = body[i];
if (stmt == null) {
continue;
}
stmt.accept(visitor);
List<IToken> t = visitor.tokens;
for (Iterator<IToken> iterator = t.iterator(); iterator.hasNext();) {
SourceToken tok = (SourceToken) iterator.next();
//if it is found here, it is a local type
tok.type = IToken.TYPE_LOCAL;
if (tok.getAst().beginLine <= endLine) {
comps.add(tok);
}
}
}
} catch (Exception e) {
Log.log(e);
}
}
}
return comps.toArray(new SourceToken[0]);
}
/**
*
* @param argName this is the argument (cannot have dots)
* @param activationToken this is the actual activation token we're looking for
* (may have dots).
*
* Note that argName == activationToken first part before the dot (they may be equal)
* @return a list of tokens for the local
*/
@Override
public Collection<IToken> getInterfaceForLocal(String activationToken) {
return getInterfaceForLocal(activationToken, true, true);
}
public Collection<IToken> getInterfaceForLocal(String activationToken, boolean addAttributeAccess,
boolean addLocalsFromHasAttr) {
Set<SourceToken> comps = new HashSet<SourceToken>();
Iterator<SimpleNode> it = this.scope.topDownIterator();
if (!it.hasNext()) {
return new ArrayList<IToken>();
}
SimpleNode element = it.next();
String dottedActTok = activationToken + '.';
//ok, that's the scope we have to analyze
SequencialASTIteratorVisitor visitor = SequencialASTIteratorVisitor.create(element);
ArrayList<Class> classes = new ArrayList<Class>(2);
if (addAttributeAccess) {
classes.add(Attribute.class);
}
if (addLocalsFromHasAttr) {
classes.add(Call.class);
}
Iterator<ASTEntry> iterator = visitor.getIterator(classes.toArray(new Class[classes.size()]));
while (iterator.hasNext()) {
ASTEntry entry = iterator.next();
if (entry.node instanceof Attribute) {
String rep = NodeUtils.getFullRepresentationString(entry.node);
if (rep.startsWith(dottedActTok)) {
rep = rep.substring(dottedActTok.length());
if (NodeUtils.isValidNameRepresentation(rep)) { //that'd be something that can happen when trying to recreate the parsing
comps.add(new SourceToken(entry.node, FullRepIterable.getFirstPart(rep), "", "", "",
IToken.TYPE_OBJECT_FOUND_INTERFACE, this.nature));
}
}
} else if (entry.node instanceof Call) {
Call call = (Call) entry.node;
if ("hasattr".equals(NodeUtils.getFullRepresentationString(call.func)) && call.args != null
&& call.args.length == 2) {
String rep = NodeUtils.getFullRepresentationString(call.args[0]);
if (rep.equals(activationToken)) {
exprType node = call.args[1];
if (node instanceof Str) {
Str str = (Str) node;
String attrName = str.s;
if (NodeUtils.isValidNameRepresentation(attrName)) {
comps.add(new SourceToken(node, attrName, "", "", "",
IToken.TYPE_OBJECT_FOUND_INTERFACE, this.nature));
}
}
}
}
}
}
return new ArrayList<IToken>(comps);
}
/**
* @see org.python.pydev.core.ILocalScope#getLocalImportedModules(int, int, java.lang.String)
*/
@Override
public List<IToken> getLocalImportedModules(int line, int col, String moduleName) {
ArrayList<IToken> importedModules = new ArrayList<IToken>();
for (Iterator<SimpleNode> iter = this.scope.iterator(); iter.hasNext();) {
SimpleNode element = iter.next();
if (element instanceof FunctionDef) {
FunctionDef f = (FunctionDef) element;
for (int i = 0; i < f.body.length; i++) {
stmtType stmt = f.body[i];
if (stmt != null) {
importedModules.addAll(GlobalModelVisitor.getTokens(stmt, GlobalModelVisitor.ALIAS_MODULES,
moduleName, null, false, this.nature));
}
}
}
}
return importedModules;
}
/**
* @see org.python.pydev.core.ILocalScope#getClassDef()
*/
@Override
public ClassDef getClassDef() {
for (Iterator<SimpleNode> it = this.scope.topDownIterator(); it.hasNext();) {
SimpleNode node = it.next();
if (node instanceof ClassDef) {
return (ClassDef) node;
}
}
return null;
}
/**
* @see org.python.pydev.core.ILocalScope#isLastClassDef()
*/
@Override
public boolean isLastClassDef() {
if (this.scope.size() > 0 && this.scope.peek() instanceof ClassDef) {
return true;
}
return false;
}
@Override
public Iterator iterator() {
return scope.topDownIterator();
}
@Override
public int getIfMainLine() {
return ifMainLine;
}
@Override
public int getScopeEndLine() {
return scopeEndLine;
}
@Override
public void setIfMainLine(int original) {
this.ifMainLine = original;
}
@Override
public void setScopeEndLine(int beginLine) {
this.scopeEndLine = beginLine;
}
/**
* Constant containing the calls that are checked for implementations.
*
* Couldn't find anything similar for pyprotocols.
*
* Zope has a different heuristic which is also checked:
* assert Interface.implementedBy(foo)
*
* maps the method name to check -> index of the class in the call (or negative if class is the caller)
*
* TODO: This should be made public to the user...
*/
public static final Map<String, Integer> ISINSTANCE_POSSIBILITIES = new HashMap<String, Integer>();
static {
ISINSTANCE_POSSIBILITIES.put("isinstance".toLowerCase(), 2);
ISINSTANCE_POSSIBILITIES.put("IsImplementation".toLowerCase(), 2);
ISINSTANCE_POSSIBILITIES.put("IsInterfaceDeclared".toLowerCase(), 2);
ISINSTANCE_POSSIBILITIES.put("implementedBy".toLowerCase(), -1);
}
/**
* @see {@link ILocalScope#getPossibleClassesForActivationToken(String)}
*/
@Override
public List<ITypeInfo> getPossibleClassesForActivationToken(String actTok) {
List<ITypeInfo> ret = new ArrayList<>();
Iterator<SimpleNode> it = this.scope.topDownIterator();
if (!it.hasNext()) {
return ret;
}
SimpleNode element = it.next();
//ok, that's the scope we have to analyze
ITypeInfo typeForParameter = NodeUtils.getTypeForParameterFromAST(actTok, element);
if (typeForParameter != null) {
ret.add(typeForParameter);
}
//Search for assert isinstance().
SequencialASTIteratorVisitor visitor = SequencialASTIteratorVisitor.create(element);
Iterator<ASTEntry> iterator = visitor.getIterator();
ArrayList<Object> lst = new ArrayList<Object>();
Object nameDefinition = null;
while (iterator.hasNext()) {
ASTEntry entry = iterator.next();
if (entry.node.specialsAfter != null) {
lst.addAll(entry.node.specialsAfter);
}
if (entry.node.specialsBefore != null) {
lst.addAll(entry.node.specialsBefore);
}
if (!(entry.node instanceof Assert)) {
if (entry.node instanceof Str) {
lst.add(entry.node);
}
if (entry.node instanceof Name) {
Name name = (Name) entry.node;
if (name.ctx == Name.Load) {
if (actTok.equals(name.id)) {
nameDefinition = name;
}
}
}
if (entry.node instanceof Expr) {
Expr expr = (Expr) entry.node;
if (expr.value instanceof Attribute) {
Attribute attribute = (Attribute) expr.value;
if (actTok.equals(NodeUtils.getFullRepresentationString(attribute))) {
nameDefinition = attribute;
}
}
}
continue;
}
Assert ass = (Assert) entry.node;
if (ass.test instanceof Call) {
Call call = (Call) ass.test;
String rep = NodeUtils.getFullRepresentationString(call.func);
if (rep == null) {
continue;
}
Integer classIndex = ISINSTANCE_POSSIBILITIES.get(FullRepIterable.getLastPart(rep).toLowerCase());
if (classIndex != null) {
if (call.args != null && (call.args.length >= Math.max(classIndex, 1))) {
//in all cases, the instance is the 1st parameter.
String foundActTok = NodeUtils.getFullRepresentationString(call.args[0]);
if (foundActTok != null && foundActTok.equals(actTok)) {
if (classIndex > 0) {
exprType type = call.args[classIndex - 1];
if (type instanceof Tuple) {
//case: isinstance(obj, (Class1,Class2))
Tuple tuple = (Tuple) type;
for (exprType expr : tuple.elts) {
addRepresentationIfPossible(ret, expr);
}
} else {
//case: isinstance(obj, Class)
addRepresentationIfPossible(ret, type);
}
} else {
//zope case Interface.implementedBy(obj) -> Interface added
ret.add(new TypeInfo(FullRepIterable.getWithoutLastPart(rep)));
}
}
}
}
}
}
if (nameDefinition != null) {
int s = lst.size();
for (int i = 0; i < s; i++) {
Object object = lst.get(i);
if (object instanceof commentType) {
commentType commentType = (commentType) object;
//according to http://sphinx-doc.org/ext/autodoc.html#directive-autoattribute,
//to be a valid comment must be before the definition or in the same line.
// if (Math.abs(commentType.beginLine - nameDefinition.beginLine) <= 2) { --Not checking it (being a bit more lenient -- and if it's defined once in the context we'll be sure it'll be found.
if (commentType.id != null) {
String trim = commentType.id.trim();
if (trim.startsWith("#")) {
trim = trim.substring(1).trim();
}
if (trim.startsWith(":")) {
String type = NodeUtils.getTypeForParameterFromDocstring(actTok, trim.substring(1));
if (type != null) {
ret.add(new TypeInfo(type));
}
} else if (trim.startsWith("@")) {
String type = NodeUtils.getTypeForParameterFromDocstring(actTok, trim);
if (type != null) {
ret.add(new TypeInfo(type));
}
}
// }
}
} else if (object instanceof Str) {
Str str = (Str) object;
// if (Math.abs(str.beginLine - nameDefinition.beginLine) <= 2) {
if (str.s != null) {
String trim = str.s.trim();
if (trim.startsWith("#")) {
trim = trim.substring(1).trim();
}
if (trim.startsWith(":")) {
String type = NodeUtils.getTypeForParameterFromDocstring(actTok, trim.substring(1));
if (type != null) {
ret.add(new TypeInfo(type));
}
} else if (trim.startsWith("@")) {
String type = NodeUtils.getTypeForParameterFromDocstring(actTok, trim);
if (type != null) {
ret.add(new TypeInfo(type));
}
}
}
// }
}
}
}
return ret;
}
/**
* @param ret the list where the representation should be added
* @param expr the Name or Attribute that determines the class that should be added
*/
private void addRepresentationIfPossible(List<ITypeInfo> ret, exprType expr) {
if (expr instanceof Name || expr instanceof Attribute) {
String string = NodeUtils.getFullRepresentationString(expr);
if (string != null) {
ret.add(new TypeInfo(string));
}
}
}
public IPythonNature getPythonNature() {
return this.nature;
}
}