Skip to content

Commit 06c7d26

Browse files
authored
stubgen: Gracefully handle invalid Optional and recognize aliases to PEP 604 unions (#17386)
This Fixes 2 issues with invalid `Optional` (inspired by an error reported in #17197): - do not crash on empty `Optional` - treat `Optional` with more than one index as an unknown type instead of choosing the first type. It also fixes PEP 604 unions not being recognized as type aliases.
1 parent b202552 commit 06c7d26

File tree

3 files changed

+39
-1
lines changed

3 files changed

+39
-1
lines changed

mypy/stubgen.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,8 @@ def visit_index_expr(self, node: IndexExpr) -> str:
314314
return " | ".join([item.accept(self) for item in node.index.items])
315315
return node.index.accept(self)
316316
if base_fullname == "typing.Optional":
317+
if isinstance(node.index, TupleExpr):
318+
return self.stubgen.add_name("_typeshed.Incomplete")
317319
return f"{node.index.accept(self)} | None"
318320
base = node.base.accept(self)
319321
index = node.index.accept(self)
@@ -1060,6 +1062,10 @@ def is_alias_expression(self, expr: Expression, top_level: bool = True) -> bool:
10601062
else:
10611063
return False
10621064
return all(self.is_alias_expression(i, top_level=False) for i in indices)
1065+
elif isinstance(expr, OpExpr) and expr.op == "|":
1066+
return self.is_alias_expression(
1067+
expr.left, top_level=False
1068+
) and self.is_alias_expression(expr.right, top_level=False)
10631069
else:
10641070
return False
10651071

mypy/stubutil.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,9 @@ def visit_unbound_type(self, t: UnboundType) -> str:
257257
if fullname == "typing.Union":
258258
return " | ".join([item.accept(self) for item in t.args])
259259
if fullname == "typing.Optional":
260-
return f"{t.args[0].accept(self)} | None"
260+
if len(t.args) == 1:
261+
return f"{t.args[0].accept(self)} | None"
262+
return self.stubgen.add_name("_typeshed.Incomplete")
261263
if fullname in TYPING_BUILTIN_REPLACEMENTS:
262264
s = self.stubgen.add_name(TYPING_BUILTIN_REPLACEMENTS[fullname], require=True)
263265
if self.known_modules is not None and "." in s:

test-data/unit/stubgen.test

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4366,3 +4366,33 @@ class Foo(Enum):
43664366
class Bar(Enum):
43674367
A = ...
43684368
B = ...
4369+
4370+
[case testGracefullyHandleInvalidOptionalUsage]
4371+
from typing import Optional
4372+
4373+
x: Optional # invalid
4374+
y: Optional[int] # valid
4375+
z: Optional[int, str] # invalid
4376+
w: Optional[int | str] # valid
4377+
r: Optional[type[int | str]]
4378+
4379+
X = Optional
4380+
Y = Optional[int]
4381+
Z = Optional[int, str]
4382+
W = Optional[int | str]
4383+
R = Optional[type[int | str]]
4384+
4385+
[out]
4386+
from _typeshed import Incomplete
4387+
from typing import Optional
4388+
4389+
x: Incomplete
4390+
y: int | None
4391+
z: Incomplete
4392+
w: int | str | None
4393+
r: type[int | str] | None
4394+
X = Optional
4395+
Y = int | None
4396+
Z = Incomplete
4397+
W = int | str | None
4398+
R = type[int | str] | None

0 commit comments

Comments
 (0)