Skip to content

Commit 917cc75

Browse files
ilevkivskyihauntsaninja
authored andcommitted
An alternative fix for a union-like literal string (#17639)
It is unfortunate to add two extra slots to a common type (and I guess this is why it was rejected in the original PR), but all other alternatives I tried are hacky and/or dangerous. So, this is a price to pay for introducing a new type syntax. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> (cherry picked from commit 3da16bd)
1 parent 7d805b3 commit 917cc75

File tree

4 files changed

+23
-7
lines changed

4 files changed

+23
-7
lines changed

mypy/fastparse.py

+1-3
Original file line numberDiff line numberDiff line change
@@ -329,12 +329,10 @@ def parse_type_string(
329329
"""
330330
try:
331331
_, node = parse_type_comment(f"({expr_string})", line=line, column=column, errors=None)
332-
if isinstance(node, UnboundType) and node.original_str_expr is None:
332+
if isinstance(node, (UnboundType, UnionType)) and node.original_str_expr is None:
333333
node.original_str_expr = expr_string
334334
node.original_str_fallback = expr_fallback_name
335335
return node
336-
elif isinstance(node, UnionType):
337-
return node
338336
else:
339337
return RawExpressionType(expr_string, expr_fallback_name, line, column)
340338
except (SyntaxError, ValueError):

mypy/typeanal.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -1575,7 +1575,11 @@ def analyze_literal_type(self, t: UnboundType) -> Type:
15751575

15761576
def analyze_literal_param(self, idx: int, arg: Type, ctx: Context) -> list[Type] | None:
15771577
# This UnboundType was originally defined as a string.
1578-
if isinstance(arg, UnboundType) and arg.original_str_expr is not None:
1578+
if (
1579+
isinstance(arg, ProperType)
1580+
and isinstance(arg, (UnboundType, UnionType))
1581+
and arg.original_str_expr is not None
1582+
):
15791583
assert arg.original_str_fallback is not None
15801584
return [
15811585
LiteralType(

mypy/types.py

+13-3
Original file line numberDiff line numberDiff line change
@@ -914,7 +914,7 @@ class UnboundType(ProperType):
914914

915915
def __init__(
916916
self,
917-
name: str | None,
917+
name: str,
918918
args: Sequence[Type] | None = None,
919919
line: int = -1,
920920
column: int = -1,
@@ -926,7 +926,6 @@ def __init__(
926926
super().__init__(line, column)
927927
if not args:
928928
args = []
929-
assert name is not None
930929
self.name = name
931930
self.args = tuple(args)
932931
# Should this type be wrapped in an Optional?
@@ -2847,7 +2846,13 @@ def is_singleton_type(self) -> bool:
28472846
class UnionType(ProperType):
28482847
"""The union type Union[T1, ..., Tn] (at least one type argument)."""
28492848

2850-
__slots__ = ("items", "is_evaluated", "uses_pep604_syntax")
2849+
__slots__ = (
2850+
"items",
2851+
"is_evaluated",
2852+
"uses_pep604_syntax",
2853+
"original_str_expr",
2854+
"original_str_fallback",
2855+
)
28512856

28522857
def __init__(
28532858
self,
@@ -2866,6 +2871,11 @@ def __init__(
28662871
self.is_evaluated = is_evaluated
28672872
# uses_pep604_syntax is True if Union uses OR syntax (X | Y)
28682873
self.uses_pep604_syntax = uses_pep604_syntax
2874+
# The meaning of these two is the same as for UnboundType. A UnionType can be
2875+
# return by type parser from a string "A|B", and we need to be able to fall back
2876+
# to plain string, when such a string appears inside a Literal[...].
2877+
self.original_str_expr: str | None = None
2878+
self.original_str_fallback: str | None = None
28692879

28702880
def can_be_true_default(self) -> bool:
28712881
return any(item.can_be_true for item in self.items)

test-data/unit/check-literal.test

+4
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,12 @@ reveal_type(g1) # N: Revealed type is "def (x: Literal['A['])"
1212

1313
def f2(x: 'A B') -> None: pass # E: Invalid type comment or annotation
1414
def g2(x: Literal['A B']) -> None: pass
15+
def h2(x: 'A|int') -> None: pass # E: Name "A" is not defined
16+
def i2(x: Literal['A|B']) -> None: pass
1517
reveal_type(f2) # N: Revealed type is "def (x: Any)"
1618
reveal_type(g2) # N: Revealed type is "def (x: Literal['A B'])"
19+
reveal_type(h2) # N: Revealed type is "def (x: Union[Any, builtins.int])"
20+
reveal_type(i2) # N: Revealed type is "def (x: Literal['A|B'])"
1721
[builtins fixtures/tuple.pyi]
1822
[out]
1923

0 commit comments

Comments
 (0)