Repository: python/mypy

Issue:
stubgen: Don't generate `Incomplete | None = None`
In typeshed we've replaced all instances of `: Incomplete | None = None` in argument annotations with just `=None` (python/typeshed#14029). We've also added a check to flake8-pyi to avoid regressions (PyCQA/flake8-pyi#509). The reason for this change was originally outlines by @Avasam:

> This is still interpreted as Any | None by mypy. And now more correctly interpreted as Unknown | None by pyright.

It would be great if we could change the output of stubgen accordingly.


Discussion:

Patch:
diff --git a/mypy/stubgen.py b/mypy/stubgen.py
index 3173bfdf9f5c..e51469b5ab7d 100755
--- a/mypy/stubgen.py
+++ b/mypy/stubgen.py
@@ -565,7 +565,7 @@ def _get_func_args(self, o: FuncDef, ctx: FunctionContext) -> list[ArgSig]:
             default = "..."
             if arg_.initializer:
                 if not typename:
-                    typename = self.get_str_type_of_node(arg_.initializer, True, False)
+                    typename = self.get_str_type_of_node(arg_.initializer, can_be_incomplete=False)
                 potential_default, valid = self.get_str_default_of_node(arg_.initializer)
                 if valid and len(potential_default) <= 200:
                     default = potential_default
@@ -1305,9 +1305,7 @@ def is_private_member(self, fullname: str) -> bool:
         parts = fullname.split(".")
         return any(self.is_private_name(part) for part in parts)
 
-    def get_str_type_of_node(
-        self, rvalue: Expression, can_infer_optional: bool = False, can_be_any: bool = True
-    ) -> str:
+    def get_str_type_of_node(self, rvalue: Expression, *, can_be_incomplete: bool = True) -> str:
         rvalue = self.maybe_unwrap_unary_expr(rvalue)
 
         if isinstance(rvalue, IntExpr):
@@ -1327,9 +1325,7 @@ def get_str_type_of_node(
                 return "complex"
         if isinstance(rvalue, NameExpr) and rvalue.name in ("True", "False"):
             return "bool"
-        if can_infer_optional and isinstance(rvalue, NameExpr) and rvalue.name == "None":
-            return f"{self.add_name('_typeshed.Incomplete')} | None"
-        if can_be_any:
+        if can_be_incomplete:
             return self.add_name("_typeshed.Incomplete")
         else:
             return ""


Test patch:
diff --git a/test-data/unit/stubgen.test b/test-data/unit/stubgen.test
index 86d33e3af51d..5ff458736436 100644
--- a/test-data/unit/stubgen.test
+++ b/test-data/unit/stubgen.test
@@ -30,12 +30,10 @@ def g(b=-1, c=0): ...
 def f(a, b: int = 2) -> None: ...
 def g(b: int = -1, c: int = 0) -> None: ...
 
-[case testDefaultArgNone]
+[case testFuncDefaultArgNone]
 def f(x=None): ...
 [out]
-from _typeshed import Incomplete
-
-def f(x: Incomplete | None = None) -> None: ...
+def f(x=None) -> None: ...
 
 [case testDefaultArgBool]
 def f(x=True, y=False): ...
@@ -1379,7 +1377,7 @@ async def f(a):
 [out]
 async def f(a) -> None: ...
 
-[case testInferOptionalOnlyFunc]
+[case testMethodDefaultArgNone]
 class A:
     x = None
     def __init__(self, a=None):
@@ -1391,8 +1389,8 @@ from _typeshed import Incomplete
 
 class A:
     x: Incomplete
-    def __init__(self, a: Incomplete | None = None) -> None: ...
-    def method(self, a: Incomplete | None = None) -> None: ...
+    def __init__(self, a=None) -> None: ...
+    def method(self, a=None) -> None: ...
 
 [case testAnnotationImportsFrom]
 import foo
@@ -2618,32 +2616,29 @@ class A(metaclass=abc.ABCMeta):
     @abc.abstractmethod
     def x(self): ...
 
-[case testClassWithNameIncompleteOrOptional]
+[case testClassWithNameIncomplete]
 Y = object()
 
-def g(x=None): pass
+def g():
+    yield 1
 
 x = g()
 
 class Incomplete:
     pass
 
-def Optional():
-    return 0
-
 [out]
 from _typeshed import Incomplete as _Incomplete
+from collections.abc import Generator
 
 Y: _Incomplete
 
-def g(x: _Incomplete | None = None) -> None: ...
+def g() -> Generator[_Incomplete]: ...
 
 x: _Incomplete
 
 class Incomplete: ...
 
-def Optional(): ...
-
 [case testExportedNameImported]
 # modules: main a b
 from a import C