Adds a kind of auto-import for names defined in already imported files. Hopefully fixes PY-119.

Also refactors resolve processors and adds a bunch of small code improvements.
This commit is contained in:
Dmitry Cheryasov
2009-04-22 06:15:45 +04:00
parent f74e85b4bb
commit 11be0689a1
39 changed files with 1338 additions and 716 deletions
@@ -2,7 +2,7 @@ package com.jetbrains.python.psi.impl;
import com.intellij.psi.*;
import com.jetbrains.python.psi.PyReferenceExpression;
import com.jetbrains.python.psi.PyResolveUtil;
import com.jetbrains.python.psi.resolve.VariantsProcessor;
import com.jetbrains.python.psi.types.PyType;
/**
@@ -26,7 +26,7 @@ public class PyJavaClassType implements PyType {
}
public Object[] getCompletionVariants(final PyReferenceExpression referenceExpression) {
final PyResolveUtil.VariantsProcessor processor = new PyResolveUtil.VariantsProcessor();
final VariantsProcessor processor = new VariantsProcessor();
myClass.processDeclarations(processor, ResolveState.initial(), null, referenceExpression);
return processor.getResult();
}
@@ -7,8 +7,11 @@ GNAME.item=item
### Actions: ACT ###
ACT.FAMILY.import=import
# AddImport
# Actions and associated commands
ACT.NAME.add.import=Add import
ACT.NAME.use.import=Find in imported modules
ACT.CMD.use.import=Use an imported module
### Quick fixes ###
QFIX.add.parameter.self=Add parameter 'self'
@@ -133,4 +136,5 @@ runcfg.labels.interpreter=&Interpreter:
runcfg.labels.interpreter_options=Interpreter &options:
runcfg.labels.working_directory=&Working directory:
runcfg.captions.script_parameters_dialog=Enter script parameters
runcfg.captions.interpreter_options_dialog=Enter interpreter options
runcfg.captions.interpreter_options_dialog=Enter interpreter options
ACT.qualify.with.module=Qualify with module
@@ -15,13 +15,14 @@ import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.IncorrectOperationException;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.PythonLanguage;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.ResolveImportUtil;
import com.jetbrains.python.psi.resolve.PyResolveUtil;
import com.jetbrains.python.psi.resolve.PyClassScopeProcessor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -67,7 +68,7 @@ public class AddImportAction implements HintAction, QuestionAction, LocalQuickFi
/**
* Finds first import statement that imports given name.
*/
private static class ImportLookupProcessor implements PsiScopeProcessor {
private static class ImportLookupProcessor implements PyClassScopeProcessor {
String name;
PsiElement found;
@@ -101,6 +102,11 @@ public class AddImportAction implements HintAction, QuestionAction, LocalQuickFi
public PsiElement getFound() {
return found;
}
@NotNull
public Class[] getPossibleTargets() {
return NAME_DEFINER_ONLY;
}
}
/*
@@ -0,0 +1,222 @@
package com.jetbrains.python.actions;
import com.intellij.codeInsight.hint.QuestionAction;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.colors.EditorColorsScheme;
import com.intellij.openapi.ui.popup.PopupChooserBuilder;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.ui.SimpleColoredComponent;
import com.intellij.ui.SimpleTextAttributes;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.PythonLanguage;
import com.jetbrains.python.psi.*;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.awt.*;
import java.util.List;
/**
* Turns an unqualified unresolved identifier into qualifed and resolvable.
* User: dcheryasov
* Date: Apr 15, 2009 6:24:48 PM
*/
public class ImportFromExistingAction implements QuestionAction {
PyElement myTarget;
List<Pair<PyImportElement, PsiElement>> mySources; // list of <import, imported_item>
Editor myEditor;
String myName;
/**
* @param target element to become qualified as imported.
* @param sources clauses of import to be used.
*/
public ImportFromExistingAction(@NotNull PyElement target, @NotNull List<Pair<PyImportElement, PsiElement>> sources, String name, Editor editor) {
mySources = sources;
myTarget = target;
myEditor = editor;
myName = name;
}
/**
* Alters either target (by qualifying a name) or source (by explicitly importing the name).
* @return true if action succeeded
*/
public boolean execute() {
// check if the tree is sane
PsiDocumentManager.getInstance(myTarget.getProject()).commitAllDocuments();
if (!myTarget.isValid()) return false;
if ((myTarget instanceof PyQualifiedExpression) && ((((PyQualifiedExpression)myTarget).getQualifier() != null))) return false; // we cannot be qualified
for (Pair<PyImportElement, PsiElement> src : mySources) {
if (!src.getFirst().isValid()) return false;
if (!src.getSecond().isValid()) return false;
}
// act
if (mySources.size() > 1) {
selectSourceAndDo();
}
else doWriteAction(mySources.get(0).getFirst());
return true;
}
private void selectSourceAndDo() {
// GUI part
QualifiedHolder[] items = new QualifiedHolder[mySources.size()];
int i = 0;
for (Pair<PyImportElement, PsiElement> pair : mySources) {
items[i] = new QualifiedHolder(pair.getFirst(), pair.getSecond(), myName);
i += 1;
}
final JList list = new JList(items);
list.setCellRenderer(new CellRenderer());
Runnable runnable = new Runnable() {
public void run() {
int index = list.getSelectedIndex();
if (index < 0) return;
PsiDocumentManager.getInstance(myTarget.getProject()).commitAllDocuments();
doWriteAction(mySources.get(index).getFirst());
}
};
new PopupChooserBuilder(list).
setTitle(PyBundle.message("ACT.qualify.with.module")).
setItemChoosenCallback(runnable).
createPopup().
showInBestPositionFor(myEditor)
;
}
private void doIt(final PyImportElement src) {
// did user choose 'import' or 'from import'?
PsiElement parent = src.getParent();
if (parent instanceof PyFromImportStatement) {
// add another import element right after the one we got
final PyElementGenerator gen = PythonLanguage.getInstance().getElementGenerator();
final Project project = myTarget.getProject();
PsiElement new_elt = gen.
createFromText(project, PyImportElement.class, "from foo import " + myName, new int[]{0,6})
;
PyUtil.addListNode(parent, new_elt, null, false, true);
}
else { // just 'import'
// all we need is to qualify our target
myTarget.replace(
PythonLanguage.getInstance().
getElementGenerator().
createExpressionFromText(myTarget.getProject(), src.getVisibleName()+ "." + myName)
);
}
}
private void doWriteAction(final PyImportElement src) {
CommandProcessor.getInstance().executeCommand(src.getProject(), new Runnable() {
public void run() {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
public void run() {
doIt(src);
}
});
}
}, PyBundle.message("ACT.CMD.use.import"), null);
}
// items to store in list
private static class QualifiedHolder {
final PyImportElement mySrc;
final PsiElement myItem;
final String myName;
public QualifiedHolder(PyImportElement src, PsiElement item, String name) {
mySrc = src;
myItem = item;
myName = name;
}
public Icon getIcon() {
return myItem.getIcon(0);
}
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
PsiElement parent = mySrc.getParent();
if (parent instanceof PyFromImportStatement) {
sb.append(myName);
}
else {
sb.append(mySrc.getVisibleName()).append(".").append(myName);
}
if (myItem instanceof PyFunction) {
sb.append("(");
// below: ", ".join([x.getRepr(False) for x in getParameters()])
PyParameter[] params = ((PyFunction)myItem).getParameterList().getParameters();
String[] param_reprs = new String[params.length];
for (int i=0; i < params.length; i += 1) param_reprs[i] = params[i].getRepr(false);
PyUtil.joinSubarray(param_reprs, 0, params.length, ", ", sb);
sb.append(")");
}
else if (myItem instanceof PyClass) {
PyClass[] supers = ((PyClass)myItem).getSuperClasses();
if (supers.length > 0) {
sb.append("(");
// ", ".join(x.getName() for x in getSuperClasses())
String[] super_names = new String[supers.length];
for (int i=0; i < supers.length; i += 1) super_names[i] = supers[i].getName();
PyUtil.joinSubarray(super_names, 0, supers.length, ", ", sb);
sb.append(")");
}
}
if (parent instanceof PyFromImportStatement) {
sb.append(" from ").append(((PyFromImportStatement)parent).getImportSource().getReferencedName());
}
return sb.toString();
}
}
// Stolen from FQNameCellRenderer
private static class CellRenderer extends SimpleColoredComponent implements ListCellRenderer {
private final Font FONT;
public CellRenderer() {
EditorColorsScheme scheme = EditorColorsManager.getInstance().getGlobalScheme();
FONT = new Font(scheme.getEditorFontName(), Font.PLAIN, scheme.getEditorFontSize());
setOpaque(true);
}
// value is a QualifiedHolder
public Component getListCellRendererComponent(
JList list,
Object value, // expected to be
int index,
boolean isSelected,
boolean cellHasFocus
){
clear();
QualifiedHolder item = (QualifiedHolder)value;
setIcon(item.getIcon());
String item_name = item.toString();
append(item_name, SimpleTextAttributes.REGULAR_ATTRIBUTES);
setFont(FONT);
if (isSelected) {
setBackground(list.getSelectionBackground());
setForeground(list.getSelectionForeground());
}
else {
setBackground(list.getBackground());
setForeground(list.getForeground());
}
return this;
}
}
}
@@ -0,0 +1,104 @@
package com.jetbrains.python.actions;
import com.intellij.codeInsight.CodeInsightUtilBase;
import com.intellij.codeInsight.daemon.impl.ShowAutoImportPass;
import com.intellij.codeInsight.hint.HintManager;
import com.intellij.codeInspection.HintAction;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.util.IncorrectOperationException;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.psi.PyElement;
import com.jetbrains.python.psi.PyImportElement;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
/**
* Handles cases when an unresolved name may be imported from one of existing imported modules.
* The object contains a list of modules from which given name might be imported.
* User: dcheryasov
* Date: Apr 15, 2009 2:06:25 PM
*/
public class ImportFromExistingFix implements HintAction {
PyElement myNode;
List<Pair<PyImportElement, PsiElement>> myImports; // from where and what to import
String myName;
/**
* Creates a new, empty fix object.
* @param node to which the fix applies.
*/
public ImportFromExistingFix(PyElement node, String name) {
myNode = node;
myImports = new ArrayList<Pair<PyImportElement, PsiElement>>();
myName = name;
}
/**
* Creates a mew fix object with one import variant.
* @param node to which the fix applies.
* @param source from which the name is importable.
* @param name
*/
public ImportFromExistingFix(PyElement node, PyImportElement source, PsiElement item, String name) {
this(node, name);
addImport(source, item);
}
/**
* Adds another import source.
* @param source an import statement from which the name is importable.
*/
public void addImport(PyImportElement source, PsiElement item) {
myImports.add(new Pair<PyImportElement, PsiElement>(source, item));
}
@NotNull
public String getText() {
return PyBundle.message("ACT.NAME.use.import");
}
@NotNull
public String getFamilyName() {
return PyBundle.message("ACT.FAMILY.import");
}
public boolean showHint(Editor editor) {
if (myNode == null || !myNode.isValid() || myNode.getName() == null || myImports.size() <= 0) {
return false; // TODO: also return false if an on-the-fly unambiguous fix is possible?
}
final String message = ShowAutoImportPass.getMessage(
myImports.size() > 1,
myImports.get(0).getFirst().getVisibleName()+"."+myNode.getName()
);
final ImportFromExistingAction action = new ImportFromExistingAction(myNode, myImports, myName, editor);
HintManager.getInstance().showQuestionHint(
editor, message,
myNode.getTextOffset(),
myNode.getTextRange().getEndOffset(), action);
return true;
}
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
return myNode != null && myNode.isValid() && myImports.size() > 0;
}
public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
// make sure file is committed, writable, etc
if (!CodeInsightUtilBase.prepareFileForWrite(file)) return;
// act
ImportFromExistingAction action = new ImportFromExistingAction(myNode, myImports, myName, editor);
action.execute(); // assume that action runs in WriteAction on its own behalf
}
public boolean startInWriteAction() {
return false; // multiple variants may make us show a menu
}
}
@@ -11,6 +11,7 @@ import com.jetbrains.python.actions.AddSelfQuickFix;
import com.jetbrains.python.actions.RenameToSelfQuickFix;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.PyDecorator;
import com.jetbrains.python.psi.resolve.PyResolveUtil;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
@@ -8,6 +8,8 @@ import com.intellij.psi.PsiElementVisitor;
import com.jetbrains.python.PyTokenTypes;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.resolve.PyResolveUtil;
import com.jetbrains.python.psi.resolve.ResolveProcessor;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
@@ -68,7 +70,7 @@ public class PyRedeclarationInspection extends LocalInspectionTool {
private void _checkAbove(PyElement node, String kind) {
String name = node.getName();
if (name != null) {
PyResolveUtil.ResolveProcessor proc = new PyResolveUtil.ResolveProcessor(node.getName());
ResolveProcessor proc = new ResolveProcessor(node.getName());
PyResolveUtil.treeCrawlUp(proc, node);
PsiElement found = proc.getResult();
// TODO: check if the redefined name is used somehow
@@ -1,10 +1,7 @@
package com.jetbrains.python.inspections;
import com.intellij.codeHighlighting.HighlightDisplayLevel;
import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemHighlightType;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.codeInspection.*;
import com.intellij.lang.annotation.HighlightSeverity;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
@@ -15,14 +12,20 @@ import com.jetbrains.python.PyBundle;
import com.jetbrains.python.actions.AddFieldQuickFix;
import com.jetbrains.python.actions.AddImportAction;
import com.jetbrains.python.actions.AddMethodQuickFix;
import com.jetbrains.python.actions.ImportFromExistingFix;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyBuiltinCache;
import com.jetbrains.python.psi.resolve.CollectProcessor;
import com.jetbrains.python.psi.resolve.PyResolveUtil;
import com.jetbrains.python.psi.types.PyClassType;
import com.jetbrains.python.psi.types.PyModuleType;
import com.jetbrains.python.psi.types.PyNoneType;
import com.jetbrains.python.psi.types.PyType;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
/**
* Marks references that fail to resolve.
@@ -69,6 +72,62 @@ public class PyUnresolvedReferencesInspection extends LocalInspectionTool {
super(holder);
}
@Nullable
static HintAction proposeImportFixes(final PyElement node, String ref_text) {
boolean worthy_fix = false;
ImportFromExistingFix fix = null;
// maybe the name is importable via some exisitng 'import foo' statement, and only needs a qualifier.
// walk up collecting all such statements and analyzing
CollectProcessor import_prc = new CollectProcessor(PyImportStatement.class);
PyResolveUtil.treeCrawlUp(import_prc, node);
List<PsiElement> result = import_prc.getResult();
if (result.size() > 0) {
fix = new ImportFromExistingFix(node, ref_text); // initially it is almost as lightweight as a plain list
for (PsiElement stmt : import_prc.getResult()) {
for (PyImportElement ielt : ((PyImportStatement)stmt).getImportElements()) {
final PyReferenceExpression src = ielt.getImportReference();
if (src != null) {
PsiElement dst = src.resolve();
if (dst instanceof PyFile) {
PsiElement res = ((PyFile)dst).findExportedName(ref_text);
if (res != null) {
fix.addImport(ielt, res);
worthy_fix = true;
}
}
}
}
}
}
// maybe the name is importable via some exisitng 'from foo import ...' statement, and only needs another name to be imported.
// walk up collecting all such statements and analyzing
CollectProcessor from_import_prc = new CollectProcessor(PyFromImportStatement.class);
PyResolveUtil.treeCrawlUp(from_import_prc, node);
result = from_import_prc.getResult();
if (result.size() > 0) {
if (fix == null) fix = new ImportFromExistingFix(node, ref_text); // it might have been created in the previous scan, or not.
for (PsiElement stmt : from_import_prc.getResult()) {
PyFromImportStatement from_stmt = (PyFromImportStatement)stmt;
PyImportElement[] ielts = from_stmt.getImportElements();
if (ielts != null && ielts.length > 0) {
final PyReferenceExpression src = from_stmt.getImportSource();
if (src != null) {
PsiElement dst = src.resolve();
if (dst instanceof PyFile) {
PsiElement res = ((PyFile)dst).findExportedName(ref_text);
if (res != null) {
fix.addImport(ielts[ielts.length-1], res); // last element; action expects to add to tail
worthy_fix = true;
}
}
}
}
}
}
if (worthy_fix) return fix;
else return null;
}
@Override
public void visitPyElement(final PyElement node) {
super.visitPyElement(node); //To change body of overridden methods use File | Settings | File Templates.
@@ -88,10 +147,11 @@ public class PyUnresolvedReferencesInspection extends LocalInspectionTool {
unresolved = (reference.resolve() == null);
}
if (unresolved) {
StringBuffer description_buf = new StringBuffer("");
StringBuffer description_buf = new StringBuffer(""); // TODO: clear description_buf logic. maybe a flag is needed instead.
String text = reference.getElement().getText();
String ref_text = reference.getRangeInElement().substring(text); // text of the part we're working with
LocalQuickFix action = null;
HintAction hint_action = null;
if (ref_text.length() <= 0) return; // empty text, nothing to highlight
if (reference instanceof PyReferenceExpression) {
PyReferenceExpression refex = (PyReferenceExpression)reference;
@@ -102,15 +162,20 @@ public class PyUnresolvedReferencesInspection extends LocalInspectionTool {
}
// unqualified:
// may be module's
if ((new PyModuleType(node.getContainingFile())).getPossibleInstanceMembers().contains(refname)) continue;
// may be try: import; not an error not to resolve
if ((PsiTreeUtil.getParentOfType(
PsiTreeUtil.getParentOfType(node, PyImportElement.class), PyTryExceptStatement.class, PyIfStatement.class) != null)
) {
if (PyModuleType.getPossibleInstanceMembers().contains(refname)) continue;
// may be a "try: import ..."; not an error not to resolve
if ((
PsiTreeUtil.getParentOfType(
PsiTreeUtil.getParentOfType(node, PyImportElement.class), PyTryExceptStatement.class, PyIfStatement.class
) != null
)) {
severity = HighlightSeverity.INFO;
String errmsg = PyBundle.message("INSP.module.$0.not.found", ref_text);
description_buf.append(errmsg);
// TODO: mark the node so that future references pointing to it won't result in a error, but in a warning
}
// look in other imported modules for this whole name
hint_action = proposeImportFixes(node, ref_text);
}
if (reference instanceof PsiReferenceEx) {
final String s = ((PsiReferenceEx)reference).getUnresolvedDescription();
@@ -127,11 +192,7 @@ public class PyUnresolvedReferencesInspection extends LocalInspectionTool {
// this almost always means that we don't know the type, so don't show an error in this case
continue;
}
/*
PyReferenceExpression qref = (PyReferenceExpression)qexpr;
PsiElement qual_resolved = qref.resolve();
*/
if (/*qual_resolved instanceof PyClass*/ qtype != null && qtype instanceof PyClassType) {
if (qtype != null && qtype instanceof PyClassType) {
PyClass cls = ((PyClassType)qtype).getPyClass();
if (cls != null) {
if (reference.getElement().getParent() instanceof PyCallExpression) {
@@ -160,7 +221,7 @@ public class PyUnresolvedReferencesInspection extends LocalInspectionTool {
}
PsiElement point = node.getLastChild(); // usually the identifier at the end of qual ref
if (point == null) point = node;
registerProblem(/*reference.getElement()*/ point, description, hl_type, null, action);
registerProblem(point, description, hl_type, hint_action, action);
}
}
}
@@ -4,8 +4,10 @@
package com.jetbrains.python.psi;
import com.intellij.openapi.util.Key;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
@@ -20,7 +22,16 @@ public interface PyFile extends PyElement, PsiFile {
List<PyFunction> getTopLevelFunctions();
List<PyTargetExpression> getTopLevelAttributes();
/**
* Looks for a name exported by this file, preferably in an efficient way.
* @param name what to find
* @param project project to use (useful for out-of-hierarchy files)
* @return found element, or null.
*/
@Nullable
PsiElement findExportedName(String name);
/**
@return an URL of file, maybe bogus if virtual file is not present.
*/
@@ -33,5 +33,10 @@ public interface PyImportElement extends PyElement, NameDefiner {
@Nullable
PyTargetExpression getAsName();
/**
* @return name under which the element is wisible, that is, "as name" is there is one, or just name.
*/
@Nullable
String getVisibleName();
}
@@ -19,6 +19,7 @@ package com.jetbrains.python.psi;
import com.intellij.psi.PsiNamedElement;
import com.intellij.psi.StubBasedPsiElement;
import com.jetbrains.python.psi.stubs.PyParameterStub;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
@@ -35,5 +36,12 @@ public interface PyParameter extends PyElement, PsiNamedElement, PyExpression, S
@Nullable
PyExpression getDefaultValue();
/**
* @param includeDefaultValue if true, include the default value after an " = ".
* @return Canonical representation of parameter. Includes asterisks for *param and **param, and name.
*/
@NotNull
String getRepr(boolean includeDefaultValue);
}
@@ -1,643 +0,0 @@
/*
* Copyright 2005 Pythonid Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS"; BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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.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;
import java.util.*;
/**
* Ref resolution routines.
* User: yole
* Date: 14.06.2005
*/
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 getConcealingParent(PsiElement elt) {
return PsiTreeUtil.getParentOfType(elt, PyClass.class, PyFunction.class);
}
protected static PsiElement getInnermostChildOf(PsiElement elt) {
PsiElement feeler = elt;
PsiElement seeker;
seeker = feeler;
// find innermost last child of the subtree we're in
while (feeler != null) {
seeker = feeler;
feeler = feeler.getLastChild();
}
return seeker;
}
/**
* Returns closest previous node of given class, as input file would have it.
* @param elt node from which to look for a previous atatement.
* @param cls class of the previous node to find.
* @return previous statement, or null.
*/
@Nullable
public static <T> T getPrevNodeOf(PsiElement elt, Class<T> cls) {
PsiElement seeker = elt;
while (seeker != null) {
PsiElement feeler = seeker.getPrevSibling();
if (feeler != null) {
seeker = getInnermostChildOf(feeler);
}
else { // we were the first subnode
// find something above the parent node we've not exhausted yet
seeker = seeker.getParent();
if (seeker instanceof PyFile) return null; // all file nodes have been looked up, in vain
}
if (cls.isInstance(seeker)) return (T)seeker;
}
// here elt is null or a PsiFile is not up in the parent chain.
return null;
}
/**
* Crawls up the PSI tree, checking nodes as if crawling backwards through source lexemes.
* @param processor a visitor that says when the crawl is done and collects info.
* @param elt element from which we start (not checked by processor); if null, the search immediately returns null.
* @param roof if not null, search continues only below the roof and including it.
* @param fromunder if true, begin search not above elt, but from a [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, boolean fromunder, PsiElement elt, PsiElement roof) {
if (elt == null) return null; // can't find anyway.
PsiElement seeker = elt;
PsiElement cap = getConcealingParent(elt);
do {
ProgressManager.getInstance().checkCanceled();
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 a cap?
while (true) {
PsiElement local_cap = getConcealingParent(seeker);
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 (
(local_cap != elt) && // elt isn't the cap of seeker itself
((cap == null) || !PsiTreeUtil.isAncestor(local_cap, cap, true)) // elt's cap is not under local cap
) { // only look at local cap and above
if (local_cap instanceof NameDefiner) seeker = local_cap;
else seeker = getPrevNodeOf(local_cap, NameDefiner.class);
}
else break; // seeker is contextually under elt already
}
// are we still under the roof?
if ((roof != null) && (seeker != null) && ! PsiTreeUtil.isAncestor(roof, seeker, false)) return null;
// maybe we're capped by a class?
if (refersFromMethodToClass(cap, seeker)) continue;
// check what we got
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;
}
@Nullable
public static PsiElement resolveOffContext(@NotNull PyReferenceExpression refex) {
// 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 ret = null;
PsiElement our_cap = getConcealingParent(refex);
ResolveProcessor proc = new ResolveProcessor(refex.getReferencedName()); // processor reusable till first hit
if (our_cap != null) {
PsiElement cap = our_cap;
while (true) {
cap = getConcealingParent(cap);
if (cap == null) cap = refex.getContainingFile();
ret = treeCrawlUp(proc, true, cap);
if ((ret != null) && !PsiTreeUtil.isAncestor(our_cap, ret, true)) { // found something and it is below our cap
// maybe we're in a method, and what we found is in its class context?
if (! refersFromMethodToClass(our_cap, ret)) {
break; // not in method -> must be all right
}
}
if (cap instanceof PsiFile) break; // file level, can't try more
}
}
return ret;
}
/**
* @param inner an element presumably inside a method within a class, or a method itself.
* @param outer an element presumably in the class context.
* @return true if an outer element is in a class context, while the inner is a method or function inside it.
* @see com.jetbrains.python.psi.PyResolveUtil#getConcealingParent(com.intellij.psi.PsiElement)
*/
protected static boolean refersFromMethodToClass(final PsiElement inner, final PsiElement outer) {
return (
(getConcealingParent(outer) instanceof PyClass) && // outer is in a class context
(PsiTreeUtil.getParentOfType(inner, PyFunction.class, false) != null) // inner is a function or method within the class
);
}
/**
* 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 fromunder if true, search not above elt, but from a [possibly imaginary] node right below elt; so elt gets analyzed, too.
* @param elt element from which we start (not checked by processor); if null, the search immediately fails.
* @return first element that the processor accepted.
*/
@Nullable
public static PsiElement treeCrawlUp(PsiScopeProcessor processor, boolean fromunder, PsiElement elt) {
return treeCrawlUp(processor, fromunder, elt, null);
}
/**
* Returns treeCrawlUp(processor, elt, false). A convenience method.
* @see PyResolveUtil#treeCrawlUp(com.intellij.psi.scope.PsiScopeProcessor,boolean,com.intellij.psi.PsiElement)
*/
@Nullable
public static PsiElement treeCrawlUp(PsiScopeProcessor processor, PsiElement elt) {
return treeCrawlUp(processor, false, elt);
}
/**
* Tries to match two [qualified] reference expression paths by names; 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.
* If either source or target is null, false is returned.
* @see #unwindQualifiers(PyQualifiedExpression) .
* @param source_path expression path to match (the longer list of qualifiers).
* @param target_path expression path to match against (hopeful sublist of qualifiers of source).
* @return true if source matches target.
*/
public static <S extends PyExpression, T extends PyExpression> boolean pathsMatch(List<S> source_path, List<T> target_path) {
// turn qualifiers into lists
if ((source_path == null) || (target_path == null)) return false;
// compare until target is exhausted
Iterator<S> source_iter = source_path.iterator();
for (final T target_elt : target_path) {
if (source_iter.hasNext()) {
S source_elt = source_iter.next();
if (!target_elt.getText().equals(source_elt.getText())) return false;
}
else return false; // source exhausted before target
}
return true;
}
/**
* 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 PyQualifiedExpression, or expr is null, null is returned.
* @param expr an experssion to unwind.
* @return path as a list of ref expressions, or null.
*/
@Nullable
public static <T extends PyQualifiedExpression> List<T> unwindQualifiers(final T expr) {
final List<T> path = new LinkedList<T>();
PyExpression maybe_step;
T step = expr;
try {
while (step != null) {
path.add(0, step);
maybe_step = step.getQualifier();
step = (T)maybe_step;
}
}
catch (ClassCastException e) {
return null;
}
return path;
}
public static String toPath(PyQualifiedExpression expr, String separator) {
if (expr == null) return "";
List<PyQualifiedExpression> path = unwindQualifiers(expr);
if (path != null) {
StringBuilder buf = new StringBuilder();
boolean is_not_first = false;
for (PyQualifiedExpression ex : path) {
if (is_not_first) buf.append(separator);
else is_not_first = true;
buf.append(ex.getName());
}
return buf.toString();
}
else return expr.getName();
}
public static class CollectProcessor<T extends PsiElement> implements PsiScopeProcessor {
Class<T>[] my_collectables;
List<T> my_result;
public CollectProcessor(Class<T>... collectables) {
my_collectables = collectables;
my_result = new ArrayList<T>();
}
public boolean execute(final PsiElement element, final ResolveState state) {
for (Class<T> cls : my_collectables) {
if (cls.isInstance(element)) {
my_result.add((T)element);
}
}
return false;
}
public <T> T getHint(final Class<T> hintClass) {
return null;
}
public void handleEvent(final Event event, final Object associated) {
}
public List<T> getResult() {
return my_result;
}
}
public static class ResolveProcessor implements PyScopeProcessor {
private final String myName;
private PsiElement myResult = null;
/*private Set<String> mySeen;*/
private final List<NameDefiner> myDefiners;
public ResolveProcessor(final String name) {
myName = name;
/*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";
}
/*
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();
if (file != null) {
if (myName.equals(file.getNameWithoutExtension())) {
myResult = element;
return false;
}
else if (ResolveImportUtil.INIT_PY.equals(file.getName())) {
VirtualFile dir = file.getParent();
if ((dir != null) && myName.equals(dir.getName())) {
myResult = element;
return false;
}
}
}
}
else if (element instanceof PsiNamedElement) {
if (myName.equals(((PsiNamedElement)element).getName())) {
myResult = element;
return false;
}
}
else if (element instanceof PyReferenceExpression) {
PyReferenceExpression expr = (PyReferenceExpression)element;
String referencedName = expr.getReferencedName();
if (referencedName != null && referencedName.equals(myName)) {
myResult = element;
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;
if (!PsiTreeUtil.isAncestor(element, by_name, true)) { // non-trivial definer
addNameDefiner(definer);
}
return false;
}
}
return true;
}
public boolean execute(final PsiElement element, final String asName) {
if (asName.equals(myName)) {
myResult = element;
return false;
}
return true;
}
@Nullable
public <T> T getHint(Class<T> hintClass) {
return null;
}
public void handleEvent(Event event, Object associated) {
}
}
public static class MultiResolveProcessor implements PsiScopeProcessor {
private final String _name;
private final List<ResolveResult> _results = new ArrayList<ResolveResult>();
public MultiResolveProcessor(String name) {
_name = name;
}
public ResolveResult[] getResults() {
return _results.toArray(new ResolveResult[_results.size()]);
}
public boolean execute(PsiElement element, ResolveState substitutor) {
if (element instanceof PsiNamedElement) {
if (_name.equals(((PsiNamedElement)element).getName())) {
_results.add(new PsiElementResolveResult(element));
}
}
else if (element instanceof PyReferenceExpression) {
PyReferenceExpression expr = (PyReferenceExpression)element;
String referencedName = expr.getReferencedName();
if (referencedName != null && referencedName.equals(_name)) {
_results.add(new PsiElementResolveResult(element));
}
}
return true;
}
public <T> T getHint(Class<T> hintClass) {
return null;
}
public void handleEvent(Event event, Object associated) {
}
}
public static class VariantsProcessor implements PsiScopeProcessor {
private final Map<String, LookupElement> myVariants = new HashMap<String, LookupElement>();
protected String my_notice;
public VariantsProcessor() {
// empty
}
public VariantsProcessor(final Filter filter) {
my_filter = filter;
}
protected Filter my_filter;
public void setNotice(@Nullable String notice) {
my_notice = notice;
}
protected void setupItem(LookupItem item) {
if (my_notice != null) {
setItemNotice(item, my_notice);
}
}
protected static void setItemNotice(final LookupItem item, String notice) {
item.setAttribute(item.TAIL_TEXT_ATTR, notice);
item.setAttribute(item.TAIL_TEXT_SMALL_ATTR, "");
}
public LookupElement[] getResult() {
final Collection<LookupElement> variants = myVariants.values();
return variants.toArray(new LookupElement[variants.size()]);
}
public List<LookupElement> getResultList() {
return new ArrayList<LookupElement>(myVariants.values());
}
public boolean execute(PsiElement element, ResolveState substitutor) {
if (my_filter != null && !my_filter.accept(element)) return true; // skip whatever the filter rejects
// TODO: refactor to look saner; much code duplication
if (element instanceof PsiNamedElement) {
final PsiNamedElement psiNamedElement = (PsiNamedElement)element;
final String name = psiNamedElement.getName();
if (!myVariants.containsKey(name)) {
final LookupItem lookup_item = (LookupItem)LookupElementFactory.getInstance().createLookupElement(psiNamedElement);
setupItem(lookup_item);
myVariants.put(name, lookup_item);
}
}
else if (element instanceof PyReferenceExpression) {
PyReferenceExpression expr = (PyReferenceExpression)element;
String referencedName = expr.getReferencedName();
if (referencedName != null && !myVariants.containsKey(referencedName)) {
final LookupItem lookup_item = (LookupItem)LookupElementFactory.getInstance().createLookupElement(referencedName);
setupItem(lookup_item);
myVariants.put(referencedName, lookup_item);
}
}
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)) {
final LookupItem lookup_item = (LookupItem)LookupElementFactory.getInstance().createLookupElement(referencedName);
setupItem(lookup_item);
if (definer instanceof PyImportElement) { // set notice to imported module name if needed
PsiElement maybe_from_import = definer.getParent();
if (maybe_from_import instanceof PyFromImportStatement) {
final PyFromImportStatement from_import = (PyFromImportStatement)maybe_from_import;
PyReferenceExpression src = from_import.getImportSource();
if (src != null) {
setItemNotice(lookup_item, " | " + src.getName());
}
}
}
myVariants.put(referencedName, lookup_item);
}
}
}
}
return true;
}
@Nullable
public <T> T getHint(Class<T> hintClass) {
return null;
}
public void handleEvent(Event event, Object associated) {
}
}
/**
* A simple interface allowing to filter processor results.
*/
public interface Filter {
/**
* @param target the object a processor is currently looking at.
* @return true if the object is acceptable as a processor result.
*/
boolean accept(Object target);
}
public static class FilterNotInstance implements Filter {
Object instance;
public FilterNotInstance(Object instance) {
this.instance = instance;
}
public boolean accept(final Object target) {
return (instance != target);
}
}
/**
* Collects all assignments in context above given element, if they match given naming pattern.
* Used to track creation of attributes by assignment (e.g in constructor).
*/
public static class AssignmentCollectProcessor<T extends PyExpression> implements PsiScopeProcessor {
List<T> my_qualifier;
List<PyExpression> my_result;
Set<String> my_seen_names;
/**
* Creates an instance to collect assignments of attributes to the object identified by 'qualifier'.
* E.g. if qualifier = {"foo", "bar"} then assignments like "foo.bar.baz = ..." will be considered.
* The collection continues up to the point of latest redefinition of the object identified by 'qualifier',
* that is, up to the point of something like "foo.bar = ..." or "foo = ...".
* @param qualifier qualifying names, outermost first; must not be empty.
*/
public AssignmentCollectProcessor(@NotNull List<T> qualifier) {
assert qualifier.size() > 0;
my_qualifier = qualifier;
my_result = new ArrayList<PyExpression>();
my_seen_names = new HashSet<String>();
}
public boolean execute(final PsiElement element, final ResolveState state) {
if (element instanceof PyAssignmentStatement) {
final PyAssignmentStatement assignment = (PyAssignmentStatement)element;
for (PyExpression ex : assignment.getTargets()) {
if (ex instanceof PyTargetExpression) {
final PyTargetExpression target = (PyTargetExpression)ex;
List<PyTargetExpression> quals = unwindQualifiers(target);
if (quals != null) {
if (quals.size() == my_qualifier.size()+1 && pathsMatch(quals, my_qualifier)) {
// a new attribute follows last qualifier; collect it.
PyTargetExpression last_elt = quals.get(quals.size() - 1); // last item is the outermost, new, attribute.
String last_elt_name = last_elt.getName();
if (!my_seen_names.contains(last_elt_name)) { // no dupes, only remember the latest
my_result.add(last_elt);
my_seen_names.add(last_elt_name);
}
}
else if (quals.size() < my_qualifier.size()+1 && pathsMatch(my_qualifier, quals)) {
// qualifier(s) get redefined; collect no more.
return false;
}
}
}
}
}
return true; // nothing interesting found, continue
}
/**
* @return a collection of exressions (parts of assignment expressions) where new attributes were defined. E.g. for "a.b.c = 1",
* the expression for 'c' is in the result.
*/
@NotNull
public Collection<PyExpression> getResult() {
return my_result;
}
public <T> T getHint(final Class<T> hintClass) {
return null;
}
public void handleEvent(final Event event, final Object associated) {
// empty
}
}
}
@@ -42,8 +42,8 @@ import java.util.Comparator;
import java.util.List;
public class PyUtil {
private PyUtil() {
}
private PyUtil() {
}
public static void ensureWritable(PsiElement element) {
PsiDocumentManager docmgr = PsiDocumentManager.getInstance(
@@ -358,4 +358,31 @@ public class PyUtil {
point, Balloon.Position.above
);
}
@NonNls
/**
* Returns a quoted string representation, or "null".
*/
public static String nvl(Object s) {
if (s != null) {
return "'" + s.toString() + "'";
}
else {
return "null";
}
}
public static void addListNode(PsiElement target, PsiElement source, ASTNode beforeThis, boolean isFirst, boolean isLast) {
ensureWritable(target);
ASTNode node = target.getNode();
assert node != null;
ASTNode itemNode = source.getNode();
assert itemNode != null;
Project project = target.getProject();
PyElementGenerator gen = PythonLanguage.getInstance().getElementGenerator();
if (! isFirst) node.addChild(gen.createComma(project), beforeThis);
node.addChild(itemNode, beforeThis);
if (! isLast) node.addChild(gen.createComma(project), beforeThis);
}
}
@@ -6,6 +6,6 @@ import com.intellij.psi.scope.PsiScopeProcessor;
/**
* @author yole
*/
public interface PyScopeProcessor extends PsiScopeProcessor {
public interface PyAsScopeProcessor extends PsiScopeProcessor {
boolean execute(PsiElement element, String asName);
}
@@ -27,6 +27,8 @@ import com.jetbrains.python.PyElementTypes;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.PyTokenTypes;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.resolve.PyResolveUtil;
import com.jetbrains.python.psi.resolve.VariantsProcessor;
import com.jetbrains.python.psi.stubs.PyClassStub;
import com.jetbrains.python.psi.stubs.PyFunctionStub;
import com.jetbrains.python.validation.DocStringAnnotator;
@@ -326,7 +328,7 @@ public class PyClassImpl extends PyPresentableElementImpl<PyClassStub> implement
if (!processor.execute(expr, substitutor)) return false;
}
//
if (processor instanceof PyResolveUtil.VariantsProcessor) {
if (processor instanceof VariantsProcessor) {
return true;
}
return processor.execute(this, substitutor);
@@ -18,6 +18,7 @@ package com.jetbrains.python.psi.impl;
import com.intellij.extapi.psi.PsiFileBase;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.FileViewProvider;
import com.intellij.psi.PsiElement;
@@ -31,6 +32,8 @@ import com.jetbrains.python.PyElementTypes;
import com.jetbrains.python.PythonFileType;
import com.jetbrains.python.PythonLanguage;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.resolve.PyResolveUtil;
import com.jetbrains.python.psi.resolve.ResolveProcessor;
import com.jetbrains.python.psi.types.PyClassType;
import com.jetbrains.python.psi.types.PyType;
import org.jetbrains.annotations.NotNull;
@@ -151,6 +154,13 @@ public class PyFileImpl extends PsiFileBase implements PyFile, PyExpression {
return getTopLevelItems(PyElementTypes.TARGET_EXPRESSION, PyTargetExpression.class);
}
public PsiElement findExportedName(String name) {
// dull plain resolve, as fast as stub index or better
ResolveProcessor proc = new ResolveProcessor(name);
PyResolveUtil.treeCrawlUp(proc, true, getLastChild());
return proc.getResult();
}
public List<PyExpression> getImportTargets() {
List<PyExpression> ret = new ArrayList<PyExpression>();
List<PyImportStatement> imports = getTopLevelItems(PyElementTypes.IMPORT_STATEMENT, PyImportStatement.class);
@@ -25,6 +25,8 @@ import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.util.PsiTreeUtil;
import com.jetbrains.python.PyElementTypes;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.resolve.PyAsScopeProcessor;
import com.jetbrains.python.psi.resolve.PyResolveUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -54,6 +56,16 @@ public class PyImportElementImpl extends PyElementImpl implements PyImportElemen
return (PyTargetExpression)asNameNode.getPsi();
}
@Nullable
public String getVisibleName() {
PyTargetExpression asname = getAsName();
if (asname != null) return asname.getName();
for (PyElement name_elt : iterateNames()) {
return name_elt.getName(); // first to come must be right
}
return null; // we might have not found any names
}
@Override
public boolean processDeclarations(@NotNull final PsiScopeProcessor processor, @NotNull final ResolveState state, final PsiElement lastParent,
@NotNull final PsiElement place) {
@@ -65,10 +77,10 @@ public class PyImportElementImpl extends PyElementImpl implements PyImportElemen
if (importRef != null) {
final PsiElement element = importRef.resolve();
if (element != null) {
if (processor instanceof PyScopeProcessor) {
if (processor instanceof PyAsScopeProcessor) {
PyTargetExpression asName = getAsName();
if (asName != null) {
return ((PyScopeProcessor) processor).execute(element, asName.getText()); // might resolve to asName to show the source of name
return ((PyAsScopeProcessor) processor).execute(element, asName.getText()); // might resolve to asName to show the source of name
}
// maybe the incoming name is qualified
PyReferenceExpression place_ref = PsiTreeUtil.getChildOfType(place, PyReferenceExpression.class);
@@ -102,6 +102,19 @@ public class PyParameterImpl extends PyPresentableElementImpl<PyParameterStub> i
return null;
}
@NotNull
public String getRepr(boolean includeDefaultValue) {
StringBuffer sb = new StringBuffer();
if (isPositionalContainer()) sb.append("*");
else if (isKeywordContainer()) sb.append("**");
sb.append(getName());
if (includeDefaultValue) {
PyExpression default_v = getDefaultValue();
if (default_v != null) sb.append("=").append(PyUtil.getReadableRepr(default_v, true));
}
return sb.toString();
}
public Icon getIcon(final int flags) {
return Icons.PARAMETER_ICON;
}
@@ -54,21 +54,7 @@ public class PyParameterListImpl extends PyBaseElementImpl<PyParameterListStub>
PyUtil.ensureWritable(this);
ASTNode beforeWhat = paren.getNode(); // the closing bracket will be this
PyParameter[] params = getParameters();
addItemNode(param, beforeWhat, true, params.length == 0);
}
}
// TODO: open for general usage by all list-like structurtes
private void addItemNode(PyParameter item, ASTNode beforeThis, boolean isFirst, boolean isLast) {
PyUtil.ensureWritable(this);
ASTNode node = getNode();
ASTNode itemNode = item.getNode();
if (! isFirst) {
node.addChild(getLanguage().getElementGenerator().createComma(getProject()), beforeThis);
}
node.addChild(itemNode, beforeThis);
if (! isLast) {
node.addChild(getLanguage().getElementGenerator().createComma(getProject()), beforeThis);
PyUtil.addListNode(this, param, beforeWhat, true, params.length == 0);
}
}
@@ -36,6 +36,7 @@ import com.jetbrains.python.PyIcons;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.PyTokenTypes;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.resolve.*;
import com.jetbrains.python.psi.types.PyClassType;
import com.jetbrains.python.psi.types.PyModuleType;
import com.jetbrains.python.psi.types.PyNoneType;
@@ -204,7 +205,7 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere
// here we have an unqualified expr. it may be defined:
// ...in current file
PyResolveUtil.ResolveProcessor processor = new PyResolveUtil.ResolveProcessor(referencedName);
ResolveProcessor processor = new ResolveProcessor(referencedName);
PsiElement uexpr = PyResolveUtil.treeCrawlUp(processor, this);
if ((uexpr != null)) {
if ((uexpr instanceof PyClass)) {
@@ -230,7 +231,7 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere
if (uexpr == null) {
// ...as a builtin symbol
PyFile bfile = PyBuiltinCache.getInstance(getProject()).getBuiltinsFile();
uexpr = PyResolveUtil.treeCrawlUp(new PyResolveUtil.ResolveProcessor(referencedName), true, bfile);
uexpr = PyResolveUtil.treeCrawlUp(new ResolveProcessor(referencedName), true, bfile);
}
if (uexpr == null) {
uexpr = PyResolveUtil.resolveOffContext(this);
@@ -251,8 +252,7 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere
private static Collection<PyExpression> collectAssignedAttributes(PyQualifiedExpression qualifier) {
List<PyQualifiedExpression> qualifier_path = PyResolveUtil.unwindQualifiers(qualifier);
if (qualifier_path != null) {
PyResolveUtil.AssignmentCollectProcessor<PyQualifiedExpression> proc =
new PyResolveUtil.AssignmentCollectProcessor<PyQualifiedExpression>(qualifier_path)
AssignmentCollectProcessor proc = new AssignmentCollectProcessor(qualifier_path)
;
PyResolveUtil.treeCrawlUp(proc, qualifier);
return proc.getResult();
@@ -391,14 +391,14 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere
}
// include our own names
final PyResolveUtil.VariantsProcessor processor = new PyResolveUtil.VariantsProcessor();
final VariantsProcessor processor = new VariantsProcessor();
PyResolveUtil.treeCrawlUp(processor, this); // names from here
// scan all "import *" and include names provided by them
PyResolveUtil.CollectProcessor<PyStarImportElement> collect_proc;
collect_proc = new PyResolveUtil.CollectProcessor<PyStarImportElement>(PyStarImportElement.class);
CollectProcessor collect_proc;
collect_proc = new CollectProcessor(PyStarImportElement.class);
PyResolveUtil.treeCrawlUp(collect_proc, this);
List<PyStarImportElement> stars = collect_proc.getResult();
for (PyStarImportElement star_elt : stars) {
List<PsiElement> stars = collect_proc.getResult();
for (PsiElement star_elt : stars) {
final PyFromImportStatement from_import_stmt = (PyFromImportStatement)star_elt.getParent();
if (from_import_stmt != null) {
final PyReferenceExpression import_src = from_import_stmt.getImportSource();
@@ -1,6 +1,7 @@
package com.jetbrains.python.psi.impl;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.resolve.PyResolveUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.lang.ASTNode;
@@ -13,6 +13,9 @@ import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.containers.HashSet;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.resolve.PyResolveUtil;
import com.jetbrains.python.psi.resolve.ResolveProcessor;
import com.jetbrains.python.psi.resolve.VariantsProcessor;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.NotNull;
@@ -370,13 +373,13 @@ public class ResolveImportUtil {
public static PsiElement resolveChild(final PsiElement parent, final String referencedName, final PsiFile containingFile, boolean fileOnly) {
PsiDirectory dir = null;
PsiElement ret = null;
PyResolveUtil.ResolveProcessor processor = null;
ResolveProcessor processor = null;
if (parent instanceof PyFile) {
boolean is_dir = (parent.getCopyableUserData(PyFile.KEY_IS_DIRECTORY) == Boolean.TRUE);
PyFile pfparent = (PyFile)parent;
if (! is_dir) {
// look for name in the file:
processor = new PyResolveUtil.ResolveProcessor(referencedName);
processor = new ResolveProcessor(referencedName);
//ret = PyResolveUtil.treeWalkUp(processor, parent, null, importRef);
ret = PyResolveUtil.treeCrawlUp(processor, true, parent);
if (ret != null) return ret;
@@ -407,8 +410,7 @@ public class ResolveImportUtil {
@Nullable
private static PsiElement resolveInDirectory(final String referencedName,
final PsiFile containingFile,
final PsiDirectory dir,
PyResolveUtil.ResolveProcessor processor) {
final PsiDirectory dir, ResolveProcessor processor) {
final PsiFile file = dir.findFile(referencedName + PY_SUFFIX);
if (file != null) return file;
final PsiDirectory subdir = dir.findSubdirectory(referencedName);
@@ -417,7 +419,7 @@ public class ResolveImportUtil {
final PsiFile initPy = dir.findFile(INIT_PY);
if (initPy == containingFile) return null; // don't dive into the file we're in
if (initPy != null) {
if (processor == null) processor = new PyResolveUtil.ResolveProcessor(referencedName); // should not normally happen
if (processor == null) processor = new ResolveProcessor(referencedName); // should not normally happen
return PyResolveUtil.treeCrawlUp(processor, true, initPy);//PyResolveUtil.treeWalkUp(processor, initPy, null, importRef);
}
}
@@ -440,7 +442,7 @@ public class ResolveImportUtil {
if (src != null) {
PsiElement mod = src.resolve();
if (mod != null) {
final PyResolveUtil.VariantsProcessor processor = new PyResolveUtil.VariantsProcessor();
final VariantsProcessor processor = new VariantsProcessor();
PyResolveUtil.treeCrawlUp(processor, true, mod);
/*
for (LookupElement le : processor.getResult()) {
@@ -492,7 +494,7 @@ public class ResolveImportUtil {
variants.addAll(visitor.getResult());
}
return variants.toArray(new String[variants.size()]);
return variants.toArray(new Object[variants.size()]);
}
}
@@ -0,0 +1,83 @@
package com.jetbrains.python.psi.resolve;
import com.intellij.psi.PsiElement;
import com.intellij.psi.ResolveState;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.jetbrains.python.psi.PyAssignmentStatement;
import com.jetbrains.python.psi.PyExpression;
import com.jetbrains.python.psi.PyTargetExpression;
import org.jetbrains.annotations.NotNull;
import java.util.*;
public class AssignmentCollectProcessor implements PsiScopeProcessor {
/**
* Collects all assignments in context above given element, if they match given naming pattern.
* Used to track creation of attributes by assignment (e.g in constructor).
*/
List<? extends PyExpression> my_qualifier;
List<PyExpression> my_result;
Set<String> my_seen_names;
/**
* Creates an instance to collect assignments of attributes to the object identified by 'qualifier'.
* E.g. if qualifier = {"foo", "bar"} then assignments like "foo.bar.baz = ..." will be considered.
* The collection continues up to the point of latest redefinition of the object identified by 'qualifier',
* that is, up to the point of something like "foo.bar = ..." or "foo = ...".
*
* @param qualifier qualifying names, outermost first; must not be empty.
*/
public AssignmentCollectProcessor(@NotNull List<? extends PyExpression> qualifier) {
assert qualifier.size() > 0;
my_qualifier = qualifier;
my_result = new ArrayList<PyExpression>();
my_seen_names = new HashSet<String>();
}
public boolean execute(final PsiElement element, final ResolveState state) {
if (element instanceof PyAssignmentStatement) {
final PyAssignmentStatement assignment = (PyAssignmentStatement)element;
for (PyExpression ex : assignment.getTargets()) {
if (ex instanceof PyTargetExpression) {
final PyTargetExpression target = (PyTargetExpression)ex;
List<PyTargetExpression> quals = PyResolveUtil.unwindQualifiers(target);
if (quals != null) {
if (quals.size() == my_qualifier.size() + 1 && PyResolveUtil.pathsMatch(quals, my_qualifier)) {
// a new attribute follows last qualifier; collect it.
PyTargetExpression last_elt = quals.get(quals.size() - 1); // last item is the outermost, new, attribute.
String last_elt_name = last_elt.getName();
if (!my_seen_names.contains(last_elt_name)) { // no dupes, only remember the latest
my_result.add(last_elt);
my_seen_names.add(last_elt_name);
}
}
else if (quals.size() < my_qualifier.size() + 1 && PyResolveUtil.pathsMatch(my_qualifier, quals)) {
// qualifier(s) get redefined; collect no more.
return false;
}
}
}
}
}
return true; // nothing interesting found, continue
}
/**
* @return a collection of exressions (parts of assignment expressions) where new attributes were defined. E.g. for "a.b.c = 1",
* the expression for 'c' is in the result.
*/
@NotNull
public Collection<PyExpression> getResult() {
return my_result;
}
public <T> T getHint(final Class<T> hintClass) {
return null;
}
public void handleEvent(final Event event, final Object associated) {
// empty
}
}
@@ -0,0 +1,44 @@
package com.jetbrains.python.psi.resolve;
import com.intellij.psi.PsiElement;
import com.intellij.psi.ResolveState;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
public class CollectProcessor implements PyClassScopeProcessor {
Class<? extends PsiElement>[] my_collectables;
List<PsiElement> my_result;
public CollectProcessor(Class<? extends PsiElement>... collectables) {
my_collectables = collectables;
my_result = new ArrayList<PsiElement>();
}
public boolean execute(final PsiElement element, final ResolveState state) {
for (Class cls : my_collectables) {
if (cls.isInstance(element)) {
my_result.add(element);
}
}
return true; // collect till we drop
}
public <T> T getHint(final Class<T> hintClass) {
return null;
}
public void handleEvent(final Event event, final Object associated) {
}
public List<PsiElement> getResult() {
return my_result;
}
@NotNull
public Class[] getPossibleTargets() {
return my_collectables;
}
}
@@ -0,0 +1,45 @@
package com.jetbrains.python.psi.resolve;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.*;
import com.jetbrains.python.psi.PyReferenceExpression;
import java.util.List;
import java.util.ArrayList;
public class MultiResolveProcessor implements PsiScopeProcessor {
private final String _name;
private final List<ResolveResult> _results = new ArrayList<ResolveResult>();
public MultiResolveProcessor(String name) {
_name = name;
}
public ResolveResult[] getResults() {
return _results.toArray(new ResolveResult[_results.size()]);
}
public boolean execute(PsiElement element, ResolveState substitutor) {
if (element instanceof PsiNamedElement) {
if (_name.equals(((PsiNamedElement)element).getName())) {
_results.add(new PsiElementResolveResult(element));
}
}
else if (element instanceof PyReferenceExpression) {
PyReferenceExpression expr = (PyReferenceExpression)element;
String referencedName = expr.getReferencedName();
if (referencedName != null && referencedName.equals(_name)) {
_results.add(new PsiElementResolveResult(element));
}
}
return true;
}
public <T> T getHint(Class<T> hintClass) {
return null;
}
public void handleEvent(Event event, Object associated) {
}
}
@@ -0,0 +1,11 @@
package com.jetbrains.python.psi.resolve;
import com.intellij.psi.PsiElement;
import com.intellij.psi.scope.PsiScopeProcessor;
/**
* @author yole
*/
public interface PyAsScopeProcessor extends PsiScopeProcessor {
boolean execute(PsiElement element, String asName);
}
@@ -0,0 +1,23 @@
package com.jetbrains.python.psi.resolve;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.jetbrains.python.psi.NameDefiner;
import org.jetbrains.annotations.NotNull;
/**
* Processor capable of giving multiple hints on what it's looking for.
* User: dcheryasov
* Date: Apr 19, 2009
*/
public interface PyClassScopeProcessor extends PsiScopeProcessor {
/**
* @return classes of nodes that might be interesting for the processor.
* ??? Instances of NameDefiner are always considered interesting.
* ??? An empty list makes processor see only NameDefiners.
* @see com.jetbrains.python.psi.NameDefiner
*/
@NotNull
Class[] getPossibleTargets();
Class[] NAME_DEFINER_ONLY = {NameDefiner.class};
}
@@ -0,0 +1,311 @@
/*
* Copyright 2005 Pythonid Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS"; BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.psi.resolve;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.ResolveState;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.util.PsiTreeUtil;
import com.jetbrains.python.psi.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
/**
* Ref resolution routines.
* User: yole
* Date: 14.06.2005
*/
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 getConcealingParent(PsiElement elt) {
return PsiTreeUtil.getParentOfType(elt, PyClass.class, PyFunction.class);
}
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 classes which class of the previous nodes to find.
* @return previous statement, or null.
*/
@Nullable
public static PsiElement getPrevNodeOf(PsiElement elt, Class... classes) {
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 (seeker instanceof NameDefiner) return seeker;
for (Class cls : classes) {
if (cls.isInstance(seeker)) return seeker;
}
}
// here elt is null or a PsiFile is not up in the parent chain.
return null;
}
@Nullable
public static PsiElement getPrevNodeOf(PsiElement elt, PsiScopeProcessor proc) {
if (proc instanceof PyClassScopeProcessor) {
return getPrevNodeOf(elt, ((PyClassScopeProcessor)proc).getPossibleTargets());
}
else return getPrevNodeOf(elt, PyClassScopeProcessor.NAME_DEFINER_ONLY);
}
/**
* 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 returns null.
* @param roof if not null, search continues only below the roof and including it.
* @param fromunder if true, begin search not above elt, but from a [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, boolean fromunder, PsiElement elt, PsiElement roof) {
if (elt == null) return null; // can't find anyway.
PsiElement seeker = elt;
PsiElement cap = getConcealingParent(elt);
do {
ProgressManager.getInstance().checkCanceled();
if (fromunder) {
fromunder = false; // only honour fromunder once per call
seeker = getPrevNodeOf(getInnermostChildOf(seeker), processor);
}
else { // main case
seeker = getPrevNodeOf(seeker, processor);
}
// aren't we in the same defining assignment, global, etc?
if ((seeker instanceof NameDefiner) && ((NameDefiner)seeker).mustResolveOutside() && PsiTreeUtil.isAncestor(seeker, elt, true)) {
seeker = getPrevNodeOf(seeker, processor);
}
// maybe we're under a cap?
while (true) {
PsiElement local_cap = getConcealingParent(seeker);
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 (
(local_cap != elt) && // elt isn't the cap of seeker itself
((cap == null) || !PsiTreeUtil.isAncestor(local_cap, cap, true)) // elt's cap is not under local cap
) { // only look at local cap and above
if (local_cap instanceof NameDefiner) seeker = local_cap;
else seeker = getPrevNodeOf(local_cap, processor);
}
else break; // seeker is contextually under elt already
}
// are we still under the roof?
if ((roof != null) && (seeker != null) && ! PsiTreeUtil.isAncestor(roof, seeker, false)) return null;
// maybe we're capped by a class?
if (refersFromMethodToClass(cap, seeker)) continue;
// check what we got
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;
}
@Nullable
public static PsiElement resolveOffContext(@NotNull PyReferenceExpression refex) {
// 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 ret = null;
PsiElement our_cap = getConcealingParent(refex);
ResolveProcessor proc = new ResolveProcessor(refex.getReferencedName()); // processor reusable till first hit
if (our_cap != null) {
PsiElement cap = our_cap;
while (true) {
cap = getConcealingParent(cap);
if (cap == null) cap = refex.getContainingFile();
ret = treeCrawlUp(proc, true, cap);
if ((ret != null) && !PsiTreeUtil.isAncestor(our_cap, ret, true)) { // found something and it is below our cap
// maybe we're in a method, and what we found is in its class context?
if (! refersFromMethodToClass(our_cap, ret)) {
break; // not in method -> must be all right
}
}
if (cap instanceof PsiFile) break; // file level, can't try more
}
}
return ret;
}
/**
* @param inner an element presumably inside a method within a class, or a method itself.
* @param outer an element presumably in the class context.
* @return true if an outer element is in a class context, while the inner is a method or function inside it.
* @see PyResolveUtil#getConcealingParent(com.intellij.psi.PsiElement)
*/
protected static boolean refersFromMethodToClass(final PsiElement inner, final PsiElement outer) {
return (
(getConcealingParent(outer) instanceof PyClass) && // outer is in a class context
(PsiTreeUtil.getParentOfType(inner, PyFunction.class, false) != null) // inner is a function or method within the class
);
}
/**
* 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 fromunder if true, search not above elt, but from a [possibly imaginary] node right below elt; so elt gets analyzed, too.
* @param elt element from which we start (not checked by processor); if null, the search immediately fails.
* @return first element that the processor accepted.
*/
@Nullable
public static PsiElement treeCrawlUp(PsiScopeProcessor processor, boolean fromunder, PsiElement elt) {
return treeCrawlUp(processor, fromunder, elt, null);
}
/**
* Returns treeCrawlUp(processor, elt, false). A convenience method.
* @see PyResolveUtil#treeCrawlUp(com.intellij.psi.scope.PsiScopeProcessor,boolean,com.intellij.psi.PsiElement)
*/
@Nullable
public static PsiElement treeCrawlUp(PsiScopeProcessor processor, PsiElement elt) {
return treeCrawlUp(processor, false, elt);
}
/**
* Tries to match two [qualified] reference expression paths by names; 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.
* If either source or target is null, false is returned.
* @see #unwindQualifiers(PyQualifiedExpression) .
* @param source_path expression path to match (the longer list of qualifiers).
* @param target_path expression path to match against (hopeful sublist of qualifiers of source).
* @return true if source matches target.
*/
public static <S extends PyExpression, T extends PyExpression> boolean pathsMatch(List<S> source_path, List<T> target_path) {
// turn qualifiers into lists
if ((source_path == null) || (target_path == null)) return false;
// compare until target is exhausted
Iterator<S> source_iter = source_path.iterator();
for (final T target_elt : target_path) {
if (source_iter.hasNext()) {
S source_elt = source_iter.next();
if (!target_elt.getText().equals(source_elt.getText())) return false;
}
else return false; // source exhausted before target
}
return true;
}
/**
* 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 PyQualifiedExpression, or expr is null, null is returned.
* @param expr an experssion to unwind.
* @return path as a list of ref expressions, or null.
*/
@Nullable
public static <T extends PyQualifiedExpression> List<T> unwindQualifiers(final T expr) {
final List<T> path = new LinkedList<T>();
PyExpression maybe_step;
T step = expr;
try {
while (step != null) {
path.add(0, step);
maybe_step = step.getQualifier();
step = (T)maybe_step;
}
}
catch (ClassCastException e) {
return null;
}
return path;
}
public static String toPath(PyQualifiedExpression expr, String separator) {
if (expr == null) return "";
List<PyQualifiedExpression> path = unwindQualifiers(expr);
if (path != null) {
StringBuilder buf = new StringBuilder();
boolean is_not_first = false;
for (PyQualifiedExpression ex : path) {
if (is_not_first) buf.append(separator);
else is_not_first = true;
buf.append(ex.getName());
}
return buf.toString();
}
else {
String s = expr.getName();
return s != null? s : "";
}
}
/**
* A simple interface allowing to filter processor results.
*/
public interface Filter {
/**
* @param target the object a processor is currently looking at.
* @return true if the object is acceptable as a processor result.
*/
boolean accept(Object target);
}
public static class FilterNotInstance implements Filter {
Object instance;
public FilterNotInstance(Object instance) {
this.instance = instance;
}
public boolean accept(final Object target) {
return (instance != target);
}
}
}
@@ -0,0 +1,112 @@
package com.jetbrains.python.psi.resolve;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiNamedElement;
import com.intellij.psi.ResolveState;
import com.intellij.psi.util.PsiTreeUtil;
import com.jetbrains.python.psi.NameDefiner;
import com.jetbrains.python.psi.PyFile;
import com.jetbrains.python.psi.PyReferenceExpression;
import com.jetbrains.python.psi.PyUtil;
import com.jetbrains.python.psi.impl.ResolveImportUtil;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
public class ResolveProcessor implements PyAsScopeProcessor {
private final String myName;
private PsiElement myResult = null;
private final List<NameDefiner> myDefiners;
public ResolveProcessor(final String name) {
myName = name;
myDefiners = new ArrayList<NameDefiner>(2); // 1 is typical, 2 is sometimes, more is rare.
}
public PsiElement getResult() {
return myResult;
}
/**
* 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 PyUtil.nvl(myName) + ", " + PyUtil.nvl(myResult);
}
public boolean execute(PsiElement element, ResolveState substitutor) {
if (element instanceof PyFile) {
final VirtualFile file = ((PyFile)element).getVirtualFile();
if (file != null) {
if (myName.equals(file.getNameWithoutExtension())) {
myResult = element;
return false;
}
else if (ResolveImportUtil.INIT_PY.equals(file.getName())) {
VirtualFile dir = file.getParent();
if ((dir != null) && myName.equals(dir.getName())) {
myResult = element;
return false;
}
}
}
}
else if (element instanceof PsiNamedElement) {
if (myName.equals(((PsiNamedElement)element).getName())) {
myResult = element;
return false;
}
}
else if (element instanceof PyReferenceExpression) {
PyReferenceExpression expr = (PyReferenceExpression)element;
String referencedName = expr.getReferencedName();
if (referencedName != null && referencedName.equals(myName)) {
myResult = element;
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;
if (!PsiTreeUtil.isAncestor(element, by_name, true)) { // non-trivial definer
addNameDefiner(definer);
}
return false;
}
}
return true;
}
public boolean execute(final PsiElement element, final String asName) {
if (asName.equals(myName)) {
myResult = element;
return false;
}
return true;
}
@Nullable
public <T> T getHint(Class<T> hintClass) {
return null;
}
public void handleEvent(Event event, Object associated) {
}
}
@@ -0,0 +1,110 @@
package com.jetbrains.python.psi.resolve;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementFactory;
import com.intellij.codeInsight.lookup.LookupItem;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiNamedElement;
import com.intellij.psi.ResolveState;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.jetbrains.python.psi.*;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public class VariantsProcessor implements PsiScopeProcessor {
private final Map<String, LookupElement> myVariants = new HashMap<String, LookupElement>();
protected String my_notice;
public VariantsProcessor() {
// empty
}
public VariantsProcessor(final PyResolveUtil.Filter filter) {
my_filter = filter;
}
protected PyResolveUtil.Filter my_filter;
public void setNotice(@Nullable String notice) {
my_notice = notice;
}
protected void setupItem(LookupItem item) {
if (my_notice != null) {
setItemNotice(item, my_notice);
}
}
protected static void setItemNotice(final LookupItem item, String notice) {
item.setAttribute(item.TAIL_TEXT_ATTR, notice);
item.setAttribute(item.TAIL_TEXT_SMALL_ATTR, "");
}
public LookupElement[] getResult() {
final Collection<LookupElement> variants = myVariants.values();
return variants.toArray(new LookupElement[variants.size()]);
}
public List<LookupElement> getResultList() {
return new ArrayList<LookupElement>(myVariants.values());
}
public boolean execute(PsiElement element, ResolveState substitutor) {
if (my_filter != null && !my_filter.accept(element)) return true; // skip whatever the filter rejects
// TODO: refactor to look saner; much code duplication
if (element instanceof PsiNamedElement) {
final PsiNamedElement psiNamedElement = (PsiNamedElement)element;
final String name = psiNamedElement.getName();
if (!myVariants.containsKey(name)) {
final LookupItem lookup_item = (LookupItem)LookupElementFactory.getInstance().createLookupElement(psiNamedElement);
setupItem(lookup_item);
myVariants.put(name, lookup_item);
}
}
else if (element instanceof PyReferenceExpression) {
PyReferenceExpression expr = (PyReferenceExpression)element;
String referencedName = expr.getReferencedName();
if (referencedName != null && !myVariants.containsKey(referencedName)) {
final LookupItem lookup_item = (LookupItem)LookupElementFactory.getInstance().createLookupElement(referencedName);
setupItem(lookup_item);
myVariants.put(referencedName, lookup_item);
}
}
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)) {
final LookupItem lookup_item = (LookupItem)LookupElementFactory.getInstance().createLookupElement(referencedName);
setupItem(lookup_item);
if (definer instanceof PyImportElement) { // set notice to imported module name if needed
PsiElement maybe_from_import = definer.getParent();
if (maybe_from_import instanceof PyFromImportStatement) {
final PyFromImportStatement from_import = (PyFromImportStatement)maybe_from_import;
PyReferenceExpression src = from_import.getImportSource();
if (src != null) {
setItemNotice(lookup_item, " | " + src.getName());
}
}
}
myVariants.put(referencedName, lookup_item);
}
}
}
}
return true;
}
@Nullable
public <T> T getHint(Class<T> hintClass) {
return null;
}
public void handleEvent(Event event, Object associated) {
}
}
@@ -5,7 +5,9 @@ import com.intellij.psi.ResolveState;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyExpression;
import com.jetbrains.python.psi.PyReferenceExpression;
import com.jetbrains.python.psi.PyResolveUtil;
import com.jetbrains.python.psi.resolve.PyResolveUtil;
import com.jetbrains.python.psi.resolve.ResolveProcessor;
import com.jetbrains.python.psi.resolve.VariantsProcessor;
import com.jetbrains.python.psi.impl.PyBuiltinCache;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -51,7 +53,7 @@ public class PyClassType implements PyType {
@Nullable
public PsiElement resolveMember(final String name) {
if (myClass == null) return null;
PyResolveUtil.ResolveProcessor processor = new PyResolveUtil.ResolveProcessor(name);
ResolveProcessor processor = new ResolveProcessor(name);
myClass.processDeclarations(processor, ResolveState.initial(), null, myClass); // our members are strictly within us.
final PsiElement resolveResult = processor.getResult();
//final PsiElement resolveResult = PyResolveUtil.treeWalkUp(new PyResolveUtil.ResolveProcessor(name), myClass, null, null);
@@ -94,7 +96,7 @@ public class PyClassType implements PyType {
}
public Object[] getCompletionVariants(final PyReferenceExpression referenceExpression) {
final PyResolveUtil.VariantsProcessor processor = new PyResolveUtil.VariantsProcessor(new PyResolveUtil.FilterNotInstance(myClass));
final VariantsProcessor processor = new VariantsProcessor(new PyResolveUtil.FilterNotInstance(myClass));
myClass.processDeclarations(processor, ResolveState.initial(), null, referenceExpression);
List<Object> ret = new ArrayList<Object>();
ret.addAll(processor.getResultList());
@@ -4,7 +4,7 @@ import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.ResolveState;
import com.jetbrains.python.psi.PyReferenceExpression;
import com.jetbrains.python.psi.PyResolveUtil;
import com.jetbrains.python.psi.resolve.VariantsProcessor;
import com.jetbrains.python.psi.impl.ResolveImportUtil;
import org.jetbrains.annotations.NotNull;
@@ -43,7 +43,7 @@ public class PyModuleType implements PyType { // TODO: make it a PyClassType ref
}
public Object[] getCompletionVariants(final PyReferenceExpression referenceExpression) {
final PyResolveUtil.VariantsProcessor processor = new PyResolveUtil.VariantsProcessor();
final VariantsProcessor processor = new VariantsProcessor();
myModule.processDeclarations(processor, ResolveState.initial(), null, referenceExpression);
return processor.getResult();
}
@@ -55,7 +55,7 @@ public class PyModuleType implements PyType { // TODO: make it a PyClassType ref
}
@NotNull
public Set<String> getPossibleInstanceMembers() {
public static Set<String> getPossibleInstanceMembers() {
return ourPossibleFields;
}
@@ -8,7 +8,7 @@ import com.jetbrains.python.PyNames;
import com.jetbrains.python.PyTokenTypes;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyReferenceExpression;
import com.jetbrains.python.psi.PyResolveUtil;
import com.jetbrains.python.psi.resolve.PyResolveUtil;
import com.jetbrains.python.psi.impl.PyBuiltinCache;
/**
@@ -0,0 +1,4 @@
def foo(a):
pass
bar = 2
@@ -0,0 +1,3 @@
from AddToImportFromFoo import bar
<warning descr="Unresolved reference 'foo'">foo</warning> # must get imported
@@ -0,0 +1,3 @@
from AddToImportFromFoo import bar, foo
foo # must get imported
@@ -0,0 +1,3 @@
import QualifyByImportFoo
<warning descr="Unresolved reference 'foo'">foo</warning> # must be qualified
@@ -0,0 +1 @@
foo = "yes"
@@ -19,7 +19,7 @@ import java.util.Collection;
import java.util.List;
/**
* Test action that various inspections add.
* Test actions that various inspections add.
* User: dcheryasov
* Date: Nov 29, 2008 12:47:08 AM
*/
@@ -29,6 +29,22 @@ public class QuickFixTest extends DaemonAnalyzerTestCase {
doInspectionTest("AddImport.py", PyUnresolvedReferencesInspection.class, PyBundle.message("ACT.NAME.add.import"), true, true);
}
public void testQualifyByImport() throws Exception {
doInspectionTest(
new String[]{"QualifyByImport.py", "QualifyByImportFoo.py"},
PyUnresolvedReferencesInspection.class, "QualifyByImportFoo.foo?", true, false
);
}
public void testAddToImportFromList() throws Exception {
doInspectionTest(
new String[]{"AddToImportFromList.py", "AddToImportFromFoo.py"},
PyUnresolvedReferencesInspection.class, "foo(a) from AddToImportFromFoo?", true, false
);
}
// TODO: add a test for multiple variants of above
public void testAddSelf() throws Exception {
doInspectionTest("AddSelf.py", PyMethodParametersInspection.class, PyBundle.message("QFIX.add.parameter.self"), true, true);
}
@@ -92,7 +108,7 @@ public class QuickFixTest extends DaemonAnalyzerTestCase {
}
protected String getTestDataPath() {
return PathManager.getHomePath() + "/plugins/python/testData/";
return PathManager.getHomePath() + "/plugins/python/testData/inspections/";
}
protected void doInspectionTest(String testFileName,
@@ -100,14 +116,31 @@ public class QuickFixTest extends DaemonAnalyzerTestCase {
String quickFixName,
boolean applyFix,
boolean available
) throws Exception {
doInspectionTest(new String[]{testFileName}, inspectionClass, quickFixName, applyFix, available);
}
/**
* Runs daemon passes and looks for given fix within infos.
* @param testFiles names of files to participate; first is used for inspection and then for check by "_after".
* @param inspectionClass what inspection to run
* @param quickFixName how the resulting fix should be named (the human-readable name users see)
* @param applyFix true if the fix needs to be applied
* @param available true if the fix should be available, false if it should be explicitly not available.
* @throws Exception
*/
protected void doInspectionTest(String[] testFiles,
final Class inspectionClass,
String quickFixName,
boolean applyFix,
boolean available
) throws Exception {
final LocalInspectionTool tool = (LocalInspectionTool)inspectionClass.newInstance();
enableInspectionTool(tool);
final String s = "inspections/" + testFileName;
configureByFile(s);
configureByFiles(null, testFiles);
Collection<HighlightInfo> infos = doDoTest(true, false);
doQuickFixTest(infos, quickFixName, applyFix, available, s);
doQuickFixTest(infos, quickFixName, applyFix, available, testFiles[0]);
disableInspectionTool(tool.getShortName());
}