Skip to content

Commit e139a0d

Browse files
authored
Fix enum truthiness for StrEnum (#18379)
Fixes #18376 See also https://snarky.ca/unravelling-not-in-python/
1 parent 60bff6c commit e139a0d

File tree

3 files changed

+91
-18
lines changed

3 files changed

+91
-18
lines changed

mypy/typeops.py

+5-7
Original file line numberDiff line numberDiff line change
@@ -648,19 +648,14 @@ def _remove_redundant_union_items(items: list[Type], keep_erased: bool) -> list[
648648
return items
649649

650650

651-
def _get_type_method_ret_type(t: Type, *, name: str) -> Type | None:
652-
t = get_proper_type(t)
653-
651+
def _get_type_method_ret_type(t: ProperType, *, name: str) -> Type | None:
654652
# For Enum literals the ret_type can change based on the Enum
655653
# we need to check the type of the enum rather than the literal
656654
if isinstance(t, LiteralType) and t.is_enum_literal():
657655
t = t.fallback
658656

659657
if isinstance(t, Instance):
660658
sym = t.type.get(name)
661-
# Fallback to the metaclass for the lookup when necessary
662-
if not sym and (m := t.type.metaclass_type):
663-
sym = m.type.get(name)
664659
if sym:
665660
sym_type = get_proper_type(sym.type)
666661
if isinstance(sym_type, CallableType):
@@ -733,7 +728,10 @@ def false_only(t: Type) -> ProperType:
733728
if ret_type:
734729
if not ret_type.can_be_false:
735730
return UninhabitedType(line=t.line)
736-
elif isinstance(t, Instance) and t.type.is_final:
731+
elif isinstance(t, Instance):
732+
if t.type.is_final or t.type.is_enum:
733+
return UninhabitedType(line=t.line)
734+
elif isinstance(t, LiteralType) and t.is_enum_literal():
737735
return UninhabitedType(line=t.line)
738736

739737
new_t = copy_type(t)

test-data/unit/check-enum.test

+83-10
Original file line numberDiff line numberDiff line change
@@ -181,27 +181,100 @@ def infer_truth(truth: Truth) -> None:
181181
[case testEnumTruthyness]
182182
# mypy: warn-unreachable
183183
import enum
184+
from typing_extensions import Literal
185+
184186
class E(enum.Enum):
185-
x = 0
186-
if not E.x:
187-
"noop"
187+
zero = 0
188+
one = 1
189+
190+
def print(s: str) -> None: ...
191+
192+
if E.zero:
193+
print("zero is true")
194+
if not E.zero:
195+
print("zero is false") # E: Statement is unreachable
196+
197+
if E.one:
198+
print("one is true")
199+
if not E.one:
200+
print("one is false") # E: Statement is unreachable
201+
202+
def main(zero: Literal[E.zero], one: Literal[E.one]) -> None:
203+
if zero:
204+
print("zero is true")
205+
if not zero:
206+
print("zero is false") # E: Statement is unreachable
207+
if one:
208+
print("one is true")
209+
if not one:
210+
print("one is false") # E: Statement is unreachable
188211
[builtins fixtures/tuple.pyi]
189-
[out]
190-
main:6: error: Statement is unreachable
191212

192213
[case testEnumTruthynessCustomDunderBool]
193214
# mypy: warn-unreachable
194215
import enum
195216
from typing_extensions import Literal
217+
196218
class E(enum.Enum):
197-
x = 0
219+
zero = 0
220+
one = 1
198221
def __bool__(self) -> Literal[False]:
199222
return False
200-
if E.x:
201-
"noop"
223+
224+
def print(s: str) -> None: ...
225+
226+
if E.zero:
227+
print("zero is true") # E: Statement is unreachable
228+
if not E.zero:
229+
print("zero is false")
230+
231+
if E.one:
232+
print("one is true") # E: Statement is unreachable
233+
if not E.one:
234+
print("one is false")
235+
236+
def main(zero: Literal[E.zero], one: Literal[E.one]) -> None:
237+
if zero:
238+
print("zero is true") # E: Statement is unreachable
239+
if not zero:
240+
print("zero is false")
241+
if one:
242+
print("one is true") # E: Statement is unreachable
243+
if not one:
244+
print("one is false")
245+
[builtins fixtures/enum.pyi]
246+
247+
[case testEnumTruthynessStrEnum]
248+
# mypy: warn-unreachable
249+
import enum
250+
from typing_extensions import Literal
251+
252+
class E(enum.StrEnum):
253+
empty = ""
254+
not_empty = "asdf"
255+
256+
def print(s: str) -> None: ...
257+
258+
if E.empty:
259+
print("empty is true")
260+
if not E.empty:
261+
print("empty is false")
262+
263+
if E.not_empty:
264+
print("not_empty is true")
265+
if not E.not_empty:
266+
print("not_empty is false")
267+
268+
def main(empty: Literal[E.empty], not_empty: Literal[E.not_empty]) -> None:
269+
if empty:
270+
print("empty is true")
271+
if not empty:
272+
print("empty is false")
273+
if not_empty:
274+
print("not_empty is true")
275+
if not not_empty:
276+
print("not_empty is false")
202277
[builtins fixtures/enum.pyi]
203-
[out]
204-
main:9: error: Statement is unreachable
205278

206279
[case testEnumUnique]
207280
import enum

test-data/unit/fixtures/enum.pyi

+3-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ class tuple(Generic[T]):
1111
def __getitem__(self, x: int) -> T: pass
1212

1313
class int: pass
14-
class str: pass
14+
class str:
15+
def __len__(self) -> int: pass
16+
1517
class dict: pass
1618
class ellipsis: pass

0 commit comments

Comments
 (0)