Renewed resolver, closes PY-19 and supposedly PY-13.

Introduces a new internal interface for easier resolution.
       Corrects fromQualifiedPackageImportFile and LookAhead tests.
       There is a known problem with Jython resolution using "import *", to be fixed later.
This commit is contained in:
Dmitry Cheryasov
2008-07-30 07:47:38 +04:00
parent 37de2911db
commit dfd8e8b62f
30 changed files with 730 additions and 114 deletions
@@ -21,6 +21,7 @@ public interface PyElementTypes {
PyElementType DECORATED_FUNCTION_DECLARATION = new PyElementType("DECORATED_FUNCTION_DECLARATION", PyDecoratedFunctionImpl.class);
PyElementType ARGUMENT_LIST = new PyElementType("ARGUMENT_LIST", PyArgumentListImpl.class);
PyElementType IMPORT_ELEMENT = new PyElementType("IMPORT_ELEMENT", PyImportElementImpl.class);
PyElementType STAR_IMPORT_ELEMENT = new PyElementType("STAR_IMPORT_ELEMENT", PyStarImportElementImpl.class);
PyElementType EXCEPT_BLOCK = new PyElementType("EXCEPT_BLOCK", PyExceptBlockImpl.class);
PyElementType PRINT_TARGET = new PyElementType("PRINT_TARGET", PyPrintTargetImpl.class);
@@ -350,7 +350,9 @@ public class StatementParsing
from_future = true;
}
if (builder.getTokenType() == PyTokenTypes.MULT) {
final PsiBuilder.Marker star_import_mark = builder.mark();
builder.advanceLexer();
star_import_mark.done(PyElementTypes.STAR_IMPORT_ELEMENT);
}
else if (builder.getTokenType() == PyTokenTypes.LPAR) {
builder.advanceLexer();
@@ -0,0 +1,131 @@
package com.jetbrains.python.psi;
import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Iterator;
import java.util.NoSuchElementException;
/**
* PSI element that (re)defnies names in following namespace, e.g. assignment statement does.
* User: dcheryasov
* Date: Jul 3, 2008
*/
public interface NameDefiner extends PsiElement {
/**
* @return an iterator that iterates over defined names, in order of definition.
* Complex targets count, too: "(y, x[1]) = (1, 2)" return both "y" and "x[1]".
*/
@NotNull
Iterable<PyElement> iterateNames();
/**
* @param the_name an unqualified name.
* @return an element which is defined under that name in this instance, or null.
*/
@Nullable
PsiElement getElementNamed(String the_name);
/**
* @return true if names found inside its children cannot be resolved to names defined by this statement..
* E.g. name <tt>a</tt> is defined in statement <tt>a = a + 1</tt> but the <tt>a</tt> on the left hand side
* must not resolve to the <tt>a</tt> on the right hand side.
*/
boolean mustResolveOutside();
/**
* Convenience iterator wrapper; makes items cast to given type (presumably PyExpression).
* @param <T> class to cast to.
*/
class ArrayIterable<T> implements Iterable<T> {
protected T[] mySource;
public ArrayIterable(T[] source){
mySource = source;
}
public Iterator<T> iterator() {
return new ArrayIter<T>(mySource);
}
}
class ArrayIter<T> implements Iterator<T> {
protected int my_index;
protected T[] content;
public ArrayIter(T[] content) {
this.content = content;
my_index = 0;
}
public boolean hasNext() {
return ((content != null) && (my_index < content.length));
}
public T next() {
if (hasNext()) {
T ret = content[my_index];
my_index += 1;
return ret;
}
else throw new NoSuchElementException(content == null ? "Null content" : "Only got " + content.length + "items");
}
public void remove() {
throw new UnsupportedOperationException("Can't remove targets from iter");
}
}
class SingleIterable<T> implements Iterable<T> {
T content;
public SingleIterable(T content) {
this.content = content;
}
public Iterator<T> iterator() {
return new SingleIter();
}
class SingleIter implements Iterator<T> {
boolean expired;
SingleIter() {
expired = false;
}
public boolean hasNext() {
return !expired;
}
public T next() {
if (hasNext()) {
expired = true;
return content;
}
else throw new NoSuchElementException("Single iter expired");
}
public void remove() {
throw new UnsupportedOperationException("Can't remove targets from single iter");
}
}
}
class IterHelper {
private IterHelper() {}
@Nullable
public static PyElement findName(Iterable<PyElement> it, String name) {
PyElement ret = null;
for (PyElement elt : it) {
if ((elt != null) && (name.equals(elt.getName()))) {
ret = elt;
break;
}
}
return ret;
}
}
}
@@ -25,7 +25,7 @@ import org.jetbrains.annotations.Nullable;
* Time: 13:05:17
* To change this template use File | Settings | File Templates.
*/
public interface PyAssignmentStatement extends PyStatement {
public interface PyAssignmentStatement extends PyStatement, NameDefiner {
PyExpression[] getTargets();
@Nullable PyExpression getAssignedValue();
}
@@ -30,7 +30,7 @@ import org.jetbrains.annotations.Nullable;
* Time: 0:26:47
* To change this template use File | Settings | File Templates.
*/
public interface PyClass extends PsiNamedElement, PyElement, PyDocStringOwner, StubBasedPsiElement<PyClassStub> {
public interface PyClass extends PsiNamedElement, PyElement, NameDefiner, PyDocStringOwner, StubBasedPsiElement<PyClassStub> {
@NotNull
PyStatementList getStatementList();
@@ -21,7 +21,24 @@ import com.intellij.psi.tree.TokenSet;
import org.jetbrains.annotations.Nullable;
public interface PyElement extends NavigatablePsiElement {
/**
* An empty array to return cheaply without allocating it anew.
*/
PyElement[] EMPTY_ARRAY = new PyElement[0];
/**
* Find a parent element of specified class.
* @param aClass the class to look for.
* @param &lt;T> the class to look for and to return. (Logically the same as aClass, but Java fails to express this concisely.)
* @return A parent element whose class is <tt>T</tt>, if it exists, or null.
*/
@Nullable <T extends PyElement> T getContainingElement(Class<T> aClass);
/**
* Find a parent whose element type is in the set.
* @param tokenSet a set of element types
* @return A parent element whose element type belongs to tokenSet, if it exists, or null.
*/
@Nullable PyElement getContainingElement(TokenSet tokenSet);
}
@@ -26,7 +26,7 @@ import org.jetbrains.annotations.NotNull;
* Time: 23:37:32
* To change this template use File | Settings | File Templates.
*/
public interface PyExceptBlock extends PyElement {
public interface PyExceptBlock extends PyElement, NameDefiner {
PyExceptBlock[] EMPTY_ARRAY = new PyExceptBlock[0];
@Nullable PyExpression getExceptClass();
@@ -26,7 +26,7 @@ import org.jetbrains.annotations.Nullable;
* Time: 21:20:52
* To change this template use File | Settings | File Templates.
*/
public interface PyForStatement extends PyStatement {
public interface PyForStatement extends PyStatement, NameDefiner {
@NotNull PyStatementList getStatementList();
@Nullable PyStatementList getElseStatementList();
@Nullable PyExpression getTargetExpression();
@@ -30,7 +30,7 @@ import org.jetbrains.annotations.Nullable;
* Time: 23:01:03
* To change this template use File | Settings | File Templates.
*/
public interface PyFunction extends PsiNamedElement, PyElement, PyDocStringOwner, StubBasedPsiElement<PyFunctionStub> {
public interface PyFunction extends PsiNamedElement, PyElement, NameDefiner, PyDocStringOwner, StubBasedPsiElement<PyFunctionStub> {
PyFunction[] EMPTY_ARRAY = new PyFunction[0];
/**
@@ -25,6 +25,6 @@ import org.jetbrains.annotations.NotNull;
* Time: 10:29:19
* To change this template use File | Settings | File Templates.
*/
public interface PyGlobalStatement extends PyStatement {
public interface PyGlobalStatement extends PyStatement, NameDefiner {
@NotNull PyReferenceExpression[] getGlobals();
}
@@ -25,7 +25,7 @@ import org.jetbrains.annotations.Nullable;
* Time: 22:22:17
* To change this template use File | Settings | File Templates.
*/
public interface PyImportElement extends PyElement {
public interface PyImportElement extends PyElement, NameDefiner {
@Nullable
PyReferenceExpression getImportReference();
@@ -26,6 +26,6 @@ import com.jetbrains.python.psi.stubs.PyParameterListStub;
* Time: 23:03:11
* To change this template use File | Settings | File Templates.
*/
public interface PyParameterList extends PyElement, StubBasedPsiElement<PyParameterListStub> {
public interface PyParameterList extends PyElement, StubBasedPsiElement<PyParameterListStub>, NameDefiner {
PyParameter[] getParameters();
}
@@ -21,7 +21,9 @@ import com.intellij.codeInsight.lookup.LookupElementFactory;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.jetbrains.python.PyElementTypes;
import com.jetbrains.python.psi.impl.PyScopeProcessor;
import com.jetbrains.python.psi.impl.ResolveImportUtil;
import org.jetbrains.annotations.NotNull;
@@ -41,6 +43,7 @@ public class PyResolveUtil {
@NotNull
public static String getReadableRepr(PsiElement elt) {
if (elt == null) return "null!";
ASTNode node = elt.getNode();
if (node == null) return "null";
else {
@@ -54,35 +57,155 @@ public class PyResolveUtil {
private PyResolveUtil() {
}
/**
* Tries to find nearest parent that conceals nams definded inside it. Such elements are 'class' and 'def':
* anything defined within it does not seep to the namespace below them, but is concealed within.
* @param elt starting point of search.
* @return 'class' or 'def' element, or null if not found.
*/
@Nullable
public static PsiElement treeWalkUp(PsiScopeProcessor processor, PsiElement elt, PsiElement lastParent, PsiElement place) {
if (elt == null) return null;
public static PsiElement getConcealingParent(PsiElement elt) {
PsiElement top = PsiTreeUtil.getParentOfType(elt, PyClass.class, PyFunction.class);
return top;
}
PsiElement cur = elt;
protected static PsiElement getInnermostChildOf(PsiElement elt) {
PsiElement feeler = elt;
PsiElement seeker;
seeker = feeler;
// find innermost last child of the subtree we're in
while (feeler != null) {
seeker = feeler;
feeler = feeler.getLastChild();
}
return seeker;
}
/**
* Returns closest previous node of given class, as input file would have it.
* @param elt node from which to look for a previous atatement.
* @param cls class of the previous node to find.
* @return previous statement, or null.
*/
@Nullable
public static <T> T getPrevNodeOf(PsiElement elt, Class<T> cls) {
PsiElement seeker = elt;
while (seeker != null) {
PsiElement feeler = seeker.getPrevSibling();
if (feeler != null) {
seeker = getInnermostChildOf(feeler);
}
else { // we were the first subnode
// find something above the parent node we've not exhausted yet
seeker = seeker.getParent();
if (seeker instanceof PyFile) return null; // all file nodes have been looked up, in vain
}
if (cls.isInstance(seeker)) return (T)seeker;
}
// here elt is null or a PsiFile is not up in the parent chain.
return null;
}
/**
* Crawls up the PSI tree, checking nodes as if crawling backwards through source lexemes.
* @param processor a visitor that says when the crawl is done and collects info.
* @param elt element from which we start (not checked by processor); if null, the search immediately fails.
* @param fromunder if true, search not above elt, but from an [possibly imaginary] node right below elt; so elt gets analyzed, too.
* @return first element that the processor accepted.
*/
@Nullable
public static PsiElement treeCrawlUp(PsiScopeProcessor processor, PsiElement elt, boolean fromunder) {
if (elt == null) return null; // can't find anyway.
PsiElement seeker = elt;
PsiElement cap = getConcealingParent(elt);
do {
if ((processor instanceof ResolveProcessor) && !(((ResolveProcessor)processor).approve(cur))) {
return null;
}
/* // resolution debug tracker
if (cur instanceof PsiFile) System.out.println(processor.toString() + ": " + cur.toString());
else System.out.println(processor.toString() + ": " + _fmt_node(cur));
*/
if (!cur.processDeclarations(processor, ResolveState.initial(), cur == elt ? lastParent : null, elt)) {
if (processor instanceof ResolveProcessor) {
return ((ResolveProcessor)processor).getResult();
if (fromunder) {
fromunder = false; // only honour fromunder once per call
seeker = getPrevNodeOf(getInnermostChildOf(seeker), NameDefiner.class);
}
else { // main case
seeker = getPrevNodeOf(seeker, NameDefiner.class);
}
// aren't we in the same defining assignment, global, etc?
if ((seeker != null) && ((NameDefiner)seeker).mustResolveOutside() && PsiTreeUtil.isAncestor(seeker, elt, true)
) {
seeker = getPrevNodeOf(seeker, NameDefiner.class);
}
// maybe we're under cap
while (true) {
PsiElement local_cap = getConcealingParent(seeker);
if ((local_cap != null) && (local_cap != cap)) { // only look at local cap and above
if (local_cap instanceof NameDefiner) seeker = local_cap;
else seeker = getPrevNodeOf(local_cap, NameDefiner.class);
}
if (local_cap == null) break; // seeker is in global context
if (local_cap == cap) break; // seeker is in the same context as elt
if ((cap != null) && PsiTreeUtil.isAncestor(local_cap, cap, true)) break; // seeker is in a context above elt's
}
if (cur instanceof PsiFile) break;
cur = cur.getPrevSibling();
// maybe we're capped by a class
PsiElement possible_class_cap = getConcealingParent(seeker);
if (possible_class_cap instanceof PyClass) continue; // class implicitly qualifies things, and we're looking for unqualified.
// check
if (seeker != null) {
if (!processor.execute(seeker, ResolveState.initial())) {
if (processor instanceof ResolveProcessor) {
return ((ResolveProcessor)processor).getResult();
}
else return seeker; // can't point to exact element, but somewhere here
}
}
} while (seeker != null);
return null;
}
/**
* Returns treeCrawlUp(processor, elt, false). A convenience method.
* @see com.jetbrains.python.psi.PyResolveUtil#treeCrawlUp(PsiScopeProcessor, PsiElement, boolean)
*/
@Nullable
public static PsiElement treeCrawlUp(PsiScopeProcessor processor, PsiElement elt) {
return treeCrawlUp(processor, elt, false);
}
public static class DeclRefPair {
public final PsiElement decl;
public final PsiElement ref;
public DeclRefPair(PsiElement decl, PsiElement ref) {
this.decl = decl;
this.ref = ref;
}
while (cur != null);
}
if (elt == place) return null;
return treeWalkUp(processor, elt.getContext(), elt, place);
/**
* Resolves a (qualified) reference by collecting all its (left-hand) qualifiers and resolving left to right.
* E.g. an attemt to resolve "z" in "x.y.z" resolves x, then y in x, then z in x.y.
* @param target the reference to resolve.
* @return a list of (declaration, ref) pairs for all qualifeirs of target, with ref == target for the last element.
* E.g. for "x.y.z" the result is {(X, x), (Y, y), (Z, z)}, where X, Y and Z are declarations which x, y, and z refer to.
* When a declaration for a reference cannot be found, null is given instead.
* E.g. {(X, x), (Y, y), (null, z)} means that element z failed to resolve to a declaration.
*/
@NotNull
public static List<DeclRefPair> resolveQualified(PyReferenceExpression target) {
List<DeclRefPair> ret = new LinkedList<DeclRefPair>();
try {
final ASTNode[] nodes = target.getNode().getChildren(PyElementTypes.EXPRESSIONS);
if (nodes.length > 0) {
PyExpression first = (PyExpression)nodes[nodes.length-1]; // innermost child is leftmost qualifier
// find nearest expression that both precedes target and defines the name of first
// it may be a target of assignment, a parameter definition or a global definition.
// for this, go from target backwards.
}
}
catch (NullPointerException ex) {
ret.add(new DeclRefPair(null, target)); // on NPE, return at least a safe "dunno"
// TODO: log this
}
return ret;
}
// NOTE: to be moved to more general scope
/**
* Tries to match two [qualified] reference expression paths; target must be a 'sublist' of source to match.
* E.g., 'a.b.c.d' and 'a.b.c' would match, while 'a.b.c' and 'a.b.c.d' would not. Eqaully, 'a.b.c' and 'a.b.d' would not match.
@@ -109,7 +232,7 @@ public class PyResolveUtil {
/**
* Unwinds a [multi-level] qualified expression into a path, as seen in source text, i.e. outermost qualifier first.
* If any qualifier happens to be not a referencce expression, or expr is null, null is returned.
* If any qualifier happens to be not a reference expression, or expr is null, null is returned.
* @param expr an experssion to unwind.
* @return path as a list of ref expressions, or null.
*/
@@ -189,6 +312,14 @@ public class PyResolveUtil {
return false;
}
}
else if (element instanceof NameDefiner) {
final NameDefiner definer = (NameDefiner)element;
PsiElement by_name = definer.getElementNamed(myName);
if (by_name != null) {
myResult = by_name;
return false;
}
}
return true;
}
@@ -271,6 +402,7 @@ public class PyResolveUtil {
}
public boolean execute(PsiElement element, ResolveState substitutor) {
// TODO: refactor to look saner; much code duplication
if (element instanceof PsiNamedElement) {
final PsiNamedElement psiNamedElement = (PsiNamedElement)element;
final String name = psiNamedElement.getName();
@@ -285,6 +417,17 @@ public class PyResolveUtil {
myVariants.put(referencedName, LookupElementFactory.getInstance().createLookupElement(element, referencedName));
}
}
else if (element instanceof NameDefiner) {
final NameDefiner definer = (NameDefiner)element;
for (PyElement expr: definer.iterateNames()) {
if (expr != null) { // NOTE: maybe rather have SingleIterables skip nulls outright?
String referencedName = expr.getName();
if (referencedName != null && !myVariants.containsKey(referencedName)) {
myVariants.put(referencedName, LookupElementFactory.getInstance().createLookupElement(element, referencedName));
}
}
}
}
return true;
}
@@ -0,0 +1,9 @@
package com.jetbrains.python.psi;
/**
* Marks the star in "from foo import *".
* User: dcheryasov
* Date: Jul 28, 2008
*/
public interface PyStarImportElement extends PyElement, NameDefiner {
}
@@ -239,4 +239,5 @@ public class PyArgumentListImpl extends PyElementImpl implements PyArgumentList
me.removeChild(node);
}
}
}
@@ -18,8 +18,8 @@ package com.jetbrains.python.psi.impl;
import com.intellij.lang.ASTNode;
import com.intellij.psi.PsiElement;
import com.intellij.psi.ResolveState;
import com.intellij.psi.PsiErrorElement;
import com.intellij.psi.ResolveState;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.util.PsiTreeUtil;
import com.jetbrains.python.PyElementTypes;
@@ -27,6 +27,9 @@ import com.jetbrains.python.psi.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
/**
* Created by IntelliJ IDEA.
* User: yole
@@ -97,4 +100,35 @@ public class PyAssignmentStatementImpl extends PyElementImpl implements PyAssign
}
return true;
}
protected static List<PyElement> _unfoldParenExprs(PyElement[] targets, List<PyElement> receiver) {
// NOTE: this proliferation of instanceofs is not very beautiful. Maybe rewrite using a visitor.
for (PyElement exp : targets) {
if (exp instanceof PyParenthesizedExpression) {
final PyParenthesizedExpression parex = (PyParenthesizedExpression)exp;
PyExpression cont = parex.getContainedExpression();
if (cont instanceof PyTupleExpression) {
final PyTupleExpression tupex = (PyTupleExpression)cont;
_unfoldParenExprs(tupex.getElements(), receiver);
}
else receiver.add(exp);
}
else receiver.add(exp);
}
return receiver;
}
@NotNull
public Iterable<PyElement> iterateNames() {
PyExpression[] targets = getTargets();
return _unfoldParenExprs(targets, new ArrayList<PyElement>(targets.length));
}
public PyElement getElementNamed(final String the_name) {
return IterHelper.findName(iterateNames(), the_name);
}
public boolean mustResolveOutside() {
return true; // a = a+1 resolves 'a' outside itself.
}
}
@@ -137,6 +137,7 @@ public class PyClassImpl extends PyPresentableElementImpl<PyClassStub> implement
@NotNull
public PyFunction[] getMethods() {
// TODO: gather all top-level functions, maybe within control statements
final PyClassStub classStub = getStub();
if (classStub != null) {
return classStub.getChildrenByType(PyElementTypes.FUNCTION_DECLARATION, new PyFunction[0]);
@@ -211,11 +212,13 @@ public class PyClassImpl extends PyPresentableElementImpl<PyClassStub> implement
return result.toArray(new PyTargetExpression[result.size()]);
}
@Override
@Override
public boolean processDeclarations(@NotNull PsiScopeProcessor processor,
@NotNull ResolveState substitutor,
PsiElement lastParent,
@NotNull PsiElement place) {
@NotNull PsiElement place)
{
/* */
for(PyFunction func: getMethods()) {
if (func == lastParent) continue;
if (!processor.execute(func, substitutor)) return false;
@@ -231,6 +234,7 @@ public class PyClassImpl extends PyPresentableElementImpl<PyClassStub> implement
if (processor instanceof PyResolveUtil.VariantsProcessor) {
return true;
}
/**/
return processor.execute(this, substitutor);
}
@@ -246,4 +250,17 @@ public class PyClassImpl extends PyPresentableElementImpl<PyClassStub> implement
public String toString() {
return "PyClass: " + getName();
}
@NotNull
public Iterable<PyElement> iterateNames() {
return new SingleIterable<PyElement>(this);
}
public PyElement getElementNamed(final String the_name) {
return the_name.equals(getName())? this: null;
}
public boolean mustResolveOutside() {
return false;
}
}
@@ -17,13 +17,10 @@
package com.jetbrains.python.psi.impl;
import com.intellij.lang.ASTNode;
import com.jetbrains.python.psi.PyElementVisitor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import com.jetbrains.python.PyElementTypes;
import com.jetbrains.python.psi.PyExceptBlock;
import com.jetbrains.python.psi.PyExpression;
import com.jetbrains.python.psi.PyStatementList;
import com.jetbrains.python.psi.*;
/**
* Created by IntelliJ IDEA.
@@ -33,23 +30,37 @@ import com.jetbrains.python.psi.PyStatementList;
* To change this template use File | Settings | File Templates.
*/
public class PyExceptBlockImpl extends PyElementImpl implements PyExceptBlock {
public PyExceptBlockImpl(ASTNode astNode) {
super(astNode);
}
public PyExceptBlockImpl(ASTNode astNode) {
super(astNode);
}
@Override protected void acceptPyVisitor(PyElementVisitor pyVisitor) {
pyVisitor.visitPyExceptBlock(this);
}
@Override protected void acceptPyVisitor(PyElementVisitor pyVisitor) {
pyVisitor.visitPyExceptBlock(this);
}
public @Nullable PyExpression getExceptClass() {
return childToPsi(PyElementTypes.EXPRESSIONS, 0);
}
public @Nullable PyExpression getExceptClass() {
return childToPsi(PyElementTypes.EXPRESSIONS, 0);
}
public @Nullable PyExpression getTarget() {
return childToPsi(PyElementTypes.EXPRESSIONS, 1);
}
public @Nullable PyExpression getTarget() {
return childToPsi(PyElementTypes.EXPRESSIONS, 1);
}
public @NotNull PyStatementList getStatementList() {
return childToPsiNotNull(PyElementTypes.STATEMENT_LIST);
}
public @NotNull PyStatementList getStatementList() {
return childToPsiNotNull(PyElementTypes.STATEMENT_LIST);
}
@NotNull
public Iterable<PyElement> iterateNames() {
return new SingleIterable<PyElement>(getTarget());
}
public PyElement getElementNamed(final String the_name) {
PyElement target = getTarget();
return ((target != null) && the_name.equals(target.getName()))? target : null;
}
public boolean mustResolveOutside() {
return false;
}
}
@@ -84,4 +84,21 @@ public class PyForStatementImpl extends PyElementImpl implements PyForStatement
}
return true;
}
@NotNull
public Iterable<PyElement> iterateNames() {
PyExpression tgt = getTargetExpression();
if (tgt instanceof PyTupleExpression) {
return new ArrayIterable<PyElement>(((PyTupleExpression)(tgt)).getElements());
}
else return new SingleIterable<PyElement>(tgt);
}
public PyElement getElementNamed(final String the_name) {
return IterHelper.findName(iterateNames(), the_name);
}
public boolean mustResolveOutside() {
return false;
}
}
@@ -118,14 +118,16 @@ public class PyFunctionImpl extends PyPresentableElementImpl<PyFunctionStub> imp
public boolean processDeclarations(@NotNull PsiScopeProcessor processor,
@NotNull ResolveState substitutor,
PsiElement lastParent,
@NotNull PsiElement place) {
@NotNull PsiElement place)
{
/*
if (lastParent != null && lastParent.getParent() == this) {
final PyParameter[] params = getParameterList().getParameters();
for (PyParameter param : params) {
if (!processor.execute(param, substitutor)) return false;
}
}
*/
return processor.execute(this, substitutor);
}
@@ -150,4 +152,17 @@ public class PyFunctionImpl extends PyPresentableElementImpl<PyFunctionStub> imp
}
return super.getElementLocation();
}
@NotNull
public Iterable<PyElement> iterateNames() {
return new SingleIterable<PyElement>(this);
}
public PyElement getElementNamed(final String the_name) {
return the_name.equals(getName())? this : null;
}
public boolean mustResolveOutside() {
return false;
}
}
@@ -61,4 +61,17 @@ public class PyGlobalStatementImpl extends PyElementImpl implements PyGlobalStat
}
return true;
}
@NotNull
public Iterable<PyElement> iterateNames() {
return new ArrayIterable<PyElement>(getGlobals());
}
public PyElement getElementNamed(final String the_name) {
return IterHelper.findName(iterateNames(), the_name);
}
public boolean mustResolveOutside() {
return true;
}
}
@@ -22,10 +22,7 @@ import com.intellij.psi.ResolveState;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.util.PsiTreeUtil;
import com.jetbrains.python.PyElementTypes;
import com.jetbrains.python.psi.PyImportElement;
import com.jetbrains.python.psi.PyReferenceExpression;
import com.jetbrains.python.psi.PyTargetExpression;
import com.jetbrains.python.psi.PyResolveUtil;
import com.jetbrains.python.psi.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -94,4 +91,43 @@ public class PyImportElementImpl extends PyElementImpl implements PyImportElemen
return true;
}
@NotNull
public Iterable<PyElement> iterateNames() {
PyElement ret = getAsName();
if (ret == null) {
List<PyReferenceExpression> unwound_path = PyResolveUtil.unwindRefPath(getImportReference());
if ((unwound_path != null) && (unwound_path.size() > 0)) ret = unwound_path.get(0);
}
return new SingleIterable<PyElement>(ret);
}
public PsiElement getElementNamed(final String the_name) {
PyElement named_elt = IterHelper.findName(iterateNames(), the_name);
if (named_elt != null) {
PsiElement from_elt = null;
PyReferenceExpression import_ref = getImportReference(); // import what?
if (import_ref == null) return null;
String import_ref_name = import_ref.getName();
if (import_ref_name == null) return null; // no imported name
/*
PyFromImportStatement import_from_stmt = PsiTreeUtil.getParentOfType(this, PyFromImportStatement.class);
if (import_from_stmt != null) {
PyReferenceExpression from_src = import_from_stmt.getImportSource();
if (from_src != null) {
//return ResolveImportUtil.resolvePythonImport2(from_src, import_ref_name);
return ResolveImportUtil.resolveImportReference(import_ref);
}
}
// else return ResolveImportUtil.resolvePythonImport2(import_ref, null);
else return ResolveImportUtil.resolveImportReference(import_ref);
*/
return ResolveImportUtil.resolveImportReference(import_ref);
}
// no element of this name
return null;
}
public boolean mustResolveOutside() {
return true; // formally
}
}
@@ -18,10 +18,9 @@ package com.jetbrains.python.psi.impl;
import com.intellij.lang.ASTNode;
import com.jetbrains.python.PyElementTypes;
import com.jetbrains.python.psi.PyElementVisitor;
import com.jetbrains.python.psi.PyParameter;
import com.jetbrains.python.psi.PyParameterList;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.stubs.PyParameterListStub;
import org.jetbrains.annotations.NotNull;
/**
* Created by IntelliJ IDEA.
@@ -47,4 +46,17 @@ public class PyParameterListImpl extends PyBaseElementImpl<PyParameterListStub>
public PyParameter[] getParameters() {
return getStubOrPsiChildren(PyElementTypes.FORMAL_PARAMETER, new PyParameter[0]);
}
@NotNull
public Iterable<PyElement> iterateNames() {
return new ArrayIterable<PyElement>(getParameters());
}
public PyElement getElementNamed(final String the_name) {
return IterHelper.findName(iterateNames(), the_name);
}
public boolean mustResolveOutside() {
return false; // we don't exactly have children to resolve, but if we did...
}
}
@@ -36,7 +36,9 @@ import com.jetbrains.python.psi.types.PyType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Created by IntelliJ IDEA.
@@ -95,6 +97,13 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere
return getNode().findChildByType(PyTokenTypes.IDENTIFIER);
}
@Nullable
@Override
public String getName() {
return getReferencedName();
}
/**
* Resolves reference to the most obvious point.
* Imported module names: to module file (or directory for a qualifier).
@@ -119,6 +128,7 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere
If we ever need to exactly tell a dir from __init__.py, that logic has to change.
*/
}
else return null; // dir without __init__.py does not resolve
}
return target;
}
@@ -132,7 +142,39 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere
return null;
}
return PyResolveUtil.treeWalkUp(new PyResolveUtil.ResolveProcessor(referencedName), this, this, null);
// here we have an unqualified expr. it may be defined:
// ...in current file
//PsiElement ret = PyResolveUtil.treeWalkUp(new PyResolveUtil.ResolveProcessor(referencedName), this, this, null);
PsiElement ret = PyResolveUtil.treeCrawlUp(new PyResolveUtil.ResolveProcessor(referencedName), this);
if (ret == null) {
// ...as a part of current module
PyType otype = PyBuiltinCache.getInstance(this.getProject()).getObjectType(); // "object" as a closest kin to "module"
ret = otype.resolveMember(getName());
}
if (ret == null) {
// ...as a builtin symbol
PyFile bfile = PyBuiltinCache.getInstance(this.getProject()).getBuiltinsFile();
ret = PyResolveUtil.treeCrawlUp(new PyResolveUtil.ResolveProcessor(referencedName), bfile, true);
}
if (ret == null) {
// if we're under a cap, an external object that we want to use might be also defined below us.
// look through all contexts, closest first.
PsiElement our_cap = PyResolveUtil.getConcealingParent(this);
PyResolveUtil.ResolveProcessor proc = new PyResolveUtil.ResolveProcessor(referencedName); // reusable till first hit
if (our_cap != null) {
PsiElement cap = our_cap;
while (true) {
cap = PyResolveUtil.getConcealingParent(cap);
if (cap == null) cap = this.getContainingFile();
ret = PyResolveUtil.treeCrawlUp(proc, cap, true);
if ((ret != null) && !PsiTreeUtil.isAncestor(our_cap, ret, true)) {
break;
}
if (cap instanceof PsiFile) break; // file level, can't try more
}
}
}
return ret;
}
/**
@@ -213,8 +255,13 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere
return new Object[0];
}
if (PsiTreeUtil.getParentOfType(this, PyImportElement.class) != null) {
// complete to possible modules
return ResolveImportUtil.suggestImportVariants(this);
}
final PyResolveUtil.VariantsProcessor processor = new PyResolveUtil.VariantsProcessor();
PyResolveUtil.treeWalkUp(processor, this, this, null);
PyResolveUtil.treeCrawlUp(processor, this);
return processor.getResult();
}
@@ -262,6 +309,7 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere
}
private boolean isBuiltInConstant() {
// TODO: generalize
String name = getReferencedName();
return PyNames.NONE.equals(name) || "True".equals(name) || "False".equals(name);
}
@@ -0,0 +1,43 @@
package com.jetbrains.python.psi.impl;
import com.jetbrains.python.psi.PyStarImportElement;
import com.jetbrains.python.psi.PyElement;
import com.jetbrains.python.psi.PyFromImportStatement;
import com.jetbrains.python.psi.PyReferenceExpression;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.lang.ASTNode;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Simplest PyStarImportElement possible.
* User: dcheryasov
* Date: Jul 28, 2008
*/
public class PyStarImportElementImpl extends PyElementImpl implements PyStarImportElement {
public PyStarImportElementImpl(ASTNode astNode) {
super(astNode);
}
@NotNull
public Iterable<PyElement> iterateNames() {
return new ArrayIterable<PyElement>(PyElement.EMPTY_ARRAY);
}
@Nullable
public PsiElement getElementNamed(final String the_name) {
PyFromImportStatement import_from_stmt = PsiTreeUtil.getParentOfType(this, PyFromImportStatement.class);
if (import_from_stmt != null) {
PyReferenceExpression from_src = import_from_stmt.getImportSource();
// XXX won't work in Jython. Use resolvePythonImport with a mock reference
return ResolveImportUtil.resolvePythonImport2(from_src, the_name);
}
else return null;
}
public boolean mustResolveOutside() {
return true; // we don't have children, but...
}
}
@@ -12,6 +12,10 @@ import com.intellij.psi.PsiManager;
import com.jetbrains.python.psi.*;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* @author yole
*/
@@ -24,80 +28,101 @@ public class ResolveImportUtil {
private ResolveImportUtil() {
}
/**
* Resolves a reference in an import statement into whatever object it refers to.
* @param importRef a reference within an import element.
* @return the object importRef refers to, or null.
*/
@Nullable
static PsiElement resolveImportReference(final PyReferenceExpression importRef) {
if (importRef == null) return null; // fail fast
final String referencedName = importRef.getReferencedName();
if (referencedName == null) return null;
PsiElement importFrom = null;
PyReferenceExpression source = null;
if (importRef.getParent() instanceof PyImportElement) {
PyImportElement parent = (PyImportElement) importRef.getParent();
if (parent.getParent() instanceof PyFromImportStatement) {
PyFromImportStatement stmt = (PyFromImportStatement) parent.getParent();
final PyReferenceExpression source = stmt.getImportSource();
source = stmt.getImportSource();
if (source == null) return null;
importFrom = resolveImportReference(source);
}
}
PsiElement result = resolvePythonImport(importRef, importFrom, referencedName);
PsiElement result;
if (source != null) {
result = resolvePythonImport2(source, referencedName);
}
else result = resolvePythonImport2(importRef, null);
if (result != null) {
return result;
}
return resolveForeignImport(importRef, importFrom);
return resolveForeignImport(importRef, resolveImportReference(source));
}
/**
* Resolves either <tt>import foo</tt> or <tt>from foo import bar</tt>.
* @param importRef refers to the name of the module being imported (the <tt>foo</tt>).
* @param referencedName the name imported from the module (the <tt>bar</tt> in <tt>import from</tt>), or null (for just <tt>import foo</tt>).
* @return element the name resolves to, or null.
*/
@Nullable
private static PsiElement resolvePythonImport(final PyReferenceExpression importRef, final PsiElement importFrom,
final String referencedName) {
public static PsiElement resolvePythonImport2(final PyReferenceExpression importRef, final String referencedName) {
final String the_name = referencedName != null? referencedName : importRef.getName();
PsiFile containing_file = importRef.getContainingFile();
/*
True resolve order is:
- local modules,
- builtins? (check),
- modules from sys.path (aka SdkOrderEntries).
(http://docs.python.org/ref/import.html)
*/
// TODO: assume some things like sys to be only from __builtins__
// TODO: rewrite entirely imitating Python import process: global module table, under-initialisation, etc.
// qualified imports resolve their children
final PyExpression qualifier = importRef.getQualifier();
if (qualifier instanceof PyReferenceExpression) {
// resolve qualifier (all of them, recursively)
PsiElement qualifierElement = ((PyReferenceExpression) qualifier).resolve();
if (qualifierElement == null) return null;
return resolveChild(qualifierElement, referencedName, importRef);
//
return resolveChild(qualifierElement, the_name, containing_file);
}
*/
PsiElement last_resolved = null;
List<PyReferenceExpression> ref_path = PyResolveUtil.unwindRefPath(importRef);
Iterator<PyReferenceExpression> it = ref_path.iterator();
if (ref_path.size() > 1) { // it was a qualified name
if (it.hasNext()) {
last_resolved = it.next().resolve(); // our topmost qualifier, not ourselves for certain
}
else return null; // topmost qualifier not found
while (it.hasNext()) {
last_resolved = resolveChild(last_resolved, it.next().getName(), containing_file);
if (last_resolved == null) return null; // anything in the chain unresolved means that the whole chain fails
}
if (referencedName != null) {
return resolveChild(last_resolved, referencedName, containing_file);
}
else return last_resolved;
}
if (importFrom != null) {
return resolveChild(importFrom, referencedName, importRef);
// non-qualified name
if (referencedName != null) {
return resolveChild(importRef.resolve(), referencedName, containing_file);
// the importRef.resolve() does not recurse infinitely because we're asked to resolve referencedName, not importRef itself
}
// unqualified import can be found:
// in the same dir
final PsiFile pfile = importRef.getContainingFile();
if (pfile != null) {
PsiDirectory pdir = pfile.getContainingDirectory();
if (pdir != null) {
PsiElement elt = resolveChild(pdir, referencedName, importRef);
PsiElement elt = resolveChild(pdir, the_name, null);
if (elt != null) return elt;
}
}
}
// .. or in SDK roots
final Module module = ModuleUtil.findModuleForPsiElement(importRef);
if (module != null) {
RootPolicy<PsiElement> resolvePolicy = new RootPolicy<PsiElement>() {
/*
public PsiElement visitModuleSourceOrderEntry(final ModuleSourceOrderEntry moduleOrderEntry, final PsiElement value) {
if (value != null) return value;
return resolveInRoots(moduleOrderEntry.getRootModel().getContentRoots(), referencedName, importRef);
}
*/
@Nullable
public PsiElement visitJdkOrderEntry(final JdkOrderEntry jdkOrderEntry, final PsiElement value) {
if (value != null) return value;
return resolveInRoots(jdkOrderEntry.getRootFiles(OrderRootType.SOURCES), referencedName, importRef);
return resolveInRoots(jdkOrderEntry.getRootFiles(OrderRootType.SOURCES), the_name, importRef);
}
};
return ModuleRootManager.getInstance(module).processOrder(resolvePolicy, null);
@@ -108,17 +133,19 @@ public class ResolveImportUtil {
importRef.getContainingFile().getVirtualFile()
)
) {
PsiElement elt = resolveInRoots(entry.getFiles(OrderRootType.CLASSES), referencedName, importRef);
PsiElement elt = resolveInRoots(entry.getFiles(OrderRootType.CLASSES), the_name, importRef);
if (elt != null) return elt;
}
}
catch (NullPointerException ex) {
return null;
return null; // any cut corners migt result in an NPE; resolution fails, but not the IDE.
}
}
return null; // normally unreachable
return null; // not resolved by any means
}
@Nullable
private static PsiElement resolveForeignImport(final PyReferenceExpression importRef, final PsiElement importFrom) {
for(PyImportResolver resolver: Extensions.getExtensions(PyImportResolver.EP_NAME)) {
@@ -163,18 +190,18 @@ public class ResolveImportUtil {
}
/**
Tries to find referencedName under the parent element. Used to reesolve any names that look imported.
Tries to find referencedName under the parent element. Used to resolve any names that look imported.
Parent might happen to be a PyFile(__init__.py), then it is treated <i>both</i> as a file and as ist base dir.
For details of this ugly magic, see {@link com.jetbrains.python.psi.impl.PyReferenceExpressionImpl#resolve()}.
@param parent element under which to look for referenced name.
@param referencedName which name to look for.
@param importRef import reference which resolution led to this call.
@param parent element under which to look for referenced name.
* @param referencedName which name to look for.
* @param containingFile
@return the element the referencedName resolves to, or null.
@todo: Honor module's __all__ value.
@todo: Honor package's __path__ value (hard).
*/
@Nullable
public static PsiElement resolveChild(final PsiElement parent, final String referencedName, final PyReferenceExpression importRef) {
public static PsiElement resolveChild(final PsiElement parent, final String referencedName, final PsiFile containingFile) {
PsiDirectory dir = null;
PsiElement ret = null;
PyResolveUtil.ResolveProcessor processor = null;
@@ -190,7 +217,8 @@ public class ResolveImportUtil {
*/
// look for name in the file:
processor = new PyResolveUtil.ResolveProcessor(referencedName);
ret = PyResolveUtil.treeWalkUp(processor, parent, null, importRef);
//ret = PyResolveUtil.treeWalkUp(processor, parent, null, importRef);
ret = PyResolveUtil.treeCrawlUp(processor, parent, true);
if (ret != null) return ret;
}
else { // the file was a fake __init__.py covering a reference to dir
@@ -207,13 +235,46 @@ public class ResolveImportUtil {
if (subdir != null) return subdir;
else { // not a subdir, not a file; could be a name in parent/__init__.py
final PsiFile initPy = dir.findFile(INIT_PY);
if ((importRef != null) && (initPy == importRef.getContainingFile())) return ret; // don't dive into the file we're in
if (initPy == containingFile) return ret; // don't dive into the file we're in
if (initPy != null) {
if (processor == null) processor = new PyResolveUtil.ResolveProcessor(referencedName); // should not normally happen
return PyResolveUtil.treeWalkUp(processor, initPy, null, importRef);
return PyResolveUtil.treeCrawlUp(processor, initPy, true);//PyResolveUtil.treeWalkUp(processor, initPy, null, importRef);
}
}
}
return ret;
}
/**
* Finds reasonable names to import to complete a patrial name.
* @param partial_ref reference containing the partial name.
* @return an array of names ready for gtVariants().
*/
public static String[] suggestImportVariants(PyReferenceExpression partial_ref) {
List<String> variants = new ArrayList<String>();
String prefix_u = partial_ref.getNode().getText().toUpperCase(); // we try case-insensitively
//
// look at current dir
final VirtualFile pfile = partial_ref.getContainingFile().getVirtualFile();
if (pfile != null) {
VirtualFile pdir = pfile.getParent();
_siftDir(pdir, prefix_u, variants, pfile) ;
}
// look in SDK
// TODO: implement, reusing resolver code
return variants.toArray(new String[variants.size()]);
}
static void _siftDir(VirtualFile pdir, String prefix, List<String> variants, VirtualFile pfile) {
if (pdir != null) {
for (VirtualFile a_file : pdir.getChildren()) {
// TODO: check extensions, chack subdirs with __init__.py
if ((a_file != pfile) && (a_file.getName().toUpperCase().startsWith(prefix))) {
variants.add(a_file.getName());
}
}
}
}
}
@@ -10,7 +10,7 @@ import com.jetbrains.python.psi.PyStringLiteralExpression;
*/
public class StringConstantAnnotator extends PyAnnotator {
public static final String MISSING_Q = "Missing closing quote";
public static final String PREMATURE_Q = "Premature closing quote";
//public static final String PREMATURE_Q = "Premature closing quote";
public void visitPyStringLiteralExpression(final PyStringLiteralExpression node) {
String s = node.getText();
String msg = "";
@@ -34,11 +34,15 @@ public class StringConstantAnnotator extends PyAnnotator {
char c = s.charAt(index);
if (esc) esc = false;
else {
if (c == first_quote) { // impossible with current lexer, but who knows :)
if (c != first_quote) {
if (c == '\\') esc = true;
}
/*
else { // impossible with current lexer, but who knows :)
msg = PREMATURE_Q + " [" + first_quote + "]";
ok = false;
}
else if (c == '\\') esc = true;
*/
}
index += 1;
}
+3 -4
View File
@@ -1,5 +1,4 @@
g<ref>gg("q")
def f():
return f<ref>oo
def f(q): pass
ggg = f
foo = 1