From 09a3a016eede8c13dbc72966b756af168bd5f576 Mon Sep 17 00:00:00 2001 From: "alexey.afanasiev" Date: Mon, 6 Mar 2017 13:50:39 +0300 Subject: [PATCH 01/33] IDEA-166411 Twig plugin freezes on .groovy.twig files --- .../plugins/groovy/formatter/blocks/GroovyBlockGenerator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/formatter/blocks/GroovyBlockGenerator.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/formatter/blocks/GroovyBlockGenerator.java index d54618609dbe..3e5c6b84f59a 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/formatter/blocks/GroovyBlockGenerator.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/formatter/blocks/GroovyBlockGenerator.java @@ -626,7 +626,7 @@ public class GroovyBlockGenerator { private static void addChildNodes(PsiElement elem, ArrayList childNodes, TextRange range, PsiElement root) { ASTNode node = elem.getNode(); - if (range.contains(elem.getTextRange()) && node != null && elem != root) { + if (range.contains(elem.getTextRange()) && node != null && elem != root && !(elem instanceof PsiFile)) { childNodes.add(node); } else { for (PsiElement child : elem.getChildren()) { From 90a40266e3e97fed9f1e41a13c9d6909e33cd2b7 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Mon, 6 Mar 2017 11:53:24 +0100 Subject: [PATCH 02/33] Cleanup (dead code; typos) --- .../src/com/intellij/psi/LambdaUtil.java | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/java/java-psi-api/src/com/intellij/psi/LambdaUtil.java b/java/java-psi-api/src/com/intellij/psi/LambdaUtil.java index 9fe15b18af5c..95833732fa89 100644 --- a/java/java-psi-api/src/com/intellij/psi/LambdaUtil.java +++ b/java/java-psi-api/src/com/intellij/psi/LambdaUtil.java @@ -255,16 +255,16 @@ public class LambdaUtil { } @NotNull - private static List hasSubsignature(List signatures) { + private static List hasSubSignature(List signatures) { for (HierarchicalMethodSignature signature : signatures) { - boolean subsignature = true; + boolean subSignature = true; for (HierarchicalMethodSignature methodSignature : signatures) { if (!signature.equals(methodSignature) && !skipMethod(signature, methodSignature)) { - subsignature = false; + subSignature = false; break; } } - if (subsignature) return Collections.singletonList(signature); + if (subSignature) return Collections.singletonList(signature); } return signatures; } @@ -307,7 +307,7 @@ public class LambdaUtil { } } - return hasSubsignature(methods); + return hasSubSignature(methods); } @@ -554,12 +554,6 @@ public class LambdaUtil { typeByExpression instanceof PsiLambdaParameterType; } - public static boolean isLambdaReturnExpression(PsiElement element) { - final PsiElement parent = element.getParent(); - return parent instanceof PsiLambdaExpression || - parent instanceof PsiReturnStatement && PsiTreeUtil.getParentOfType(parent, PsiLambdaExpression.class, true, PsiMethod.class) != null; - } - public static PsiReturnStatement[] getReturnStatements(PsiLambdaExpression lambdaExpression) { final PsiElement body = lambdaExpression.getBody(); return body instanceof PsiCodeBlock ? PsiUtil.findReturnStatements((PsiCodeBlock)body) : PsiReturnStatement.EMPTY_ARRAY; From 2c5949a2bb6d92206d72e1eb927b4d0eff917f57 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Mon, 6 Mar 2017 17:53:05 +0700 Subject: [PATCH 03/33] inplaceIntroduceField#replaceAll_after: variable name changed after mockJdk version updated --- .../inplaceIntroduceField/replaceAll_after.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/java/java-tests/testData/refactoring/inplaceIntroduceField/replaceAll_after.java b/java/java-tests/testData/refactoring/inplaceIntroduceField/replaceAll_after.java index a818fa631b43..15eddb42a09a 100644 --- a/java/java-tests/testData/refactoring/inplaceIntroduceField/replaceAll_after.java +++ b/java/java-tests/testData/refactoring/inplaceIntroduceField/replaceAll_after.java @@ -15,11 +15,11 @@ */ class Test { - private String x; + private String s; void simpleMethod() { - x = ""; - System.out.println(x); - System.out.println(x); + s = ""; + System.out.println(s); + System.out.println(s); } } From 851e4b8f79f60f9700ce4cf843f157047511846f Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Sat, 4 Feb 2017 22:07:10 +0300 Subject: [PATCH 04/33] PY-21180 Slightly boost resolving imports in projects like Tensorflow by caching findCanonicalImportName() --- .../src/com/jetbrains/python/psi/PyUtil.java | 17 ++-- .../psi/resolve/QualifiedNameFinder.java | 83 +++++++++---------- 2 files changed, 49 insertions(+), 51 deletions(-) diff --git a/python/src/com/jetbrains/python/psi/PyUtil.java b/python/src/com/jetbrains/python/psi/PyUtil.java index 3c5c1b1ee3ce..3e368553c1fa 100644 --- a/python/src/com/jetbrains/python/psi/PyUtil.java +++ b/python/src/com/jetbrains/python/psi/PyUtil.java @@ -846,20 +846,19 @@ public class PyUtil { }); } - public static T getParameterizedCachedValue(@NotNull PsiElement element, @NotNull P param, @NotNull NotNullFunction f) { - final Map cache = CachedValuesManager.getCachedValue(element, new CachedValueProvider>() { + private static final Object NULL_VALUE = new Object(); + public static T getParameterizedCachedValue(@NotNull PsiElement element, @Nullable P param, @NotNull NullableFunction f) { + final Map cache = CachedValuesManager.getCachedValue(element, new CachedValueProvider>() { @Nullable @Override - public Result> compute() { + public Result> compute() { + // TODO should be concurrent map instead return Result.create(Maps.newHashMap(), PsiModificationTracker.MODIFICATION_COUNT); } }); - T result = cache.get(param); - if (result == null) { - result = f.fun(param); - cache.put(param, result); - } - return result; + final Object wrapped = cache.computeIfAbsent(param, p -> ObjectUtils.notNull(f.fun(param), NULL_VALUE)); + //noinspection unchecked + return (T)ObjectUtils.nullizeByCondition(wrapped, NULL_VALUE::equals); } /** diff --git a/python/src/com/jetbrains/python/psi/resolve/QualifiedNameFinder.java b/python/src/com/jetbrains/python/psi/resolve/QualifiedNameFinder.java index bab8681fbbd6..1baebc144899 100644 --- a/python/src/com/jetbrains/python/psi/resolve/QualifiedNameFinder.java +++ b/python/src/com/jetbrains/python/psi/resolve/QualifiedNameFinder.java @@ -27,10 +27,7 @@ import com.intellij.psi.util.QualifiedName; import com.jetbrains.python.PyNames; import com.jetbrains.python.codeInsight.controlflow.ScopeOwner; import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil; -import com.jetbrains.python.psi.PyClass; -import com.jetbrains.python.psi.PyElement; -import com.jetbrains.python.psi.PyFile; -import com.jetbrains.python.psi.PyFunction; +import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.PyBuiltinCache; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -114,49 +111,51 @@ public class QualifiedNameFinder { */ @Nullable public static QualifiedName findCanonicalImportPath(@NotNull PsiElement symbol, @Nullable PsiElement foothold) { - PsiFileSystemItem srcfile = symbol instanceof PsiFileSystemItem ? (PsiFileSystemItem)symbol : symbol.getContainingFile(); - if (srcfile == null) { - return null; - } - VirtualFile virtualFile = srcfile.getVirtualFile(); - if (virtualFile == null) { - return null; - } - if (srcfile instanceof PsiFile && symbol instanceof PsiNamedElement && !(symbol instanceof PsiFileSystemItem)) { - PsiElement toplevel = symbol; - if (symbol instanceof PyFunction) { - final PyClass containingClass = ((PyFunction)symbol).getContainingClass(); - if (containingClass != null) { - toplevel = containingClass; - } + return PyUtil.getParameterizedCachedValue(symbol, foothold, param -> { + PsiFileSystemItem srcfile = symbol instanceof PsiFileSystemItem ? (PsiFileSystemItem)symbol : symbol.getContainingFile(); + if (srcfile == null) { + return null; } - PsiDirectory dir = ((PsiFile)srcfile).getContainingDirectory(); - while (dir != null) { - PsiFile initPy = dir.findFile(PyNames.INIT_DOT_PY); - if (initPy == null) { - break; - } - if (initPy instanceof PyFile) { - //noinspection ConstantConditions - final List resolved = ((PyFile)initPy).multiResolveName(((PsiNamedElement)toplevel).getName()); - final PsiElement finalTopLevel = toplevel; - if (resolved.stream().anyMatch(r -> r.getElement() == finalTopLevel)) { - virtualFile = dir.getVirtualFile(); + VirtualFile virtualFile = srcfile.getVirtualFile(); + if (virtualFile == null) { + return null; + } + if (srcfile instanceof PsiFile && symbol instanceof PsiNamedElement && !(symbol instanceof PsiFileSystemItem)) { + PsiElement toplevel = symbol; + if (symbol instanceof PyFunction) { + final PyClass containingClass = ((PyFunction)symbol).getContainingClass(); + if (containingClass != null) { + toplevel = containingClass; } } - dir = dir.getParentDirectory(); - } - } - final QualifiedName qname = findShortestImportableQName(foothold != null ? foothold : symbol, virtualFile); - if (qname != null) { - for (PyCanonicalPathProvider provider : Extensions.getExtensions(PyCanonicalPathProvider.EP_NAME)) { - final QualifiedName restored = provider.getCanonicalPath(qname, foothold); - if (restored != null) { - return restored; + PsiDirectory dir = ((PsiFile)srcfile).getContainingDirectory(); + while (dir != null) { + PsiFile initPy = dir.findFile(PyNames.INIT_DOT_PY); + if (initPy == null) { + break; + } + if (initPy instanceof PyFile) { + //noinspection ConstantConditions + final List resolved = ((PyFile)initPy).multiResolveName(((PsiNamedElement)toplevel).getName()); + final PsiElement finalTopLevel = toplevel; + if (resolved.stream().anyMatch(r -> r.getElement() == finalTopLevel)) { + virtualFile = dir.getVirtualFile(); + } + } + dir = dir.getParentDirectory(); } } - } - return qname; + final QualifiedName qname = findShortestImportableQName(param != null ? param : symbol, virtualFile); + if (qname != null) { + for (PyCanonicalPathProvider provider : Extensions.getExtensions(PyCanonicalPathProvider.EP_NAME)) { + final QualifiedName restored = provider.getCanonicalPath(qname, param); + if (restored != null) { + return restored; + } + } + } + return qname; + }); } @Nullable From b529a826e89ac7d2fdce45b42c5fe7223d4723df Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Mon, 6 Feb 2017 12:20:22 +0300 Subject: [PATCH 05/33] PY-21180 PyUtil#getParametrizedCachedValue uses ConcurrentHashMap in case multiple read-only threads update the same cache simultaneously --- python/src/com/jetbrains/python/psi/PyUtil.java | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/python/src/com/jetbrains/python/psi/PyUtil.java b/python/src/com/jetbrains/python/psi/PyUtil.java index 3e368553c1fa..dc656f0a5e66 100644 --- a/python/src/com/jetbrains/python/psi/PyUtil.java +++ b/python/src/com/jetbrains/python/psi/PyUtil.java @@ -846,19 +846,12 @@ public class PyUtil { }); } - private static final Object NULL_VALUE = new Object(); public static T getParameterizedCachedValue(@NotNull PsiElement element, @Nullable P param, @NotNull NullableFunction f) { - final Map cache = CachedValuesManager.getCachedValue(element, new CachedValueProvider>() { - @Nullable - @Override - public Result> compute() { - // TODO should be concurrent map instead - return Result.create(Maps.newHashMap(), PsiModificationTracker.MODIFICATION_COUNT); - } + final Map, Optional> cache = CachedValuesManager.getCachedValue(element, () -> { + // concurrent hash map is a null-hostile collection + return CachedValueProvider.Result.create(Maps.newConcurrentMap(), PsiModificationTracker.MODIFICATION_COUNT); }); - final Object wrapped = cache.computeIfAbsent(param, p -> ObjectUtils.notNull(f.fun(param), NULL_VALUE)); - //noinspection unchecked - return (T)ObjectUtils.nullizeByCondition(wrapped, NULL_VALUE::equals); + return cache.computeIfAbsent(Optional.ofNullable(param), p -> Optional.ofNullable(f.fun(param))).orElse(null); } /** From 4cdf5b6a214103d2913fc6ff9710e6e14d9bc74a Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Thu, 9 Feb 2017 18:46:04 +0300 Subject: [PATCH 06/33] PY-22570 Filter out private names when collecting symbols imported via wildcard for code completion --- .../psi/impl/PyFromImportStatementImpl.java | 15 +++++++++++++-- .../completion/namesReexportedViaStarImport/a.py | 1 + .../namesReexportedViaStarImport/pkg/__init__.py | 2 ++ .../namesReexportedViaStarImport/pkg/mod1.py | 2 ++ .../namesReexportedViaStarImport/pkg/mod2.py | 4 ++++ .../jetbrains/python/PythonCompletionTest.java | 9 +++++++++ 6 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 python/testData/completion/namesReexportedViaStarImport/a.py create mode 100644 python/testData/completion/namesReexportedViaStarImport/pkg/__init__.py create mode 100644 python/testData/completion/namesReexportedViaStarImport/pkg/mod1.py create mode 100644 python/testData/completion/namesReexportedViaStarImport/pkg/mod2.py diff --git a/python/src/com/jetbrains/python/psi/impl/PyFromImportStatementImpl.java b/python/src/com/jetbrains/python/psi/impl/PyFromImportStatementImpl.java index 0a8a015b62f1..9ac5bae4811d 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyFromImportStatementImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyFromImportStatementImpl.java @@ -17,11 +17,12 @@ package com.jetbrains.python.psi.impl; import com.google.common.collect.ImmutableList; import com.intellij.lang.ASTNode; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; +import com.intellij.psi.scope.DelegatingScopeProcessor; import com.intellij.psi.scope.PsiScopeProcessor; import com.intellij.psi.stubs.IStubElementType; import com.intellij.psi.util.QualifiedName; -import com.intellij.util.ArrayFactory; import com.intellij.util.ArrayUtil; import com.intellij.util.containers.ContainerUtil; import com.jetbrains.python.PyElementTypes; @@ -161,7 +162,17 @@ public class PyFromImportStatementImpl extends PyBaseElementImpl \ No newline at end of file diff --git a/python/testData/completion/namesReexportedViaStarImport/pkg/__init__.py b/python/testData/completion/namesReexportedViaStarImport/pkg/__init__.py new file mode 100644 index 000000000000..0ef7b811ce65 --- /dev/null +++ b/python/testData/completion/namesReexportedViaStarImport/pkg/__init__.py @@ -0,0 +1,2 @@ +from .mod1 import * +from .mod2 import * diff --git a/python/testData/completion/namesReexportedViaStarImport/pkg/mod1.py b/python/testData/completion/namesReexportedViaStarImport/pkg/mod1.py new file mode 100644 index 000000000000..c56290a94e2e --- /dev/null +++ b/python/testData/completion/namesReexportedViaStarImport/pkg/mod1.py @@ -0,0 +1,2 @@ +foo = 1 +_foo = 2 \ No newline at end of file diff --git a/python/testData/completion/namesReexportedViaStarImport/pkg/mod2.py b/python/testData/completion/namesReexportedViaStarImport/pkg/mod2.py new file mode 100644 index 000000000000..eed13f89da1e --- /dev/null +++ b/python/testData/completion/namesReexportedViaStarImport/pkg/mod2.py @@ -0,0 +1,4 @@ +__all__ = ['_bar'] + +bar = 1 +_bar = 2 diff --git a/python/testSrc/com/jetbrains/python/PythonCompletionTest.java b/python/testSrc/com/jetbrains/python/PythonCompletionTest.java index 4e105d631e98..6e66e1a7d0d8 100644 --- a/python/testSrc/com/jetbrains/python/PythonCompletionTest.java +++ b/python/testSrc/com/jetbrains/python/PythonCompletionTest.java @@ -1105,6 +1105,15 @@ public class PythonCompletionTest extends PyTestCase { assertContainsElements(suggested, "baz"); } + // PY-22570 + public void testNamesReexportedViaStarImport() { + myFixture.copyDirectoryToProject(getTestName(true), ""); + myFixture.configureByFile("a.py"); + myFixture.completeBasic(); + final List variants = myFixture.getLookupElementStrings(); + assertSameElements(variants, "mod1", "mod2", "foo", "_bar"); + } + @Override protected String getTestDataPath() { return super.getTestDataPath() + "/completion"; From 81d84c9f5674bd16361ea5a4a7dea4eba601fdc2 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Fri, 10 Feb 2017 16:06:18 +0300 Subject: [PATCH 07/33] Simplify a few places in resolve of names in Python modules and star imports --- .../jetbrains/python/psi/impl/PyFileImpl.java | 15 +++------------ .../psi/impl/PyStarImportElementImpl.java | 18 ++++++------------ 2 files changed, 9 insertions(+), 24 deletions(-) diff --git a/python/src/com/jetbrains/python/psi/impl/PyFileImpl.java b/python/src/com/jetbrains/python/psi/impl/PyFileImpl.java index 105346707114..b950c0e12759 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyFileImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyFileImpl.java @@ -29,6 +29,7 @@ import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; +import com.intellij.psi.scope.DelegatingScopeProcessor; import com.intellij.psi.scope.PsiScopeProcessor; import com.intellij.psi.stubs.StubElement; import com.intellij.psi.util.PsiModificationTracker; @@ -298,25 +299,15 @@ public class PyFileImpl extends PsiFileBase implements PyFile, PyExpression { @NotNull PsiElement place) { final List dunderAll = getDunderAll(); final List remainingDunderAll = dunderAll == null ? null : new ArrayList<>(dunderAll); - PsiScopeProcessor wrapper = new PsiScopeProcessor() { + PsiScopeProcessor wrapper = new DelegatingScopeProcessor(processor) { @Override public boolean execute(@NotNull PsiElement element, @NotNull ResolveState state) { - if (!processor.execute(element, state)) return false; + if (!super.execute(element, state)) return false; if (remainingDunderAll != null && element instanceof PyElement) { remainingDunderAll.remove(((PyElement)element).getName()); } return true; } - - @Override - public T getHint(@NotNull Key hintKey) { - return processor.getHint(hintKey); - } - - @Override - public void handleEvent(@NotNull Event event, @Nullable Object associated) { - processor.handleEvent(event, associated); - } }; Set pyFiles = resolveState.get(PROCESSED_FILES); diff --git a/python/src/com/jetbrains/python/psi/impl/PyStarImportElementImpl.java b/python/src/com/jetbrains/python/psi/impl/PyStarImportElementImpl.java index 5a7c8b29823c..784162c6cccf 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyStarImportElementImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyStarImportElementImpl.java @@ -37,6 +37,8 @@ import javax.swing.*; import java.util.Collections; import java.util.List; +import static com.jetbrains.python.psi.PyUtil.as; + /** * @author dcheryasov */ @@ -93,21 +95,13 @@ public class PyStarImportElementImpl extends PyBaseElementImpl importedFiles = fromImportStatement.resolveImportSourceCandidates(); for (PsiElement importedFile : new HashSet<>(importedFiles)) { // resolver gives lots of duplicates - final PsiElement source = PyUtil.turnDirIntoInit(importedFile); - if (source instanceof PyFile) { - PyFile sourceFile = (PyFile)source; + final PyFile sourceFile = as(PyUtil.turnDirIntoInit(importedFile), PyFile.class); + if (sourceFile != null && PyUtil.isStarImportableFrom(name, sourceFile)) { final PyModuleType moduleType = new PyModuleType(sourceFile); final List results = moduleType.resolveMember(name, null, AccessDirection.READ, PyResolveContext.defaultContext()); - if (results != null && !results.isEmpty() && PyUtil.isStarImportableFrom(name, sourceFile)) { - if (results.isEmpty()) { - return Collections.emptyList(); - } - final List res = Lists.newArrayList(); - for (RatedResolveResult result : results) { - res.add(result); - } - return res; + if (results != null && !results.isEmpty()) { + return Lists.newArrayList(results); } } } From 766517951b0daf4e7dee0da6275fafa8acf26c23 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Fri, 3 Mar 2017 16:02:22 +0300 Subject: [PATCH 08/33] PY-21180 Avoid possible collisions of caches in PyUtil#getParameterizedCachedValue() First, each cache has the unique user data key, associated with the function passed to getParameterizedCachedValue(), as opposed to the instance CachedValueProvider which is actually the same for all caches created this way. Second, in QualifiedNameFinder#findCanonicalImportPath() use pair of (symbol, anchor) as the cache key so that the lambda doesn't capture values from outer scope. --- .../jetbrains/python/psi/impl/PyPsiUtils.java | 2 +- .../src/com/jetbrains/python/psi/PyUtil.java | 3 +- .../psi/resolve/QualifiedNameFinder.java | 88 ++++++++++--------- 3 files changed, 51 insertions(+), 42 deletions(-) diff --git a/python/psi-api/src/com/jetbrains/python/psi/impl/PyPsiUtils.java b/python/psi-api/src/com/jetbrains/python/psi/impl/PyPsiUtils.java index 8811c5000d75..cc867d2a69e8 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/impl/PyPsiUtils.java +++ b/python/psi-api/src/com/jetbrains/python/psi/impl/PyPsiUtils.java @@ -624,7 +624,7 @@ public class PyPsiUtils { Preconditions.checkArgument(!module.isDisposed(), String.format("Module %s is disposed", module)); } - @NotNull + @Nullable public static PsiFileSystemItem getFileSystemItem(@NotNull PsiElement element) { if (element instanceof PsiFileSystemItem) { return (PsiFileSystemItem)element; diff --git a/python/src/com/jetbrains/python/psi/PyUtil.java b/python/src/com/jetbrains/python/psi/PyUtil.java index dc656f0a5e66..7e4c74feea09 100644 --- a/python/src/com/jetbrains/python/psi/PyUtil.java +++ b/python/src/com/jetbrains/python/psi/PyUtil.java @@ -847,7 +847,8 @@ public class PyUtil { } public static T getParameterizedCachedValue(@NotNull PsiElement element, @Nullable P param, @NotNull NullableFunction f) { - final Map, Optional> cache = CachedValuesManager.getCachedValue(element, () -> { + final CachedValuesManager manager = CachedValuesManager.getManager(element.getProject()); + final Map, Optional> cache = CachedValuesManager.getCachedValue(element, manager.getKeyForClass(f.getClass()), () -> { // concurrent hash map is a null-hostile collection return CachedValueProvider.Result.create(Maps.newConcurrentMap(), PsiModificationTracker.MODIFICATION_COUNT); }); diff --git a/python/src/com/jetbrains/python/psi/resolve/QualifiedNameFinder.java b/python/src/com/jetbrains/python/psi/resolve/QualifiedNameFinder.java index 1baebc144899..24c4213715ef 100644 --- a/python/src/com/jetbrains/python/psi/resolve/QualifiedNameFinder.java +++ b/python/src/com/jetbrains/python/psi/resolve/QualifiedNameFinder.java @@ -18,6 +18,7 @@ package com.jetbrains.python.psi.resolve; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.module.Module; import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.util.Couple; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VfsUtilCore; @@ -29,6 +30,7 @@ import com.jetbrains.python.codeInsight.controlflow.ScopeOwner; import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.PyBuiltinCache; +import com.jetbrains.python.psi.impl.PyPsiUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -111,51 +113,57 @@ public class QualifiedNameFinder { */ @Nullable public static QualifiedName findCanonicalImportPath(@NotNull PsiElement symbol, @Nullable PsiElement foothold) { - return PyUtil.getParameterizedCachedValue(symbol, foothold, param -> { - PsiFileSystemItem srcfile = symbol instanceof PsiFileSystemItem ? (PsiFileSystemItem)symbol : symbol.getContainingFile(); - if (srcfile == null) { - return null; - } - VirtualFile virtualFile = srcfile.getVirtualFile(); - if (virtualFile == null) { - return null; - } - if (srcfile instanceof PsiFile && symbol instanceof PsiNamedElement && !(symbol instanceof PsiFileSystemItem)) { - PsiElement toplevel = symbol; - if (symbol instanceof PyFunction) { - final PyClass containingClass = ((PyFunction)symbol).getContainingClass(); - if (containingClass != null) { - toplevel = containingClass; - } - } - PsiDirectory dir = ((PsiFile)srcfile).getContainingDirectory(); - while (dir != null) { - PsiFile initPy = dir.findFile(PyNames.INIT_DOT_PY); - if (initPy == null) { - break; - } - if (initPy instanceof PyFile) { - //noinspection ConstantConditions - final List resolved = ((PyFile)initPy).multiResolveName(((PsiNamedElement)toplevel).getName()); - final PsiElement finalTopLevel = toplevel; - if (resolved.stream().anyMatch(r -> r.getElement() == finalTopLevel)) { - virtualFile = dir.getVirtualFile(); - } - } - dir = dir.getParentDirectory(); + return PyUtil.getParameterizedCachedValue(symbol, Couple.of(symbol, foothold), QualifiedNameFinder::doFindCanonicalImportPath); + } + + @Nullable + private static QualifiedName doFindCanonicalImportPath(@NotNull Couple param) { + final PsiElement symbol = param.getFirst(); + final PsiElement foothold = param.getSecond(); + + final PsiFileSystemItem srcfile = PyPsiUtils.getFileSystemItem(symbol); + if (srcfile == null) { + return null; + } + VirtualFile virtualFile = srcfile.getVirtualFile(); + if (virtualFile == null) { + return null; + } + if (srcfile instanceof PsiFile && symbol instanceof PsiNamedElement && !(symbol instanceof PsiFileSystemItem)) { + PsiElement toplevel = symbol; + if (symbol instanceof PyFunction) { + final PyClass containingClass = ((PyFunction)symbol).getContainingClass(); + if (containingClass != null) { + toplevel = containingClass; } } - final QualifiedName qname = findShortestImportableQName(param != null ? param : symbol, virtualFile); - if (qname != null) { - for (PyCanonicalPathProvider provider : Extensions.getExtensions(PyCanonicalPathProvider.EP_NAME)) { - final QualifiedName restored = provider.getCanonicalPath(qname, param); - if (restored != null) { - return restored; + PsiDirectory dir = ((PsiFile)srcfile).getContainingDirectory(); + while (dir != null) { + PsiFile initPy = dir.findFile(PyNames.INIT_DOT_PY); + if (initPy == null) { + break; + } + if (initPy instanceof PyFile) { + //noinspection ConstantConditions + final List resolved = ((PyFile)initPy).multiResolveName(((PsiNamedElement)toplevel).getName()); + final PsiElement finalTopLevel = toplevel; + if (resolved.stream().anyMatch(r -> r.getElement() == finalTopLevel)) { + virtualFile = dir.getVirtualFile(); } } + dir = dir.getParentDirectory(); } - return qname; - }); + } + final QualifiedName qname = findShortestImportableQName(foothold != null ? foothold : symbol, virtualFile); + if (qname != null) { + for (PyCanonicalPathProvider provider : Extensions.getExtensions(PyCanonicalPathProvider.EP_NAME)) { + final QualifiedName restored = provider.getCanonicalPath(qname, foothold); + if (restored != null) { + return restored; + } + } + } + return qname; } @Nullable From c6f1c260ab9c68dfba7ebcce999563395030b6bf Mon Sep 17 00:00:00 2001 From: Aleksey Pivovarov Date: Mon, 6 Mar 2017 14:00:28 +0300 Subject: [PATCH 09/33] IDEA-169169 Revert "IDEA-168502 ui: fix memory leak via ActionToolbarImpl" `transparentOnly=true` mode reuses presentations from previous runs. This reverts commit fa9d2a79f4b53904518d8a10ab61d985926e3290. --- .../openapi/actionSystem/impl/ActionToolbarImpl.java | 2 -- .../actionSystem/impl/PresentationFactory.java | 11 ----------- 2 files changed, 13 deletions(-) diff --git a/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionToolbarImpl.java b/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionToolbarImpl.java index 01742027e476..b2103ce43c42 100644 --- a/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionToolbarImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionToolbarImpl.java @@ -951,8 +951,6 @@ public class ActionToolbarImpl extends JPanel implements ActionToolbar, QuickAct myPlace, myActionManager, transparentOnly); if (forced || !newVisibleActions.equals(myVisibleActions)) { - myPresentationFactory.retainAll(newVisibleActions); - boolean shouldRebuildUI = newVisibleActions.isEmpty() || myVisibleActions.isEmpty(); myVisibleActions = newVisibleActions; diff --git a/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/PresentationFactory.java b/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/PresentationFactory.java index 6f1aa461341b..6c4df4a8abb2 100644 --- a/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/PresentationFactory.java +++ b/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/PresentationFactory.java @@ -18,11 +18,9 @@ package com.intellij.openapi.actionSystem.impl; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.Presentation; import com.intellij.openapi.application.ApplicationManager; -import com.intellij.util.containers.HashSet; import com.intellij.util.containers.WeakHashMap; import org.jetbrains.annotations.NotNull; -import java.util.Collection; import java.util.Map; public class PresentationFactory { @@ -54,13 +52,4 @@ public class PresentationFactory { ApplicationManager.getApplication().assertIsDispatchThread(); myAction2Presentation.clear(); } - - public void retainAll(@NotNull Collection actions) { - HashSet toRemove = new HashSet<>(myAction2Presentation.keySet()); - toRemove.removeAll(actions); - - for (AnAction action : toRemove) { - myAction2Presentation.remove(action); - } - } } From 9aa4667e353da83bc65ade1f0fe245e2f5c9ead5 Mon Sep 17 00:00:00 2001 From: peter Date: Mon, 6 Mar 2017 12:20:45 +0100 Subject: [PATCH 10/33] DuplicateStringLiteralInspection: save time by not restoring from anchors --- .../DuplicateStringLiteralInspection.java | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/plugins/java-i18n/src/com/intellij/codeInspection/duplicateStringLiteral/DuplicateStringLiteralInspection.java b/plugins/java-i18n/src/com/intellij/codeInspection/duplicateStringLiteral/DuplicateStringLiteralInspection.java index b113fef17bf6..6282312da9b9 100644 --- a/plugins/java-i18n/src/com/intellij/codeInspection/duplicateStringLiteral/DuplicateStringLiteralInspection.java +++ b/plugins/java-i18n/src/com/intellij/codeInspection/duplicateStringLiteral/DuplicateStringLiteralInspection.java @@ -120,10 +120,10 @@ public class DuplicateStringLiteralInspection extends BaseLocalInspectionTool { } @NotNull - private List findDuplicateLiterals(String stringToFind, Project project) { + private List findDuplicateLiterals(String stringToFind, Project project) { Set resultFiles = getCandidateFiles(stringToFind, project); if (resultFiles.isEmpty()) return Collections.emptyList(); - List foundExpr = new ArrayList<>(); + List foundExpr = new ArrayList<>(); for (final PsiFile file : resultFiles) { ProgressManager.checkCanceled(); @@ -138,7 +138,7 @@ public class DuplicateStringLiteralInspection extends BaseLocalInspectionTool { if (element == null || !(element.getParent() instanceof PsiLiteralExpression)) return true; PsiLiteralExpression expression = (PsiLiteralExpression)element.getParent(); if (Comparing.equal(stringToFind, expression.getValue()) && shouldCheck(expression)) { - foundExpr.add(PsiAnchor.create(expression)); + foundExpr.add(expression); } return true; }); @@ -157,10 +157,8 @@ public class DuplicateStringLiteralInspection extends BaseLocalInspectionTool { List foundExpr = getDuplicateLiterals(stringToFind, originalExpression); if (foundExpr.isEmpty()) return; Set classes = new THashSet<>(); - for (PsiLiteralExpression expr : foundExpr) { - if (expr == originalExpression) continue; + for (PsiElement aClass : foundExpr) { ProgressManager.checkCanceled(); - PsiElement aClass = expr; do { aClass = PsiTreeUtil.getParentOfType(aClass, PsiClass.class); } @@ -208,15 +206,12 @@ public class DuplicateStringLiteralInspection extends BaseLocalInspectionTool { @NotNull private List getDuplicateLiterals(String stringToFind, PsiLiteralExpression place) { Project project = place.getProject(); - Map> map = CachedValuesManager.getManager(project).getCachedValue(project, () -> { - Map> value = ConcurrentFactoryMap.createConcurrentMap( + Map> map = CachedValuesManager.getManager(project).getCachedValue(project, () -> { + Map> value = ConcurrentFactoryMap.createConcurrentMap( s -> Collections.unmodifiableList(findDuplicateLiterals(s, project))); return CachedValueProvider.Result.create(value, PsiModificationTracker.MODIFICATION_COUNT); }); - return ContainerUtil.mapNotNull(map.get(stringToFind), anchor -> { - PsiLiteralExpression literal = (PsiLiteralExpression)anchor.retrieve(); - return literal == place ? null : literal; - }); + return ContainerUtil.filter(map.get(stringToFind), literal -> literal != place); } private boolean shouldCheck(@NotNull PsiLiteralExpression expression) { From 42ec74906491dc6a032ae694433ae045cf72d9dc Mon Sep 17 00:00:00 2001 From: Maxim Kropotov Date: Mon, 6 Mar 2017 09:52:20 +0300 Subject: [PATCH 11/33] WEB-25750 Intentions: restore "Check RegExp" action --- .../intelliLang/inject/AbstractLanguageInjectionSupport.java | 5 +++++ .../plugins/intelliLang/inject/CommentLanguageInjector.java | 2 ++ .../plugins/intelliLang/inject/LanguageInjectionSupport.java | 2 ++ 3 files changed, 9 insertions(+) diff --git a/plugins/IntelliLang/src/org/intellij/plugins/intelliLang/inject/AbstractLanguageInjectionSupport.java b/plugins/IntelliLang/src/org/intellij/plugins/intelliLang/inject/AbstractLanguageInjectionSupport.java index 44fab8be2d3d..b5f783937aa6 100644 --- a/plugins/IntelliLang/src/org/intellij/plugins/intelliLang/inject/AbstractLanguageInjectionSupport.java +++ b/plugins/IntelliLang/src/org/intellij/plugins/intelliLang/inject/AbstractLanguageInjectionSupport.java @@ -56,6 +56,11 @@ public abstract class AbstractLanguageInjectionSupport extends LanguageInjection return false; } + @Override + public boolean useDefaultCommentInjector() { + return true; + } + @Nullable @Override public BaseInjection findCommentInjection(@NotNull PsiElement host, @Nullable Ref commentRef) { diff --git a/plugins/IntelliLang/src/org/intellij/plugins/intelliLang/inject/CommentLanguageInjector.java b/plugins/IntelliLang/src/org/intellij/plugins/intelliLang/inject/CommentLanguageInjector.java index de479811a62e..696bbf5f9436 100644 --- a/plugins/IntelliLang/src/org/intellij/plugins/intelliLang/inject/CommentLanguageInjector.java +++ b/plugins/IntelliLang/src/org/intellij/plugins/intelliLang/inject/CommentLanguageInjector.java @@ -63,6 +63,8 @@ public class CommentLanguageInjector implements MultiHostInjector { if (support == myInjectorSupport && applicableFound) continue; applicableFound = true; + if (!support.useDefaultCommentInjector()) + continue; BaseInjection injection = support.findCommentInjection(host, null); if (injection == null) continue; if (!InjectorUtils.registerInjectionSimple(host, injection, support, registrar)) continue; diff --git a/plugins/IntelliLang/src/org/intellij/plugins/intelliLang/inject/LanguageInjectionSupport.java b/plugins/IntelliLang/src/org/intellij/plugins/intelliLang/inject/LanguageInjectionSupport.java index 167ddb4db25a..23230a249c8c 100644 --- a/plugins/IntelliLang/src/org/intellij/plugins/intelliLang/inject/LanguageInjectionSupport.java +++ b/plugins/IntelliLang/src/org/intellij/plugins/intelliLang/inject/LanguageInjectionSupport.java @@ -57,6 +57,8 @@ public abstract class LanguageInjectionSupport { public abstract boolean useDefaultInjector(PsiLanguageInjectionHost host); + public abstract boolean useDefaultCommentInjector(); + @Nullable public abstract BaseInjection findCommentInjection(@NotNull PsiElement host, @Nullable Ref commentRef); From 7c72d1ce13af77560e6ee68548d630665674aa79 Mon Sep 17 00:00:00 2001 From: irengrig Date: Mon, 6 Mar 2017 10:55:53 +0100 Subject: [PATCH 12/33] WEB-25757 JSON schema completion issues (tsconfig.json) + tests for json schema based insertion handler also some side effects were fixed (found while writing tests) --- ...onBySchemaObjectCompletionContributor.java | 24 ++-- .../jsonSchema/impl/JsonLikePsiWalker.java | 2 +- .../impl/JsonOriginalPsiWalker.java | 17 ++- .../impl/ReadJsonSchemaFromPsi.java | 27 ++-- .../impl/JsonBySchemaHeavyCompletionTest.java | 126 ++++++++++++++++++ .../completion/insertEnumValue/Schema.json | 7 + .../completion/insertEnumValue/testValue.json | 3 + .../insertEnumValue/testValue_after.json | 3 + .../completion/insertPropertyName/Schema.json | 25 ++++ .../insertPropertyName/testBooleanType.json | 3 + .../testBooleanTypeComma.json | 4 + .../testBooleanTypeComma_after.json | 4 + .../testBooleanType_after.json | 3 + .../insertPropertyName/testIntegerType.json | 3 + .../testIntegerTypeComma.json | 4 + .../testIntegerTypeComma_after.json | 4 + .../testIntegerType_after.json | 3 + .../insertPropertyName/testName.json | 3 + .../testNameWithDefaultIntegerValue.json | 3 + ...eWithDefaultIntegerValueNoQuotesComma.json | 4 + ...efaultIntegerValueNoQuotesComma_after.json | 4 + ...testNameWithDefaultIntegerValue_after.json | 3 + .../testNameWithDefaultStringValue.json | 3 + .../testNameWithDefaultStringValueComma.json | 4 + ...NameWithDefaultStringValueComma_after.json | 4 + ...estNameWithDefaultStringValueNoQuotes.json | 3 + ...eWithDefaultStringValueNoQuotes_after.json | 3 + .../testNameWithDefaultStringValue_after.json | 3 + .../insertPropertyName/testName_after.json | 3 + .../insertPropertyName/testObjectType.json | 3 + .../testObjectType_after.json | 5 + .../insertPropertyName/testStringType.json | 3 + .../testStringTypeComma.json | 4 + .../testStringTypeComma_after.json | 4 + .../testStringType_after.json | 3 + 35 files changed, 294 insertions(+), 30 deletions(-) create mode 100644 json/tests/test/com/jetbrains/jsonSchema/impl/JsonBySchemaHeavyCompletionTest.java create mode 100644 json/tests/testData/jsonSchema/completion/insertEnumValue/Schema.json create mode 100644 json/tests/testData/jsonSchema/completion/insertEnumValue/testValue.json create mode 100644 json/tests/testData/jsonSchema/completion/insertEnumValue/testValue_after.json create mode 100644 json/tests/testData/jsonSchema/completion/insertPropertyName/Schema.json create mode 100644 json/tests/testData/jsonSchema/completion/insertPropertyName/testBooleanType.json create mode 100644 json/tests/testData/jsonSchema/completion/insertPropertyName/testBooleanTypeComma.json create mode 100644 json/tests/testData/jsonSchema/completion/insertPropertyName/testBooleanTypeComma_after.json create mode 100644 json/tests/testData/jsonSchema/completion/insertPropertyName/testBooleanType_after.json create mode 100644 json/tests/testData/jsonSchema/completion/insertPropertyName/testIntegerType.json create mode 100644 json/tests/testData/jsonSchema/completion/insertPropertyName/testIntegerTypeComma.json create mode 100644 json/tests/testData/jsonSchema/completion/insertPropertyName/testIntegerTypeComma_after.json create mode 100644 json/tests/testData/jsonSchema/completion/insertPropertyName/testIntegerType_after.json create mode 100644 json/tests/testData/jsonSchema/completion/insertPropertyName/testName.json create mode 100644 json/tests/testData/jsonSchema/completion/insertPropertyName/testNameWithDefaultIntegerValue.json create mode 100644 json/tests/testData/jsonSchema/completion/insertPropertyName/testNameWithDefaultIntegerValueNoQuotesComma.json create mode 100644 json/tests/testData/jsonSchema/completion/insertPropertyName/testNameWithDefaultIntegerValueNoQuotesComma_after.json create mode 100644 json/tests/testData/jsonSchema/completion/insertPropertyName/testNameWithDefaultIntegerValue_after.json create mode 100644 json/tests/testData/jsonSchema/completion/insertPropertyName/testNameWithDefaultStringValue.json create mode 100644 json/tests/testData/jsonSchema/completion/insertPropertyName/testNameWithDefaultStringValueComma.json create mode 100644 json/tests/testData/jsonSchema/completion/insertPropertyName/testNameWithDefaultStringValueComma_after.json create mode 100644 json/tests/testData/jsonSchema/completion/insertPropertyName/testNameWithDefaultStringValueNoQuotes.json create mode 100644 json/tests/testData/jsonSchema/completion/insertPropertyName/testNameWithDefaultStringValueNoQuotes_after.json create mode 100644 json/tests/testData/jsonSchema/completion/insertPropertyName/testNameWithDefaultStringValue_after.json create mode 100644 json/tests/testData/jsonSchema/completion/insertPropertyName/testName_after.json create mode 100644 json/tests/testData/jsonSchema/completion/insertPropertyName/testObjectType.json create mode 100644 json/tests/testData/jsonSchema/completion/insertPropertyName/testObjectType_after.json create mode 100644 json/tests/testData/jsonSchema/completion/insertPropertyName/testStringType.json create mode 100644 json/tests/testData/jsonSchema/completion/insertPropertyName/testStringTypeComma.json create mode 100644 json/tests/testData/jsonSchema/completion/insertPropertyName/testStringTypeComma_after.json create mode 100644 json/tests/testData/jsonSchema/completion/insertPropertyName/testStringType_after.json diff --git a/json/src/com/jetbrains/jsonSchema/impl/JsonBySchemaObjectCompletionContributor.java b/json/src/com/jetbrains/jsonSchema/impl/JsonBySchemaObjectCompletionContributor.java index eace684d7c39..09f4820cb17f 100644 --- a/json/src/com/jetbrains/jsonSchema/impl/JsonBySchemaObjectCompletionContributor.java +++ b/json/src/com/jetbrains/jsonSchema/impl/JsonBySchemaObjectCompletionContributor.java @@ -112,7 +112,7 @@ class JsonBySchemaObjectCompletionContributor extends CompletionContributor { @NotNull VirtualFile schemaFile, @NotNull List steps) { if (isName) { - final boolean insertComma = myWalker.hasPropertiesBehind(myPosition); + final boolean insertComma = myWalker.hasPropertiesBehindAndNoComma(myPosition); final boolean hasValue = myWalker.isPropertyWithValue(myPosition.getParent().getParent()); final Collection properties = myWalker.getPropertyNamesOfParentObject(myOriginalPosition); @@ -222,7 +222,7 @@ class JsonBySchemaObjectCompletionContributor extends CompletionContributor { final JsonSchemaType type = jsonSchemaObject.getType(); final List values = jsonSchemaObject.getEnum(); - if (type != null || !ContainerUtil.isEmpty(values)) { + if (type != null || !ContainerUtil.isEmpty(values) || jsonSchemaObject.getDefault() != null) { builder = builder.withInsertHandler(createPropertyInsertHandler(jsonSchemaObject, hasValue, insertComma)); } else if (!hasValue) { builder = builder.withInsertHandler(createDefaultPropertyInsertHandler(hasValue, insertComma)); @@ -261,7 +261,8 @@ class JsonBySchemaObjectCompletionContributor extends CompletionContributor { final List values = jsonSchemaObject.getEnum(); if (type == null && values != null && !values.isEmpty()) type = detectType(values); final Object defaultValue = jsonSchemaObject.getDefault(); - final String defaultValueAsString = defaultValue == null ? null : String.valueOf(defaultValue); + final String defaultValueAsString = defaultValue == null ? null : defaultValue instanceof String ? "\"" + defaultValue + "\"" : + String.valueOf(defaultValue); JsonSchemaType finalType = type; return new InsertHandler() { @Override @@ -355,18 +356,19 @@ class JsonBySchemaObjectCompletionContributor extends CompletionContributor { String defaultValue, List values, JsonSchemaType type, String comma) { - final boolean isNumber = JsonSchemaType._integer.equals(type) || JsonSchemaType._number.equals(type); - String start = isNumber ? ":" : ":\""; - String end = isNumber ? "" : "\"" + comma; + final boolean isNumber = type != null && (JsonSchemaType._integer.equals(type) || JsonSchemaType._number.equals(type)) || + type == null && (defaultValue != null && + !StringUtil.isQuotedString(defaultValue) || values != null && ContainerUtil.and(values, v -> !(v instanceof String))); boolean hasValues = !ContainerUtil.isEmpty(values); boolean hasDefaultValue = !StringUtil.isEmpty(defaultValue); - String stringToInsert = start + (hasDefaultValue ? defaultValue : "") + end; - EditorModificationUtil.insertStringAtCaret(editor, stringToInsert, false, true, start.length() - comma.length()); - if (hasDefaultValue) { + String stringToInsert = ":" + (hasDefaultValue ? defaultValue : (isNumber ? "" : "\"\"")) + comma; + EditorModificationUtil.insertStringAtCaret(editor, stringToInsert, false, true, 1); + if (!isNumber || hasDefaultValue) { SelectionModel model = editor.getSelectionModel(); int caretStart = model.getSelectionStart(); - int newOffset = caretStart + defaultValue.length(); - model.setSelection(caretStart, newOffset); + int newOffset = caretStart + (hasDefaultValue ? defaultValue.length() : 1); + if (hasDefaultValue && !isNumber) newOffset--; + model.setSelection(isNumber ? caretStart : (caretStart + 1), newOffset); editor.getCaretModel().moveToOffset(newOffset); } diff --git a/json/src/com/jetbrains/jsonSchema/impl/JsonLikePsiWalker.java b/json/src/com/jetbrains/jsonSchema/impl/JsonLikePsiWalker.java index 287cbe92a072..a51a839d31fe 100644 --- a/json/src/com/jetbrains/jsonSchema/impl/JsonLikePsiWalker.java +++ b/json/src/com/jetbrains/jsonSchema/impl/JsonLikePsiWalker.java @@ -34,7 +34,7 @@ public interface JsonLikePsiWalker { PsiElement goUpToCheckable(@NotNull final PsiElement element); List findPosition(@NotNull final PsiElement element, boolean isName, boolean forceLastTransition); boolean isNameQuoted(); - boolean hasPropertiesBehind(@NotNull PsiElement element); + boolean hasPropertiesBehindAndNoComma(@NotNull PsiElement element); Set getPropertyNamesOfParentObject(@NotNull PsiElement element); @Nullable JsonPropertyAdapter getParentPropertyAdapter(@NotNull PsiElement element); boolean isTopJsonElement(@NotNull PsiElement element); diff --git a/json/src/com/jetbrains/jsonSchema/impl/JsonOriginalPsiWalker.java b/json/src/com/jetbrains/jsonSchema/impl/JsonOriginalPsiWalker.java index ece32b536416..66c12fe4d211 100644 --- a/json/src/com/jetbrains/jsonSchema/impl/JsonOriginalPsiWalker.java +++ b/json/src/com/jetbrains/jsonSchema/impl/JsonOriginalPsiWalker.java @@ -15,13 +15,13 @@ */ package com.jetbrains.jsonSchema.impl; +import com.intellij.json.JsonElementTypes; import com.intellij.json.psi.*; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.impl.source.tree.LeafPsiElement; import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -122,12 +122,21 @@ public class JsonOriginalPsiWalker implements JsonLikePsiWalker { } @Override - public boolean hasPropertiesBehind(@NotNull PsiElement element) { + public boolean hasPropertiesBehindAndNoComma(@NotNull PsiElement element) { + PsiElement current = element instanceof JsonProperty ? element : PsiTreeUtil.getParentOfType(element, JsonProperty.class); + while (current != null && current.getNode().getElementType() != JsonElementTypes.COMMA) { + current = current.getNextSibling(); + } + int commaOffset = current == null ? Integer.MAX_VALUE : current.getTextRange().getStartOffset(); final int offset = element.getTextRange().getStartOffset(); final JsonObject object = PsiTreeUtil.getParentOfType(element, JsonObject.class); if (object != null) { - return ContainerUtil.or(object.getPropertyList(), prop -> prop.getTextRange().getStartOffset() >= offset && - !PsiTreeUtil.isAncestor(prop, element, false)); + for (JsonProperty property : object.getPropertyList()) { + final int pOffset = property.getTextRange().getStartOffset(); + if (pOffset >= offset && !PsiTreeUtil.isAncestor(property, element, false)) { + return pOffset < commaOffset; + } + } } return false; } diff --git a/json/src/com/jetbrains/jsonSchema/impl/ReadJsonSchemaFromPsi.java b/json/src/com/jetbrains/jsonSchema/impl/ReadJsonSchemaFromPsi.java index 06c9526b3d46..96df01450bca 100644 --- a/json/src/com/jetbrains/jsonSchema/impl/ReadJsonSchemaFromPsi.java +++ b/json/src/com/jetbrains/jsonSchema/impl/ReadJsonSchemaFromPsi.java @@ -179,12 +179,7 @@ public class ReadJsonSchemaFromPsi { if (value instanceof JsonStringLiteral) { objects.add("\"" + StringUtil.unquoteString(((JsonStringLiteral)value).getValue()) + "\""); } else if (value instanceof JsonNumberLiteral) { - try { - final int i = Integer.parseInt(value.getText()); - objects.add(i); - } catch (NumberFormatException e) { - objects.add(((JsonNumberLiteral)value).getValue()); - } + objects.add(getNumber(value)); } else if (value instanceof JsonBooleanLiteral) { objects.add(((JsonBooleanLiteral)value).getValue()); } else if (value instanceof JsonNullLiteral) { @@ -196,6 +191,17 @@ public class ReadJsonSchemaFromPsi { }; } + @NotNull + private static Number getNumber(@NotNull JsonValue value) { + Number numberValue; + try { + numberValue = Integer.parseInt(value.getText()); + } catch (NumberFormatException e) { + numberValue = ((JsonNumberLiteral)value).getValue(); + } + return numberValue; + } + private PairConsumer createDependencies() { return (element, object) -> { if (element instanceof JsonObject) { @@ -330,17 +336,10 @@ public class ReadJsonSchemaFromPsi { } else if (element instanceof JsonStringLiteral) { object.setDefault(StringUtil.unquoteString(((JsonStringLiteral)element).getValue())); } else if (element instanceof JsonNumberLiteral) { - object.setDefault(((JsonNumberLiteral)element).getValue()); + object.setDefault(getNumber((JsonNumberLiteral) element)); } else if (element instanceof JsonBooleanLiteral) { object.setDefault(((JsonBooleanLiteral)element).getValue()); } }; } - - private class MyStringReader implements PairConsumer { - @Override - public void consume(JsonElement element, JsonSchemaObject object) { - - } - } } diff --git a/json/tests/test/com/jetbrains/jsonSchema/impl/JsonBySchemaHeavyCompletionTest.java b/json/tests/test/com/jetbrains/jsonSchema/impl/JsonBySchemaHeavyCompletionTest.java new file mode 100644 index 000000000000..65eecad8a1ec --- /dev/null +++ b/json/tests/test/com/jetbrains/jsonSchema/impl/JsonBySchemaHeavyCompletionTest.java @@ -0,0 +1,126 @@ +/* + * Copyright 2000-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.jsonSchema.impl; + +import com.intellij.codeInsight.completion.CodeCompletionHandlerBase; +import com.intellij.codeInsight.completion.CompletionType; +import com.jetbrains.jsonSchema.JsonSchemaHeavyAbstractTest; +import com.jetbrains.jsonSchema.JsonSchemaMappingsConfigurationBase; + +import java.util.Collections; + +/** + * @author Irina.Chernushina on 3/4/2017. + */ +public class JsonBySchemaHeavyCompletionTest extends JsonSchemaHeavyAbstractTest { + @Override + protected String getBasePath() { + return "/tests/testData/jsonSchema/completion"; + } + + public void testInsertEnumValue() throws Exception { + baseInsertTest("insertPropertyName", "testName"); + } + + public void testInsertNameWithDefaultStringValue() throws Exception { + baseInsertTest("insertPropertyName", "testNameWithDefaultStringValue"); + } + + public void testInsertNameWithDefaultIntegerValue() throws Exception { + baseInsertTest("insertPropertyName", "testNameWithDefaultIntegerValue"); + } + + public void testInsertIntegerType() throws Exception { + baseInsertTest("insertPropertyName", "testIntegerType"); + } + + public void testInsertStringType() throws Exception { + baseInsertTest("insertPropertyName", "testStringType"); + } + + public void testInsertObjectType() throws Exception { + baseInsertTest("insertPropertyName", "testObjectType"); + } + + public void testInsertBooleanType() throws Exception { + baseInsertTest("insertPropertyName", "testBooleanType"); + } + + //no quotes + public void testNameWithDefaultStringValueNoQuotes() throws Exception { + baseInsertTest("insertPropertyName", "testNameWithDefaultStringValueNoQuotes"); + } + + public void testNameWithDefaultIntegerValueNoQuotesComma() throws Exception { + baseInsertTest("insertPropertyName", "testNameWithDefaultIntegerValueNoQuotesComma"); + } + + //comma + public void testInsertIntegerTypeComma() throws Exception { + baseInsertTest("insertPropertyName", "testIntegerTypeComma"); + } + + public void testInsertBooleanTypeComma() throws Exception { + baseInsertTest("insertPropertyName", "testBooleanTypeComma"); + } + + public void testStringTypeComma() throws Exception { + baseInsertTest("insertPropertyName", "testStringTypeComma"); + } + + public void testNameWithDefaultStringValueComma() throws Exception { + baseInsertTest("insertPropertyName", "testNameWithDefaultStringValueComma"); + } + + private void baseInsertTest(final String folder, final String testFile) throws Exception { + skeleton(new Callback() { + @Override + public void registerSchemes() { + final String moduleDir = getModuleDir(getProject()); + + final JsonSchemaMappingsConfigurationBase.SchemaInfo base = + new JsonSchemaMappingsConfigurationBase.SchemaInfo("base", moduleDir + "/Schema.json", false, + Collections + .singletonList(new JsonSchemaMappingsConfigurationBase.Item("*.json", true, false))); + addSchema(base); + } + + @Override + public void configureFiles() throws Exception { + configureByFiles(null, "/" + folder + "/" + testFile + ".json", "/" + folder + "/Schema.json"); + } + + @Override + public void doCheck() { + final CodeCompletionHandlerBase handlerBase = new CodeCompletionHandlerBase(CompletionType.BASIC); + handlerBase.invokeCompletion(getProject(), getEditor()); + if (myItems != null) { + selectItem(myItems[0]); + } + try { + checkResultByFile("/" + folder + "/" + testFile + "_after.json"); + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + }); + } + + public void testInsertStringProperty() throws Exception { + + } +} diff --git a/json/tests/testData/jsonSchema/completion/insertEnumValue/Schema.json b/json/tests/testData/jsonSchema/completion/insertEnumValue/Schema.json new file mode 100644 index 000000000000..7b4f071c0294 --- /dev/null +++ b/json/tests/testData/jsonSchema/completion/insertEnumValue/Schema.json @@ -0,0 +1,7 @@ +{ + "properties": { + "jsx": { + "enum": [ "preserve", "react", "react-native" ] + } + } +} \ No newline at end of file diff --git a/json/tests/testData/jsonSchema/completion/insertEnumValue/testValue.json b/json/tests/testData/jsonSchema/completion/insertEnumValue/testValue.json new file mode 100644 index 000000000000..661c1ad7dc7a --- /dev/null +++ b/json/tests/testData/jsonSchema/completion/insertEnumValue/testValue.json @@ -0,0 +1,3 @@ +{ + "jsx": +} \ No newline at end of file diff --git a/json/tests/testData/jsonSchema/completion/insertEnumValue/testValue_after.json b/json/tests/testData/jsonSchema/completion/insertEnumValue/testValue_after.json new file mode 100644 index 000000000000..9357f4a220b8 --- /dev/null +++ b/json/tests/testData/jsonSchema/completion/insertEnumValue/testValue_after.json @@ -0,0 +1,3 @@ +{ + "jsx": "preserve" +} \ No newline at end of file diff --git a/json/tests/testData/jsonSchema/completion/insertPropertyName/Schema.json b/json/tests/testData/jsonSchema/completion/insertPropertyName/Schema.json new file mode 100644 index 000000000000..51a473a1d7b8 --- /dev/null +++ b/json/tests/testData/jsonSchema/completion/insertPropertyName/Schema.json @@ -0,0 +1,25 @@ +{ + "properties": { + "jsx": { + "enum": [ "preserve", "react", "react-native" ] + }, + "withStringDefaultValue": { + "default": "stringDefault" + }, + "withIntegerDefaultValue": { + "default": 118 + }, + "integerType": { + "type": "integer" + }, + "stringType": { + "type": "string" + }, + "objectType": { + "type": "object" + }, + "booleanType": { + "type": "boolean" + } + } +} \ No newline at end of file diff --git a/json/tests/testData/jsonSchema/completion/insertPropertyName/testBooleanType.json b/json/tests/testData/jsonSchema/completion/insertPropertyName/testBooleanType.json new file mode 100644 index 000000000000..fc719517cf97 --- /dev/null +++ b/json/tests/testData/jsonSchema/completion/insertPropertyName/testBooleanType.json @@ -0,0 +1,3 @@ +{ + "booleanTy" +} \ No newline at end of file diff --git a/json/tests/testData/jsonSchema/completion/insertPropertyName/testBooleanTypeComma.json b/json/tests/testData/jsonSchema/completion/insertPropertyName/testBooleanTypeComma.json new file mode 100644 index 000000000000..e065e2af3963 --- /dev/null +++ b/json/tests/testData/jsonSchema/completion/insertPropertyName/testBooleanTypeComma.json @@ -0,0 +1,4 @@ +{ + "booleanTy" + "someOtherProperty": false +} \ No newline at end of file diff --git a/json/tests/testData/jsonSchema/completion/insertPropertyName/testBooleanTypeComma_after.json b/json/tests/testData/jsonSchema/completion/insertPropertyName/testBooleanTypeComma_after.json new file mode 100644 index 000000000000..619a737e0fdb --- /dev/null +++ b/json/tests/testData/jsonSchema/completion/insertPropertyName/testBooleanTypeComma_after.json @@ -0,0 +1,4 @@ +{ + "booleanType": false, + "someOtherProperty": false +} \ No newline at end of file diff --git a/json/tests/testData/jsonSchema/completion/insertPropertyName/testBooleanType_after.json b/json/tests/testData/jsonSchema/completion/insertPropertyName/testBooleanType_after.json new file mode 100644 index 000000000000..fa272c99a836 --- /dev/null +++ b/json/tests/testData/jsonSchema/completion/insertPropertyName/testBooleanType_after.json @@ -0,0 +1,3 @@ +{ + "booleanType": false +} \ No newline at end of file diff --git a/json/tests/testData/jsonSchema/completion/insertPropertyName/testIntegerType.json b/json/tests/testData/jsonSchema/completion/insertPropertyName/testIntegerType.json new file mode 100644 index 000000000000..c71f54604d5e --- /dev/null +++ b/json/tests/testData/jsonSchema/completion/insertPropertyName/testIntegerType.json @@ -0,0 +1,3 @@ +{ + "integerTy" +} \ No newline at end of file diff --git a/json/tests/testData/jsonSchema/completion/insertPropertyName/testIntegerTypeComma.json b/json/tests/testData/jsonSchema/completion/insertPropertyName/testIntegerTypeComma.json new file mode 100644 index 000000000000..40369c25f74f --- /dev/null +++ b/json/tests/testData/jsonSchema/completion/insertPropertyName/testIntegerTypeComma.json @@ -0,0 +1,4 @@ +{ + "integerTy" + "someOtherProperty": 1 +} \ No newline at end of file diff --git a/json/tests/testData/jsonSchema/completion/insertPropertyName/testIntegerTypeComma_after.json b/json/tests/testData/jsonSchema/completion/insertPropertyName/testIntegerTypeComma_after.json new file mode 100644 index 000000000000..500d7a2d8e4c --- /dev/null +++ b/json/tests/testData/jsonSchema/completion/insertPropertyName/testIntegerTypeComma_after.json @@ -0,0 +1,4 @@ +{ + "integerType":, + "someOtherProperty": 1 +} \ No newline at end of file diff --git a/json/tests/testData/jsonSchema/completion/insertPropertyName/testIntegerType_after.json b/json/tests/testData/jsonSchema/completion/insertPropertyName/testIntegerType_after.json new file mode 100644 index 000000000000..fc659a697430 --- /dev/null +++ b/json/tests/testData/jsonSchema/completion/insertPropertyName/testIntegerType_after.json @@ -0,0 +1,3 @@ +{ + "integerType": +} \ No newline at end of file diff --git a/json/tests/testData/jsonSchema/completion/insertPropertyName/testName.json b/json/tests/testData/jsonSchema/completion/insertPropertyName/testName.json new file mode 100644 index 000000000000..a3830a723dc7 --- /dev/null +++ b/json/tests/testData/jsonSchema/completion/insertPropertyName/testName.json @@ -0,0 +1,3 @@ +{ + "js" +} \ No newline at end of file diff --git a/json/tests/testData/jsonSchema/completion/insertPropertyName/testNameWithDefaultIntegerValue.json b/json/tests/testData/jsonSchema/completion/insertPropertyName/testNameWithDefaultIntegerValue.json new file mode 100644 index 000000000000..89f350fc70ef --- /dev/null +++ b/json/tests/testData/jsonSchema/completion/insertPropertyName/testNameWithDefaultIntegerValue.json @@ -0,0 +1,3 @@ +{ + "withIntegerDefault" +} \ No newline at end of file diff --git a/json/tests/testData/jsonSchema/completion/insertPropertyName/testNameWithDefaultIntegerValueNoQuotesComma.json b/json/tests/testData/jsonSchema/completion/insertPropertyName/testNameWithDefaultIntegerValueNoQuotesComma.json new file mode 100644 index 000000000000..bab9defb389e --- /dev/null +++ b/json/tests/testData/jsonSchema/completion/insertPropertyName/testNameWithDefaultIntegerValueNoQuotesComma.json @@ -0,0 +1,4 @@ +{ + withIntegerDefault + "someOtherProperty": 112 +} \ No newline at end of file diff --git a/json/tests/testData/jsonSchema/completion/insertPropertyName/testNameWithDefaultIntegerValueNoQuotesComma_after.json b/json/tests/testData/jsonSchema/completion/insertPropertyName/testNameWithDefaultIntegerValueNoQuotesComma_after.json new file mode 100644 index 000000000000..d4b99358c14f --- /dev/null +++ b/json/tests/testData/jsonSchema/completion/insertPropertyName/testNameWithDefaultIntegerValueNoQuotesComma_after.json @@ -0,0 +1,4 @@ +{ + "withIntegerDefaultValue": 118, + "someOtherProperty": 112 +} \ No newline at end of file diff --git a/json/tests/testData/jsonSchema/completion/insertPropertyName/testNameWithDefaultIntegerValue_after.json b/json/tests/testData/jsonSchema/completion/insertPropertyName/testNameWithDefaultIntegerValue_after.json new file mode 100644 index 000000000000..0e276905bb16 --- /dev/null +++ b/json/tests/testData/jsonSchema/completion/insertPropertyName/testNameWithDefaultIntegerValue_after.json @@ -0,0 +1,3 @@ +{ + "withIntegerDefaultValue": 118 +} \ No newline at end of file diff --git a/json/tests/testData/jsonSchema/completion/insertPropertyName/testNameWithDefaultStringValue.json b/json/tests/testData/jsonSchema/completion/insertPropertyName/testNameWithDefaultStringValue.json new file mode 100644 index 000000000000..cf78bbbd228b --- /dev/null +++ b/json/tests/testData/jsonSchema/completion/insertPropertyName/testNameWithDefaultStringValue.json @@ -0,0 +1,3 @@ +{ + "withStringDefault" +} \ No newline at end of file diff --git a/json/tests/testData/jsonSchema/completion/insertPropertyName/testNameWithDefaultStringValueComma.json b/json/tests/testData/jsonSchema/completion/insertPropertyName/testNameWithDefaultStringValueComma.json new file mode 100644 index 000000000000..e52a254b2a12 --- /dev/null +++ b/json/tests/testData/jsonSchema/completion/insertPropertyName/testNameWithDefaultStringValueComma.json @@ -0,0 +1,4 @@ +{ + "withStringDefault", + "some": 2 +} \ No newline at end of file diff --git a/json/tests/testData/jsonSchema/completion/insertPropertyName/testNameWithDefaultStringValueComma_after.json b/json/tests/testData/jsonSchema/completion/insertPropertyName/testNameWithDefaultStringValueComma_after.json new file mode 100644 index 000000000000..b76abc10ba9f --- /dev/null +++ b/json/tests/testData/jsonSchema/completion/insertPropertyName/testNameWithDefaultStringValueComma_after.json @@ -0,0 +1,4 @@ +{ + "withStringDefaultValue": "stringDefault", + "some": 2 +} \ No newline at end of file diff --git a/json/tests/testData/jsonSchema/completion/insertPropertyName/testNameWithDefaultStringValueNoQuotes.json b/json/tests/testData/jsonSchema/completion/insertPropertyName/testNameWithDefaultStringValueNoQuotes.json new file mode 100644 index 000000000000..02cf872a64ca --- /dev/null +++ b/json/tests/testData/jsonSchema/completion/insertPropertyName/testNameWithDefaultStringValueNoQuotes.json @@ -0,0 +1,3 @@ +{ + withStringDefault +} \ No newline at end of file diff --git a/json/tests/testData/jsonSchema/completion/insertPropertyName/testNameWithDefaultStringValueNoQuotes_after.json b/json/tests/testData/jsonSchema/completion/insertPropertyName/testNameWithDefaultStringValueNoQuotes_after.json new file mode 100644 index 000000000000..3432fd7449dc --- /dev/null +++ b/json/tests/testData/jsonSchema/completion/insertPropertyName/testNameWithDefaultStringValueNoQuotes_after.json @@ -0,0 +1,3 @@ +{ + "withStringDefaultValue": "stringDefault" +} \ No newline at end of file diff --git a/json/tests/testData/jsonSchema/completion/insertPropertyName/testNameWithDefaultStringValue_after.json b/json/tests/testData/jsonSchema/completion/insertPropertyName/testNameWithDefaultStringValue_after.json new file mode 100644 index 000000000000..3432fd7449dc --- /dev/null +++ b/json/tests/testData/jsonSchema/completion/insertPropertyName/testNameWithDefaultStringValue_after.json @@ -0,0 +1,3 @@ +{ + "withStringDefaultValue": "stringDefault" +} \ No newline at end of file diff --git a/json/tests/testData/jsonSchema/completion/insertPropertyName/testName_after.json b/json/tests/testData/jsonSchema/completion/insertPropertyName/testName_after.json new file mode 100644 index 000000000000..5a5b64cf526b --- /dev/null +++ b/json/tests/testData/jsonSchema/completion/insertPropertyName/testName_after.json @@ -0,0 +1,3 @@ +{ + "jsx": "" +} \ No newline at end of file diff --git a/json/tests/testData/jsonSchema/completion/insertPropertyName/testObjectType.json b/json/tests/testData/jsonSchema/completion/insertPropertyName/testObjectType.json new file mode 100644 index 000000000000..edc306cc2428 --- /dev/null +++ b/json/tests/testData/jsonSchema/completion/insertPropertyName/testObjectType.json @@ -0,0 +1,3 @@ +{ + "objectTy" +} \ No newline at end of file diff --git a/json/tests/testData/jsonSchema/completion/insertPropertyName/testObjectType_after.json b/json/tests/testData/jsonSchema/completion/insertPropertyName/testObjectType_after.json new file mode 100644 index 000000000000..22356e03f956 --- /dev/null +++ b/json/tests/testData/jsonSchema/completion/insertPropertyName/testObjectType_after.json @@ -0,0 +1,5 @@ +{ + "objectType": { + + } +} \ No newline at end of file diff --git a/json/tests/testData/jsonSchema/completion/insertPropertyName/testStringType.json b/json/tests/testData/jsonSchema/completion/insertPropertyName/testStringType.json new file mode 100644 index 000000000000..9dd1cf02af96 --- /dev/null +++ b/json/tests/testData/jsonSchema/completion/insertPropertyName/testStringType.json @@ -0,0 +1,3 @@ +{ + "stringTy" +} \ No newline at end of file diff --git a/json/tests/testData/jsonSchema/completion/insertPropertyName/testStringTypeComma.json b/json/tests/testData/jsonSchema/completion/insertPropertyName/testStringTypeComma.json new file mode 100644 index 000000000000..b7d59797825b --- /dev/null +++ b/json/tests/testData/jsonSchema/completion/insertPropertyName/testStringTypeComma.json @@ -0,0 +1,4 @@ +{ + "stringTy" + "someOtherProperty": "123" +} \ No newline at end of file diff --git a/json/tests/testData/jsonSchema/completion/insertPropertyName/testStringTypeComma_after.json b/json/tests/testData/jsonSchema/completion/insertPropertyName/testStringTypeComma_after.json new file mode 100644 index 000000000000..f1d2dcb290de --- /dev/null +++ b/json/tests/testData/jsonSchema/completion/insertPropertyName/testStringTypeComma_after.json @@ -0,0 +1,4 @@ +{ + "stringType": "", + "someOtherProperty": "123" +} \ No newline at end of file diff --git a/json/tests/testData/jsonSchema/completion/insertPropertyName/testStringType_after.json b/json/tests/testData/jsonSchema/completion/insertPropertyName/testStringType_after.json new file mode 100644 index 000000000000..4941857678e8 --- /dev/null +++ b/json/tests/testData/jsonSchema/completion/insertPropertyName/testStringType_after.json @@ -0,0 +1,3 @@ +{ + "stringType": "" +} \ No newline at end of file From dbea349130ffa7933dcf7e527b4cbc43987afa0e Mon Sep 17 00:00:00 2001 From: irengrig Date: Mon, 6 Mar 2017 11:21:12 +0100 Subject: [PATCH 13/33] cleanup --- .../jsonSchema/impl/JsonBySchemaHeavyCompletionTest.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/json/tests/test/com/jetbrains/jsonSchema/impl/JsonBySchemaHeavyCompletionTest.java b/json/tests/test/com/jetbrains/jsonSchema/impl/JsonBySchemaHeavyCompletionTest.java index 65eecad8a1ec..9531abab953f 100644 --- a/json/tests/test/com/jetbrains/jsonSchema/impl/JsonBySchemaHeavyCompletionTest.java +++ b/json/tests/test/com/jetbrains/jsonSchema/impl/JsonBySchemaHeavyCompletionTest.java @@ -119,8 +119,4 @@ public class JsonBySchemaHeavyCompletionTest extends JsonSchemaHeavyAbstractTest } }); } - - public void testInsertStringProperty() throws Exception { - - } } From 210d85078525a1e98819d08bb2ba229f41eaa566 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Mon, 6 Mar 2017 18:40:12 +0700 Subject: [PATCH 14/33] ChainCallInplaceIntroducer: fixed variable creation --- .../ChainCallInplaceIntroducer.java | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/java/java-impl/src/com/intellij/refactoring/introduceVariable/ChainCallInplaceIntroducer.java b/java/java-impl/src/com/intellij/refactoring/introduceVariable/ChainCallInplaceIntroducer.java index de01d378a48f..97a6523b8ee1 100644 --- a/java/java-impl/src/com/intellij/refactoring/introduceVariable/ChainCallInplaceIntroducer.java +++ b/java/java-impl/src/com/intellij/refactoring/introduceVariable/ChainCallInplaceIntroducer.java @@ -20,12 +20,16 @@ import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Computable; import com.intellij.psi.*; +import com.intellij.psi.search.searches.ReferencesSearch; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.refactoring.chainCall.ChainCallExtractor; import com.intellij.refactoring.ui.TypeSelectorManagerImpl; import com.intellij.util.ArrayUtil; +import one.util.streamex.StreamEx; import org.jetbrains.annotations.Nullable; +import java.util.Objects; + /** * @author Tagir Valeev */ @@ -59,23 +63,28 @@ public class ChainCallInplaceIntroducer extends JavaVariableInplaceIntroducer { return myCall; } - @Nullable @Override - protected PsiVariable introduceVariable() { - PsiVariable variable = super.introduceVariable(); + protected PsiVariable createFieldToStartTemplateOn(String[] names, PsiType psiType) { + PsiVariable variable = introduceVariable(); if (variable instanceof PsiLocalVariable) { PsiLambdaExpression lambda = ApplicationManager.getApplication().runWriteAction( (Computable)() -> ChainCallExtractor.extractMappingStep(myProject, (PsiLocalVariable)variable)); if (lambda != null) { - PsiParameter parameter = ArrayUtil.getFirstElement(lambda.getParameterList().getParameters()); + PsiParameter parameter = Objects.requireNonNull(ArrayUtil.getFirstElement(lambda.getParameterList().getParameters())); myParameter = parameter; myCall = PsiTreeUtil.getParentOfType(lambda, PsiMethodCallExpression.class); - myOccurrences = PsiExpression.EMPTY_ARRAY; - myOccurrenceMarkers = null; myExprMarker = null; myExpr = null; + myOccurrences = StreamEx.of(ReferencesSearch.search(parameter).findAll()).map(PsiReference::getElement).select(PsiExpression.class) + .toArray(PsiExpression[]::new); + myOccurrenceMarkers = null; + final PsiIdentifier identifier = variable.getNameIdentifier(); + if (identifier != null) { + myEditor.getCaretModel().moveToOffset(identifier.getTextOffset()); + } setAdvertisementText(null); PsiDocumentManager.getInstance(myProject).doPostponedOperationsAndUnblockDocument(myEditor.getDocument()); + initOccurrencesMarkers(); return parameter; } else if (!variable.isValid()) { From db956da977a8d82f33cb0f77bd9fe138038e0394 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Mon, 6 Mar 2017 18:46:49 +0700 Subject: [PATCH 15/33] JavaVariableInplaceIntroducer: cleanup after previous refactoring --- .../introduceVariable/JavaVariableInplaceIntroducer.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/java/java-impl/src/com/intellij/refactoring/introduceVariable/JavaVariableInplaceIntroducer.java b/java/java-impl/src/com/intellij/refactoring/introduceVariable/JavaVariableInplaceIntroducer.java index 270cc341ea30..d5b68adc402b 100644 --- a/java/java-impl/src/com/intellij/refactoring/introduceVariable/JavaVariableInplaceIntroducer.java +++ b/java/java-impl/src/com/intellij/refactoring/introduceVariable/JavaVariableInplaceIntroducer.java @@ -398,8 +398,6 @@ public class JavaVariableInplaceIntroducer extends AbstractJavaInplaceIntroducer protected PsiVariable createFieldToStartTemplateOn(String[] names, PsiType psiType) { PsiVariable variable = introduceVariable(); - if (variable == null) return null; - final PsiVariable restoredVar = getVariable(); if (restoredVar != null) { variable = restoredVar; @@ -429,7 +427,6 @@ public class JavaVariableInplaceIntroducer extends AbstractJavaInplaceIntroducer return variable; } - @Nullable protected PsiVariable introduceVariable() { PsiVariable variable = IntroduceVariableBase .introduce(myProject, myExpr, myEditor, myChosenAnchor.getElement(), getOccurrences(), mySettings); From 23c5b28fbc6dced7b6597f3bd861a438ce8fc1b6 Mon Sep 17 00:00:00 2001 From: Ivan Chirkov Date: Mon, 6 Mar 2017 12:44:32 +0100 Subject: [PATCH 16/33] Google Protocol Buffers 0.5.9 is not compatible anymore. IDEA-139981, IDEA-168095. --- platform/platform-resources/src/brokenPlugins.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/platform/platform-resources/src/brokenPlugins.txt b/platform/platform-resources/src/brokenPlugins.txt index d075f05404aa..c2a003f5f079 100644 --- a/platform/platform-resources/src/brokenPlugins.txt +++ b/platform/platform-resources/src/brokenPlugins.txt @@ -68,3 +68,4 @@ com.jetbrains.performancePlugin 1.1 1.3 1.3.1 1.3.2 org.vuejs 1.1.0 1.0.1 1.0.0 Vue.js 1.1.0 1.0.1 1.0.0 com.intellij.resharper.unity 1.5.0.0 1.5.0.1 +com.nmatveev.idea-plugin-protobuf 0.5.9 From 6a624f9e2f6e5a858bab1c3581193e91415fc589 Mon Sep 17 00:00:00 2001 From: Alexandr Evstigneev Date: Mon, 6 Mar 2017 14:57:28 +0300 Subject: [PATCH 17/33] added NotNull annotations to getDefaultProvider methods implementations --- .../highlighting/HighlightUsagesDescriptionLocation.java | 1 + .../com/intellij/usageView/UsageViewTypeLocation.java | 9 +++++---- .../intellij/ide/util/DeleteNameDescriptionLocation.java | 1 + .../intellij/ide/util/DeleteTypeDescriptionLocation.java | 1 + .../util/NonCodeSearchDescriptionLocation.java | 2 ++ .../refactoring/util/RefactoringDescriptionLocation.java | 2 ++ .../intellij/usageView/UsageViewLongNameLocation.java | 3 ++- .../intellij/usageView/UsageViewNodeTextLocation.java | 1 + .../intellij/usageView/UsageViewShortNameLocation.java | 1 + 9 files changed, 16 insertions(+), 5 deletions(-) diff --git a/platform/core-impl/src/com/intellij/codeInsight/highlighting/HighlightUsagesDescriptionLocation.java b/platform/core-impl/src/com/intellij/codeInsight/highlighting/HighlightUsagesDescriptionLocation.java index 10b41243d3cd..a9671defef2b 100644 --- a/platform/core-impl/src/com/intellij/codeInsight/highlighting/HighlightUsagesDescriptionLocation.java +++ b/platform/core-impl/src/com/intellij/codeInsight/highlighting/HighlightUsagesDescriptionLocation.java @@ -29,6 +29,7 @@ public class HighlightUsagesDescriptionLocation extends ElementDescriptionLocati private HighlightUsagesDescriptionLocation() { } + @NotNull @Override public ElementDescriptionProvider getDefaultProvider() { return new ElementDescriptionProvider() { diff --git a/platform/indexing-impl/src/com/intellij/usageView/UsageViewTypeLocation.java b/platform/indexing-impl/src/com/intellij/usageView/UsageViewTypeLocation.java index 8ded650b95e1..e1829d750380 100644 --- a/platform/indexing-impl/src/com/intellij/usageView/UsageViewTypeLocation.java +++ b/platform/indexing-impl/src/com/intellij/usageView/UsageViewTypeLocation.java @@ -17,15 +17,15 @@ package com.intellij.usageView; import com.intellij.ide.TypePresentationService; -import com.intellij.psi.*; -import com.intellij.psi.meta.PsiMetaData; -import com.intellij.psi.meta.PsiMetaOwner; -import com.intellij.psi.meta.PsiPresentableMetaData; import com.intellij.lang.LangBundle; import com.intellij.lang.Language; import com.intellij.lang.findUsages.FindUsagesProvider; import com.intellij.lang.findUsages.LanguageFindUsages; import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.*; +import com.intellij.psi.meta.PsiMetaData; +import com.intellij.psi.meta.PsiMetaOwner; +import com.intellij.psi.meta.PsiPresentableMetaData; import org.jetbrains.annotations.NotNull; /** @@ -37,6 +37,7 @@ public class UsageViewTypeLocation extends ElementDescriptionLocation { public static final UsageViewTypeLocation INSTANCE = new UsageViewTypeLocation(); + @NotNull @Override public ElementDescriptionProvider getDefaultProvider() { return DEFAULT_PROVIDER; diff --git a/platform/lang-impl/src/com/intellij/ide/util/DeleteNameDescriptionLocation.java b/platform/lang-impl/src/com/intellij/ide/util/DeleteNameDescriptionLocation.java index 16e584b4820e..cd06ba0f465f 100644 --- a/platform/lang-impl/src/com/intellij/ide/util/DeleteNameDescriptionLocation.java +++ b/platform/lang-impl/src/com/intellij/ide/util/DeleteNameDescriptionLocation.java @@ -32,6 +32,7 @@ public class DeleteNameDescriptionLocation extends ElementDescriptionLocation { public static DeleteNameDescriptionLocation INSTANCE = new DeleteNameDescriptionLocation(); private static final ElementDescriptionProvider ourDefaultProvider = new DefaultProvider(); + @NotNull @Override public ElementDescriptionProvider getDefaultProvider() { return ourDefaultProvider; diff --git a/platform/lang-impl/src/com/intellij/ide/util/DeleteTypeDescriptionLocation.java b/platform/lang-impl/src/com/intellij/ide/util/DeleteTypeDescriptionLocation.java index 82c7354622ba..679bbb70e85a 100644 --- a/platform/lang-impl/src/com/intellij/ide/util/DeleteTypeDescriptionLocation.java +++ b/platform/lang-impl/src/com/intellij/ide/util/DeleteTypeDescriptionLocation.java @@ -37,6 +37,7 @@ public class DeleteTypeDescriptionLocation extends ElementDescriptionLocation { private static final ElementDescriptionProvider ourDefaultProvider = new DefaultProvider(); + @NotNull @Override public ElementDescriptionProvider getDefaultProvider() { return ourDefaultProvider; diff --git a/platform/lang-impl/src/com/intellij/refactoring/util/NonCodeSearchDescriptionLocation.java b/platform/lang-impl/src/com/intellij/refactoring/util/NonCodeSearchDescriptionLocation.java index f3238e4b67b7..9d9ea7c219e8 100644 --- a/platform/lang-impl/src/com/intellij/refactoring/util/NonCodeSearchDescriptionLocation.java +++ b/platform/lang-impl/src/com/intellij/refactoring/util/NonCodeSearchDescriptionLocation.java @@ -18,6 +18,7 @@ package com.intellij.refactoring.util; import com.intellij.psi.ElementDescriptionLocation; import com.intellij.psi.ElementDescriptionProvider; +import org.jetbrains.annotations.NotNull; /** * @author yole @@ -32,6 +33,7 @@ public class NonCodeSearchDescriptionLocation extends ElementDescriptionLocation public static final NonCodeSearchDescriptionLocation NON_JAVA = new NonCodeSearchDescriptionLocation(true); public static final NonCodeSearchDescriptionLocation STRINGS_AND_COMMENTS = new NonCodeSearchDescriptionLocation(false); + @NotNull @Override public ElementDescriptionProvider getDefaultProvider() { return DefaultNonCodeSearchElementDescriptionProvider.INSTANCE; diff --git a/platform/lang-impl/src/com/intellij/refactoring/util/RefactoringDescriptionLocation.java b/platform/lang-impl/src/com/intellij/refactoring/util/RefactoringDescriptionLocation.java index ed2ae876c262..71bc763ce30f 100644 --- a/platform/lang-impl/src/com/intellij/refactoring/util/RefactoringDescriptionLocation.java +++ b/platform/lang-impl/src/com/intellij/refactoring/util/RefactoringDescriptionLocation.java @@ -18,6 +18,7 @@ package com.intellij.refactoring.util; import com.intellij.psi.ElementDescriptionLocation; import com.intellij.psi.ElementDescriptionProvider; +import org.jetbrains.annotations.NotNull; /** * @author yole @@ -36,6 +37,7 @@ public class RefactoringDescriptionLocation extends ElementDescriptionLocation { return myWithParent; } + @NotNull @Override public ElementDescriptionProvider getDefaultProvider() { return DefaultRefactoringElementDescriptionProvider.INSTANCE; diff --git a/platform/lang-impl/src/com/intellij/usageView/UsageViewLongNameLocation.java b/platform/lang-impl/src/com/intellij/usageView/UsageViewLongNameLocation.java index aa699ae29c9a..5d53dab6c3b8 100644 --- a/platform/lang-impl/src/com/intellij/usageView/UsageViewLongNameLocation.java +++ b/platform/lang-impl/src/com/intellij/usageView/UsageViewLongNameLocation.java @@ -18,8 +18,8 @@ package com.intellij.usageView; import com.intellij.psi.ElementDescriptionLocation; import com.intellij.psi.ElementDescriptionProvider; -import com.intellij.psi.PsiElement; import com.intellij.psi.PsiDirectory; +import com.intellij.psi.PsiElement; import com.intellij.psi.impl.file.PsiDirectoryFactory; import org.jetbrains.annotations.NotNull; @@ -32,6 +32,7 @@ public class UsageViewLongNameLocation extends ElementDescriptionLocation { public static final UsageViewLongNameLocation INSTANCE = new UsageViewLongNameLocation(); + @NotNull @Override public ElementDescriptionProvider getDefaultProvider() { return DEFAULT_PROVIDER; diff --git a/platform/lang-impl/src/com/intellij/usageView/UsageViewNodeTextLocation.java b/platform/lang-impl/src/com/intellij/usageView/UsageViewNodeTextLocation.java index 94692f9c2e27..a98a4efac78e 100644 --- a/platform/lang-impl/src/com/intellij/usageView/UsageViewNodeTextLocation.java +++ b/platform/lang-impl/src/com/intellij/usageView/UsageViewNodeTextLocation.java @@ -37,6 +37,7 @@ public class UsageViewNodeTextLocation extends ElementDescriptionLocation { public static final UsageViewNodeTextLocation INSTANCE = new UsageViewNodeTextLocation(); + @NotNull @Override public ElementDescriptionProvider getDefaultProvider() { return DEFAULT_PROVIDER; diff --git a/platform/lang-impl/src/com/intellij/usageView/UsageViewShortNameLocation.java b/platform/lang-impl/src/com/intellij/usageView/UsageViewShortNameLocation.java index ad1e3edd2d18..2f635fd2db8c 100644 --- a/platform/lang-impl/src/com/intellij/usageView/UsageViewShortNameLocation.java +++ b/platform/lang-impl/src/com/intellij/usageView/UsageViewShortNameLocation.java @@ -34,6 +34,7 @@ public class UsageViewShortNameLocation extends ElementDescriptionLocation { public static final UsageViewShortNameLocation INSTANCE = new UsageViewShortNameLocation(); + @NotNull @Override public ElementDescriptionProvider getDefaultProvider() { return DEFAULT_PROVIDER; From 46fd99f98747576647096a70fc6f5dd19a345371 Mon Sep 17 00:00:00 2001 From: Ekaterina Tuzova Date: Mon, 6 Mar 2017 15:05:37 +0300 Subject: [PATCH 18/33] PY-10649 Pyramid: Show yellow notification about not run setup.py develop for opened project --- .../setupPy/SetupTaskChooserAction.java | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/python/src/com/jetbrains/python/packaging/setupPy/SetupTaskChooserAction.java b/python/src/com/jetbrains/python/packaging/setupPy/SetupTaskChooserAction.java index 74bb08d30cc3..8c0743100cce 100644 --- a/python/src/com/jetbrains/python/packaging/setupPy/SetupTaskChooserAction.java +++ b/python/src/com/jetbrains/python/packaging/setupPy/SetupTaskChooserAction.java @@ -78,18 +78,23 @@ public class SetupTaskChooserAction extends AnAction { } public static void runSetupTask(String taskName, Module module) { - final PyFile setupPy = PyPackageUtil.findSetupPy(module); - try { - final List options = SetupTaskIntrospector.getSetupTaskOptions(module, taskName); - List parameters = new ArrayList<>(); - parameters.add(taskName); - if (options != null) { - SetupTaskDialog dialog = new SetupTaskDialog(module.getProject(), taskName, options); - if (!dialog.showAndGet()) { - return; - } - parameters.addAll(dialog.getCommandLine()); + final List options = SetupTaskIntrospector.getSetupTaskOptions(module, taskName); + List parameters = new ArrayList<>(); + parameters.add(taskName); + if (options != null) { + SetupTaskDialog dialog = new SetupTaskDialog(module.getProject(), taskName, options); + if (!dialog.showAndGet()) { + return; } + parameters.addAll(dialog.getCommandLine()); + } + runSetupTask(taskName, module, parameters); + } + + public static void runSetupTask(String taskName, Module module, List parameters) { + try { + final PyFile setupPy = PyPackageUtil.findSetupPy(module); + if (setupPy == null) return; final PythonTask task = new PythonTask(module, taskName); final VirtualFile virtualFile = setupPy.getVirtualFile(); task.setRunnerScript(virtualFile.getPath()); From 41aaebef6908b6d77c5c50534fd7cc54c6876e1a Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Mon, 13 Feb 2017 17:05:46 +0300 Subject: [PATCH 19/33] PY-20057 Initial support of typing.Type (types of direct classes) --- .../typing/PyTypingTypeProvider.java | 24 +++++++++++++ .../documentation/PyTypeModelBuilder.java | 25 +++++++++++++ .../com/jetbrains/python/PyTypingTest.java | 36 +++++++++++++++++++ 3 files changed, 85 insertions(+) diff --git a/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java b/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java index 6ee353183b47..d1de08405a58 100644 --- a/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java +++ b/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java @@ -62,6 +62,7 @@ public class PyTypingTypeProvider extends PyTypeProviderBase { public static final String COROUTINE = "typing.Coroutine"; public static final String NAMEDTUPLE = "typing.NamedTuple"; public static final String GENERIC = "typing.Generic"; + public static final String TYPE = "typing.Type"; public static final Pattern TYPE_COMMENT_PATTERN = Pattern.compile("# *type: *(.*)"); @@ -482,6 +483,10 @@ public class PyTypingTypeProvider extends PyTypeProviderBase { if (genericType != null) { return Ref.create(genericType); } + final PyType classObjType = getClassObjectType(resolved, context); + if (classObjType != null) { + return Ref.create(classObjType); + } final Ref classType = getClassType(resolved, context.getTypeContext()); if (classType != null) { return classType; @@ -501,6 +506,25 @@ public class PyTypingTypeProvider extends PyTypeProviderBase { } } + @Nullable + private static PyType getClassObjectType(@Nullable PsiElement resolved, @NotNull Context context) { + if (resolved instanceof PySubscriptionExpression) { + final PySubscriptionExpression subsExpr = (PySubscriptionExpression)resolved; + final PyExpression operand = subsExpr.getOperand(); + final Collection operandNames = resolveToQualifiedNames(operand, context.getTypeContext()); + if (operandNames.contains(TYPE)) { + final PyExpression indexExpr = subsExpr.getIndexExpression(); + if (indexExpr != null) { + final PyType type = Ref.deref(getType(indexExpr, context)); + if (type instanceof PyClassType && !((PyClassType)type).isDefinition()) { + return new PyClassTypeImpl(((PyClassType)type).getPyClass(), false); + } + } + } + } + return null; + } + @Nullable private static Ref getAnyType(@NotNull PsiElement element) { final PyQualifiedNameOwner qualifiedNameOwner = as(element, PyQualifiedNameOwner.class); diff --git a/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java b/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java index 7fc8cf0a6a6b..2f39404ecfa8 100644 --- a/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java +++ b/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java @@ -186,6 +186,19 @@ public class PyTypeModelBuilder { visitor.param(this); } } + + static class ClassObjectType extends TypeModel { + private final TypeModel classType; + + public ClassObjectType(TypeModel classType) { + this.classType = classType; + } + + @Override + void accept(@NotNull TypeVisitor visitor) { + visitor.classObject(this); + } + } /** * Builds tree-like type model for PyType @@ -251,6 +264,9 @@ public class PyTypeModelBuilder { else if (type instanceof PyCallableType && !(type instanceof PyClassLikeType)) { result = build((PyCallableType)type); } + else if (type instanceof PyClassType && !((PyClassType)type).isDefinition()) { + result = new ClassObjectType(build(type, allowUnions)); + } if (result == null) { result = NamedType.nameOrAny(type); } @@ -309,6 +325,8 @@ public class PyTypeModelBuilder { void optional(OptionalType type); void tuple(TupleType type); + + void classObject(ClassObjectType type); } private static class TypeToStringVisitor extends TypeNameVisitor { @@ -503,5 +521,12 @@ public class PyTypeModelBuilder { } add("]"); } + + @Override + public void classObject(ClassObjectType type) { + add("Type["); + type.classType.accept(this); + add("]"); + } } } diff --git a/python/testSrc/com/jetbrains/python/PyTypingTest.java b/python/testSrc/com/jetbrains/python/PyTypingTest.java index 8a7e76ad6ffc..4b64e1e9e884 100644 --- a/python/testSrc/com/jetbrains/python/PyTypingTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypingTest.java @@ -783,6 +783,42 @@ public class PyTypingTest extends PyTestCase { "expr = C(0).get()\n"); } + // PY-20057 + public void testClassObjectType() { + doTest("Type[MyClass]", + "from typing import Type\n" + + "\n" + + "class MyClass:\n" + + " pass\n" + + "\n" + + "def f(x: Type[MyClass]): \n" + + " expr = x"); + } + + // PY-20057 + public void testConstrainedClassObjectTypeOfParam() { + doTest("Type[T]", + "from typing import Type, TypeVar\n" + + "\n" + + "T = TypeVar('T', bound=int)\n" + + "\n" + + "def f(x: Type[T]):\n" + + " expr = x"); + } + + // PY-20057 + public void testConstrainedClassObjectTypeOfResultValue() { + doTest("int", + "from typing import Type, TypeVar\n" + + "\n" + + "T = TypeVar('T', bound=int)\n" + + "\n" + + "def f(x: Type[T]) -> T:\n" + + " return x()\n" + + "\n" + + "expr = f(int)"); + } + private void doTestNoInjectedText(@NotNull String text) { myFixture.configureByText(PythonFileType.INSTANCE, text); final InjectedLanguageManager languageManager = InjectedLanguageManager.getInstance(myFixture.getProject()); From 46c229030ea39a41f150c218193e8e21e94ca5f4 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Tue, 14 Feb 2017 14:37:31 +0300 Subject: [PATCH 20/33] PY-20057 Pull up isDefinition() to PyGenericType via new PyInstantiableType interface Additionally, I've changed rendering of type vars so that it's done in PyTypeModelBuilder rather than in PyGenericType itself in order to properly wrap type vars in "Type[]" before displaying them. Moreover, it's fixed a couple of cases uncovered earlier: formatting a type var constrained with a single upper bound and formatting a type var constrained with some compound type, e.g. generic. --- .../python/psi/types/PyClassLikeType.java | 5 +- .../python/psi/types/PyInstantiableType.java | 27 +++++++++ .../jetbrains/python/psi/types/PyType.java | 1 + .../typing/PyTypingTypeProvider.java | 9 ++- .../documentation/PyTypeModelBuilder.java | 59 +++++++++++++++++-- .../python/psi/types/PyGenericType.java | 38 +++++++----- python/testData/quickdoc/TypeVars.html | 1 + python/testData/quickdoc/TypeVars.py | 10 ++++ .../com/jetbrains/python/PyQuickDocTest.java | 5 ++ .../com/jetbrains/python/PyTypingTest.java | 2 +- 10 files changed, 131 insertions(+), 26 deletions(-) create mode 100644 python/psi-api/src/com/jetbrains/python/psi/types/PyInstantiableType.java create mode 100644 python/testData/quickdoc/TypeVars.html create mode 100644 python/testData/quickdoc/TypeVars.py diff --git a/python/psi-api/src/com/jetbrains/python/psi/types/PyClassLikeType.java b/python/psi-api/src/com/jetbrains/python/psi/types/PyClassLikeType.java index f7d40633fd48..f962f3cd6548 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/types/PyClassLikeType.java +++ b/python/psi-api/src/com/jetbrains/python/psi/types/PyClassLikeType.java @@ -31,10 +31,7 @@ import java.util.Set; /** * @author vlan */ -public interface PyClassLikeType extends PyCallableType, PyWithAncestors { - boolean isDefinition(); - - PyClassLikeType toInstance(); +public interface PyClassLikeType extends PyCallableType, PyWithAncestors, PyInstantiableType { @Nullable String getClassQName(); diff --git a/python/psi-api/src/com/jetbrains/python/psi/types/PyInstantiableType.java b/python/psi-api/src/com/jetbrains/python/psi/types/PyInstantiableType.java new file mode 100644 index 000000000000..18ea6331140d --- /dev/null +++ b/python/psi-api/src/com/jetbrains/python/psi/types/PyInstantiableType.java @@ -0,0 +1,27 @@ +/* + * Copyright 2000-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.python.psi.types; + +/** + * A type that can describe either particular instantiated instance of a class or a class itself. + * + * @author Mikhail Golubev + */ +public interface PyInstantiableType> extends PyType { + boolean isDefinition(); + + T toInstance(); +} diff --git a/python/psi-api/src/com/jetbrains/python/psi/types/PyType.java b/python/psi-api/src/com/jetbrains/python/psi/types/PyType.java index a458f9488951..4a668e23759c 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/types/PyType.java +++ b/python/psi-api/src/com/jetbrains/python/psi/types/PyType.java @@ -66,6 +66,7 @@ public interface PyType { Key> CTX_NAMES = new Key<>("Completion variants names"); /** + * TODO rename it to something like getPresentableName(), because it's not clear that these names are actually visible to end-user * @return name of the type */ @Nullable diff --git a/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java b/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java index d1de08405a58..d392e60a0064 100644 --- a/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java +++ b/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java @@ -516,8 +516,13 @@ public class PyTypingTypeProvider extends PyTypeProviderBase { final PyExpression indexExpr = subsExpr.getIndexExpression(); if (indexExpr != null) { final PyType type = Ref.deref(getType(indexExpr, context)); - if (type instanceof PyClassType && !((PyClassType)type).isDefinition()) { - return new PyClassTypeImpl(((PyClassType)type).getPyClass(), false); + final PyClassType classType = as(type, PyClassType.class); + if (classType != null && !classType.isDefinition()) { + return new PyClassTypeImpl(classType.getPyClass(), true); + } + final PyGenericType typeVar = as(type, PyGenericType.class); + if (typeVar != null && !typeVar.isDefinition()) { + return new PyGenericType(typeVar.getName(), typeVar.getBound(), true); } } } diff --git a/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java b/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java index 2f39404ecfa8..b243d1423ff8 100644 --- a/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java +++ b/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java @@ -199,6 +199,21 @@ public class PyTypeModelBuilder { visitor.classObject(this); } } + + static class GenericType extends TypeModel { + private final String name; + private final List bounds; + + public GenericType(@Nullable String name, @NotNull List bounds) { + this.name = name; + this.bounds = bounds; + } + + @Override + void accept(@NotNull TypeVisitor visitor) { + visitor.genericType(this); + } + } /** * Builds tree-like type model for PyType @@ -262,10 +277,25 @@ public class PyTypeModelBuilder { } } else if (type instanceof PyCallableType && !(type instanceof PyClassLikeType)) { - result = build((PyCallableType)type); + result = buildCallable((PyCallableType)type); } - else if (type instanceof PyClassType && !((PyClassType)type).isDefinition()) { - result = new ClassObjectType(build(type, allowUnions)); + else if (type instanceof PyInstantiableType && ((PyInstantiableType)type).isDefinition()) { + result = new ClassObjectType(build(((PyInstantiableType)type).toInstance(), allowUnions)); + } + else if (type instanceof PyGenericType) { + //assert !((PyGenericType)type).isDefinition() + final PyType bound = ((PyGenericType)type).getBound(); + final List boundNames; + if (bound instanceof PyUnionType) { + boundNames = ContainerUtil.map(((PyUnionType)bound).getMembers(), t -> build(t, allowUnions)); + } + else if (bound != null) { + boundNames = Collections.singletonList(build(bound, allowUnions)); + } + else { + boundNames = Collections.emptyList(); + } + result = new GenericType(type.getName(), boundNames); } if (result == null) { result = NamedType.nameOrAny(type); @@ -295,7 +325,7 @@ public class PyTypeModelBuilder { return null; } - private TypeModel build(@NotNull PyCallableType type) { + private TypeModel buildCallable(@NotNull PyCallableType type) { List parameterModels = null; final List parameters = type.getParameters(myContext); if (parameters != null) { @@ -327,6 +357,8 @@ public class PyTypeModelBuilder { void tuple(TupleType type); void classObject(ClassObjectType type); + + void genericType(GenericType type); } private static class TypeToStringVisitor extends TypeNameVisitor { @@ -528,5 +560,24 @@ public class PyTypeModelBuilder { type.classType.accept(this); add("]"); } + + @Override + public void genericType(GenericType type) { + add("TypeVar('"); + add(type.name); + add("'"); + if (!type.bounds.isEmpty()) { + add(", "); + boolean first = true; + for (TypeModel bound : type.bounds) { + if (!first) { + add(", "); + } + bound.accept(this); + first = false; + } + } + add(")"); + } } } diff --git a/python/src/com/jetbrains/python/psi/types/PyGenericType.java b/python/src/com/jetbrains/python/psi/types/PyGenericType.java index b3f47fb9a4ae..9d419bc33f93 100644 --- a/python/src/com/jetbrains/python/psi/types/PyGenericType.java +++ b/python/src/com/jetbrains/python/psi/types/PyGenericType.java @@ -15,11 +15,9 @@ */ package com.jetbrains.python.psi.types; -import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiElement; -import com.intellij.util.Function; +import com.intellij.util.ArrayUtil; import com.intellij.util.ProcessingContext; -import com.jetbrains.python.PyNames; import com.jetbrains.python.psi.AccessDirection; import com.jetbrains.python.psi.PyExpression; import com.jetbrains.python.psi.resolve.PyResolveContext; @@ -28,17 +26,24 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.List; +import java.util.Objects; /** * @author vlan */ -public class PyGenericType implements PyType { +public class PyGenericType implements PyType, PyInstantiableType { @NotNull private final String myName; - @Nullable private PyType myBound; + @Nullable private final PyType myBound; + private boolean myIsDefinition = false; public PyGenericType(@NotNull String name, @Nullable PyType bound) { + this(name, bound, false); + } + + public PyGenericType(@NotNull String name, @Nullable PyType bound, boolean isDefinition) { myName = name; myBound = bound; + myIsDefinition = isDefinition; } @Nullable @@ -52,20 +57,13 @@ public class PyGenericType implements PyType { @Override public Object[] getCompletionVariants(String completionPrefix, PsiElement location, ProcessingContext context) { - return new Object[0]; + return ArrayUtil.EMPTY_OBJECT_ARRAY; } @NotNull @Override public String getName() { - if (myBound instanceof PyUnionType) { - final PyUnionType bounds = (PyUnionType)myBound; - final String boundsString = StringUtil.join(bounds.getMembers(), type -> type != null ? type.getName() : PyNames.UNKNOWN_TYPE, ", "); - return "TypeVar('" + myName + "', " + boundsString + ")"; - } - else { - return "TypeVar('" + myName + "')"; - } + return myName; } @Override @@ -86,7 +84,7 @@ public class PyGenericType implements PyType { return false; } final PyGenericType type = (PyGenericType)o; - return myName.equals(type.myName); + return myName.equals(type.myName) && Objects.equals(myBound, type.getBound()) && myIsDefinition == type.isDefinition(); } @Override @@ -104,4 +102,14 @@ public class PyGenericType implements PyType { public PyType getBound() { return myBound; } + + @Override + public boolean isDefinition() { + return myIsDefinition; + } + + @Override + public PyGenericType toInstance() { + return myIsDefinition ? new PyGenericType(myName, myBound, false) : this; + } } diff --git a/python/testData/quickdoc/TypeVars.html b/python/testData/quickdoc/TypeVars.html new file mode 100644 index 000000000000..344bc1b3b4a7 --- /dev/null +++ b/python/testData/quickdoc/TypeVars.html @@ -0,0 +1 @@ +def f(p1: T1, p2: T2, p3: T3)
Inferred type: (p1: TypeVar('T1', int), p2: TypeVar('T2', int, str), p3: TypeVar('T3', List[bool])) -> None
\ No newline at end of file diff --git a/python/testData/quickdoc/TypeVars.py b/python/testData/quickdoc/TypeVars.py new file mode 100644 index 000000000000..5bbb833557a2 --- /dev/null +++ b/python/testData/quickdoc/TypeVars.py @@ -0,0 +1,10 @@ +from typing import TypeVar, List + +T1 = TypeVar('T1', int) +T2 = TypeVar('T2', int, str) +T3 = TypeVar('T3', List[bool]) + +def f(p1: T1, p2: T2, p3: T3): + pass + +f() \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PyQuickDocTest.java b/python/testSrc/com/jetbrains/python/PyQuickDocTest.java index 7746b33e004b..d635a492df46 100644 --- a/python/testSrc/com/jetbrains/python/PyQuickDocTest.java +++ b/python/testSrc/com/jetbrains/python/PyQuickDocTest.java @@ -277,4 +277,9 @@ public class PyQuickDocTest extends LightMarkedTestCase { public void testUnknownTuple() { runWithLanguageLevel(LanguageLevel.PYTHON35, this::checkHTMLOnly); } + + public void testTypeVars() { + myFixture.copyDirectoryToProject("typing", ""); + runWithLanguageLevel(LanguageLevel.PYTHON35, this::checkHTMLOnly); + } } diff --git a/python/testSrc/com/jetbrains/python/PyTypingTest.java b/python/testSrc/com/jetbrains/python/PyTypingTest.java index 4b64e1e9e884..21e08c7948c4 100644 --- a/python/testSrc/com/jetbrains/python/PyTypingTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypingTest.java @@ -797,7 +797,7 @@ public class PyTypingTest extends PyTestCase { // PY-20057 public void testConstrainedClassObjectTypeOfParam() { - doTest("Type[T]", + doTest("Type[TypeVar('T', int)]", "from typing import Type, TypeVar\n" + "\n" + "T = TypeVar('T', bound=int)\n" + From b6dbc5b98978be811a801e87e70b54454c7ca9dd Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Tue, 14 Feb 2017 17:53:40 +0300 Subject: [PATCH 21/33] PY-20057 Unify type vars constrained by class types and instance types so that annotations like "def f(x: Type[T]) -> T" (and vice-versa) work as expected. Additionally, I've added a new method toClass() in PyInstantiableType interface that is supposed to be the opposite to existing toInstance(). --- .../python/psi/impl/PyJavaClassType.java | 7 +++++++ .../python/psi/types/PyInstantiableType.java | 6 ++++++ .../src/com/jetbrains/python/PyCustomType.java | 11 ++++++++++- .../codeInsight/stdlib/PyNamedTupleType.java | 7 +++++++ .../python/psi/types/PyClassTypeImpl.java | 7 +++++++ .../python/psi/types/PyCollectionTypeImpl.java | 7 +++++++ .../python/psi/types/PyGenericType.java | 7 +++++++ .../python/psi/types/PyTypeChecker.java | 17 ++++++++++++++++- .../com/jetbrains/python/PyTypingTest.java | 17 +++++++++++++++-- 9 files changed, 82 insertions(+), 4 deletions(-) diff --git a/python/pluginJava/com/jetbrains/python/psi/impl/PyJavaClassType.java b/python/pluginJava/com/jetbrains/python/psi/impl/PyJavaClassType.java index a06208a41e4d..ae1e30b7ba3e 100644 --- a/python/pluginJava/com/jetbrains/python/psi/impl/PyJavaClassType.java +++ b/python/pluginJava/com/jetbrains/python/psi/impl/PyJavaClassType.java @@ -128,11 +128,18 @@ public class PyJavaClassType implements PyClassLikeType { return myDefinition; } + @NotNull @Override public PyClassLikeType toInstance() { return myDefinition ? new PyJavaClassType(myClass, false) : this; } + @NotNull + @Override + public PyClassLikeType toClass() { + return myDefinition ? this : new PyJavaClassType(myClass, true); + } + @Nullable @Override public String getClassQName() { diff --git a/python/psi-api/src/com/jetbrains/python/psi/types/PyInstantiableType.java b/python/psi-api/src/com/jetbrains/python/psi/types/PyInstantiableType.java index 18ea6331140d..9bb670c7c606 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/types/PyInstantiableType.java +++ b/python/psi-api/src/com/jetbrains/python/psi/types/PyInstantiableType.java @@ -15,6 +15,8 @@ */ package com.jetbrains.python.psi.types; +import org.jetbrains.annotations.NotNull; + /** * A type that can describe either particular instantiated instance of a class or a class itself. * @@ -23,5 +25,9 @@ package com.jetbrains.python.psi.types; public interface PyInstantiableType> extends PyType { boolean isDefinition(); + @NotNull T toInstance(); + + @NotNull + T toClass(); } diff --git a/python/src/com/jetbrains/python/PyCustomType.java b/python/src/com/jetbrains/python/PyCustomType.java index 46acab051843..d6eddc458670 100644 --- a/python/src/com/jetbrains/python/PyCustomType.java +++ b/python/src/com/jetbrains/python/PyCustomType.java @@ -84,14 +84,23 @@ public class PyCustomType implements PyClassLikeType { return !myInstanceType; } + @NotNull @Override public final PyClassLikeType toInstance() { return myInstanceType ? this - : new PyCustomType(myQualifiedName, myFilter, true, myTypesToMimic.toArray(new PyClassLikeType[myTypesToMimic.size()])); + : new PyCustomType(myQualifiedName, myFilter, true, myTypesToMimic.toArray(new PyClassLikeType[0])); } + @NotNull + @Override + public PyClassLikeType toClass() { + return myInstanceType + ? new PyCustomType(myQualifiedName, myFilter, false, myTypesToMimic.toArray(new PyClassLikeType[0])) + : this; + } + @Nullable @Override public final String getClassQName() { diff --git a/python/src/com/jetbrains/python/codeInsight/stdlib/PyNamedTupleType.java b/python/src/com/jetbrains/python/codeInsight/stdlib/PyNamedTupleType.java index c209106f909b..2835e373a58c 100644 --- a/python/src/com/jetbrains/python/codeInsight/stdlib/PyNamedTupleType.java +++ b/python/src/com/jetbrains/python/codeInsight/stdlib/PyNamedTupleType.java @@ -103,11 +103,18 @@ public class PyNamedTupleType extends PyClassTypeImpl implements PyCallableType return null; } + @NotNull @Override public PyClassType toInstance() { return myDefinitionLevel == 1 ? new PyNamedTupleType(myClass, myDeclaration, myName, myFields, 0) : this; } + @NotNull + @Override + public PyClassLikeType toClass() { + return myDefinitionLevel == 0 ? this : new PyNamedTupleType(myClass, myDeclaration, myName, myFields, 1); + } + @Override public String toString() { return "PyNamedTupleType: " + myName; diff --git a/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java b/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java index 71de5731379c..32d8aec23cb0 100644 --- a/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java +++ b/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java @@ -107,11 +107,18 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType { return myIsDefinition; } + @NotNull @Override public PyClassType toInstance() { return myIsDefinition ? withUserDataCopy(new PyClassTypeImpl(myClass, false)) : this; } + @NotNull + @Override + public PyClassLikeType toClass() { + return myIsDefinition ? this : new PyClassTypeImpl(myClass, true); + } + /** * Wrap new instance to copy user data to it */ diff --git a/python/src/com/jetbrains/python/psi/types/PyCollectionTypeImpl.java b/python/src/com/jetbrains/python/psi/types/PyCollectionTypeImpl.java index 92bb5a904d1f..ea48feab5568 100644 --- a/python/src/com/jetbrains/python/psi/types/PyCollectionTypeImpl.java +++ b/python/src/com/jetbrains/python/psi/types/PyCollectionTypeImpl.java @@ -70,11 +70,18 @@ public class PyCollectionTypeImpl extends PyClassTypeImpl implements PyCollectio return new PyCollectionTypeImpl(pyClass, isDefinition, elementTypes); } + @NotNull @Override public PyClassType toInstance() { return myIsDefinition ? withUserDataCopy(new PyCollectionTypeImpl(myClass, false, myElementTypes)) : this; } + @NotNull + @Override + public PyClassLikeType toClass() { + return myIsDefinition ? this : withUserDataCopy(new PyCollectionTypeImpl(myClass, true, myElementTypes)); + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/python/src/com/jetbrains/python/psi/types/PyGenericType.java b/python/src/com/jetbrains/python/psi/types/PyGenericType.java index 9d419bc33f93..cbd568bfe764 100644 --- a/python/src/com/jetbrains/python/psi/types/PyGenericType.java +++ b/python/src/com/jetbrains/python/psi/types/PyGenericType.java @@ -108,8 +108,15 @@ public class PyGenericType implements PyType, PyInstantiableType return myIsDefinition; } + @NotNull @Override public PyGenericType toInstance() { return myIsDefinition ? new PyGenericType(myName, myBound, false) : this; } + + @NotNull + @Override + public PyGenericType toClass() { + return myIsDefinition ? this : new PyGenericType(myName, myBound, true); + } } diff --git a/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java b/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java index 5d46f1aa18e5..a507ffc5a876 100644 --- a/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java +++ b/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java @@ -416,7 +416,22 @@ public class PyTypeChecker { @NotNull TypeEvalContext context) { if (hasGenerics(type, context)) { if (type instanceof PyGenericType) { - final PyType substitution = substitutions.get((PyGenericType)type); + final PyGenericType typeVar = (PyGenericType)type; + PyType substitution = substitutions.get(typeVar); + if (substitution == null) { + if (!typeVar.isDefinition()) { + final PyInstantiableType classType = as(substitutions.get(typeVar.toClass()), PyInstantiableType.class); + if (classType != null) { + substitution = classType.toInstance(); + } + } + else { + final PyInstantiableType instanceType = as(substitutions.get(typeVar.toInstance()), PyInstantiableType.class); + if (instanceType != null) { + substitution = instanceType.toClass(); + } + } + } if (substitution instanceof PyGenericType && substitution != type) { final PyType recursive = substitute(substitution, substitutions, context); if (recursive != null) { diff --git a/python/testSrc/com/jetbrains/python/PyTypingTest.java b/python/testSrc/com/jetbrains/python/PyTypingTest.java index 21e08c7948c4..734e5b8d42b6 100644 --- a/python/testSrc/com/jetbrains/python/PyTypingTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypingTest.java @@ -807,11 +807,11 @@ public class PyTypingTest extends PyTestCase { } // PY-20057 - public void testConstrainedClassObjectTypeOfResultValue() { + public void testFunctionCreatesInstanceFromType() { doTest("int", "from typing import Type, TypeVar\n" + "\n" + - "T = TypeVar('T', bound=int)\n" + + "T = TypeVar('T')\n" + "\n" + "def f(x: Type[T]) -> T:\n" + " return x()\n" + @@ -819,6 +819,19 @@ public class PyTypingTest extends PyTestCase { "expr = f(int)"); } + // PY-20057 + public void testFunctionReturnsTypeOfInstance() { + doTest("Type[int]", + "from typing import Type, TypeVar\n" + + "\n" + + "T = TypeVar('T')\n" + + "\n" + + "def f(x: T) -> Type[T]:\n" + + " return type(T)\n" + + " \n" + + "expr = f(42)"); + } + private void doTestNoInjectedText(@NotNull String text) { myFixture.configureByText(PythonFileType.INSTANCE, text); final InjectedLanguageManager languageManager = InjectedLanguageManager.getInstance(myFixture.getProject()); From 2c79c98d22513936691a4dee2e3ff80ed477fd4d Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Wed, 15 Feb 2017 15:26:05 +0300 Subject: [PATCH 22/33] PY-20057 Consider incompatible types one of which is Type[] in the type checker --- .../python/psi/types/PyTypeChecker.java | 10 ++++++- .../ClassObjectType.py | 28 +++++++++++++++++++ .../Py3TypeCheckerInspectionTest.java | 5 ++++ 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 python/testData/inspections/PyTypeCheckerInspection/ClassObjectType.py diff --git a/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java b/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java index a507ffc5a876..abe39749ae37 100644 --- a/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java +++ b/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java @@ -73,10 +73,18 @@ public class PyTypeChecker { return match(expected, builtinCache.getStrOrUnicodeType(), context, substitutions, recursive); } } + if (expected instanceof PyInstantiableType && actual instanceof PyInstantiableType + && ((PyInstantiableType)expected).isDefinition() ^ ((PyInstantiableType)actual).isDefinition()) { + return false; + } if (expected instanceof PyGenericType && substitutions != null) { final PyGenericType generic = (PyGenericType)expected; final PyType subst = substitutions.get(generic); - final PyType bound = generic.getBound(); + PyType bound = generic.getBound(); + // Promote int in Type[TypeVar('T', int)] to Type[int] before checking that bounds match + if (generic.isDefinition() && bound instanceof PyInstantiableType) { + bound = ((PyInstantiableType)bound).toClass(); + } if (!match(bound, actual, context, substitutions, recursive)) { return false; } diff --git a/python/testData/inspections/PyTypeCheckerInspection/ClassObjectType.py b/python/testData/inspections/PyTypeCheckerInspection/ClassObjectType.py new file mode 100644 index 000000000000..4bb448278189 --- /dev/null +++ b/python/testData/inspections/PyTypeCheckerInspection/ClassObjectType.py @@ -0,0 +1,28 @@ +from typing import Type, TypeVar + +class MyClass: + pass + +def expects_myclass(x: Type[MyClass]): + pass + +expects_myclass(MyClass()) +expects_class(MyClass) + +T1 = TypeVar('T1') +def expects_any_class(x: Type[T1]): + pass + +# expects_any_class(MyClass) +expects_any_class(MyClass()) +# expects_any_class(object) +expects_any_class(object()) + +T2 = TypeVar('T2', MyClass) +def expects_myclass_descendant(x: Type[T2]): + pass + +expects_myclass_descendant(MyClass) +expects_myclass_descendant(MyClass()) +expects_myclass_descendant(object) +expects_myclass_descendant(object()) \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/inspections/Py3TypeCheckerInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/Py3TypeCheckerInspectionTest.java index 62775974104c..4b0e8ac896c0 100644 --- a/python/testSrc/com/jetbrains/python/inspections/Py3TypeCheckerInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/Py3TypeCheckerInspectionTest.java @@ -198,4 +198,9 @@ public class Py3TypeCheckerInspectionTest extends PyTestCase { public void testBuiltinInputPy3() { doTest(); } + + // PY-200057 + public void testClassObjectType() { + doTest(); + } } From 05951d275f0f9e669c1f0cbaf541595c69212add Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Wed, 15 Feb 2017 19:32:12 +0300 Subject: [PATCH 23/33] PY-22730 Properly render Union and Optional types containing TypeVars Namely, don't consider them "unknown" wrapping them in another weak Union and rendering using PyType#getName() rather than PyTypeModelBuilder. --- .../python/documentation/PyTypeModelBuilder.java | 2 +- .../com/jetbrains/python/psi/types/PyTypeChecker.java | 8 ++++++-- .../OptionalAndUnionTypesContainingTypeVars.html | 1 + .../OptionalAndUnionTypesContainingTypeVars.py | 11 +++++++++++ .../testSrc/com/jetbrains/python/PyQuickDocTest.java | 6 ++++++ 5 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 python/testData/quickdoc/OptionalAndUnionTypesContainingTypeVars.html create mode 100644 python/testData/quickdoc/OptionalAndUnionTypesContainingTypeVars.py diff --git a/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java b/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java index b243d1423ff8..b24646262593 100644 --- a/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java +++ b/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java @@ -266,7 +266,7 @@ public class PyTypeModelBuilder { } else if (type instanceof PyUnionType && allowUnions) { final PyUnionType unionType = (PyUnionType)type; - if (type instanceof PyDynamicallyEvaluatedType || PyTypeChecker.isUnknown(type)) { + if (type instanceof PyDynamicallyEvaluatedType || PyTypeChecker.isUnknown(type, false)) { result = new UnknownType(build(unionType.excludeNull(myContext), true)); } else { diff --git a/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java b/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java index abe39749ae37..3b0819fbfa12 100644 --- a/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java +++ b/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java @@ -346,13 +346,17 @@ public class PyTypeChecker { } public static boolean isUnknown(@Nullable PyType type) { - if (type == null || type instanceof PyGenericType) { + return isUnknown(type, true); + } + + public static boolean isUnknown(@Nullable PyType type, boolean genericsAreUnknown) { + if (type == null || (genericsAreUnknown && type instanceof PyGenericType)) { return true; } if (type instanceof PyUnionType) { final PyUnionType union = (PyUnionType)type; for (PyType t : union.getMembers()) { - if (isUnknown(t)) { + if (isUnknown(t, genericsAreUnknown)) { return true; } } diff --git a/python/testData/quickdoc/OptionalAndUnionTypesContainingTypeVars.html b/python/testData/quickdoc/OptionalAndUnionTypesContainingTypeVars.html new file mode 100644 index 000000000000..38ed0a947505 --- /dev/null +++ b/python/testData/quickdoc/OptionalAndUnionTypesContainingTypeVars.html @@ -0,0 +1 @@ +def f(x1: Optional[T], x2: Union[T, Tuple[Any, Any]])
Inferred type: (x1: Optional[TypeVar('T', int)], x2: Union[TypeVar('T', int), Tuple[Any, Any]]) -> None
\ No newline at end of file diff --git a/python/testData/quickdoc/OptionalAndUnionTypesContainingTypeVars.py b/python/testData/quickdoc/OptionalAndUnionTypesContainingTypeVars.py new file mode 100644 index 000000000000..61f83aa38952 --- /dev/null +++ b/python/testData/quickdoc/OptionalAndUnionTypesContainingTypeVars.py @@ -0,0 +1,11 @@ +from typing import TypeVar, Optional, Union, Tuple, Any + + +T = TypeVar('T', int) + + +def f(x1: Optional[T], x2: Union[T, Tuple[Any, Any]]): + print(x1, x2) + + +f diff --git a/python/testSrc/com/jetbrains/python/PyQuickDocTest.java b/python/testSrc/com/jetbrains/python/PyQuickDocTest.java index d635a492df46..d4f4a6041c9f 100644 --- a/python/testSrc/com/jetbrains/python/PyQuickDocTest.java +++ b/python/testSrc/com/jetbrains/python/PyQuickDocTest.java @@ -282,4 +282,10 @@ public class PyQuickDocTest extends LightMarkedTestCase { myFixture.copyDirectoryToProject("typing", ""); runWithLanguageLevel(LanguageLevel.PYTHON35, this::checkHTMLOnly); } + + // PY-22730 + public void testOptionalAndUnionTypesContainingTypeVars() { + myFixture.copyDirectoryToProject("typing", ""); + runWithLanguageLevel(LanguageLevel.PYTHON36, this::checkHTMLOnly); + } } From 0ba869fd2db43f607e8df4773970328ba7989ec5 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Wed, 15 Feb 2017 19:41:12 +0300 Subject: [PATCH 24/33] PY-20057 Add test cases on Type[Something] wrapped in typing.Optional --- .../PyTypeCheckerInspection/ClassObjectType.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/python/testData/inspections/PyTypeCheckerInspection/ClassObjectType.py b/python/testData/inspections/PyTypeCheckerInspection/ClassObjectType.py index 4bb448278189..e30edbc21fda 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/ClassObjectType.py +++ b/python/testData/inspections/PyTypeCheckerInspection/ClassObjectType.py @@ -1,4 +1,4 @@ -from typing import Type, TypeVar +from typing import Type, TypeVar, Optional class MyClass: pass @@ -13,9 +13,9 @@ T1 = TypeVar('T1') def expects_any_class(x: Type[T1]): pass -# expects_any_class(MyClass) +expects_any_class(MyClass) expects_any_class(MyClass()) -# expects_any_class(object) +expects_any_class(object) expects_any_class(object()) T2 = TypeVar('T2', MyClass) @@ -25,4 +25,12 @@ def expects_myclass_descendant(x: Type[T2]): expects_myclass_descendant(MyClass) expects_myclass_descendant(MyClass()) expects_myclass_descendant(object) -expects_myclass_descendant(object()) \ No newline at end of file +expects_myclass_descendant(object()) + +def expects_myclass_descendant_or_none(x: Optional[Type[T2]]): + pass + +expects_myclass_descendant_or_none(MyClass) +expects_myclass_descendant_or_none(MyClass()) +expects_myclass_descendant_or_none(object) +expects_myclass_descendant_or_none(object()) \ No newline at end of file From 30c007d8002d867de9cdb09152be046bb4012fd1 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Mon, 27 Feb 2017 18:26:00 +0300 Subject: [PATCH 25/33] PY-20057 Fix a few tests broken due to changes in PyGenericType --- .../src/com/jetbrains/python/psi/types/PyGenericType.java | 3 +-- .../PyTypeCheckerInspection/FunctionParameterReturnType.py | 2 +- .../PyTypeCheckerInspection/MetaClassIteration.py | 2 +- python/testSrc/com/jetbrains/python/PyTypeParserTest.java | 6 ++---- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/python/src/com/jetbrains/python/psi/types/PyGenericType.java b/python/src/com/jetbrains/python/psi/types/PyGenericType.java index cbd568bfe764..bcfd524dc416 100644 --- a/python/src/com/jetbrains/python/psi/types/PyGenericType.java +++ b/python/src/com/jetbrains/python/psi/types/PyGenericType.java @@ -26,7 +26,6 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.List; -import java.util.Objects; /** * @author vlan @@ -84,7 +83,7 @@ public class PyGenericType implements PyType, PyInstantiableType return false; } final PyGenericType type = (PyGenericType)o; - return myName.equals(type.myName) && Objects.equals(myBound, type.getBound()) && myIsDefinition == type.isDefinition(); + return myName.equals(type.myName) && myIsDefinition == type.isDefinition(); } @Override diff --git a/python/testData/inspections/PyTypeCheckerInspection/FunctionParameterReturnType.py b/python/testData/inspections/PyTypeCheckerInspection/FunctionParameterReturnType.py index efdfc5722cde..3a4d0a8c1273 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/FunctionParameterReturnType.py +++ b/python/testData/inspections/PyTypeCheckerInspection/FunctionParameterReturnType.py @@ -15,7 +15,7 @@ def test(): for item in func(f, []): pass - for item in func(int, []): + for item in func(int, []): pass for item in func(lambda x: int(x), []): diff --git a/python/testData/inspections/PyTypeCheckerInspection/MetaClassIteration.py b/python/testData/inspections/PyTypeCheckerInspection/MetaClassIteration.py index 0ec69266af7c..daaefdd4cc32 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/MetaClassIteration.py +++ b/python/testData/inspections/PyTypeCheckerInspection/MetaClassIteration.py @@ -23,7 +23,7 @@ for x in C1: pass -for y in C2: +for y in C2: pass diff --git a/python/testSrc/com/jetbrains/python/PyTypeParserTest.java b/python/testSrc/com/jetbrains/python/PyTypeParserTest.java index 79e1a3e7c958..af5b38ff7c8c 100644 --- a/python/testSrc/com/jetbrains/python/PyTypeParserTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypeParserTest.java @@ -24,8 +24,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; -import static com.jetbrains.python.psi.PyUtil.as; - /** * @author yole */ @@ -139,7 +137,7 @@ public class PyTypeParserTest extends PyTestCase { final PyType type = PyTypeParser.getTypeByName(myFixture.getFile(), "T"); assertNotNull(type); assertInstanceOf(type, PyGenericType.class); - assertEquals("TypeVar('T')", type.getName()); + assertEquals("T", type.getName()); } // PY-4223 @@ -251,7 +249,7 @@ public class PyTypeParserTest extends PyTestCase { assertEquals("int", type0.getName()); final PyType type1 = parameterTypes.get(1).getType(context); assertNotNull(type1); - assertEquals("TypeVar('T')", type1.getName()); + assertEquals("T", type1.getName()); } public void testCallableWithoutArgs() { From c700e57db42fea4c6cc20b1732215c45decae4b9 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Mon, 27 Feb 2017 21:20:38 +0300 Subject: [PATCH 26/33] PY-20057 Partially support type checking between type and typing.Type[] --- .../src/com/jetbrains/python/psi/types/PyTypeChecker.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java b/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java index 3b0819fbfa12..e9ca9b01554e 100644 --- a/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java +++ b/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java @@ -69,10 +69,15 @@ public class PyTypeChecker { final PyClassType classType = (PyClassType)actual; final PyBuiltinCache builtinCache = PyBuiltinCache.getInstance(classType.getPyClass()); - if (actual == builtinCache.getObjectType(PyNames.BASESTRING)) { + if (actual.equals(builtinCache.getObjectType(PyNames.BASESTRING))) { return match(expected, builtinCache.getStrOrUnicodeType(), context, substitutions, recursive); } } + if (expected instanceof PyClassType && + expected.equals(PyBuiltinCache.getInstance(((PyClassType)expected).getPyClass()).getTypeType()) && + actual instanceof PyInstantiableType && ((PyInstantiableType)actual).isDefinition()) { + return true; + } if (expected instanceof PyInstantiableType && actual instanceof PyInstantiableType && ((PyInstantiableType)expected).isDefinition() ^ ((PyInstantiableType)actual).isDefinition()) { return false; From 04f49fbbc454062795f2679bab7f1bc455099fd2 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Tue, 28 Feb 2017 16:51:29 +0300 Subject: [PATCH 27/33] PY-20057 Treat the second argument of isinstance() as instance type, not class type --- .../codeInsight/controlflow/PyTypeAssertionEvaluator.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/python/src/com/jetbrains/python/codeInsight/controlflow/PyTypeAssertionEvaluator.java b/python/src/com/jetbrains/python/codeInsight/controlflow/PyTypeAssertionEvaluator.java index a6c983c6b5ab..377d5b4520b3 100644 --- a/python/src/com/jetbrains/python/codeInsight/controlflow/PyTypeAssertionEvaluator.java +++ b/python/src/com/jetbrains/python/codeInsight/controlflow/PyTypeAssertionEvaluator.java @@ -68,7 +68,11 @@ public class PyTypeAssertionEvaluator extends PyRecursiveElementVisitor { final PyReferenceExpression target = (PyReferenceExpression)args[0]; final PyExpression typeElement = args[1]; - pushAssertion(target, myPositive, context -> context.getType(typeElement)); + // TODO support tuple of types as the second argument of isinstance() + pushAssertion(target, myPositive, context -> { + final PyType type = context.getType(typeElement); + return type instanceof PyClassType ? ((PyClassType)type).toInstance() : type; + }); } } else if (node.isCalleeText(PyNames.CALLABLE_BUILTIN)) { @@ -183,7 +187,7 @@ public class PyTypeAssertionEvaluator extends PyRecursiveElementVisitor { final PyType initial = context.getType(target); final PyType suggested = suggestedType.apply(context); - if (!PyUnionType.class.isInstance(initial) && + if (!(initial instanceof PyUnionType) && !PyTypeChecker.isUnknown(initial) && PyTypeChecker.match(suggested, initial, context)) { return initial; From 9ce723de0d0eed439134a1afe279f8979d985e5a Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Tue, 28 Feb 2017 16:59:39 +0300 Subject: [PATCH 28/33] PY-20057 typing.Type, type and object compatibility --- .../typing/PyTypingTypeProvider.java | 14 ++++--- .../python/psi/types/PyTypeChecker.java | 32 +++++++------- .../TypeAndClassObjectTypesCompatibility.py | 42 +++++++++++++++++++ .../com/jetbrains/python/PyTypingTest.java | 9 ++++ .../Py3TypeCheckerInspectionTest.java | 5 +++ 5 files changed, 80 insertions(+), 22 deletions(-) create mode 100644 python/testData/inspections/PyTypeCheckerInspection/TypeAndClassObjectTypesCompatibility.py diff --git a/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java b/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java index d392e60a0064..b7900b73461b 100644 --- a/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java +++ b/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java @@ -471,6 +471,10 @@ public class PyTypingTypeProvider extends PyTypeProviderBase { if (callableType != null) { return Ref.create(callableType); } + final PyType classObjType = getClassObjectType(resolved, context); + if (classObjType != null) { + return Ref.create(classObjType); + } final PyType parameterizedType = getParameterizedType(resolved, context); if (parameterizedType != null) { return Ref.create(parameterizedType); @@ -483,10 +487,6 @@ public class PyTypingTypeProvider extends PyTypeProviderBase { if (genericType != null) { return Ref.create(genericType); } - final PyType classObjType = getClassObjectType(resolved, context); - if (classObjType != null) { - return Ref.create(classObjType); - } final Ref classType = getClassType(resolved, context.getTypeContext()); if (classType != null) { return classType; @@ -507,7 +507,7 @@ public class PyTypingTypeProvider extends PyTypeProviderBase { } @Nullable - private static PyType getClassObjectType(@Nullable PsiElement resolved, @NotNull Context context) { + private static PyType getClassObjectType(@NotNull PsiElement resolved, @NotNull Context context) { if (resolved instanceof PySubscriptionExpression) { final PySubscriptionExpression subsExpr = (PySubscriptionExpression)resolved; final PyExpression operand = subsExpr.getOperand(); @@ -527,6 +527,10 @@ public class PyTypingTypeProvider extends PyTypeProviderBase { } } } + // Replace plain non-parametrized Type with its builtin counterpart + else if (TYPE.equals(getQualifiedName(resolved))) { + return PyTypeParser.getTypeByName(resolved, PyNames.TYPE); + } return null; } diff --git a/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java b/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java index e9ca9b01554e..0ed38bd8b7a0 100644 --- a/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java +++ b/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java @@ -65,23 +65,27 @@ public class PyTypeChecker { private static boolean match(@Nullable PyType expected, @Nullable PyType actual, @NotNull TypeEvalContext context, @Nullable Map substitutions, boolean recursive) { // TODO: subscriptable types?, module types?, etc. - if (actual instanceof PyClassType) { - final PyClassType classType = (PyClassType)actual; - final PyBuiltinCache builtinCache = PyBuiltinCache.getInstance(classType.getPyClass()); - - if (actual.equals(builtinCache.getObjectType(PyNames.BASESTRING))) { - return match(expected, builtinCache.getStrOrUnicodeType(), context, substitutions, recursive); + // Special cases: object and type + if (expected instanceof PyClassType) { + final PyBuiltinCache builtinCache = PyBuiltinCache.getInstance(((PyClassType)expected).getPyClass()); + if (expected.equals(builtinCache.getObjectType())) { + return true; + } + if (expected.equals(builtinCache.getTypeType()) && + actual instanceof PyInstantiableType && ((PyInstantiableType)actual).isDefinition()) { + return true; } - } - if (expected instanceof PyClassType && - expected.equals(PyBuiltinCache.getInstance(((PyClassType)expected).getPyClass()).getTypeType()) && - actual instanceof PyInstantiableType && ((PyInstantiableType)actual).isDefinition()) { - return true; } if (expected instanceof PyInstantiableType && actual instanceof PyInstantiableType && ((PyInstantiableType)expected).isDefinition() ^ ((PyInstantiableType)actual).isDefinition()) { return false; } + if (actual instanceof PyClassType) { + final PyBuiltinCache builtinCache = PyBuiltinCache.getInstance(((PyClassType)actual).getPyClass()); + if (actual.equals(builtinCache.getObjectType(PyNames.BASESTRING))) { + return match(expected, builtinCache.getStrOrUnicodeType(), context, substitutions, recursive); + } + } if (expected instanceof PyGenericType && substitutions != null) { final PyGenericType generic = (PyGenericType)expected; final PyType subst = substitutions.get(generic); @@ -115,12 +119,6 @@ public class PyTypeChecker { if (expected == null || actual == null) { return true; } - if (expected instanceof PyClassType) { - final PyClass c = ((PyClassType)expected).getPyClass(); - if ("object".equals(c.getName())) { - return true; - } - } if (isUnknown(actual)) { return true; } diff --git a/python/testData/inspections/PyTypeCheckerInspection/TypeAndClassObjectTypesCompatibility.py b/python/testData/inspections/PyTypeCheckerInspection/TypeAndClassObjectTypesCompatibility.py new file mode 100644 index 000000000000..1a9e4eda32cd --- /dev/null +++ b/python/testData/inspections/PyTypeCheckerInspection/TypeAndClassObjectTypesCompatibility.py @@ -0,0 +1,42 @@ +from typing import TypeVar, Type + +T = TypeVar('T') +S = TypeVar('T', str) + + +def expects_type(x: type): + pass + + +def expects_typing_type(x: Type): + expects_type(x) + + +def expects_any_type_via_type_var(x: Type[T]): + expects_type(x) + + +def expects_str_class(x: Type[str]): + expects_type(x) + + +def expects_str_subclass(x: Type[S]): + expects_type(x) + + +def expects_object(x: object): + expects_type(x) + + +expects_type(type) +expects_type(object) +expects_typing_type(type) +expects_typing_type(object) +expects_str_class(type) +expects_any_type_via_type_var(type) +expects_str_subclass(type) +expects_object(type) + + + + diff --git a/python/testSrc/com/jetbrains/python/PyTypingTest.java b/python/testSrc/com/jetbrains/python/PyTypingTest.java index 734e5b8d42b6..d33fa8a7e5ea 100644 --- a/python/testSrc/com/jetbrains/python/PyTypingTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypingTest.java @@ -832,6 +832,15 @@ public class PyTypingTest extends PyTestCase { "expr = f(42)"); } + // PY-20057 + public void testNonParametrizedTypingTypeMapsToBuiltinType() { + doTest("type", + "from typing import Type\n" + + "\n" + + "def f(x: Type):\n" + + " expr = x"); + } + private void doTestNoInjectedText(@NotNull String text) { myFixture.configureByText(PythonFileType.INSTANCE, text); final InjectedLanguageManager languageManager = InjectedLanguageManager.getInstance(myFixture.getProject()); diff --git a/python/testSrc/com/jetbrains/python/inspections/Py3TypeCheckerInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/Py3TypeCheckerInspectionTest.java index 4b0e8ac896c0..33698bd3374e 100644 --- a/python/testSrc/com/jetbrains/python/inspections/Py3TypeCheckerInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/Py3TypeCheckerInspectionTest.java @@ -203,4 +203,9 @@ public class Py3TypeCheckerInspectionTest extends PyTestCase { public void testClassObjectType() { doTest(); } + + // PY-20057 + public void testTypeAndClassObjectTypesCompatibility() { + doTest(); + } } From 1bce2d9bf50eaa41b64d05e21baf554d7d87d120 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Wed, 1 Mar 2017 16:19:39 +0300 Subject: [PATCH 29/33] PY-20057 Render the type of "type" class as just "type" instead of "Type[type]" --- .../python/documentation/PyTypeModelBuilder.java | 10 +++++++++- .../TypeAndClassObjectTypesCompatibility.py | 4 ++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java b/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java index b24646262593..017b812fc5c3 100644 --- a/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java +++ b/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java @@ -21,6 +21,7 @@ import com.intellij.psi.PsiElement; import com.intellij.util.containers.ContainerUtil; import com.jetbrains.python.PyNames; import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider; +import com.jetbrains.python.psi.impl.PyBuiltinCache; import com.jetbrains.python.psi.types.*; import com.jetbrains.python.toolbox.ChainIterable; import org.jetbrains.annotations.NotNull; @@ -280,7 +281,14 @@ public class PyTypeModelBuilder { result = buildCallable((PyCallableType)type); } else if (type instanceof PyInstantiableType && ((PyInstantiableType)type).isDefinition()) { - result = new ClassObjectType(build(((PyInstantiableType)type).toInstance(), allowUnions)); + final PyInstantiableType instanceType = ((PyInstantiableType)type).toInstance(); + // Special case: render Type[type] as just type + if (type instanceof PyClassType && instanceType.equals(PyBuiltinCache.getInstance(((PyClassType)type).getPyClass()).getTypeType())) { + result = NamedType.nameOrAny(type); + } + else { + result = new ClassObjectType(build(instanceType, allowUnions)); + } } else if (type instanceof PyGenericType) { //assert !((PyGenericType)type).isDefinition() diff --git a/python/testData/inspections/PyTypeCheckerInspection/TypeAndClassObjectTypesCompatibility.py b/python/testData/inspections/PyTypeCheckerInspection/TypeAndClassObjectTypesCompatibility.py index 1a9e4eda32cd..b484b262720f 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/TypeAndClassObjectTypesCompatibility.py +++ b/python/testData/inspections/PyTypeCheckerInspection/TypeAndClassObjectTypesCompatibility.py @@ -32,9 +32,9 @@ expects_type(type) expects_type(object) expects_typing_type(type) expects_typing_type(object) -expects_str_class(type) +expects_str_class(type) expects_any_type_via_type_var(type) -expects_str_subclass(type) +expects_str_subclass(type) expects_object(type) From 0a2c73161dafc2124dc1173dc118051fe8093174 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Wed, 1 Mar 2017 18:07:14 +0300 Subject: [PATCH 30/33] PY-20057 Support Type[Any] and handle illegal typing.Type annotations --- .../typing/PyTypingTypeProvider.java | 25 +++++++++++-------- .../TypeAndClassObjectTypesCompatibility.py | 5 ++++ .../com/jetbrains/python/PyTypingTest.java | 18 +++++++++++++ 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java b/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java index b7900b73461b..9ec2fe8ac819 100644 --- a/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java +++ b/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java @@ -36,6 +36,7 @@ import com.jetbrains.python.codeInsight.functionTypeComments.psi.PyFunctionTypeA import com.jetbrains.python.codeInsight.functionTypeComments.psi.PyFunctionTypeAnnotationFile; import com.jetbrains.python.codeInsight.functionTypeComments.psi.PyParameterTypeList; import com.jetbrains.python.psi.*; +import com.jetbrains.python.psi.impl.PyBuiltinCache; import com.jetbrains.python.psi.impl.PyExpressionCodeFragmentImpl; import com.jetbrains.python.psi.resolve.PyResolveContext; import com.jetbrains.python.psi.resolve.PyResolveImportUtil; @@ -63,6 +64,7 @@ public class PyTypingTypeProvider extends PyTypeProviderBase { public static final String NAMEDTUPLE = "typing.NamedTuple"; public static final String GENERIC = "typing.Generic"; public static final String TYPE = "typing.Type"; + public static final String ANY = "typing.Any"; public static final Pattern TYPE_COMMENT_PATTERN = Pattern.compile("# *type: *(.*)"); @@ -471,9 +473,9 @@ public class PyTypingTypeProvider extends PyTypeProviderBase { if (callableType != null) { return Ref.create(callableType); } - final PyType classObjType = getClassObjectType(resolved, context); + final Ref classObjType = getClassObjectType(resolved, context); if (classObjType != null) { - return Ref.create(classObjType); + return classObjType; } final PyType parameterizedType = getParameterizedType(resolved, context); if (parameterizedType != null) { @@ -507,7 +509,7 @@ public class PyTypingTypeProvider extends PyTypeProviderBase { } @Nullable - private static PyType getClassObjectType(@NotNull PsiElement resolved, @NotNull Context context) { + private static Ref getClassObjectType(@NotNull PsiElement resolved, @NotNull Context context) { if (resolved instanceof PySubscriptionExpression) { final PySubscriptionExpression subsExpr = (PySubscriptionExpression)resolved; final PyExpression operand = subsExpr.getOperand(); @@ -515,32 +517,33 @@ public class PyTypingTypeProvider extends PyTypeProviderBase { if (operandNames.contains(TYPE)) { final PyExpression indexExpr = subsExpr.getIndexExpression(); if (indexExpr != null) { + if (resolveToQualifiedNames(indexExpr, context.getTypeContext()).contains(ANY)) { + return Ref.create(PyBuiltinCache.getInstance(resolved).getTypeType()); + } final PyType type = Ref.deref(getType(indexExpr, context)); final PyClassType classType = as(type, PyClassType.class); if (classType != null && !classType.isDefinition()) { - return new PyClassTypeImpl(classType.getPyClass(), true); + return Ref.create(new PyClassTypeImpl(classType.getPyClass(), true)); } final PyGenericType typeVar = as(type, PyGenericType.class); if (typeVar != null && !typeVar.isDefinition()) { - return new PyGenericType(typeVar.getName(), typeVar.getBound(), true); + return Ref.create(new PyGenericType(typeVar.getName(), typeVar.getBound(), true)); } } + // Map Type[Something] with unsupported type parameter to Any, instead of generic type for the class "type" + return Ref.create(); } } // Replace plain non-parametrized Type with its builtin counterpart else if (TYPE.equals(getQualifiedName(resolved))) { - return PyTypeParser.getTypeByName(resolved, PyNames.TYPE); + return Ref.create(PyBuiltinCache.getInstance(resolved).getTypeType()); } return null; } @Nullable private static Ref getAnyType(@NotNull PsiElement element) { - final PyQualifiedNameOwner qualifiedNameOwner = as(element, PyQualifiedNameOwner.class); - if (qualifiedNameOwner != null && "typing.Any".equals(qualifiedNameOwner.getQualifiedName())) { - return Ref.create(); - } - return null; + return ANY.equals(getQualifiedName(element)) ? Ref.create() : null; } @Nullable diff --git a/python/testData/inspections/PyTypeCheckerInspection/TypeAndClassObjectTypesCompatibility.py b/python/testData/inspections/PyTypeCheckerInspection/TypeAndClassObjectTypesCompatibility.py index b484b262720f..cc0be1130066 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/TypeAndClassObjectTypesCompatibility.py +++ b/python/testData/inspections/PyTypeCheckerInspection/TypeAndClassObjectTypesCompatibility.py @@ -12,6 +12,10 @@ def expects_typing_type(x: Type): expects_type(x) +def expects_typing_type_any(x: Type[Any]): + expects_type(x) + + def expects_any_type_via_type_var(x: Type[T]): expects_type(x) @@ -31,6 +35,7 @@ def expects_object(x: object): expects_type(type) expects_type(object) expects_typing_type(type) +expects_typing_type_any(type) expects_typing_type(object) expects_str_class(type) expects_any_type_via_type_var(type) diff --git a/python/testSrc/com/jetbrains/python/PyTypingTest.java b/python/testSrc/com/jetbrains/python/PyTypingTest.java index d33fa8a7e5ea..84ac1a202199 100644 --- a/python/testSrc/com/jetbrains/python/PyTypingTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypingTest.java @@ -840,6 +840,24 @@ public class PyTypingTest extends PyTestCase { "def f(x: Type):\n" + " expr = x"); } + + // PY-20057 + public void testTypingTypeOfAnyMapsToBuiltinType() { + doTest("type", + "from typing import Type, Any\n" + + "\n" + + "def f(x: Type[Any]):\n" + + " expr = x"); + } + + // PY-20057 + public void testIllegalTypingTypeFormat() { + doTest("Tuple[Any, Any, Any]", + "from typing import Type, Tuple\n" + + "\n" + + "def f(x: Tuple[Type[42], Type[], Type[unresolved]]):\n" + + " expr = x"); + } private void doTestNoInjectedText(@NotNull String text) { myFixture.configureByText(PythonFileType.INSTANCE, text); From d5cbfa6950c7bb74297d50f15810447c000eb5f2 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Wed, 1 Mar 2017 18:50:37 +0300 Subject: [PATCH 31/33] PY-20057 Support Type[Union[Class1, Class2]] annotation syntax --- .../typing/PyTypingTypeProvider.java | 6 ++++++ .../documentation/PyTypeModelBuilder.java | 9 ++++++-- .../ClassObjectTypeWithUnion.py | 21 +++++++++++++++++++ .../com/jetbrains/python/PyTypingTest.java | 9 ++++++++ .../Py3TypeCheckerInspectionTest.java | 5 +++++ 5 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 python/testData/inspections/PyTypeCheckerInspection/ClassObjectTypeWithUnion.py diff --git a/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java b/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java index 9ec2fe8ac819..6ab2f86239f7 100644 --- a/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java +++ b/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java @@ -529,6 +529,12 @@ public class PyTypingTypeProvider extends PyTypeProviderBase { if (typeVar != null && !typeVar.isDefinition()) { return Ref.create(new PyGenericType(typeVar.getName(), typeVar.getBound(), true)); } + // Represent Type[Union[str, int]] internally as Union[Type[str], Type[int]] + final PyUnionType unionType = as(type, PyUnionType.class); + if (unionType != null && + unionType.getMembers().stream().allMatch(t -> t instanceof PyClassType && !((PyClassType)t).isDefinition())) { + return Ref.create(PyUnionType.union(ContainerUtil.map(unionType.getMembers(), t -> ((PyClassType)t).toClass()))); + } } // Map Type[Something] with unsupported type parameter to Any, instead of generic type for the class "type" return Ref.create(); diff --git a/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java b/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java index 017b812fc5c3..80a36ad769e4 100644 --- a/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java +++ b/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java @@ -267,14 +267,19 @@ public class PyTypeModelBuilder { } else if (type instanceof PyUnionType && allowUnions) { final PyUnionType unionType = (PyUnionType)type; + final Collection unionMembers = unionType.getMembers(); if (type instanceof PyDynamicallyEvaluatedType || PyTypeChecker.isUnknown(type, false)) { result = new UnknownType(build(unionType.excludeNull(myContext), true)); } + else if (unionMembers.stream().allMatch(t -> t instanceof PyClassType && ((PyClassType)t).isDefinition())) { + final List instanceTypes = ContainerUtil.map(unionMembers, t -> build(((PyClassType)t).toInstance(), allowUnions)); + result = new ClassObjectType(new OneOf(instanceTypes)); + } else { result = Optional .ofNullable(getOptionalType(unionType)) - .map(optionalType -> new OptionalType(build(optionalType, true))) - .orElseGet(() -> new OneOf(Collections2.transform(unionType.getMembers(), t -> build(t, false)))); + .map(optionalType -> new OptionalType(build(optionalType, true))) + .orElseGet(() -> new OneOf(Collections2.transform(unionMembers, t -> build(t, false)))); } } else if (type instanceof PyCallableType && !(type instanceof PyClassLikeType)) { diff --git a/python/testData/inspections/PyTypeCheckerInspection/ClassObjectTypeWithUnion.py b/python/testData/inspections/PyTypeCheckerInspection/ClassObjectTypeWithUnion.py new file mode 100644 index 000000000000..4e22f3045773 --- /dev/null +++ b/python/testData/inspections/PyTypeCheckerInspection/ClassObjectTypeWithUnion.py @@ -0,0 +1,21 @@ +from typing import Type, Union + +class MyClass: + pass + +def expects_myclass_or_str1(x: Type[Union[MyClass, str]]): + pass + +expects_myclass_or_str1(MyClass) +expects_myclass_or_str1(str) +expects_myclass_or_str1(int) +expects_myclass_or_str1(42) + + +def expects_myclass_or_str2(x: Union[Type[MyClass], Type[str]]): + pass + +expects_myclass_or_str2(MyClass) +expects_myclass_or_str2(str) +expects_myclass_or_str2(int) +expects_myclass_or_str2(42) diff --git a/python/testSrc/com/jetbrains/python/PyTypingTest.java b/python/testSrc/com/jetbrains/python/PyTypingTest.java index 84ac1a202199..9342dd00adfe 100644 --- a/python/testSrc/com/jetbrains/python/PyTypingTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypingTest.java @@ -858,6 +858,15 @@ public class PyTypingTest extends PyTestCase { "def f(x: Tuple[Type[42], Type[], Type[unresolved]]):\n" + " expr = x"); } + + // PY-20057 + public void testUnionOfClassObjectTypes() { + doTest("Type[Union[int, str]]", + "from typing import Type, Union\n" + + "\n" + + "def f(x: Type[Union[int, str]]):\n" + + " expr = x"); + } private void doTestNoInjectedText(@NotNull String text) { myFixture.configureByText(PythonFileType.INSTANCE, text); diff --git a/python/testSrc/com/jetbrains/python/inspections/Py3TypeCheckerInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/Py3TypeCheckerInspectionTest.java index 33698bd3374e..fd89036b5c53 100644 --- a/python/testSrc/com/jetbrains/python/inspections/Py3TypeCheckerInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/Py3TypeCheckerInspectionTest.java @@ -208,4 +208,9 @@ public class Py3TypeCheckerInspectionTest extends PyTestCase { public void testTypeAndClassObjectTypesCompatibility() { doTest(); } + + // PY-20057 + public void testClassObjectTypeWithUnion() { + doTest(); + } } From fb845a9ba2a366958ea26bc4c21f8f7062cbe5a4 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Wed, 1 Mar 2017 21:49:35 +0300 Subject: [PATCH 32/33] PY-22730 Don't render Optional[Any] as Union[None, Any] Specifically, handle "weak" union type only after checking that a union can be displayed as Optional. --- .../python/documentation/PyTypeModelBuilder.java | 16 +++++++++------- .../PyTypeCheckerInspection/ClassObjectType.py | 6 +++--- .../OptionalOfBoundTypeVarInWarnings.py | 11 +++++++++++ .../com/jetbrains/python/Py3TypeTest.java | 2 +- .../testSrc/com/jetbrains/python/PyTypeTest.java | 4 ++-- .../Py3TypeCheckerInspectionTest.java | 5 +++++ 6 files changed, 31 insertions(+), 13 deletions(-) create mode 100644 python/testData/inspections/PyTypeCheckerInspection/OptionalOfBoundTypeVarInWarnings.py diff --git a/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java b/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java index 80a36ad769e4..88240e22865e 100644 --- a/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java +++ b/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java @@ -17,6 +17,7 @@ package com.jetbrains.python.documentation; import com.google.common.collect.Collections2; import com.google.common.collect.Maps; +import com.intellij.openapi.util.Ref; import com.intellij.psi.PsiElement; import com.intellij.util.containers.ContainerUtil; import com.jetbrains.python.PyNames; @@ -268,7 +269,11 @@ public class PyTypeModelBuilder { else if (type instanceof PyUnionType && allowUnions) { final PyUnionType unionType = (PyUnionType)type; final Collection unionMembers = unionType.getMembers(); - if (type instanceof PyDynamicallyEvaluatedType || PyTypeChecker.isUnknown(type, false)) { + final Ref optionalType = getOptionalType(unionType); + if (optionalType != null) { + result = new OptionalType(build(optionalType.get(), true)); + } + else if (type instanceof PyDynamicallyEvaluatedType || PyTypeChecker.isUnknown(type, false)) { result = new UnknownType(build(unionType.excludeNull(myContext), true)); } else if (unionMembers.stream().allMatch(t -> t instanceof PyClassType && ((PyClassType)t).isDefinition())) { @@ -276,10 +281,7 @@ public class PyTypeModelBuilder { result = new ClassObjectType(new OneOf(instanceTypes)); } else { - result = Optional - .ofNullable(getOptionalType(unionType)) - .map(optionalType -> new OptionalType(build(optionalType, true))) - .orElseGet(() -> new OneOf(Collections2.transform(unionMembers, t -> build(t, false)))); + result = new OneOf(Collections2.transform(unionMembers, t -> build(t, false))); } } else if (type instanceof PyCallableType && !(type instanceof PyClassLikeType)) { @@ -318,7 +320,7 @@ public class PyTypeModelBuilder { } @Nullable - private static PyType getOptionalType(@NotNull PyUnionType type) { + private static Ref getOptionalType(@NotNull PyUnionType type) { final Collection members = type.getMembers(); if (members.size() == 2) { boolean foundNone = false; @@ -332,7 +334,7 @@ public class PyTypeModelBuilder { } } if (foundNone) { - return optional; + return Ref.create(optional); } } return null; diff --git a/python/testData/inspections/PyTypeCheckerInspection/ClassObjectType.py b/python/testData/inspections/PyTypeCheckerInspection/ClassObjectType.py index e30edbc21fda..9a829ed33c98 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/ClassObjectType.py +++ b/python/testData/inspections/PyTypeCheckerInspection/ClassObjectType.py @@ -31,6 +31,6 @@ def expects_myclass_descendant_or_none(x: Optional[Type[T2]]): pass expects_myclass_descendant_or_none(MyClass) -expects_myclass_descendant_or_none(MyClass()) -expects_myclass_descendant_or_none(object) -expects_myclass_descendant_or_none(object()) \ No newline at end of file +expects_myclass_descendant_or_none(MyClass()) +expects_myclass_descendant_or_none(object) +expects_myclass_descendant_or_none(object()) \ No newline at end of file diff --git a/python/testData/inspections/PyTypeCheckerInspection/OptionalOfBoundTypeVarInWarnings.py b/python/testData/inspections/PyTypeCheckerInspection/OptionalOfBoundTypeVarInWarnings.py new file mode 100644 index 000000000000..64cee3d5ba3b --- /dev/null +++ b/python/testData/inspections/PyTypeCheckerInspection/OptionalOfBoundTypeVarInWarnings.py @@ -0,0 +1,11 @@ +from typing import Optional, TypeVar + + +T = TypeVar('T', int) + + +def expects_int_subclass_or_none(x: Optional[T]): + pass + + +expects_int_subclass_or_none('foo') \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/Py3TypeTest.java b/python/testSrc/com/jetbrains/python/Py3TypeTest.java index f948e98cd602..ff5dd8a79fe0 100644 --- a/python/testSrc/com/jetbrains/python/Py3TypeTest.java +++ b/python/testSrc/com/jetbrains/python/Py3TypeTest.java @@ -450,7 +450,7 @@ public class Py3TypeTest extends PyTestCase { // PY-20757 public void testMinElseNone() { - doTest("Union[None, Any]", + doTest("Optional[Any]", "def get_value(v):\n" + " if v:\n" + " return min(v)\n" + diff --git a/python/testSrc/com/jetbrains/python/PyTypeTest.java b/python/testSrc/com/jetbrains/python/PyTypeTest.java index c57ba0b361e1..f75e723a94d9 100644 --- a/python/testSrc/com/jetbrains/python/PyTypeTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypeTest.java @@ -1496,14 +1496,14 @@ public class PyTypeTest extends PyTestCase { // PY-20409 public void testGetFromDictWithDefaultNoneValue() { - doTest("Union[None, Any]", + doTest("Optional[Any]", "d = {}\n" + "expr = d.get(\"abc\", None)"); } // PY-20757 public void testMinOrNone() { - doTest("Union[None, Any]", + doTest("Optional[Any]", "def get_value(v):\n" + " if v:\n" + " return min(v)\n" + diff --git a/python/testSrc/com/jetbrains/python/inspections/Py3TypeCheckerInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/Py3TypeCheckerInspectionTest.java index fd89036b5c53..9fbc9fe97bc7 100644 --- a/python/testSrc/com/jetbrains/python/inspections/Py3TypeCheckerInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/Py3TypeCheckerInspectionTest.java @@ -213,4 +213,9 @@ public class Py3TypeCheckerInspectionTest extends PyTestCase { public void testClassObjectTypeWithUnion() { doTest(); } + + // PY-22730 + public void testOptionalOfBoundTypeVarInWarnings() { + doTest(); + } } From 613fc3561f3eb27b2e54a6466d09c3f2eea12110 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Mon, 6 Mar 2017 12:58:06 +0300 Subject: [PATCH 33/33] PY-20057 Check names of types before comparing directly via PyBuiltinCache to avoid expensive access to the cache in most cases when handling special types: object, type and basestring in the type checker. --- .../python/psi/types/PyTypeChecker.java | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java b/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java index 0ed38bd8b7a0..0ca884be7b24 100644 --- a/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java +++ b/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java @@ -65,13 +65,16 @@ public class PyTypeChecker { private static boolean match(@Nullable PyType expected, @Nullable PyType actual, @NotNull TypeEvalContext context, @Nullable Map substitutions, boolean recursive) { // TODO: subscriptable types?, module types?, etc. + final PyClassType expectedClassType = as(expected, PyClassType.class); + final PyClassType actualClassType = as(actual, PyClassType.class); + // Special cases: object and type - if (expected instanceof PyClassType) { - final PyBuiltinCache builtinCache = PyBuiltinCache.getInstance(((PyClassType)expected).getPyClass()); - if (expected.equals(builtinCache.getObjectType())) { + if (expectedClassType != null && ArrayUtil.contains(expectedClassType.getName(), PyNames.OBJECT, PyNames.TYPE)) { + final PyBuiltinCache builtinCache = PyBuiltinCache.getInstance(expectedClassType.getPyClass()); + if (expectedClassType.equals(builtinCache.getObjectType())) { return true; } - if (expected.equals(builtinCache.getTypeType()) && + if (expectedClassType.equals(builtinCache.getTypeType()) && actual instanceof PyInstantiableType && ((PyInstantiableType)actual).isDefinition()) { return true; } @@ -80,9 +83,9 @@ public class PyTypeChecker { && ((PyInstantiableType)expected).isDefinition() ^ ((PyInstantiableType)actual).isDefinition()) { return false; } - if (actual instanceof PyClassType) { - final PyBuiltinCache builtinCache = PyBuiltinCache.getInstance(((PyClassType)actual).getPyClass()); - if (actual.equals(builtinCache.getObjectType(PyNames.BASESTRING))) { + if (actualClassType != null && PyNames.BASESTRING.equals(actualClassType.getName())) { + final PyBuiltinCache builtinCache = PyBuiltinCache.getInstance(actualClassType.getPyClass()); + if (actualClassType.equals(builtinCache.getObjectType(PyNames.BASESTRING))) { return match(expected, builtinCache.getStrOrUnicodeType(), context, substitutions, recursive); } } @@ -153,9 +156,9 @@ public class PyTypeChecker { } return false; } - if (expected instanceof PyClassType && actual instanceof PyClassType) { - final PyClass superClass = ((PyClassType)expected).getPyClass(); - final PyClass subClass = ((PyClassType)actual).getPyClass(); + if (expectedClassType != null && actualClassType != null) { + final PyClass superClass = expectedClassType.getPyClass(); + final PyClass subClass = actualClassType.getPyClass(); if (expected instanceof PyTupleType && actual instanceof PyTupleType) { final PyTupleType superTupleType = (PyTupleType)expected; final PyTupleType subTupleType = (PyTupleType)actual; @@ -224,15 +227,15 @@ public class PyTypeChecker { else if (matchClasses(superClass, subClass, context)) { return true; } - else if (((PyClassType)actual).isDefinition() && PyNames.CALLABLE.equals(expected.getName())) { + else if (actualClassType.isDefinition() && PyNames.CALLABLE.equals(expected.getName())) { return true; } if (expected.equals(actual)) { return true; } } - if (actual instanceof PyFunctionTypeImpl && expected instanceof PyClassType) { - final PyClass superClass = ((PyClassType)expected).getPyClass(); + if (actual instanceof PyFunctionTypeImpl && expectedClassType != null) { + final PyClass superClass = expectedClassType.getPyClass(); if (PyNames.CALLABLE.equals(superClass.getName())) { return true; } @@ -248,16 +251,15 @@ public class PyTypeChecker { } return expectedStructural.getAttributeNames().containsAll(actualStructural.getAttributeNames()); } - if (expected instanceof PyStructuralType && actual instanceof PyClassType) { - final PyClassType actualClassType = (PyClassType)actual; + if (expected instanceof PyStructuralType && actualClassType != null) { if (overridesGetAttr(actualClassType.getPyClass(), context)) { return true; } final Set actualAttributes = actualClassType.getMemberNames(true, context); return actualAttributes.containsAll(((PyStructuralType)expected).getAttributeNames()); } - if (actual instanceof PyStructuralType && expected instanceof PyClassType) { - final Set expectedAttributes = ((PyClassType)expected).getMemberNames(true, context); + if (actual instanceof PyStructuralType && expectedClassType != null) { + final Set expectedAttributes = expectedClassType.getMemberNames(true, context); return expectedAttributes.containsAll(((PyStructuralType)actual).getAttributeNames()); } if (actual instanceof PyCallableType && expected instanceof PyCallableType) {