diff --git a/python/src/com/jetbrains/pyqt/QtFileType.java b/python/src/com/jetbrains/pyqt/QtFileType.java index 8aeeb99cc653..0a5c3817291e 100644 --- a/python/src/com/jetbrains/pyqt/QtFileType.java +++ b/python/src/com/jetbrains/pyqt/QtFileType.java @@ -10,11 +10,10 @@ import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDirectory; -import com.intellij.psi.PsiElement; -import com.jetbrains.python.psi.impl.PyQualifiedName; -import com.jetbrains.python.psi.resolve.ResolveImportUtil; +import com.jetbrains.python.psi.resolve.QualifiedNameResolver; import com.jetbrains.python.sdk.PythonSdkType; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.io.IOException; @@ -108,14 +107,14 @@ public abstract class QtFileType implements FileType, INativeFileType { return null; } + @Nullable private static String findToolInPackage(String toolName, Module module, Sdk sdk, String name) { - List elements = ResolveImportUtil.resolveModulesInRootProvider(sdk.getRootProvider(), module, PyQualifiedName.fromComponents(name)); - for (PsiElement psiElement : elements) { - if (psiElement instanceof PsiDirectory) { - VirtualFile tool = ((PsiDirectory)psiElement).getVirtualFile().findChild(toolName + ".exe"); - if (tool != null) { - return tool.getPath(); - } + QualifiedNameResolver visitor = new QualifiedNameResolver(name).fromModule(module).withSdk(sdk); + List elements = visitor.resultsOfType(PsiDirectory.class); + for (PsiDirectory directory : elements) { + VirtualFile tool = directory.getVirtualFile().findChild(toolName + ".exe"); + if (tool != null) { + return tool.getPath(); } } return null; diff --git a/python/src/com/jetbrains/python/actions/GenerateBinaryStubsFix.java b/python/src/com/jetbrains/python/actions/GenerateBinaryStubsFix.java index 579e0a87156b..b04e96a1d5a3 100644 --- a/python/src/com/jetbrains/python/actions/GenerateBinaryStubsFix.java +++ b/python/src/com/jetbrains/python/actions/GenerateBinaryStubsFix.java @@ -13,7 +13,7 @@ import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiReference; import com.jetbrains.python.psi.*; -import com.jetbrains.python.psi.impl.PyImportReferenceImpl; +import com.jetbrains.python.psi.impl.PyImportReference; import com.jetbrains.python.sdk.IronPythonSdkFlavor; import com.jetbrains.python.sdk.PySkeletonRefresher; import com.jetbrains.python.sdk.PythonSdkFlavor; @@ -92,7 +92,7 @@ public class GenerateBinaryStubsFix implements LocalQuickFix { } public static boolean isApplicable(PsiReference ref) { - if (!(ref instanceof PyImportReferenceImpl)) { + if (!(ref instanceof PyImportReference)) { return false; } final Sdk sdk = getPythonSdk(ref); diff --git a/python/src/com/jetbrains/python/actions/ReplaceBuiltinsQuickFix.java b/python/src/com/jetbrains/python/actions/ReplaceBuiltinsQuickFix.java index b3bd943835fe..d9187a1f3aae 100644 --- a/python/src/com/jetbrains/python/actions/ReplaceBuiltinsQuickFix.java +++ b/python/src/com/jetbrains/python/actions/ReplaceBuiltinsQuickFix.java @@ -32,7 +32,7 @@ public class ReplaceBuiltinsQuickFix implements LocalQuickFix { PsiElement importStatement = descriptor.getPsiElement(); if (importStatement instanceof PyImportStatement) { for (PyImportElement importElement : ((PyImportStatement)importStatement).getImportElements()) { - PyReferenceExpression importReference = importElement.getImportReference(); + PyReferenceExpression importReference = importElement.getImportReferenceExpression(); if (importReference != null) { if ("__builtin__".equals(importReference.getName())) { importReference.replace(elementGenerator.createFromText(LanguageLevel.getDefault(), PyReferenceExpression.class, "builtins")); diff --git a/python/src/com/jetbrains/python/codeInsight/PyDynamicMember.java b/python/src/com/jetbrains/python/codeInsight/PyDynamicMember.java index dec572fb8f8d..c1577ab11da4 100644 --- a/python/src/com/jetbrains/python/codeInsight/PyDynamicMember.java +++ b/python/src/com/jetbrains/python/codeInsight/PyDynamicMember.java @@ -108,7 +108,7 @@ public class PyDynamicMember { } @Nullable - public PsiElement resolve(PsiElement context) { + public PsiElement resolve(@NotNull PsiElement context) { if (myTarget != null) { return myTarget; } @@ -124,7 +124,7 @@ public class PyDynamicMember { } @Nullable - private PsiElement findResolveTarget(PsiElement context) { + private PsiElement findResolveTarget(@NotNull PsiElement context) { if (myPsiPath != null) { return myPsiPath.resolve(context); } diff --git a/python/src/com/jetbrains/python/codeInsight/PyPsiPath.java b/python/src/com/jetbrains/python/codeInsight/PyPsiPath.java index 2ee5ac0fe4db..c772e3fcd6ed 100644 --- a/python/src/com/jetbrains/python/codeInsight/PyPsiPath.java +++ b/python/src/com/jetbrains/python/codeInsight/PyPsiPath.java @@ -3,12 +3,10 @@ package com.jetbrains.python.codeInsight; import com.intellij.psi.PsiElement; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.PyQualifiedName; -import com.jetbrains.python.psi.resolve.ResolveImportUtil; +import com.jetbrains.python.psi.resolve.QualifiedNameResolver; import com.jetbrains.python.psi.stubs.PyClassNameIndex; import org.jetbrains.annotations.Nullable; -import java.util.List; - /** * @author yole */ @@ -26,8 +24,8 @@ public abstract class PyPsiPath { @Nullable @Override public PsiElement resolve(PsiElement context) { - final List elements = ResolveImportUtil.resolveModulesInRoots(myQualifiedName, context); - return elements.size() > 0 ? elements.get(0) : null; + QualifiedNameResolver visitor = new QualifiedNameResolver(myQualifiedName).fromElement(context); + return visitor.firstResult(); } } diff --git a/python/src/com/jetbrains/python/codeInsight/imports/PythonReferenceImporter.java b/python/src/com/jetbrains/python/codeInsight/imports/PythonReferenceImporter.java index 5cd1f6138b3d..d7527c6e8c97 100644 --- a/python/src/com/jetbrains/python/codeInsight/imports/PythonReferenceImporter.java +++ b/python/src/com/jetbrains/python/codeInsight/imports/PythonReferenceImporter.java @@ -52,7 +52,7 @@ public class PythonReferenceImporter implements ReferenceImporter { if (refExpr.getQualifier() == null) { final PsiPolyVariantReference reference = refExpr.getReference(); if (reference.resolve() == null) { - AutoImportQuickFix fix = proposeImportFix(refExpr, reference, refExpr.getText()); + AutoImportQuickFix fix = proposeImportFix(refExpr, reference); if (fix != null && fix.getCandidatesCount() == 1) { fix.invoke(file); } @@ -67,14 +67,17 @@ public class PythonReferenceImporter implements ReferenceImporter { private static TokenSet IS_IMPORT_STATEMENT = TokenSet.create(PyElementTypes.IMPORT_STATEMENT); @Nullable - public static AutoImportQuickFix proposeImportFix(final PyElement node, PsiReference reference, String ref_text) { + public static AutoImportQuickFix proposeImportFix(final PyElement node, PsiReference reference) { + final String text = reference.getElement().getText(); + final String refText = reference.getRangeInElement().substring(text); // text of the part we're working with + // don't propose meaningless auto imports if no interpreter is configured final Module module = ModuleUtil.findModuleForPsiElement(node); if (module != null && PythonSdkType.findPythonSdk(module) == null) { return null; } PsiFile existing_import_file = null; // if there's a matching existing import, this it the file it imports - AutoImportQuickFix fix = new AutoImportQuickFix(node, reference, ref_text, !PyCodeInsightSettings.getInstance().PREFER_FROM_IMPORT); + AutoImportQuickFix fix = new AutoImportQuickFix(node, reference, refText, !PyCodeInsightSettings.getInstance().PREFER_FROM_IMPORT); Set seen_file_names = new HashSet(); // true import names // maybe the name is importable via some existing 'import foo' statement, and only needs a qualifier. // walk up collecting all such statements and analyzing @@ -84,14 +87,14 @@ public class PythonReferenceImporter implements ReferenceImporter { if (result.size() > 0) { for (PsiElement stmt : import_prc.getResult()) { for (PyImportElement ielt : ((PyImportStatement)stmt).getImportElements()) { - final PyReferenceExpression src = ielt.getImportReference(); + final PyReferenceExpression src = ielt.getImportReferenceExpression(); if (src != null) { PsiElement dst = src.getReference().resolve(); if (dst instanceof PyFile) { PyFile dst_file = (PyFile)dst; - String name = ielt.getImportReference().getReferencedName(); // ref is ok or matching would fail + String name = ielt.getImportReferenceExpression().getReferencedName(); // ref is ok or matching would fail seen_file_names.add(name); - PsiElement res = dst_file.findExportedName(ref_text); + PsiElement res = dst_file.findExportedName(refText); if (res != null && !(res instanceof PyFile) && !(res instanceof PyImportElement) && dst_file.equals(res.getContainingFile())) { existing_import_file = dst_file; fix.addImport(res, dst_file, ielt); @@ -107,14 +110,14 @@ public class PythonReferenceImporter implements ReferenceImporter { // NOTE: current indices have limitations, only finding direct definitions of classes and functions. Project project = node.getProject(); List symbols = new ArrayList(); - symbols.addAll(PyClassNameIndex.find(ref_text, project, true)); + symbols.addAll(PyClassNameIndex.find(refText, project, true)); GlobalSearchScope scope = PyClassNameIndex.projectWithLibrariesScope(project); if (!isQualifier(node)) { - symbols.addAll(PyFunctionNameIndex.find(ref_text, project, scope)); + symbols.addAll(PyFunctionNameIndex.find(refText, project, scope)); } - symbols.addAll(PyVariableNameIndex.find(ref_text, project, scope)); + symbols.addAll(PyVariableNameIndex.find(refText, project, scope)); if (!isCall(node)) { - symbols.addAll(findImportableModules(node.getContainingFile(), ref_text, project, scope)); + symbols.addAll(findImportableModules(node.getContainingFile(), refText, project, scope)); } if (symbols.size() > 0) { for (PsiElement symbol : symbols) { @@ -125,7 +128,7 @@ public class PythonReferenceImporter implements ReferenceImporter { PyQualifiedName import_path = ResolveImportUtil.findCanonicalImportPath(srcfile, node); if (import_path != null && !seen_file_names.contains(import_path.toString())) { // a new, valid hit - fix.addImport(symbol, srcfile, import_path, proposeAsName(node.getContainingFile(), ref_text, import_path)); + fix.addImport(symbol, srcfile, import_path, proposeAsName(node.getContainingFile(), refText, import_path)); seen_file_names.add(import_path.toString()); // just in case, again } } @@ -133,7 +136,7 @@ public class PythonReferenceImporter implements ReferenceImporter { } } for(PyImportCandidateProvider provider: Extensions.getExtensions(PyImportCandidateProvider.EP_NAME)) { - provider.addImportCandidates(reference, ref_text, fix); + provider.addImportCandidates(reference, refText, fix); } if (fix.getCandidatesCount() > 0) { fix.sortCandidates(); diff --git a/python/src/com/jetbrains/python/codeInsight/intentions/ImportFromToImportIntention.java b/python/src/com/jetbrains/python/codeInsight/intentions/ImportFromToImportIntention.java index 51e4c2c22de5..0700bdad3bda 100644 --- a/python/src/com/jetbrains/python/codeInsight/intentions/ImportFromToImportIntention.java +++ b/python/src/com/jetbrains/python/codeInsight/intentions/ImportFromToImportIntention.java @@ -72,7 +72,7 @@ public class ImportFromToImportIntention implements IntentionAction { ret.myModuleReference = ret.myFromImportStatement.getImportSource(); } if (ret.myModuleReference != null) { - ret.myModuleName = PyResolveUtil.toPath(ret.myModuleReference, "."); + ret.myModuleName = PyResolveUtil.toPath(ret.myModuleReference); } return ret; } @@ -106,7 +106,7 @@ public class ImportFromToImportIntention implements IntentionAction { if (info.myModuleReference != null) { PyExpression remaining_module = info.myModuleReference.getQualifier(); if (remaining_module instanceof PyQualifiedExpression) { - remaining_name = PyResolveUtil.toPath((PyQualifiedExpression)remaining_module, "."); + remaining_name = PyResolveUtil.toPath((PyQualifiedExpression)remaining_module); } else remaining_name = ""; // unqualified name: "...module" separated_name = info.myModuleReference.getReferencedName(); @@ -142,7 +142,7 @@ public class ImportFromToImportIntention implements IntentionAction { if (info.myRelativeLevel > 0) { // make sure we aren't importing a module from the relative path for (PyImportElement import_element : info.myFromImportStatement.getImportElements()) { - PyReferenceExpression ref = import_element.getImportReference(); + PyReferenceExpression ref = import_element.getImportReferenceExpression(); if (ref != null && ref.isValid()) { PsiElement target = ref.getReference().resolve(); if (target instanceof PyExpression && ((PyExpression)target).getType(TypeEvalContext.fast()) instanceof PyModuleType) return false; @@ -152,7 +152,7 @@ public class ImportFromToImportIntention implements IntentionAction { } } if (info.myModuleReference != null) { - info.myModuleName = PyResolveUtil.toPath(info.myModuleReference, "."); + info.myModuleName = PyResolveUtil.toPath(info.myModuleReference); } if (info.myModuleReference != null && info.myModuleName != null && info.myFromImportStatement != null) { myText = info.getText(); @@ -246,7 +246,7 @@ public class ImportFromToImportIntention implements IntentionAction { PyImportElement ielt = entry.getValue(); if (ielt.getAsNameElement() != null) { // we have an alias, replace it with real name - PyReferenceExpression refex = ielt.getImportReference(); + PyReferenceExpression refex = ielt.getImportReferenceExpression(); assert refex != null; // else we won't resolve to this ielt String real_name = refex.getReferencedName(); ASTNode new_qualifier = generator.createExpressionFromText(real_name).getNode(); diff --git a/python/src/com/jetbrains/python/codeInsight/intentions/ImportToImportFromIntention.java b/python/src/com/jetbrains/python/codeInsight/intentions/ImportToImportFromIntention.java index ca041cd159e0..7a7767b6d8b8 100644 --- a/python/src/com/jetbrains/python/codeInsight/intentions/ImportToImportFromIntention.java +++ b/python/src/com/jetbrains/python/codeInsight/intentions/ImportToImportFromIntention.java @@ -77,9 +77,9 @@ public class ImportToImportFromIntention implements IntentionAction { assert myImportElement != null : "isAvailable() must have returned true, but myImportElement is null"; // usages of imported name are qualifiers; what they refer to? - PyReferenceExpression reference = myImportElement.getImportReference(); + PyReferenceExpression reference = myImportElement.getImportReferenceExpression(); if (reference != null) { - myModuleName = PyResolveUtil.toPath(reference, "."); + myModuleName = PyResolveUtil.toPath(reference); myQualifierName = myImportElement.getVisibleName(); myReferee = reference.getReference().resolve(); myHasModuleReference = false; @@ -89,7 +89,7 @@ public class ImportToImportFromIntention implements IntentionAction { public boolean execute(@NotNull PsiElement element) { if (element instanceof PyReferenceExpression && PsiTreeUtil.getParentOfType(element, PyImportElement.class) == null) { PyReferenceExpression ref = (PyReferenceExpression)element; - if (myQualifierName.equals(PyResolveUtil.toPath(ref, "."))) { // filter out other names that might resolve to our target + if (myQualifierName.equals(PyResolveUtil.toPath(ref))) { // filter out other names that might resolve to our target PsiElement parent_elt = ref.getParent(); if (parent_elt instanceof PyQualifiedExpression) { // really qualified by us, not just referencing? PsiElement resolved = ref.getReference().resolve(); @@ -108,7 +108,7 @@ public class ImportToImportFromIntention implements IntentionAction { public void invoke() { assert myImportElement != null : "isAvailable() must have returned true, but myImportElement is null"; - PyUtil.sure(myImportElement.getImportReference()); + PyUtil.sure(myImportElement.getImportReferenceExpression()); Project project = myImportElement.getProject(); // usages of imported name are qualifiers; what they refer to? @@ -181,8 +181,8 @@ public class ImportToImportFromIntention implements IntentionAction { public String getText() { String module_name = "?"; if (myImportElement != null) { - PyReferenceExpression reference = myImportElement.getImportReference(); - if (reference != null) module_name = PyResolveUtil.toPath(reference, "."); + PyReferenceExpression reference = myImportElement.getImportReferenceExpression(); + if (reference != null) module_name = PyResolveUtil.toPath(reference); } return PyBundle.message("INTN.convert.to.from.$0.import.$1", getDots()+module_name, "..."); } diff --git a/python/src/com/jetbrains/python/codeInsight/intentions/ImportToggleAliasIntention.java b/python/src/com/jetbrains/python/codeInsight/intentions/ImportToggleAliasIntention.java index 184e71e284e6..94b67f5d0283 100644 --- a/python/src/com/jetbrains/python/codeInsight/intentions/ImportToggleAliasIntention.java +++ b/python/src/com/jetbrains/python/codeInsight/intentions/ImportToggleAliasIntention.java @@ -64,7 +64,7 @@ public class ImportToggleAliasIntention implements IntentionAction { return false; } } - final PyReferenceExpression referenceExpression = myImportElement.getImportReference(); + final PyReferenceExpression referenceExpression = myImportElement.getImportReferenceExpression(); if (referenceExpression == null || referenceExpression.getReference().resolve() == null) { return false; } @@ -74,7 +74,7 @@ public class ImportToggleAliasIntention implements IntentionAction { public String getText() { String add_name = "Add alias"; if (myImportElement != null) { - PyReferenceExpression refex = myImportElement.getImportReference(); + PyReferenceExpression refex = myImportElement.getImportReferenceExpression(); if (refex != null) { add_name = PyBundle.message("INTN.add.alias.for.import.$0", refex.getText()); } @@ -108,10 +108,10 @@ public class ImportToggleAliasIntention implements IntentionAction { // final String target_name; // we set in in the source final String remove_name; // we replace it in the source - PyReferenceExpression reference = sure(state.myImportElement.getImportReference()); + PyReferenceExpression reference = sure(state.myImportElement.getImportReferenceExpression()); // search for references to us with the right name try { - String imported_name = PyResolveUtil.toPath(reference, "."); + String imported_name = PyResolveUtil.toPath(reference); if (state.myAlias != null) { // have to remove alias, rename everything to original target_name = imported_name; @@ -150,7 +150,7 @@ public class ImportToggleAliasIntention implements IntentionAction { public boolean execute(@NotNull PsiElement element) { if (element instanceof PyReferenceExpression && PsiTreeUtil.getParentOfType(element, PyImportElement.class) == null) { PyReferenceExpression ref = (PyReferenceExpression)element; - if (remove_name.equals(PyResolveUtil.toPath(ref, "."))) { // filter out other names that might resolve to our target + if (remove_name.equals(PyResolveUtil.toPath(ref))) { // filter out other names that might resolve to our target PsiElement resolved = ref.getReference().resolve(); if (resolved == referee) references.add(ref.getReference()); } diff --git a/python/src/com/jetbrains/python/documentation/DocStringTypeReference.java b/python/src/com/jetbrains/python/documentation/DocStringTypeReference.java index d34e80e7f49f..a6fdae0442ea 100644 --- a/python/src/com/jetbrains/python/documentation/DocStringTypeReference.java +++ b/python/src/com/jetbrains/python/documentation/DocStringTypeReference.java @@ -78,7 +78,7 @@ public class DocStringTypeReference extends PsiReferenceBase { } if (myType instanceof PyImportedModuleType) { final PyImportedModule module = ((PyImportedModuleType)myType).getImportedModule(); - return PyUtil.turnDirIntoInit(ResolveImportUtil.resolveInRoots(module.getContainingFile(), module.getImportedPrefix())); + return PyUtil.turnDirIntoInit(ResolveImportUtil.resolveModuleInRoots(module.getImportedPrefix(), module.getContainingFile())); } return null; } diff --git a/python/src/com/jetbrains/python/documentation/DocumentationBuilder.java b/python/src/com/jetbrains/python/documentation/DocumentationBuilder.java index 2507436b7238..1c2ba537f885 100644 --- a/python/src/com/jetbrains/python/documentation/DocumentationBuilder.java +++ b/python/src/com/jetbrains/python/documentation/DocumentationBuilder.java @@ -19,10 +19,7 @@ import com.jetbrains.python.console.PyConsoleUtil; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.PyBuiltinCache; import com.jetbrains.python.psi.impl.PyCallExpressionHelper; -import com.jetbrains.python.psi.resolve.PyResolveContext; -import com.jetbrains.python.psi.resolve.QualifiedResolveResult; -import com.jetbrains.python.psi.resolve.ResolveImportUtil; -import com.jetbrains.python.psi.resolve.RootVisitor; +import com.jetbrains.python.psi.resolve.*; import com.jetbrains.python.psi.types.PyClassType; import com.jetbrains.python.psi.types.PyType; import com.jetbrains.python.psi.types.TypeEvalContext; @@ -498,7 +495,7 @@ class DocumentationBuilder { } else { RootFinder finder = new RootFinder(path); - ResolveImportUtil.visitRoots(followed, finder); + RootVisitorHost.visitRoots(followed, finder); final String root_path = finder.getResult(); if (root_path != null) { String after_part = path.substring(root_path.length()); diff --git a/python/src/com/jetbrains/python/findUsages/PyUsageTypeProvider.java b/python/src/com/jetbrains/python/findUsages/PyUsageTypeProvider.java index 14c695dab65e..cc8a6f19ac26 100644 --- a/python/src/com/jetbrains/python/findUsages/PyUsageTypeProvider.java +++ b/python/src/com/jetbrains/python/findUsages/PyUsageTypeProvider.java @@ -14,8 +14,6 @@ import com.jetbrains.python.psi.types.PyTypeReference; import com.jetbrains.python.psi.types.TypeEvalContext; import org.jetbrains.annotations.Nullable; -import java.util.Set; - /** * @author yole */ @@ -79,8 +77,8 @@ public class PyUsageTypeProvider implements UsageTypeProviderEx { final PsiElement element = ((PsiElementUsageTarget)targets[0]).getElement(); if (element instanceof PyFunction) { PyFunction function = (PyFunction) element; - final Set flags = PyUtil.detectDecorationsAndWrappersOf(function); - PyCallExpression.PyMarkedCallee callee = new PyCallExpression.PyMarkedCallee(function, flags, 1, true); + final PyFunction.Modifier modifier = function.getModifier(); + PyCallExpression.PyMarkedCallee callee = new PyCallExpression.PyMarkedCallee(function, modifier, 1, true); CallArgumentsMappingImpl mapping = new CallArgumentsMappingImpl(call.getArgumentList()); mapping.mapArguments(callee, null); if (mapping.hasProblems()) { diff --git a/python/src/com/jetbrains/python/inspections/PyCallByClassInspection.java b/python/src/com/jetbrains/python/inspections/PyCallByClassInspection.java index 3938df79eefe..1d3b033b62d9 100644 --- a/python/src/com/jetbrains/python/inspections/PyCallByClassInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyCallByClassInspection.java @@ -14,8 +14,8 @@ import org.jetbrains.annotations.NotNull; import java.util.Map; -import static com.jetbrains.python.psi.PyFunction.Flag.CLASSMETHOD; -import static com.jetbrains.python.psi.PyFunction.Flag.STATICMETHOD; +import static com.jetbrains.python.psi.PyFunction.Modifier.CLASSMETHOD; +import static com.jetbrains.python.psi.PyFunction.Modifier.STATICMETHOD; /** * Checks for for calls like X.method(y,...), where y is not an instance of X. @@ -77,7 +77,7 @@ public class PyCallByClassInspection extends PyInspection { if (arglist != null) { CallArgumentsMapping analysis = arglist.analyzeCall(resolveWithoutImplicits()); final PyCallExpression.PyMarkedCallee markedCallee = analysis.getMarkedCallee(); - if (markedCallee != null && !markedCallee.getFlags().contains(STATICMETHOD)) { + if (markedCallee != null && markedCallee.getModifier() != STATICMETHOD) { PyParameter[] params = markedCallee.getCallable().getParameterList().getParameters(); if (params.length > 0 && params[0] instanceof PyNamedParameter) { PyNamedParameter first_param = (PyNamedParameter)params[0]; @@ -89,7 +89,7 @@ public class PyCallByClassInspection extends PyInspection { PyType first_arg_type = myTypeEvalContext.getType(first_arg); if (first_arg_type instanceof PyClassType) { final PyClassType first_arg_class_type = (PyClassType)first_arg_type; - if (first_arg_class_type.isDefinition() && !markedCallee.getFlags().contains(CLASSMETHOD)) { + if (first_arg_class_type.isDefinition() && markedCallee.getModifier() != CLASSMETHOD) { registerProblem( first_arg, PyBundle.message("INSP.instance.of.$0.excpected", qual_class.getQualifiedName()) diff --git a/python/src/com/jetbrains/python/inspections/PyMethodFirstArgAssignmentInspection.java b/python/src/com/jetbrains/python/inspections/PyMethodFirstArgAssignmentInspection.java index 0716e5c352cc..0b86d1ad3f55 100644 --- a/python/src/com/jetbrains/python/inspections/PyMethodFirstArgAssignmentInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyMethodFirstArgAssignmentInspection.java @@ -12,7 +12,6 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.List; -import java.util.Set; /** * Reports assignment to 'self' or 'cls'. @@ -64,8 +63,8 @@ public class PyMethodFirstArgAssignmentInspection extends PyInspection { final String first_param_name = first_parm.getName(); if (first_param_name == null || first_param_name.length() < 1) return null; // ignore cases of incorrect code // is it a static method? - Set flagSet = PyUtil.detectDecorationsAndWrappersOf(method); - if (flagSet.contains(PyFunction.Flag.STATICMETHOD)) return null; // these may do whatever they please + PyFunction.Modifier modifier = method.getModifier(); + if (modifier == PyFunction.Modifier.STATICMETHOD) return null; // these may do whatever they please return first_param_name; } diff --git a/python/src/com/jetbrains/python/inspections/PyMethodParametersInspection.java b/python/src/com/jetbrains/python/inspections/PyMethodParametersInspection.java index d8b6f449bc43..2954872e93c0 100644 --- a/python/src/com/jetbrains/python/inspections/PyMethodParametersInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyMethodParametersInspection.java @@ -13,6 +13,7 @@ import com.jetbrains.python.PyNames; import com.jetbrains.python.actions.AddSelfQuickFix; import com.jetbrains.python.actions.RenameParameterQuickFix; import com.jetbrains.python.psi.*; +import com.jetbrains.python.psi.impl.PyQualifiedName; import com.jetbrains.python.psi.resolve.ResolveImportUtil; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NonNls; @@ -56,7 +57,7 @@ public class PyMethodParametersInspection extends PyInspection { synchronized (this) { // other threads would wait as long in resolveInRoots() anyway if (myPossibleZopeRef == null) { myPossibleZopeRef = new Ref(); - ret = ResolveImportUtil.resolveInRoots(foothold, "zope.interface.Interface"); + ret = ResolveImportUtil.resolveModuleInRoots(PyQualifiedName.fromComponents("zope.interface.Interface"), foothold); myPossibleZopeRef.set(ret); // null is OK } else ret = myPossibleZopeRef.get(); diff --git a/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java b/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java index 13a9c1ee892b..1044bcda2e61 100644 --- a/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java @@ -40,7 +40,7 @@ import com.jetbrains.python.console.PydevConsoleRunner; import com.jetbrains.python.documentation.DocStringParameterReference; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.PyBuiltinCache; -import com.jetbrains.python.psi.impl.PyImportReferenceImpl; +import com.jetbrains.python.psi.impl.PyImportReference; import com.jetbrains.python.psi.impl.PyImportStatementNavigator; import com.jetbrains.python.psi.impl.PyOperatorReferenceImpl; import com.jetbrains.python.psi.resolve.ImportedResolveResult; @@ -290,7 +290,7 @@ public class PyUnresolvedReferencesInspection extends PyInspection { myAllImports.remove(node.getParent()); } } - else if (reference instanceof PyImportReferenceImpl && + else if (reference instanceof PyImportReference && target == reference.getElement().getContainingFile()) { final boolean insideFromImport = PsiTreeUtil.getParentOfType(node, PyFromImportStatement.class) != null; if ((insideFromImport && PyImportStatementNavigator.getImportStatementByElement(node) != null) || !insideFromImport) { @@ -323,11 +323,10 @@ public class PyUnresolvedReferencesInspection extends PyInspection { } private void registerUnresolvedReferenceProblem(final PyElement node, final PsiReference reference, HighlightSeverity severity) { - final StringBuilder description_buf = new StringBuilder(""); // TODO: clear description_buf logic. maybe a flag is needed instead. + String description = null; final String text = reference.getElement().getText(); final String ref_text = reference.getRangeInElement().substring(text); // text of the part we're working with final PsiElement element = reference.getElement(); - final boolean ref_is_importable = PythonReferenceImporter.isImportable(element); final List actions = new ArrayList(2); if (ref_text.length() <= 0) return; // empty text, nothing to highlight final String refname = (element instanceof PyQualifiedExpression) ? ((PyQualifiedExpression)element).getReferencedName() : ref_text; @@ -342,42 +341,16 @@ public class PyUnresolvedReferencesInspection extends PyInspection { if (refex.getQualifier() != null) { final PyClassType object_type = PyBuiltinCache.getInstance(node).getObjectType(); if ((object_type != null) && object_type.getPossibleInstanceMembers().contains(refname)) return; - } else { if (LanguageLevel.forElement(node).isOlderThan(LanguageLevel.PYTHON26)) { - if (refname.equals("with")) { + if ("with".equals(refname)) { actions.add(new UnresolvedRefAddFutureImportQuickFix(refex)); } } - PyClass containedClass = PsiTreeUtil.getParentOfType(node, PyClass.class); - if (containedClass != null) { - for (PyTargetExpression target : containedClass.getInstanceAttributes()) { - if (Comparing.strEqual(node.getName(), target.getName())) { - actions.add(new UnresolvedReferenceAddSelfQuickFix(refex)); - } - } - for (PyStatement statement : containedClass.getStatementList().getStatements()) { - if (statement instanceof PyAssignmentStatement) { - if (((PyAssignmentStatement)statement).getLeftHandSideExpression().getText().equals(refex.getText())) { - PyExpression callexpr = ((PyAssignmentStatement)statement).getAssignedValue(); - if (callexpr instanceof PyCallExpression) { - PyType type = myTypeEvalContext.getType(callexpr); - if (type != null && type instanceof PyClassType) { - String name = ((PyCallExpression)callexpr).getCallee().getText(); - if (name != null && name.equals("property")) - actions.add(new UnresolvedReferenceAddSelfQuickFix(refex)); - } - } - } - } - } - for (PyFunction method : containedClass.getMethods()) { - if (refex.getText().equals(method.getName())) { - actions.add(new UnresolvedReferenceAddSelfQuickFix(refex)); - } - } - } + if (ref_text.equals("true") || ref_text.equals("false")) + actions.add(new UnresolvedRefTrueFalseQuickFix(element)); + addAddSelfFix(node, refex, actions); PyCallExpression callExpression = PsiTreeUtil.getParentOfType(element, PyCallExpression.class); if (callExpression != null) actions.add(new UnresolvedRefCreateFunctionQuickFix(callExpression, refex)); @@ -392,8 +365,7 @@ public class PyUnresolvedReferencesInspection extends PyInspection { ) != null )) { severity = HighlightSeverity.WEAK_WARNING; - String errmsg = PyBundle.message("INSP.module.$0.not.found", ref_text); - description_buf.append(errmsg); + description = PyBundle.message("INSP.module.$0.not.found", ref_text); // TODO: mark the node so that future references pointing to it won't result in a error, but in a warning } } @@ -401,11 +373,10 @@ public class PyUnresolvedReferencesInspection extends PyInspection { if (myIgnoredIdentifiers.contains(reference.getCanonicalText())) return; } - if (reference instanceof PsiReferenceEx) { - final String s = ((PsiReferenceEx)reference).getUnresolvedDescription(); - if (s != null) description_buf.append(s); + if (reference instanceof PsiReferenceEx && description == null) { + description = ((PsiReferenceEx)reference).getUnresolvedDescription(); } - if (description_buf.length() == 0) { + if (description == null) { boolean marked_qualified = false; if (element instanceof PyQualifiedExpression) { final PyQualifiedExpression qexpr = (PyQualifiedExpression)element; @@ -416,100 +387,39 @@ public class PyUnresolvedReferencesInspection extends PyInspection { if (qualifier != null) { PyType qtype = myTypeEvalContext.getType(qualifier); if (qtype != null) { - if (qtype instanceof PyNoneType || qtype instanceof PyTypeReference || - (qtype instanceof PyUnionType && ((PyUnionType) qtype).isWeak()) || - (qtype instanceof PyImportedModuleType)) { - // this almost always means that we don't know the type, so don't show an error in this case + if (ignoreUnresolvedMemberForType(qtype, reference, ref_text)) { return; } + addCreateMemberFromUsageFixes(qtype, reference, ref_text, actions); if (qtype instanceof PyClassType) { - PyClass cls = ((PyClassType)qtype).getPyClass(); - if (cls != null) { - if (overridesGetAttr(cls)) { - return; - } - if (cls.findProperty(ref_text) != null) { - return; // a property exists but accessor is not found; other inspections handle this - } - if (! PyBuiltinCache.getInstance(node).hasInBuiltins(cls)) { - if (element.getParent() instanceof PyCallExpression) { - actions.add(new AddMethodQuickFix(ref_text, (PyClassType)qtype)); - } - else if (!(reference instanceof PyOperatorReferenceImpl)) { - actions.add(new AddFieldQuickFix(ref_text, cls, "None")); - } - } - } if (reference instanceof PyOperatorReferenceImpl) { - description_buf.append(PyBundle.message("INSP.unresolved.operator.ref", - qtype.getName(), refname, - ((PyOperatorReferenceImpl)reference).getReadableOperatorName())); + description = PyBundle.message("INSP.unresolved.operator.ref", + qtype.getName(), refname, + ((PyOperatorReferenceImpl)reference).getReadableOperatorName()); } else { - description_buf.append(PyBundle.message("INSP.unresolved.ref.$0.for.class.$1", ref_text, qtype.getName())); + description = PyBundle.message("INSP.unresolved.ref.$0.for.class.$1", ref_text, qtype.getName()); } marked_qualified = true; } - else if (qtype instanceof PyModuleType) { - PsiFile file = ((PyModuleType)qtype).getModule(); - if (file instanceof PyFile) { - actions.add(new AddFunctionQuickFix(ref_text, (PyFile)file)); - } - } - else if (qtype instanceof CythonBuiltinType || - (qtype instanceof CythonType && reference instanceof PyOperatorReferenceImpl)) { - return; - } else { - description_buf.append(PyBundle.message("INSP.cannot.find.$0.in.$1", ref_text, qtype.getName())); + description = PyBundle.message("INSP.cannot.find.$0.in.$1", ref_text, qtype.getName()); marked_qualified = true; } } } } - if (! marked_qualified) { - description_buf.append(PyBundle.message("INSP.unresolved.ref.$0", ref_text)); - if (ref_text.equals("true") || ref_text.equals("false")) - actions.add(new UnresolvedRefTrueFalseQuickFix(element)); + if (!marked_qualified) { + description = PyBundle.message("INSP.unresolved.ref.$0", ref_text); // look in other imported modules for this whole name - if (ref_is_importable) { - AutoImportQuickFix importFix = PythonReferenceImporter.proposeImportFix(node, reference, ref_text); - if (importFix != null) { - // if the context doesn't look like a function call and we only found imports of functions, suggest auto-import - // as a quickfix but no popup balloon (PY-2312) - if ((isCall(node) || !importFix.hasOnlyFunctions()) && PyCodeInsightSettings.getInstance().SHOW_IMPORT_POPUP) { - final AutoImportHintAction autoImportHintAction = new AutoImportHintAction(importFix); - actions.add(autoImportHintAction); - } - else { - actions.add(importFix); - } - } + if (PythonReferenceImporter.isImportable(element)) { + addAutoImportFix(node, reference, actions); } - if (ref_text.length() > 2 && Character.isUpperCase(ref_text.charAt(0)) && !Character.isUpperCase(ref_text.charAt(1)) && - PsiTreeUtil.getParentOfType(element, PyImportStatementBase.class) == null) { - PsiElement anchor = element; - if (element instanceof PyQualifiedExpression) { - final PyExpression qexpr = ((PyQualifiedExpression)element).getQualifier(); - if (qexpr != null) { - final PyType type = myTypeEvalContext.getType(qexpr); - if (type instanceof PyModuleType) { - anchor = ((PyModuleType) type).getModule(); - } - else { - anchor = null; - } - } - if (anchor != null) { - actions.add(new CreateClassQuickFix(ref_text, anchor)); - } - } - } + addCreateClassFix(ref_text, element, actions); } } - String description = description_buf.toString(); ProblemHighlightType hl_type; if (severity == HighlightSeverity.WARNING) { hl_type = ProblemHighlightType.GENERIC_ERROR_OR_WARNING; @@ -535,6 +445,122 @@ public class PyUnresolvedReferencesInspection extends PyInspection { registerProblem(point, description, hl_type, null, range, actions.toArray(new LocalQuickFix[actions.size()])); } + private static boolean ignoreUnresolvedMemberForType(PyType qtype, PsiReference reference, String refText) { + if (qtype instanceof PyNoneType || qtype instanceof PyTypeReference || + (qtype instanceof PyUnionType && ((PyUnionType)qtype).isWeak()) || + (qtype instanceof PyImportedModuleType)) { + // this almost always means that we don't know the type, so don't show an error in this case + return true; + } + if (qtype instanceof PyClassType) { + PyClass cls = ((PyClassType)qtype).getPyClass(); + if (cls != null) { + if (overridesGetAttr(cls)) { + return true; + } + if (cls.findProperty(refText) != null) { + return true; + } + } + } + if (qtype instanceof CythonBuiltinType || + (qtype instanceof CythonType && reference instanceof PyOperatorReferenceImpl)) { + return true; + } + return false; + } + + private static void addCreateMemberFromUsageFixes(PyType qtype, PsiReference reference, String refText, List actions) { + PsiElement element = reference.getElement(); + if (qtype instanceof PyClassType) { + PyClass cls = ((PyClassType)qtype).getPyClass(); + if (cls != null) { + if (!PyBuiltinCache.getInstance(element).hasInBuiltins(cls)) { + if (element.getParent() instanceof PyCallExpression) { + actions.add(new AddMethodQuickFix(refText, (PyClassType)qtype)); + } + else if (!(reference instanceof PyOperatorReferenceImpl)) { + actions.add(new AddFieldQuickFix(refText, cls, "None")); + } + } + } + } + else if (qtype instanceof PyModuleType) { + PsiFile file = ((PyModuleType)qtype).getModule(); + if (file instanceof PyFile) { + actions.add(new AddFunctionQuickFix(refText, (PyFile)file)); + } + } + } + + private void addAddSelfFix(PyElement node, PyReferenceExpression refex, List actions) { + PyClass containedClass = PsiTreeUtil.getParentOfType(node, PyClass.class); + if (containedClass != null) { + for (PyTargetExpression target : containedClass.getInstanceAttributes()) { + if (Comparing.strEqual(node.getName(), target.getName())) { + actions.add(new UnresolvedReferenceAddSelfQuickFix(refex)); + } + } + for (PyStatement statement : containedClass.getStatementList().getStatements()) { + if (statement instanceof PyAssignmentStatement) { + if (((PyAssignmentStatement)statement).getLeftHandSideExpression().getText().equals(refex.getText())) { + PyExpression callexpr = ((PyAssignmentStatement)statement).getAssignedValue(); + if (callexpr instanceof PyCallExpression) { + PyType type = myTypeEvalContext.getType(callexpr); + if (type != null && type instanceof PyClassType) { + String name = ((PyCallExpression)callexpr).getCallee().getText(); + if (name != null && name.equals("property")) + actions.add(new UnresolvedReferenceAddSelfQuickFix(refex)); + } + } + } + } + } + for (PyFunction method : containedClass.getMethods()) { + if (refex.getText().equals(method.getName())) { + actions.add(new UnresolvedReferenceAddSelfQuickFix(refex)); + } + } + } + } + + private static void addAutoImportFix(PyElement node, PsiReference reference, List actions) { + AutoImportQuickFix importFix = PythonReferenceImporter.proposeImportFix(node, reference); + if (importFix != null) { + // if the context doesn't look like a function call and we only found imports of functions, suggest auto-import + // as a quickfix but no popup balloon (PY-2312) + if ((isCall(node) || !importFix.hasOnlyFunctions()) && PyCodeInsightSettings.getInstance().SHOW_IMPORT_POPUP) { + final AutoImportHintAction autoImportHintAction = new AutoImportHintAction(importFix); + actions.add(autoImportHintAction); + } + else { + actions.add(importFix); + } + } + } + + private void addCreateClassFix(String refText, PsiElement element, List actions) { + if (refText.length() > 2 && Character.isUpperCase(refText.charAt(0)) && !Character.isUpperCase(refText.charAt(1)) && + PsiTreeUtil.getParentOfType(element, PyImportStatementBase.class) == null) { + PsiElement anchor = element; + if (element instanceof PyQualifiedExpression) { + final PyExpression qexpr = ((PyQualifiedExpression)element).getQualifier(); + if (qexpr != null) { + final PyType type = myTypeEvalContext.getType(qexpr); + if (type instanceof PyModuleType) { + anchor = ((PyModuleType) type).getModule(); + } + else { + anchor = null; + } + } + if (anchor != null) { + actions.add(new CreateClassQuickFix(refText, anchor)); + } + } + } + } + private static boolean isCall(PyElement node) { final PyCallExpression callExpression = PsiTreeUtil.getParentOfType(node, PyCallExpression.class); return callExpression != null && node == callExpression.getCallee(); diff --git a/python/src/com/jetbrains/python/inspections/PyUnusedLocalInspectionVisitor.java b/python/src/com/jetbrains/python/inspections/PyUnusedLocalInspectionVisitor.java index 7e3aebbff87a..072998afa52a 100644 --- a/python/src/com/jetbrains/python/inspections/PyUnusedLocalInspectionVisitor.java +++ b/python/src/com/jetbrains/python/inspections/PyUnusedLocalInspectionVisitor.java @@ -245,7 +245,7 @@ public class PyUnusedLocalInspectionVisitor extends PyInspectionVisitor { if (PsiTreeUtil.getParentOfType(element, PyClass.class) != null) { // When function is inside a class, first parameter may be either self or cls which is always 'used'. final PyFunction method = PsiTreeUtil.getParentOfType(element, PyFunction.class); - if (method != null && ! PyNames.STATICMETHOD.equals(PyUtil.getClassOrStaticMethodDecorator(method))) { + if (method != null && method.getModifier() != PyFunction.Modifier.STATICMETHOD) { final PsiElement parent = namedParameter.getParent(); if (parent instanceof PyParameterList && ((PyParameterList)parent).getParameters()[0] == namedParameter) { continue; diff --git a/python/src/com/jetbrains/python/psi/PyCallExpression.java b/python/src/com/jetbrains/python/psi/PyCallExpression.java index eb453c5828fe..78e725f654c6 100644 --- a/python/src/com/jetbrains/python/psi/PyCallExpression.java +++ b/python/src/com/jetbrains/python/psi/PyCallExpression.java @@ -5,9 +5,6 @@ import com.jetbrains.python.psi.resolve.PyResolveContext; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.EnumSet; -import java.util.Set; - /** * Represents an entire call expression, like foo() or foo.bar[1]('x'). */ @@ -85,7 +82,7 @@ public interface PyCallExpression extends PyExpression { */ class PyMarkedCallee { Callable myCallable; - Set myFlags; + PyFunction.Modifier myModifier; int myImplicitOffset; boolean myImplicitlyResolved; @@ -97,16 +94,16 @@ public interface PyCallExpression extends PyExpression { * @param offset implicit argument offset; parameters up to this are implicitly filled in the call. * @param implicitlyResolved value for {@link #isImplicitlyResolved()} */ - public PyMarkedCallee(@NotNull Callable function, Set flags, int offset, boolean implicitlyResolved) { + public PyMarkedCallee(@NotNull Callable function, PyFunction.Modifier modifier, int offset, boolean implicitlyResolved) { myCallable = function; - myFlags = flags; + myModifier = modifier; myImplicitOffset = offset; myImplicitlyResolved = implicitlyResolved; } public PyMarkedCallee(Callable callable, boolean implicitlyResolved) { myCallable = callable; - myFlags = EnumSet.noneOf(PyFunction.Flag.class); + myModifier = null; myImplicitOffset = 0; myImplicitlyResolved = implicitlyResolved; } @@ -115,8 +112,8 @@ public interface PyCallExpression extends PyExpression { return myCallable; } - public Set getFlags() { - return myFlags; + public PyFunction.Modifier getModifier() { + return myModifier; } /** diff --git a/python/src/com/jetbrains/python/psi/PyFunction.java b/python/src/com/jetbrains/python/psi/PyFunction.java index 441c0d7c19a1..51512d11faae 100644 --- a/python/src/com/jetbrains/python/psi/PyFunction.java +++ b/python/src/com/jetbrains/python/psi/PyFunction.java @@ -63,10 +63,18 @@ extends */ String getDeprecationMessage(); + /** + * Looks for two standard decorators to a function, or a wrapping assignment that closely follows it. + * + * @return a flag describing what was detected. + */ + @Nullable + Modifier getModifier(); + /** * Flags that mark common alterations of a function: decoration by and wrapping in classmethod() and staticmethod(). */ - enum Flag { + enum Modifier { /** * Function is decorated with @classmethod, its first param is the class. */ diff --git a/python/src/com/jetbrains/python/psi/PyImportElement.java b/python/src/com/jetbrains/python/psi/PyImportElement.java index 47601ac1a5fa..25fdb6f72746 100644 --- a/python/src/com/jetbrains/python/psi/PyImportElement.java +++ b/python/src/com/jetbrains/python/psi/PyImportElement.java @@ -11,7 +11,7 @@ import org.jetbrains.annotations.Nullable; */ public interface PyImportElement extends PyElement, NameDefiner, StubBasedPsiElement { @Nullable - PyReferenceExpression getImportReference(); + PyReferenceExpression getImportReferenceExpression(); @Nullable PyQualifiedName getImportedQName(); diff --git a/python/src/com/jetbrains/python/psi/PyUtil.java b/python/src/com/jetbrains/python/psi/PyUtil.java index f46bda03189e..369501beb556 100644 --- a/python/src/com/jetbrains/python/psi/PyUtil.java +++ b/python/src/com/jetbrains/python/psi/PyUtil.java @@ -17,13 +17,11 @@ import com.intellij.openapi.ui.MessageType; import com.intellij.openapi.ui.popup.Balloon; import com.intellij.openapi.ui.popup.JBPopupFactory; import com.intellij.openapi.util.Condition; -import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.WindowManager; import com.intellij.psi.*; -import com.intellij.psi.stubs.StubElement; import com.intellij.psi.tree.TokenSet; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.ui.awt.RelativePoint; @@ -39,9 +37,6 @@ import com.jetbrains.python.PyTokenTypes; import com.jetbrains.python.codeInsight.stdlib.PyNamedTupleType; import com.jetbrains.python.documentation.EpydocUtil; import com.jetbrains.python.psi.impl.PyBuiltinCache; -import com.jetbrains.python.psi.impl.PyQualifiedName; -import com.jetbrains.python.psi.stubs.PyFunctionStub; -import com.jetbrains.python.psi.stubs.PyTargetExpressionStub; import com.jetbrains.python.psi.types.PyClassType; import com.jetbrains.python.psi.types.PyTupleType; import com.jetbrains.python.psi.types.PyType; @@ -58,9 +53,8 @@ import java.io.IOException; import java.util.*; import java.util.List; -import static com.jetbrains.python.psi.PyFunction.Flag.CLASSMETHOD; -import static com.jetbrains.python.psi.PyFunction.Flag.STATICMETHOD; -import static com.jetbrains.python.psi.impl.PyCallExpressionHelper.interpretAsStaticmethodOrClassmethodWrappingCall; +import static com.jetbrains.python.psi.PyFunction.Modifier.CLASSMETHOD; +import static com.jetbrains.python.psi.PyFunction.Modifier.STATICMETHOD; public class PyUtil { private PyUtil() { @@ -443,42 +437,6 @@ public class PyUtil { if (!thing) throw new IncorrectOperationException(); } - /** - * When a function is decorated many decorators, finds the deepest builtin decorator: - *
-   * @foo
-   * @classmethod # <-- that's it
-   * @bar
-   * def moo(cls):
-   *   pass
-   * 
- * @param node the allegedly decorated function - * @return name of the built-in decorator, or null (even if there are non-built-in decorators). - */ - @Nullable - public static String getClassOrStaticMethodDecorator(@NotNull final PyFunction node) { - PyDecoratorList decolist = node.getDecoratorList(); - if (decolist != null) { - PyDecorator[] decos = decolist.getDecorators(); - if (decos.length > 0) { - for (int i = decos.length - 1; i >= 0; i -= 1) { - PyDecorator deco = decos[i]; - String deconame = deco.getName(); - if (PyNames.CLASSMETHOD.equals(deconame) || PyNames.STATICMETHOD.equals(deconame)) { - return deconame; - } - for(PyKnownDecoratorProvider provider: KnownDecoratorProviderHolder.KNOWN_DECORATOR_PROVIDERS) { - String name = provider.toKnownDecorator(deconame); - if (name != null) { - return name; - } - } - } - } - } - return null; - } - public static boolean isInstanceAttribute(PyExpression target) { if (!(target instanceof PyTargetExpression)) { return false; @@ -612,80 +570,6 @@ public class PyUtil { } } - /** - * Looks for two standard decorators to a function, or a wrapping assignment that closely follows it. - * - * @param function what to analyze - * @return a set of flags describing what was detected. - */ - @NotNull - public static Set detectDecorationsAndWrappersOf(PyFunction function) { - Set flags = EnumSet.noneOf(PyFunction.Flag.class); - String deconame = getClassOrStaticMethodDecorator(function); - if (PyNames.CLASSMETHOD.equals(deconame)) { - flags.add(CLASSMETHOD); - } - else if (PyNames.STATICMETHOD.equals(deconame)) flags.add(STATICMETHOD); - // implicit staticmethod __new__ - PyClass cls = function.getContainingClass(); - if (cls != null && PyNames.NEW.equals(function.getName()) && cls.isNewStyleClass()) flags.add(STATICMETHOD); - // - if (!flags.contains(CLASSMETHOD) && !flags.contains(STATICMETHOD)) { // not set by decos, look for reassignment - final PyFunctionStub stub = function.getStub(); - if (stub != null) { - return getWrappersFromStub(stub); - } - String func_name = function.getName(); - if (func_name != null) { - PyAssignmentStatement assignment = PsiTreeUtil.getNextSiblingOfType(function, PyAssignmentStatement.class); - if (assignment != null) { - for (Pair pair : assignment.getTargetsToValuesMapping()) { - PyExpression value = pair.getSecond(); - if (value instanceof PyCallExpression) { - PyExpression target = pair.getFirst(); - if (target instanceof PyTargetExpression && func_name.equals(target.getName())) { - Pair interpreted = interpretAsStaticmethodOrClassmethodWrappingCall((PyCallExpression)value, function); - if (interpreted != null) { - PyFunction original = interpreted.getSecond(); - if (original == function) { - String wrapper_name = interpreted.getFirst(); - if (PyNames.CLASSMETHOD.equals(wrapper_name)) { - flags.add(CLASSMETHOD); - } - else if (PyNames.STATICMETHOD.equals(wrapper_name)) flags.add(STATICMETHOD); - } - } - } - } - } - } - } - } - return flags; - } - - private static Set getWrappersFromStub(PyFunctionStub stub) { - final StubElement parentStub = stub.getParentStub(); - final List childrenStubs = parentStub.getChildrenStubs(); - int index = childrenStubs.indexOf(stub); - if (index >= 0 && index < childrenStubs.size() - 1) { - StubElement nextStub = (StubElement) childrenStubs.get(index+1); - if (nextStub instanceof PyTargetExpressionStub) { - final PyTargetExpressionStub targetExpressionStub = (PyTargetExpressionStub)nextStub; - if (targetExpressionStub.getInitializerType() == PyTargetExpressionStub.InitializerType.CallExpression) { - final PyQualifiedName qualifiedName = targetExpressionStub.getInitializer(); - if (PyQualifiedName.fromComponents(PyNames.CLASSMETHOD).equals(qualifiedName)) { - return EnumSet.of(PyFunction.Flag.CLASSMETHOD); - } - if (PyQualifiedName.fromComponents(PyNames.STATICMETHOD).equals(qualifiedName)) { - return EnumSet.of(PyFunction.Flag.STATICMETHOD); - } - } - } - } - return EnumSet.noneOf(PyFunction.Flag.class); - } - /** * Returns child element in the psi tree * @@ -999,7 +883,7 @@ public class PyUtil { public static MethodFlags of(@NotNull PyFunction node) { PyClass cls = node.getContainingClass(); if (cls != null) { - Set flags = detectDecorationsAndWrappersOf(node); + PyFunction.Modifier modifier = node.getModifier(); boolean isMetaclassMethod = false; PyClass type_cls = PyBuiltinCache.getInstance(node).getClass("type"); for (PyClass ancestor_cls : cls.iterateAncestorClasses()) { @@ -1010,7 +894,7 @@ public class PyUtil { } final String method_name = node.getName(); boolean isSpecialMetaclassMethod = isMetaclassMethod && method_name != null && among(method_name, PyNames.INIT, "__call__"); - return new MethodFlags(flags.contains(CLASSMETHOD), flags.contains(STATICMETHOD), isMetaclassMethod, isSpecialMetaclassMethod); + return new MethodFlags(modifier == CLASSMETHOD, modifier == STATICMETHOD, isMetaclassMethod, isSpecialMetaclassMethod); } return null; } diff --git a/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java b/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java index 0d8e6e51c88d..c33a5de1aaeb 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java +++ b/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java @@ -139,7 +139,7 @@ public class PyCallExpressionHelper { @Nullable public static PyCallExpression.PyMarkedCallee resolveCallee(PyCallExpression us, PyResolveContext resolveContext, int implicitOffset) { - PyFunction.Flag wrappedFlag = null; + PyFunction.Modifier wrappedModifier = null; boolean isConstructorCall = false; PyExpression callee = us.getCallee(); @@ -167,17 +167,17 @@ public class PyCallExpressionHelper { resolved = wrapperInfo.getSecond(); String wrapper_name = wrapperInfo.getFirst(); if (PyNames.CLASSMETHOD.equals(wrapper_name)) { - wrappedFlag = PyFunction.Flag.CLASSMETHOD; + wrappedModifier = PyFunction.Modifier.CLASSMETHOD; } - else if (PyNames.STATICMETHOD.equals(wrapper_name)) wrappedFlag = PyFunction.Flag.STATICMETHOD; + else if (PyNames.STATICMETHOD.equals(wrapper_name)) wrappedModifier = PyFunction.Modifier.STATICMETHOD; } } if (resolved instanceof Callable) { - Set flags = resolved instanceof PyFunction - ? PyUtil.detectDecorationsAndWrappersOf((PyFunction) resolved) - : EnumSet.noneOf(PyFunction.Flag.class); - if (wrappedFlag != null) { - flags.add(wrappedFlag); + PyFunction.Modifier modifier = resolved instanceof PyFunction + ? ((PyFunction)resolved).getModifier() + : null; + if (modifier == null && wrappedModifier != null) { + modifier = wrappedModifier; } List qualifiers = resolveResult != null ? resolveResult.getQualifiers() : Collections.emptyList(); boolean isByInstance = isConstructorCall || @@ -187,16 +187,16 @@ public class PyCallExpressionHelper { boolean isByClass = lastQualifier == null ? false : isQualifiedByClass((Callable)resolved, lastQualifier, resolveContext.getTypeEvalContext()); final Callable callable = (Callable)resolved; - implicitOffset += getImplicitArgumentCount(callable, flags, isConstructorCall, isByInstance, isByClass); + implicitOffset += getImplicitArgumentCount(callable, modifier, isConstructorCall, isByInstance, isByClass); implicitOffset = implicitOffset < 0? 0: implicitOffset; // wrong source can trigger strange behaviour - return new PyCallExpression.PyMarkedCallee(callable, flags, implicitOffset, + return new PyCallExpression.PyMarkedCallee(callable, modifier, implicitOffset, resolveResult != null ? resolveResult.isImplicit() : false); } return null; } /** - * Calls the {@link #getImplicitArgumentCount(PyExpression, Callable, PyFunction.Flag, EnumSet, boolean) full version} + * Calls the {@link #getImplicitArgumentCount(PyExpression, Callable, com.jetbrains.python.psi.PyFunction.Modifier, EnumSet< com.jetbrains.python.psi.PyFunction.Modifier >, boolean) full version} * with null flags and with isByInstance inferred directly from call site (won't work with reassigned bound methods). * * @param callReference the call site, where arguments are given. @@ -215,7 +215,7 @@ public class PyCallExpressionHelper { QualifiedResolveResult followed = callReference.followAssignmentsChain(resolveContext); boolean isByInstance = isQualifiedByInstance(functionBeingCalled, followed.getQualifiers(), resolveContext.getTypeEvalContext()); boolean isByClass = isQualifiedByInstance(functionBeingCalled, followed.getQualifiers(), resolveContext.getTypeEvalContext()); - return getImplicitArgumentCount(functionBeingCalled, PyUtil.detectDecorationsAndWrappersOf(functionBeingCalled), false, isByInstance, isByClass); + return getImplicitArgumentCount(functionBeingCalled, functionBeingCalled.getModifier(), false, isByInstance, isByClass); } /** @@ -229,7 +229,7 @@ public class PyCallExpressionHelper { */ private static int getImplicitArgumentCount( Callable callable, - Set flags, + PyFunction.Modifier modifier, boolean isConstructorCall, boolean isByInstance, boolean isByClass @@ -247,10 +247,10 @@ public class PyCallExpressionHelper { } // decorators? - if (flags.contains(PyFunction.Flag.STATICMETHOD)) { + if (modifier == PyFunction.Modifier.STATICMETHOD) { if (isByInstance && implicit_offset > 0) implicit_offset -= 1; // might have marked it as implicit 'self' } - else if (flags.contains(PyFunction.Flag.CLASSMETHOD)) { + else if (modifier == PyFunction.Modifier.CLASSMETHOD) { if (!isByInstance) implicit_offset += 1; // Both Foo.method() and foo.method() have implicit the first arg } return implicit_offset; diff --git a/python/src/com/jetbrains/python/psi/impl/PyDecoratorImpl.java b/python/src/com/jetbrains/python/psi/impl/PyDecoratorImpl.java index d123a6a0486f..fe444fc6b4af 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyDecoratorImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyDecoratorImpl.java @@ -119,7 +119,7 @@ public class PyDecoratorImpl extends PyPresentableElementImpl i if (callee == null) return null; if (!hasArgumentList()) { // NOTE: that +1 thing looks fishy - callee = new PyMarkedCallee(callee.getCallable(), callee.getFlags(), callee.getImplicitOffset() + 1, callee.isImplicitlyResolved()); + callee = new PyMarkedCallee(callee.getCallable(), callee.getModifier(), callee.getImplicitOffset() + 1, callee.isImplicitlyResolved()); } return callee; } diff --git a/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java index ccf5a72267b1..fa0452ee37c6 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java @@ -2,6 +2,7 @@ package com.jetbrains.python.psi.impl; import com.intellij.lang.ASTNode; import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; @@ -25,6 +26,7 @@ import com.jetbrains.python.documentation.StructuredDocString; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.stubs.PyClassStub; import com.jetbrains.python.psi.stubs.PyFunctionStub; +import com.jetbrains.python.psi.stubs.PyTargetExpressionStub; import com.jetbrains.python.psi.types.*; import com.jetbrains.python.sdk.PythonSdkType; import org.jetbrains.annotations.NotNull; @@ -33,6 +35,10 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.util.*; +import static com.jetbrains.python.psi.PyFunction.Modifier.CLASSMETHOD; +import static com.jetbrains.python.psi.PyFunction.Modifier.STATICMETHOD; +import static com.jetbrains.python.psi.impl.PyCallExpressionHelper.interpretAsStaticmethodOrClassmethodWrappingCall; + /** * Implements PyFunction. */ @@ -416,4 +422,115 @@ public class PyFunctionImpl extends PyPresentableElementImpl imp } return super.getUseScope(); } + + /** + * Looks for two standard decorators to a function, or a wrapping assignment that closely follows it. + * + * @return a flag describing what was detected. + */ + @Nullable + public Modifier getModifier() { + String deconame = getClassOrStaticMethodDecorator(); + if (PyNames.CLASSMETHOD.equals(deconame)) { + return CLASSMETHOD; + } + else if (PyNames.STATICMETHOD.equals(deconame)) { + return STATICMETHOD; + } + // implicit staticmethod __new__ + PyClass cls = getContainingClass(); + if (cls != null && PyNames.NEW.equals(getName()) && cls.isNewStyleClass()) { + return STATICMETHOD; + } + // + if (getStub() != null) { + return getWrappersFromStub(); + } + String func_name = getName(); + if (func_name != null) { + PyAssignmentStatement assignment = PsiTreeUtil.getNextSiblingOfType(this, PyAssignmentStatement.class); + if (assignment != null) { + for (Pair pair : assignment.getTargetsToValuesMapping()) { + PyExpression value = pair.getSecond(); + if (value instanceof PyCallExpression) { + PyExpression target = pair.getFirst(); + if (target instanceof PyTargetExpression && func_name.equals(target.getName())) { + Pair interpreted = interpretAsStaticmethodOrClassmethodWrappingCall((PyCallExpression)value, this); + if (interpreted != null) { + PyFunction original = interpreted.getSecond(); + if (original == this) { + String wrapper_name = interpreted.getFirst(); + if (PyNames.CLASSMETHOD.equals(wrapper_name)) { + return CLASSMETHOD; + } + else if (PyNames.STATICMETHOD.equals(wrapper_name)) { + return STATICMETHOD; + } + } + } + } + } + } + } + } + return null; + } + + @Nullable + private Modifier getWrappersFromStub() { + final StubElement parentStub = getStub().getParentStub(); + final List childrenStubs = parentStub.getChildrenStubs(); + int index = childrenStubs.indexOf(getStub()); + if (index >= 0 && index < childrenStubs.size() - 1) { + StubElement nextStub = (StubElement) childrenStubs.get(index+1); + if (nextStub instanceof PyTargetExpressionStub) { + final PyTargetExpressionStub targetExpressionStub = (PyTargetExpressionStub)nextStub; + if (targetExpressionStub.getInitializerType() == PyTargetExpressionStub.InitializerType.CallExpression) { + final PyQualifiedName qualifiedName = targetExpressionStub.getInitializer(); + if (PyQualifiedName.fromComponents(PyNames.CLASSMETHOD).equals(qualifiedName)) { + return Modifier.CLASSMETHOD; + } + if (PyQualifiedName.fromComponents(PyNames.STATICMETHOD).equals(qualifiedName)) { + return Modifier.STATICMETHOD; + } + } + } + } + return null; + } + + /** + * When a function is decorated many decorators, finds the deepest builtin decorator: + *
+   * @foo
+   * @classmethod # <-- that's it
+   * @bar
+   * def moo(cls):
+   *   pass
+   * 
+ * @return name of the built-in decorator, or null (even if there are non-built-in decorators). + */ + @Nullable + private String getClassOrStaticMethodDecorator() { + PyDecoratorList decolist = getDecoratorList(); + if (decolist != null) { + PyDecorator[] decos = decolist.getDecorators(); + if (decos.length > 0) { + for (int i = decos.length - 1; i >= 0; i -= 1) { + PyDecorator deco = decos[i]; + String deconame = deco.getName(); + if (PyNames.CLASSMETHOD.equals(deconame) || PyNames.STATICMETHOD.equals(deconame)) { + return deconame; + } + for(PyKnownDecoratorProvider provider: PyUtil.KnownDecoratorProviderHolder.KNOWN_DECORATOR_PROVIDERS) { + String name = provider.toKnownDecorator(deconame); + if (name != null) { + return name; + } + } + } + } + } + return null; + } } diff --git a/python/src/com/jetbrains/python/psi/impl/PyImportElementImpl.java b/python/src/com/jetbrains/python/psi/impl/PyImportElementImpl.java index 4973c5469e47..cdb3760c52e7 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyImportElementImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyImportElementImpl.java @@ -42,7 +42,7 @@ public class PyImportElementImpl extends PyBaseElementImpl } @Nullable - public PyReferenceExpression getImportReference() { + public PyReferenceExpression getImportReferenceExpression() { final ASTNode node = getNode().findChildByType(PyElementTypes.REFERENCE_EXPRESSION); return node == null ? null : (PyReferenceExpression) node.getPsi(); } @@ -52,7 +52,7 @@ public class PyImportElementImpl extends PyBaseElementImpl if (stub != null) { return stub.getImportedQName(); } - final PyReferenceExpression importReference = getImportReference(); + final PyReferenceExpression importReference = getImportReferenceExpression(); return importReference != null ? importReference.asQualifiedName() : null; } @@ -120,7 +120,7 @@ public class PyImportElementImpl extends PyBaseElementImpl if (place.getContainingFile() != getContainingFile()) { return true; } - final PyReferenceExpression importRef = getImportReference(); + final PyReferenceExpression importRef = getImportReferenceExpression(); if (importRef != null) { final PsiElement element = importRef.getReference().resolve(); if (element != null) { @@ -136,7 +136,7 @@ public class PyImportElementImpl extends PyBaseElementImpl @NotNull private String getRefName(String default_name) { - PyReferenceExpression ref = getImportReference(); + PyReferenceExpression ref = getImportReferenceExpression(); if (ref != null) { String refname = ref.getName(); if (refname != null) return refname; @@ -156,7 +156,7 @@ public class PyImportElementImpl extends PyBaseElementImpl buf.append("from "); PyReferenceExpression imp_src = ((PyFromImportStatement)elt).getImportSource(); if (imp_src != null) { - buf.append(PyResolveUtil.toPath(imp_src, ".")); + buf.append(PyResolveUtil.toPath(imp_src)); } else { buf.append(""); @@ -189,7 +189,7 @@ public class PyImportElementImpl extends PyBaseElementImpl public Iterable iterateNames() { PyElement ret = getAsNameElement(); if (ret == null) { - List unwound_path = PyResolveUtil.unwindQualifiers(getImportReference()); + List unwound_path = PyResolveUtil.unwindQualifiers(getImportReferenceExpression()); if ((unwound_path != null) && (unwound_path.size() > 0)) ret = unwound_path.get(0); } if (ret == null) { diff --git a/python/src/com/jetbrains/python/psi/impl/PyImportReferenceImpl.java b/python/src/com/jetbrains/python/psi/impl/PyImportReference.java similarity index 87% rename from python/src/com/jetbrains/python/psi/impl/PyImportReferenceImpl.java rename to python/src/com/jetbrains/python/psi/impl/PyImportReference.java index 63d79ee80359..8393d095c98c 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyImportReferenceImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyImportReference.java @@ -29,14 +29,24 @@ import java.util.*; /** * @author yole */ -public class PyImportReferenceImpl extends PyReferenceImpl { +public class PyImportReference extends PyReferenceImpl { private final PyReferenceExpressionImpl myElement; - public PyImportReferenceImpl(PyReferenceExpressionImpl element, PyResolveContext context) { + public PyImportReference(PyReferenceExpressionImpl element, PyResolveContext context) { super(element, context); myElement = element; } + + @Override + public String getUnresolvedDescription() { + final PyImportStatement importStatement = PsiTreeUtil.getParentOfType(myElement, PyImportStatement.class); + if (importStatement != null) { + return "No module named " + myElement.getReferencedName(); + } + return super.getUnresolvedDescription(); + } + @NotNull @Override protected List resolveInner() { @@ -44,21 +54,37 @@ public class PyImportReferenceImpl extends PyReferenceImpl { final String referencedName = myElement.getReferencedName(); if (referencedName == null) return ret; - int default_submodule_rate = RatedResolveResult.RATE_HIGH; + int defaultSubmoduleRate = RatedResolveResult.RATE_HIGH; // names inside module take precedence over submodules final PyImportElement import_elt = PsiTreeUtil.getParentOfType(myElement, PyImportElement.class); if (import_elt != null) { if (ret.poke(ResolveImportUtil.findImportedNameInsideModule(import_elt, referencedName), RatedResolveResult.RATE_HIGH)) { - default_submodule_rate = RatedResolveResult.RATE_NORMAL; + defaultSubmoduleRate = RatedResolveResult.RATE_NORMAL; } } - List targets = ResolveImportUtil.resolveImportReference(myElement); + final PyElement parent = PsiTreeUtil.getParentOfType(myElement, PyImportElement.class, PyFromImportStatement.class); //importRef.getParent(); + List targets; + final PyQualifiedName qname = myElement.asQualifiedName(); + if (parent instanceof PyImportElement) { + targets = ResolveImportUtil.multiResolveImportElement((PyImportElement)parent, qname); + } + else if (parent instanceof PyFromImportStatement) { // "from foo import" + targets = ResolveImportUtil.resolveFromOrForeignImport((PyFromImportStatement)parent, qname); + } + else { + return ret; + } + addRatedResults(ret, defaultSubmoduleRate, targets); + return ret; + } + + private static void addRatedResults(ResolveResultList ret, int defaultSubmoduleRate, List targets) { for (PsiElement target : targets) { target = PyUtil.turnDirIntoInit(target); if (target != null) { // ignore dirs without __init__.py, worthless - int rate = default_submodule_rate; + int rate = defaultSubmoduleRate; if (target instanceof PyFile) { VirtualFile vFile = ((PyFile)target).getVirtualFile(); if (vFile != null && vFile.getLength() == 0) { @@ -68,8 +94,6 @@ public class PyImportReferenceImpl extends PyReferenceImpl { ret.poke(target, rate); } } - - return ret; } @NotNull @@ -250,17 +274,15 @@ public class PyImportReferenceImpl extends PyReferenceImpl { } private void fillFromQName(PyQualifiedName thisQName, InsertHandler insertHandler) { - final List dirs = ResolveImportUtil.resolveModulesInRoots(thisQName, myCurrentFile); - for (PsiElement dir : dirs) { - if (dir instanceof PsiDirectory) { - fillFromDir((PsiDirectory)dir, insertHandler); - } + QualifiedNameResolver visitor = new QualifiedNameResolver(thisQName).fromElement(myCurrentFile); + for (PsiDirectory dir : visitor.resultsOfType(PsiDirectory.class)) { + fillFromDir(dir, insertHandler); } } private void addImportedNames(@NotNull PyImportElement[] import_elts) { for (PyImportElement ielt : import_elts) { - PyReferenceExpression ref = ielt.getImportReference(); + PyReferenceExpression ref = ielt.getImportReferenceExpression(); if (ref != null) { String s = ref.getReferencedName(); if (s != null) myNamesAlready.add(s); diff --git a/python/src/com/jetbrains/python/psi/impl/PyImportStatementNavigator.java b/python/src/com/jetbrains/python/psi/impl/PyImportStatementNavigator.java index e6a3fd01ba4a..352c6d960b72 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyImportStatementNavigator.java +++ b/python/src/com/jetbrains/python/psi/impl/PyImportStatementNavigator.java @@ -3,7 +3,6 @@ package com.jetbrains.python.psi.impl; import com.intellij.psi.PsiElement; import com.intellij.psi.util.PsiTreeUtil; import com.jetbrains.python.psi.PyImportElement; -import com.jetbrains.python.psi.PyImportStatement; import com.jetbrains.python.psi.PyImportStatementBase; import org.jetbrains.annotations.Nullable; @@ -21,7 +20,7 @@ public class PyImportStatementNavigator { return null; } for (PyImportElement importElement : statement.getImportElements()) { - if (element == importElement || element == importElement.getImportReference()){ + if (element == importElement || element == importElement.getImportReferenceExpression()){ return statement; } } diff --git a/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java b/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java index b6fed8d9a065..589a7a541035 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java @@ -24,7 +24,6 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; -import java.util.Set; /** * @author yole @@ -157,12 +156,12 @@ public class PyNamedParameterImpl extends PyPresentableElementImpl flags = PyUtil.detectDecorationsAndWrappersOf(func); - if (params [0] == this && !flags.contains(PyFunction.Flag.STATICMETHOD)) { + final PyFunction.Modifier modifier = func.getModifier(); + if (params [0] == this && modifier != PyFunction.Modifier.STATICMETHOD) { // must be 'self' or 'cls' final PyClass containingClass = func.getContainingClass(); if (containingClass != null) { - return new PyClassType(containingClass, flags.contains(PyFunction.Flag.CLASSMETHOD)); + return new PyClassType(containingClass, modifier == PyFunction.Modifier.CLASSMETHOD); } } if (isKeywordContainer()) { diff --git a/python/src/com/jetbrains/python/psi/impl/PyQualifiedReferenceImpl.java b/python/src/com/jetbrains/python/psi/impl/PyQualifiedReference.java similarity index 98% rename from python/src/com/jetbrains/python/psi/impl/PyQualifiedReferenceImpl.java rename to python/src/com/jetbrains/python/psi/impl/PyQualifiedReference.java index 6c01a18db934..c0be37ce5362 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyQualifiedReferenceImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyQualifiedReference.java @@ -36,8 +36,8 @@ import java.util.*; /** * @author yole */ -public class PyQualifiedReferenceImpl extends PyReferenceImpl { - public PyQualifiedReferenceImpl(PyQualifiedExpression element, PyResolveContext context) { +public class PyQualifiedReference extends PyReferenceImpl { + public PyQualifiedReference(PyQualifiedExpression element, PyResolveContext context) { super(element, context); } @@ -344,9 +344,9 @@ public class PyQualifiedReferenceImpl extends PyReferenceImpl { } private static Collection collectAssignedAttributes(PyQualifiedExpression qualifier) { - List qualifier_path = PyResolveUtil.unwindQualifiersAsStrList(qualifier); - if (qualifier_path != null) { - AssignmentCollectProcessor proc = new AssignmentCollectProcessor(qualifier_path); + PyQualifiedName qualifierPath = PyQualifiedName.fromReferenceChain(PyResolveUtil.unwindQualifiers(qualifier)); + if (qualifierPath != null) { + AssignmentCollectProcessor proc = new AssignmentCollectProcessor(qualifierPath); PyResolveUtil.treeCrawlUp(proc, qualifier); return proc.getResult(); } diff --git a/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java index 4ac10960986a..fed3dace6786 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java @@ -59,7 +59,7 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere } } if (PsiTreeUtil.getParentOfType(this, PyImportElement.class, PyFromImportStatement.class) != null) { - return new PyImportReferenceImpl(this, context); + return new PyImportReference(this, context); } if (file != null) { @@ -74,7 +74,7 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere } if (qualifier != null) { - return new PyQualifiedReferenceImpl(this, context); + return new PyQualifiedReference(this, context); } return new PyReferenceImpl(this, context); diff --git a/python/src/com/jetbrains/python/psi/impl/PyStarImportElementImpl.java b/python/src/com/jetbrains/python/psi/impl/PyStarImportElementImpl.java index 32e16b817902..c8d4e4fab387 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyStarImportElementImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyStarImportElementImpl.java @@ -2,7 +2,6 @@ package com.jetbrains.python.psi.impl; import com.intellij.lang.ASTNode; import com.intellij.navigation.ItemPresentation; -import com.intellij.openapi.editor.colors.TextAttributesKey; import com.intellij.psi.PsiElement; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.containers.HashSet; @@ -29,9 +28,9 @@ public class PyStarImportElementImpl extends PyElementImpl implements PyStarImpo @NotNull public Iterable iterateNames() { if (getParent() instanceof PyFromImportStatement) { - PyFromImportStatement import_from_stmt = (PyFromImportStatement)getParent(); - PyReferenceExpression from_src = import_from_stmt.getImportSource(); - final List importedFiles = ResolveImportUtil.resolveImportReference(from_src); + PyFromImportStatement fromImportStatement = (PyFromImportStatement)getParent(); + final List importedFiles = ResolveImportUtil.resolveFromOrForeignImport(fromImportStatement, + fromImportStatement.getImportSourceQName()); ChainIterable chain = new ChainIterable(); for (PsiElement importedFile : new HashSet(importedFiles)) { // resolver gives lots of duplicates final PsiElement source = PyUtil.turnDirIntoInit(importedFile); @@ -50,9 +49,9 @@ public class PyStarImportElementImpl extends PyElementImpl implements PyStarImpo return null; } if (getParent() instanceof PyFromImportStatement) { - PyFromImportStatement import_from_stmt = (PyFromImportStatement)getParent(); - PyReferenceExpression from_src = import_from_stmt.getImportSource(); - final List importedFiles = ResolveImportUtil.resolveImportReference(from_src); + PyFromImportStatement fromImportStatement = (PyFromImportStatement)getParent(); + final List importedFiles = ResolveImportUtil.resolveFromOrForeignImport(fromImportStatement, + fromImportStatement.getImportSourceQName()); for (PsiElement importedFile : new HashSet(importedFiles)) { // resolver gives lots of duplicates final PsiElement source = PyUtil.turnDirIntoInit(importedFile); if (source instanceof PyFile) { @@ -80,11 +79,11 @@ public class PyStarImportElementImpl extends PyElementImpl implements PyStarImpo return new ItemPresentation() { private String getName() { - PyElement elt = PsiTreeUtil.getParentOfType(PyStarImportElementImpl.this, PyFromImportStatement.class); - if (elt instanceof PyFromImportStatement) { // always? who knows :) - PyReferenceExpression imp_src = ((PyFromImportStatement)elt).getImportSource(); + PyFromImportStatement elt = PsiTreeUtil.getParentOfType(PyStarImportElementImpl.this, PyFromImportStatement.class); + if (elt != null) { // always? who knows :) + PyReferenceExpression imp_src = elt.getImportSource(); if (imp_src != null) { - return PyResolveUtil.toPath(imp_src, "."); + return PyResolveUtil.toPath(imp_src); } } return ""; @@ -95,7 +94,7 @@ public class PyStarImportElementImpl extends PyElementImpl implements PyStarImpo } public String getLocationString() { - StringBuffer buf = new StringBuffer("| "); + StringBuilder buf = new StringBuilder("| "); buf.append("from ").append(getName()).append(" import *"); return buf.toString(); } diff --git a/python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java index 603cab69adb3..f02b38a3cdb5 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java @@ -402,7 +402,7 @@ public class PyTargetExpressionImpl extends PyPresentableElementImpl my_qualifier; - List my_result; - Set my_seen_names; + private final PyQualifiedName myQualifier; + private final List myResult; + private final Set mySeenNames; /** * Creates an instance to collect assignments of attributes to the object identified by 'qualifier'. @@ -28,11 +29,11 @@ public class AssignmentCollectProcessor implements PsiScopeProcessor { * * @param qualifier qualifying names, outermost first; must not be empty. */ - public AssignmentCollectProcessor(@NotNull List qualifier) { - assert qualifier.size() > 0; - my_qualifier = qualifier; - my_result = new ArrayList(); - my_seen_names = new HashSet(); + public AssignmentCollectProcessor(@NotNull PyQualifiedName qualifier) { + assert qualifier.getComponentCount() > 0; + myQualifier = qualifier; + myResult = new ArrayList(); + mySeenNames = new HashSet(); } public boolean execute(final PsiElement element, final ResolveState state) { @@ -41,19 +42,19 @@ public class AssignmentCollectProcessor implements PsiScopeProcessor { for (PyExpression ex : assignment.getTargets()) { if (ex instanceof PyTargetExpression) { final PyTargetExpression target = (PyTargetExpression)ex; - List quals = PyResolveUtil.unwindQualifiersAsStrList(target); List qualsExpr = PyResolveUtil.unwindQualifiers(target); - if (quals != null) { - if (quals.size() == my_qualifier.size() + 1 && PyResolveUtil.pathsMatchStr(quals, my_qualifier)) { + PyQualifiedName qualifiedName = PyQualifiedName.fromReferenceChain(qualsExpr); + if (qualifiedName != null) { + if (qualifiedName.getComponentCount() == myQualifier.getComponentCount() + 1 && qualifiedName.matchesPrefix(myQualifier)) { // a new attribute follows last qualifier; collect it. PyExpression last_elt = qualsExpr.get(qualsExpr.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); + if (!mySeenNames.contains(last_elt_name)) { // no dupes, only remember the latest + myResult.add(last_elt); + mySeenNames.add(last_elt_name); } } - else if (quals.size() < my_qualifier.size() + 1 && PyResolveUtil.pathsMatchStr(my_qualifier, quals)) { + else if (qualifiedName.getComponentCount() < myQualifier.getComponentCount() + 1 && myQualifier.matchesPrefix(qualifiedName)) { // qualifier(s) get redefined; collect no more. return false; } @@ -66,12 +67,12 @@ public class AssignmentCollectProcessor implements PsiScopeProcessor { } /** - * @return a collection of exressions (parts of assignment expressions) where new attributes were defined. E.g. for "a.b.c = 1", + * @return a collection of expressions (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 getResult() { - return my_result; + return myResult; } public T getHint(final Key hintKey) { diff --git a/python/src/com/jetbrains/python/psi/resolve/PyResolveUtil.java b/python/src/com/jetbrains/python/psi/resolve/PyResolveUtil.java index ce561f20aab1..9df00aedcbf6 100644 --- a/python/src/com/jetbrains/python/psi/resolve/PyResolveUtil.java +++ b/python/src/com/jetbrains/python/psi/resolve/PyResolveUtil.java @@ -15,11 +15,11 @@ import com.intellij.psi.util.PsiTreeUtil; import com.jetbrains.python.PyElementTypes; import com.jetbrains.python.PythonDialectsTokenSetProvider; import com.jetbrains.python.psi.*; +import com.jetbrains.python.psi.impl.PyQualifiedName; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collection; -import java.util.Iterator; import java.util.LinkedList; import java.util.List; @@ -232,57 +232,13 @@ public class PyResolveUtil { 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. - * - * @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. - * @see #unwindQualifiers(PyQualifiedExpression) . - */ - public static boolean pathsMatch(List source_path, List target_path) { - // turn qualifiers into lists - if ((source_path == null) || (target_path == null)) return false; - // compare until target is exhausted - Iterator 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; - } - - public static boolean pathsMatchStr(List source_path, List target_path) { - // turn qualifiers into lists - if ((source_path == null) || (target_path == null)) return false; - // compare until target is exhausted - Iterator source_iter = source_path.iterator(); - for (final String target_elt : target_path) { - if (source_iter.hasNext()) { - String source_elt = source_iter.next(); - if (!target_elt.equals(source_elt)) 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. * - * @param expr an experssion to unwind. + * @param expr an expression to unwind. * @return path as a list of ref expressions. */ + @NotNull public static List unwindQualifiers(final PyQualifiedExpression expr) { final List path = new LinkedList(); PyQualifiedExpression e = expr; @@ -305,27 +261,18 @@ public class PyResolveUtil { return path; } - public static String toPath(PyQualifiedExpression expr, String separator) { + public static String toPath(PyQualifiedExpression expr) { if (expr == null) return ""; List path = unwindQualifiers(expr); - if (path != null) { - StringBuilder buf = new StringBuilder(); - boolean is_not_first = false; - for (PyExpression ex : path) { - if (is_not_first) { - buf.append(separator); - } - else { - is_not_first = true; - } - buf.append(ex.getName()); - } - return buf.toString(); + final PyQualifiedName qName = PyQualifiedName.fromReferenceChain(path); + if (qName != null) { + return qName.toString(); } - else { - String s = expr.getName(); - return s != null ? s : ""; + String name = expr.getName(); + if (name != null) { + return name; } + return ""; } /** diff --git a/python/src/com/jetbrains/python/psi/resolve/QualifiedNameResolver.java b/python/src/com/jetbrains/python/psi/resolve/QualifiedNameResolver.java new file mode 100644 index 000000000000..b57d36ec28d2 --- /dev/null +++ b/python/src/com/jetbrains/python/psi/resolve/QualifiedNameResolver.java @@ -0,0 +1,277 @@ +package com.jetbrains.python.psi.resolve; + +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import com.intellij.facet.FacetManager; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleManager; +import com.intellij.openapi.module.ModuleUtil; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiDirectory; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiManager; +import com.jetbrains.django.facet.DjangoFacetType; +import com.jetbrains.python.console.PydevConsoleRunner; +import com.jetbrains.python.psi.impl.PyBuiltinCache; +import com.jetbrains.python.psi.impl.PyQualifiedName; +import com.jetbrains.python.sdk.PythonSdkType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +/** + * Resolves the specified qualified name in the specified context (module, all modules or a file) to a file or directory. + * + * @author yole + */ +public class QualifiedNameResolver implements RootVisitor { + boolean myCheckForPackage = true; + @Nullable private Module myModule; + private PsiFile myFootholdFile; + private final @NotNull PyQualifiedName myQualifiedName; + @NotNull PsiManager myPsiManager; + final Set results = Sets.newLinkedHashSet(); + private boolean myAcceptRootAsTopLevelPackage; + private boolean myVisitAllModules = false; + private int myRelativeLevel = -1; + private boolean myWithoutRoots; + private Sdk myWithSdk; + + public QualifiedNameResolver(@NotNull String qNameString) { + myQualifiedName = PyQualifiedName.fromDottedString(qNameString); + } + + public QualifiedNameResolver(@NotNull PyQualifiedName qName) { + myQualifiedName = qName; + } + + public QualifiedNameResolver fromElement(@NotNull PsiElement foothold) { + myFootholdFile = foothold.getContainingFile().getOriginalFile(); + myPsiManager = PsiManager.getInstance(foothold.getProject()); + setModule(ModuleUtil.findModuleForPsiElement(myFootholdFile)); + if (PydevConsoleRunner.isInPydevConsole(foothold)) { + withAllModules(); + } + return this; + } + + public QualifiedNameResolver fromModule(@NotNull Module module) { + setModule(module); + myPsiManager = PsiManager.getInstance(module.getProject()); + return this; + } + + private void setModule(@Nullable Module module) { + myModule = module; + if (module != null && FacetManager.getInstance(module).getFacetByType(DjangoFacetType.ID) != null) { + myAcceptRootAsTopLevelPackage = true; + } + } + + public QualifiedNameResolver withAllModules() { + myVisitAllModules = true; + return this; + } + + /** + * Specifies that we need to look for the name in the specified SDK (instead of the SDK assigned to the module, if any). + * + * @param sdk the SDK in which the name should be searched. + * @return this + */ + public QualifiedNameResolver withSdk(Sdk sdk) { + myWithSdk = sdk; + return this; + } + + /** + * Specifies whether we should attempt to resolve imports relative to the current file. + * + * @param relativeLevel if >= 0, we try to resolve at the specified number of levels above the current file. + * @return this + */ + public QualifiedNameResolver withRelative(int relativeLevel) { + myRelativeLevel = relativeLevel; + return this; + } + + /** + * Specifies that we should only try to resolve relative to the current file, not in roots. + * + * @return this + */ + public QualifiedNameResolver withoutRoots() { + myWithoutRoots = true; + return this; + } + + /** + * Specifies that we're looking for a file in a directory hierarchy, not a module in the Python package hierarchy + * (so we don't need to check for existence of __init__.py) + * + * @return + */ + public QualifiedNameResolver withPlainDirectories() { + myCheckForPackage = false; + return this; + } + + public boolean visitRoot(final VirtualFile root) { + if (!root.isValid()) { + return true; + } + PsiElement module = resolveInRoot(root); + if (module != null) { + results.add(module); + } + + if (myAcceptRootAsTopLevelPackage && myQualifiedName.matchesPrefix(PyQualifiedName.fromDottedString(root.getName()))) { + module = resolveInRoot(root.getParent()); + if (module != null) { + results.add(module); + } + } + + return true; + } + + @NotNull + public List resultsAsList() { + if (myFootholdFile != null && !myFootholdFile.isValid()) { + return Collections.emptyList(); + } + + if (myRelativeLevel >= 0) { + assert myFootholdFile != null; + PsiDirectory dir = myFootholdFile.getContainingDirectory(); + if (myRelativeLevel > 0) { + dir = ResolveImportUtil.stepBackFrom(myFootholdFile, myRelativeLevel); + + } + PsiElement module = resolveModuleAt(dir, null); + if (module != null) { + results.add(module); + } + } + + if (!myWithoutRoots) { + PythonPathCache cache = findMyCache(); + if (cache != null) { + final List cachedResults = cache.get(myQualifiedName); + if (cachedResults != null) { + return cachedResults; + } + } + + if (myVisitAllModules) { + for (Module mod : ModuleManager.getInstance(myPsiManager.getProject()).getModules()) { + RootVisitorHost.visitRoots(mod, false, this); + } + } + else if (myModule != null) { + final boolean otherSdk = withOtherSdk(); + RootVisitorHost.visitRoots(myModule, otherSdk, this); + if (otherSdk) { + RootVisitorHost.visitSdkRoots(myWithSdk, this); + } + } + else if (myFootholdFile != null) { + RootVisitorHost.visitSdkRoots(myFootholdFile, this); + } + else { + throw new IllegalStateException(); + } + + final ArrayList resultList = Lists.newArrayList(results); + if (cache != null) { + cache.put(myQualifiedName, resultList); + } + return resultList; + } + + return Lists.newArrayList(results); + } + + @Nullable + public PsiElement firstResult() { + final List results = resultsAsList(); + return results.size() > 0 ? results.get(0) : null; + } + + @NotNull + public List resultsOfType(Class clazz) { + List result = new ArrayList(); + for (PsiElement element : resultsAsList()) { + if (clazz.isInstance(element)) { + //noinspection unchecked + result.add((T) element); + } + } + return result; + } + + @Nullable + public T firstResultOfType(Class clazz) { + final List list = resultsOfType(clazz); + return list.size() > 0 ? list.get(0) : null; + } + + private boolean withOtherSdk() { + return myWithSdk != null && myWithSdk != PythonSdkType.findPythonSdk(myModule); + } + + @Nullable + private PythonPathCache findMyCache() { + if (myVisitAllModules) { + return null; + } + if (myModule != null) { + return withOtherSdk() ? null : PythonModulePathCache.getInstance(myModule); + } + if (myFootholdFile != null) { + final Sdk sdk = PyBuiltinCache.findSdkForFile(myFootholdFile); + if (sdk != null) { + return PythonSdkPathCache.getInstance(myPsiManager.getProject(), sdk); + } + } + return null; + } + + @Nullable + private PsiElement resolveInRoot(VirtualFile root) { + if (!root.isDirectory()) { + // if we have added a file as a root, it's unlikely that we'll be able to resolve anything under it in 'files only' resolve mode + return null; + } + return resolveModuleAt(myPsiManager.findDirectory(root), root); + } + + /** + * Searches for a module at given directory, unwinding qualifiers and traversing directories as needed. + * + * @param directory where to start from; top qualifier will be searched for here. + * @param root an SDK, library or content root from which we're searching, or null if we're searching relatively + * @return module's file, or null. + */ + @Nullable + private PsiElement resolveModuleAt(@Nullable PsiDirectory directory, @Nullable VirtualFile root) { + // prerequisites + if (directory == null || !directory.isValid()) return null; + + PsiElement seeker = directory; + for (String name : myQualifiedName.getComponents()) { + if (name == null) { + return null; + } + seeker = ResolveImportUtil.resolveChild(seeker, name, myFootholdFile, root, true, myCheckForPackage); + } + return seeker; + } + +} diff --git a/python/src/com/jetbrains/python/psi/resolve/ResolveImportUtil.java b/python/src/com/jetbrains/python/psi/resolve/ResolveImportUtil.java index 506f66ee2ee8..39ec7d59ce81 100644 --- a/python/src/com/jetbrains/python/psi/resolve/ResolveImportUtil.java +++ b/python/src/com/jetbrains/python/psi/resolve/ResolveImportUtil.java @@ -1,11 +1,7 @@ package com.jetbrains.python.psi.resolve; -import com.google.common.collect.Lists; -import com.google.common.collect.Sets; -import com.intellij.facet.FacetManager; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.module.ModuleUtil; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.roots.*; @@ -15,11 +11,8 @@ import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.util.Processor; import com.intellij.util.containers.HashSet; -import com.jetbrains.django.facet.DjangoFacetType; import com.jetbrains.python.PyNames; -import com.jetbrains.python.console.PydevConsoleRunner; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.PyBuiltinCache; import com.jetbrains.python.psi.impl.PyFileImpl; @@ -29,7 +22,10 @@ import com.jetbrains.python.sdk.PythonSdkType; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; import static com.jetbrains.python.psi.FutureFeature.ABSOLUTE_IMPORT; @@ -113,7 +109,7 @@ public class ResolveImportUtil { } @NotNull - private static List multiResolveImportElement(PyImportElement import_element, final PyQualifiedName qName) { + public static List multiResolveImportElement(PyImportElement import_element, final PyQualifiedName qName) { if (qName == null) return Collections.emptyList(); // TODO: search for entire names, not for first component only! @@ -160,28 +156,6 @@ public class ResolveImportUtil { return result != null ? Collections.singletonList(result) : Collections.emptyList(); } - @NotNull - public static List resolveImportReference(final PyReferenceExpression importRef) { - // prerequisites - if (importRef == null) return Collections.emptyList(); - if (!importRef.isValid()) return Collections.emptyList(); // we often catch a reparse while in a process of resolution - final String referencedName = importRef.getReferencedName(); // it will be the "foo" in later comments - if (referencedName == null) return Collections.emptyList(); - final PsiFile file = importRef.getContainingFile(); - if (file == null || !file.isValid()) return Collections.emptyList(); - - final PyElement parent = PsiTreeUtil.getParentOfType(importRef, PyImportElement.class, PyFromImportStatement.class); //importRef.getParent(); - final PyQualifiedName qname = importRef.asQualifiedName(); - if (parent instanceof PyImportElement) { - PyImportElement import_element = (PyImportElement)parent; - return multiResolveImportElement(import_element, qname); - } - else if (parent instanceof PyFromImportStatement) { // "from foo import" - return resolveFromOrForeignImport((PyFromImportStatement)parent, qname); - } - return Collections.emptyList(); - } - public static List resolveFromOrForeignImport(PyFromImportStatement fromImportStatement, PyQualifiedName qname) { final List results = resolveFromImportStatementSource(fromImportStatement, qname); if (results.isEmpty() && qname != null && qname.getComponentCount() > 0) { @@ -215,152 +189,41 @@ public class ResolveImportUtil { * * @param qualifiedName qualified name of the module reference to resolve * @param source_file where that reference resides; serves as PSI foothold to determine module, project, etc. - * @param import_is_absolute if false, try old python 2.x's "relative first, absolute next" approach. - * @param relative_level if > 0, step back from source_file and resolve from there (even if import_is_absolute is false!). + * @param importIsAbsolute if false, try old python 2.x's "relative first, absolute next" approach. + * @param relativeLevel if > 0, step back from source_file and resolve from there (even if importIsAbsolute is false!). * @return list of possible candidates */ @NotNull public static List resolveModule(@Nullable PyQualifiedName qualifiedName, PsiFile source_file, - boolean import_is_absolute, int relative_level) { + boolean importIsAbsolute, int relativeLevel) { if (qualifiedName == null) return Collections.emptyList(); - String marker = StringUtil.join(qualifiedName.getComponents(), ".") + "#" + Integer.toString(relative_level); + String marker = StringUtil.join(qualifiedName.getComponents(), ".") + "#" + Integer.toString(relativeLevel); Set being_imported = ourBeingImported.get(); if (being_imported.contains(marker)) return Collections.emptyList(); // break endless loop in import try { being_imported.add(marker); - if (relative_level > 0) { + QualifiedNameResolver visitor = new QualifiedNameResolver(qualifiedName).fromElement(source_file); + if (relativeLevel > 0) { // "from ...module import" - final PsiElement module = resolveModuleAt(stepBackFrom(source_file, relative_level), source_file, qualifiedName); - return module != null ? Collections.singletonList(module) : Collections.emptyList(); + visitor.withRelative(relativeLevel).withoutRoots(); } else { // "from module import" - if (import_is_absolute) { - return resolveModulesInRoots(qualifiedName, source_file); - } - else { - final PsiDirectory dir = source_file.getOriginalFile().getContainingDirectory(); - PsiElement module = resolveModuleAt(dir, source_file, qualifiedName); - if (module != null) { - return Collections.singletonList(module); - } - List found_in_roots = resolveModulesInRoots(qualifiedName, source_file); - if (found_in_roots.size() > 0) return found_in_roots; - - return Collections.emptyList(); + if (!importIsAbsolute) { + visitor.withRelative(0); } } + return visitor.resultsAsList(); } finally { being_imported.remove(marker); } } - /** - * Searches for a module at given directory, unwinding qualifiers and traversing directories as needed. - * - * @param directory where to start from; top qualifier will be searched for here. - * @param sourceFile the file containing the import statement being resolved - * @param qualifiedName the qualified name of the module to search - * @return module's file, or null. - */ - @Nullable - private static PsiElement resolveModuleAt(PsiDirectory directory, PsiFile sourceFile, PyQualifiedName qualifiedName) { - // prerequisites - if (directory == null || !directory.isValid()) return null; - if (sourceFile == null || !sourceFile.isValid()) return null; - - PsiElement seeker = directory; - for (String name : qualifiedName.getComponents()) { - if (name == null) { - return null; - } - seeker = resolveChild(seeker, name, sourceFile, null, true, true); - } - return seeker; - } - @Nullable public static PsiElement resolveModuleInRoots(@NotNull PyQualifiedName moduleQualifiedName, @Nullable PsiElement foothold) { - final List candidates = resolveModulesInRoots(moduleQualifiedName, foothold); - return candidates.isEmpty() ? null : candidates.get(0); - } - - /** - * Returns the list of directories/files under different project roots which match the specified qualified name. - * - * @param moduleQualifiedName the qualified name to find - * @param foothold the PSI element in the context of which the search is performed - * @return the list of matching directories or files, or an empty list if nothing was found - */ - @NotNull - public static List resolveModulesInRoots(@NotNull PyQualifiedName moduleQualifiedName, @Nullable PsiElement foothold) { - if (foothold == null || !foothold.isValid()) return Collections.emptyList(); - PsiFile footholdFile = foothold.getContainingFile(); - if (footholdFile == null || !footholdFile.isValid()) return Collections.emptyList(); - - PythonPathCache cache = getPathCache(foothold); - if (cache != null) { - final List cachedResults = cache.get(moduleQualifiedName); - if (cachedResults != null) { - return cachedResults; - } - } - - final Module module = ModuleUtil.findModuleForPsiElement(foothold); - - List results; - - if (PydevConsoleRunner.isInPydevConsole(foothold)) { - results = visitRootsInAllModules(moduleQualifiedName, foothold, footholdFile); - } - else { - results = visitRoots(moduleQualifiedName, foothold.getManager(), module, foothold, true); - } - - if (cache != null) { - cache.put(moduleQualifiedName, results); - } - return results; - } - - private static List visitRootsInAllModules(PyQualifiedName moduleQualifiedName, PsiElement foothold, PsiFile footholdFile) { - Set res = Sets.newHashSet(); - for (Module mod : ModuleManager.getInstance(footholdFile.getProject()).getModules()) { - res.addAll(visitRoots(moduleQualifiedName, foothold.getManager(), mod, foothold, true)); - } - return Lists.newArrayList(res); - } - - private static List visitRoots(@NotNull PyQualifiedName moduleQualifiedName, - @NotNull PsiManager manager, - @Nullable Module module, - @Nullable PsiElement foothold, - boolean checkForPackage) { - - PsiFile footholdFile = foothold != null ? foothold.getContainingFile() : null; - boolean has_djando_facet = false; - if (module != null) { - has_djando_facet = FacetManager.getInstance(module).getFacetByType(DjangoFacetType.ID) != null; - } - ResolveInRootVisitor visitor; - // resolve the name considering every source root as a package dir, as if it's a deployed package. django console does so. - if (has_djando_facet) { - visitor = new ResolveInRootAsTopPackageVisitor(moduleQualifiedName, manager, footholdFile, checkForPackage); - } - else { - visitor = new ResolveInRootVisitor(moduleQualifiedName, manager, footholdFile, checkForPackage); - } - if (module != null) { - visitRoots(module, visitor); - return visitor.resultsAsList(); - } - else if (foothold != null) { - visitSdkRoots(foothold, visitor); - return visitor.resultsAsList(); - } - else { - throw new IllegalStateException(); - } + if (foothold == null) return null; + QualifiedNameResolver visitor = new QualifiedNameResolver(moduleQualifiedName).fromElement(foothold); + return visitor.firstResult(); } @Nullable @@ -379,175 +242,6 @@ public class ResolveImportUtil { return cache; } - @NotNull - public static List resolveModulesInRoots(@NotNull Module module, @NotNull PyQualifiedName moduleQualifiedName, - boolean checkForPackage) { - PythonPathCache cache = PythonModulePathCache.getInstance(module); - final List cachedResults = cache.get(moduleQualifiedName); - if (cachedResults != null) { - return cachedResults; - } - List results = visitRoots(moduleQualifiedName, PsiManager.getInstance(module.getProject()), module, null, checkForPackage); - cache.put(moduleQualifiedName, results); - return results; - } - - @NotNull - public static List resolveModulesInRootProvider(@NotNull RootProvider rootProvider, - @NotNull Module module, - @NotNull PyQualifiedName moduleQualifiedName) { - ResolveInRootVisitor visitor = new ResolveInRootVisitor(moduleQualifiedName, PsiManager.getInstance(module.getProject()), null, - true); - if (visitModuleContentEntries(ModuleRootManager.getInstance(module), visitor)) { - for (VirtualFile file : rootProvider.getFiles(OrderRootType.CLASSES)) { - visitor.visitRoot(file); - } - } - return visitor.resultsAsList(); - } - - public static void visitRoots(@NotNull final PsiElement elt, @NotNull final RootVisitor visitor) { - // real search - final Module module = ModuleUtil.findModuleForPsiElement(elt); - if (module != null) { - visitRoots(module, visitor); - } - else { - visitSdkRoots(elt, visitor); - } - } - - public static void visitRoots(@NotNull Module module, final RootVisitor visitor) { - OrderEnumerator.orderEntries(module).recursively().forEach(new Processor() { - @Override - public boolean process(OrderEntry orderEntry) { - if (orderEntry instanceof ModuleSourceOrderEntry) { - return visitModuleContentEntries(((ModuleSourceOrderEntry)orderEntry).getRootModel(), visitor); - } - return visitOrderEntryRoots(visitor, orderEntry); - } - }); - } - - /** - * Visits module content, sdk roots and libraries - */ - public static void visitRoots(@NotNull Module module, @NotNull Sdk sdk, RootVisitor visitor) { - if (!visitModuleContentEntries(ModuleRootManager.getInstance(module), visitor)) return; - // else look in SDK roots - if (visitSdkRoots(visitor, sdk)) return; - - //look in libraries - ModuleRootManager rootManager = ModuleRootManager.getInstance(module); - rootManager.orderEntries().process(new LibraryRootVisitingPolicy(visitor), null); - } - - private static void visitSdkRoots(PsiElement elt, RootVisitor visitor) { - // no module, another way to look in SDK roots - final PsiFile elt_psifile = elt.getContainingFile(); - if (elt_psifile != null) { // formality - final VirtualFile elt_vfile = elt_psifile.getOriginalFile().getVirtualFile(); - List orderEntries = null; - if (elt_vfile != null) { // reality - final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(elt.getProject()).getFileIndex(); - orderEntries = fileIndex.getOrderEntriesForFile(elt_vfile); - if (orderEntries.size() > 0) { - for (OrderEntry entry : orderEntries) { - if (!visitOrderEntryRoots(visitor, entry)) break; - } - } - else { - orderEntries = null; - } - } - - // out-of-project file or non-file(e.g. console) - use roots of SDK assigned to project - if (orderEntries == null) { - final Sdk sdk = ProjectRootManager.getInstance(elt.getProject()).getProjectSdk(); - if (sdk != null) { - visitSdkRoots(visitor, sdk); - } - } - } - } - - private static boolean visitSdkRoots(@NotNull RootVisitor visitor, @NotNull Sdk sdk) { - final VirtualFile[] roots = sdk.getRootProvider().getFiles(OrderRootType.CLASSES); - for (VirtualFile root : roots) { - if (!visitor.visitRoot(root)) { - return true; - } - } - return false; - } - - - private static boolean visitModuleContentEntries(ModuleRootModel rootModel, RootVisitor visitor) { - // look in module sources - Set contentRoots = Sets.newHashSet(); - for (ContentEntry entry : rootModel.getContentEntries()) { - VirtualFile rootFile = entry.getFile(); - - if (rootFile != null && !visitor.visitRoot(rootFile)) return false; - contentRoots.add(rootFile); - for (VirtualFile folder : entry.getSourceFolderFiles()) { - if (!visitor.visitRoot(folder)) return false; - } - } - return true; - } - - private static boolean visitOrderEntryRoots(RootVisitor visitor, OrderEntry entry) { - Set allRoots = new LinkedHashSet(); - Collections.addAll(allRoots, entry.getFiles(OrderRootType.SOURCES)); - Collections.addAll(allRoots, entry.getFiles(OrderRootType.CLASSES)); - for (VirtualFile root : allRoots) { - if (!visitor.visitRoot(root)) { - return false; - } - } - return true; - } - - @Nullable - public static PsiElement resolveInRoots(@NotNull final PsiElement context, final String name) { - return resolveInRoots(context, PyQualifiedName.fromDottedString(name)); - } - - /** - * Looks for a name among element's module's roots; if there's no module, then among project's roots. - * - * @param context PSI element that defines the module and/or the project. - * @param qualifiedName module name to be found among roots. - * @return a PsiFile, a child of a root. - */ - @Nullable - public static PsiElement resolveInRoots(@NotNull final PsiElement context, final PyQualifiedName qualifiedName) { - // NOTE: a quick and dirty temporary fix for "current dir" root path, which is assumed to be present first (but may be not). - if (qualifiedName.getComponentCount() == 1) { - PsiElement res = resolveInCurrentDir(context, qualifiedName.getLastComponent()); - if (res != null) { - return res; - } - } - return resolveModuleInRoots(qualifiedName, context); - } - - @Nullable - public static PsiElement resolveInCurrentDir(@NotNull final PsiElement elt, final String refName) { - PsiFile pfile = elt.getContainingFile(); - VirtualFile vfile = pfile.getVirtualFile(); - if (vfile == null) { // we're probably within a copy, e.g. for completion; get the real thing - pfile = pfile.getOriginalFile(); - } - PsiDirectory pdir = pfile.getContainingDirectory(); - if (pdir != null) { - PsiElement child_elt = resolveChild(pdir, refName, pfile, null, true, true); - if (child_elt != null) return child_elt; - } - return null; - } - @Nullable private static PsiElement resolveForeignImport(@NotNull final PyElement importElement, @NotNull final PyQualifiedName importText, @@ -561,99 +255,6 @@ public class ResolveImportUtil { return null; } - public static boolean findInRoots(Module module, Sdk pythonSdk, String name) { - ResolveNameVisitor - visitor = new ResolveNameVisitor(PsiManager.getInstance(module.getProject()), name); - visitRoots(module, pythonSdk, visitor); - return visitor.isFound(); - } - - static class ResolveInRootVisitor implements RootVisitor { - final PsiFile myFootholdFile; - final boolean myCheckForPackage; - final @NotNull PyQualifiedName myQualifiedName; - final @NotNull PsiManager myPsiManager; - final Set results = Sets.newLinkedHashSet(); - - public ResolveInRootVisitor(@NotNull PyQualifiedName qName, - @NotNull PsiManager psiManager, - @Nullable PsiFile foothold_file, - boolean checkForPackage) { - myQualifiedName = qName; - myPsiManager = psiManager; - myFootholdFile = foothold_file; - myCheckForPackage = checkForPackage; - } - - public boolean visitRoot(final VirtualFile root) { - if (!root.isValid()) { - return true; - } - PsiElement module = resolveInRoot(root, myQualifiedName, myPsiManager, myFootholdFile, myCheckForPackage); - if (module != null) { - results.add(module); - } - - return true; - } - - @NotNull - public List resultsAsList() { - return Lists.newArrayList(results); - } - - @Nullable - protected static PsiElement resolveInRoot(VirtualFile root, - PyQualifiedName qualifiedName, - PsiManager psiManager, - @Nullable PsiFile foothold_file, - boolean checkForPackage) { - PsiElement module = root.isDirectory() ? psiManager.findDirectory(root) : psiManager.findFile(root); - if (module == null) return null; - for (String component : qualifiedName.getComponents()) { - if (component == null) { - module = null; - break; - } - module = resolveChild(module, component, foothold_file, root, false, checkForPackage); // only files, we want a module - } - return module; - } - } - - /** - * Visits roots and detects if qName is a name of top package coincinding with a root: - * that is, tha package is not one of root's children, but the root itself. - */ - private static class ResolveInRootAsTopPackageVisitor extends ResolveInRootVisitor { - public ResolveInRootAsTopPackageVisitor(@NotNull PyQualifiedName qName, - @NotNull PsiManager psiManager, - @Nullable PsiFile foothold_file, - boolean checkForPackage) { - super(qName, psiManager, foothold_file, checkForPackage); - } - - @Override - public boolean visitRoot(VirtualFile root) { - if (!root.isValid()) { - return true; - } - PsiElement module = resolveInRoot(root, myQualifiedName, myPsiManager, myFootholdFile, myCheckForPackage); - if (module != null) { - results.add(module); - } - - if (myQualifiedName.matchesPrefix(PyQualifiedName.fromDottedString(root.getName()))) { - module = resolveInRoot(root.getParent(), myQualifiedName, myPsiManager, myFootholdFile, myCheckForPackage); - if (module != null) { - results.add(module); - } - } - - return true; - } - } - /** * Tries to find referencedName under the parent element. Used to resolve any names that look imported. * Parent might happen to be a PyFile(__init__.py), then it is treated both as a file and as ist base dir. @@ -685,7 +286,10 @@ public class ResolveImportUtil { // OTOH, quite often a module named foo exports a class or function named foo, which is used as a fallback // by a module one level higher (e.g. curses.set_key). Prefer it to submodule if possible. - ret = ((PyFileImpl)parent).getElementNamed(referencedName, false); + PsiElement elementNamed = ((PyFileImpl)parent).getElementNamed(referencedName, false); + if (!fileOnly || PyUtil.instanceOf(elementNamed, PsiFile.class, PsiDirectory.class)) { + ret = elementNamed; + } if (ret != null && !PyUtil.instanceOf(ret, PsiFile.class, PsiDirectory.class) && PsiTreeUtil.getStubOrPsiParentOfType(ret, PyExceptPart.class) == null) { return ret; @@ -865,7 +469,7 @@ public class ResolveImportUtil { return name; } PathChoosingVisitor visitor = new PathChoosingVisitor(vfile); - visitRoots(foothold, visitor); + RootVisitorHost.visitRoots(foothold, visitor); final PyQualifiedName result = visitor.getResult(); if (cache != null) { cache.putName(vfile, result); @@ -881,7 +485,7 @@ public class ResolveImportUtil { return name.toString(); } PathChoosingVisitor visitor = new PathChoosingVisitor(vfile); - visitRoots(module, visitor); + RootVisitorHost.visitRoots(module, false, visitor); final PyQualifiedName result = visitor.getResult(); cache.putName(vfile, result); return result == null ? null : result.toString(); @@ -964,27 +568,6 @@ public class ResolveImportUtil { return null; } - public static class LibraryRootVisitingPolicy extends RootPolicy { - private final RootVisitor myVisitor; - - public LibraryRootVisitingPolicy(RootVisitor visitor) { - myVisitor = visitor; - } - - @Nullable - public PsiElement visitJdkOrderEntry(final JdkOrderEntry jdkOrderEntry, final PsiElement value) { - return null; - } - - @Nullable - @Override - public PsiElement visitLibraryOrderEntry(LibraryOrderEntry libraryOrderEntry, PsiElement value) { - if (value != null) return value; // for chaining in processOrder() - visitOrderEntryRoots(myVisitor, libraryOrderEntry); - return null; - } - } - /** * When a name is imported from a module, tries to find the definition of that name inside the module, * as opposed to looking for submodules. @@ -1051,34 +634,4 @@ public class ResolveImportUtil { } return PointInImport.NONE; } - - public static class ResolveNameVisitor implements RootVisitor { - final @NotNull PsiManager myPsiManager; - private boolean myFound = false; - private final String myName; - - - public ResolveNameVisitor(@NotNull PsiManager psiManager, String name) { - myPsiManager = psiManager; - myName = name; - } - - public boolean visitRoot(final VirtualFile root) { - if (!root.isValid()) { - return true; - } - PsiElement module = root.isDirectory() ? myPsiManager.findDirectory(root) : myPsiManager.findFile(root); - module = resolveChild(module, myName, null, root, false, false); - if (module != null) { - myFound = true; - return false; - } - - return true; - } - - public boolean isFound() { - return myFound; - } - } } diff --git a/python/src/com/jetbrains/python/psi/resolve/RootVisitorHost.java b/python/src/com/jetbrains/python/psi/resolve/RootVisitorHost.java new file mode 100644 index 000000000000..c6490d97878f --- /dev/null +++ b/python/src/com/jetbrains/python/psi/resolve/RootVisitorHost.java @@ -0,0 +1,115 @@ +package com.jetbrains.python.psi.resolve; + +import com.google.common.collect.Sets; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleUtil; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.roots.*; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.util.Processor; +import org.jetbrains.annotations.NotNull; + +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +/** + * @author yole + */ +public class RootVisitorHost { + public static void visitRoots(@NotNull final PsiElement elt, @NotNull final RootVisitor visitor) { + // real search + final Module module = ModuleUtil.findModuleForPsiElement(elt); + if (module != null) { + visitRoots(module, false, visitor); + } + else { + final PsiFile containingFile = elt.getContainingFile(); + if (containingFile != null) { + visitSdkRoots(containingFile, visitor); + } + } + } + + public static void visitRoots(@NotNull Module module, final boolean skipSdk, final RootVisitor visitor) { + OrderEnumerator enumerator = OrderEnumerator.orderEntries(module).recursively(); + if (skipSdk) { + enumerator = enumerator.withoutSdk(); + } + enumerator.forEach(new Processor() { + @Override + public boolean process(OrderEntry orderEntry) { + if (orderEntry instanceof ModuleSourceOrderEntry) { + return visitModuleContentEntries(((ModuleSourceOrderEntry)orderEntry).getRootModel(), visitor); + } + return visitOrderEntryRoots(visitor, orderEntry); + } + }); + } + + static void visitSdkRoots(PsiFile file, RootVisitor visitor) { + // formality + final VirtualFile elt_vfile = file.getOriginalFile().getVirtualFile(); + List orderEntries = null; + if (elt_vfile != null) { // reality + final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(file.getProject()).getFileIndex(); + orderEntries = fileIndex.getOrderEntriesForFile(elt_vfile); + if (orderEntries.size() > 0) { + for (OrderEntry entry : orderEntries) { + if (!visitOrderEntryRoots(visitor, entry)) break; + } + } + else { + orderEntries = null; + } + } + + // out-of-project file or non-file(e.g. console) - use roots of SDK assigned to project + if (orderEntries == null) { + final Sdk sdk = ProjectRootManager.getInstance(file.getProject()).getProjectSdk(); + if (sdk != null) { + visitSdkRoots(sdk, visitor); + } + } + } + + public static boolean visitSdkRoots(@NotNull Sdk sdk, @NotNull RootVisitor visitor) { + final VirtualFile[] roots = sdk.getRootProvider().getFiles(OrderRootType.CLASSES); + for (VirtualFile root : roots) { + if (!visitor.visitRoot(root)) { + return true; + } + } + return false; + } + + static boolean visitModuleContentEntries(ModuleRootModel rootModel, RootVisitor visitor) { + // look in module sources + Set contentRoots = Sets.newHashSet(); + for (ContentEntry entry : rootModel.getContentEntries()) { + VirtualFile rootFile = entry.getFile(); + + if (rootFile != null && !visitor.visitRoot(rootFile)) return false; + contentRoots.add(rootFile); + for (VirtualFile folder : entry.getSourceFolderFiles()) { + if (!visitor.visitRoot(folder)) return false; + } + } + return true; + } + + static boolean visitOrderEntryRoots(RootVisitor visitor, OrderEntry entry) { + Set allRoots = new LinkedHashSet(); + Collections.addAll(allRoots, entry.getFiles(OrderRootType.SOURCES)); + Collections.addAll(allRoots, entry.getFiles(OrderRootType.CLASSES)); + for (VirtualFile root : allRoots) { + if (!visitor.visitRoot(root)) { + return false; + } + } + return true; + } +} diff --git a/python/src/com/jetbrains/python/psi/types/PyClassMembersProviderBase.java b/python/src/com/jetbrains/python/psi/types/PyClassMembersProviderBase.java index cb8a86967412..db6ef38cb0b4 100644 --- a/python/src/com/jetbrains/python/psi/types/PyClassMembersProviderBase.java +++ b/python/src/com/jetbrains/python/psi/types/PyClassMembersProviderBase.java @@ -2,6 +2,7 @@ package com.jetbrains.python.psi.types; import com.intellij.psi.PsiElement; import com.jetbrains.python.codeInsight.PyDynamicMember; +import com.jetbrains.python.psi.PyClass; import org.jetbrains.annotations.Nullable; import java.util.Collection; @@ -26,9 +27,13 @@ public class PyClassMembersProviderBase implements PyClassMembersProvider { public static PsiElement resolveMemberByName(Collection members, PyClassType clazz, String name) { + final PyClass pyClass = clazz.getPyClass(); + if (pyClass == null) { + return null; + } for (PyDynamicMember member : members) { if (member.getName().equals(name)) { - return member.resolve(clazz.getPyClass()); + return member.resolve(pyClass); } } return null; diff --git a/python/src/com/jetbrains/python/refactoring/introduce/field/PyIntroduceFieldHandler.java b/python/src/com/jetbrains/python/refactoring/introduce/field/PyIntroduceFieldHandler.java index 1a3547657246..9b2181b05aea 100644 --- a/python/src/com/jetbrains/python/refactoring/introduce/field/PyIntroduceFieldHandler.java +++ b/python/src/com/jetbrains/python/refactoring/introduce/field/PyIntroduceFieldHandler.java @@ -122,8 +122,8 @@ public class PyIntroduceFieldHandler extends IntroduceHandler { final PyFunction function = (PyFunction)myScope; final PyParameter[] parameters = function.getParameterList().getParameters(); if (parameters.length > 0 && result == parameters[0]) { - final Set flags = PyUtil.detectDecorationsAndWrappersOf(function); - if (!(flags.contains(PyFunction.Flag.STATICMETHOD))) { + final PyFunction.Modifier modifier = function.getModifier(); + if (modifier != PyFunction.Modifier.STATICMETHOD) { // 'self' is not a local scope dependency return; } @@ -247,8 +247,8 @@ public class PyIntroduceFieldHandler extends IntroduceHandler { private static boolean isInStaticMethod(PsiElement element) { PyFunction containingMethod = PsiTreeUtil.getParentOfType(element, PyFunction.class, false, PyClass.class); if (containingMethod != null) { - final Set flags = PyUtil.detectDecorationsAndWrappersOf(containingMethod); - return flags.contains(PyFunction.Flag.STATICMETHOD); + final PyFunction.Modifier modifier = containingMethod.getModifier(); + return modifier == PyFunction.Modifier.STATICMETHOD; } return false; } diff --git a/python/src/com/jetbrains/python/validation/HighlightingAnnotator.java b/python/src/com/jetbrains/python/validation/HighlightingAnnotator.java index 4dbe62cf561a..16009aad9984 100644 --- a/python/src/com/jetbrains/python/validation/HighlightingAnnotator.java +++ b/python/src/com/jetbrains/python/validation/HighlightingAnnotator.java @@ -6,8 +6,6 @@ import com.intellij.util.ArrayUtil; import com.jetbrains.python.highlighting.PyHighlighter; import com.jetbrains.python.psi.*; -import java.util.Set; - /** * @author yole */ @@ -39,8 +37,8 @@ public class HighlightingAnnotator extends PyAnnotator { boolean isSelf = false; final int index = ArrayUtil.find(function.getParameterList().getParameters(), node); if (function.getContainingClass() != null && index == 0) { - final Set flags = PyUtil.detectDecorationsAndWrappersOf(function); - if (!flags.contains(PyFunction.Flag.CLASSMETHOD) && !flags.contains(PyFunction.Flag.STATICMETHOD)) { + final PyFunction.Modifier modifier = function.getModifier(); + if (modifier != PyFunction.Modifier.CLASSMETHOD && modifier != PyFunction.Modifier.STATICMETHOD) { isSelf = true; } } diff --git a/python/testData/inspections/AddFunctionToModule.py b/python/testData/inspections/AddFunctionToModule.py index d0426f1db205..db6c9bb36946 100644 --- a/python/testData/inspections/AddFunctionToModule.py +++ b/python/testData/inspections/AddFunctionToModule.py @@ -2,4 +2,4 @@ import AddFunctionToModu # do something -AddFunctionToModule.frob(1) +AddFunctionToModule.frob(1) diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection/importFunction.py b/python/testData/inspections/PyUnresolvedReferencesInspection/importFunction.py new file mode 100644 index 000000000000..2bfbaa3d0ee9 --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection/importFunction.py @@ -0,0 +1 @@ +import collections.OrderedDict diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection/unresolvedImport.py b/python/testData/inspections/PyUnresolvedReferencesInspection/unresolvedImport.py index 853423274bc8..6920e61d6f58 100644 --- a/python/testData/inspections/PyUnresolvedReferencesInspection/unresolvedImport.py +++ b/python/testData/inspections/PyUnresolvedReferencesInspection/unresolvedImport.py @@ -1 +1 @@ -import wurm +import wurm diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection/unresolvedImportedModule.py b/python/testData/inspections/PyUnresolvedReferencesInspection/unresolvedImportedModule.py index 9a9bd1864596..536498f81fad 100644 --- a/python/testData/inspections/PyUnresolvedReferencesInspection/unresolvedImportedModule.py +++ b/python/testData/inspections/PyUnresolvedReferencesInspection/unresolvedImportedModule.py @@ -1,2 +1,2 @@ -import deliverance.proxycommand +import deliverance.proxycommand deliverance.proxycommand.main() diff --git a/python/testSrc/com/jetbrains/python/PyMultiFileResolveTest.java b/python/testSrc/com/jetbrains/python/PyMultiFileResolveTest.java index 196c45ead729..053781491a75 100644 --- a/python/testSrc/com/jetbrains/python/PyMultiFileResolveTest.java +++ b/python/testSrc/com/jetbrains/python/PyMultiFileResolveTest.java @@ -1,7 +1,6 @@ package com.jetbrains.python; import com.intellij.openapi.fileTypes.FileType; -import com.intellij.openapi.fileTypes.FileTypeManager; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileFilter; import com.intellij.psi.*; @@ -210,14 +209,6 @@ public class PyMultiFileResolveTest extends PyResolveTestCase { assertResolvesTo(PyTargetExpression.class, "__all__"); } - public void testDunderAllImport() { - assertResolvesTo(PyTargetExpression.class, "__all__"); - } - - public void testDunderAllImportResolve() { - assertResolvesTo(PyTargetExpression.class, "__all__"); - } - public void testDunderAllConflict() { assertResolvesTo(PyFunction.class, "do_stuff", "/src/mypackage1.py"); } diff --git a/python/testSrc/com/jetbrains/python/PyResolveCalleeTest.java b/python/testSrc/com/jetbrains/python/PyResolveCalleeTest.java index 5fc6feac9798..11f591c8b9b4 100644 --- a/python/testSrc/com/jetbrains/python/PyResolveCalleeTest.java +++ b/python/testSrc/com/jetbrains/python/PyResolveCalleeTest.java @@ -7,8 +7,6 @@ import com.jetbrains.python.psi.PyCallExpression; import com.jetbrains.python.psi.PyFunction; import com.jetbrains.python.psi.resolve.PyResolveContext; -import java.util.EnumSet; - /** * Tests callee resolution in PyCallExpressionImpl. * User: dcheryasov @@ -31,7 +29,7 @@ public class PyResolveCalleeTest extends PyTestCase { public void testClassCall() { PyCallExpression.PyMarkedCallee resolved = resolveCallee(); assertNotNull(resolved.getCallable()); - assertTrue(resolved.getFlags().equals(EnumSet.noneOf(PyFunction.Flag.class))); + assertEquals(null, resolved.getModifier()); } public void testDecoCall() { @@ -43,13 +41,13 @@ public class PyResolveCalleeTest extends PyTestCase { public void testDecoParamCall() { PyCallExpression.PyMarkedCallee resolved = resolveCallee(); assertNotNull(resolved.getCallable()); - assertTrue(resolved.getFlags().equals(EnumSet.noneOf(PyFunction.Flag.class))); + assertNull(resolved.getModifier()); } public void testWrappedStaticMethod() { PyCallExpression.PyMarkedCallee resolved = resolveCallee(); assertNotNull(resolved.getCallable()); assertEquals(0, resolved.getImplicitOffset()); - assertEquals(resolved.getFlags(), EnumSet.of(PyFunction.Flag.STATICMETHOD)); + assertEquals(resolved.getModifier(), PyFunction.Modifier.STATICMETHOD); } } diff --git a/python/testSrc/com/jetbrains/python/PyStubsTest.java b/python/testSrc/com/jetbrains/python/PyStubsTest.java index 7c069f4cc23d..2abfbea5891c 100644 --- a/python/testSrc/com/jetbrains/python/PyStubsTest.java +++ b/python/testSrc/com/jetbrains/python/PyStubsTest.java @@ -21,9 +21,7 @@ import com.jetbrains.python.psi.stubs.PyVariableNameIndex; import com.jetbrains.python.toolbox.Maybe; import java.util.Collection; -import java.util.EnumSet; import java.util.List; -import java.util.Set; /** * @author max @@ -335,8 +333,8 @@ public class PyStubsTest extends PyTestCase { final PyClass pyClass = file.getTopLevelClasses().get(0); final PyFunction[] methods = pyClass.getMethods(); assertEquals(1, methods.length); - final Set flags = PyUtil.detectDecorationsAndWrappersOf(methods[0]); - assertEquals(EnumSet.of(PyFunction.Flag.STATICMETHOD), flags); + final PyFunction.Modifier modifier = methods[0].getModifier(); + assertEquals(PyFunction.Modifier.STATICMETHOD, modifier); assertNotParsed(file); } diff --git a/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java index fc8bf9acbbbb..33a3e270252d 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java @@ -73,6 +73,10 @@ public class PyUnresolvedReferencesInspectionTest extends PyTestCase { doTest(); } + public void testImportFunction() { // PY-1896 + doTest(); + } + public void testImportToContainingFile() { // PY-4372 myFixture.copyFileToProject("inspections/PyUnresolvedReferencesInspection/__init__.py", "PyUnresolvedReferencesInspection/__init__.py"); myFixture.copyFileToProject("inspections/PyUnresolvedReferencesInspection/importToContainingFile.py", "PyUnresolvedReferencesInspection/importToContainingFile.py");