diff --git a/python/psi-api/src/com/jetbrains/python/psi/Callable.java b/python/psi-api/src/com/jetbrains/python/psi/Callable.java index c156564506a3..fd1b6abddd6b 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/Callable.java +++ b/python/psi-api/src/com/jetbrains/python/psi/Callable.java @@ -10,7 +10,7 @@ import org.jetbrains.annotations.Nullable; * @author dcheryasov */ -public interface Callable extends PyTypedElement { +public interface Callable extends PyTypedElement, PyQualifiedNameOwner { /** * @return a list of parameters passed to this callable, possibly empty. @@ -29,12 +29,4 @@ public interface Callable extends PyTypedElement { */ @Nullable PyFunction asMethod(); - - /** - * Returns the qualified name of the function. - * - * @return the qualified name of the function, or null for a lambda expression. - */ - @Nullable - String getQualifiedName(); } diff --git a/python/psi-api/src/com/jetbrains/python/psi/Property.java b/python/psi-api/src/com/jetbrains/python/psi/Property.java index aad101b0a575..60373dd0a0db 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/Property.java +++ b/python/psi-api/src/com/jetbrains/python/psi/Property.java @@ -1,5 +1,7 @@ package com.jetbrains.python.psi; +import com.jetbrains.python.psi.types.PyType; +import com.jetbrains.python.psi.types.TypeEvalContext; import com.jetbrains.python.toolbox.Maybe; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -51,4 +53,9 @@ public interface Property { @NotNull Maybe getByDirection(@NotNull AccessDirection direction); + /** + * Get the return type of the property getter. + */ + @Nullable + PyType getType(@NotNull TypeEvalContext context); } diff --git a/python/psi-api/src/com/jetbrains/python/psi/PyClass.java b/python/psi-api/src/com/jetbrains/python/psi/PyClass.java index 4b34a1635377..bdc69149a8d5 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/PyClass.java +++ b/python/psi-api/src/com/jetbrains/python/psi/PyClass.java @@ -21,7 +21,7 @@ import java.util.List; * Represents a class declaration in source. */ public interface PyClass extends PsiNameIdentifierOwner, PyStatement, NameDefiner, PyDocStringOwner, StubBasedPsiElement, - ScopeOwner, PyDecoratable, PyTypedElement { + ScopeOwner, PyDecoratable, PyTypedElement, PyQualifiedNameOwner { ArrayFactory ARRAY_FACTORY = new ArrayFactory() { @NotNull @Override @@ -149,9 +149,6 @@ public interface PyClass extends PsiNameIdentifierOwner, PyStatement, NameDefine boolean isSubclass(@NotNull String superClassQName); - @Nullable - String getQualifiedName(); - /** * Returns the list of names in the class' __slots__ attribute, or null if the class * does not define such an attribute. diff --git a/python/psi-api/src/com/jetbrains/python/psi/PyQualifiedNameOwner.java b/python/psi-api/src/com/jetbrains/python/psi/PyQualifiedNameOwner.java new file mode 100644 index 000000000000..ad915f78a17f --- /dev/null +++ b/python/psi-api/src/com/jetbrains/python/psi/PyQualifiedNameOwner.java @@ -0,0 +1,19 @@ +package com.jetbrains.python.psi; + +import org.jetbrains.annotations.Nullable; + +/** + * Base class for elements that have a qualified name (classes and functions). + * + * @author yole + */ +public interface PyQualifiedNameOwner extends PyElement { + /** + * Returns the qualified name of the element. + * + * @return the qualified name of the element, or null if the element doesn't have a name (for example, it is a lambda expression) or + * is contained inside an element that doesn't have a qualified name. + */ + @Nullable + String getQualifiedName(); +} diff --git a/python/psi-api/src/com/jetbrains/python/psi/types/TypeEvalContext.java b/python/psi-api/src/com/jetbrains/python/psi/types/TypeEvalContext.java index 2a5c2b475f5d..ff0d48ad734e 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/types/TypeEvalContext.java +++ b/python/psi-api/src/com/jetbrains/python/psi/types/TypeEvalContext.java @@ -3,7 +3,6 @@ package com.jetbrains.python.psi.types; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; -import com.intellij.psi.StubBasedPsiElement; import com.jetbrains.python.psi.PyTypedElement; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -158,8 +157,8 @@ public class TypeEvalContext { } } - public boolean maySwitchToAST(@NotNull StubBasedPsiElement element) { - return myAllowStubToAST || (element.getStub() == null && (myOrigin == null || myOrigin == element.getContainingFile())); + public boolean maySwitchToAST(@NotNull PsiElement element) { + return myAllowStubToAST || myOrigin == element.getContainingFile(); } @Nullable diff --git a/python/python-rest/src/com/jetbrains/rest/run/RestConfigurationEditor.java b/python/python-rest/src/com/jetbrains/rest/run/RestConfigurationEditor.java index e1791aef50b6..24951861dd59 100644 --- a/python/python-rest/src/com/jetbrains/rest/run/RestConfigurationEditor.java +++ b/python/python-rest/src/com/jetbrains/rest/run/RestConfigurationEditor.java @@ -95,9 +95,6 @@ public class RestConfigurationEditor extends SettingsEditor + + + + diff --git a/python/src/com/jetbrains/python/PyGotoSymbolContributor.java b/python/src/com/jetbrains/python/PyGotoSymbolContributor.java index dac92071202c..3c4c9f5ff51a 100644 --- a/python/src/com/jetbrains/python/PyGotoSymbolContributor.java +++ b/python/src/com/jetbrains/python/PyGotoSymbolContributor.java @@ -3,13 +3,10 @@ package com.jetbrains.python; import com.intellij.navigation.GotoClassContributor; import com.intellij.navigation.NavigationItem; import com.intellij.openapi.project.Project; -import com.intellij.psi.PsiFile; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.stubs.StubIndex; import com.intellij.util.ArrayUtil; -import com.jetbrains.python.psi.PyClass; -import com.jetbrains.python.psi.PyFunction; -import com.jetbrains.python.psi.impl.PyPresentableElementImpl; +import com.jetbrains.python.psi.PyQualifiedNameOwner; import com.jetbrains.python.psi.search.PyProjectScopeBuilder; import com.jetbrains.python.psi.stubs.PyClassNameIndex; import com.jetbrains.python.psi.stubs.PyFunctionNameIndex; @@ -50,17 +47,8 @@ public class PyGotoSymbolContributor implements GotoClassContributor { @Override public String getQualifiedName(NavigationItem item) { - if (item instanceof PyClass) { - return ((PyClass) item).getQualifiedName(); - } - if (item instanceof PyFunction) { - PyFunction function = (PyFunction) item; - final PyClass containingClass = function.getContainingClass(); - if (containingClass != null) { - return containingClass.getQualifiedName() + "." + function.getName(); - } - PsiFile file = function.getContainingFile(); - return PyPresentableElementImpl.getPackageForFile(file) + "." + function.getName(); + if (item instanceof PyQualifiedNameOwner) { + return ((PyQualifiedNameOwner) item).getQualifiedName(); } return null; } diff --git a/python/src/com/jetbrains/python/codeInsight/completion/PyClassNameCompletionContributor.java b/python/src/com/jetbrains/python/codeInsight/completion/PyClassNameCompletionContributor.java index 552aa0025153..c93209b05b12 100644 --- a/python/src/com/jetbrains/python/codeInsight/completion/PyClassNameCompletionContributor.java +++ b/python/src/com/jetbrains/python/codeInsight/completion/PyClassNameCompletionContributor.java @@ -42,20 +42,31 @@ public class PyClassNameCompletionContributor extends CompletionContributor { if (parent instanceof PyReferenceExpression && ((PyReferenceExpression)parent).getQualifier() != null) { return; } + if (parent instanceof PyStringLiteralExpression) { + String prefix = parent.getText().substring(0, parameters.getOffset() - parent.getTextRange().getStartOffset()); + if (prefix.contains(".")) { + return; + } + } final FileViewProvider provider = element.getContainingFile().getViewProvider(); if (provider instanceof MultiplePsiFilesPerDocumentFileViewProvider) return; if (PsiTreeUtil.getParentOfType(element, PyImportStatementBase.class) != null) { return; } final PsiFile originalFile = parameters.getOriginalFile(); - addVariantsFromIndex(result, originalFile, PyClassNameIndex.KEY, IMPORTING_INSERT_HANDLER, Conditions.alwaysTrue()); - addVariantsFromIndex(result, originalFile, PyFunctionNameIndex.KEY, FUNCTION_INSERT_HANDLER, IS_TOPLEVEL); - addVariantsFromIndex(result, originalFile, PyVariableNameIndex.KEY, IMPORTING_INSERT_HANDLER, IS_TOPLEVEL); - addVariantsFromModules(result, originalFile); + addVariantsFromIndex(result, originalFile, PyClassNameIndex.KEY, + parent instanceof PyStringLiteralExpression ? STRING_LITERAL_INSERT_HANDLER : IMPORTING_INSERT_HANDLER, + Conditions.alwaysTrue()); + addVariantsFromIndex(result, originalFile, PyFunctionNameIndex.KEY, + parent instanceof PyStringLiteralExpression ? STRING_LITERAL_INSERT_HANDLER : FUNCTION_INSERT_HANDLER, IS_TOPLEVEL); + addVariantsFromIndex(result, originalFile, PyVariableNameIndex.KEY, + parent instanceof PyStringLiteralExpression ? STRING_LITERAL_INSERT_HANDLER : IMPORTING_INSERT_HANDLER, + IS_TOPLEVEL); + addVariantsFromModules(result, originalFile, parent instanceof PyStringLiteralExpression); } } - private static void addVariantsFromModules(CompletionResultSet result, PsiFile targetFile) { + private static void addVariantsFromModules(CompletionResultSet result, PsiFile targetFile, boolean inStringLiteral) { Collection files = FileTypeIndex.getFiles(PythonFileType.INSTANCE, PyProjectScopeBuilder.excludeSdkTestsScope(targetFile)); for (VirtualFile file : files) { PsiFile pyFile = targetFile.getManager().findFile(file); @@ -64,7 +75,7 @@ public class PyClassNameCompletionContributor extends CompletionContributor { if (PythonReferenceImporter.isImportableModule(targetFile, importable)) { LookupElementBuilder element = PyModuleType.buildFileLookupElement(importable, null); if (element != null) { - result.addElement(element.withInsertHandler(IMPORTING_INSERT_HANDLER)); + result.addElement(element.withInsertHandler(inStringLiteral ? STRING_LITERAL_INSERT_HANDLER : IMPORTING_INSERT_HANDLER)); } } } @@ -113,6 +124,21 @@ public class PyClassNameCompletionContributor extends CompletionContributor { } }; + private static final InsertHandler STRING_LITERAL_INSERT_HANDLER = new InsertHandler() { + @Override + public void handleInsert(InsertionContext context, LookupElement item) { + PsiElement element = item.getPsiElement(); + if (element instanceof PyQualifiedNameOwner) { + String qName = ((PyQualifiedNameOwner) element).getQualifiedName(); + String name = ((PyQualifiedNameOwner) element).getName(); + if (qName != null && name != null) { + String qNamePrefix = qName.substring(0, qName.length()-name.length()); + context.getDocument().insertString(context.getStartOffset(), qNamePrefix); + } + } + } + }; + private static void addImportForLookupElement(final InsertionContext context, final LookupElement item, final int tailOffset) { PsiDocumentManager manager = PsiDocumentManager.getInstance(context.getProject()); Document document = manager.getDocument(context.getFile()); diff --git a/python/src/com/jetbrains/python/psi/impl/PyBlockEvaluator.java b/python/src/com/jetbrains/python/psi/impl/PyBlockEvaluator.java index d6a8d48fdbd0..19a9277438ed 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyBlockEvaluator.java +++ b/python/src/com/jetbrains/python/psi/impl/PyBlockEvaluator.java @@ -66,7 +66,8 @@ public class PyBlockEvaluator { if (currentValue instanceof Map) { Object mapKey = prepareEvaluator().evaluate(indexExpression); if (mapKey != null) { - ((Map)currentValue).put(mapKey, prepareEvaluator().evaluate(node.getAssignedValue())); + Object value = myEvaluateCollectionItems ? prepareEvaluator().evaluate(node.getAssignedValue()) : node.getAssignedValue(); + ((Map)currentValue).put(mapKey, value); } } } diff --git a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java index 1eb2a9b1fcb4..829cde42f98e 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java @@ -691,6 +691,24 @@ public class PyClassImpl extends PyPresentableElementImpl implement mySite = site; } + @NotNull + @Override + public Maybe getGetter() { + return filterNonStubExpression(myGetter); + } + + @NotNull + @Override + public Maybe getSetter() { + return filterNonStubExpression(mySetter); + } + + @NotNull + @Override + public Maybe getDeleter() { + return filterNonStubExpression(myDeleter); + } + public String getName() { return myName; } @@ -704,15 +722,29 @@ public class PyClassImpl extends PyPresentableElementImpl implement public Maybe getByDirection(@NotNull AccessDirection direction) { switch (direction) { case READ: - return myGetter; + return getGetter(); case WRITE: - return mySetter; + return getSetter(); case DELETE: - return myDeleter; + return getDeleter(); } throw new IllegalArgumentException("Unknown direction " + PyUtil.nvl(direction)); } + @Nullable + @Override + public PyType getType(@NotNull TypeEvalContext context) { + final Callable callable = myGetter.valueOrNull(); + if (callable != null) { + // Ignore return types of non stub-based elements if we are not allowed to use AST + if (!(callable instanceof StubBasedPsiElement) && !context.maySwitchToAST(callable)) { + return null; + } + return callable.getReturnType(context, null); + } + return null; + } + @NotNull @Override protected Maybe translate(@Nullable PyExpression expr) { @@ -733,6 +765,17 @@ public class PyClassImpl extends PyPresentableElementImpl implement return NONE; } + @NotNull + private static Maybe filterNonStubExpression(@NotNull Maybe maybeCallable) { + final Callable callable = maybeCallable.valueOrNull(); + if (callable != null) { + if (!(callable instanceof StubBasedPsiElement)) { + return UNKNOWN_CALL; + } + } + return maybeCallable; + } + public String toString() { return "property(" + myGetter + ", " + mySetter + ", " + myDeleter + ", " + myDoc + ")"; } diff --git a/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java index f77f6501f6f8..3e702bf69ef5 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java @@ -27,7 +27,6 @@ import com.jetbrains.python.psi.impl.references.PyReferenceImpl; import com.jetbrains.python.psi.resolve.*; import com.jetbrains.python.psi.types.*; import com.jetbrains.python.refactoring.PyDefUseUtil; -import com.jetbrains.python.toolbox.Maybe; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -269,10 +268,13 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere if (classType.isDefinition()) { return Ref.create(PyBuiltinCache.getInstance(pyClass).getObjectType(PyNames.PROPERTY)); } - final Maybe accessor = property.getByDirection(AccessDirection.of(this)); - final Callable callable = accessor.valueOrNull(); - final PyType type = (callable != null) ? callable.getReturnType(context, this) : null; - return Ref.create(type); + if (AccessDirection.of(this) == AccessDirection.READ) { + final PyType type = property.getType(context); + if (type != null) { + return Ref.create(type); + } + } + return Ref.create(); } } else if (qualifierType instanceof PyUnionType) { diff --git a/python/src/com/jetbrains/python/psi/impl/PyStringLiteralExpressionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyStringLiteralExpressionImpl.java index e110791a9699..d45df200fccc 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyStringLiteralExpressionImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyStringLiteralExpressionImpl.java @@ -1,8 +1,10 @@ package com.jetbrains.python.psi.impl; +import com.intellij.icons.AllIcons; import com.intellij.lang.ASTNode; import com.intellij.lang.Language; import com.intellij.lang.injection.InjectedLanguageManager; +import com.intellij.navigation.ItemPresentation; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.TextRange; @@ -24,6 +26,7 @@ import org.intellij.lang.regexp.psi.RegExpGroup; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import javax.swing.*; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -304,6 +307,29 @@ public class PyStringLiteralExpressionImpl extends PyElementImpl implements PySt return ReferenceProvidersRegistry.getReferencesFromProviders(this, PsiReferenceService.Hints.NO_HINTS); } + @Override + public ItemPresentation getPresentation() { + return new ItemPresentation() { + @Nullable + @Override + public String getPresentableText() { + return getStringValue(); + } + + @Nullable + @Override + public String getLocationString() { + return "(" + PyPresentableElementImpl.getPackageForFile(getContainingFile()) + ")"; + } + + @Nullable + @Override + public Icon getIcon(boolean unused) { + return AllIcons.Nodes.Variable; + } + }; + } + public PsiLanguageInjectionHost updateText(@NotNull String text) { // TODO is this the correct implementation? most likely not ASTNode valueNode = getNode().getFirstChildNode(); diff --git a/python/src/com/jetbrains/python/testing/pytest/PyTestConfigurationEditor.java b/python/src/com/jetbrains/python/testing/pytest/PyTestConfigurationEditor.java index 3e5928e71496..ca350f9560b2 100644 --- a/python/src/com/jetbrains/python/testing/pytest/PyTestConfigurationEditor.java +++ b/python/src/com/jetbrains/python/testing/pytest/PyTestConfigurationEditor.java @@ -92,9 +92,6 @@ public class PyTestConfigurationEditor extends SettingsEditor