From 0c6e18ae5d96ecab7dd4857f0d99cd1127a9674b Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Tue, 25 Apr 2023 18:08:41 -0600 Subject: [PATCH] Add explanation if argument type is incompatible because of a "numbers" type (#15137) Types from `numbers` aren't really supported in any useful way. Make it more explicit, since this is surprising. Work on #3186. --- mypy/messages.py | 20 +++++++++++++++++ test-data/unit/check-classes.test | 33 +++++++++++++++++++++++++++++ test-data/unit/lib-stub/numbers.pyi | 10 +++++++++ 3 files changed, 63 insertions(+) create mode 100644 test-data/unit/lib-stub/numbers.pyi diff --git a/mypy/messages.py b/mypy/messages.py index 030e95f2e88a6..3b112dff44bfc 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -137,6 +137,14 @@ "typing._SpecialForm": "typing-medium.pyi", } +UNSUPPORTED_NUMBERS_TYPES: Final = { + "numbers.Number", + "numbers.Complex", + "numbers.Real", + "numbers.Rational", + "numbers.Integral", +} + class MessageBuilder: """Helper class for reporting type checker error messages with parameters. @@ -792,6 +800,7 @@ def incompatible_argument( for type in get_proper_types(expected_types): if isinstance(arg_type, Instance) and isinstance(type, Instance): notes = append_invariance_notes(notes, arg_type, type) + notes = append_numbers_notes(notes, arg_type, type) object_type = get_proper_type(object_type) if isinstance(object_type, TypedDictType): code = codes.TYPEDDICT_ITEM @@ -2992,6 +3001,17 @@ def append_invariance_notes( return notes +def append_numbers_notes( + notes: list[str], arg_type: Instance, expected_type: Instance +) -> list[str]: + """Explain if an unsupported type from "numbers" is used in a subtype check.""" + if expected_type.type.fullname in UNSUPPORTED_NUMBERS_TYPES: + notes.append('Types from "numbers" aren\'t supported for static type checking') + notes.append("See https://peps.python.org/pep-0484/#the-numeric-tower") + notes.append("Consider using a protocol instead, such as typing.SupportsFloat") + return notes + + def make_inferred_type_note( context: Context, subtype: Type, supertype: Type, supertype_str: str ) -> str: diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index a33f8b0e6eb9d..6724b7398d2b5 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -7826,3 +7826,36 @@ class D: # and that's what matters. a, b = self.f() # E: "C" has no attribute "__iter__" (not iterable) [builtins fixtures/tuple.pyi] + +[case testUsingNumbersType] +from numbers import Number, Complex, Real, Rational, Integral + +def f1(x: Number) -> None: pass +f1(1) # E: Argument 1 to "f1" has incompatible type "int"; expected "Number" \ + # N: Types from "numbers" aren't supported for static type checking \ + # N: See https://peps.python.org/pep-0484/#the-numeric-tower \ + # N: Consider using a protocol instead, such as typing.SupportsFloat + +def f2(x: Complex) -> None: pass +f2(1) # E: Argument 1 to "f2" has incompatible type "int"; expected "Complex" \ + # N: Types from "numbers" aren't supported for static type checking \ + # N: See https://peps.python.org/pep-0484/#the-numeric-tower \ + # N: Consider using a protocol instead, such as typing.SupportsFloat + +def f3(x: Real) -> None: pass +f3(1) # E: Argument 1 to "f3" has incompatible type "int"; expected "Real" \ + # N: Types from "numbers" aren't supported for static type checking \ + # N: See https://peps.python.org/pep-0484/#the-numeric-tower \ + # N: Consider using a protocol instead, such as typing.SupportsFloat + +def f4(x: Rational) -> None: pass +f4(1) # E: Argument 1 to "f4" has incompatible type "int"; expected "Rational" \ + # N: Types from "numbers" aren't supported for static type checking \ + # N: See https://peps.python.org/pep-0484/#the-numeric-tower \ + # N: Consider using a protocol instead, such as typing.SupportsFloat + +def f5(x: Integral) -> None: pass +f5(1) # E: Argument 1 to "f5" has incompatible type "int"; expected "Integral" \ + # N: Types from "numbers" aren't supported for static type checking \ + # N: See https://peps.python.org/pep-0484/#the-numeric-tower \ + # N: Consider using a protocol instead, such as typing.SupportsFloat diff --git a/test-data/unit/lib-stub/numbers.pyi b/test-data/unit/lib-stub/numbers.pyi new file mode 100644 index 0000000000000..fad173c9a8b66 --- /dev/null +++ b/test-data/unit/lib-stub/numbers.pyi @@ -0,0 +1,10 @@ +# Test fixture for numbers +# +# The numbers module isn't properly supported, but we want to test that mypy +# can tell that it doesn't work as expected. + +class Number: pass +class Complex: pass +class Real: pass +class Rational: pass +class Integral: pass