diff --git a/python/src/com/jetbrains/python/PyElementTypes.java b/python/src/com/jetbrains/python/PyElementTypes.java index 2b88d30cc69a..86040c3fa558 100644 --- a/python/src/com/jetbrains/python/PyElementTypes.java +++ b/python/src/com/jetbrains/python/PyElementTypes.java @@ -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); diff --git a/python/src/com/jetbrains/python/parsing/StatementParsing.java b/python/src/com/jetbrains/python/parsing/StatementParsing.java index 4e96391bfa0e..391b69ac8369 100644 --- a/python/src/com/jetbrains/python/parsing/StatementParsing.java +++ b/python/src/com/jetbrains/python/parsing/StatementParsing.java @@ -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(); diff --git a/python/src/com/jetbrains/python/psi/NameDefiner.java b/python/src/com/jetbrains/python/psi/NameDefiner.java new file mode 100644 index 000000000000..d330bbbc373b --- /dev/null +++ b/python/src/com/jetbrains/python/psi/NameDefiner.java @@ -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 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 a is defined in statement a = a + 1 but the a on the left hand side + * must not resolve to the a on the right hand side. + */ + boolean mustResolveOutside(); + + + /** + * Convenience iterator wrapper; makes items cast to given type (presumably PyExpression). + * @param class to cast to. + */ + class ArrayIterable implements Iterable { + protected T[] mySource; + public ArrayIterable(T[] source){ + mySource = source; + } + + public Iterator iterator() { + return new ArrayIter(mySource); + } + } + + class ArrayIter implements Iterator { + + 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 implements Iterable { + + T content; + public SingleIterable(T content) { + this.content = content; + } + + public Iterator iterator() { + return new SingleIter(); + } + + class SingleIter implements Iterator { + + 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 it, String name) { + PyElement ret = null; + for (PyElement elt : it) { + if ((elt != null) && (name.equals(elt.getName()))) { + ret = elt; + break; + } + } + return ret; + } + } + +} diff --git a/python/src/com/jetbrains/python/psi/PyAssignmentStatement.java b/python/src/com/jetbrains/python/psi/PyAssignmentStatement.java index 0add022c56ea..d313859dd831 100644 --- a/python/src/com/jetbrains/python/psi/PyAssignmentStatement.java +++ b/python/src/com/jetbrains/python/psi/PyAssignmentStatement.java @@ -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(); } diff --git a/python/src/com/jetbrains/python/psi/PyClass.java b/python/src/com/jetbrains/python/psi/PyClass.java index 0c8fb8aa8aa9..9236c343730c 100644 --- a/python/src/com/jetbrains/python/psi/PyClass.java +++ b/python/src/com/jetbrains/python/psi/PyClass.java @@ -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 { +public interface PyClass extends PsiNamedElement, PyElement, NameDefiner, PyDocStringOwner, StubBasedPsiElement { @NotNull PyStatementList getStatementList(); diff --git a/python/src/com/jetbrains/python/psi/PyElement.java b/python/src/com/jetbrains/python/psi/PyElement.java index 01c1b98e97b0..4368e4f3ef04 100644 --- a/python/src/com/jetbrains/python/psi/PyElement.java +++ b/python/src/com/jetbrains/python/psi/PyElement.java @@ -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 T, if it exists, or null. + */ @Nullable T getContainingElement(Class 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); } diff --git a/python/src/com/jetbrains/python/psi/PyExceptBlock.java b/python/src/com/jetbrains/python/psi/PyExceptBlock.java index 1609e49c147a..afb3dba950d8 100644 --- a/python/src/com/jetbrains/python/psi/PyExceptBlock.java +++ b/python/src/com/jetbrains/python/psi/PyExceptBlock.java @@ -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(); diff --git a/python/src/com/jetbrains/python/psi/PyForStatement.java b/python/src/com/jetbrains/python/psi/PyForStatement.java index 24689b6c9ae0..3d3922bfd3c0 100644 --- a/python/src/com/jetbrains/python/psi/PyForStatement.java +++ b/python/src/com/jetbrains/python/psi/PyForStatement.java @@ -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(); diff --git a/python/src/com/jetbrains/python/psi/PyFunction.java b/python/src/com/jetbrains/python/psi/PyFunction.java index f41f8722a1bc..dcff13c65cb9 100644 --- a/python/src/com/jetbrains/python/psi/PyFunction.java +++ b/python/src/com/jetbrains/python/psi/PyFunction.java @@ -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 { +public interface PyFunction extends PsiNamedElement, PyElement, NameDefiner, PyDocStringOwner, StubBasedPsiElement { PyFunction[] EMPTY_ARRAY = new PyFunction[0]; /** diff --git a/python/src/com/jetbrains/python/psi/PyGlobalStatement.java b/python/src/com/jetbrains/python/psi/PyGlobalStatement.java index 1fec3501684f..6d71dfabdb3a 100644 --- a/python/src/com/jetbrains/python/psi/PyGlobalStatement.java +++ b/python/src/com/jetbrains/python/psi/PyGlobalStatement.java @@ -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(); } diff --git a/python/src/com/jetbrains/python/psi/PyImportElement.java b/python/src/com/jetbrains/python/psi/PyImportElement.java index d8d4caeda4ce..01d906d749e2 100644 --- a/python/src/com/jetbrains/python/psi/PyImportElement.java +++ b/python/src/com/jetbrains/python/psi/PyImportElement.java @@ -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(); diff --git a/python/src/com/jetbrains/python/psi/PyParameterList.java b/python/src/com/jetbrains/python/psi/PyParameterList.java index 39e0b862a7ec..134908a4d902 100644 --- a/python/src/com/jetbrains/python/psi/PyParameterList.java +++ b/python/src/com/jetbrains/python/psi/PyParameterList.java @@ -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 { +public interface PyParameterList extends PyElement, StubBasedPsiElement, NameDefiner { PyParameter[] getParameters(); } diff --git a/python/src/com/jetbrains/python/psi/PyResolveUtil.java b/python/src/com/jetbrains/python/psi/PyResolveUtil.java index 126bbffdb323..5c76fe3ffed1 100644 --- a/python/src/com/jetbrains/python/psi/PyResolveUtil.java +++ b/python/src/com/jetbrains/python/psi/PyResolveUtil.java @@ -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 getPrevNodeOf(PsiElement elt, Class 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 resolveQualified(PyReferenceExpression target) { + List ret = new LinkedList(); + 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; } diff --git a/python/src/com/jetbrains/python/psi/PyStarImportElement.java b/python/src/com/jetbrains/python/psi/PyStarImportElement.java new file mode 100644 index 000000000000..1f91048da631 --- /dev/null +++ b/python/src/com/jetbrains/python/psi/PyStarImportElement.java @@ -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 { +} diff --git a/python/src/com/jetbrains/python/psi/impl/PyArgumentListImpl.java b/python/src/com/jetbrains/python/psi/impl/PyArgumentListImpl.java index a9ecf9df6a1c..dcca28ac68d8 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyArgumentListImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyArgumentListImpl.java @@ -239,4 +239,5 @@ public class PyArgumentListImpl extends PyElementImpl implements PyArgumentList me.removeChild(node); } } + } diff --git a/python/src/com/jetbrains/python/psi/impl/PyAssignmentStatementImpl.java b/python/src/com/jetbrains/python/psi/impl/PyAssignmentStatementImpl.java index 90dccd5c1147..4aec75047b69 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyAssignmentStatementImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyAssignmentStatementImpl.java @@ -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 _unfoldParenExprs(PyElement[] targets, List 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 iterateNames() { + PyExpression[] targets = getTargets(); + return _unfoldParenExprs(targets, new ArrayList(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. + } } diff --git a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java index ef131e01f113..8830113e0f74 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java @@ -137,6 +137,7 @@ public class PyClassImpl extends PyPresentableElementImpl 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 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 implement if (processor instanceof PyResolveUtil.VariantsProcessor) { return true; } + /**/ return processor.execute(this, substitutor); } @@ -246,4 +250,17 @@ public class PyClassImpl extends PyPresentableElementImpl implement public String toString() { return "PyClass: " + getName(); } + + @NotNull + public Iterable iterateNames() { + return new SingleIterable(this); + } + + public PyElement getElementNamed(final String the_name) { + return the_name.equals(getName())? this: null; + } + + public boolean mustResolveOutside() { + return false; + } } diff --git a/python/src/com/jetbrains/python/psi/impl/PyExceptBlockImpl.java b/python/src/com/jetbrains/python/psi/impl/PyExceptBlockImpl.java index fb78d624e0a6..c0c0bd9d9619 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyExceptBlockImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyExceptBlockImpl.java @@ -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 iterateNames() { + return new SingleIterable(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; + } } diff --git a/python/src/com/jetbrains/python/psi/impl/PyForStatementImpl.java b/python/src/com/jetbrains/python/psi/impl/PyForStatementImpl.java index ecc2dcda0ba5..3f1a21fb4ddf 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyForStatementImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyForStatementImpl.java @@ -84,4 +84,21 @@ public class PyForStatementImpl extends PyElementImpl implements PyForStatement } return true; } + + @NotNull + public Iterable iterateNames() { + PyExpression tgt = getTargetExpression(); + if (tgt instanceof PyTupleExpression) { + return new ArrayIterable(((PyTupleExpression)(tgt)).getElements()); + } + else return new SingleIterable(tgt); + } + + public PyElement getElementNamed(final String the_name) { + return IterHelper.findName(iterateNames(), the_name); + } + + public boolean mustResolveOutside() { + return false; + } } diff --git a/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java index 6d6ac6e21852..ae5f740c00a8 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java @@ -118,14 +118,16 @@ public class PyFunctionImpl extends PyPresentableElementImpl 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 imp } return super.getElementLocation(); } + + @NotNull + public Iterable iterateNames() { + return new SingleIterable(this); + } + + public PyElement getElementNamed(final String the_name) { + return the_name.equals(getName())? this : null; + } + + public boolean mustResolveOutside() { + return false; + } } diff --git a/python/src/com/jetbrains/python/psi/impl/PyGlobalStatementImpl.java b/python/src/com/jetbrains/python/psi/impl/PyGlobalStatementImpl.java index 409b30cad6cb..bcca7575d78e 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyGlobalStatementImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyGlobalStatementImpl.java @@ -61,4 +61,17 @@ public class PyGlobalStatementImpl extends PyElementImpl implements PyGlobalStat } return true; } + + @NotNull + public Iterable iterateNames() { + return new ArrayIterable(getGlobals()); + } + + public PyElement getElementNamed(final String the_name) { + return IterHelper.findName(iterateNames(), the_name); + } + + public boolean mustResolveOutside() { + return true; + } } diff --git a/python/src/com/jetbrains/python/psi/impl/PyImportElementImpl.java b/python/src/com/jetbrains/python/psi/impl/PyImportElementImpl.java index eb27e3bbc0f7..5e5721e60620 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyImportElementImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyImportElementImpl.java @@ -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 iterateNames() { + PyElement ret = getAsName(); + if (ret == null) { + List unwound_path = PyResolveUtil.unwindRefPath(getImportReference()); + if ((unwound_path != null) && (unwound_path.size() > 0)) ret = unwound_path.get(0); + } + return new SingleIterable(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 + } } diff --git a/python/src/com/jetbrains/python/psi/impl/PyParameterListImpl.java b/python/src/com/jetbrains/python/psi/impl/PyParameterListImpl.java index 737f714ddd95..439c5e6b5871 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyParameterListImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyParameterListImpl.java @@ -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 public PyParameter[] getParameters() { return getStubOrPsiChildren(PyElementTypes.FORMAL_PARAMETER, new PyParameter[0]); } + + @NotNull + public Iterable iterateNames() { + return new ArrayIterable(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... + } } diff --git a/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java index a1e6d3f7ecd4..5e732bf8241e 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java @@ -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); } diff --git a/python/src/com/jetbrains/python/psi/impl/PyStarImportElementImpl.java b/python/src/com/jetbrains/python/psi/impl/PyStarImportElementImpl.java new file mode 100644 index 000000000000..c7892d3411c0 --- /dev/null +++ b/python/src/com/jetbrains/python/psi/impl/PyStarImportElementImpl.java @@ -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 iterateNames() { + return new ArrayIterable(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... + } +} diff --git a/python/src/com/jetbrains/python/psi/impl/ResolveImportUtil.java b/python/src/com/jetbrains/python/psi/impl/ResolveImportUtil.java index 832ce6daa77d..c650c42818af 100644 --- a/python/src/com/jetbrains/python/psi/impl/ResolveImportUtil.java +++ b/python/src/com/jetbrains/python/psi/impl/ResolveImportUtil.java @@ -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 import foo or from foo import bar. + * @param importRef refers to the name of the module being imported (the foo). + * @param referencedName the name imported from the module (the bar in import from), or null (for just import foo). + * @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 ref_path = PyResolveUtil.unwindRefPath(importRef); + Iterator 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 resolvePolicy = new RootPolicy() { - /* - 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 both 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 variants = new ArrayList(); + 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 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()); + } + } + } + } + } diff --git a/python/src/com/jetbrains/python/validation/StringConstantAnnotator.java b/python/src/com/jetbrains/python/validation/StringConstantAnnotator.java index ed9161e057ea..392c71571e84 100644 --- a/python/src/com/jetbrains/python/validation/StringConstantAnnotator.java +++ b/python/src/com/jetbrains/python/validation/StringConstantAnnotator.java @@ -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; } diff --git a/python/testData/resolve/LookAhead.py b/python/testData/resolve/LookAhead.py index 42b460ea43fd..293dd3fee90d 100644 --- a/python/testData/resolve/LookAhead.py +++ b/python/testData/resolve/LookAhead.py @@ -1,5 +1,4 @@ -ggg("q") +def f(): + return foo -def f(q): pass - -ggg = f +foo = 1 \ No newline at end of file diff --git a/python/testData/resolve/multiFile/fromQualifiedPackageImportFile/mypackage/__init__.py b/python/testData/resolve/multiFile/fromQualifiedPackageImportFile/mypackage/__init__.py index e69de29bb2d1..e076cfbb59a6 100644 --- a/python/testData/resolve/multiFile/fromQualifiedPackageImportFile/mypackage/__init__.py +++ b/python/testData/resolve/multiFile/fromQualifiedPackageImportFile/mypackage/__init__.py @@ -0,0 +1 @@ +# obligatory diff --git a/python/testData/resolve/multiFile/fromQualifiedPackageImportFile/mypackage/child/__init__.py b/python/testData/resolve/multiFile/fromQualifiedPackageImportFile/mypackage/child/__init__.py new file mode 100644 index 000000000000..e076cfbb59a6 --- /dev/null +++ b/python/testData/resolve/multiFile/fromQualifiedPackageImportFile/mypackage/child/__init__.py @@ -0,0 +1 @@ +# obligatory