From 3128b7f70a2771f5d129e277970b4e8d9b5f581d Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Tue, 30 Jul 2013 18:48:54 +0400 Subject: [PATCH 01/14] Fixed type name for bounded generic types --- .../src/com/jetbrains/python/psi/types/PyGenericType.java | 2 +- .../src/com/jetbrains/python/psi/types/PyUnionType.java | 8 ++++---- .../inspections/PyTypeCheckerInspection/BoundedGeneric.py | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/python/src/com/jetbrains/python/psi/types/PyGenericType.java b/python/src/com/jetbrains/python/psi/types/PyGenericType.java index 20e1986872dc..bf0c5565a14c 100644 --- a/python/src/com/jetbrains/python/psi/types/PyGenericType.java +++ b/python/src/com/jetbrains/python/psi/types/PyGenericType.java @@ -39,7 +39,7 @@ public class PyGenericType implements PyType { @NotNull @Override public String getName() { - return myBound != null ? myName + " (" + myBound.getName() + ")" : myName; + return myBound != null ? myName + " <= " + myBound.getName() : myName; } @Override diff --git a/python/src/com/jetbrains/python/psi/types/PyUnionType.java b/python/src/com/jetbrains/python/psi/types/PyUnionType.java index 2d56e8f291b2..c69beca25529 100644 --- a/python/src/com/jetbrains/python/psi/types/PyUnionType.java +++ b/python/src/com/jetbrains/python/psi/types/PyUnionType.java @@ -53,11 +53,11 @@ public class PyUnionType implements PyType { } public String getName() { - return "one of (" + StringUtil.join(myMembers, new NullableFunction() { - public String fun(PyType pyType) { - return pyType == null ? "unknown" : pyType.getName(); + return StringUtil.join(myMembers, new NullableFunction() { + public String fun(PyType type) { + return type != null ? type.getName() : null; } - }, ", ") + ")"; + }, " | "); } /** diff --git a/python/testData/inspections/PyTypeCheckerInspection/BoundedGeneric.py b/python/testData/inspections/PyTypeCheckerInspection/BoundedGeneric.py index 3d79685273a2..a89e7816ea87 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/BoundedGeneric.py +++ b/python/testData/inspections/PyTypeCheckerInspection/BoundedGeneric.py @@ -1,12 +1,12 @@ def test(): def f(x): """ - :type x: T <= int or str + :type x: T <= int | str :rtype: T """ pass x = f(10) y = f('foo') - z = f([]) + z = f([]) return x + y From 375938e79af5171704b0251ff02918240e34fae6 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Tue, 30 Jul 2013 20:20:42 +0400 Subject: [PATCH 02/14] Use '|' operator instead of 'or' for union types (PY-10423) --- .../python/documentation/PyTypeModelBuilder.java | 10 ++++------ .../PyTypeCheckerInspection/BoundedGeneric.py | 2 +- .../PyTypeCheckerInspection/BuiltinNumeric.py | 2 +- .../PyTypeCheckerInspection/DictGenerics.py | 2 +- .../PyTypeCheckerInspection/DictLiterals.py | 6 +++--- .../inspections/PyTypeCheckerInspection/Generator.py | 6 +++--- .../PyTypeCheckerInspection/GenericUserClasses.py | 2 +- .../PyTypeCheckerInspection/GenericUserFunctions.py | 2 +- .../inspections/PyTypeCheckerInspection/ListTuple.py | 2 +- .../PyTypeCheckerInspection/LocalTypeResolve.py | 2 +- .../PyTypeCheckerInspection/MapReturnElementType.py | 4 ++-- .../PropertyAndFactoryFunction.py | 2 +- .../PyTypeCheckerInspection/RightOperators.py | 2 +- .../PyTypeCheckerInspection/StringInteger.py | 6 +++--- .../PyTypeCheckerInspection/UnionReturnTypes.py | 2 +- .../testData/quickdoc/HoverOverControlFlowUnion.html | 2 +- python/testData/quickdoc/Variable.html | 2 +- 17 files changed, 27 insertions(+), 29 deletions(-) diff --git a/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java b/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java index c92dfc33bb92..663951be6809 100644 --- a/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java +++ b/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java @@ -260,17 +260,15 @@ public class PyTypeModelBuilder { add("..."); return; } - add("one of ("); - processListCommaSeparated(oneOf.oneOfTypes); - add(")"); + processListCommaSeparated(oneOf.oneOfTypes, " | "); myDepth--; } - private void processListCommaSeparated(Collection list) { + private void processListCommaSeparated(Collection list, String separator) { boolean first = true; for (TypeModel t : list) { if (!first) { - add(", "); + add(separator); } else { first = false; @@ -310,7 +308,7 @@ public class PyTypeModelBuilder { return; } add("("); - processListCommaSeparated(function.parameters); + processListCommaSeparated(function.parameters, ", "); add(") -> "); function.returnType.accept(this); add("\n"); diff --git a/python/testData/inspections/PyTypeCheckerInspection/BoundedGeneric.py b/python/testData/inspections/PyTypeCheckerInspection/BoundedGeneric.py index a89e7816ea87..7fe25baa2b2a 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/BoundedGeneric.py +++ b/python/testData/inspections/PyTypeCheckerInspection/BoundedGeneric.py @@ -9,4 +9,4 @@ def test(): x = f(10) y = f('foo') z = f([]) - return x + y + return x + y diff --git a/python/testData/inspections/PyTypeCheckerInspection/BuiltinNumeric.py b/python/testData/inspections/PyTypeCheckerInspection/BuiltinNumeric.py index 27b7db5cf6e8..41a6d03b9ae9 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/BuiltinNumeric.py +++ b/python/testData/inspections/PyTypeCheckerInspection/BuiltinNumeric.py @@ -9,4 +9,4 @@ def test(): u'bar') pow(False, True) round(False, - 'foo') + 'foo') diff --git a/python/testData/inspections/PyTypeCheckerInspection/DictGenerics.py b/python/testData/inspections/PyTypeCheckerInspection/DictGenerics.py index 46f6ff06ac25..028614f594ff 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/DictGenerics.py +++ b/python/testData/inspections/PyTypeCheckerInspection/DictGenerics.py @@ -5,4 +5,4 @@ def test_dict_generics(d): xs = d.items() d2 = dict(xs) for k, v in d2.items(): - print k + v + print k + v diff --git a/python/testData/inspections/PyTypeCheckerInspection/DictLiterals.py b/python/testData/inspections/PyTypeCheckerInspection/DictLiterals.py index f6d79460c4e4..533bccc2235f 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/DictLiterals.py +++ b/python/testData/inspections/PyTypeCheckerInspection/DictLiterals.py @@ -1,8 +1,8 @@ def test(): xs = {'foo': 1, 'bar': 2} for v in xs.values(): - print(v + None) + print(v + None) for k in xs.keys(): - print(k + None) + print(k + None) for k in xs: - print(k + None) + print(k + None) diff --git a/python/testData/inspections/PyTypeCheckerInspection/Generator.py b/python/testData/inspections/PyTypeCheckerInspection/Generator.py index b14de72cb06d..8bd8b28f2780 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/Generator.py +++ b/python/testData/inspections/PyTypeCheckerInspection/Generator.py @@ -89,9 +89,9 @@ def test(): f_8(gen(11)), f_9(gen(11)), f_10(gen(11)), - f_11(gen(11)), - f_12(gen(11)), - f_13(gen(11)), + f_11(gen(11)), + f_12(gen(11)), + f_13(gen(11)), f_14(gen(11)), f_15(gen(11)), f_15('foo'.split('o')), diff --git a/python/testData/inspections/PyTypeCheckerInspection/GenericUserClasses.py b/python/testData/inspections/PyTypeCheckerInspection/GenericUserClasses.py index d8a5472a0408..c114d411997a 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/GenericUserClasses.py +++ b/python/testData/inspections/PyTypeCheckerInspection/GenericUserClasses.py @@ -20,7 +20,7 @@ def test_generic_user_class(): self.x = value c = User1(10) - print(c.get() + 'foo') + print(c.get() + 'foo') c.put(14) c.put('foo') diff --git a/python/testData/inspections/PyTypeCheckerInspection/GenericUserFunctions.py b/python/testData/inspections/PyTypeCheckerInspection/GenericUserFunctions.py index f2a882768c90..46368e5c7a7c 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/GenericUserFunctions.py +++ b/python/testData/inspections/PyTypeCheckerInspection/GenericUserFunctions.py @@ -38,7 +38,7 @@ def test(): result = f1([1, 2, 3]) print(result) - print(result + 'foo') + print(result + 'foo') f2(1, ['foo'], 'bar') diff --git a/python/testData/inspections/PyTypeCheckerInspection/ListTuple.py b/python/testData/inspections/PyTypeCheckerInspection/ListTuple.py index 95306f40c8eb..01ffe6f0d442 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/ListTuple.py +++ b/python/testData/inspections/PyTypeCheckerInspection/ListTuple.py @@ -7,5 +7,5 @@ def f(spam, eggs): def test(): - f([1, 2, 3], + f([1, 2, 3], (False, 2, '')) diff --git a/python/testData/inspections/PyTypeCheckerInspection/LocalTypeResolve.py b/python/testData/inspections/PyTypeCheckerInspection/LocalTypeResolve.py index 9d86b619c827..01bf260199ce 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/LocalTypeResolve.py +++ b/python/testData/inspections/PyTypeCheckerInspection/LocalTypeResolve.py @@ -5,4 +5,4 @@ def test(): c = C() x = c.f() y = x - return y + 'foo' + return y + 'foo' diff --git a/python/testData/inspections/PyTypeCheckerInspection/MapReturnElementType.py b/python/testData/inspections/PyTypeCheckerInspection/MapReturnElementType.py index 39ea03805b54..85f08d6dfc18 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/MapReturnElementType.py +++ b/python/testData/inspections/PyTypeCheckerInspection/MapReturnElementType.py @@ -1,5 +1,5 @@ def test(): xs = map(lambda x: x + 1, [1, 2, 3]) - print('foo' + xs[0]) + print('foo' + xs[0]) ys = map(str, iter([1, 2, 3])) - print(1 + ys[0], 'bar' + ys[1]) + print(1 + ys[0], 'bar' + ys[1]) diff --git a/python/testData/inspections/PyTypeCheckerInspection/PropertyAndFactoryFunction.py b/python/testData/inspections/PyTypeCheckerInspection/PropertyAndFactoryFunction.py index 008864cede03..8cf0bfcce4a9 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/PropertyAndFactoryFunction.py +++ b/python/testData/inspections/PyTypeCheckerInspection/PropertyAndFactoryFunction.py @@ -7,4 +7,4 @@ def f(): return C() def test(): - f().foo + 1 + f().foo + 1 diff --git a/python/testData/inspections/PyTypeCheckerInspection/RightOperators.py b/python/testData/inspections/PyTypeCheckerInspection/RightOperators.py index 5a90ed0890ce..3e58c273927c 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/RightOperators.py +++ b/python/testData/inspections/PyTypeCheckerInspection/RightOperators.py @@ -5,5 +5,5 @@ class C(object): def test_right_operators(): o = C() xs = [ - o * [], + o * [], ] diff --git a/python/testData/inspections/PyTypeCheckerInspection/StringInteger.py b/python/testData/inspections/PyTypeCheckerInspection/StringInteger.py index a551543eddec..ce379047b8ed 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/StringInteger.py +++ b/python/testData/inspections/PyTypeCheckerInspection/StringInteger.py @@ -1,8 +1,8 @@ def test(): print('foo' + 'bar') print(2 + 3) - print('foo' + 3) - print(3 + 'foo') + print('foo' + 3) + print(3 + 'foo') print('foo' + 'bar' * 3) print('foo' + 3 * 'bar') - print('foo' + 2 * 3) + print('foo' + 2 * 3) diff --git a/python/testData/inspections/PyTypeCheckerInspection/UnionReturnTypes.py b/python/testData/inspections/PyTypeCheckerInspection/UnionReturnTypes.py index 76888e9a902f..ba8ec2d9f536 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/UnionReturnTypes.py +++ b/python/testData/inspections/PyTypeCheckerInspection/UnionReturnTypes.py @@ -17,7 +17,7 @@ def test(): """ x1 = f1(42) f2(x1) # Weaker union types - f3(x1) + f3(x1) f2(x1.count('')) f3(x1.count('')) diff --git a/python/testData/quickdoc/HoverOverControlFlowUnion.html b/python/testData/quickdoc/HoverOverControlFlowUnion.html index 056d6ba8ce3c..5b9ce6b5ca02 100644 --- a/python/testData/quickdoc/HoverOverControlFlowUnion.html +++ b/python/testData/quickdoc/HoverOverControlFlowUnion.html @@ -1,2 +1,2 @@ variable "x" -Inferred type: one of (str, int) +Inferred type: str | int diff --git a/python/testData/quickdoc/Variable.html b/python/testData/quickdoc/Variable.html index 62d6e80474b1..eac55f8d9c13 100644 --- a/python/testData/quickdoc/Variable.html +++ b/python/testData/quickdoc/Variable.html @@ -1 +1 @@ -Assigned to y

Inferred type: one of (int, str)
\ No newline at end of file +Assigned to y

Inferred type: int | str
From a7b015bb89215dcc72eafca6f9c9091c9889069a Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Wed, 31 Jul 2013 15:43:39 +0400 Subject: [PATCH 03/14] Use '[]' operator instead of 'of' for parameteried types (PY-10423) --- .../documentation/PyTypeModelBuilder.java | 28 ++++++++++++++----- .../PyTypeCheckerInspection/Generator.py | 22 +++++++-------- .../GenericUserFunctions.py | 2 +- .../PyTypeCheckerInspection/ListTuple.py | 2 +- 4 files changed, 34 insertions(+), 20 deletions(-) diff --git a/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java b/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java index 663951be6809..1f9983514e9d 100644 --- a/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java +++ b/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java @@ -14,7 +14,9 @@ import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; import java.util.Collection; +import java.util.List; import java.util.Map; import static com.jetbrains.python.documentation.DocumentationBuilderKit.$; @@ -62,11 +64,11 @@ public class PyTypeModelBuilder { static class CollectionOf extends TypeModel { private String collectionName; - private TypeModel elementType; + private List elementTypes; - private CollectionOf(String collectionName, TypeModel elementType) { + private CollectionOf(String collectionName, List elementTypes) { this.collectionName = collectionName; - this.elementType = elementType; + this.elementTypes = elementTypes; } @Override @@ -145,8 +147,19 @@ public class PyTypeModelBuilder { if (type instanceof PyCollectionType) { final String name = type.getName(); final PyType elementType = ((PyCollectionType)type).getElementType(myContext); - if (elementType != null) { - result = new CollectionOf(name, build(elementType, true)); + final List elementTypes = new ArrayList(); + if (elementType instanceof PyTupleType) { + final PyTupleType tupleType = (PyTupleType)elementType; + final int n = tupleType.getElementCount(); + for (int i = 0; i < n; i++) { + elementTypes.add(build(tupleType.getElementType(i), true)); + } + } + else if (elementType != null) { + elementTypes.add(build(elementType, true)); + } + if (!elementTypes.isEmpty()) { + result = new CollectionOf(name, elementTypes); } } else if (type instanceof PyUnionType && allowUnions) { @@ -288,8 +301,9 @@ public class PyTypeModelBuilder { return; } addType(collectionOf.collectionName); - add(" of "); - collectionOf.elementType.accept(this); + add("["); + processListCommaSeparated(collectionOf.elementTypes, ", "); + add("]"); myDepth--; } diff --git a/python/testData/inspections/PyTypeCheckerInspection/Generator.py b/python/testData/inspections/PyTypeCheckerInspection/Generator.py index 8bd8b28f2780..870b822aad6b 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/Generator.py +++ b/python/testData/inspections/PyTypeCheckerInspection/Generator.py @@ -79,19 +79,19 @@ def test(): return xs return [ ''.join(gen(10)), - f_1(gen(11)), - f_2(gen(11)), - f_3(gen(11)), - f_4(gen(11)), - f_5(gen(11)), - f_6(gen(11)), - f_7(gen(11)), - f_8(gen(11)), + f_1(gen(11)), + f_2(gen(11)), + f_3(gen(11)), + f_4(gen(11)), + f_5(gen(11)), + f_6(gen(11)), + f_7(gen(11)), + f_8(gen(11)), f_9(gen(11)), f_10(gen(11)), - f_11(gen(11)), - f_12(gen(11)), - f_13(gen(11)), + f_11(gen(11)), + f_12(gen(11)), + f_13(gen(11)), f_14(gen(11)), f_15(gen(11)), f_15('foo'.split('o')), diff --git a/python/testData/inspections/PyTypeCheckerInspection/GenericUserFunctions.py b/python/testData/inspections/PyTypeCheckerInspection/GenericUserFunctions.py index 46368e5c7a7c..143b3e17cfde 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/GenericUserFunctions.py +++ b/python/testData/inspections/PyTypeCheckerInspection/GenericUserFunctions.py @@ -40,7 +40,7 @@ def test(): print(result) print(result + 'foo') - f2(1, ['foo'], 'bar') + f2(1, ['foo'], 'bar') result = f3(1, 'foo', True) f4(result) diff --git a/python/testData/inspections/PyTypeCheckerInspection/ListTuple.py b/python/testData/inspections/PyTypeCheckerInspection/ListTuple.py index 01ffe6f0d442..c6146b10612e 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/ListTuple.py +++ b/python/testData/inspections/PyTypeCheckerInspection/ListTuple.py @@ -7,5 +7,5 @@ def f(spam, eggs): def test(): - f([1, 2, 3], + f([1, 2, 3], (False, 2, '')) From a16af3a991bb0c44ed9feaa47b8b0118d7386439 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Wed, 31 Jul 2013 15:57:39 +0400 Subject: [PATCH 04/14] Extracted constant for unknown type --- python/psi-api/src/com/jetbrains/python/PyNames.java | 1 + .../com/jetbrains/python/debugger/PySignatureUtil.java | 3 ++- .../python/documentation/PyTypeModelBuilder.java | 9 ++++----- .../jetbrains/python/psi/types/PyCallableTypeImpl.java | 5 +++-- .../python/psi/types/PyDynamicallyEvaluatedType.java | 3 ++- .../src/com/jetbrains/python/psi/types/PyTupleType.java | 2 +- .../src/com/jetbrains/python/psi/types/PyTypeParser.java | 2 +- 7 files changed, 14 insertions(+), 11 deletions(-) diff --git a/python/psi-api/src/com/jetbrains/python/PyNames.java b/python/psi-api/src/com/jetbrains/python/PyNames.java index 49111f5391db..531a9fdf0289 100644 --- a/python/psi-api/src/com/jetbrains/python/PyNames.java +++ b/python/psi-api/src/com/jetbrains/python/PyNames.java @@ -134,6 +134,7 @@ public class PyNames { public static final String NOT_IMPLEMENTED_ERROR = "NotImplementedError"; + public static final String UNKNOWN_TYPE = "unknown"; /** * Contains all known predefined names of "__foo__" form. diff --git a/python/src/com/jetbrains/python/debugger/PySignatureUtil.java b/python/src/com/jetbrains/python/debugger/PySignatureUtil.java index e44b0573b0f6..fbacc612ef07 100644 --- a/python/src/com/jetbrains/python/debugger/PySignatureUtil.java +++ b/python/src/com/jetbrains/python/debugger/PySignatureUtil.java @@ -4,6 +4,7 @@ import com.google.common.base.Function; import com.google.common.collect.Collections2; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiElement; +import com.jetbrains.python.PyNames; import com.jetbrains.python.psi.PyClass; import com.jetbrains.python.psi.PyFunction; import com.jetbrains.python.psi.types.PyClassType; @@ -49,7 +50,7 @@ public class PySignatureUtil { return type.getName(); } else { - return "unknown"; + return PyNames.UNKNOWN_TYPE; } } diff --git a/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java b/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java index 1f9983514e9d..9399b75f9201 100644 --- a/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java +++ b/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java @@ -5,12 +5,12 @@ import com.google.common.collect.Collections2; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.intellij.psi.PsiElement; +import com.jetbrains.python.PyNames; import com.jetbrains.python.psi.PyFunction; import com.jetbrains.python.psi.PyNamedParameter; import com.jetbrains.python.psi.PyParameter; import com.jetbrains.python.psi.types.*; import com.jetbrains.python.toolbox.ChainIterable; -import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -26,7 +26,6 @@ import static com.jetbrains.python.documentation.DocumentationBuilderKit.combUp; * @author traff */ public class PyTypeModelBuilder { - @NonNls static final String UNKNOWN = "unknown"; private final Map myVisited = Maps.newHashMap(); private final TypeEvalContext myContext; @@ -139,7 +138,7 @@ public class PyTypeModelBuilder { return evaluated; } if (myVisited.containsKey(type)) { //already evaluating? - return type != null ? _(type.getName()) : _(UNKNOWN); + return type != null ? _(type.getName()) : _(PyNames.UNKNOWN_TYPE); } myVisited.put(type, null); //mark as evaluating @@ -177,7 +176,7 @@ public class PyTypeModelBuilder { } } if (result == null) { - result = type != null ? _(type.getName()) : _(UNKNOWN); + result = type != null ? _(type.getName()) : _(PyNames.UNKNOWN_TYPE); } myVisited.put(type, result); return result; @@ -193,7 +192,7 @@ public class PyTypeModelBuilder { final PyNamedParameter np = p.getAsNamed(); if (np != null) { TypeModel paramType = - _(UNKNOWN); + _(PyNames.UNKNOWN_TYPE); final PyType t = myContext.getType(np); if (t != null) { paramType = build(t, true); diff --git a/python/src/com/jetbrains/python/psi/types/PyCallableTypeImpl.java b/python/src/com/jetbrains/python/psi/types/PyCallableTypeImpl.java index e1c51ed92ef8..9a2359ed2f70 100644 --- a/python/src/com/jetbrains/python/psi/types/PyCallableTypeImpl.java +++ b/python/src/com/jetbrains/python/psi/types/PyCallableTypeImpl.java @@ -3,6 +3,7 @@ package com.jetbrains.python.psi.types; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.Function; 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.PyQualifiedExpression; @@ -64,11 +65,11 @@ public class PyCallableTypeImpl implements PyCallableType { new Function() { @Override public String fun(PyType type) { - return type != null ? type.getName() : "unknown"; + return type != null ? type.getName() : PyNames.UNKNOWN_TYPE; } }, ", "), - myReturnType != null ? myReturnType.getName() : "unknown"); + myReturnType != null ? myReturnType.getName() : PyNames.UNKNOWN_TYPE); } @Override diff --git a/python/src/com/jetbrains/python/psi/types/PyDynamicallyEvaluatedType.java b/python/src/com/jetbrains/python/psi/types/PyDynamicallyEvaluatedType.java index 837a861be096..a8b83501373f 100644 --- a/python/src/com/jetbrains/python/psi/types/PyDynamicallyEvaluatedType.java +++ b/python/src/com/jetbrains/python/psi/types/PyDynamicallyEvaluatedType.java @@ -1,5 +1,6 @@ package com.jetbrains.python.psi.types; +import com.jetbrains.python.PyNames; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; @@ -34,6 +35,6 @@ public class PyDynamicallyEvaluatedType extends PyUnionType { @Override public String getName() { PyType res = excludeNull(); - return res != null ? res.getName() : "unknown"; + return res != null ? res.getName() : PyNames.UNKNOWN_TYPE; } } diff --git a/python/src/com/jetbrains/python/psi/types/PyTupleType.java b/python/src/com/jetbrains/python/psi/types/PyTupleType.java index c4157f1d3ce4..715cb5262f71 100644 --- a/python/src/com/jetbrains/python/psi/types/PyTupleType.java +++ b/python/src/com/jetbrains/python/psi/types/PyTupleType.java @@ -42,7 +42,7 @@ public class PyTupleType extends PyClassTypeImpl implements PySubscriptableType return "(" + StringUtil.join(myElementTypes, new Function() { @Nullable public String fun(PyType pyType) { - return pyType == null ? "unknown" : pyType.getName(); + return pyType == null ? PyNames.UNKNOWN_TYPE : pyType.getName(); } }, ",") + ")"; } diff --git a/python/src/com/jetbrains/python/psi/types/PyTypeParser.java b/python/src/com/jetbrains/python/psi/types/PyTypeParser.java index 8af0776f1825..1e57982053b9 100644 --- a/python/src/com/jetbrains/python/psi/types/PyTypeParser.java +++ b/python/src/com/jetbrains/python/psi/types/PyTypeParser.java @@ -449,7 +449,7 @@ public class PyTypeParser { final String name = token.getText().toString(); final TextRange range = token.getRange(); - if ("unknown".equals(name)) { + if (PyNames.UNKNOWN_TYPE.equals(name)) { return EMPTY_RESULT; } else if (PyNames.NONE.equals(name)) { From 6ba29011ff8d9e61a7eb6d9ceaca39645c25466b Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Wed, 31 Jul 2013 16:03:36 +0400 Subject: [PATCH 05/14] Spaces in tuple types --- python/src/com/jetbrains/python/psi/types/PyTupleType.java | 2 +- .../PyCallingNonCallableInspection/tupleNonCallable.py | 2 +- .../inspections/PyTypeCheckerInspection/GenericUserFunctions.py | 2 +- .../testData/inspections/PyTypeCheckerInspection/ListTuple.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/python/src/com/jetbrains/python/psi/types/PyTupleType.java b/python/src/com/jetbrains/python/psi/types/PyTupleType.java index 715cb5262f71..533116388006 100644 --- a/python/src/com/jetbrains/python/psi/types/PyTupleType.java +++ b/python/src/com/jetbrains/python/psi/types/PyTupleType.java @@ -44,7 +44,7 @@ public class PyTupleType extends PyClassTypeImpl implements PySubscriptableType public String fun(PyType pyType) { return pyType == null ? PyNames.UNKNOWN_TYPE : pyType.getName(); } - }, ",") + ")"; + }, ", ") + ")"; } @Override diff --git a/python/testData/inspections/PyCallingNonCallableInspection/tupleNonCallable.py b/python/testData/inspections/PyCallingNonCallableInspection/tupleNonCallable.py index a4e04009336a..3433394d67c6 100644 --- a/python/testData/inspections/PyCallingNonCallableInspection/tupleNonCallable.py +++ b/python/testData/inspections/PyCallingNonCallableInspection/tupleNonCallable.py @@ -1 +1 @@ -(1,2)() +(1,2)() diff --git a/python/testData/inspections/PyTypeCheckerInspection/GenericUserFunctions.py b/python/testData/inspections/PyTypeCheckerInspection/GenericUserFunctions.py index 143b3e17cfde..65590ceca6fe 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/GenericUserFunctions.py +++ b/python/testData/inspections/PyTypeCheckerInspection/GenericUserFunctions.py @@ -43,4 +43,4 @@ def test(): f2(1, ['foo'], 'bar') result = f3(1, 'foo', True) - f4(result) + f4(result) diff --git a/python/testData/inspections/PyTypeCheckerInspection/ListTuple.py b/python/testData/inspections/PyTypeCheckerInspection/ListTuple.py index c6146b10612e..2dfd05166f70 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/ListTuple.py +++ b/python/testData/inspections/PyTypeCheckerInspection/ListTuple.py @@ -8,4 +8,4 @@ def f(spam, eggs): def test(): f([1, 2, 3], - (False, 2, '')) + (False, 2, '')) From 99ab2569c4f90733f48bfb3f73524d06a6d1a199 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Wed, 31 Jul 2013 16:45:14 +0400 Subject: [PATCH 06/14] Fixed type checking of 'for' loop targets for weak unions --- .../python/inspections/PyTypeCheckerInspection.java | 2 +- .../PyTypeCheckerInspection/ForLoopIteration.py | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/python/src/com/jetbrains/python/inspections/PyTypeCheckerInspection.java b/python/src/com/jetbrains/python/inspections/PyTypeCheckerInspection.java index ffb8dc978b02..9002e762105f 100644 --- a/python/src/com/jetbrains/python/inspections/PyTypeCheckerInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyTypeCheckerInspection.java @@ -63,7 +63,7 @@ public class PyTypeCheckerInspection extends PyInspection { final PyExpression source = node.getForPart().getSource(); if (source != null) { final PyType type = myTypeEvalContext.getType(source); - if (type != null && !PyABCUtil.isSubtype(type, PyNames.ITERABLE)) { + if (!PyTypeChecker.isUnknown(type) && !PyABCUtil.isSubtype(type, PyNames.ITERABLE)) { registerProblem(source, String.format("Expected 'collections.Iterable', got '%s' instead", PythonDocumentationProvider.getTypeName(type, myTypeEvalContext))); } diff --git a/python/testData/inspections/PyTypeCheckerInspection/ForLoopIteration.py b/python/testData/inspections/PyTypeCheckerInspection/ForLoopIteration.py index 7182a08b8a92..7c67dc4b5e67 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/ForLoopIteration.py +++ b/python/testData/inspections/PyTypeCheckerInspection/ForLoopIteration.py @@ -1,6 +1,16 @@ -def test(): +def test(p1): for x in 'foo': pass for x in 42: pass + + for x in f('foo', p1): + pass + + +def f(c, x): + if c: + return 10 + else: + return x From af6829b12922d83cf5264651dc0c878418157182 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Wed, 31 Jul 2013 17:14:46 +0400 Subject: [PATCH 07/14] Return weak parameter types and search for all local usages --- .../python/psi/impl/PyNamedParameterImpl.java | 13 ++++++++----- 1 file changed, 8 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 ddf4f1445291..f9f175265e56 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java @@ -2,7 +2,6 @@ package com.jetbrains.python.psi.impl; import com.intellij.lang.ASTNode; import com.intellij.openapi.extensions.Extensions; -import com.intellij.openapi.util.Ref; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiReference; @@ -27,6 +26,8 @@ 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; /** @@ -235,7 +236,7 @@ public class PyNamedParameterImpl extends PyPresentableElementImpl ref = Ref.create(null); + final List types = new ArrayList(); processLocalCalls(func, new Processor() { @Override public boolean process(@NotNull PyCallExpression call) { @@ -247,8 +248,8 @@ public class PyNamedParameterImpl extends PyPresentableElementImpl Date: Wed, 31 Jul 2013 17:15:37 +0400 Subject: [PATCH 08/14] Deterministic ordering in return types of 'yield' generator functions --- python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java index c61693c075d7..81392e382669 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java @@ -252,7 +252,7 @@ public class PyFunctionImpl extends PyPresentableElementImpl imp final PyBuiltinCache cache = PyBuiltinCache.getInstance(this); final PyClass listClass = cache.getClass("list"); final PyStatementList statements = getStatementList(); - final Set types = new HashSet(); + final Set types = new LinkedHashSet(); if (statements != null && listClass != null) { statements.accept(new PyRecursiveElementVisitor() { @Override From 0d29b7b0e8c168715258b0ba094e871a5a8c8ec0 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Wed, 31 Jul 2013 17:36:10 +0400 Subject: [PATCH 09/14] Fixed unification of parameters in function types --- .../python/psi/types/PyCallableTypeImpl.java | 8 ++++--- .../python/psi/types/PyTypeChecker.java | 23 +++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/python/src/com/jetbrains/python/psi/types/PyCallableTypeImpl.java b/python/src/com/jetbrains/python/psi/types/PyCallableTypeImpl.java index 9a2359ed2f70..e57ad127db8a 100644 --- a/python/src/com/jetbrains/python/psi/types/PyCallableTypeImpl.java +++ b/python/src/com/jetbrains/python/psi/types/PyCallableTypeImpl.java @@ -18,10 +18,10 @@ import java.util.List; * @author vlan */ public class PyCallableTypeImpl implements PyCallableType { - @NotNull private final List myParameterTypes; + @Nullable private final List myParameterTypes; @Nullable private final PyType myReturnType; - public PyCallableTypeImpl(@NotNull List parameterTypes, @Nullable PyType returnType) { + public PyCallableTypeImpl(@Nullable List parameterTypes, @Nullable PyType returnType) { myParameterTypes = parameterTypes; myReturnType = returnType; } @@ -61,6 +61,7 @@ public class PyCallableTypeImpl implements PyCallableType { @Override public String getName() { return String.format("(%s) -> %s", + myParameterTypes != null ? StringUtil.join(myParameterTypes, new Function() { @Override @@ -68,7 +69,8 @@ public class PyCallableTypeImpl implements PyCallableType { return type != null ? type.getName() : PyNames.UNKNOWN_TYPE; } }, - ", "), + ", ") : + "...", myReturnType != null ? myReturnType.getName() : PyNames.UNKNOWN_TYPE); } diff --git a/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java b/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java index 429524e49c24..bbbf097021f5 100644 --- a/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java +++ b/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java @@ -228,6 +228,16 @@ public class PyTypeChecker { collectGenerics(tuple.getElementType(i), context, collected, visited); } } + else if (type instanceof PyCallableType) { + final PyCallableType callable = (PyCallableType)type; + final List parameters = callable.getParameterTypes(context); + if (parameters != null) { + for (PyType parameter : parameters) { + collectGenerics(parameter, context, collected, visited); + } + } + collectGenerics(callable.getCallType(context, null), context, collected, visited); + } } @Nullable @@ -262,6 +272,19 @@ public class PyTypeChecker { } return new PyTupleType((PyTupleType)type, results.toArray(new PyType[results.size()])); } + else if (type instanceof PyCallableType) { + final PyCallableType callable = (PyCallableType)type; + List substParams = null; + final List parameters = callable.getParameterTypes(context); + if (parameters != null) { + substParams = new ArrayList(); + for (PyType parameter : parameters) { + substParams.add(substitute(parameter, substitutions, context)); + } + } + final PyType substResult = substitute(callable.getCallType(context, null), substitutions, context); + return new PyCallableTypeImpl(substParams, substResult); + } } return type; } From 3bffbc94d70779e6a5cf285dad7af14923e1bedb Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Wed, 31 Jul 2013 17:53:49 +0400 Subject: [PATCH 10/14] Switched from type checking to comparing type strings in PyTypeTest --- .../com/jetbrains/python/PyTypeTest.java | 73 +++++++++---------- 1 file changed, 35 insertions(+), 38 deletions(-) diff --git a/python/testSrc/com/jetbrains/python/PyTypeTest.java b/python/testSrc/com/jetbrains/python/PyTypeTest.java index 2348a1039500..2046a3230983 100644 --- a/python/testSrc/com/jetbrains/python/PyTypeTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypeTest.java @@ -25,18 +25,18 @@ public class PyTypeTest extends PyTestCase { } public void testBinaryExprType() { - doTest("int or long or float or complex", + doTest("int", "expr = 1 + 2"); - doTest("str or unicode", + doTest("str | unicode", "expr = '1' + '2'"); - doTest("str or unicode", + doTest("str", "expr = '%s' % ('a')"); - doTest("list", + doTest("list[int]", "expr = [1] + [2]"); } public void testAssignmentChainBinaryExprType() { - doTest("int or long or float or complex", + doTest("int", "class C(object):\n" + " def __add__(self, other):\n" + " return -1\n" + @@ -56,7 +56,7 @@ public class PyTypeTest extends PyTestCase { } public void testUnionOfTuples() { - doTest("(int or str, str or int)", + doTest("(int | str, str | int)", "def x():\n" + " if True:\n" + " return (1, 'a')\n" + @@ -80,7 +80,7 @@ public class PyTypeTest extends PyTestCase { } public void testSet() { - doTest("set of int", + doTest("set[int]", "expr = {1, 2, 3}"); } @@ -112,10 +112,18 @@ public class PyTypeTest extends PyTestCase { } public void testPropertyType() { - doTest("unknown", + doTest("property", "class C:\n" + - " x = property(lambda self: object(), None, None)\n" + - "expr = C.x"); + " x = property(lambda self: 'foo', None, None)\n" + + "expr = C.x\n"); + } + + public void testPropertyInstanceType() { + doTest("str", + "class C:\n" + + " x = property(lambda self: 'foo', None, None)\n" + + "c = C()\n" + + "expr = c.x\n"); } public void testIterationType() { @@ -129,7 +137,7 @@ public class PyTypeTest extends PyTestCase { } public void testSliceType() { - doTest("list of int", + doTest("list[int]", "l = [1, 2, 3]; expr = l[0:1]"); } @@ -215,15 +223,12 @@ public class PyTypeTest extends PyTestCase { } public void testUnknownTypeInUnion() { - final String text = "def f(c, x):\n" + - " if c:\n" + - " return 1\n" + - " return x\n" + - "expr = f(1, g())\n"; - PyExpression expr = parseExpr(text); - PyType t = getTypeEvalContext(expr).getType(expr); - assertTrue(PyTypeChecker.isUnknown(t)); - doTest("int", text); + doTest("int | unknown", + "def f(c, x):\n" + + " if c:\n" + + " return 1\n" + + " return x\n" + + "expr = f(1, g())\n"); } public void testIsInstance() { @@ -341,7 +346,7 @@ public class PyTypeTest extends PyTestCase { public void testYieldFromType() { PythonLanguageLevelPusher.setForcedLanguageLevel(myFixture.getProject(), LanguageLevel.PYTHON33); try { - doTest("str or int or float", + doTest("str | int | float", "def subgen():\n" + " for i in [1, 2, 3]:\n" + " yield i\n" + @@ -429,7 +434,7 @@ public class PyTypeTest extends PyTestCase { // PY-7215 public void testFunctionWithNestedGenerator() { - doTest("list of int", + doTest("list[int]", "def f():\n" + " def g():\n" + " yield 10\n" + @@ -468,7 +473,7 @@ public class PyTypeTest extends PyTestCase { // EA-40207 public void testRecursion() { - doTest("list of list", + doTest("list[list]", "def f():\n" + " return [f()]\n" + "expr = f()\n"); @@ -533,7 +538,7 @@ public class PyTypeTest extends PyTestCase { } public void testLogicalAndExpression() { - doTest("str or int", + doTest("str | int", "expr = 'foo' and 2"); } @@ -552,16 +557,17 @@ public class PyTypeTest extends PyTestCase { } public void testParameterFromUsages() { - doTest("int", + doTest("int | str | unknown", "def foo(bar):\n" + " expr = bar\n" + "def use_foo(x):\n" + " foo(x)\n" + - " foo(3)\n"); + " foo(3)\n" + + " foo('bar')\n"); } public void testUpperBoundGeneric() { - doTest("int or str", + doTest("int | str", "def foo(x):\n" + " '''\n" + " :type x: T <= int or str\n" + @@ -649,20 +655,11 @@ public class PyTypeTest extends PyTestCase { return myFixture.findElementByText("expr", PyExpression.class); } - private static String msg(PyType expected, PyType actual, TypeEvalContext context) { - return String.format("Expected: %s, actual: %s", - PythonDocumentationProvider.getTypeName(expected, context), - PythonDocumentationProvider.getTypeName(actual, context)); - } - private void doTest(final String expectedType, final String text) { PyExpression expr = parseExpr(text); TypeEvalContext context = getTypeEvalContext(expr); PyType actual = context.getType(expr); - PyType expected = PyTypeParser.getTypeByName(expr, expectedType); - if (expected != null) { - assertNotNull(context.printTrace(), actual); - assertTrue(msg(expected, actual, context), PyTypeChecker.match(expected, actual, context)); - } + final String actualType = PythonDocumentationProvider.getTypeName(actual, context); + assertEquals(expectedType, actualType); } } From 5069bc03b2ebf0eee5f5ca94528296ad7619c47c Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Wed, 31 Jul 2013 19:24:10 +0400 Subject: [PATCH 11/14] Don't show unknown unions in type description strings --- .../documentation/PyTypeModelBuilder.java | 37 ++++++++++++++++--- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java b/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java index 9399b75f9201..fec9dc880c2d 100644 --- a/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java +++ b/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java @@ -89,6 +89,19 @@ public class PyTypeModelBuilder { } } + static class UnknownType extends TypeModel { + private final TypeModel type; + + private UnknownType(TypeModel type) { + this.type = type; + } + + @Override + void accept(TypeVisitor visitor) { + visitor.unknown(this); + } + } + private static TypeModel _(String name) { return new NamedType(name); } @@ -162,8 +175,8 @@ public class PyTypeModelBuilder { } } else if (type instanceof PyUnionType && allowUnions) { - if (type instanceof PyDynamicallyEvaluatedType) { - result = build(((PyDynamicallyEvaluatedType)type).exclude(null, myContext), true); + if (type instanceof PyDynamicallyEvaluatedType || PyTypeChecker.isUnknown(type)) { + result = new UnknownType(build(((PyUnionType)type).excludeNull(), true)); } else { result = new OneOf( @@ -182,7 +195,6 @@ public class PyTypeModelBuilder { return result; } - public TypeModel build(PyFunction function) { final PyType returnType = function.getReturnType(myContext, null); return new FunctionType(build(returnType, true), Collections2.transform(Lists.newArrayList(function.getParameterList().getParameters()), @@ -191,8 +203,7 @@ public class PyTypeModelBuilder { public TypeModel apply(PyParameter p) { final PyNamedParameter np = p.getAsNamed(); if (np != null) { - TypeModel paramType = - _(PyNames.UNKNOWN_TYPE); + TypeModel paramType = _(PyNames.UNKNOWN_TYPE); final PyType t = myContext.getType(np); if (t != null) { paramType = build(t, true); @@ -214,6 +225,8 @@ public class PyTypeModelBuilder { void function(FunctionType type); void param(ParamType text); + + void unknown(UnknownType type); } private static class TypeToStringVisitor extends TypeNameVisitor { @@ -232,6 +245,15 @@ public class PyTypeModelBuilder { public String getString() { return myStringBuilder.toString(); } + + @Override + public void unknown(UnknownType type) { + final TypeModel nested = type.type; + if (nested != null) { + nested.accept(this); + } + add(" | " + PyNames.UNKNOWN_TYPE); + } } private static class TypeToBodyWithLinksVisitor extends TypeNameVisitor { @@ -346,5 +368,10 @@ public class PyTypeModelBuilder { } myDepth--; } + + @Override + public void unknown(UnknownType type) { + type.type.accept(this); + } } } From 08a4fe32d022f7acbacb9e4b7bfa286a1c29edac Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Wed, 31 Jul 2013 20:27:27 +0400 Subject: [PATCH 12/14] Fixed type signature for 'str.replace' for Python 3 (PY-10402) --- python/helpers/StdlibTypes.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/helpers/StdlibTypes.properties b/python/helpers/StdlibTypes.properties index 0cc9d7d78116..e823e665a859 100644 --- a/python/helpers/StdlibTypes.properties +++ b/python/helpers/StdlibTypes.properties @@ -777,7 +777,7 @@ __builtin__.str.replace = \ :type old: string \n\ :type new: string \n\ :type count: int or long \n\ - :rtype: bytes \n\ + :rtype: string \n\ __builtin__.str.rfind = \ :type sub: string \n\ From bb3431028268c18b290f68446c6eb984cda72991 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Wed, 31 Jul 2013 21:22:55 +0400 Subject: [PATCH 13/14] Show more details about types of functions (PY-10413) --- .../python/psi/impl/PyJavaClassType.java | 2 +- .../python/psi/impl/PyJavaMethodType.java | 2 +- .../python/psi/types/PyCallableType.java | 5 +- .../documentation/PyTypeModelBuilder.java | 49 +++++++++++++++---- .../PythonDocumentationProvider.java | 1 + .../python/psi/types/PyCallableTypeImpl.java | 34 +++++++++---- .../python/psi/types/PyClassTypeImpl.java | 2 +- .../python/psi/types/PyFunctionType.java | 25 +++++++++- .../python/psi/types/PyTypeChecker.java | 28 ++++++----- .../python/psi/types/PyTypeParser.java | 8 +-- .../FunctionParameterReturnType.py | 28 +++++++++++ .../jetbrains/python/PyTypeParserTest.java | 9 ++-- .../PyTypeCheckerInspectionTest.java | 5 ++ 13 files changed, 152 insertions(+), 46 deletions(-) create mode 100644 python/testData/inspections/PyTypeCheckerInspection/FunctionParameterReturnType.py diff --git a/python/pluginSrc/com/jetbrains/python/psi/impl/PyJavaClassType.java b/python/pluginSrc/com/jetbrains/python/psi/impl/PyJavaClassType.java index 7c3a3ac34cc1..83f9e1476d87 100644 --- a/python/pluginSrc/com/jetbrains/python/psi/impl/PyJavaClassType.java +++ b/python/pluginSrc/com/jetbrains/python/psi/impl/PyJavaClassType.java @@ -98,7 +98,7 @@ public class PyJavaClassType implements PyClassLikeType { @Nullable @Override - public List getParameterTypes(@NotNull TypeEvalContext context) { + public List getParameters(@NotNull TypeEvalContext context) { return null; } diff --git a/python/pluginSrc/com/jetbrains/python/psi/impl/PyJavaMethodType.java b/python/pluginSrc/com/jetbrains/python/psi/impl/PyJavaMethodType.java index 2594a1eed1f4..9916559f07d1 100644 --- a/python/pluginSrc/com/jetbrains/python/psi/impl/PyJavaMethodType.java +++ b/python/pluginSrc/com/jetbrains/python/psi/impl/PyJavaMethodType.java @@ -41,7 +41,7 @@ public class PyJavaMethodType implements PyCallableType { @Nullable @Override - public List getParameterTypes(@NotNull TypeEvalContext context) { + public List getParameters(@NotNull TypeEvalContext context) { return null; } diff --git a/python/psi-api/src/com/jetbrains/python/psi/types/PyCallableType.java b/python/psi-api/src/com/jetbrains/python/psi/types/PyCallableType.java index 83c544fe9762..482df90af817 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/types/PyCallableType.java +++ b/python/psi-api/src/com/jetbrains/python/psi/types/PyCallableType.java @@ -1,5 +1,6 @@ package com.jetbrains.python.psi.types; +import com.intellij.openapi.util.Pair; import com.jetbrains.python.psi.PyQualifiedExpression; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -31,8 +32,8 @@ public interface PyCallableType extends PyType { /** * Returns the list of parameter types. * - * @return list of positional parameters or null if not applicable. + * @return list of (name, type) pairs or null if not applicable. Name and type in pair may be null. */ @Nullable - List getParameterTypes(@NotNull TypeEvalContext context); + List> getParameters(@NotNull TypeEvalContext context); } diff --git a/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java b/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java index fec9dc880c2d..3876dc6300dd 100644 --- a/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java +++ b/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java @@ -4,9 +4,10 @@ import com.google.common.base.Function; import com.google.common.collect.Collections2; import com.google.common.collect.Lists; import com.google.common.collect.Maps; +import com.intellij.openapi.util.Pair; import com.intellij.psi.PsiElement; import com.jetbrains.python.PyNames; -import com.jetbrains.python.psi.PyFunction; +import com.jetbrains.python.psi.Callable; import com.jetbrains.python.psi.PyNamedParameter; import com.jetbrains.python.psi.PyParameter; import com.jetbrains.python.psi.types.*; @@ -108,10 +109,15 @@ public class PyTypeModelBuilder { static class FunctionType extends TypeModel { private TypeModel returnType; - private Collection parameters; + @Nullable private Collection parameters; - FunctionType(@NotNull TypeModel returnType, Collection parameters) { - this.returnType = returnType; + FunctionType(@Nullable TypeModel returnType, @Nullable Collection parameters) { + if (returnType != null) { + this.returnType = returnType; + } + else { + this.returnType = _(PyNames.UNKNOWN_TYPE); + } this.parameters = parameters; } @@ -188,6 +194,9 @@ public class PyTypeModelBuilder { })); } } + else if (type instanceof PyCallableType && !(type instanceof PyClassLikeType)) { + result = build((PyCallableType)type); + } if (result == null) { result = type != null ? _(type.getName()) : _(PyNames.UNKNOWN_TYPE); } @@ -195,9 +204,23 @@ public class PyTypeModelBuilder { return result; } - public TypeModel build(PyFunction function) { - final PyType returnType = function.getReturnType(myContext, null); - return new FunctionType(build(returnType, true), Collections2.transform(Lists.newArrayList(function.getParameterList().getParameters()), + private TypeModel build(@NotNull PyCallableType type) { + List parameterModels = null; + final List> parameters = type.getParameters(myContext); + if (parameters != null) { + parameterModels = new ArrayList(); + for (Pair parameter : parameters) { + parameterModels.add(new ParamType(parameter.getFirst(), build(parameter.getSecond(), true))); + } + } + final PyType ret = type.getCallType(myContext, null); + final TypeModel returnType = build(ret, true); + return new FunctionType(returnType, parameterModels); + } + + public TypeModel build(Callable callable) { + final PyType returnType = callable.getReturnType(myContext, null); + return new FunctionType(build(returnType, true), Collections2.transform(Lists.newArrayList(callable.getParameterList().getParameters()), new Function() { @Override public TypeModel apply(PyParameter p) { @@ -208,7 +231,8 @@ public class PyTypeModelBuilder { if (t != null) { paramType = build(t, true); } - return new ParamType(np.getName(), paramType); + final String name = PyFunctionType.getParameterName(np); + return new ParamType(name, paramType); } return new ParamType(p.toString(), null); } @@ -343,10 +367,15 @@ public class PyTypeModelBuilder { return; } add("("); - processListCommaSeparated(function.parameters, ", "); + final Collection parameters = function.parameters; + if (parameters != null) { + processListCommaSeparated(parameters, ", "); + } + else { + add("..."); + } add(") -> "); function.returnType.accept(this); - add("\n"); myDepth--; } diff --git a/python/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java b/python/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java index 6ee637fe6f10..9a040841ae74 100644 --- a/python/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java +++ b/python/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java @@ -126,6 +126,7 @@ public class PythonDocumentationProvider extends AbstractDocumentationProvider i if (!PyNames.INIT.equals(name)) { cat.addItem(escaper.apply("\nInferred type: ")); getTypeDescription(fun, cat); + cat.addItem(BR); } return cat; } diff --git a/python/src/com/jetbrains/python/psi/types/PyCallableTypeImpl.java b/python/src/com/jetbrains/python/psi/types/PyCallableTypeImpl.java index e57ad127db8a..8b3cb754b437 100644 --- a/python/src/com/jetbrains/python/psi/types/PyCallableTypeImpl.java +++ b/python/src/com/jetbrains/python/psi/types/PyCallableTypeImpl.java @@ -1,5 +1,6 @@ package com.jetbrains.python.psi.types; +import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.Function; import com.intellij.util.ProcessingContext; @@ -18,11 +19,11 @@ import java.util.List; * @author vlan */ public class PyCallableTypeImpl implements PyCallableType { - @Nullable private final List myParameterTypes; + @Nullable private final List> myParameters; @Nullable private final PyType myReturnType; - public PyCallableTypeImpl(@Nullable List parameterTypes, @Nullable PyType returnType) { - myParameterTypes = parameterTypes; + public PyCallableTypeImpl(@Nullable List> parameters, @Nullable PyType returnType) { + myParameters = parameters; myReturnType = returnType; } @@ -39,8 +40,8 @@ public class PyCallableTypeImpl implements PyCallableType { @Nullable @Override - public List getParameterTypes(@NotNull TypeEvalContext context) { - return myParameterTypes; + public List> getParameters(@NotNull TypeEvalContext context) { + return myParameters; } @Nullable @@ -61,12 +62,25 @@ public class PyCallableTypeImpl implements PyCallableType { @Override public String getName() { return String.format("(%s) -> %s", - myParameterTypes != null ? - StringUtil.join(myParameterTypes, - new Function() { + myParameters != null ? + StringUtil.join(myParameters, + new Function, String>() { @Override - public String fun(PyType type) { - return type != null ? type.getName() : PyNames.UNKNOWN_TYPE; + public String fun(Pair param) { + if (param != null) { + final StringBuilder builder = new StringBuilder(); + final String name = param.getFirst(); + final PyType type = param.getSecond(); + if (name != null) { + builder.append(name); + if (type != null) { + builder.append(": "); + } + } + builder.append(type != null ? type.getName() : PyNames.UNKNOWN_TYPE); + return builder.toString(); + } + return PyNames.UNKNOWN_TYPE; } }, ", ") : diff --git a/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java b/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java index 2ca0fbcf2c02..446aba417165 100644 --- a/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java +++ b/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java @@ -279,7 +279,7 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType { @Nullable @Override - public List getParameterTypes(@NotNull TypeEvalContext context) { + public List> getParameters(@NotNull TypeEvalContext context) { return null; } diff --git a/python/src/com/jetbrains/python/psi/types/PyFunctionType.java b/python/src/com/jetbrains/python/psi/types/PyFunctionType.java index f0d3a7b76372..20072ba86284 100644 --- a/python/src/com/jetbrains/python/psi/types/PyFunctionType.java +++ b/python/src/com/jetbrains/python/psi/types/PyFunctionType.java @@ -1,5 +1,6 @@ package com.jetbrains.python.psi.types; +import com.intellij.openapi.util.Pair; import com.intellij.util.ProcessingContext; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.resolve.PyResolveContext; @@ -7,6 +8,7 @@ import com.jetbrains.python.psi.resolve.RatedResolveResult; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -35,8 +37,15 @@ public class PyFunctionType implements PyCallableType { @Nullable @Override - public List getParameterTypes(@NotNull TypeEvalContext context) { - return null; + public List> getParameters(@NotNull TypeEvalContext context) { + final List> result = new ArrayList>(); + for (PyParameter parameter : myCallable.getParameterList().getParameters()) { + if (parameter instanceof PyNamedParameter) { + final PyNamedParameter namedParameter = (PyNamedParameter)parameter; + result.add(Pair.create(getParameterName(namedParameter), context.getType(namedParameter))); + } + } + return result; } @Override @@ -70,4 +79,16 @@ public class PyFunctionType implements PyCallableType { public Callable getCallable() { return myCallable; } + + @Nullable + public static String getParameterName(@NotNull PyNamedParameter namedParameter) { + String name = namedParameter.getName(); + if (namedParameter.isPositionalContainer()) { + name = "*" + name; + } + else if (namedParameter.isKeywordContainer()) { + name = "**" + name; + } + return name; + } } diff --git a/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java b/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java index bbbf097021f5..4c3660dcbdb3 100644 --- a/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java +++ b/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java @@ -1,5 +1,6 @@ package com.jetbrains.python.psi.types; +import com.intellij.openapi.util.Pair; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiPolyVariantReference; import com.intellij.psi.PsiReference; @@ -144,12 +145,15 @@ public class PyTypeChecker { final PyCallableType expectedCallable = (PyCallableType)expected; final PyCallableType actualCallable = (PyCallableType)actual; if (expectedCallable.isCallable() && actualCallable.isCallable()) { - final List expectedParameters = expectedCallable.getParameterTypes(context); - final List actualParameters = actualCallable.getParameterTypes(context); + final List> expectedParameters = expectedCallable.getParameters(context); + final List> actualParameters = actualCallable.getParameters(context); if (expectedParameters != null && actualParameters != null) { final int size = Math.min(expectedParameters.size(), actualParameters.size()); for (int i = 0; i < size; i++) { - if (!match(expectedParameters.get(i), actualParameters.get(i), context, substitutions, recursive)) { + final Pair expectedParam = expectedParameters.get(i); + final Pair actualParam = actualParameters.get(i); + // TODO: Check named and star params, not only positional ones + if (!match(expectedParam.getSecond(), actualParam.getSecond(), context, substitutions, recursive)) { return false; } } @@ -230,10 +234,12 @@ public class PyTypeChecker { } else if (type instanceof PyCallableType) { final PyCallableType callable = (PyCallableType)type; - final List parameters = callable.getParameterTypes(context); + final List> parameters = callable.getParameters(context); if (parameters != null) { - for (PyType parameter : parameters) { - collectGenerics(parameter, context, collected, visited); + for (Pair parameter : parameters) { + if (parameter != null) { + collectGenerics(parameter.getSecond(), context, collected, visited); + } } } collectGenerics(callable.getCallType(context, null), context, collected, visited); @@ -274,12 +280,12 @@ public class PyTypeChecker { } else if (type instanceof PyCallableType) { final PyCallableType callable = (PyCallableType)type; - List substParams = null; - final List parameters = callable.getParameterTypes(context); + List> substParams = null; + final List> parameters = callable.getParameters(context); if (parameters != null) { - substParams = new ArrayList(); - for (PyType parameter : parameters) { - substParams.add(substitute(parameter, substitutions, context)); + substParams = new ArrayList>(); + for (Pair parameter : parameters) { + substParams.add(Pair.create(parameter.getFirst(), substitute(parameter.getSecond(), substitutions, context))); } } final PyType substResult = substitute(callable.getCallType(context, null), substitutions, context); diff --git a/python/src/com/jetbrains/python/psi/types/PyTypeParser.java b/python/src/com/jetbrains/python/psi/types/PyTypeParser.java index 1e57982053b9..7aab2400b333 100644 --- a/python/src/com/jetbrains/python/psi/types/PyTypeParser.java +++ b/python/src/com/jetbrains/python/psi/types/PyTypeParser.java @@ -230,7 +230,7 @@ public class PyTypeParser { new Function>, ParseResult>, ParseResult>() { @Override public ParseResult fun(Pair>, ParseResult> value) { - final List parameterTypes = new ArrayList(); + final List> parameters = new ArrayList>(); final ParseResult returnResult = value.getSecond(); ParseResult result; final Pair> firstPair = value.getFirst(); @@ -238,17 +238,17 @@ public class PyTypeParser { final ParseResult first = firstPair.getFirst(); final List second = firstPair.getSecond(); result = first; - parameterTypes.add(first.getType()); + parameters.add(Pair.create(null, first.getType())); for (ParseResult r : second) { result = result.merge(r); - parameterTypes.add(r.getType()); + parameters.add(Pair.create(null, r.getType())); } result = result.merge(returnResult); } else { result = returnResult; } - return result.withType(new PyCallableTypeImpl(parameterTypes, returnResult.getType())); + return result.withType(new PyCallableTypeImpl(parameters, returnResult.getType())); } }) .named("callable-expr"); diff --git a/python/testData/inspections/PyTypeCheckerInspection/FunctionParameterReturnType.py b/python/testData/inspections/PyTypeCheckerInspection/FunctionParameterReturnType.py new file mode 100644 index 000000000000..6d43c2ce0a27 --- /dev/null +++ b/python/testData/inspections/PyTypeCheckerInspection/FunctionParameterReturnType.py @@ -0,0 +1,28 @@ +def func(f, seq): + """ + :param f: my param + :type f: (unknown) -> str + :rtype: list[str] + """ + return [f(v) for v in seq] + + +def f(x): + return int(x) + + +def test(): + for item in func(f, []): + pass + + for item in func(int, []): + pass + + for item in func(lambda x: int(x), []): + pass + + for item in func(lambda x: str(x), []): + pass + + for item in func(str, []): + pass diff --git a/python/testSrc/com/jetbrains/python/PyTypeParserTest.java b/python/testSrc/com/jetbrains/python/PyTypeParserTest.java index ed6850b99fea..105893830283 100644 --- a/python/testSrc/com/jetbrains/python/PyTypeParserTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypeParserTest.java @@ -1,5 +1,6 @@ package com.jetbrains.python; +import com.intellij.openapi.util.Pair; import com.jetbrains.python.fixtures.PyTestCase; import com.jetbrains.python.psi.types.*; @@ -236,11 +237,11 @@ public class PyTypeParserTest extends PyTestCase { assertNotNull(callableType); final PyType returnType = callableType.getCallType(getTypeEvalContext(), null); assertInstanceOf(returnType, PyGenericType.class); - final List parameterTypes = callableType.getParameterTypes(getTypeEvalContext()); + final List> parameterTypes = callableType.getParameters(getTypeEvalContext()); assertNotNull(parameterTypes); assertEquals(2, parameterTypes.size()); - assertEquals("int", parameterTypes.get(0).getName()); - assertEquals("T", parameterTypes.get(1).getName()); + assertEquals("int", parameterTypes.get(0).getSecond().getName()); + assertEquals("T", parameterTypes.get(1).getSecond().getName()); } public void testCallableWithoutArgs() { @@ -252,7 +253,7 @@ public class PyTypeParserTest extends PyTestCase { final PyType returnType = callableType.getCallType(getTypeEvalContext(), null); assertNotNull(returnType); assertEquals("int", returnType.getName()); - final List parameterTypes = callableType.getParameterTypes(getTypeEvalContext()); + final List> parameterTypes = callableType.getParameters(getTypeEvalContext()); assertNotNull(parameterTypes); assertEquals(0, parameterTypes.size()); } diff --git a/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java index 1bd48b30da31..98441d80bd33 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java @@ -198,4 +198,9 @@ public class PyTypeCheckerInspectionTest extends PyTestCase { public void testMapReturnElementType() { doTest(); } + + // PY-10413 + public void testFunctionParameterReturnType() { + doTest(); + } } From c0a7dd24c13f7f6864caeae74410fda5fbf2665c Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Thu, 1 Aug 2013 15:25:40 +0400 Subject: [PATCH 14/14] Renamed TypeNameVisitor.processListCommaSeparated to processList --- .../python/documentation/PyTypeModelBuilder.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java b/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java index 3876dc6300dd..8e6db24d6d8b 100644 --- a/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java +++ b/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java @@ -318,11 +318,11 @@ public class PyTypeModelBuilder { add("..."); return; } - processListCommaSeparated(oneOf.oneOfTypes, " | "); + processList(oneOf.oneOfTypes, " | "); myDepth--; } - private void processListCommaSeparated(Collection list, String separator) { + private void processList(Collection list, String separator) { boolean first = true; for (TypeModel t : list) { if (!first) { @@ -347,7 +347,7 @@ public class PyTypeModelBuilder { } addType(collectionOf.collectionName); add("["); - processListCommaSeparated(collectionOf.elementTypes, ", "); + processList(collectionOf.elementTypes, ", "); add("]"); myDepth--; } @@ -369,7 +369,7 @@ public class PyTypeModelBuilder { add("("); final Collection parameters = function.parameters; if (parameters != null) { - processListCommaSeparated(parameters, ", "); + processList(parameters, ", "); } else { add("...");