diff --git a/mypy/stubtest.py b/mypy/stubtest.py index 98ab8a043aaf..c02a3efd8dc0 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -55,6 +55,17 @@ def __repr__(self) -> str: T = TypeVar("T") MaybeMissing: typing_extensions.TypeAlias = Union[T, Missing] + +class Unrepresentable: + """Marker object for unrepresentable parameter defaults.""" + + def __repr__(self) -> str: + return "" + + +UNREPRESENTABLE: typing_extensions.Final = Unrepresentable() + + _formatter: typing_extensions.Final = FancyFormatter(sys.stdout, sys.stderr, False) @@ -681,6 +692,7 @@ def _verify_arg_default_value( if ( stub_default is not UNKNOWN and stub_default is not ... + and runtime_arg.default is not UNREPRESENTABLE and ( stub_default != runtime_arg.default # We want the types to match exactly, e.g. in case the stub has @@ -1483,7 +1495,27 @@ def is_read_only_property(runtime: object) -> bool: def safe_inspect_signature(runtime: Any) -> inspect.Signature | None: try: - return inspect.signature(runtime) + try: + return inspect.signature(runtime) + except ValueError: + if ( + hasattr(runtime, "__text_signature__") + and "" in runtime.__text_signature__ + ): + # Try to fix up the signature. Workaround for + # https://github.com/python/cpython/issues/87233 + sig = runtime.__text_signature__.replace("", "...") + sig = inspect._signature_fromstr(inspect.Signature, runtime, sig) # type: ignore[attr-defined] + assert isinstance(sig, inspect.Signature) + new_params = [ + parameter.replace(default=UNREPRESENTABLE) + if parameter.default is ... + else parameter + for parameter in sig.parameters.values() + ] + return sig.replace(parameters=new_params) + else: + raise except Exception: # inspect.signature throws ValueError all the time # catch RuntimeError because of https://bugs.python.org/issue39504 diff --git a/mypy/test/teststubtest.py b/mypy/test/teststubtest.py index a2e9668a9ac4..34b266115166 100644 --- a/mypy/test/teststubtest.py +++ b/mypy/test/teststubtest.py @@ -428,6 +428,16 @@ def test_default_value(self) -> Iterator[Case]: error=None, ) + # Simulate "" + yield Case( + stub="def f11() -> None: ...", + runtime=""" + def f11(text=None) -> None: pass + f11.__text_signature__ = "(text=)" + """, + error="f11", + ) + @collect_cases def test_static_class_method(self) -> Iterator[Case]: yield Case( @@ -2281,6 +2291,14 @@ def f(a: int, b: int, *, c: int, d: int = 0, **kwargs: Any) -> None: == "def (a, b, *, c, d = ..., **kwargs)" ) + def test_builtin_signature_with_unrepresentable_default(self) -> None: + sig = mypy.stubtest.safe_inspect_signature(bytes.hex) + assert sig is not None + assert ( + str(mypy.stubtest.Signature.from_inspect_signature(sig)) + == "def (self, sep = ..., bytes_per_sep = ...)" + ) + def test_config_file(self) -> None: runtime = "temp = 5\n" stub = "from decimal import Decimal\ntemp: Decimal\n"