sco1/flake8-annotations

Positional only indicator triggers ANN001 for self arguments

languitar opened this issue · 2 comments

Describe the bug

When using the new 3.8 indicator for positional only arguments / in a method with a self argument and ANN101 set to being ignored, ANN001 is raised for the self argument.

To Reproduce
Minimal code example to reproduce the behavior:

class Foo:
    def foo(self, /, test: int) -> None:
        pass

This will yield:

[flake8 ANN001] [I] Missing type annotation for function argument 'self'

This doesn't happen without the / indicator.

Version Information

❯ poetry run flake8 --version
3.8.3 (black: 0.2.1, dlint: 0.10.3, flake-mutable: 1.2.0, flake8-annotations: 2.3.0, flake8-bandit: 2.1.2, flake8-bugbear: 20.1.4, flake8-cognitive-complexity: 0.1.0, flake8-comprehensions: 3.2.3, flake8-debugger: 3.2.1, flake8-docstrings: 1.5.0, pydocstyle: 5.0.2,
flake8-eradicate: 0.4.0, flake8-expression-complexity: 0.0.8, flake8-mock: 0.3, flake8-pie: 0.5.0, flake8-print: 3.1.4, flake8-pytest-style: 1.3.0, flake8-string-format: 0.3.0, flake8-tidy-imports: 4.1.0, flake8-variables-names: 0.0.3, flake8_builtins: 1.5.2, flake8_commas: 2.0.0,
flake8_pep3101: 1.2.1, import-order: 0.18.1, mccabe: 0.6.1, naming: 0.11.1, pycodestyle: 2.6.0, pyflakes: 2.2.0) CPython 3.8.5 on Linux

As well as your Python version:

❯ poetry run python --version
Python 3.8.5
sco1 commented

Thanks for the report. I believe this can be traced back to how the arguments list is constructed from the AST prior to hitting the error classifier.

In __init__.py we have:

AST_ARG_TYPES = ("args", "vararg", "kwonlyargs", "kwarg")
if PY_GTE_38:
# Positional-only args introduced in Python 3.8
AST_ARG_TYPES += ("posonlyargs",)

Which ends up resolving to ('args', 'vararg', 'kwonlyargs', 'kwarg', 'posonlyargs') for Python 3.8, so positional only arguments end up at the end of the arguments list of the Function objects we generate from the AST:

for arg_type in AST_ARG_TYPES:
args = node.args.__getattribute__(arg_type)
if args:
if not isinstance(args, list):
args = [args]
new_function.args.extend(
[Argument.from_arg_node(arg, arg_type.upper()) for arg in args]
)

Since it's at the end, we miss the "is first argument" criteria and end up misclassifying the error type.

Unfortunately this means that there's no workaround to provide, but the fix should be simple.

Edit: This should now be fixed by verrsion 2.4.1

Thanks for fixing this quickly!