Skip to content

Commit 9a50d9a

Browse files
authored
Merge branch 'master' into match-subject-inference
2 parents df6f5dd + 6e449e7 commit 9a50d9a

File tree

7 files changed

+245
-62
lines changed

7 files changed

+245
-62
lines changed

mypy/checkexpr.py

Lines changed: 30 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@
120120
from mypy.semanal_enum import ENUM_BASES
121121
from mypy.state import state
122122
from mypy.subtypes import (
123+
covers_at_runtime,
123124
find_member,
124125
is_equivalent,
125126
is_same_type,
@@ -154,7 +155,6 @@
154155
get_type_vars,
155156
is_literal_type_like,
156157
make_simplified_union,
157-
simple_literal_type,
158158
true_only,
159159
try_expanding_sum_type_to_union,
160160
try_getting_str_literals,
@@ -4049,14 +4049,21 @@ def lookup_definer(typ: Instance, attr_name: str) -> str | None:
40494049

40504050
variants_raw = [(op_name, left_op, left_type, right_expr)]
40514051
elif (
4052-
is_subtype(right_type, left_type)
4053-
and isinstance(left_type, Instance)
4054-
and isinstance(right_type, Instance)
4055-
and not (
4056-
left_type.type.alt_promote is not None
4057-
and left_type.type.alt_promote.type is right_type.type
4052+
(
4053+
# Checking (A implies B) using the logically equivalent (not A or B), where
4054+
# A: left and right are both `Instance` objects
4055+
# B: right's __rop__ method is different from left's __op__ method
4056+
not (isinstance(left_type, Instance) and isinstance(right_type, Instance))
4057+
or (
4058+
lookup_definer(left_type, op_name) != lookup_definer(right_type, rev_op_name)
4059+
and (
4060+
left_type.type.alt_promote is None
4061+
or left_type.type.alt_promote.type is not right_type.type
4062+
)
4063+
)
40584064
)
4059-
and lookup_definer(left_type, op_name) != lookup_definer(right_type, rev_op_name)
4065+
# Note: use `covers_at_runtime` instead of `is_subtype` (#19006)
4066+
and covers_at_runtime(right_type, left_type)
40604067
):
40614068
# When we do "A() + B()" where B is a subclass of A, we'll actually try calling
40624069
# B's __radd__ method first, but ONLY if B explicitly defines or overrides the
@@ -5899,7 +5906,7 @@ def check_for_comp(self, e: GeneratorExpr | DictionaryComprehension) -> None:
58995906

59005907
def visit_conditional_expr(self, e: ConditionalExpr, allow_none_return: bool = False) -> Type:
59015908
self.accept(e.cond)
5902-
ctx = self.type_context[-1]
5909+
ctx: Type | None = self.type_context[-1]
59035910

59045911
# Gain type information from isinstance if it is there
59055912
# but only for the current expression
@@ -5910,63 +5917,26 @@ def visit_conditional_expr(self, e: ConditionalExpr, allow_none_return: bool = F
59105917
elif else_map is None:
59115918
self.msg.redundant_condition_in_if(True, e.cond)
59125919

5920+
if ctx is None:
5921+
# When no context is provided, compute each branch individually, and
5922+
# use the union of the results as artificial context. Important for:
5923+
# - testUnificationDict
5924+
# - testConditionalExpressionWithEmpty
5925+
ctx_if_type = self.analyze_cond_branch(
5926+
if_map, e.if_expr, context=ctx, allow_none_return=allow_none_return
5927+
)
5928+
ctx_else_type = self.analyze_cond_branch(
5929+
else_map, e.else_expr, context=ctx, allow_none_return=allow_none_return
5930+
)
5931+
ctx = make_simplified_union([ctx_if_type, ctx_else_type])
5932+
59135933
if_type = self.analyze_cond_branch(
59145934
if_map, e.if_expr, context=ctx, allow_none_return=allow_none_return
59155935
)
5916-
5917-
# we want to keep the narrowest value of if_type for union'ing the branches
5918-
# however, it would be silly to pass a literal as a type context. Pass the
5919-
# underlying fallback type instead.
5920-
if_type_fallback = simple_literal_type(get_proper_type(if_type)) or if_type
5921-
5922-
# Analyze the right branch using full type context and store the type
5923-
full_context_else_type = self.analyze_cond_branch(
5936+
else_type = self.analyze_cond_branch(
59245937
else_map, e.else_expr, context=ctx, allow_none_return=allow_none_return
59255938
)
59265939

5927-
if not mypy.checker.is_valid_inferred_type(if_type, self.chk.options):
5928-
# Analyze the right branch disregarding the left branch.
5929-
else_type = full_context_else_type
5930-
# we want to keep the narrowest value of else_type for union'ing the branches
5931-
# however, it would be silly to pass a literal as a type context. Pass the
5932-
# underlying fallback type instead.
5933-
else_type_fallback = simple_literal_type(get_proper_type(else_type)) or else_type
5934-
5935-
# If it would make a difference, re-analyze the left
5936-
# branch using the right branch's type as context.
5937-
if ctx is None or not is_equivalent(else_type_fallback, ctx):
5938-
# TODO: If it's possible that the previous analysis of
5939-
# the left branch produced errors that are avoided
5940-
# using this context, suppress those errors.
5941-
if_type = self.analyze_cond_branch(
5942-
if_map,
5943-
e.if_expr,
5944-
context=else_type_fallback,
5945-
allow_none_return=allow_none_return,
5946-
)
5947-
5948-
elif if_type_fallback == ctx:
5949-
# There is no point re-running the analysis if if_type is equal to ctx.
5950-
# That would be an exact duplicate of the work we just did.
5951-
# This optimization is particularly important to avoid exponential blowup with nested
5952-
# if/else expressions: https://github.com/python/mypy/issues/9591
5953-
# TODO: would checking for is_proper_subtype also work and cover more cases?
5954-
else_type = full_context_else_type
5955-
else:
5956-
# Analyze the right branch in the context of the left
5957-
# branch's type.
5958-
else_type = self.analyze_cond_branch(
5959-
else_map,
5960-
e.else_expr,
5961-
context=if_type_fallback,
5962-
allow_none_return=allow_none_return,
5963-
)
5964-
5965-
# In most cases using if_type as a context for right branch gives better inferred types.
5966-
# This is however not the case for literal types, so use the full context instead.
5967-
if is_literal_type_like(full_context_else_type) and not is_literal_type_like(else_type):
5968-
else_type = full_context_else_type
5969-
59705940
res: Type = make_simplified_union([if_type, else_type])
59715941
if has_uninhabited_component(res) and not isinstance(
59725942
get_proper_type(self.type_context[-1]), UnionType

mypyc/irbuild/statement.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -598,7 +598,9 @@ def make_entry(type: Expression) -> tuple[ValueGenFunc, int]:
598598
(make_entry(type) if type else None, var, make_handler(body))
599599
for type, var, body in zip(t.types, t.vars, t.handlers)
600600
]
601-
else_body = (lambda: builder.accept(t.else_body)) if t.else_body else None
601+
602+
_else_body = t.else_body
603+
else_body = (lambda: builder.accept(_else_body)) if _else_body else None
602604
transform_try_except(builder, body, handlers, else_body, t.line)
603605

604606

test-data/unit/check-expressions.test

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -681,6 +681,29 @@ class B:
681681
s: str
682682
s = A() + B() # E: Unsupported operand types for + ("A" and "B")
683683

684+
685+
[case testReverseBinaryOperator4]
686+
from typing import assert_type, Never
687+
688+
class Size(tuple[int, ...]):
689+
def __add__(self, other: tuple[int, ...], /) -> "Size": return Size() # type: ignore[override]
690+
def __radd__(self, other: tuple[int, ...], /) -> "Size": return Size()
691+
692+
size: Size = Size([3, 4])
693+
tup0: tuple[()] = ()
694+
tup1: tuple[int] = (1,)
695+
tup2: tuple[int, int] = (1, 2)
696+
tupN: tuple[int, ...] = (1, 2, 3)
697+
tupX: tuple[Never, ...] = ()
698+
699+
assert_type(tup0 + size, Size)
700+
assert_type(tup1 + size, Size)
701+
assert_type(tup2 + size, Size)
702+
assert_type(tupN + size, Size)
703+
assert_type(tupX + size, Size)
704+
705+
[builtins fixtures/tuple-typeshed.pyi]
706+
684707
[case testBinaryOperatorWithAnyRightOperand]
685708
from typing import Any, cast
686709
class A: pass

test-data/unit/check-literal.test

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2949,6 +2949,115 @@ reveal_type(C().collection) # N: Revealed type is "builtins.list[Literal['word'
29492949
reveal_type(C().word) # N: Revealed type is "Literal['word']"
29502950
[builtins fixtures/tuple.pyi]
29512951

2952+
[case testStringIntUnionTernary]
2953+
# https://github.com/python/mypy/issues/19534
2954+
def test(b: bool) -> None:
2955+
l = 1 if b else "a"
2956+
reveal_type(l) # N: Revealed type is "builtins.int | builtins.str"
2957+
[builtins fixtures/tuple.pyi]
2958+
2959+
[case testListComprehensionTernary]
2960+
# https://github.com/python/mypy/issues/19534
2961+
def test(b: bool) -> None:
2962+
l = [1] if b else ["a"]
2963+
reveal_type(l) # N: Revealed type is "builtins.list[builtins.int] | builtins.list[builtins.str]"
2964+
[builtins fixtures/list.pyi]
2965+
2966+
[case testLambdaTernary]
2967+
from typing import TypeVar, Union, Callable, reveal_type
2968+
2969+
NOOP = lambda: None
2970+
class A: pass
2971+
class B:
2972+
attr: Union[A, None]
2973+
2974+
def test_static(x: Union[A, None]) -> None:
2975+
def foo(t: A) -> None: ...
2976+
2977+
l1: Callable[[], object] = (lambda: foo(x)) if x is not None else NOOP
2978+
r1: Callable[[], object] = NOOP if x is None else (lambda: foo(x))
2979+
l2 = (lambda: foo(x)) if x is not None else NOOP
2980+
r2 = NOOP if x is None else (lambda: foo(x))
2981+
reveal_type(l2) # N: Revealed type is "def ()"
2982+
reveal_type(r2) # N: Revealed type is "def ()"
2983+
2984+
def test_generic(x: Union[A, None]) -> None:
2985+
T = TypeVar("T")
2986+
def bar(t: T) -> T: return t
2987+
2988+
l1: Callable[[], None] = (lambda: bar(x)) if x is None else NOOP
2989+
r1: Callable[[], None] = NOOP if x is not None else (lambda: bar(x))
2990+
l2 = (lambda: bar(x)) if x is None else NOOP
2991+
r2 = NOOP if x is not None else (lambda: bar(x))
2992+
reveal_type(l2) # N: Revealed type is "def ()"
2993+
reveal_type(r2) # N: Revealed type is "def ()"
2994+
2995+
2996+
[case testLambdaTernaryIndirectAttribute]
2997+
# fails due to binder issue inside `check_func_def`
2998+
# https://github.com/python/mypy/issues/19561
2999+
from typing import TypeVar, Union, Callable, reveal_type
3000+
3001+
NOOP = lambda: None
3002+
class A: pass
3003+
class B:
3004+
attr: Union[A, None]
3005+
3006+
def test_static_with_attr(x: B) -> None:
3007+
def foo(t: A) -> None: ...
3008+
3009+
l1: Callable[[], None] = (lambda: foo(x.attr)) if x.attr is not None else NOOP # E: Argument 1 to "foo" has incompatible type "A | None"; expected "A"
3010+
r1: Callable[[], None] = NOOP if x.attr is None else (lambda: foo(x.attr)) # E: Argument 1 to "foo" has incompatible type "A | None"; expected "A"
3011+
l2 = (lambda: foo(x.attr)) if x.attr is not None else NOOP # E: Argument 1 to "foo" has incompatible type "A | None"; expected "A"
3012+
r2 = NOOP if x.attr is None else (lambda: foo(x.attr)) # E: Argument 1 to "foo" has incompatible type "A | None"; expected "A"
3013+
reveal_type(l2) # N: Revealed type is "def ()"
3014+
reveal_type(r2) # N: Revealed type is "def ()"
3015+
3016+
def test_generic_with_attr(x: B) -> None:
3017+
T = TypeVar("T")
3018+
def bar(t: T) -> T: return t
3019+
3020+
l1: Callable[[], None] = (lambda: bar(x.attr)) if x.attr is None else NOOP # E: Incompatible types in assignment (expression has type "Callable[[], A | None]", variable has type "Callable[[], None]")
3021+
r1: Callable[[], None] = NOOP if x.attr is not None else (lambda: bar(x.attr)) # E: Incompatible types in assignment (expression has type "Callable[[], A | None]", variable has type "Callable[[], None]")
3022+
l2 = (lambda: bar(x.attr)) if x.attr is None else NOOP
3023+
r2 = NOOP if x.attr is not None else (lambda: bar(x.attr))
3024+
reveal_type(l2) # N: Revealed type is "def () -> __main__.A | None"
3025+
reveal_type(r2) # N: Revealed type is "def () -> __main__.A | None"
3026+
3027+
[case testLambdaTernaryDoubleIndirectAttribute]
3028+
# fails due to binder issue inside `check_func_def`
3029+
# https://github.com/python/mypy/issues/19561
3030+
from typing import TypeVar, Union, Callable, reveal_type
3031+
3032+
NOOP = lambda: None
3033+
class A: pass
3034+
class B:
3035+
attr: Union[A, None]
3036+
class C:
3037+
attr: B
3038+
3039+
def test_static_with_attr(x: C) -> None:
3040+
def foo(t: A) -> None: ...
3041+
3042+
l1: Callable[[], None] = (lambda: foo(x.attr.attr)) if x.attr.attr is not None else NOOP # E: Argument 1 to "foo" has incompatible type "A | None"; expected "A"
3043+
r1: Callable[[], None] = NOOP if x.attr.attr is None else (lambda: foo(x.attr.attr)) # E: Argument 1 to "foo" has incompatible type "A | None"; expected "A"
3044+
l2 = (lambda: foo(x.attr.attr)) if x.attr.attr is not None else NOOP # E: Argument 1 to "foo" has incompatible type "A | None"; expected "A"
3045+
r2 = NOOP if x.attr.attr is None else (lambda: foo(x.attr.attr)) # E: Argument 1 to "foo" has incompatible type "A | None"; expected "A"
3046+
reveal_type(l2) # N: Revealed type is "def ()"
3047+
reveal_type(r2) # N: Revealed type is "def ()"
3048+
3049+
def test_generic_with_attr(x: C) -> None:
3050+
T = TypeVar("T")
3051+
def bar(t: T) -> T: return t
3052+
3053+
l1: Callable[[], None] = (lambda: bar(x.attr.attr)) if x.attr.attr is None else NOOP # E: Incompatible types in assignment (expression has type "Callable[[], A | None]", variable has type "Callable[[], None]")
3054+
r1: Callable[[], None] = NOOP if x.attr.attr is not None else (lambda: bar(x.attr.attr)) # E: Incompatible types in assignment (expression has type "Callable[[], A | None]", variable has type "Callable[[], None]")
3055+
l2 = (lambda: bar(x.attr.attr)) if x.attr.attr is None else NOOP
3056+
r2 = NOOP if x.attr.attr is not None else (lambda: bar(x.attr.attr))
3057+
reveal_type(l2) # N: Revealed type is "def () -> __main__.A | None"
3058+
reveal_type(r2) # N: Revealed type is "def () -> __main__.A | None"
3059+
3060+
29523061
[case testLiteralTernaryUnionNarrowing]
29533062
from typing import Literal, Optional
29543063

test-data/unit/check-optional.test

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -428,7 +428,9 @@ reveal_type(l) # N: Revealed type is "builtins.list[typing.Generator[builtins.s
428428
[builtins fixtures/list.pyi]
429429

430430
[case testNoneListTernary]
431-
x = [None] if "" else [1] # E: List item 0 has incompatible type "int"; expected "None"
431+
# https://github.com/python/mypy/issues/19534
432+
x = [None] if "" else [1]
433+
reveal_type(x) # N: Revealed type is "builtins.list[None] | builtins.list[builtins.int]"
432434
[builtins fixtures/list.pyi]
433435

434436
[case testListIncompatibleErrorMessage]

test-data/unit/check-python313.test

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,3 +319,21 @@ class A[Y = X, X = int]: ... # E: Name "X" is not defined
319319

320320
class B[Y = X]: ... # E: Name "X" is not defined
321321
[builtins fixtures/tuple.pyi]
322+
323+
324+
[case testTernaryOperatorWithTypeVarDefault]
325+
# https://github.com/python/mypy/issues/18817
326+
327+
class Ok[T, E = None]:
328+
def __init__(self, value: T) -> None:
329+
self._value = value
330+
331+
class Err[E, T = None]:
332+
def __init__(self, value: E) -> None:
333+
self._value = value
334+
335+
type Result[T, E] = Ok[T, E] | Err[E, T]
336+
337+
class Bar[U]:
338+
def foo(data: U, cond: bool) -> Result[U, str]:
339+
return Ok(data) if cond else Err("Error")
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# tuple definition from typeshed,
2+
from typing import (
3+
Generic,
4+
Sequence,
5+
TypeVar,
6+
Iterable,
7+
Iterator,
8+
Any,
9+
overload,
10+
Self,
11+
Protocol,
12+
)
13+
from types import GenericAlias
14+
15+
_T = TypeVar("_T")
16+
_T_co = TypeVar('_T_co', covariant=True)
17+
18+
class tuple(Sequence[_T_co], Generic[_T_co]):
19+
def __new__(cls, iterable: Iterable[_T_co] = ..., /) -> Self: ...
20+
def __len__(self) -> int: ...
21+
def __contains__(self, key: object, /) -> bool: ...
22+
@overload
23+
def __getitem__(self, key: SupportsIndex, /) -> _T_co: ...
24+
@overload
25+
def __getitem__(self, key: slice, /) -> tuple[_T_co, ...]: ...
26+
def __iter__(self) -> Iterator[_T_co]: ...
27+
def __lt__(self, value: tuple[_T_co, ...], /) -> bool: ...
28+
def __le__(self, value: tuple[_T_co, ...], /) -> bool: ...
29+
def __gt__(self, value: tuple[_T_co, ...], /) -> bool: ...
30+
def __ge__(self, value: tuple[_T_co, ...], /) -> bool: ...
31+
def __eq__(self, value: object, /) -> bool: ...
32+
def __hash__(self) -> int: ...
33+
@overload
34+
def __add__(self, value: tuple[_T_co, ...], /) -> tuple[_T_co, ...]: ...
35+
@overload
36+
def __add__(self, value: tuple[_T, ...], /) -> tuple[_T_co | _T, ...]: ...
37+
def __mul__(self, value: SupportsIndex, /) -> tuple[_T_co, ...]: ...
38+
def __rmul__(self, value: SupportsIndex, /) -> tuple[_T_co, ...]: ...
39+
def count(self, value: Any, /) -> int: ...
40+
def index(self, value: Any, start: SupportsIndex = ..., stop: SupportsIndex = ..., /) -> int: ...
41+
def __class_getitem__(cls, item: Any, /) -> GenericAlias: ...
42+
43+
class dict: pass
44+
class int: pass
45+
class slice: pass
46+
class bool(int): pass
47+
class str: pass # For convenience
48+
class object: pass
49+
class type: pass
50+
class ellipsis: pass
51+
class SupportsIndex(Protocol):
52+
def __index__(self) -> int: pass
53+
class list(Sequence[_T], Generic[_T]):
54+
@overload
55+
def __getitem__(self, i: int) -> _T: ...
56+
@overload
57+
def __getitem__(self, s: slice) -> list[_T]: ...
58+
def __contains__(self, item: object) -> bool: ...
59+
def __iter__(self) -> Iterator[_T]: ...

0 commit comments

Comments
 (0)