Skip to content

Commit e6b1896

Browse files
authored
literals_interactions.py: do not mandate optional narrowing behaviour from type checkers (#2209)
1 parent b6411fa commit e6b1896

File tree

7 files changed

+65
-34
lines changed

7 files changed

+65
-34
lines changed
Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
1-
conformant = "Partial"
21
notes = """
3-
Does not narrow type of `x` with `x in Literal` type guard pattern.
2+
Does not narrow `str` or `LiteralString` types to `Literal` string types via equality or containment checks.
43
"""
54
output = """
5+
literals_interactions.py:14: error: Tuple index out of range [misc]
66
literals_interactions.py:15: error: Tuple index out of range [misc]
77
literals_interactions.py:16: error: Tuple index out of range [misc]
88
literals_interactions.py:17: error: Tuple index out of range [misc]
9-
literals_interactions.py:18: error: Tuple index out of range [misc]
10-
literals_interactions.py:106: error: Argument 1 to "expects_bad_status" has incompatible type "str"; expected "Literal['MALFORMED', 'ABORTED']" [arg-type]
11-
literals_interactions.py:109: error: Argument 1 to "expects_pending_status" has incompatible type "str"; expected "Literal['PENDING']" [arg-type]
9+
literals_interactions.py:111: error: Argument 1 to "expects_bad_status" has incompatible type "str"; expected "Literal['MALFORMED', 'ABORTED']" [arg-type]
10+
literals_interactions.py:113: error: Argument 1 to "expects_bad_status" has incompatible type "str"; expected "Literal['MALFORMED', 'ABORTED']" [arg-type]
11+
literals_interactions.py:116: error: Argument 1 to "expects_bad_status" has incompatible type "str"; expected "Literal['MALFORMED', 'ABORTED']" [arg-type]
12+
literals_interactions.py:119: error: Argument 1 to "expects_pending_status" has incompatible type "str"; expected "Literal['PENDING']" [arg-type]
13+
literals_interactions.py:128: error: Argument 1 to "expects_bad_status" has incompatible type "str"; expected "Literal['MALFORMED', 'ABORTED']" [arg-type]
14+
literals_interactions.py:130: error: Argument 1 to "expects_bad_status" has incompatible type "str"; expected "Literal['MALFORMED', 'ABORTED']" [arg-type]
15+
literals_interactions.py:133: error: Argument 1 to "expects_bad_status" has incompatible type "str"; expected "Literal['MALFORMED', 'ABORTED']" [arg-type]
16+
literals_interactions.py:136: error: Argument 1 to "expects_pending_status" has incompatible type "str"; expected "Literal['PENDING']" [arg-type]
1217
"""
13-
conformance_automated = "Fail"
18+
conformance_automated = "Pass"
1419
errors_diff = """
15-
Line 106: Unexpected errors ['literals_interactions.py:106: error: Argument 1 to "expects_bad_status" has incompatible type "str"; expected "Literal[\\'MALFORMED\\', \\'ABORTED\\']" [arg-type]']
16-
Line 109: Unexpected errors ['literals_interactions.py:109: error: Argument 1 to "expects_pending_status" has incompatible type "str"; expected "Literal[\\'PENDING\\']" [arg-type]']
1720
"""

conformance/results/pyrefly/literals_interactions.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ conformance_automated = "Pass"
33
errors_diff = """
44
"""
55
output = """
6-
ERROR literals_interactions.py:15:7-8: Index 5 out of range for tuple with 3 elements [bad-index]
7-
ERROR literals_interactions.py:16:7-8: Index -5 out of range for tuple with 3 elements [bad-index]
8-
ERROR literals_interactions.py:17:7-8: Index 4 out of range for tuple with 3 elements [bad-index]
9-
ERROR literals_interactions.py:18:7-9: Index -4 out of range for tuple with 3 elements [bad-index]
6+
ERROR literals_interactions.py:14:7-8: Index 5 out of range for tuple with 3 elements [bad-index]
7+
ERROR literals_interactions.py:15:7-8: Index -5 out of range for tuple with 3 elements [bad-index]
8+
ERROR literals_interactions.py:16:7-8: Index 4 out of range for tuple with 3 elements [bad-index]
9+
ERROR literals_interactions.py:17:7-9: Index -4 out of range for tuple with 3 elements [bad-index]
1010
"""

conformance/results/pyright/literals_interactions.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
conformant = "Pass"
22
output = """
3-
literals_interactions.py:15:5 - error: Index 5 is out of range for type tuple[int, str, list[bool]] (reportGeneralTypeIssues)
4-
literals_interactions.py:16:5 - error: Index -5 is out of range for type tuple[int, str, list[bool]] (reportGeneralTypeIssues)
5-
literals_interactions.py:17:5 - error: Index 4 is out of range for type tuple[int, str, list[bool]] (reportGeneralTypeIssues)
6-
literals_interactions.py:18:5 - error: Index -4 is out of range for type tuple[int, str, list[bool]] (reportGeneralTypeIssues)
3+
literals_interactions.py:14:5 - error: Index 5 is out of range for type tuple[int, str, list[bool]] (reportGeneralTypeIssues)
4+
literals_interactions.py:15:5 - error: Index -5 is out of range for type tuple[int, str, list[bool]] (reportGeneralTypeIssues)
5+
literals_interactions.py:16:5 - error: Index 4 is out of range for type tuple[int, str, list[bool]] (reportGeneralTypeIssues)
6+
literals_interactions.py:17:5 - error: Index -4 is out of range for type tuple[int, str, list[bool]] (reportGeneralTypeIssues)
77
"""
88
conformance_automated = "Pass"
99
errors_diff = """

conformance/results/results.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -579,11 +579,11 @@ <h3>Python Type System Conformance Test Results</h3>
579579
<a class="test_group" href="https://typing.readthedocs.io/en/latest/spec/literal.html">Literals</a>
580580
</th></tr>
581581
<tr><th class="column col1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;literals_interactions</th>
582-
<th class="column col2 partially-conformant"><div class="hover-text">Partial<span class="tooltip-text" id="bottom"><p>Does not narrow type of `x` with `x in Literal` type guard pattern.</p></span></div></th>
582+
<th class="column col2 conformant"><div class="hover-text">Pass*<span class="tooltip-text" id="bottom"><p>Does not narrow `str` or `LiteralString` types to `Literal` string types via equality or containment checks.</p></span></div></th>
583583
<th class="column col2 conformant">Pass</th>
584584
<th class="column col2 conformant">Pass</th>
585585
<th class="column col2 conformant">Pass</th>
586-
<th class="column col2 partially-conformant"><div class="hover-text">Partial<span class="tooltip-text" id="bottom"><p>Deliberately does not allow `str` to be narrowed to literal string types through equality or containment checks due to the possibility of `str` subclasses that could have unexpected equality semantics.</p></span></div></th>
586+
<th class="column col2 conformant"><div class="hover-text">Pass*<span class="tooltip-text" id="bottom"><p>Deliberately does not allow `str` to be narrowed to literal string types through equality or containment checks due to the possibility of `str` subclasses that could have unexpected equality semantics.</p><p>Incorrectly fails to narrow the type `LiteralString & ~Literal["MALFORMED"]` to `Literal["ABORTED"]` after an `== "ABORTED"` check.</p></span></div></th>
587587
</tr>
588588
<tr><th class="column col1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;literals_literalstring</th>
589589
<th class="column col2 not-conformant"><div class="hover-text">Unsupported<span class="tooltip-text" id="bottom"><p>Support for `LiteralString` is not implemented.</p></span></div></th>
Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
1-
conformance_automated = "Fail"
2-
conformant = "Partial"
1+
conformance_automated = "Pass"
32
notes = """
43
Deliberately does not allow `str` to be narrowed to literal string types through equality or containment checks due to the possibility of `str` subclasses that could have unexpected equality semantics.
4+
Incorrectly fails to narrow the type `LiteralString & ~Literal["MALFORMED"]` to `Literal["ABORTED"]` after an `== "ABORTED"` check.
55
"""
66
errors_diff = """
7-
Line 106: Unexpected errors ['literals_interactions.py:106:35: error[invalid-argument-type] Argument to function `expects_bad_status` is incorrect: Expected `Literal["MALFORMED", "ABORTED"]`, found `str`']
8-
Line 109: Unexpected errors ['literals_interactions.py:109:32: error[invalid-argument-type] Argument to function `expects_pending_status` is incorrect: Expected `Literal["PENDING"]`, found `str`']
97
"""
108
output = """
11-
literals_interactions.py:15:5: error[index-out-of-bounds] Index 5 is out of bounds for tuple `tuple[int, str, list[bool]]` with length 3
12-
literals_interactions.py:16:5: error[index-out-of-bounds] Index -5 is out of bounds for tuple `tuple[int, str, list[bool]]` with length 3
13-
literals_interactions.py:17:5: error[index-out-of-bounds] Index 4 is out of bounds for tuple `tuple[int, str, list[bool]]` with length 3
14-
literals_interactions.py:18:5: error[index-out-of-bounds] Index -4 is out of bounds for tuple `tuple[int, str, list[bool]]` with length 3
15-
literals_interactions.py:106:35: error[invalid-argument-type] Argument to function `expects_bad_status` is incorrect: Expected `Literal["MALFORMED", "ABORTED"]`, found `str`
16-
literals_interactions.py:109:32: error[invalid-argument-type] Argument to function `expects_pending_status` is incorrect: Expected `Literal["PENDING"]`, found `str`
9+
literals_interactions.py:14:5: error[index-out-of-bounds] Index 5 is out of bounds for tuple `tuple[int, str, list[bool]]` with length 3
10+
literals_interactions.py:15:5: error[index-out-of-bounds] Index -5 is out of bounds for tuple `tuple[int, str, list[bool]]` with length 3
11+
literals_interactions.py:16:5: error[index-out-of-bounds] Index 4 is out of bounds for tuple `tuple[int, str, list[bool]]` with length 3
12+
literals_interactions.py:17:5: error[index-out-of-bounds] Index -4 is out of bounds for tuple `tuple[int, str, list[bool]]` with length 3
13+
literals_interactions.py:113:28: error[invalid-argument-type] Argument to function `expects_bad_status` is incorrect: Expected `Literal["MALFORMED", "ABORTED"]`, found `LiteralString & ~Literal["MALFORMED"]`
14+
literals_interactions.py:128:28: error[invalid-argument-type] Argument to function `expects_bad_status` is incorrect: Expected `Literal["MALFORMED", "ABORTED"]`, found `str`
15+
literals_interactions.py:130:28: error[invalid-argument-type] Argument to function `expects_bad_status` is incorrect: Expected `Literal["MALFORMED", "ABORTED"]`, found `str & ~Literal["MALFORMED"]`
16+
literals_interactions.py:133:28: error[invalid-argument-type] Argument to function `expects_bad_status` is incorrect: Expected `Literal["MALFORMED", "ABORTED"]`, found `str`
17+
literals_interactions.py:136:32: error[invalid-argument-type] Argument to function `expects_pending_status` is incorrect: Expected `Literal["PENDING"]`, found `str`
1718
"""

conformance/results/zuban/literals_interactions.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ conformance_automated = "Pass"
22
errors_diff = """
33
"""
44
output = """
5+
literals_interactions.py:14: error: Tuple index out of range [misc]
56
literals_interactions.py:15: error: Tuple index out of range [misc]
67
literals_interactions.py:16: error: Tuple index out of range [misc]
78
literals_interactions.py:17: error: Tuple index out of range [misc]
8-
literals_interactions.py:18: error: Tuple index out of range [misc]
99
"""

conformance/tests/literals_interactions.py

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,8 @@
33
"""
44

55
# Specification: https://typing.readthedocs.io/en/latest/spec/literal.html#interactions-with-other-types-and-features
6-
76
from enum import Enum
8-
from typing import IO, Any, Final, Generic, Literal, TypeVar, assert_type, overload
7+
from typing import IO, Any, Final, Generic, Literal, TypeVar, LiteralString, assert_type, overload
98

109

1110
def func1(v: tuple[int, str, list[bool]], a: Literal[0], b: Literal[5], c: Literal[-5]):
@@ -93,6 +92,12 @@ def parse_status1(s: str | Status) -> None:
9392
assert_type(s, str)
9493

9594

95+
# > Type checkers may optionally perform additional analysis for both
96+
# > enum and non-enum Literal types beyond what is described in the section above.
97+
#
98+
# > For example, it may be useful to perform narrowing based on things
99+
# > like containment or equality checks:
100+
96101
def expects_bad_status(status: Literal["MALFORMED", "ABORTED"]):
97102
...
98103

@@ -101,12 +106,34 @@ def expects_pending_status(status: Literal["PENDING"]):
101106
...
102107

103108

104-
def parse_status2(status: str) -> None:
109+
def parse_status2(status: LiteralString) -> None:
110+
if status == "MALFORMED":
111+
expects_bad_status(status) # E? narrowing the type here is sound, but optional per the spec
112+
elif status == "ABORTED":
113+
expects_bad_status(status) # E? narrowing the type here is sound, but optional per the spec
114+
115+
if status in ("MALFORMED", "ABORTED"):
116+
expects_bad_status(status) # E? narrowing the type here is sound, but optional per the spec
117+
118+
if status == "PENDING":
119+
expects_pending_status(status) # E? narrowing the type here is sound, but optional per the spec
120+
121+
122+
# Narrowing `str` to `Literal` strings is unsound given the possiblity of
123+
# user-defined `str` subclasses that could have custom equality semantics,
124+
# but is explicitly listed by the spec as optional analysis that type checkers
125+
# may perform.
126+
def parse_status3(status: str) -> None:
127+
if status == "MALFORMED":
128+
expects_bad_status(status) # E? narrowing the type here is unsound, but allowed per the spec
129+
elif status == "ABORTED":
130+
expects_bad_status(status) # E? narrowing the type here is unsound, but allowed per the spec
131+
105132
if status in ("MALFORMED", "ABORTED"):
106-
return expects_bad_status(status)
133+
expects_bad_status(status) # E? narrowing the type here is unsound, but allowed per the spec
107134

108135
if status == "PENDING":
109-
expects_pending_status(status)
136+
expects_pending_status(status) # E? narrowing the type here is unsound, but allowed per the spec
110137

111138

112139
final_val1: Final = 3

0 commit comments

Comments
 (0)