From ed18fea5b17ef3a969b37b4906dd7c237ddb1825 Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Sat, 9 Sep 2023 23:35:07 -0700 Subject: [PATCH] Document and rename overload-overlap error code (#16074) A new error code was introduced in https://github.com/python/mypy/pull/16061 As per https://github.com/python/mypy/pull/16068, we didn't previously run doc builds on changes to errorcodes.py, causing tests to fail on master when this was merged. Renaming the code as per: https://github.com/python/mypy/pull/16061#issuecomment-1710613890 All type ignores should be unsafe, so we should save the unsafe adjective for things that are really unsafe. As it stands, there are many cases where overloads overlap somewhat benignly. Fixes #8656 --- docs/source/error_code_list.rst | 35 ++++++++++++++++++++++++++++ docs/source/more_types.rst | 5 +++- mypy/errorcodes.py | 4 ++-- mypy/messages.py | 2 +- mypy/types.py | 2 +- test-data/unit/check-errorcodes.test | 2 +- 6 files changed, 44 insertions(+), 6 deletions(-) diff --git a/docs/source/error_code_list.rst b/docs/source/error_code_list.rst index a865a4dd1532..4decd37e6e8a 100644 --- a/docs/source/error_code_list.rst +++ b/docs/source/error_code_list.rst @@ -1114,6 +1114,41 @@ Warn about cases where a bytes object may be converted to a string in an unexpec print(f"The alphabet starts with {b!r}") # The alphabet starts with b'abc' print(f"The alphabet starts with {b.decode('utf-8')}") # The alphabet starts with abc +.. _code-overload-overlap: + +Check that overloaded functions don't overlap [overload-overlap] +---------------------------------------------------------------- + +Warn if multiple ``@overload`` variants overlap in potentially unsafe ways. +This guards against the following situation: + +.. code-block:: python + + from typing import overload + + class A: ... + class B(A): ... + + @overload + def foo(x: B) -> int: ... # Error: Overloaded function signatures 1 and 2 overlap with incompatible return types [overload-overlap] + @overload + def foo(x: A) -> str: ... + def foo(x): ... + + def takes_a(a: A) -> str: + return foo(a) + + a: A = B() + value = takes_a(a) + # mypy will think that value is a str, but it could actually be an int + reveal_type(value) # Revealed type is "builtins.str" + + +Note that in cases where you ignore this error, mypy will usually still infer the +types you expect. + +See :ref:`overloading ` for more explanation. + .. _code-annotation-unchecked: Notify about an annotation in an unchecked function [annotation-unchecked] diff --git a/docs/source/more_types.rst b/docs/source/more_types.rst index 4e6e9204fdca..b27764a9e87c 100644 --- a/docs/source/more_types.rst +++ b/docs/source/more_types.rst @@ -501,7 +501,7 @@ To prevent these kinds of issues, mypy will detect and prohibit inherently unsaf overlapping overloads on a best-effort basis. Two variants are considered unsafely overlapping when both of the following are true: -1. All of the arguments of the first variant are compatible with the second. +1. All of the arguments of the first variant are potentially compatible with the second. 2. The return type of the first variant is *not* compatible with (e.g. is not a subtype of) the second. @@ -510,6 +510,9 @@ the ``object`` argument in the second, yet the ``int`` return type is not a subt ``str``. Both conditions are true, so mypy will correctly flag ``unsafe_func`` as being unsafe. +Note that in cases where you ignore the overlapping overload error, mypy will usually +still infer the types you expect at callsites. + However, mypy will not detect *all* unsafe uses of overloads. For example, suppose we modify the above snippet so it calls ``summarize`` instead of ``unsafe_func``: diff --git a/mypy/errorcodes.py b/mypy/errorcodes.py index 70b8cffe9053..cd9978c2f31c 100644 --- a/mypy/errorcodes.py +++ b/mypy/errorcodes.py @@ -262,8 +262,8 @@ def __hash__(self) -> int: # This is a catch-all for remaining uncategorized errors. MISC: Final = ErrorCode("misc", "Miscellaneous other checks", "General") -UNSAFE_OVERLOAD: Final[ErrorCode] = ErrorCode( - "unsafe-overload", +OVERLOAD_OVERLAP: Final[ErrorCode] = ErrorCode( + "overload-overlap", "Warn if multiple @overload variants overlap in unsafe ways", "General", sub_code_of=MISC, diff --git a/mypy/messages.py b/mypy/messages.py index a58c5f91c4b1..b6fdaf06a8e0 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -1604,7 +1604,7 @@ def overloaded_signatures_overlap(self, index1: int, index2: int, context: Conte "Overloaded function signatures {} and {} overlap with " "incompatible return types".format(index1, index2), context, - code=codes.UNSAFE_OVERLOAD, + code=codes.OVERLOAD_OVERLAP, ) def overloaded_signature_will_never_match( diff --git a/mypy/types.py b/mypy/types.py index cee4595b67cc..04d90c9dc124 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -3019,7 +3019,7 @@ def get_proper_type(typ: Type | None) -> ProperType | None: @overload -def get_proper_types(types: list[Type] | tuple[Type, ...]) -> list[ProperType]: # type: ignore[unsafe-overload] +def get_proper_types(types: list[Type] | tuple[Type, ...]) -> list[ProperType]: # type: ignore[overload-overlap] ... diff --git a/test-data/unit/check-errorcodes.test b/test-data/unit/check-errorcodes.test index 72edf2f22c05..ac7c8b4c9f9d 100644 --- a/test-data/unit/check-errorcodes.test +++ b/test-data/unit/check-errorcodes.test @@ -1077,7 +1077,7 @@ x = 1 # type: ignore # E: Unused "type: ignore" comment [unused-ignore] from typing import overload, Union @overload -def unsafe_func(x: int) -> int: ... # E: Overloaded function signatures 1 and 2 overlap with incompatible return types [unsafe-overload] +def unsafe_func(x: int) -> int: ... # E: Overloaded function signatures 1 and 2 overlap with incompatible return types [overload-overlap] @overload def unsafe_func(x: object) -> str: ... def unsafe_func(x: object) -> Union[int, str]: