diff --git a/python/src/com/jetbrains/python/inspections/PyTypeCheckerInspection.java b/python/src/com/jetbrains/python/inspections/PyTypeCheckerInspection.java index 602db9139323..e77195dce7d6 100644 --- a/python/src/com/jetbrains/python/inspections/PyTypeCheckerInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyTypeCheckerInspection.java @@ -93,20 +93,19 @@ public class PyTypeCheckerInspection extends PyInspection { final String superName = PythonDocumentationProvider.getTypeName(superType, context); String expected = String.format("'%s'", superName); final boolean hasGenerics = PyTypeChecker.hasGenerics(superType, 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); - + highlightType = ProblemHighlightType.WEAK_WARNING; } } final String msg = String.format("Expected type %s, got '%s' instead", expected, PythonDocumentationProvider.getTypeName(subType, context)); - final ProblemHighlightType highlightType = hasGenerics ? ProblemHighlightType.WEAK_WARNING : - ProblemHighlightType.GENERIC_ERROR_OR_WARNING; registerProblem(node, msg, highlightType); return msg; } diff --git a/python/src/com/jetbrains/python/psi/types/PyGenericType.java b/python/src/com/jetbrains/python/psi/types/PyGenericType.java index 498a8ae74407..20e1986872dc 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 myName; + return myBound != null ? myName + " (" + myBound.getName() + ")" : myName; } @Override @@ -71,7 +71,7 @@ public class PyGenericType implements PyType { @NotNull @Override public String toString() { - return "PyGenericType: " + myName; + return "PyGenericType: " + getName(); } @Nullable diff --git a/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java b/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java index 57641c8cec3b..97882e8b2908 100644 --- a/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java +++ b/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java @@ -44,6 +44,32 @@ public class PyTypeChecker { private static boolean match(@Nullable PyType expected, @Nullable PyType actual, @NotNull TypeEvalContext context, @Nullable Map substitutions, boolean recursive) { // TODO: subscriptable types?, module types?, etc. + if (expected instanceof PyGenericType && substitutions != null) { + final PyGenericType generic = (PyGenericType)expected; + final PyType subst = substitutions.get(generic); + final PyType bound = generic.getBound(); + if (!match(bound, actual, context, substitutions, recursive)) { + return false; + } + else if (subst != null) { + if (expected.equals(actual)) { + return true; + } + else if (recursive) { + return match(subst, actual, context, substitutions, false); + } + else { + return false; + } + } + else if (actual != null && !(actual instanceof PyReturnTypeReference)) { + substitutions.put(generic, actual); + } + else if (bound != null) { + substitutions.put(generic, bound); + } + return true; + } if (expected == null || actual == null) { return true; } @@ -62,25 +88,6 @@ public class PyTypeChecker { if (actual instanceof PyTypeReference) { return match(expected, ((PyTypeReference)actual).resolve(null, context), context, substitutions, false); } - if (expected instanceof PyGenericType && substitutions != null) { - final PyGenericType generic = (PyGenericType)expected; - final PyType subst = substitutions.get(generic); - if (subst != null) { - if (expected.equals(actual)) { - return true; - } - else if (recursive) { - return match(subst, actual, context, substitutions, false); - } - else { - return false; - } - } - else { - substitutions.put(generic, actual); - return true; - } - } if (isUnknown(actual)) { return true; } diff --git a/python/src/com/jetbrains/python/psi/types/PyUnionType.java b/python/src/com/jetbrains/python/psi/types/PyUnionType.java index eff3aec345e8..2d56e8f291b2 100644 --- a/python/src/com/jetbrains/python/psi/types/PyUnionType.java +++ b/python/src/com/jetbrains/python/psi/types/PyUnionType.java @@ -215,4 +215,9 @@ public class PyUnionType implements PyType { public int hashCode() { return myMembers.hashCode(); } + + @Override + public String toString() { + return "PyUnionType: " + getName(); + } } diff --git a/python/testData/inspections/PyTypeCheckerInspection/BoundedGeneric.py b/python/testData/inspections/PyTypeCheckerInspection/BoundedGeneric.py new file mode 100644 index 000000000000..14202d5d177a --- /dev/null +++ b/python/testData/inspections/PyTypeCheckerInspection/BoundedGeneric.py @@ -0,0 +1,12 @@ +def test(): + def f(x): + """ + :type x: T (int or str) + :rtype: T + """ + pass + + x = f(10) + y = f('foo') + z = f([]) + return x + y diff --git a/python/testSrc/com/jetbrains/python/PyTypeTest.java b/python/testSrc/com/jetbrains/python/PyTypeTest.java index 2e950c23da36..a76ad1552984 100644 --- a/python/testSrc/com/jetbrains/python/PyTypeTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypeTest.java @@ -572,6 +572,17 @@ public class PyTypeTest extends PyTestCase { " foo(3)\n"); } + public void testUpperBoundGeneric() { + doTest("int or str", + "def foo(x):\n" + + " '''\n" + + " :type x: T (int or str)\n" + + " :rtype: T\n" + + " '''\n" + + "def bar(x):\n" + + " expr = foo(x)\n"); + } + private static TypeEvalContext getTypeEvalContext(@NotNull PyExpression element) { return TypeEvalContext.userInitiated(element.getContainingFile()).withTracing(); } @@ -594,6 +605,7 @@ public class PyTypeTest extends PyTestCase { PyType expected = PyTypeParser.getTypeByName(expr, expectedType); if (expected != null) { assertNotNull(context.printTrace(), actual); + assertFalse(context.printTrace(), actual instanceof PyReturnTypeReference); assertTrue(msg(expected, actual, context), PyTypeChecker.match(expected, actual, context)); } } diff --git a/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java index d84e8f9bb0a5..781490dbd737 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java @@ -180,4 +180,8 @@ public class PyTypeCheckerInspectionTest extends PyTestCase { public void testFieldWithNoneInStub() { doMultiFileTest(); } + + public void testBoundedGeneric() { + doTest(); + } }