Skip to content

Commit 8125e31

Browse files
committed
Tidy up the loop in check_overlapping_overloads, reducing the None checking by preparing the list of valid items
1 parent 86dc7fe commit 8125e31

File tree

1 file changed

+28
-41
lines changed

1 file changed

+28
-41
lines changed

mypy/checker.py

Lines changed: 28 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -913,37 +913,24 @@ def check_overlapping_overloads(self, defn: OverloadedFuncDef) -> None:
913913

914914
is_descriptor_get = defn.info and defn.name == "__get__"
915915

916-
# Pre-extract callable types and literal fingerprints for each overload item.
917-
item_sigs: list[CallableType | None] = []
918-
item_literal_fingerprints: list[LiteralFingerprint] = []
919-
for item in defn.items:
920-
assert isinstance(item, Decorator)
921-
sig = self.extract_callable_type(item.var.type, item)
922-
item_sigs.append(sig)
923-
item_literal_fingerprints.append(
924-
build_literal_fingerprint(sig) if sig is not None else {}
925-
)
926-
916+
# Pre-extract callable types and literal fingerprints for each overload
917+
# item, skipping items whose signature could not be extracted.
918+
# Each entry is (original 0-based index, Decorator, sig, fingerprint).
919+
prepared_items: list[tuple[int, Decorator, CallableType, LiteralFingerprint]] = []
927920
for i, item in enumerate(defn.items):
928921
assert isinstance(item, Decorator)
929-
sig1 = item_sigs[i]
930-
if sig1 is None:
931-
continue
932-
933-
for j, item2 in enumerate(defn.items[i + 1 :], i + 1):
934-
assert isinstance(item2, Decorator)
935-
sig2 = item_sigs[j]
936-
if sig2 is None:
937-
continue
922+
sig = self.extract_callable_type(item.var.type, item)
923+
if sig is not None:
924+
prepared_items.append((i, item, sig, build_literal_fingerprint(sig)))
938925

926+
for prepared_items_i, (i, item, sig1, literals_fingerprint1) in enumerate(prepared_items):
927+
for j, item2, sig2, literals_fingerprint2 in prepared_items[prepared_items_i + 1 :]:
939928
if not are_argument_counts_overlapping(sig1, sig2):
940929
continue
941930

942931
# If there is any argument position where both overloads
943932
# carry a LiteralType with different values they are disjoint.
944-
if literal_args_are_disjoint(
945-
item_literal_fingerprints[i], item_literal_fingerprints[j]
946-
):
933+
if literal_args_are_disjoint(literals_fingerprint1, literals_fingerprint2):
947934
continue
948935

949936
if overload_can_never_match(sig1, sig2):
@@ -8978,36 +8965,22 @@ def detach_callable(typ: CallableType, class_type_vars: list[TypeVarLikeType]) -
89788965
return typ.copy_modified(variables=list(typ.variables) + class_type_vars)
89798966

89808967

8981-
# Fingerprint type for literal-disjointness checks: maps argument index to
8968+
# Fingerprint type for literal-disjointedness checks: maps argument index to
89828969
# the set of (Python type of value, value) pairs present at that position.
89838970
# Using type(value) as part of the key means Literal[1] (int) and
89848971
# Literal[True] (bool) are kept distinct even though 1 == True in Python.
89858972
# A union such as Literal["a", "b"] or Literal["a"] | Literal["b"] produces
8986-
# a frozenset of two entries; a plain Literal["a"] produces a singleton set.
8973+
# a frozenset of two entries; a plain Literal["a"] produces a length 1 set.
89878974
LiteralFingerprint = dict[int, frozenset[tuple[type, LiteralValue]]]
89888975

89898976

8990-
def literal_args_are_disjoint(fp1: LiteralFingerprint, fp2: LiteralFingerprint) -> bool:
8991-
"""Return True if two overloads are provably disjoint via a Literal argument.
8992-
8993-
If there is any argument position where both carry only LiteralType values
8994-
and those value sets are disjoint, no single call can match both overloads
8995-
and the pairwise overlap check can be skipped entirely.
8996-
"""
8997-
for idx, vals1 in fp1.items():
8998-
vals2 = fp2.get(idx)
8999-
if vals2 is not None and vals1.isdisjoint(vals2):
9000-
return True
9001-
return False
9002-
9003-
90048977
def build_literal_fingerprint(sig: CallableType) -> LiteralFingerprint:
90058978
"""Build a LiteralFingerprint for one overload signature.
90068979
90078980
Each argument position that carries only LiteralType values (including
90088981
unions such as ``Literal["a", "b"]``) is recorded as a frozenset of
90098982
``(type(value), value)`` pairs. Positions with any non-literal type are
9010-
omitted so the disjointness check is conservative.
8983+
omitted so the disjointedness check is conservative.
90118984
"""
90128985
fingerprint: LiteralFingerprint = {}
90138986
for idx, arg_type in enumerate(sig.arg_types):
@@ -9019,7 +8992,7 @@ def build_literal_fingerprint(sig: CallableType) -> LiteralFingerprint:
90198992
# represented as a UnionType of LiteralTypes. Collect all the
90208993
# literal values; if any member is not a LiteralType the whole
90218994
# position is skipped (a non-literal in the union makes it too
9022-
# broad to prove disjointness).
8995+
# broad to prove disjointedness).
90238996
vals: set[tuple[type, LiteralValue]] = set()
90248997
for member in proper.items:
90258998
m = get_proper_type(member)
@@ -9033,6 +9006,20 @@ def build_literal_fingerprint(sig: CallableType) -> LiteralFingerprint:
90339006
return fingerprint
90349007

90359008

9009+
def literal_args_are_disjoint(fp1: LiteralFingerprint, fp2: LiteralFingerprint) -> bool:
9010+
"""Return True if two overloads are provably disjoint via a Literal argument.
9011+
9012+
If there is any argument position where both carry only LiteralType values
9013+
and those value sets are disjoint, no single call can match both overloads
9014+
and the pairwise overlap check can be skipped entirely.
9015+
"""
9016+
for idx, vals1 in fp1.items():
9017+
vals2 = fp2.get(idx)
9018+
if vals2 is not None and vals1.isdisjoint(vals2):
9019+
return True
9020+
return False
9021+
9022+
90369023
def overload_can_never_match(signature: CallableType, other: CallableType) -> bool:
90379024
"""Check if the 'other' method can never be matched due to 'signature'.
90389025

0 commit comments

Comments
 (0)