mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
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:
@@ -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 <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;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
g<ref>gg("q")
|
||||
def f():
|
||||
return f<ref>oo
|
||||
|
||||
def f(q): pass
|
||||
|
||||
ggg = f
|
||||
foo = 1
|
||||
@@ -0,0 +1 @@
|
||||
# obligatory
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
# obligatory
|
||||
Reference in New Issue
Block a user