Adds multi-resolve to imported names; fixes PY-120.

This commit is contained in:
Dmitry Cheryasov
2008-12-05 17:24:13 +03:00
parent c7ca892aaa
commit 80d7945468
9 changed files with 352 additions and 74 deletions
@@ -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
@@ -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<String> mySeen;
/*private Set<String> mySeen;*/
private List<NameDefiner> myDefiners;
public ResolveProcessor(final String name) {
myName = name;
mySeen = new HashSet<String>();
/*mySeen = new HashSet<String>();*/
myDefiners = new ArrayList<NameDefiner>(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<String> 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 List<NameDefiner>getDefiners() {
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;
}
}
@@ -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;
}
@@ -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();
}
@@ -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("<none>");
}
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<PyElement> iterateNames() {
PyElement ret = getAsName();
@@ -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<RatedResolveResult> {
// 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<PsiElement> 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<RatedResolveResult> resolveInner() {
//List<PsiElement> ret = new ArrayList<PsiElement>();
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<PyQualifiedExpression> 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<PyExpression> collectAssignedAttributes(PyQualifiedExpression qualifier) {
List<PyQualifiedExpression> 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<RatedResolveResult> targets = resolveInner();
if (targets.size() == 0) return ResolveResult.EMPTY_ARRAY;
List<ResolveResult> ret = new ArrayList<ResolveResult>();
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<RatedResolveResult> ret = new SortedList<RatedResolveResult>(new Comparator<RatedResolveResult>() {
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;
}
@@ -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;
}
};
}
}
@@ -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"));
}
}
}
@@ -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;