Skip to content

Commit

Permalink
Fixed Self checks when a method has positional-only arguments
Browse files Browse the repository at this point in the history
  • Loading branch information
agronholm committed Nov 3, 2024
1 parent eb1e869 commit 0a39830
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 5 deletions.
1 change: 1 addition & 0 deletions docs/versionhistory.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ This library adheres to
(`#481 <https://github.com/agronholm/typeguard/pull/481>`_)
- Fixed checking of protocols on the class level (against ``type[SomeProtocol]``)
(`#498 <https://github.com/agronholm/typeguard/pull/498>`_)
- Fixed ``Self`` checks in instance/class methods that have positional-only arguments

**4.4.0** (2024-10-27)

Expand Down
11 changes: 6 additions & 5 deletions src/typeguard/_transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(),
)
Expand Down
61 changes: 61 additions & 0 deletions tests/test_transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand Down

0 comments on commit 0a39830

Please sign in to comment.