From 7c2e3d4a11a1010f71415ac3675a7e768f82986e Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Wed, 10 Dec 2014 14:44:58 +0300 Subject: [PATCH 01/18] Initial implementation of structural types We infer structural types based in the usages of the attributes of a function parameter. Currently we do it by inspecting only the body of the function and ignore passing the parameter to subsequent calls. --- .../python/psi/impl/PyNamedParameterImpl.java | 42 ++++++++- .../python/psi/types/PyStructuralType.java | 87 +++++++++++++++++++ .../UnionReturnTypes.py | 4 +- .../com/jetbrains/python/PyTypeTest.java | 7 ++ 4 files changed, 135 insertions(+), 5 deletions(-) create mode 100644 python/src/com/jetbrains/python/psi/types/PyStructuralType.java diff --git a/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java b/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java index e43a51f3de36..d0b553d2928f 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java @@ -42,9 +42,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; +import java.util.*; /** * @author yole @@ -276,6 +274,12 @@ public class PyNamedParameterImpl extends PyBaseElementImpl attributes = collectUsedAttributes(); + if (!attributes.isEmpty()) { + return new PyStructuralType(attributes, true); + } + } } } return null; @@ -286,6 +290,38 @@ public class PyNamedParameterImpl extends PyBaseElementImpl collectUsedAttributes() { + final Set result = new LinkedHashSet(); + final ScopeOwner owner = ScopeUtil.getScopeOwner(this); + final String name = getName(); + if (owner != null && name != null) { + owner.accept(new PyRecursiveElementVisitor() { + @Override + public void visitPyElement(PyElement node) { + if (node instanceof ScopeOwner && node != owner) { + return; + } + if (node instanceof PyQualifiedExpression) { + final PyQualifiedExpression expr = (PyQualifiedExpression)node; + final PyExpression qualifier = expr.getQualifier(); + if (qualifier != null) { + final PsiReference ref = qualifier.getReference(); + if (ref != null && ref.isReferenceTo(PyNamedParameterImpl.this)) { + final String attributeName = expr.getReferencedName(); + if (attributeName != null && !result.contains(attributeName)) { + result.add(attributeName); + } + } + } + } + super.visitPyElement(node); + } + }); + } + return result; + } + private static void processLocalCalls(@NotNull PyFunction function, @NotNull Processor processor) { final PsiFile file = function.getContainingFile(); final String name = function.getName(); diff --git a/python/src/com/jetbrains/python/psi/types/PyStructuralType.java b/python/src/com/jetbrains/python/psi/types/PyStructuralType.java new file mode 100644 index 000000000000..3d6271d9628b --- /dev/null +++ b/python/src/com/jetbrains/python/psi/types/PyStructuralType.java @@ -0,0 +1,87 @@ +/* + * Copyright 2000-2014 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; + +import com.intellij.codeInsight.lookup.LookupElementBuilder; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.PsiElement; +import com.intellij.util.PlatformIcons; +import com.intellij.util.ProcessingContext; +import com.jetbrains.python.psi.AccessDirection; +import com.jetbrains.python.psi.PyExpression; +import com.jetbrains.python.psi.resolve.PyResolveContext; +import com.jetbrains.python.psi.resolve.RatedResolveResult; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +/** + * @author vlan + */ +public class PyStructuralType implements PyType { + @NotNull private final Set myAttributes; + private final boolean myInferredFromUsages; + + public PyStructuralType(@NotNull Set attributes, boolean inferredFromUsages) { + myAttributes = attributes; + myInferredFromUsages = inferredFromUsages; + } + + @Nullable + @Override + public List resolveMember(@NotNull String name, + @Nullable PyExpression location, + @NotNull AccessDirection direction, + @NotNull PyResolveContext resolveContext) { + return Collections.emptyList(); + } + + @Override + public Object[] getCompletionVariants(String completionPrefix, PsiElement location, ProcessingContext context) { + final List variants = new ArrayList(); + for (String attribute : myAttributes) { + if (!attribute.equals(completionPrefix)) { + variants.add(LookupElementBuilder.create(attribute).withIcon(PlatformIcons.FIELD_ICON)); + } + } + return variants.toArray(); + } + + @Nullable + @Override + public String getName() { + return "{" + StringUtil.join(myAttributes, ", ") + "}"; + } + + @Override + public boolean isBuiltin() { + return false; + } + + @Override + public void assertValid(String message) { + } + + @Override + public String toString() { + return "PyStructuralType(" + StringUtil.join(myAttributes, ", ") + ")"; + } + + public boolean isInferredFromUsages() { + return myInferredFromUsages; + } +} diff --git a/python/testData/inspections/PyTypeCheckerInspection/UnionReturnTypes.py b/python/testData/inspections/PyTypeCheckerInspection/UnionReturnTypes.py index ba8ec2d9f536..5c2d00d3b53c 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/UnionReturnTypes.py +++ b/python/testData/inspections/PyTypeCheckerInspection/UnionReturnTypes.py @@ -1,4 +1,4 @@ -def test(): +def test(c): def f1(c): if c < 0: return [] @@ -15,7 +15,7 @@ def test(): """ :type x: int """ - x1 = f1(42) + x1 = f1(c) f2(x1) # Weaker union types f3(x1) diff --git a/python/testSrc/com/jetbrains/python/PyTypeTest.java b/python/testSrc/com/jetbrains/python/PyTypeTest.java index 7453e78613a8..fc5f846dc94e 100644 --- a/python/testSrc/com/jetbrains/python/PyTypeTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypeTest.java @@ -960,6 +960,13 @@ public class PyTypeTest extends PyTestCase { " expr = x\n"); } + public void testStructuralType() { + doTest("{foo, bar}", + "def f(x):\n" + + " x.foo + x.bar()\n" + + " expr = x\n"); + } + private static TypeEvalContext getTypeEvalContext(@NotNull PyExpression element) { return TypeEvalContext.userInitiated(element.getProject(), element.getContainingFile()).withTracing(); } From 1d1ab275afcb3256043b29916db1125be25fe0aa Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Wed, 10 Dec 2014 14:45:30 +0300 Subject: [PATCH 02/18] Fix code completion and find usages for implicitly inferred types Recently introduced structural types affect code insight for implicitly inferred types. If an expression is of a structural type that has been inferred from attributes usage then we should add implicit results we used to infer for it before the introduction of structural types. --- .../python/psi/impl/references/PyQualifiedReference.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/python/src/com/jetbrains/python/psi/impl/references/PyQualifiedReference.java b/python/src/com/jetbrains/python/psi/impl/references/PyQualifiedReference.java index cc772f32554e..7043d423ac7d 100644 --- a/python/src/com/jetbrains/python/psi/impl/references/PyQualifiedReference.java +++ b/python/src/com/jetbrains/python/psi/impl/references/PyQualifiedReference.java @@ -267,6 +267,9 @@ public class PyQualifiedReference extends PyReferenceImpl { if (qualifierType != null) { Collection variants = new ArrayList(); Collections.addAll(variants, getVariantFromHasAttr(qualifier)); + if (qualifierType instanceof PyStructuralType && ((PyStructuralType)qualifierType).isInferredFromUsages()) { + Collections.addAll(variants, getUntypedVariants()); + } if (qualifier instanceof PyQualifiedExpression) { Collection attrs = collectAssignedAttributes((PyQualifiedExpression)qualifier); for (PyExpression ex : attrs) { @@ -423,7 +426,8 @@ public class PyQualifiedReference extends PyReferenceImpl { final PyExpression qualifier = myElement.getQualifier(); if (qualifier != null) { final PyType qualifierType = resolveContext.getTypeEvalContext().getType(qualifier); - if (qualifierType == null) { + if (qualifierType == null || + (qualifierType instanceof PyStructuralType && ((PyStructuralType)qualifierType).isInferredFromUsages())) { return true; } } From 8ceb9fb02cbf63601f51d98207da4c20e9e4c5f5 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Wed, 10 Dec 2014 14:47:41 +0300 Subject: [PATCH 03/18] Suppress type and attribute inspections for structural types Currently the type checker is not capable of checking structural types. We have to disable these inspections until we update our type cheker. --- python/src/com/jetbrains/python/psi/types/PyTypeChecker.java | 3 ++- 1 file changed, 2 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 8bafd8e2c55d..6d88e9b8c96e 100644 --- a/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java +++ b/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java @@ -201,7 +201,8 @@ public class PyTypeChecker { } public static boolean isUnknown(@Nullable PyType type) { - if (type == null || type instanceof PyGenericType) { + if (type == null || type instanceof PyGenericType || + (type instanceof PyStructuralType && ((PyStructuralType)type).isInferredFromUsages())) { return true; } if (type instanceof PyUnionType) { From 83144832a75944236d0360a280f527ea3b60e3ed Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Wed, 10 Dec 2014 15:48:10 +0300 Subject: [PATCH 04/18] Infer structural types for function parameters from nested calls Using a parameter as an argument of a nested function call augments the attribute contract for that parameter with attributes used during the call. We use the available TypeEvalContext to keep the analysis of nested calls fast. --- .../python/psi/impl/PyNamedParameterImpl.java | 31 +++++++++++++++++-- .../python/psi/types/PyStructuralType.java | 4 +++ .../ActualStructuralType.py | 19 ++++++++++++ .../ExpectedStructuralType.py | 16 ++++++++++ .../StructuralTypesForNestedCalls.py | 10 ++++++ .../PyTypeCheckerInspectionTest.java | 12 +++++++ 6 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 python/testData/inspections/PyTypeCheckerInspection/ActualStructuralType.py create mode 100644 python/testData/inspections/PyTypeCheckerInspection/ExpectedStructuralType.py create mode 100644 python/testData/inspections/PyTypeCheckerInspection/StructuralTypesForNestedCalls.py diff --git a/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java b/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java index d0b553d2928f..8e3c0d2ff22c 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java @@ -275,7 +275,7 @@ public class PyNamedParameterImpl extends PyBaseElementImpl attributes = collectUsedAttributes(); + final Set attributes = collectUsedAttributes(context); if (!attributes.isEmpty()) { return new PyStructuralType(attributes, true); } @@ -291,7 +291,7 @@ public class PyNamedParameterImpl extends PyBaseElementImpl collectUsedAttributes() { + private Set collectUsedAttributes(@NotNull final TypeEvalContext context) { final Set result = new LinkedHashSet(); final ScopeOwner owner = ScopeUtil.getScopeOwner(this); final String name = getName(); @@ -314,6 +314,15 @@ public class PyNamedParameterImpl extends PyBaseElementImpl entry : mapping.getPlainMappedParams().entrySet()) { + if (entry.getKey() == element) { + return entry.getValue(); + } + } + } + } + return null; + } + private static void processLocalCalls(@NotNull PyFunction function, @NotNull Processor processor) { final PsiFile file = function.getContainingFile(); final String name = function.getName(); diff --git a/python/src/com/jetbrains/python/psi/types/PyStructuralType.java b/python/src/com/jetbrains/python/psi/types/PyStructuralType.java index 3d6271d9628b..d94de573943a 100644 --- a/python/src/com/jetbrains/python/psi/types/PyStructuralType.java +++ b/python/src/com/jetbrains/python/psi/types/PyStructuralType.java @@ -84,4 +84,8 @@ public class PyStructuralType implements PyType { public boolean isInferredFromUsages() { return myInferredFromUsages; } + + public Set getAttributeNames() { + return myAttributes; + } } diff --git a/python/testData/inspections/PyTypeCheckerInspection/ActualStructuralType.py b/python/testData/inspections/PyTypeCheckerInspection/ActualStructuralType.py new file mode 100644 index 000000000000..ea8fd806334f --- /dev/null +++ b/python/testData/inspections/PyTypeCheckerInspection/ActualStructuralType.py @@ -0,0 +1,19 @@ +def f(x): + """ + :type x: str + """ + pass + + +def g(x): + return x.lower() + + +def test(x, y): + x.upper() + f(x) + g(x) + + y.foo() + f(y) + g(y) diff --git a/python/testData/inspections/PyTypeCheckerInspection/ExpectedStructuralType.py b/python/testData/inspections/PyTypeCheckerInspection/ExpectedStructuralType.py new file mode 100644 index 000000000000..2f543d17d9b9 --- /dev/null +++ b/python/testData/inspections/PyTypeCheckerInspection/ExpectedStructuralType.py @@ -0,0 +1,16 @@ +def f(x): + return x.foo + x.bar() + + +def g(x): + return x.lower() + + +def test(x): + x.foo + f(x) + g(x) + + z = 'foo' + f(x) + g(x) diff --git a/python/testData/inspections/PyTypeCheckerInspection/StructuralTypesForNestedCalls.py b/python/testData/inspections/PyTypeCheckerInspection/StructuralTypesForNestedCalls.py new file mode 100644 index 000000000000..5e22d50690c3 --- /dev/null +++ b/python/testData/inspections/PyTypeCheckerInspection/StructuralTypesForNestedCalls.py @@ -0,0 +1,10 @@ +def f(x): + return x.foo + g(x) + + +def g(x): + return x.bar + + +def test(): + f('string') diff --git a/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java index 65ea0ed75497..984b2ca4e95d 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java @@ -242,4 +242,16 @@ public class PyTypeCheckerInspectionTest extends PyTestCase { public void testRecursiveDictAttribute() { doTest(); } + + public void testExpectedStructuralType() { + doTest(); + } + + public void testActualStructuralType() { + doTest(); + } + + public void testStructuralTypesForNestedCalls() { + doTest(); + } } From d01a1f7f6710cd572c3252822b840a5200990328 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Wed, 10 Dec 2014 16:01:05 +0300 Subject: [PATCH 05/18] Don't treat structural types as unknown Since we are going to check structural types properly, we should stop treating them as unknown. It requires a few modifications in the places where the PyTypeChecker.isUnknown() check was enough. --- .../unresolvedReference/PyUnresolvedReferencesInspection.java | 3 +++ .../python/psi/impl/references/PyQualifiedReference.java | 3 ++- python/src/com/jetbrains/python/psi/types/PyTypeChecker.java | 3 +-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java b/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java index 43bc2a925c1c..71be74b03f30 100644 --- a/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java +++ b/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java @@ -705,6 +705,9 @@ public class PyUnresolvedReferencesInspection extends PyInspection { // this almost always means that we don't know the type, so don't show an error in this case return true; } + if (type instanceof PyStructuralType && ((PyStructuralType)type).isInferredFromUsages()) { + return true; + } if (type instanceof PyImportedModuleType) { PyImportedModule module = ((PyImportedModuleType)type).getImportedModule(); if (module.resolve() == null) { diff --git a/python/src/com/jetbrains/python/psi/impl/references/PyQualifiedReference.java b/python/src/com/jetbrains/python/psi/impl/references/PyQualifiedReference.java index 7043d423ac7d..534e27e9a8e8 100644 --- a/python/src/com/jetbrains/python/psi/impl/references/PyQualifiedReference.java +++ b/python/src/com/jetbrains/python/psi/impl/references/PyQualifiedReference.java @@ -102,7 +102,8 @@ public class PyQualifiedReference extends PyReferenceImpl { } } - if (PyTypeChecker.isUnknown(qualifierType) && + if ((PyTypeChecker.isUnknown(qualifierType) || + (qualifierType instanceof PyStructuralType && ((PyStructuralType)qualifierType).isInferredFromUsages())) && myContext.allowImplicits() && canQualifyAnImplicitName(qualifier, qualifierType)) { addImplicitResolveResults(referencedName, ret); } diff --git a/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java b/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java index 6d88e9b8c96e..8bafd8e2c55d 100644 --- a/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java +++ b/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java @@ -201,8 +201,7 @@ public class PyTypeChecker { } public static boolean isUnknown(@Nullable PyType type) { - if (type == null || type instanceof PyGenericType || - (type instanceof PyStructuralType && ((PyStructuralType)type).isInferredFromUsages())) { + if (type == null || type instanceof PyGenericType) { return true; } if (type instanceof PyUnionType) { From 9e870b5a8c642c558b2040c43a975e55701f033d Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Wed, 10 Dec 2014 19:29:15 +0300 Subject: [PATCH 06/18] Don't expect the type checker to check inferred structural types against other structural types --- .../PyTypeCheckerInspection/ActualStructuralType.py | 4 ++-- .../PyTypeCheckerInspection/ExpectedStructuralType.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/python/testData/inspections/PyTypeCheckerInspection/ActualStructuralType.py b/python/testData/inspections/PyTypeCheckerInspection/ActualStructuralType.py index ea8fd806334f..a3714f1ef1ff 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/ActualStructuralType.py +++ b/python/testData/inspections/PyTypeCheckerInspection/ActualStructuralType.py @@ -15,5 +15,5 @@ def test(x, y): g(x) y.foo() - f(y) - g(y) + f(y) + g(y) diff --git a/python/testData/inspections/PyTypeCheckerInspection/ExpectedStructuralType.py b/python/testData/inspections/PyTypeCheckerInspection/ExpectedStructuralType.py index 2f543d17d9b9..d550559e1f9a 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/ExpectedStructuralType.py +++ b/python/testData/inspections/PyTypeCheckerInspection/ExpectedStructuralType.py @@ -9,8 +9,8 @@ def g(x): def test(x): x.foo f(x) - g(x) + g(x) z = 'foo' - f(x) - g(x) + f(z) + g(z) From 088337a49c47fc658e8ff9aec9269daa33237432 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Wed, 10 Dec 2014 20:50:14 +0300 Subject: [PATCH 07/18] Fixed searching for nested attributes access for structural types There was a missing check if an argument of a nested function call is really a reference to the parameter we're processing. It resulted it adding unrelated attributes to structural types. --- .../python/psi/impl/PyNamedParameterImpl.java | 13 ++++++++----- python/testSrc/com/jetbrains/python/PyTypeTest.java | 10 ++++++++++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java b/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java index 8e3c0d2ff22c..32443e54a6c9 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java @@ -315,11 +315,14 @@ public class PyNamedParameterImpl extends PyBaseElementImpl Date: Wed, 10 Dec 2014 20:57:06 +0300 Subject: [PATCH 08/18] Don't check structural types against the types from 'abc' Proper structural checking for the types from 'abc' is not implemented. --- python/src/com/jetbrains/python/psi/types/PyABCUtil.java | 4 ++++ .../IterateOverParamWithNoAttributes.py | 8 ++++++++ .../python/inspections/PyTypeCheckerInspectionTest.java | 4 ++++ 3 files changed, 16 insertions(+) create mode 100644 python/testData/inspections/PyTypeCheckerInspection/IterateOverParamWithNoAttributes.py diff --git a/python/src/com/jetbrains/python/psi/types/PyABCUtil.java b/python/src/com/jetbrains/python/psi/types/PyABCUtil.java index 562937855dd1..bfc90678a605 100644 --- a/python/src/com/jetbrains/python/psi/types/PyABCUtil.java +++ b/python/src/com/jetbrains/python/psi/types/PyABCUtil.java @@ -84,6 +84,10 @@ public class PyABCUtil { } public static boolean isSubtype(@NotNull PyType type, @NotNull String superClassName, @NotNull TypeEvalContext context) { + if (type instanceof PyStructuralType) { + // TODO: Convert abc types to structural types and check them properly + return true; + } if (type instanceof PyClassType) { final PyClassType classType = (PyClassType)type; final PyClass pyClass = classType.getPyClass(); diff --git a/python/testData/inspections/PyTypeCheckerInspection/IterateOverParamWithNoAttributes.py b/python/testData/inspections/PyTypeCheckerInspection/IterateOverParamWithNoAttributes.py new file mode 100644 index 000000000000..15bf88a198d2 --- /dev/null +++ b/python/testData/inspections/PyTypeCheckerInspection/IterateOverParamWithNoAttributes.py @@ -0,0 +1,8 @@ +def f(xs): + ys = 'string' + for x in xs: + g(ys) + + +def g(x): + return x.lower() diff --git a/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java index 984b2ca4e95d..39f882f3b977 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java @@ -254,4 +254,8 @@ public class PyTypeCheckerInspectionTest extends PyTestCase { public void testStructuralTypesForNestedCalls() { doTest(); } + + public void testIterateOverParamWithNoAttributes() { + doTest(); + } } From 291fafe5e6de88308aab273d011892aba4e25f09 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Wed, 10 Dec 2014 21:05:40 +0300 Subject: [PATCH 09/18] Initial type checker for structural types It checks structural types against other structural types and class types. --- .../python/psi/types/PyTypeChecker.java | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java b/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java index 8bafd8e2c55d..89d3432d8b5c 100644 --- a/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java +++ b/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java @@ -154,6 +154,25 @@ public class PyTypeChecker { return true; } } + if (actual instanceof PyStructuralType && ((PyStructuralType)actual).isInferredFromUsages()) { + return true; + } + if (expected instanceof PyStructuralType && actual instanceof PyStructuralType) { + final PyStructuralType expectedStructural = (PyStructuralType)expected; + final PyStructuralType actualStructural = (PyStructuralType)actual; + if (expectedStructural.isInferredFromUsages()) { + return true; + } + return expectedStructural.getAttributeNames().containsAll(actualStructural.getAttributeNames()); + } + if (expected instanceof PyStructuralType && actual instanceof PyClassType) { + final Set actualAttributes = getClassAttributes(((PyClassType)actual).getPyClass(), true); + return actualAttributes.containsAll(((PyStructuralType)expected).getAttributeNames()); + } + if (actual instanceof PyStructuralType && expected instanceof PyClassType) { + final Set expectedAttributes = getClassAttributes(((PyClassType)expected).getPyClass(), true); + return expectedAttributes.containsAll(((PyStructuralType)actual).getAttributeNames()); + } if (actual instanceof PyCallableType && expected instanceof PyCallableType) { final PyCallableType expectedCallable = (PyCallableType)expected; final PyCallableType actualCallable = (PyCallableType)actual; @@ -180,6 +199,26 @@ public class PyTypeChecker { return matchNumericTypes(expected, actual); } + @NotNull + private static Set getClassAttributes(@NotNull PyClass cls, boolean inherited) { + final Set attributes = new HashSet(); + for (PyFunction function : cls.getMethods(false)) { + attributes.add(function.getName()); + } + for (PyTargetExpression instanceAttribute : cls.getInstanceAttributes()) { + attributes.add(instanceAttribute.getName()); + } + for (PyTargetExpression classAttribute : cls.getClassAttributes()) { + attributes.add(classAttribute.getName()); + } + if (inherited) { + for (PyClass ancestor : cls.getAncestorClasses()) { + attributes.addAll(getClassAttributes(ancestor, false)); + } + } + return attributes; + } + private static boolean matchNumericTypes(PyType expected, PyType actual) { final String superName = expected.getName(); final String subName = actual.getName(); From 4ff3ecb752f8531e465a1fb43488344ec85af619 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Thu, 11 Dec 2014 18:39:33 +0300 Subject: [PATCH 10/18] Fixed callable inspection for usages-based structural types We suppose that a structural type inferred from the usages of attributes is callable, it's just another aspect of its attribute-based contract. We don't add '__call__' to the type at the moment, but it might be a better solution. Some other special methods like '__getitem__' are processed correctly because they are resolvable references to appropriate methods. --- python/src/com/jetbrains/python/psi/types/PyTypeChecker.java | 3 +++ .../PyCallingNonCallableInspection/structuralType.py | 3 +++ .../inspections/PyCallingNonCallableInspectionTest.java | 4 ++++ 3 files changed, 10 insertions(+) create mode 100644 python/testData/inspections/PyCallingNonCallableInspection/structuralType.py diff --git a/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java b/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java index 89d3432d8b5c..dccd8a19b414 100644 --- a/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java +++ b/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java @@ -559,6 +559,9 @@ public class PyTypeChecker { else if (type instanceof PyCallableType) { return ((PyCallableType) type).isCallable(); } + else if (type instanceof PyStructuralType && ((PyStructuralType)type).isInferredFromUsages()) { + return true; + } return false; } diff --git a/python/testData/inspections/PyCallingNonCallableInspection/structuralType.py b/python/testData/inspections/PyCallingNonCallableInspection/structuralType.py new file mode 100644 index 000000000000..1cf114c6a6c3 --- /dev/null +++ b/python/testData/inspections/PyCallingNonCallableInspection/structuralType.py @@ -0,0 +1,3 @@ +def f(x): + x.foo + x.bar + x(0) diff --git a/python/testSrc/com/jetbrains/python/inspections/PyCallingNonCallableInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyCallingNonCallableInspectionTest.java index b4ae36c8e668..d0c758bde64b 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyCallingNonCallableInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyCallingNonCallableInspectionTest.java @@ -99,6 +99,10 @@ public class PyCallingNonCallableInspectionTest extends PyTestCase { doTest(); } + public void testStructuralType() { + doTest(); + } + private void doTest() { setLanguageLevel(LanguageLevel.PYTHON27); try { From 229c254abc613d7183f16a75b94fe4663209abcc Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Fri, 12 Dec 2014 15:58:23 +0300 Subject: [PATCH 11/18] Fixed collecting wrong '__contains__' attribute for structural types The 'x in y' expression implies that 'y' has the '__contains__' attribute, while our references in qualified expressions tell that the 'x' references the name '__contains__'. In order to fix this we have to treat the operators with a receiver on the right side of the expression differently. --- .../psi-api/src/com/jetbrains/python/PyNames.java | 2 +- .../python/psi/impl/PyNamedParameterImpl.java | 14 +++++++++----- .../jetbrains/python/psi/types/PyTypeChecker.java | 2 +- .../testSrc/com/jetbrains/python/PyTypeTest.java | 9 +++++++++ 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/python/psi-api/src/com/jetbrains/python/PyNames.java b/python/psi-api/src/com/jetbrains/python/PyNames.java index b82576428c3c..21e5be632d59 100644 --- a/python/psi-api/src/com/jetbrains/python/PyNames.java +++ b/python/psi-api/src/com/jetbrains/python/PyNames.java @@ -472,7 +472,7 @@ public class PyNames { } public static boolean isRightOperatorName(@Nullable String name) { - return name != null && name.matches("__r[a-z]+__"); + return name != null && (name.matches("__r[a-z]+__") || CONTAINS.equals(name)); } /** diff --git a/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java b/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java index 32443e54a6c9..222a98df8a5f 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java @@ -306,11 +306,15 @@ public class PyNamedParameterImpl extends PyBaseElementImpl Date: Fri, 12 Dec 2014 19:07:16 +0300 Subject: [PATCH 12/18] Don't add untyped completion variants for structural types They mostly duplicate each other for scopes of local functions. For structural types we add only implicit variants obtained from a name-based class guess. The test data for seenMembers has been updated in order not to clash with structural types completion. --- .../impl/references/PyQualifiedReference.java | 44 ++++++++++++++----- python/testData/completion/seenMembers.py | 8 ++-- .../completion/structuralType.after.py | 4 ++ python/testData/completion/structuralType.py | 4 ++ .../python/PythonCompletionTest.java | 4 ++ 5 files changed, 49 insertions(+), 15 deletions(-) create mode 100644 python/testData/completion/structuralType.after.py create mode 100644 python/testData/completion/structuralType.py diff --git a/python/src/com/jetbrains/python/psi/impl/references/PyQualifiedReference.java b/python/src/com/jetbrains/python/psi/impl/references/PyQualifiedReference.java index 534e27e9a8e8..65b15fad3458 100644 --- a/python/src/com/jetbrains/python/psi/impl/references/PyQualifiedReference.java +++ b/python/src/com/jetbrains/python/psi/impl/references/PyQualifiedReference.java @@ -43,7 +43,10 @@ import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.PyBuiltinCache; import com.jetbrains.python.psi.impl.PyImportedModule; import com.jetbrains.python.psi.impl.ResolveResultList; -import com.jetbrains.python.psi.resolve.*; +import com.jetbrains.python.psi.resolve.ImplicitResolveResult; +import com.jetbrains.python.psi.resolve.PyResolveContext; +import com.jetbrains.python.psi.resolve.QualifiedNameFinder; +import com.jetbrains.python.psi.resolve.RatedResolveResult; import com.jetbrains.python.psi.search.PyProjectScopeBuilder; import com.jetbrains.python.psi.stubs.PyClassNameIndexInsensitive; import com.jetbrains.python.psi.stubs.PyFunctionNameIndex; @@ -265,11 +268,14 @@ public class PyQualifiedReference extends PyReferenceImpl { ProcessingContext ctx = new ProcessingContext(); final Set namesAlready = new HashSet(); ctx.put(PyType.CTX_NAMES, namesAlready); + final Collection variants = new ArrayList(); if (qualifierType != null) { - Collection variants = new ArrayList(); Collections.addAll(variants, getVariantFromHasAttr(qualifier)); if (qualifierType instanceof PyStructuralType && ((PyStructuralType)qualifierType).isInferredFromUsages()) { - Collections.addAll(variants, getUntypedVariants()); + final PyClassType guessedType = guessClassTypeByName(); + if (guessedType != null) { + Collections.addAll(variants, getTypeCompletionVariants(myElement, guessedType)); + } } if (qualifier instanceof PyQualifiedExpression) { Collection attrs = collectAssignedAttributes((PyQualifiedExpression)qualifier); @@ -299,7 +305,16 @@ public class PyQualifiedReference extends PyReferenceImpl { return qualifierType.getCompletionVariants(element.getName(), element, ctx); } } - return getUntypedVariants(); + else { + final PyClassType guessedType = guessClassTypeByName(); + if (guessedType != null) { + Collections.addAll(variants, getTypeCompletionVariants(myElement, guessedType)); + } + if (qualifier instanceof PyReferenceExpression) { + Collections.addAll(variants, collectSeenMembers(qualifier.getText())); + } + return variants.toArray(); + } } private Object[] getVariantFromHasAttr(PyExpression qualifier) { @@ -319,7 +334,8 @@ public class PyQualifiedReference extends PyReferenceImpl { return variants.toArray(); } - private Object[] getUntypedVariants() { + @Nullable + private PyClassType guessClassTypeByName() { final PyExpression qualifierElement = myElement.getQualifier(); if (qualifierElement instanceof PyReferenceExpression) { PyReferenceExpression qualifier = (PyReferenceExpression)qualifierElement; @@ -328,13 +344,11 @@ public class PyQualifiedReference extends PyReferenceImpl { Collection classes = PyClassNameIndexInsensitive.find(className, getElement().getProject()); classes = filterByImports(classes, myElement.getContainingFile()); if (classes.size() == 1) { - final PyClassType classType = new PyClassTypeImpl(classes.iterator().next(), false); - return getTypeCompletionVariants(myElement, classType); + return new PyClassTypeImpl(classes.iterator().next(), false); } } - return collectSeenMembers(qualifier.getText()); } - return ArrayUtil.EMPTY_OBJECT_ARRAY; + return null; } private static Collection filterByImports(Collection classes, PsiFile containingFile) { @@ -362,6 +376,16 @@ public class PyQualifiedReference extends PyReferenceImpl { @Override public void visitPyReferenceExpression(PyReferenceExpression node) { super.visitPyReferenceExpression(node); + visitPyQualifiedExpression(node); + } + + @Override + public void visitPyTargetExpression(PyTargetExpression node) { + super.visitPyTargetExpression(node); + visitPyQualifiedExpression(node); + } + + private void visitPyQualifiedExpression(PyQualifiedExpression node) { if (node != myElement) { final PyExpression qualifier = node.getQualifier(); if (qualifier != null && qualifier.getText().equals(text)) { @@ -377,7 +401,7 @@ public class PyQualifiedReference extends PyReferenceImpl { for (String member : members) { results.add(AutoCompletionPolicy.NEVER_AUTOCOMPLETE.applyPolicy(LookupElementBuilder.create(member))); } - return results.toArray(new Object[results.size()]); + return ArrayUtil.toObjectArray(results); } private static Collection collectAssignedAttributes(PyQualifiedExpression qualifier) { diff --git a/python/testData/completion/seenMembers.py b/python/testData/completion/seenMembers.py index ba886b709c3e..8dc6336d489f 100644 --- a/python/testData/completion/seenMembers.py +++ b/python/testData/completion/seenMembers.py @@ -1,5 +1,3 @@ -def foo(element): - print(element.children) - -def bar(element): - element.ch \ No newline at end of file +for region in regions: + region.children = [] + region.ch diff --git a/python/testData/completion/structuralType.after.py b/python/testData/completion/structuralType.after.py new file mode 100644 index 000000000000..777f1d32c519 --- /dev/null +++ b/python/testData/completion/structuralType.after.py @@ -0,0 +1,4 @@ +def f(x): + x.foo + x.bar + x.bar diff --git a/python/testData/completion/structuralType.py b/python/testData/completion/structuralType.py new file mode 100644 index 000000000000..95de6aace20e --- /dev/null +++ b/python/testData/completion/structuralType.py @@ -0,0 +1,4 @@ +def f(x): + x.foo + x.bar + x.b diff --git a/python/testSrc/com/jetbrains/python/PythonCompletionTest.java b/python/testSrc/com/jetbrains/python/PythonCompletionTest.java index 68765f72eee8..487b43ef370c 100644 --- a/python/testSrc/com/jetbrains/python/PythonCompletionTest.java +++ b/python/testSrc/com/jetbrains/python/PythonCompletionTest.java @@ -753,4 +753,8 @@ public class PythonCompletionTest extends PyTestCase { assertNotNull(variants); assertEmpty(variants); } + + public void testStructuralType() { + doTest(); + } } From 41226d7f4b1811dba5f9c1481930818f446d2993 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Mon, 15 Dec 2014 17:08:54 +0300 Subject: [PATCH 13/18] Use separate type eval context for code completion We used to have context.allowLocalUsages() for permitting a code analysis to guess types from file-local function calls. This analysis mode came into conflict with the new structural types, since they both provided types for function parameters. Another problem is that the user used to see the types inferred from local calls in the documentation pop-up and it was not clear where did they come from. The solution is to introduce a new type eval context for code completion only named TypeEvalContext.codeCompletion() and allow file-local call based type inference only in this context. --- .../python/psi/types/TypeEvalConstraints.java | 9 ++++-- .../python/psi/types/TypeEvalContext.java | 32 ++++++++++++------- .../PyDictKeyNamesCompletionContributor.java | 2 +- .../python/psi/impl/PyNamedParameterImpl.java | 4 +-- .../KeywordArgumentCompletionUtil.java | 2 +- .../impl/references/PyImportReference.java | 3 +- .../impl/references/PyQualifiedReference.java | 2 +- .../resolve/CompletionVariantsProcessor.java | 2 +- .../python/psi/types/PyClassTypeImpl.java | 2 +- .../python/psi/types/PyFunctionType.java | 2 +- .../com/jetbrains/python/PyTypeTest.java | 21 ++++++++---- 11 files changed, 52 insertions(+), 29 deletions(-) diff --git a/python/psi-api/src/com/jetbrains/python/psi/types/TypeEvalConstraints.java b/python/psi-api/src/com/jetbrains/python/psi/types/TypeEvalConstraints.java index ebeaf366a6ad..f3a94b336de2 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/types/TypeEvalConstraints.java +++ b/python/psi-api/src/com/jetbrains/python/psi/types/TypeEvalConstraints.java @@ -30,14 +30,17 @@ import org.jetbrains.annotations.Nullable; class TypeEvalConstraints { final boolean myAllowDataFlow; final boolean myAllowStubToAST; + final boolean myAllowCallContext; @Nullable final PsiFile myOrigin; /** * @see com.jetbrains.python.psi.types.TypeEvalContext */ - TypeEvalConstraints(final boolean allowDataFlow, final boolean allowStubToAST, @Nullable final PsiFile origin) { + TypeEvalConstraints(final boolean allowDataFlow, final boolean allowStubToAST, final boolean allowCallContext, + @Nullable final PsiFile origin) { myAllowDataFlow = allowDataFlow; myAllowStubToAST = allowStubToAST; + myAllowCallContext = allowCallContext; myOrigin = origin; } @@ -50,6 +53,7 @@ class TypeEvalConstraints { if (myAllowDataFlow != that.myAllowDataFlow) return false; if (myAllowStubToAST != that.myAllowStubToAST) return false; + if (myAllowCallContext != that.myAllowCallContext) return false; if (myOrigin != null ? !myOrigin.equals(that.myOrigin) : that.myOrigin != null) return false; return true; @@ -60,11 +64,12 @@ class TypeEvalConstraints { int result = (myAllowDataFlow ? 1 : 0); result = 31 * result + (myAllowStubToAST ? 1 : 0); result = 31 * result + (myOrigin != null ? myOrigin.hashCode() : 0); + result = 31 * result + (myAllowCallContext ? 1 : 0); return result; } @Override public String toString() { - return String.format("TypeEvalConstraints(%b, %b, %s)", myAllowDataFlow, myAllowStubToAST, myOrigin); + return String.format("TypeEvalConstraints(%b, %b, %b, %s)", myAllowDataFlow, myAllowStubToAST, myAllowCallContext, myOrigin); } } diff --git a/python/psi-api/src/com/jetbrains/python/psi/types/TypeEvalContext.java b/python/psi-api/src/com/jetbrains/python/psi/types/TypeEvalContext.java index 002a566eeb39..54af2a23e3c4 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/types/TypeEvalContext.java +++ b/python/psi-api/src/com/jetbrains/python/psi/types/TypeEvalContext.java @@ -65,8 +65,8 @@ public class TypeEvalContext { } }; - private TypeEvalContext(boolean allowDataFlow, boolean allowStubToAST, @Nullable PsiFile origin) { - myConstraints = new TypeEvalConstraints(allowDataFlow, allowStubToAST, origin); + private TypeEvalContext(boolean allowDataFlow, boolean allowStubToAST, boolean allowCallContext, @Nullable PsiFile origin) { + myConstraints = new TypeEvalConstraints(allowDataFlow, allowStubToAST, allowCallContext, origin); } @Override @@ -83,17 +83,29 @@ public class TypeEvalContext { return myConstraints.myAllowDataFlow || element.getContainingFile() == myConstraints.myOrigin; } - public boolean allowLocalUsages(@NotNull PsiElement element) { - return myConstraints.myAllowStubToAST && myConstraints.myAllowDataFlow && element.getContainingFile() == myConstraints.myOrigin; + public boolean allowCallContext(@NotNull PsiElement element) { + return myConstraints.myAllowCallContext && element.getContainingFile() == myConstraints.myOrigin; + } + + /** + * Create a context for code completion. + *

