From 80d7945468f365d015350ffa9f15f725d1df393c Mon Sep 17 00:00:00 2001 From: Dmitry Cheryasov Date: Fri, 5 Dec 2008 17:24:13 +0300 Subject: [PATCH] Adds multi-resolve to imported names; fixes PY-120. --- .../PyUnresolvedReferencesInspection.java | 11 +- .../jetbrains/python/psi/PyResolveUtil.java | 37 ++++-- .../python/psi/RatedResolveResult.java | 35 +++++ .../python/psi/impl/PyCallExpressionImpl.java | 14 +- .../python/psi/impl/PyImportElementImpl.java | 60 +++++++++ .../psi/impl/PyReferenceExpressionImpl.java | 121 +++++++++++++----- .../psi/impl/PyStarImportElementImpl.java | 39 ++++++ .../validation/AssignTargetAnnotator.java | 46 ++++--- .../python/PyMultiFileResolveTest.java | 63 ++++++++- 9 files changed, 352 insertions(+), 74 deletions(-) create mode 100644 python/src/com/jetbrains/python/psi/RatedResolveResult.java diff --git a/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java b/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java index 2375a574d360..cc861dab8674 100644 --- a/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java @@ -9,6 +9,7 @@ import com.intellij.lang.annotation.HighlightSeverity; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElementVisitor; import com.intellij.psi.PsiReference; +import com.intellij.psi.PsiPolyVariantReference; import com.intellij.psi.util.PsiTreeUtil; import com.jetbrains.python.PyBundle; import com.jetbrains.python.psi.*; @@ -76,7 +77,15 @@ public class PyUnresolvedReferencesInspection extends LocalInspectionTool { severity = ((PsiReferenceEx) reference).getUnresolvedHighlightSeverity(); if (severity == null) continue; } - if (reference.resolve() == null) { + boolean unresolved; + if (reference instanceof PsiPolyVariantReference) { + final PsiPolyVariantReference poly = (PsiPolyVariantReference)reference; + unresolved = (poly.multiResolve(false).length == 0); + } + else { + unresolved = (reference.resolve() == null); + } + if (unresolved) { StringBuffer description_buf = new StringBuffer(""); String text = reference.getElement().getText(); String ref_text = reference.getRangeInElement().substring(text); // text of the part we're working with diff --git a/python/src/com/jetbrains/python/psi/PyResolveUtil.java b/python/src/com/jetbrains/python/psi/PyResolveUtil.java index bb0dfd6cb5be..f5960a445727 100644 --- a/python/src/com/jetbrains/python/psi/PyResolveUtil.java +++ b/python/src/com/jetbrains/python/psi/PyResolveUtil.java @@ -19,13 +19,14 @@ package com.jetbrains.python.psi; import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.codeInsight.lookup.LookupElementFactory; import com.intellij.codeInsight.lookup.LookupItem; -import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; import com.intellij.psi.scope.PsiScopeProcessor; import com.intellij.psi.util.PsiTreeUtil; import com.jetbrains.python.psi.impl.PyScopeProcessor; import com.jetbrains.python.psi.impl.ResolveImportUtil; +import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -307,30 +308,47 @@ public class PyResolveUtil { public static class ResolveProcessor implements PyScopeProcessor { private String myName; private PsiElement myResult = null; - private Set mySeen; + /*private Set mySeen;*/ + private List myDefiners; public ResolveProcessor(final String name) { myName = name; - mySeen = new HashSet(); + /*mySeen = new HashSet();*/ + myDefiners = new ArrayList(2); // 1 is typical, 2 is sometimes, more is rare. } public PsiElement getResult() { return myResult; } - + + @NonNls static String _nvl(Object s) { if (s != null) return "'" + s.toString() + "'"; - else return "null"; // TODO: move to PyNames + else return "null"; } - + + /* public Set getSeen() { return mySeen; } - + */ + + /** + * Adds a NameDefiner point which is a secondary resolution target. E.g. import statement for imported name. + * @param definer + */ + protected void addNameDefiner(NameDefiner definer) { + myDefiners.add(definer); + } + + public ListgetDefiners() { + return myDefiners; + } + public String toString() { return _nvl(myName) + ", " + _nvl(myResult); } - + public boolean execute(PsiElement element, ResolveState substitutor) { if (element instanceof PyFile) { final VirtualFile file = ((PyFile)element).getVirtualFile(); @@ -367,6 +385,9 @@ public class PyResolveUtil { PsiElement by_name = definer.getElementNamed(myName); if (by_name != null) { myResult = by_name; + if (!PsiTreeUtil.isAncestor(element, by_name, true)) { // non-trivial definer + addNameDefiner(definer); + } return false; } } diff --git a/python/src/com/jetbrains/python/psi/RatedResolveResult.java b/python/src/com/jetbrains/python/psi/RatedResolveResult.java new file mode 100644 index 000000000000..25f38fdcbb0c --- /dev/null +++ b/python/src/com/jetbrains/python/psi/RatedResolveResult.java @@ -0,0 +1,35 @@ +package com.jetbrains.python.psi; + +import com.intellij.psi.ResolveResult; + +/** + * Provides a way to sort results of multi-resolve. + * User: dcheryasov + * Date: Dec 5, 2008 11:06:30 AM + */ +public interface RatedResolveResult extends ResolveResult { + + RatedResolveResult[] EMPTY_ARRAY = new RatedResolveResult[0]; + + /** + * Results with higher rate are shown higher in the list of multiResolve(). + * @see com.intellij.psi.PsiPolyVariantReference#multiResolve(boolean) + * @return desired rate. If in doubt, use 0. + */ + int getRate(); + + /** + * For unusual items that need to be on top. + */ + int RATE_HIGH = 1000; + + /** + * For regular references. + */ + int RATE_NORMAL = 0; + + /** + * For additional, less important results. + */ + int RATE_LOW = -1000; +} diff --git a/python/src/com/jetbrains/python/psi/impl/PyCallExpressionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyCallExpressionImpl.java index 452702fcd2d1..6fdf5dd4a7cd 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyCallExpressionImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyCallExpressionImpl.java @@ -19,6 +19,7 @@ package com.jetbrains.python.psi.impl; import com.intellij.lang.ASTNode; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiReference; +import com.intellij.psi.ResolveResult; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.IncorrectOperationException; import com.jetbrains.python.psi.*; @@ -146,12 +147,15 @@ public class PyCallExpressionImpl extends PyElementImpl implements PyCallExpress public PyType getType() { PyExpression callee = getCallee(); if (callee instanceof PyReferenceExpression) { - PsiElement target = ((PyReferenceExpression)callee).resolve(); - if (target instanceof PyClass) { - return new PyClassType((PyClass) target, false); // we call a class name, that is, the constructor, we get an instance. + ResolveResult[] targets = ((PyReferenceExpression)callee).multiResolve(false); + if (targets.length == 0) { + PsiElement target = targets[0].getElement(); + if (target instanceof PyClass) { + return new PyClassType((PyClass) target, false); // we call a class name, that is, the constructor, we get an instance. + } + // TODO: look at well-known functions and their return types + return PyReferenceExpressionImpl.getReferenceTypeFromProviders(target); } - // TODO: look at well-known functions and their return types - return PyReferenceExpressionImpl.getReferenceTypeFromProviders(target); } return callee.getType(); } diff --git a/python/src/com/jetbrains/python/psi/impl/PyImportElementImpl.java b/python/src/com/jetbrains/python/psi/impl/PyImportElementImpl.java index a7051b86c526..de21f0554f35 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyImportElementImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyImportElementImpl.java @@ -17,6 +17,8 @@ package com.jetbrains.python.psi.impl; import com.intellij.lang.ASTNode; +import com.intellij.navigation.ItemPresentation; +import com.intellij.openapi.editor.colors.TextAttributesKey; import com.intellij.psi.PsiElement; import com.intellij.psi.ResolveState; import com.intellij.psi.scope.PsiScopeProcessor; @@ -26,6 +28,7 @@ import com.jetbrains.python.psi.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import javax.swing.*; import java.util.List; /** @@ -89,6 +92,63 @@ public class PyImportElementImpl extends PyElementImpl implements PyImportElemen return true; } + @Override + public ItemPresentation getPresentation() { + return new ItemPresentation() { + + @NotNull + private String getRefName(String default_name) { + PyReferenceExpression ref = getImportReference(); + if (ref != null) { + String refname = ref.getName(); + if (refname != null) return refname; + } + return default_name; + } + + public String getPresentableText() { + return getRefName(""); + } + + public String getLocationString() { + PyElement elt = PsiTreeUtil.getParentOfType(PyImportElementImpl.this, PyImportStatement.class, PyFromImportStatement.class); + StringBuffer buf = new StringBuffer("| "); + if (elt != null) { // always? who knows :) + if (elt instanceof PyFromImportStatement) { // from ... import ... + buf.append("from "); + PyReferenceExpression imp_src = ((PyFromImportStatement)elt).getImportSource(); + if (imp_src != null) { + buf.append(PyResolveUtil.toPath(imp_src, ".")); + } + else buf.append(""); + buf.append(" import "); + } + else { // "import ... " + buf.append("import "); + } + buf.append(getRefName("")); + } + else { + buf.append("import?.. "); + } + // are we the name or the 'as'? + PyTargetExpression as_part = getAsName(); + if (as_part != null) { + buf.append(" as ").append(as_part.getName()); + } + return buf.toString(); + } + + public Icon getIcon(final boolean open) { + return null; + } + + public TextAttributesKey getTextAttributesKey() { + return null; + } + }; + } + @NotNull public Iterable iterateNames() { PyElement ret = getAsName(); diff --git a/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java index 78d3ce3d2a2d..1effeca32192 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java @@ -30,6 +30,7 @@ import com.intellij.psi.impl.source.resolve.ResolveCache; import com.intellij.psi.scope.PsiScopeProcessor; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.IncorrectOperationException; +import com.intellij.util.containers.SortedList; import com.jetbrains.python.PyElementTypes; import com.jetbrains.python.PyIcons; import com.jetbrains.python.PyNames; @@ -121,16 +122,39 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere return results.length == 1 ? results[0].getElement() : null; } + + private static class ResultList extends ArrayList { + // Allows to add non-null elements and discard nulls in a hassle-free way. + public boolean poke(final PsiElement what, final int rate) { + if (what == null) return false; + super.add(new RatedResolveResult() { + public int getRate() { return rate; } + + public PsiElement getElement() { return what; } + + public boolean isValidResult() { return true; } + }); + return true; + } + + public void pokeAll(Collection elts, int rate) { + for (PsiElement elt : elts) poke(elt, rate); + } + } + /** * Does actual resolution of resolve(). * @return resolution result. * @see #resolve() */ private - @Nullable - PsiElement resolveInner() { + @NotNull + List resolveInner() { + //List ret = new ArrayList(); + ResultList ret = new ResultList(); + final String referencedName = getReferencedName(); - if (referencedName == null) return null; + if (referencedName == null) return ret; if (PsiTreeUtil.getParentOfType(this, PyImportElement.class, PyFromImportStatement.class) != null) { PsiElement target = ResolveImportUtil.resolveImportReference(this); @@ -145,9 +169,13 @@ 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 + else { + ret.clear(); + return ret; // dir without __init__.py does not resolve + } } - return target; + ret.poke(target, RatedResolveResult.RATE_HIGH); + return ret; } final PyExpression qualifier = getQualifier(); @@ -159,42 +187,67 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere List qualifier_path = PyResolveUtil.unwindQualifiers((PyQualifiedExpression)qualifier); if (qualifier_path != null) { for (PyExpression ex : collectAssignedAttributes((PyQualifiedExpression)qualifier)) { - if (referencedName.equals(ex.getName())) return ex; + if (referencedName.equals(ex.getName())) { + ret.poke(ex, RatedResolveResult.RATE_NORMAL); + return ret; + } } } } // resolve within the type proper - return qualifierType.resolveMember(referencedName); + PsiElement ref_elt = qualifierType.resolveMember(referencedName); + if (ref_elt != null) ret.poke(ref_elt, RatedResolveResult.RATE_NORMAL); + return ret; } - return null; + return ret; } // here we have an unqualified expr. it may be defined: // ...in current file - PsiElement ret = PyResolveUtil.treeCrawlUp(new PyResolveUtil.ResolveProcessor(referencedName), this); - if ((ret != null) && (ret instanceof PyClass)) { - // is it a case of the bizarre "class Foo(Foo)" construct? - PyClass cls = (PyClass)ret; - for (PyExpression base_expr : cls.getSuperClassExpressions()){ - if (base_expr == this) return null; // cannot resolve us, the base class ref, to the class being defined + PyResolveUtil.ResolveProcessor processor = new PyResolveUtil.ResolveProcessor(referencedName); + PsiElement uexpr = PyResolveUtil.treeCrawlUp(processor, this); + if ((uexpr != null)) { + if ((uexpr instanceof PyClass)) { + // is it a case of the bizarre "class Foo(Foo)" construct? + PyClass cls = (PyClass)uexpr; + for (PyExpression base_expr : cls.getSuperClassExpressions()){ + if (base_expr == this) { + ret.clear(); + return ret; // cannot resolve us, the base class ref, to the class being defined + } + } + } + // sort what we got + for (NameDefiner hit : processor.getDefiners()) { + ret.poke(hit, getRate(hit)); } } - if (ret == null) { + if (uexpr == null) { // ...as a part of current module - PyType otype = PyBuiltinCache.getInstance(this.getProject()).getObjectType(); // "object" as a closest kin to "module" - if (otype != null) ret = otype.resolveMember(getName()); + PyType otype = PyBuiltinCache.getInstance(getProject()).getObjectType(); // "object" as a closest kin to "module" + if (otype != null) uexpr = otype.resolveMember(getName()); } - if (ret == null) { + if (uexpr == null) { // ...as a builtin symbol - PyFile bfile = PyBuiltinCache.getInstance(this.getProject()).getBuiltinsFile(); - ret = PyResolveUtil.treeCrawlUp(new PyResolveUtil.ResolveProcessor(referencedName), true, bfile); + PyFile bfile = PyBuiltinCache.getInstance(getProject()).getBuiltinsFile(); + uexpr = PyResolveUtil.treeCrawlUp(new PyResolveUtil.ResolveProcessor(referencedName), true, bfile); } - if (ret == null) { - ret = PyResolveUtil.resolveOffContext(this); + if (uexpr == null) { + uexpr = PyResolveUtil.resolveOffContext(this); } + ret.poke(uexpr, getRate(uexpr)); return ret; } + // NOTE: very crude + private static int getRate(PsiElement elt) { + int rate; + if (elt instanceof PyImportElement || elt instanceof PyStarImportElement) rate = RatedResolveResult.RATE_LOW; + else if (elt instanceof PyFile) rate = RatedResolveResult.RATE_HIGH; + else rate = RatedResolveResult.RATE_NORMAL; + return rate; + } + private static Collection collectAssignedAttributes(PyQualifiedExpression qualifier) { List qualifier_path = PyResolveUtil.unwindQualifiers(qualifier); if (qualifier_path != null) { @@ -213,25 +266,21 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere // crude logic right here to see it work - PsiElement target = resolveInner(); - if (target == null) return ResolveResult.EMPTY_ARRAY; + List targets = resolveInner(); + if (targets.size() == 0) return ResolveResult.EMPTY_ARRAY; - List ret = new ArrayList(); - ret.add(new PsiElementResolveResult(target)); - - if (target instanceof PsiDirectory) { - final PsiDirectory dir = (PsiDirectory)target; - final PsiFile file = dir.findFile(ResolveImportUtil.INIT_PY); - if (file != null) { - ret.add(0, new PsiElementResolveResult(file)); + List ret = new SortedList(new Comparator() { + public int compare(final RatedResolveResult one, final RatedResolveResult another) { + return another.getRate() - one.getRate(); } - } + }); + ret.addAll(targets); return ret.toArray(new ResolveResult[ret.size()]); } /** - * Resolves reference to possible referred elements. + * Reso lves reference to possible referred elements. * First element is always what resolve() would return. * Imported module names: to module file, or {directory, '___init__.py}' for a qualifier. * @todo Local identifiers: a list of definitions in the most recent compound statement @@ -402,7 +451,9 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere return PyNoneType.INSTANCE; } } - PsiElement target = resolve(); + ResolveResult[] targets = multiResolve(false); + if (targets.length == 0) return null; + PsiElement target = targets[0].getElement(); if (target == this) { return null; } diff --git a/python/src/com/jetbrains/python/psi/impl/PyStarImportElementImpl.java b/python/src/com/jetbrains/python/psi/impl/PyStarImportElementImpl.java index e2291d3c8c9d..738c00e583a3 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyStarImportElementImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyStarImportElementImpl.java @@ -4,9 +4,13 @@ import com.jetbrains.python.psi.*; import com.intellij.psi.PsiElement; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.lang.ASTNode; +import com.intellij.navigation.ItemPresentation; +import com.intellij.openapi.editor.colors.TextAttributesKey; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import javax.swing.*; + /** * Simplest PyStarImportElement possible. * User: dcheryasov @@ -37,4 +41,39 @@ public class PyStarImportElementImpl extends PyElementImpl implements PyStarImpo public boolean mustResolveOutside() { return true; // we don't have children, but... } + + @Override + public ItemPresentation getPresentation() { + return new ItemPresentation() { + + private String getName() { + PyElement elt = PsiTreeUtil.getParentOfType(PyStarImportElementImpl.this, PyFromImportStatement.class); + if (elt instanceof PyFromImportStatement) { // always? who knows :) + PyReferenceExpression imp_src = ((PyFromImportStatement)elt).getImportSource(); + if (imp_src != null) { + return PyResolveUtil.toPath(imp_src, "."); + } + } + return ""; + } + + public String getPresentableText() { + return getName(); + } + + public String getLocationString() { + StringBuffer buf = new StringBuffer("| "); + buf.append("from ").append(getName()).append(" import *"); + return buf.toString(); + } + + public Icon getIcon(final boolean open) { + return null; + } + + public TextAttributesKey getTextAttributesKey() { + return null; + } + }; + } } diff --git a/python/src/com/jetbrains/python/validation/AssignTargetAnnotator.java b/python/src/com/jetbrains/python/validation/AssignTargetAnnotator.java index 36e64f2f4527..099c0f6d5844 100644 --- a/python/src/com/jetbrains/python/validation/AssignTargetAnnotator.java +++ b/python/src/com/jetbrains/python/validation/AssignTargetAnnotator.java @@ -16,6 +16,8 @@ package com.jetbrains.python.validation; +import static com.jetbrains.python.PyBundle.message; +import com.jetbrains.python.PyNames; import com.jetbrains.python.psi.*; /** @@ -61,50 +63,53 @@ public class AssignTargetAnnotator extends PyAnnotator { } private class ExprVisitor extends PyElementVisitor { - private Operation _op; + private Operation _op; + private final String DELETING_NONE = message("ANN.deleting.none"); + private final String ASSIGNMENT_TO_NONE = message("ANN.assign.to.none"); + private final String CANT_ASSIGN_TO_FUNCTION_CALL = message("ANN.cant.assign.to.call"); + private final String CANT_DELETE_FUNCTION_CALL = message("ANN.cant.delete.call"); - public ExprVisitor(Operation op) { + public ExprVisitor(Operation op) { _op = op; } @Override public void visitPyReferenceExpression(final PyReferenceExpression node) { String referencedName = node.getReferencedName(); - if (referencedName != null && referencedName.equals("None")) { + if (referencedName != null && referencedName.equals(PyNames.NONE)) { getHolder().createErrorAnnotation(node, - (_op == Operation.Delete) ? "deleting None" : "assignment to None"); + (_op == Operation.Delete) ? DELETING_NONE : ASSIGNMENT_TO_NONE); } } @Override public void visitPyTargetExpression(final PyTargetExpression node) { String targetName = node.getName(); - if (targetName != null && targetName.equals("None")) { + if (targetName != null && targetName.equals(PyNames.NONE)) { getHolder().createErrorAnnotation(node, - (_op == Operation.Delete) ? "deleting None" : "assignment to None"); + (_op == Operation.Delete) ? DELETING_NONE : ASSIGNMENT_TO_NONE); } } @Override public void visitPyCallExpression(final PyCallExpression node) { getHolder().createErrorAnnotation(node, - (_op == Operation.Delete) ? "can't delete function call" : "can't assign to function call"); + (_op == Operation.Delete) ? CANT_DELETE_FUNCTION_CALL : CANT_ASSIGN_TO_FUNCTION_CALL); } @Override public void visitPyGeneratorExpression(final PyGeneratorExpression node) { getHolder().createErrorAnnotation(node, - (_op == Operation.AugAssign) - ? "augmented assign to generator expression not possible" - : "assign to generator expression not possible"); + message(_op == Operation.AugAssign ? "ANN.cant.aug.assign.to.generator" : "ANN.cant.assign.to.generator") + ); } @Override public void visitPyBinaryExpression(final PyBinaryExpression node) { - getHolder().createErrorAnnotation(node, "can't assign to operator"); + getHolder().createErrorAnnotation(node, message("ANN.cant.assign.to.operator")); } @Override public void visitPyTupleExpression(final PyTupleExpression node) { if (node.getElements().length == 0) { - getHolder().createErrorAnnotation(node, "can't assign to ()"); + getHolder().createErrorAnnotation(node, message("ANN.cant.assign.to.parens")); } else if (_op == Operation.AugAssign) { - getHolder().createErrorAnnotation(node, "augmented assign to tuple literal or generator expression not possible"); + getHolder().createErrorAnnotation(node, message("ANN.cant.aug.assign.to.tuple.or.generator")); } else { node.acceptChildren(this); @@ -113,7 +118,7 @@ public class AssignTargetAnnotator extends PyAnnotator { @Override public void visitPyParenthesizedExpression(final PyParenthesizedExpression node) { if (_op == Operation.AugAssign) { - getHolder().createErrorAnnotation(node, "augmented assign to tuple literal or generator expression not possible"); + getHolder().createErrorAnnotation(node, message("ANN.cant.aug.assign.to.tuple.or.generator")); } else { node.acceptChildren(this); @@ -122,10 +127,10 @@ public class AssignTargetAnnotator extends PyAnnotator { @Override public void visitPyListLiteralExpression(final PyListLiteralExpression node) { if (node.getElements().length == 0) { - getHolder().createErrorAnnotation(node, "can't assign to []"); + getHolder().createErrorAnnotation(node, message("ANN.cant.assign.to.brackets")); } else if (_op == Operation.AugAssign) { - getHolder().createErrorAnnotation(node, "augmented assign to list literal or comprehension not possible"); + getHolder().createErrorAnnotation(node, message("ANN.cant.aug.assign.to.list.or.comprh")); } else { node.acceptChildren(this); @@ -134,9 +139,8 @@ public class AssignTargetAnnotator extends PyAnnotator { @Override public void visitPyListCompExpression(final PyListCompExpression node) { getHolder().createErrorAnnotation(node, - _op == Operation.AugAssign - ? "augmented assign to list comprehension not possible" - : "can't assign to list comprehension"); + message(_op == Operation.AugAssign ? "ANN.cant.aug.assign.to.comprh" : "ANN.cant.assign.to.comprh") + ); } public void visitPyNumericLiteralExpression(final PyNumericLiteralExpression node) { @@ -148,11 +152,11 @@ public class AssignTargetAnnotator extends PyAnnotator { } private void checkLiteral(PyExpression node) { - getHolder().createErrorAnnotation(node, "can't assign to literal"); + getHolder().createErrorAnnotation(node, message("ANN.cant.assign.to.literal")); } public void visitPyLambdaExpression(final PyLambdaExpression node) { - getHolder().createErrorAnnotation(node, "can't assign to lambda"); + getHolder().createErrorAnnotation(node, message("ANN.cant.assign.to.lambda")); } } } diff --git a/python/testSrc/com/jetbrains/python/PyMultiFileResolveTest.java b/python/testSrc/com/jetbrains/python/PyMultiFileResolveTest.java index 5b2b7fff6521..e55aea2ebf3e 100644 --- a/python/testSrc/com/jetbrains/python/PyMultiFileResolveTest.java +++ b/python/testSrc/com/jetbrains/python/PyMultiFileResolveTest.java @@ -6,9 +6,7 @@ import com.intellij.openapi.editor.Document; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; import com.intellij.testFramework.PsiTestUtil; -import com.jetbrains.python.psi.PyFile; -import com.jetbrains.python.psi.PyFunction; -import com.jetbrains.python.psi.PyTargetExpression; +import com.jetbrains.python.psi.*; /** * @author yole @@ -20,17 +18,41 @@ public class PyMultiFileResolveTest extends CodeInsightTestCase { assertEquals("ImportedFile.py", ((PyFile) element).getName()); } + /* public void testFromImport() throws Exception { PsiElement element = doResolve(); assertTrue(element instanceof PyFunction); assertEquals("func", ((PyFunction) element).getName()); } + */ + public void testFromImport() throws Exception { + ResolveResult[] results = doMultiResolve(); + assertTrue(results.length == 2); // func and import stmt + PsiElement func_elt = results[0].getElement(); + assertTrue("is PyFunction?", func_elt instanceof PyFunction); + assertEquals("named 'func'?", "func", ((PyFunction) func_elt).getName()); + PsiElement import_elt = results[1].getElement(); + assertTrue("is import?", import_elt instanceof PyImportElement); + } + + /* public void testFromImportStar() throws Exception { PsiElement element = doResolve(); assertTrue(element instanceof PyFunction); assertEquals("func", ((PyFunction) element).getName()); } + */ + + public void testFromImportStar() throws Exception { + ResolveResult[] results = doMultiResolve(); + assertTrue(results.length == 2); // func and import-* stmt + PsiElement func_elt = results[0].getElement(); + assertTrue("is PyFunction?", func_elt instanceof PyFunction); + assertEquals("named 'func'?", "func", ((PyFunction) func_elt).getName()); + PsiElement import_elt = results[1].getElement(); + assertTrue("is import?", import_elt instanceof PyStarImportElement); + } protected void _checkInitPyDir(PsiElement elt, String dirname) throws Exception { assertTrue(elt instanceof PyFile); @@ -79,15 +101,36 @@ public class PyMultiFileResolveTest extends CodeInsightTestCase { assertTrue(element instanceof PyFunction); } + /* public void testTransitiveImport() throws Exception { PsiElement element = doResolve(); assertTrue(element instanceof PyTargetExpression); } + */ + public void testTransitiveImport() throws Exception { + ResolveResult[] results = doMultiResolve(); + assertTrue(results.length == 2); // func and import stmt + PsiElement elt = results[0].getElement(); + assertTrue("is target?", elt instanceof PyTargetExpression); + } + + /* public void testResolveInPkg() throws Exception { PsiElement element = doResolve(); assertTrue(element instanceof PyFunction); } + */ + + public void testResolveInPkg() throws Exception { + ResolveResult[] results = doMultiResolve(); + assertTrue(results.length == 2); // func and import stmt + PsiElement func_elt = results[0].getElement(); + assertTrue("is PyFunction?", func_elt instanceof PyFunction); + assertEquals("named 'token'?", "token", ((PyFunction) func_elt).getName()); + PsiElement import_elt = results[1].getElement(); + assertTrue("is import?", import_elt instanceof PyImportElement); + } // Currently fails due to inadequate stubs public void testCircularImport() throws Exception { @@ -96,7 +139,7 @@ public class PyMultiFileResolveTest extends CodeInsightTestCase { } - private PsiElement doResolve() throws Exception { + private PsiFile prepareFile() throws Exception { String testName = getTestName(true); String fileName = getTestName(false) + ".py"; String root = PathManager.getHomePath() + "/plugins/python/testData/resolve/multiFile/" + testName; @@ -107,11 +150,23 @@ public class PyMultiFileResolveTest extends CodeInsightTestCase { VirtualFile sourceFile = rootDir.findChild(fileName); assert sourceFile != null; PsiFile psiFile = myPsiManager.findFile(sourceFile); + return psiFile; + } + + private PsiElement doResolve() throws Exception { + PsiFile psiFile = prepareFile(); int offset = findMarkerOffset(psiFile); final PsiReference ref = psiFile.findReferenceAt(offset); return ref.resolve(); } + private ResolveResult[] doMultiResolve() throws Exception { + PsiFile psiFile = prepareFile(); + int offset = findMarkerOffset(psiFile); + final PsiPolyVariantReference ref = (PsiPolyVariantReference)psiFile.findReferenceAt(offset); + return ref.multiResolve(false); + } + private int findMarkerOffset(final PsiFile psiFile) { Document document = PsiDocumentManager.getInstance(myProject).getDocument(psiFile); assert document != null;