Skip to content

Commit 3da16bd

Browse files
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>
1 parent 2632ea3 commit 3da16bd

File tree

4 files changed

+23
-7
lines changed

4 files changed

+23
-7
lines changed

mypy/fastparse.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -331,12 +331,10 @@ def parse_type_string(
331331
"""
332332
try:
333333
_, node = parse_type_comment(f"({expr_string})", line=line, column=column, errors=None)
334-
if isinstance(node, UnboundType) and node.original_str_expr is None:
334+
if isinstance(node, (UnboundType, UnionType)) and node.original_str_expr is None:
335335
node.original_str_expr = expr_string
336336
node.original_str_fallback = expr_fallback_name
337337
return node
338-
elif isinstance(node, UnionType):
339-
return node
340338
else:
341339
return RawExpressionType(expr_string, expr_fallback_name, line, column)
342340
except (SyntaxError, ValueError):

mypy/typeanal.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1615,7 +1615,11 @@ def analyze_literal_type(self, t: UnboundType) -> Type:
16151615

16161616
def analyze_literal_param(self, idx: int, arg: Type, ctx: Context) -> list[Type] | None:
16171617
# This UnboundType was originally defined as a string.
1618-
if isinstance(arg, UnboundType) and arg.original_str_expr is not None:
1618+
if (
1619+
isinstance(arg, ProperType)
1620+
and isinstance(arg, (UnboundType, UnionType))
1621+
and arg.original_str_expr is not None
1622+
):
16191623
assert arg.original_str_fallback is not None
16201624
return [
16211625
LiteralType(

mypy/types.py

Lines changed: 13 additions & 3 deletions
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?
@@ -2849,7 +2848,13 @@ def is_singleton_type(self) -> bool:
28492848
class UnionType(ProperType):
28502849
"""The union type Union[T1, ..., Tn] (at least one type argument)."""
28512850

2852-
__slots__ = ("items", "is_evaluated", "uses_pep604_syntax")
2851+
__slots__ = (
2852+
"items",
2853+
"is_evaluated",
2854+
"uses_pep604_syntax",
2855+
"original_str_expr",
2856+
"original_str_fallback",
2857+
)
28532858

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

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

test-data/unit/check-literal.test

Lines changed: 4 additions & 0 deletions
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)