Skip to content

Commit e7f95f8

Browse files
refactor visit_conditional_expr
1 parent 07d4a1b commit e7f95f8

File tree

4 files changed

+159
-56
lines changed

4 files changed

+159
-56
lines changed

mypy/checker.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1406,7 +1406,13 @@ def check_func_def(
14061406
new_frame: Frame | None = None
14071407
for frame in old_binder.frames:
14081408
for key, narrowed_type in frame.types.items():
1409-
key_var = extract_var_from_literal_hash(key)
1409+
# get the variable from the key, considering that it might be a
1410+
# nested MemberExpr like Foo.attr1.attr2.attr3
1411+
_key = key
1412+
while _key[0] == "Member":
1413+
_key = _key[1]
1414+
key_var = extract_var_from_literal_hash(_key)
1415+
14101416
if key_var is not None and not self.is_var_redefined_in_outer_context(
14111417
key_var, defn.line
14121418
):

mypy/checkexpr.py

Lines changed: 14 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,6 @@
141141
get_type_vars,
142142
is_literal_type_like,
143143
make_simplified_union,
144-
simple_literal_type,
145144
true_only,
146145
try_expanding_sum_type_to_union,
147146
try_getting_str_literals,
@@ -5903,63 +5902,26 @@ def visit_conditional_expr(self, e: ConditionalExpr, allow_none_return: bool = F
59035902
elif else_map is None:
59045903
self.msg.redundant_condition_in_if(True, e.cond)
59055904

5905+
if ctx is None:
5906+
# When no context is provided, compute each branch individually, and
5907+
# use the union of the results as artificial context. Important for:
5908+
# - testUnificationDict
5909+
# - testConditionalExpressionWithEmpty
5910+
ctx_if_type = self.analyze_cond_branch(
5911+
if_map, e.if_expr, context=ctx, allow_none_return=allow_none_return
5912+
)
5913+
ctx_else_type = self.analyze_cond_branch(
5914+
else_map, e.else_expr, context=ctx, allow_none_return=allow_none_return
5915+
)
5916+
ctx = make_simplified_union([ctx_if_type, ctx_else_type])
5917+
59065918
if_type = self.analyze_cond_branch(
59075919
if_map, e.if_expr, context=ctx, allow_none_return=allow_none_return
59085920
)
5909-
5910-
# we want to keep the narrowest value of if_type for union'ing the branches
5911-
# however, it would be silly to pass a literal as a type context. Pass the
5912-
# underlying fallback type instead.
5913-
if_type_fallback = simple_literal_type(get_proper_type(if_type)) or if_type
5914-
5915-
# Analyze the right branch using full type context and store the type
5916-
full_context_else_type = self.analyze_cond_branch(
5921+
else_type = self.analyze_cond_branch(
59175922
else_map, e.else_expr, context=ctx, allow_none_return=allow_none_return
59185923
)
59195924

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

test-data/unit/check-literal.test

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2946,6 +2946,140 @@ reveal_type(C().collection) # N: Revealed type is "builtins.list[Literal['word'
29462946
reveal_type(C().word) # N: Revealed type is "Literal['word']"
29472947
[builtins fixtures/tuple.pyi]
29482948

2949+
[case testStringLiteralTernary]
2950+
def test(b: bool) -> None:
2951+
l = "foo" if b else "bar"
2952+
reveal_type(l) # N: Revealed type is "builtins.str"
2953+
[builtins fixtures/tuple.pyi]
2954+
2955+
[case testintLiteralTernary]
2956+
def test(b: bool) -> None:
2957+
l = 0 if b else 1
2958+
reveal_type(l) # N: Revealed type is "builtins.int"
2959+
[builtins fixtures/tuple.pyi]
2960+
2961+
[case testStringIntUnionTernary]
2962+
def test(b: bool) -> None:
2963+
l = 1 if b else "a"
2964+
reveal_type(l) # N: Revealed type is "Union[builtins.int, builtins.str]"
2965+
[builtins fixtures/tuple.pyi]
2966+
2967+
[case testListComprehensionTernary]
2968+
# gh-19534
2969+
def test(b: bool) -> None:
2970+
l = [1] if b else ["a"]
2971+
reveal_type(l) # N: Revealed type is "Union[builtins.list[builtins.int], builtins.list[builtins.str]]"
2972+
[builtins fixtures/list.pyi]
2973+
2974+
[case testSetComprehensionTernary]
2975+
# gh-19534
2976+
def test(b: bool) -> None:
2977+
s = {1} if b else {"a"}
2978+
reveal_type(s) # N: Revealed type is "Union[builtins.set[builtins.int], builtins.set[builtins.str]]"
2979+
[builtins fixtures/set.pyi]
2980+
2981+
[case testDictComprehensionTernary]
2982+
# gh-19534
2983+
def test(b: bool) -> None:
2984+
d = {1:1} if "" else {"a": "a"}
2985+
reveal_type(d) # N: Revealed type is "Union[builtins.dict[builtins.int, builtins.int], builtins.dict[builtins.str, builtins.str]]"
2986+
[builtins fixtures/dict.pyi]
2987+
2988+
[case testLambdaTernary]
2989+
from typing import TypeVar, Union, Callable, reveal_type
2990+
2991+
NOOP = lambda: None
2992+
class A: pass
2993+
class B:
2994+
attr: Union[A, None]
2995+
2996+
def test_static(x: Union[A, None]) -> None:
2997+
def foo(t: A) -> None: ...
2998+
2999+
l1: Callable[[], object] = (lambda: foo(x)) if x is not None else NOOP
3000+
r1: Callable[[], object] = NOOP if x is None else (lambda: foo(x))
3001+
l2 = (lambda: foo(x)) if x is not None else NOOP
3002+
r2 = NOOP if x is None else (lambda: foo(x))
3003+
reveal_type(l2) # N: Revealed type is "def ()"
3004+
reveal_type(r2) # N: Revealed type is "def ()"
3005+
3006+
def test_generic(x: Union[A, None]) -> None:
3007+
T = TypeVar("T")
3008+
def bar(t: T) -> T: return t
3009+
3010+
l1: Callable[[], None] = (lambda: bar(x)) if x is None else NOOP
3011+
r1: Callable[[], None] = NOOP if x is not None else (lambda: bar(x))
3012+
l2 = (lambda: bar(x)) if x is None else NOOP
3013+
r2 = NOOP if x is not None else (lambda: bar(x))
3014+
reveal_type(l2) # N: Revealed type is "def ()"
3015+
reveal_type(r2) # N: Revealed type is "def ()"
3016+
3017+
3018+
[case testLambdaTernaryIndirectAttribute]
3019+
# fails due to binder issue inside `check_func_def`
3020+
# gh-19561
3021+
from typing import TypeVar, Union, Callable, reveal_type
3022+
3023+
NOOP = lambda: None
3024+
class A: pass
3025+
class B:
3026+
attr: Union[A, None]
3027+
3028+
def test_static_with_attr(x: B) -> None:
3029+
def foo(t: A) -> None: ...
3030+
3031+
l1: Callable[[], None] = (lambda: foo(x.attr)) if x.attr is not None else NOOP
3032+
r1: Callable[[], None] = NOOP if x.attr is None else (lambda: foo(x.attr))
3033+
l2 = (lambda: foo(x.attr)) if x.attr is not None else NOOP
3034+
r2 = NOOP if x.attr is None else (lambda: foo(x.attr))
3035+
reveal_type(l2) # N: Revealed type is "def ()"
3036+
reveal_type(r2) # N: Revealed type is "def ()"
3037+
3038+
def test_generic_with_attr(x: B) -> None:
3039+
T = TypeVar("T")
3040+
def bar(t: T) -> T: return t
3041+
3042+
l1: Callable[[], None] = (lambda: bar(x.attr)) if x.attr is None else NOOP
3043+
r1: Callable[[], None] = NOOP if x.attr is not None else (lambda: bar(x.attr))
3044+
l2 = (lambda: bar(x.attr)) if x.attr is None else NOOP
3045+
r2 = NOOP if x.attr is not None else (lambda: bar(x.attr))
3046+
reveal_type(l2) # N: Revealed type is "def ()"
3047+
reveal_type(r2) # N: Revealed type is "def ()"
3048+
3049+
[case testLambdaTernaryDoubleIndirectAttribute]
3050+
# fails due to binder issue inside `check_func_def`
3051+
# gh-19561
3052+
from typing import TypeVar, Union, Callable, reveal_type
3053+
3054+
NOOP = lambda: None
3055+
class A: pass
3056+
class B:
3057+
attr: Union[A, None]
3058+
class C:
3059+
attr: B
3060+
3061+
def test_static_with_attr(x: C) -> None:
3062+
def foo(t: A) -> None: ...
3063+
3064+
l1: Callable[[], None] = (lambda: foo(x.attr.attr)) if x.attr.attr is not None else NOOP
3065+
r1: Callable[[], None] = NOOP if x.attr.attr is None else (lambda: foo(x.attr.attr))
3066+
l2 = (lambda: foo(x.attr.attr)) if x.attr.attr is not None else NOOP
3067+
r2 = NOOP if x.attr.attr is None else (lambda: foo(x.attr.attr))
3068+
reveal_type(l2) # N: Revealed type is "def ()"
3069+
reveal_type(r2) # N: Revealed type is "def ()"
3070+
3071+
def test_generic_with_attr(x: C) -> None:
3072+
T = TypeVar("T")
3073+
def bar(t: T) -> T: return t
3074+
3075+
l1: Callable[[], None] = (lambda: bar(x.attr.attr)) if x.attr.attr is None else NOOP
3076+
r1: Callable[[], None] = NOOP if x.attr.attr is not None else (lambda: bar(x.attr.attr))
3077+
l2 = (lambda: bar(x.attr.attr)) if x.attr.attr is None else NOOP
3078+
r2 = NOOP if x.attr.attr is not None else (lambda: bar(x.attr.attr))
3079+
reveal_type(l2) # N: Revealed type is "def ()"
3080+
reveal_type(r2) # N: Revealed type is "def ()"
3081+
3082+
29493083
[case testLiteralTernaryUnionNarrowing]
29503084
from typing import Literal, Optional
29513085

test-data/unit/check-optional.test

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

429429
[case testNoneListTernary]
430-
x = [None] if "" else [1] # E: List item 0 has incompatible type "int"; expected "None"
430+
# gh-19534
431+
x = [None] if "" else [1]
432+
reveal_type(x) # N: Revealed type is "Union[builtins.list[None], builtins.list[builtins.int]]"
431433
[builtins fixtures/list.pyi]
432434

433435
[case testListIncompatibleErrorMessage]
@@ -1107,10 +1109,9 @@ class C:
11071109
a: Optional[str]
11081110

11091111
def attribute_narrowing(c: C) -> None:
1110-
# This case is not supported, since we can't keep track of assignments to attributes.
11111112
c.a = "x"
11121113
def nested() -> str:
1113-
return c.a # E: Incompatible return value type (got "Optional[str]", expected "str")
1114+
return c.a
11141115
nested()
11151116

11161117
def assignment_in_for(x: Optional[str]) -> None:

0 commit comments

Comments
 (0)