From 0a3983039b6f8e0228ba39cb3c682c565ade19d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= Date: Sun, 3 Nov 2024 02:52:06 +0200 Subject: [PATCH] Fixed `Self` checks when a method has positional-only arguments --- docs/versionhistory.rst | 1 + src/typeguard/_transformer.py | 11 ++++--- tests/test_transformer.py | 61 +++++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 5 deletions(-) diff --git a/docs/versionhistory.rst b/docs/versionhistory.rst index 7127e61..bc663f7 100644 --- a/docs/versionhistory.rst +++ b/docs/versionhistory.rst @@ -16,6 +16,7 @@ This library adheres to (`#481 `_) - Fixed checking of protocols on the class level (against ``type[SomeProtocol]``) (`#498 `_) +- Fixed ``Self`` checks in instance/class methods that have positional-only arguments **4.4.0** (2024-10-27) diff --git a/src/typeguard/_transformer.py b/src/typeguard/_transformer.py index 937b6b5..25696a5 100644 --- a/src/typeguard/_transformer.py +++ b/src/typeguard/_transformer.py @@ -700,7 +700,7 @@ def visit_FunctionDef( else: self.target_lineno = node.lineno - all_args = node.args.args + node.args.kwonlyargs + node.args.posonlyargs + all_args = node.args.posonlyargs + node.args.args + node.args.kwonlyargs # Ensure that any type shadowed by the positional or keyword-only # argument names are ignored in this function @@ -826,19 +826,20 @@ def visit_FunctionDef( isinstance(decorator, Name) and decorator.id == "classmethod" ): + arglist = node.args.posonlyargs or node.args.args memo_kwargs["self_type"] = Name( - id=node.args.args[0].arg, ctx=Load() + id=arglist[0].arg, ctx=Load() ) break else: - if node.args.args: + if arglist := node.args.posonlyargs or node.args.args: if node.name == "__new__": memo_kwargs["self_type"] = Name( - id=node.args.args[0].arg, ctx=Load() + id=arglist[0].arg, ctx=Load() ) else: memo_kwargs["self_type"] = Attribute( - Name(id=node.args.args[0].arg, ctx=Load()), + Name(id=arglist[0].arg, ctx=Load()), "__class__", ctx=Load(), ) diff --git a/tests/test_transformer.py b/tests/test_transformer.py index 3cf735d..2e18a5a 100644 --- a/tests/test_transformer.py +++ b/tests/test_transformer.py @@ -554,6 +554,35 @@ def foo(self, x: int) -> int: ) +def test_method_posonlyargs() -> None: + node = parse( + dedent( + """ + class Foo: + def foo(self, x: int, /, y: str) -> int: + return x + """ + ) + ) + TypeguardTransformer(["Foo", "foo"]).visit(node) + assert ( + unparse(node) + == dedent( + """ + class Foo: + + def foo(self, x: int, /, y: str) -> int: + from typeguard import TypeCheckMemo + from typeguard._functions import check_argument_types, \ +check_return_type + memo = TypeCheckMemo(globals(), locals(), self_type=self.__class__) + check_argument_types('Foo.foo', {'x': (x, int), 'y': (y, str)}, memo) + return check_return_type('Foo.foo', x, int, memo) + """ + ).strip() + ) + + def test_classmethod() -> None: node = parse( dedent( @@ -585,6 +614,38 @@ def foo(cls, x: int) -> int: ) +def test_classmethod_posonlyargs() -> None: + node = parse( + dedent( + """ + class Foo: + @classmethod + def foo(cls, x: int, /, y: str) -> int: + return x + """ + ) + ) + TypeguardTransformer(["Foo", "foo"]).visit(node) + assert ( + unparse(node) + == dedent( + """ + class Foo: + + @classmethod + def foo(cls, x: int, /, y: str) -> int: + from typeguard import TypeCheckMemo + from typeguard._functions import check_argument_types, \ +check_return_type + memo = TypeCheckMemo(globals(), locals(), self_type=cls) + check_argument_types('Foo.foo', {'x': (x, int), 'y': (y, str)}, \ +memo) + return check_return_type('Foo.foo', x, int, memo) + """ + ).strip() + ) + + def test_staticmethod() -> None: node = parse( dedent(