Skip to content

Commit

Permalink
stubgen: Gracefully handle invalid Optional and recognize aliases t…
Browse files Browse the repository at this point in the history
…o 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.
  • Loading branch information
hamdanal authored Jun 17, 2024
1 parent b202552 commit 06c7d26
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 1 deletion.
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
30 changes: 30 additions & 0 deletions test-data/unit/stubgen.test
Original file line number Diff line number Diff line change
Expand Up @@ -4366,3 +4366,33 @@ 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
r: Optional[type[int | str]]

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

[out]
from _typeshed import Incomplete
from typing import Optional

x: Incomplete
y: int | None
z: Incomplete
w: int | str | None
r: type[int | str] | None
X = Optional
Y = int | None
Z = Incomplete
W = int | str | None
R = type[int | str] | None

0 comments on commit 06c7d26

Please sign in to comment.