Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions mypy/stubgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,8 @@ def visit_index_expr(self, node: IndexExpr) -> str:
return " | ".join([item.accept(self) for item in node.index.items])
return node.index.accept(self)
if base_fullname == "typing.Optional":
if isinstance(node.index, TupleExpr):
return self.stubgen.add_name("_typeshed.Incomplete")
return f"{node.index.accept(self)} | None"
base = node.base.accept(self)
index = node.index.accept(self)
Expand Down Expand Up @@ -1060,6 +1062,10 @@ def is_alias_expression(self, expr: Expression, top_level: bool = True) -> bool:
else:
return False
return all(self.is_alias_expression(i, top_level=False) for i in indices)
elif isinstance(expr, OpExpr) and expr.op == "|":
return self.is_alias_expression(
expr.left, top_level=False
) and self.is_alias_expression(expr.right, top_level=False)
else:
return False

Expand Down
4 changes: 3 additions & 1 deletion mypy/stubutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,9 @@ def visit_unbound_type(self, t: UnboundType) -> str:
if fullname == "typing.Union":
return " | ".join([item.accept(self) for item in t.args])
if fullname == "typing.Optional":
return f"{t.args[0].accept(self)} | None"
if len(t.args) == 1:
return f"{t.args[0].accept(self)} | None"
return self.stubgen.add_name("_typeshed.Incomplete")
if fullname in TYPING_BUILTIN_REPLACEMENTS:
s = self.stubgen.add_name(TYPING_BUILTIN_REPLACEMENTS[fullname], require=True)
if self.known_modules is not None and "." in s:
Expand Down
26 changes: 26 additions & 0 deletions test-data/unit/stubgen.test
Original file line number Diff line number Diff line change
Expand Up @@ -4366,3 +4366,29 @@ class Foo(Enum):
class Bar(Enum):
A = ...
B = ...

[case testGracefullyHandleInvalidOptionalUsage]
from typing import Optional

x: Optional # invalid
y: Optional[int] # valid
z: Optional[int, str] # invalid
w: Optional[int | str] # valid

X = Optional
Y = Optional[int]
Z = Optional[int, str]
W = Optional[int | str]

[out]
from _typeshed import Incomplete
from typing import Optional

x: Incomplete
y: int | None
z: Incomplete
w: int | str | None
X = Optional
Y = int | None
Z = Incomplete
W = int | str | None