Skip to content

Commit 7a66caf

Browse files
committed
Fix literal fingerprint fast-path incorrectly skipping optional arguments
build_literal_fingerprint included optional arguments (ARG_OPT / ARG_NAMED_OPT) in the fingerprint used to detect disjoint overloads. When two overloads differed only in an optional Literal arg (e.g. as_index: Literal[True] vs Literal[False]), the fast-path treated them as mutually exclusive and skipped the full overlap check - even though a caller can omit the argument entirely, matching both overloads through their other arguments. Fix by only fingerprinting required arguments (ARG_POS and ARG_NAMED). Optional arguments can always be omitted by the caller, so they can never prove two overloads are disjoint. The real-world symptom was pandas-stubs gaining spurious unused-ignore errors: overloads that master correctly flagged as overlapping were silently skipped by the fast-path, making existing type: ignore redundant.
1 parent 8125e31 commit 7a66caf

2 files changed

Lines changed: 35 additions & 5 deletions

File tree

mypy/checker.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8977,13 +8977,21 @@ def detach_callable(typ: CallableType, class_type_vars: list[TypeVarLikeType]) -
89778977
def build_literal_fingerprint(sig: CallableType) -> LiteralFingerprint:
89788978
"""Build a LiteralFingerprint for one overload signature.
89798979
8980-
Each argument position that carries only LiteralType values (including
8981-
unions such as ``Literal["a", "b"]``) is recorded as a frozenset of
8982-
``(type(value), value)`` pairs. Positions with any non-literal type are
8983-
omitted so the disjointedness check is conservative.
8980+
Each *required* argument position (ARG_POS or ARG_NAMED) that carries only
8981+
LiteralType values (including unions such as ``Literal["a", "b"]``) is
8982+
recorded as a frozenset of ``(type(value), value)`` pairs. Positions with
8983+
any non-literal type, or with an optional argument kind (ARG_OPT,
8984+
ARG_NAMED_OPT, ARG_STAR, ARG_STAR2), are omitted.
8985+
8986+
Optional arguments are excluded because a caller can always omit them,
8987+
meaning two overloads that differ only in an optional Literal argument still
8988+
overlap (a call that omits the argument matches both). Only required
8989+
arguments can prove that no single call can match both overloads.
89848990
"""
89858991
fingerprint: LiteralFingerprint = {}
8986-
for idx, arg_type in enumerate(sig.arg_types):
8992+
for idx, (arg_kind, arg_type) in enumerate(zip(sig.arg_kinds, sig.arg_types)):
8993+
if not arg_kind.is_required():
8994+
continue
89878995
proper = get_proper_type(arg_type)
89888996
if isinstance(proper, LiteralType):
89898997
fingerprint[idx] = frozenset([(type(proper.value), proper.value)])

test-data/unit/check-overloading.test

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6963,3 +6963,25 @@ def f(x: Union[Literal["b"], str]) -> str: ...
69636963
def f(x: str) -> object:
69646964
return x
69656965
[builtins fixtures/tuple.pyi]
6966+
6967+
[case testOverloadLiteralOptionalArgNotDisjoint]
6968+
# Overloads that differ in an optional positional Literal arg still overlap:
6969+
# a call like f(1) omits mode and matches both signatures.
6970+
from typing import Literal, overload
6971+
@overload
6972+
def f(x: int, mode: Literal[True] = ...) -> int: ... # E: Overloaded function signatures 1 and 2 overlap with incompatible return types # N: Flipping the order of overloads will fix this error
6973+
@overload
6974+
def f(x: object, mode: Literal[False] = ...) -> str: ...
6975+
def f(x: object, mode: bool = True) -> object: ...
6976+
[builtins fixtures/tuple.pyi]
6977+
6978+
[case testOverloadLiteralOptionalKwOnlyNotDisjoint]
6979+
# Overloads that differ in an optional keyword-only Literal arg still overlap:
6980+
# a call like f(1) omits mode and matches both signatures.
6981+
from typing import Literal, overload
6982+
@overload
6983+
def f(x: int, *, mode: Literal[True] = ...) -> int: ... # E: Overloaded function signatures 1 and 2 overlap with incompatible return types # N: Flipping the order of overloads will fix this error
6984+
@overload
6985+
def f(x: object, *, mode: Literal[False] = ...) -> str: ...
6986+
def f(x: object, *, mode: bool = True) -> object: ...
6987+
[builtins fixtures/tuple.pyi]

0 commit comments

Comments
 (0)