From 3e2ca6fe8a2ac9274096ca8c483217f6878a83c1 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Thu, 9 Nov 2023 05:30:39 -0800 Subject: [PATCH 1/5] stubtest: hack for "" defaults See python/cpython#87233 --- mypy/stubtest.py | 15 ++++++++++++++- mypy/test/teststubtest.py | 8 ++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/mypy/stubtest.py b/mypy/stubtest.py index e80ea4eac71fa..2385d68eb07b2 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -1440,7 +1440,20 @@ 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] + return sig # type: ignore[no-any-return] + 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 a52d9ef5de31a..ec870170c6619 100644 --- a/mypy/test/teststubtest.py +++ b/mypy/test/teststubtest.py @@ -2195,6 +2195,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" From 6211f766fca176d82a4f28f9e2d62d73feaf0e61 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Thu, 9 Nov 2023 05:48:34 -0800 Subject: [PATCH 2/5] More hacks --- mypy/stubtest.py | 4 ++++ mypy/test/teststubtest.py | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/mypy/stubtest.py b/mypy/stubtest.py index 2385d68eb07b2..3ea11e0b28334 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -658,6 +658,10 @@ def _verify_arg_default_value( if ( stub_default is not UNKNOWN and stub_default is not ... + # We use "..." as a hacky representation for "" + # defaults on builtins, so ignore those. "..." is unlikely to be used + # as a real default. + and runtime_arg.default is not ... and ( stub_default != runtime_arg.default # We want the types to match exactly, e.g. in case the stub has diff --git a/mypy/test/teststubtest.py b/mypy/test/teststubtest.py index ec870170c6619..7050b66e19fa8 100644 --- a/mypy/test/teststubtest.py +++ b/mypy/test/teststubtest.py @@ -426,6 +426,13 @@ def test_default_value(self) -> Iterator[Case]: error=None, ) + # Simulate "" + yield Case( + stub="def f11(text: object = None) -> None: ...", + runtime="def f11(text = ...): pass", + error=None, + ) + @collect_cases def test_static_class_method(self) -> Iterator[Case]: yield Case( From df8be79277725dcde3020b3e92a3eaef49a80719 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Thu, 9 Nov 2023 17:00:07 -0800 Subject: [PATCH 3/5] Nicer version provided by Alex Waygood --- mypy/stubtest.py | 23 ++++++++++++++++++----- mypy/test/teststubtest.py | 5 ++++- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/mypy/stubtest.py b/mypy/stubtest.py index 3ea11e0b28334..aeb6011974823 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -55,6 +55,15 @@ 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) @@ -658,10 +667,7 @@ def _verify_arg_default_value( if ( stub_default is not UNKNOWN and stub_default is not ... - # We use "..." as a hacky representation for "" - # defaults on builtins, so ignore those. "..." is unlikely to be used - # as a real default. - and runtime_arg.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 @@ -1455,7 +1461,14 @@ def safe_inspect_signature(runtime: Any) -> inspect.Signature | None: # https://github.com/python/cpython/issues/87233 sig = runtime.__text_signature__.replace("", "...") sig = inspect._signature_fromstr(inspect.Signature, runtime, sig) # type: ignore[attr-defined] - return sig # type: ignore[no-any-return] + 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: diff --git a/mypy/test/teststubtest.py b/mypy/test/teststubtest.py index 7050b66e19fa8..5ad6ce0b21900 100644 --- a/mypy/test/teststubtest.py +++ b/mypy/test/teststubtest.py @@ -429,7 +429,10 @@ def test_default_value(self) -> Iterator[Case]: # Simulate "" yield Case( stub="def f11(text: object = None) -> None: ...", - runtime="def f11(text = ...): pass", + runtime=""" + def f11(text=None) -> None: pass + f11.__text_signature__ = "(text=)" + """, error=None, ) From 885f2f795d275043f22d758bff2a1d82208eb1f8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 10 Nov 2023 01:00:30 +0000 Subject: [PATCH 4/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypy/stubtest.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mypy/stubtest.py b/mypy/stubtest.py index aeb6011974823..f11dad3bcefd2 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -55,8 +55,10 @@ 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 "" From 7542580133ef8a3f02387804d0ab85b3f6d83743 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 14 Nov 2023 07:47:24 -0800 Subject: [PATCH 5/5] better test --- mypy/test/teststubtest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mypy/test/teststubtest.py b/mypy/test/teststubtest.py index 5ad6ce0b21900..8798b34fdd7b8 100644 --- a/mypy/test/teststubtest.py +++ b/mypy/test/teststubtest.py @@ -428,12 +428,12 @@ def test_default_value(self) -> Iterator[Case]: # Simulate "" yield Case( - stub="def f11(text: object = None) -> None: ...", + stub="def f11() -> None: ...", runtime=""" def f11(text=None) -> None: pass f11.__text_signature__ = "(text=)" """, - error=None, + error="f11", ) @collect_cases