Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/source/command_line.rst
Original file line number Diff line number Diff line change
Expand Up @@ -746,6 +746,10 @@ of the above sections.
Note that :option:`--strict-equality-for-none <mypy --strict-equality-for-none>`
only works in combination with :option:`--strict-equality <mypy --strict-equality>`.

.. option:: --strict-overload-subtyping

Require subtype overload order to match supertype overload order.

.. option:: --strict-bytes

By default, mypy treats ``bytearray`` and ``memoryview`` as subtypes of ``bytes`` which
Expand Down
7 changes: 7 additions & 0 deletions docs/source/config_file.rst
Original file line number Diff line number Diff line change
Expand Up @@ -844,6 +844,13 @@ section of the command line docs.
Include ``None`` in strict equality checks (requires :confval:`strict_equality`
to be activated).

.. confval:: strict_overload_subtyping

:type: boolean
:default: False

Require subtype overload order to match supertype overload order.

.. confval:: strict_bytes

:type: boolean
Expand Down
2 changes: 1 addition & 1 deletion mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -2499,7 +2499,7 @@ def check_override(
# Use boolean variable to clarify code.
fail = False
op_method_wider_note = False
if not is_subtype(override, original, ignore_pos_arg_names=True):
if not is_subtype(override, original, ignore_pos_arg_names=True, options=self.options):
fail = True
elif isinstance(override, Overloaded) and self.is_forward_op_method(name):
# Operator method overrides cannot extend the domain, as
Expand Down
8 changes: 8 additions & 0 deletions mypy/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -929,6 +929,14 @@ def add_invertible_flag(
group=strictness_group,
)

add_invertible_flag(
"--strict-overload-subtyping",
default=False,
strict_flag=False,
help="Require subtype overload order to match supertype overload order.",
group=strictness_group,
)

add_invertible_flag(
"--strict-bytes",
default=False,
Expand Down
4 changes: 4 additions & 0 deletions mypy/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ class BuildType:
"strict_concatenate",
"strict_equality",
"strict_equality_for_none",
"strict_overload_subtyping",
"strict_optional",
"warn_no_return",
"warn_return_any",
Expand Down Expand Up @@ -235,6 +236,9 @@ def __init__(self) -> None:
# Extend the logic of `strict_equality` to comparisons with `None`.
self.strict_equality_for_none = False

# Enforce strict ordering for overloads.
self.strict_overload_subtyping = False

# Disable treating bytearray and memoryview as subtypes of bytes
self.strict_bytes = False

Expand Down
8 changes: 7 additions & 1 deletion mypy/subtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1002,7 +1002,13 @@ def visit_overloaded(self, left: Overloaded) -> bool:

# Order matters: we need to make sure that the index of
# this item is at least the index of the previous one.
if subtype_match and previous_match_left_index <= left_index:
strict_overload_subtyping = (
self.options.strict_overload_subtyping if self.options else False
)
if subtype_match and (
(not strict_overload_subtyping)
or (previous_match_left_index <= left_index)
):
previous_match_left_index = left_index
found_match = True
matched_overloads.add(left_index)
Expand Down
1 change: 1 addition & 0 deletions test-data/unit/check-errorcodes.test
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ y = [] # E: Need type annotation for "y" (hint: "y: list[<type>] = ...") [var-
[builtins fixtures/list.pyi]

[case testErrorCodeBadOverride]
# flags: --strict-overload-subtyping
from typing import overload

class A:
Expand Down
60 changes: 60 additions & 0 deletions test-data/unit/check-overloading.test
Original file line number Diff line number Diff line change
Expand Up @@ -985,7 +985,65 @@ A() + '' # E: No overload variant of "__add__" of "A" matches argument type "str
# N: def __add__(self, A, /) -> int \
# N: def __add__(self, int, /) -> int

[case testAllowOverrideOverloadSwapped]
# https://github.com/python/mypy/issues/20720
from foo import *
[file foo.pyi]
from typing import overload

def test_mutually_exclusive_types() -> None:
# note: int and str are mutually exclusive types (@disjoint_base)
class Parent:
@overload
def f(self, x: int) -> int: ...
@overload
def f(self, x: str) -> str: ...
class Child(Parent):
@overload
def f(self, x: str) -> str: ...
@overload
def f(self, x: int) -> int: ...

def test_mutually_exclusive_signatures() -> None:
# the overload call-signatures are mutually exclusive,
# so swapping is safe even if intersections exist
class X: ...
class Y: ...
class A: ...
class B: ...

class Parent:
@overload
def f(self, *, x: X) -> A: ...
@overload
def f(self, *, y: Y) -> B: ...
class Child(Parent):
@overload
def f(self, *, y: Y) -> B: ...
@overload
def f(self, *, x: X) -> A: ...

def test_same_signature_and_return() -> None:
# swapping is safe if the return types are the same, even
# even if argument types overlap

class X: ...
class Y: ...

class Parent:
@overload
def f(self, x: X, /) -> None: ...
@overload
def f(self, y: Y, /) -> None: ...
class Child(Parent):
@overload
def f(self, y: Y, /) -> None: ...
@overload
def f(self, x: X, /) -> None: ...


[case testOverrideOverloadSwapped]
# flags: --strict-overload-subtyping
from foo import *
[file foo.pyi]
from typing import overload
Expand All @@ -1003,6 +1061,7 @@ class Child(Parent):
def f(self, x: int) -> int: ...

[case testOverrideOverloadSwappedWithExtraVariants]
# flags: --strict-overload-subtyping
from foo import *
[file foo.pyi]
from typing import overload
Expand Down Expand Up @@ -1040,6 +1099,7 @@ class Child3(Parent):
def f(self, x: bool) -> bool: ...

[case testOverrideOverloadSwappedWithAdjustedVariants]
# flags: --strict-overload-subtyping
from foo import *
[file foo.pyi]
from typing import overload
Expand Down