+ * It is as detailed as {@link TypeEvalContext#userInitiated(Project, PsiFile)}, but allows inferring types based on the context in which + * the analyzed code was called or may be called. Since this is basically guesswork, the results should be used only for code completion. + */ + public static TypeEvalContext codeCompletion(@NotNull final Project project, @Nullable final PsiFile origin) { + return CACHE.getContext(project, new TypeEvalContext(true, true, true, origin)); } /** * Create the most detailed type evaluation context for user-initiated actions. *

- * Should be used for code completion, go to definition, find usages, refactorings, documentation. + * Should be used go to definition, find usages, refactorings, documentation. + *

+ * For code completion see {@link TypeEvalContext#codeCompletion(Project, PsiFile)}. */ public static TypeEvalContext userInitiated(@NotNull final Project project, @Nullable final PsiFile origin) { - return CACHE.getContext(project, new TypeEvalContext(true, true, origin)); + return CACHE.getContext(project, new TypeEvalContext(true, true, false, origin)); } /** @@ -103,20 +115,18 @@ public class TypeEvalContext { * Inspections should not create a new type evaluation context. They should re-use the context of the inspection session. */ public static TypeEvalContext codeAnalysis(@NotNull final Project project, @Nullable final PsiFile origin) { - return CACHE.getContext(project, new TypeEvalContext(false, false, origin)); + return CACHE.getContext(project, new TypeEvalContext(false, false, false, origin)); } /** * Create the most shallow type evaluation context for code insight purposes when other more detailed contexts are not available. * It's use should be minimized. - *

- *

* * @param project pass project here to enable cache. Pass null if you do not have any project. * Always do your best to pass project here: it increases performance! */ public static TypeEvalContext codeInsightFallback(@Nullable final Project project) { - final TypeEvalContext anchor = new TypeEvalContext(false, false, null); + final TypeEvalContext anchor = new TypeEvalContext(false, false, false, null); if (project != null) { return CACHE.getContext(project, anchor); } @@ -129,7 +139,7 @@ public class TypeEvalContext { * Should be used only when normal code insight context is not enough for getting good results. */ public static TypeEvalContext deepCodeInsight(@NotNull final Project project) { - return CACHE.getContext(project, new TypeEvalContext(false, true, null)); + return CACHE.getContext(project, new TypeEvalContext(false, true, false, null)); } public TypeEvalContext withTracing() { diff --git a/python/src/com/jetbrains/python/codeInsight/completion/PyDictKeyNamesCompletionContributor.java b/python/src/com/jetbrains/python/codeInsight/completion/PyDictKeyNamesCompletionContributor.java index e5b2dcc2dc65..efdb192ae6f4 100644 --- a/python/src/com/jetbrains/python/codeInsight/completion/PyDictKeyNamesCompletionContributor.java +++ b/python/src/com/jetbrains/python/codeInsight/completion/PyDictKeyNamesCompletionContributor.java @@ -129,7 +129,7 @@ public class PyDictKeyNamesCompletionContributor extends CompletionContributor { if (callee == null) return; final String name = callee.getText(); if ("dict".equals(name)) { - final TypeEvalContext context = TypeEvalContext.userInitiated(callee.getProject(), callee.getContainingFile()); + final TypeEvalContext context = TypeEvalContext.codeCompletion(callee.getProject(), callee.getContainingFile()); final PyType type = context.getType(dictConstructor); if (type != null && type.isBuiltin()) { final PyArgumentList list = dictConstructor.getArgumentList(); diff --git a/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java b/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java index 222a98df8a5f..921c43e2b866 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java @@ -244,8 +244,8 @@ public class PyNamedParameterImpl extends PyBaseElementImpl types = new ArrayList(); processLocalCalls(func, new Processor() { @Override diff --git a/python/src/com/jetbrains/python/psi/impl/references/KeywordArgumentCompletionUtil.java b/python/src/com/jetbrains/python/psi/impl/references/KeywordArgumentCompletionUtil.java index e6edd70a516e..8736e54766ac 100644 --- a/python/src/com/jetbrains/python/psi/impl/references/KeywordArgumentCompletionUtil.java +++ b/python/src/com/jetbrains/python/psi/impl/references/KeywordArgumentCompletionUtil.java @@ -66,7 +66,7 @@ public class KeywordArgumentCompletionUtil { visited.add(def); boolean needSelf = def.getContainingClass() != null && def.getModifier() != PyFunction.Modifier.STATICMETHOD; final KwArgParameterCollector collector = new KwArgParameterCollector(needSelf, ret); - final TypeEvalContext context = TypeEvalContext.userInitiated(def.getProject(), def.getContainingFile()); + final TypeEvalContext context = TypeEvalContext.codeCompletion(def.getProject(), def.getContainingFile()); final List parameters = PyUtil.getParameters(def, context); for (PyParameter parameter : parameters) { parameter.accept(collector); diff --git a/python/src/com/jetbrains/python/psi/impl/references/PyImportReference.java b/python/src/com/jetbrains/python/psi/impl/references/PyImportReference.java index be7921bd6b22..13100e9c0600 100644 --- a/python/src/com/jetbrains/python/psi/impl/references/PyImportReference.java +++ b/python/src/com/jetbrains/python/psi/impl/references/PyImportReference.java @@ -97,7 +97,8 @@ public class PyImportReference extends PyReferenceImpl { } PyExpression qualifier = myElement.getQualifier(); - final TypeEvalContext context = TypeEvalContext.userInitiated(myElement.getProject(), CompletionUtil.getOriginalOrSelf(myElement).getContainingFile()); + final TypeEvalContext context = TypeEvalContext.codeCompletion(myElement.getProject(), + CompletionUtil.getOriginalOrSelf(myElement).getContainingFile()); if (qualifier != null) { // qualifier's type must be module, it should know how to complete PyType type = context.getType(qualifier); diff --git a/python/src/com/jetbrains/python/psi/impl/references/PyQualifiedReference.java b/python/src/com/jetbrains/python/psi/impl/references/PyQualifiedReference.java index 65b15fad3458..070ee1165c36 100644 --- a/python/src/com/jetbrains/python/psi/impl/references/PyQualifiedReference.java +++ b/python/src/com/jetbrains/python/psi/impl/references/PyQualifiedReference.java @@ -264,7 +264,7 @@ public class PyQualifiedReference extends PyReferenceImpl { } final PyQualifiedExpression element = CompletionUtil.getOriginalOrSelf(myElement); - PyType qualifierType = TypeEvalContext.userInitiated(element.getProject(), element.getContainingFile()).getType(qualifier); + PyType qualifierType = TypeEvalContext.codeCompletion(element.getProject(), element.getContainingFile()).getType(qualifier); ProcessingContext ctx = new ProcessingContext(); final Set namesAlready = new HashSet(); ctx.put(PyType.CTX_NAMES, namesAlready); diff --git a/python/src/com/jetbrains/python/psi/resolve/CompletionVariantsProcessor.java b/python/src/com/jetbrains/python/psi/resolve/CompletionVariantsProcessor.java index ddc8f939970b..7f8799fde597 100644 --- a/python/src/com/jetbrains/python/psi/resolve/CompletionVariantsProcessor.java +++ b/python/src/com/jetbrains/python/psi/resolve/CompletionVariantsProcessor.java @@ -68,7 +68,7 @@ public class CompletionVariantsProcessor extends VariantsProcessor { !isSingleArgDecoratorCall(myContext, (PyFunction)object)) { final Project project = ((PyFunction)object).getProject(); item = item.withInsertHandler(PyFunctionInsertHandler.INSTANCE); - final TypeEvalContext context = TypeEvalContext.userInitiated(project, myContext != null ? myContext.getContainingFile() : null); + final TypeEvalContext context = TypeEvalContext.codeCompletion(project, myContext != null ? myContext.getContainingFile() : null); final List parameters = PyUtil.getParameters((PyFunction)object, context); final String params = StringUtil.join(parameters, new Function() { @Override diff --git a/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java b/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java index 25de66311ee1..a7b0d548ea23 100644 --- a/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java +++ b/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java @@ -418,7 +418,7 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType { CompletionUtil.getOriginalOrSelf(location) .getContainingFile() : null; - final TypeEvalContext typeEvalContext = TypeEvalContext.userInitiated(myClass.getProject(), origin); + final TypeEvalContext typeEvalContext = TypeEvalContext.codeCompletion(myClass.getProject(), origin); addInheritedMembers(prefix, location, namesAlready, context, ret, typeEvalContext); // from providers diff --git a/python/src/com/jetbrains/python/psi/types/PyFunctionType.java b/python/src/com/jetbrains/python/psi/types/PyFunctionType.java index c393dfb219b1..fde4e18fabe4 100644 --- a/python/src/com/jetbrains/python/psi/types/PyFunctionType.java +++ b/python/src/com/jetbrains/python/psi/types/PyFunctionType.java @@ -88,7 +88,7 @@ public class PyFunctionType implements PyCallableType { @Override public Object[] getCompletionVariants(String completionPrefix, PsiElement location, ProcessingContext context) { - final TypeEvalContext typeEvalContext = TypeEvalContext.userInitiated(location.getProject(), location.getContainingFile()); + final TypeEvalContext typeEvalContext = TypeEvalContext.codeCompletion(location.getProject(), location.getContainingFile()); final PyClassType delegate; if (location instanceof PyReferenceExpression) { delegate = selectFakeType(((PyReferenceExpression)location).getQualifier(), typeEvalContext); diff --git a/python/testSrc/com/jetbrains/python/PyTypeTest.java b/python/testSrc/com/jetbrains/python/PyTypeTest.java index 8a3b9ae9c934..6d7737bc31d4 100644 --- a/python/testSrc/com/jetbrains/python/PyTypeTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypeTest.java @@ -589,13 +589,14 @@ public class PyTypeTest extends PyTestCase { } public void testParameterFromUsages() { - doTest("int | str | unknown", - "def foo(bar):\n" + - " expr = bar\n" + - "def use_foo(x):\n" + - " foo(x)\n" + - " foo(3)\n" + - " foo('bar')\n"); + final String text = "def foo(bar):\n" + + " expr = bar\n" + + "def use_foo(x):\n" + + " foo(x)\n" + + " foo(3)\n" + + " foo('bar')\n"; + final PyExpression expr = parseExpr(text); + doTest("int | str | unknown", expr, TypeEvalContext.codeCompletion(expr.getProject(), expr.getContainingFile())); } public void testUpperBoundGeneric() { @@ -995,6 +996,12 @@ public class PyTypeTest extends PyTestCase { return myFixture.findElementByText("expr", PyExpression.class); } + private static void doTest(final String expectedType, final PyExpression expr, final TypeEvalContext context) { + PyType actual = context.getType(expr); + final String actualType = PythonDocumentationProvider.getTypeName(actual, context); + assertEquals(expectedType, actualType); + } + private void doTest(final String expectedType, final String text) { PyExpression expr = parseExpr(text); TypeEvalContext context = getTypeEvalContext(expr); From adacfa3dd2d05f7c05592c4bf5a99c348124ee8b Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Mon, 15 Dec 2014 19:49:25 +0300 Subject: [PATCH 14/18] Don't collect attributes conditionally accessed inside 'if' statements It's a workaround for false positives in type checking if a function expects its parameter to be of various types depending on some condition we couldn't analyze statically. Another possible source of false positives is try-except statements, but they are less likely, so we ignore them at the moment hoping that benefits from collecting the attributes of a parameter inside try-except will beat losses in the amount of false positives. --- .../python/psi/impl/PyNamedParameterImpl.java | 14 ++++++++++++++ .../testSrc/com/jetbrains/python/PyTypeTest.java | 10 ++++++++++ 2 files changed, 24 insertions(+) diff --git a/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java b/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java index 921c43e2b866..f5355dc56bd9 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java @@ -333,6 +333,20 @@ public class PyNamedParameterImpl extends PyBaseElementImpl None", + "def f(x):\n" + + " if isinstance(x, str):\n" + + " x.lower()\n" + + " x.foo\n" + + "\n" + + "expr = f\n"); + } + private static TypeEvalContext getTypeEvalContext(@NotNull PyExpression element) { return TypeEvalContext.userInitiated(element.getProject(), element.getContainingFile()).withTracing(); } From 0501431357fdf5ce3327402052ea9d7c0fc03f92 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Mon, 15 Dec 2014 20:01:23 +0300 Subject: [PATCH 15/18] Moved overridesGetAttr to PyTypeChecker --- .../PyUnresolvedReferencesInspection.java | 28 +------------------ .../python/psi/types/PyTypeChecker.java | 26 +++++++++++++++++ 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java b/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java index 71be74b03f30..781ad21bb88b 100644 --- a/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java +++ b/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java @@ -64,7 +64,6 @@ import com.jetbrains.python.psi.impl.references.PyOperatorReference; import com.jetbrains.python.psi.resolve.ImportedResolveResult; import com.jetbrains.python.psi.resolve.PyResolveContext; import com.jetbrains.python.psi.resolve.QualifiedNameFinder; -import com.jetbrains.python.psi.resolve.RatedResolveResult; import com.jetbrains.python.psi.types.*; import com.jetbrains.python.sdk.PythonSdkType; import com.jetbrains.python.sdk.skeletons.PySkeletonRefresher; @@ -723,7 +722,7 @@ public class PyUnresolvedReferencesInspection extends PyInspection { } if (type instanceof PyClassTypeImpl) { PyClass cls = ((PyClassType)type).getPyClass(); - if (overridesGetAttr(cls, myTypeEvalContext)) { + if (PyTypeChecker.overridesGetAttr(cls, myTypeEvalContext)) { return true; } if (cls.findProperty(name, true) != null) { @@ -913,31 +912,6 @@ public class PyUnresolvedReferencesInspection extends PyInspection { return callExpression != null && node == callExpression.getCallee(); } - @Nullable - private static PsiElement resolveClassMember(@NotNull PyClass cls, @NotNull String name, @NotNull TypeEvalContext context) { - final PyType type = context.getType(cls); - if (type != null) { - final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(context); - final List results = type.resolveMember(name, null, AccessDirection.READ, resolveContext); - if (results != null && !results.isEmpty()) { - return results.get(0).getElement(); - } - } - return null; - } - - private static boolean overridesGetAttr(@NotNull PyClass cls, @NotNull TypeEvalContext context) { - PsiElement method = resolveClassMember(cls, PyNames.GETATTR, context); - if (method != null) { - return true; - } - method = resolveClassMember(cls, PyNames.GETATTRIBUTE, context); - if (method != null && !PyBuiltinCache.getInstance(cls).isBuiltin(method)) { - return true; - } - return false; - } - private static void addPluginQuickFixes(PsiReference reference, final List actions) { for (PyUnresolvedReferenceQuickFixProvider provider : Extensions.getExtensions(PyUnresolvedReferenceQuickFixProvider.EP_NAME)) { provider.registerQuickFixes(reference, new Consumer() { diff --git a/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java b/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java index 2b77533606e6..122c29987b8c 100644 --- a/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java +++ b/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java @@ -21,6 +21,7 @@ import com.intellij.psi.PsiReference; import com.intellij.psi.ResolveResult; import com.jetbrains.python.PyNames; import com.jetbrains.python.psi.*; +import com.jetbrains.python.psi.impl.PyBuiltinCache; import com.jetbrains.python.psi.resolve.PyResolveContext; import com.jetbrains.python.psi.resolve.RatedResolveResult; import org.jetbrains.annotations.NotNull; @@ -566,6 +567,31 @@ public class PyTypeChecker { return false; } + public static boolean overridesGetAttr(@NotNull PyClass cls, @NotNull TypeEvalContext context) { + PsiElement method = resolveClassMember(cls, PyNames.GETATTR, context); + if (method != null) { + return true; + } + method = resolveClassMember(cls, PyNames.GETATTRIBUTE, context); + if (method != null && !PyBuiltinCache.getInstance(cls).isBuiltin(method)) { + return true; + } + return false; + } + + @Nullable + private static PsiElement resolveClassMember(@NotNull PyClass cls, @NotNull String name, @NotNull TypeEvalContext context) { + final PyType type = context.getType(cls); + if (type != null) { + final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(context); + final List results = type.resolveMember(name, null, AccessDirection.READ, resolveContext); + if (results != null && !results.isEmpty()) { + return results.get(0).getElement(); + } + } + return null; + } + public static class AnalyzeCallResults { @NotNull private final Callable myCallable; @Nullable private final PyExpression myReceiver; From 87442c402f632fd622a7f1001f21dd3daec747a7 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Mon, 15 Dec 2014 20:07:27 +0300 Subject: [PATCH 16/18] Don't check class types with '__getattr__' against structural types Classes with '__getattr__' and '__getattribute__' may have any attributes defined by the user at runtime. --- .../python/psi/types/PyTypeChecker.java | 6 +++++- .../GetAttributeAgainstStructuralType.py | 21 +++++++++++++++++++ .../PyTypeCheckerInspectionTest.java | 4 ++++ 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 python/testData/inspections/PyTypeCheckerInspection/GetAttributeAgainstStructuralType.py diff --git a/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java b/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java index 122c29987b8c..cec92a9514b3 100644 --- a/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java +++ b/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java @@ -167,7 +167,11 @@ public class PyTypeChecker { return expectedStructural.getAttributeNames().containsAll(actualStructural.getAttributeNames()); } if (expected instanceof PyStructuralType && actual instanceof PyClassType) { - final Set actualAttributes = getClassAttributes(((PyClassType)actual).getPyClass(), true); + final PyClass cls = ((PyClassType)actual).getPyClass(); + if (overridesGetAttr(cls, context)) { + return true; + } + final Set actualAttributes = getClassAttributes(cls, true); return actualAttributes.containsAll(((PyStructuralType)expected).getAttributeNames()); } if (actual instanceof PyStructuralType && expected instanceof PyClassType) { diff --git a/python/testData/inspections/PyTypeCheckerInspection/GetAttributeAgainstStructuralType.py b/python/testData/inspections/PyTypeCheckerInspection/GetAttributeAgainstStructuralType.py new file mode 100644 index 000000000000..52249d9de37f --- /dev/null +++ b/python/testData/inspections/PyTypeCheckerInspection/GetAttributeAgainstStructuralType.py @@ -0,0 +1,21 @@ +def f(x): + return x.foo + + +class C(object): + def __getattribute__(self, item): + pass + + +class D(object): + def __getattr__(self, item): + pass + + +class E(object): + pass + + +f(C()) +f(D()) +f(E()) diff --git a/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java index f820f26f0d2f..3445236b0139 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java @@ -263,4 +263,8 @@ public class PyTypeCheckerInspectionTest extends PyTestCase { public void testIterateOverParamWithNoAttributes() { doTest(); } + + public void testGetAttributeAgainstStructuralType() { + doTest(); + } } From 1fd35202a2760a22e73d524093a71e6b8d78df71 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Tue, 16 Dec 2014 16:06:30 +0300 Subject: [PATCH 17/18] Renamed local variables --- .../inspections/PyTypeCheckerInspection.java | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/python/src/com/jetbrains/python/inspections/PyTypeCheckerInspection.java b/python/src/com/jetbrains/python/inspections/PyTypeCheckerInspection.java index 389e4efd15f3..61e473da5c23 100644 --- a/python/src/com/jetbrains/python/inspections/PyTypeCheckerInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyTypeCheckerInspection.java @@ -108,26 +108,25 @@ public class PyTypeCheckerInspection extends PyInspection { } @Nullable - private String checkTypes(@Nullable PyType superType, @Nullable PyType subType, @Nullable PsiElement node, + private String checkTypes(@Nullable PyType expected, @Nullable PyType actual, @Nullable PsiElement node, @NotNull TypeEvalContext context, @NotNull Map substitutions) { - if (subType != null && superType != null) { - if (!PyTypeChecker.match(superType, subType, context, substitutions)) { - final String superName = PythonDocumentationProvider.getTypeName(superType, context); - String expected = String.format("'%s'", superName); - final boolean hasGenerics = PyTypeChecker.hasGenerics(superType, context); + if (actual != null && expected != null) { + if (!PyTypeChecker.match(expected, actual, context, substitutions)) { + final String expectedName = PythonDocumentationProvider.getTypeName(expected, context); + String quotedExpectedName = String.format("'%s'", expectedName); + final boolean hasGenerics = PyTypeChecker.hasGenerics(expected, context); ProblemHighlightType highlightType = ProblemHighlightType.GENERIC_ERROR_OR_WARNING; if (hasGenerics) { - final PyType subst = PyTypeChecker.substitute(superType, substitutions, context); - if (subst != null) { - expected = String.format("'%s' (matched generic type '%s')", - PythonDocumentationProvider.getTypeName(subst, context), - superName); + final PyType substitute = PyTypeChecker.substitute(expected, substitutions, context); + if (substitute != null) { + quotedExpectedName = String.format("'%s' (matched generic type '%s')", + PythonDocumentationProvider.getTypeName(substitute, context), + expectedName); highlightType = ProblemHighlightType.WEAK_WARNING; } } - final String msg = String.format("Expected type %s, got '%s' instead", - expected, - PythonDocumentationProvider.getTypeName(subType, context)); + final String actualName = PythonDocumentationProvider.getTypeName(actual, context); + final String msg = String.format("Expected type %s, got '%s' instead", quotedExpectedName, actualName); registerProblem(node, msg, highlightType); return msg; } From adc2d3859ba142dd933442f401bc992db741e4ac Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Tue, 16 Dec 2014 17:17:18 +0300 Subject: [PATCH 18/18] Better error messages for checking structural types The new error message mentions the attributes that the actual type is misssing instead of just telling what the expected and actual types are. --- .../inspections/PyTypeCheckerInspection.java | 37 ++++++++++++++++++- .../python/psi/types/PyTypeChecker.java | 2 +- .../ExpectedStructuralType.py | 2 +- .../GetAttributeAgainstStructuralType.py | 2 +- .../StructuralTypesForNestedCalls.py | 2 +- 5 files changed, 40 insertions(+), 5 deletions(-) diff --git a/python/src/com/jetbrains/python/inspections/PyTypeCheckerInspection.java b/python/src/com/jetbrains/python/inspections/PyTypeCheckerInspection.java index 61e473da5c23..50230b05c795 100644 --- a/python/src/com/jetbrains/python/inspections/PyTypeCheckerInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyTypeCheckerInspection.java @@ -15,13 +15,16 @@ */ package com.jetbrains.python.inspections; +import com.google.common.collect.Sets; import com.intellij.codeInspection.LocalInspectionToolSession; import com.intellij.codeInspection.ProblemHighlightType; import com.intellij.codeInspection.ProblemsHolder; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElementVisitor; +import com.intellij.util.Function; import com.intellij.util.containers.hash.LinkedHashMap; import com.jetbrains.python.PyNames; import com.jetbrains.python.documentation.PythonDocumentationProvider; @@ -32,6 +35,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Map; +import java.util.Set; /** * @author vlan @@ -126,7 +130,27 @@ public class PyTypeCheckerInspection extends PyInspection { } } final String actualName = PythonDocumentationProvider.getTypeName(actual, context); - final String msg = String.format("Expected type %s, got '%s' instead", quotedExpectedName, actualName); + String msg= String.format("Expected type %s, got '%s' instead", quotedExpectedName, actualName); + if (expected instanceof PyStructuralType) { + final Set expectedAttributes = ((PyStructuralType)expected).getAttributeNames(); + final Set actualAttributes = getAttributes(actual); + if (actualAttributes != null) { + final Sets.SetView missingAttributes = Sets.difference(expectedAttributes, actualAttributes); + if (missingAttributes.size() == 1) { + msg = String.format("Type '%s' doesn't have expected attribute '%s'", actualName, missingAttributes.iterator().next()); + } + else { + msg = String.format("Type '%s' doesn't have expected attributes %s", + actualName, + StringUtil.join(missingAttributes, new Function() { + @Override + public String fun(String s) { + return String.format("'%s'", s); + } + }, ", ")); + } + } + } registerProblem(node, msg, highlightType); return msg; } @@ -135,6 +159,17 @@ public class PyTypeCheckerInspection extends PyInspection { } } + @Nullable + private static Set getAttributes(@NotNull PyType type) { + if (type instanceof PyStructuralType) { + return ((PyStructuralType)type).getAttributeNames(); + } + else if (type instanceof PyClassType) { + return PyTypeChecker.getClassAttributes(((PyClassType)type).getPyClass(), true); + } + return null; + } + @Override public void inspectionFinished(@NotNull LocalInspectionToolSession session, @NotNull ProblemsHolder problemsHolder) { if (LOG.isDebugEnabled()) { diff --git a/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java b/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java index cec92a9514b3..d5c75466ddd0 100644 --- a/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java +++ b/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java @@ -205,7 +205,7 @@ public class PyTypeChecker { } @NotNull - private static Set getClassAttributes(@NotNull PyClass cls, boolean inherited) { + public static Set getClassAttributes(@NotNull PyClass cls, boolean inherited) { final Set attributes = new HashSet(); for (PyFunction function : cls.getMethods(false)) { attributes.add(function.getName()); diff --git a/python/testData/inspections/PyTypeCheckerInspection/ExpectedStructuralType.py b/python/testData/inspections/PyTypeCheckerInspection/ExpectedStructuralType.py index d550559e1f9a..1080906218ab 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/ExpectedStructuralType.py +++ b/python/testData/inspections/PyTypeCheckerInspection/ExpectedStructuralType.py @@ -12,5 +12,5 @@ def test(x): g(x) z = 'foo' - f(z) + f(z) g(z) diff --git a/python/testData/inspections/PyTypeCheckerInspection/GetAttributeAgainstStructuralType.py b/python/testData/inspections/PyTypeCheckerInspection/GetAttributeAgainstStructuralType.py index 52249d9de37f..617df26bcbcf 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/GetAttributeAgainstStructuralType.py +++ b/python/testData/inspections/PyTypeCheckerInspection/GetAttributeAgainstStructuralType.py @@ -18,4 +18,4 @@ class E(object): f(C()) f(D()) -f(E()) +f(E()) diff --git a/python/testData/inspections/PyTypeCheckerInspection/StructuralTypesForNestedCalls.py b/python/testData/inspections/PyTypeCheckerInspection/StructuralTypesForNestedCalls.py index 5e22d50690c3..1e4ae234bbdf 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/StructuralTypesForNestedCalls.py +++ b/python/testData/inspections/PyTypeCheckerInspection/StructuralTypesForNestedCalls.py @@ -7,4 +7,4 @@ def g(x): def test(): - f('string') + f('string')