Closes PY-30, PY-37, PY-41, and a subtle import resolution bug introduced by the previous commit

which current test suite fails to detect.
       Introduces a base class for list-comprehension-like classes, puts some life into generators.
This commit is contained in:
Dmitry Cheryasov
2008-07-31 10:36:12 +04:00
parent 8104001f7b
commit c411ef1b89
14 changed files with 274 additions and 151 deletions
@@ -0,0 +1,11 @@
package com.jetbrains.python.psi;
/**
* The "for" part of list comprehensions and generators.
* User: dcheryasov
* Date: Jul 31, 2008
*/
public interface ComprhForComponent {
PyExpression getIteratorVariable();
PyExpression getIteratedList();
}
@@ -0,0 +1,10 @@
package com.jetbrains.python.psi;
/**
* The "if" part of list comprehensions and generators.
* User: dcheryasov
* Date: Jul 31, 2008
*/
public interface ComprhIfComponent {
PyExpression getTest();
}
@@ -50,6 +50,10 @@ public interface NameDefiner extends PsiElement {
}
}
/**
* Helper iterator-over-array class.
* @param <T> array element type
*/
class ArrayIter<T> implements Iterator<T> {
protected int my_index;
@@ -78,6 +82,10 @@ public interface NameDefiner extends PsiElement {
}
}
/**
* Iterable that can only have one element.
* @param <T> element type
*/
class SingleIterable<T> implements Iterable<T> {
T content;
@@ -16,12 +16,15 @@
package com.jetbrains.python.psi;
import java.util.List;
/**
* Created by IntelliJ IDEA.
* Generator expression PSI.
* User: yole
* Date: 05.06.2005
* Time: 10:15:46
* To change this template use File | Settings | File Templates.
*/
public interface PyGeneratorExpression extends PyExpression {
public interface PyGeneratorExpression extends PyExpression, NameDefiner {
PyExpression getResultExpression();
List<ComprhForComponent> getForComponents();
List<ComprhIfComponent> getIfComponents();
}
@@ -19,25 +19,13 @@ package com.jetbrains.python.psi;
import java.util.List;
/**
* Created by IntelliJ IDEA.
* List comprehension PSI.
* User: yole
* Date: 31.05.2005
* Time: 23:32:57
* To change this template use File | Settings | File Templates.
*/
public interface PyListCompExpression extends PyExpression {
PyExpression getResultExpression();
List<ListCompComponent> getComponents();
public interface PyListCompExpression extends PyExpression, NameDefiner {
PyExpression getResultExpression();
List<ComprhForComponent> getForComponents();
List<ComprhIfComponent> getIfComponents();
interface ListCompComponent {
}
interface IfComponent extends ListCompComponent {
PyExpression getTest();
}
interface ForComponent extends ListCompComponent {
PyExpression getIteratorVariable();
PyExpression getIteratedList();
}
}
@@ -27,6 +27,7 @@ import com.intellij.psi.TokenType;
import com.intellij.util.IncorrectOperationException;
import com.jetbrains.python.PythonLanguage;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Comparator;
@@ -192,4 +193,45 @@ public class PyUtil {
private static boolean isWhitespace(ASTNode node) {
return node != null && node.getElementType().equals(TokenType.WHITE_SPACE);
}
protected static <T extends PyElement> List<T> _unfoldParenExprs(T[] targets, List<T> receiver) {
// NOTE: this proliferation of instanceofs is not very beautiful. Maybe rewrite using a visitor.
for (T 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((T[])tupex.getElements(), receiver);
}
else receiver.add(exp);
}
else receiver.add(exp);
}
return receiver;
}
// Poor man's catamorhpism :)
/**
* Flattens the representation of every element in targets, and puts all results together.
* Elements of every tuple nested in target item are brought to the top level: (a, (b, (c, d))) -> (a, b, c, d)
* Typical usage: <code>flattenedParens(some_tuple.getExpressions())</code>.
* @param targets target elements.
* @return the list of flattened expressions.
*/
@NotNull
public static <T extends PyElement> List<T> flattenedParens(T[] targets) {
return _unfoldParenExprs(targets, new ArrayList<T>(targets.length));
}
// Poor man's filter
// TODO: move to a saner place
public static boolean instanceOf(Object obj, Class... possibleClasses) {
for (Class cls : possibleClasses) {
if (cls.isInstance(obj)) return true;
}
return false;
}
}
@@ -27,9 +27,6 @@ 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
@@ -101,27 +98,11 @@ public class PyAssignmentStatementImpl extends PyElementImpl implements PyAssign
return true;
}
protected static List<PyElement> _unfoldParenExprs(PyElement[] targets, List<PyElement> receiver) {
// NOTE: this proliferation of instanceofs is not very beautiful. Maybe rewrite using a visitor.
for (PyElement exp : targets) {
if (exp instanceof PyParenthesizedExpression) {
final PyParenthesizedExpression parex = (PyParenthesizedExpression)exp;
PyExpression cont = parex.getContainedExpression();
if (cont instanceof PyTupleExpression) {
final PyTupleExpression tupex = (PyTupleExpression)cont;
_unfoldParenExprs(tupex.getElements(), receiver);
}
else receiver.add(exp);
}
else receiver.add(exp);
}
return receiver;
}
@NotNull
public Iterable<PyElement> iterateNames() {
PyExpression[] targets = getTargets();
return _unfoldParenExprs(targets, new ArrayList<PyElement>(targets.length));
PyElement[] targets = getTargets();
// return _unfoldParenExprs(targets, new ArrayList<PyElement>(targets.length));
return PyUtil.flattenedParens(targets);
}
public PyElement getElementNamed(final String the_name) {
@@ -0,0 +1,118 @@
package com.jetbrains.python.psi.impl;
import com.intellij.lang.ASTNode;
import com.intellij.psi.PsiElement;
import com.intellij.psi.tree.IElementType;
import com.jetbrains.python.PyElementTypes;
import com.jetbrains.python.PyTokenTypes;
import com.jetbrains.python.psi.*;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
/**
* Comprehension-like element base, for list comps ang generators.
* User: dcheryasov
* Date: Jul 31, 2008
*/
public class PyComprehensionElementImpl extends PyElementImpl implements NameDefiner {
public PyComprehensionElementImpl(ASTNode astNode) {
super(astNode);
}
/**
* In "[x+1 for x in (1,2,3)]" result expression is "x+1".
* @return result expression.
*/
public PyExpression getResultExpression() {
ASTNode[] exprs = getNode().getChildren(PyElementTypes.EXPRESSIONS);
return exprs.length == 0 ? null : (PyExpression)exprs[0].getPsi();
}
/**
* In "[x+1 for x in (1,2,3)]" a "for component" is "x".
* @return all "for components"
*/
public List<ComprhForComponent> getForComponents() {
ASTNode node = getNode().getFirstChildNode();
List<ComprhForComponent> list = new ArrayList<ComprhForComponent>(5);
while (node != null) {
IElementType type = node.getElementType();
ASTNode next = getNextExpression(node);
if (next == null) break;
if (type == PyTokenTypes.FOR_KEYWORD) {
ASTNode next2 = getNextExpression(next);
if (next2 == null) break;
final PyExpression variable = (PyExpression)next.getPsi();
final PyExpression iterated = (PyExpression)next2.getPsi();
list.add(new ComprhForComponent() {
public PyExpression getIteratorVariable() {
return variable;
}
public PyExpression getIteratedList() {
return iterated;
}
});
}
node = node.getTreeNext();
}
return list;
}
public List<ComprhIfComponent> getIfComponents() {
ASTNode node = getNode().getFirstChildNode();
List<ComprhIfComponent> list = new ArrayList<ComprhIfComponent>(5);
while (node != null) {
IElementType type = node.getElementType();
ASTNode next = getNextExpression(node);
if (next == null) break;
if (type == PyTokenTypes.IF_KEYWORD) {
final PyExpression test = (PyExpression)next.getPsi();
list.add(new ComprhIfComponent() {
public PyExpression getTest() {
return test;
}
});
}
node = node.getTreeNext();
}
return list;
}
private static ASTNode getNextExpression(ASTNode after) {
ASTNode node = after;
do {
node = node.getTreeNext();
}
while (node != null && !PyElementTypes.EXPRESSIONS.contains(node.getElementType()));
return node;
}
/**
* In "[x+1 for x in (1,2,3) if x > 2]" an "if component" is "x > 2".
* @return all "if components"
*/
@NotNull
public Iterable<PyElement> iterateNames() {
// extract whatever names are defined in "for" components
List<ComprhForComponent> fors = getForComponents();
PyElement[] for_targets = new PyElement[fors.size()];
int i = 0;
for (ComprhForComponent for_comp : fors) {
for_targets[i] = for_comp.getIteratorVariable();
i += 1;
}
List<PyElement> name_refs = PyUtil.flattenedParens(for_targets);
return name_refs;
}
public PsiElement getElementNamed(final String the_name) {
return IterHelper.findName(iterateNames(), the_name);
}
public boolean mustResolveOutside() {
return false;
}
}
@@ -17,9 +17,12 @@
package com.jetbrains.python.psi.impl;
import com.intellij.lang.ASTNode;
import com.jetbrains.python.psi.PyElementVisitor;
import com.jetbrains.python.psi.PyGeneratorExpression;
import com.intellij.psi.PsiElement;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.types.PyType;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* Created by IntelliJ IDEA.
@@ -28,7 +31,7 @@ import com.jetbrains.python.psi.types.PyType;
* Time: 10:16:07
* To change this template use File | Settings | File Templates.
*/
public class PyGeneratorExpressionImpl extends PyElementImpl implements PyGeneratorExpression {
public class PyGeneratorExpressionImpl extends PyComprehensionElementImpl implements PyGeneratorExpression {
public PyGeneratorExpressionImpl(ASTNode astNode) {
super(astNode);
}
@@ -41,4 +44,27 @@ public class PyGeneratorExpressionImpl extends PyElementImpl implements PyGenera
public PyType getType() {
return null;
}
@NotNull
public Iterable<PyElement> iterateNames() {
// extract whatever names are defined in "for" components
List<ComprhForComponent> fors = getForComponents();
PyElement[] for_targets = new PyElement[fors.size()];
int i = 0;
for (ComprhForComponent for_comp : fors) {
for_targets[i] = for_comp.getIteratorVariable();
i += 1;
}
List<PyElement> name_refs = PyUtil.flattenedParens(for_targets);
return name_refs;
}
public PsiElement getElementNamed(final String the_name) {
return IterHelper.findName(iterateNames(), the_name);
}
public boolean mustResolveOutside() {
return false;
}
}
@@ -29,11 +29,9 @@ import org.jetbrains.annotations.Nullable;
import java.util.List;
/**
* Created by IntelliJ IDEA.
* The "import foo" or "import foo as bar" parts.
* User: yole
* Date: 02.06.2005
* Time: 22:22:35
* To change this template use File | Settings | File Templates.
*/
public class PyImportElementImpl extends PyElementImpl implements PyImportElement {
public PyImportElementImpl(ASTNode astNode) {
@@ -104,23 +102,11 @@ public class PyImportElementImpl extends PyElementImpl implements PyImportElemen
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);
}
PyReferenceExpression import_ref = getImportReference(); // = most qualified import name: "z" for "import x.y.z"
if (getAsName() == null) { // the match was not by target expr of "import ... as foo"
if (named_elt instanceof PyReferenceExpression) import_ref = (PyReferenceExpression)named_elt; // [part of] import ref matched
else return null; // I wonder what could have matched there?
}
// else return ResolveImportUtil.resolvePythonImport2(import_ref, null);
else return ResolveImportUtil.resolveImportReference(import_ref);
*/
return ResolveImportUtil.resolveImportReference(import_ref);
}
// no element of this name
@@ -20,18 +20,11 @@ import com.intellij.lang.ASTNode;
import com.intellij.psi.PsiElement;
import com.intellij.psi.ResolveState;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.tree.IElementType;
import org.jetbrains.annotations.NotNull;
import com.jetbrains.python.PyElementTypes;
import com.jetbrains.python.PyTokenTypes;
import com.jetbrains.python.psi.PsiCached;
import com.jetbrains.python.psi.ComprhForComponent;
import com.jetbrains.python.psi.PyElementVisitor;
import com.jetbrains.python.psi.PyExpression;
import com.jetbrains.python.psi.PyListCompExpression;
import com.jetbrains.python.psi.types.PyType;
import java.util.ArrayList;
import java.util.List;
import org.jetbrains.annotations.NotNull;
/**
* Created by IntelliJ IDEA.
@@ -40,7 +33,7 @@ import java.util.List;
* Time: 23:33:16
* To change this template use File | Settings | File Templates.
*/
public class PyListCompExpressionImpl extends PyElementImpl implements PyListCompExpression {
public class PyListCompExpressionImpl extends PyComprehensionElementImpl implements PyListCompExpression {
public PyListCompExpressionImpl(ASTNode astNode) {
super(astNode);
}
@@ -50,68 +43,16 @@ public class PyListCompExpressionImpl extends PyElementImpl implements PyListCom
pyVisitor.visitPyListCompExpression(this);
}
@PsiCached
public PyExpression getResultExpression() {
ASTNode[] exprs = getNode().getChildren(PyElementTypes.EXPRESSIONS);
return exprs.length == 0 ? null : (PyExpression)exprs[0].getPsi();
}
@PsiCached
public List<ListCompComponent> getComponents() {
ASTNode node = getNode().getFirstChildNode();
List<ListCompComponent> list = new ArrayList<ListCompComponent>(5);
while (node != null) {
IElementType type = node.getElementType();
ASTNode next = getNextExpression(node);
if (next == null) break;
if (type == PyTokenTypes.IF_KEYWORD) {
final PyExpression test = (PyExpression)next.getPsi();
list.add(new IfComponent() {
public PyExpression getTest() {
return test;
}
});
}
else if (type == PyTokenTypes.FOR_KEYWORD) {
ASTNode next2 = getNextExpression(next);
if (next2 == null) break;
final PyExpression variable = (PyExpression)next.getPsi();
final PyExpression iterated = (PyExpression)next2.getPsi();
list.add(new ForComponent() {
public PyExpression getIteratorVariable() {
return variable;
}
public PyExpression getIteratedList() {
return iterated;
}
});
}
node = node.getTreeNext();
}
return list;
}
private static ASTNode getNextExpression(ASTNode after) {
ASTNode node = after;
do {
node = node.getTreeNext();
}
while (node != null && !PyElementTypes.EXPRESSIONS.contains(node.getElementType()));
return node;
}
public boolean processDeclarations(@NotNull PsiScopeProcessor processor,
@NotNull ResolveState substitutor,
PsiElement lastParent,
@NotNull PsiElement place) {
for (ListCompComponent component : getComponents()) {
if (component instanceof ForComponent) {
for (ComprhForComponent component : getForComponents()) {
if (component != null) {
//TODO: this needs to restrict resolution based on nesting
// for example, this is not valid (the i in the first for should not resolve):
// x for x in i for i in y
ForComponent forComponent = (ForComponent)component;
if (!forComponent.getIteratorVariable().processDeclarations(processor, substitutor, null, place)) return false;
if (!component.getIteratorVariable().processDeclarations(processor, substitutor, null, place)) return false;
}
}
return true;
@@ -120,4 +61,5 @@ public class PyListCompExpressionImpl extends PyElementImpl implements PyListCom
public PyType getType() {
return null;
}
}
@@ -144,8 +144,14 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere
// 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) && (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
}
}
if (ret == null) {
// ...as a part of current module
PyType otype = PyBuiltinCache.getInstance(this.getProject()).getObjectType(); // "object" as a closest kin to "module"
@@ -12,20 +12,13 @@ import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiComment;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiReference;
import com.intellij.psi.*;
import com.intellij.psi.search.FilenameIndex;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.IncorrectOperationException;
import com.jetbrains.python.PythonLanguage;
import com.jetbrains.python.psi.PyExpression;
import com.jetbrains.python.psi.PyFromImportStatement;
import com.jetbrains.python.psi.PyImportStatement;
import com.jetbrains.python.psi.PyReferenceExpression;
import com.jetbrains.python.psi.types.PyModuleType;
import com.jetbrains.python.psi.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -64,7 +57,7 @@ public class AddImportAction implements HintAction, QuestionAction {
final PsiElement element = myReference.getElement();
if (myReference instanceof PyReferenceExpression) {
final PyExpression qual = ((PyReferenceExpression)myReference).getQualifier();
if ((qual != null) && (qual.getType() instanceof PyModuleType)) return false; // don't propose to import unknown fields, etc
if ((qual != null) /*&& (qual.getType() instanceof PyModuleType)*/) return false; // don't propose to import unknown fields, etc
}
if (PsiTreeUtil.getParentOfType(element, PyImportStatement.class) != null) return false;
if (PsiTreeUtil.getParentOfType(element, PyFromImportStatement.class) != null) return false;
@@ -83,9 +76,11 @@ public class AddImportAction implements HintAction, QuestionAction {
final String referenceName = getRefName();
final PsiFile[] files = getRefFiles(referenceName);
if (files.length == 1) {
final PyImportStatement importNodeToInsert = PythonLanguage.getInstance().getElementGenerator().createImportStatementFromText(myProject, "import " + referenceName);
final PyImportStatement importNodeToInsert = PythonLanguage.getInstance().getElementGenerator().createImportStatementFromText(
myProject, "import " + referenceName + "\n\n"
);
try {
file.addBefore(importNodeToInsert, getFirstNonComment(file));
file.addAfter(importNodeToInsert, getInsertPosition(file));
}
catch (IncorrectOperationException e) {
LOG.error(e);
@@ -95,11 +90,20 @@ public class AddImportAction implements HintAction, QuestionAction {
});
}
private static PsiElement getFirstNonComment(final PsiFile file) {
final PsiElement firstChild = file.getFirstChild();
LOG.assertTrue(firstChild != null);
final PsiElement element = PsiTreeUtil.skipSiblingsForward(firstChild, PsiComment.class);
return element != null ? element : firstChild;
private static PsiElement getInsertPosition(final PsiFile file) {
PsiElement feeler = file.getFirstChild();
LOG.assertTrue(feeler != null);
// skip initial comments and whitespace and try to get just below the last import stmt
PsiElement seeker = feeler;
do {
if (PyUtil.instanceOf(feeler, PyImportStatement.class, PyFromImportStatement.class)) {
seeker = feeler;
feeler = feeler.getNextSibling();
}
else if (PyUtil.instanceOf(feeler, PsiWhiteSpace.class, PsiComment.class)) feeler = feeler.getNextSibling();
else break; // some other statement, stop
} while (feeler != null);
return seeker;
}
public boolean startInWriteAction() {
@@ -21,11 +21,9 @@ import com.jetbrains.python.psi.PyParameter;
import com.jetbrains.python.psi.PyParameterList;
/**
* Created by IntelliJ IDEA.
* Checks for anomalies in parameter lists of function declarations.
* User: yole
* Date: 12.06.2005
* Time: 11:05:22
* To change this template use File | Settings | File Templates.
*/
public class ParameterListAnnotator extends PyAnnotator {
@Override public void visitPyParameterList(final PyParameterList node) {