diff --git a/CHANGELOG.md b/CHANGELOG.md index 9199be5..5000dcd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Change Log +New error codes: +* Introduce Y068: Don't use `@override` in stub files. + ## 5.5.0 New error codes: diff --git a/ERRORCODES.md b/ERRORCODES.md index eae5916..db2565c 100644 --- a/ERRORCODES.md +++ b/ERRORCODES.md @@ -80,8 +80,8 @@ The following warnings are currently emitted by default: | Y064 | Use simpler syntax to define final literal types. For example, use `x: Final = 42` instead of `x: Final[Literal[42]]`. | Style | Y065 | Don't use bare `Incomplete` in argument and return annotations. Instead, leave them unannotated. Omitting an annotation entirely from a function will cause some type checkers to view the parameter or return type as "untyped"; this may result in stricter type-checking on code that makes use of the stubbed function. | Style | Y066 | When using if/else with `sys.version_info`, put the code for new Python versions first. | Style -| Y067 | Don't use `Incomplete | None = None` in -argument annotations. Instead, just use `=None`. | Style +| Y067 | Don't use `Incomplete \| None = None` in argument annotations. Instead, just use `=None`. | Style +| Y068 | Don't use `@override` in stub files. Problems with a function signature deviating from its superclass are inherited from the implementation, and other tools such as stubtest are better placed to recognize deviations between stubs and the implementation. | Understanding stubs ## Warnings disabled by default diff --git a/flake8_pyi/errors.py b/flake8_pyi/errors.py index 239d76e..756b6a2 100644 --- a/flake8_pyi/errors.py +++ b/flake8_pyi/errors.py @@ -127,6 +127,8 @@ class Error(NamedTuple): 'put the code for new Python versions first, e.g. "{new_syntax}"' ) Y067 = 'Y067 Use "=None" instead of "Incomplete | None = None"' +Y068 = 'Y068 Do not use "@override" in stub files.' + Y090 = ( 'Y090 "{original}" means ' '"a tuple of length 1, in which the sole element is of type {typ!r}". ' diff --git a/flake8_pyi/visitor.py b/flake8_pyi/visitor.py index e2fd4b2..4bb1cb6 100644 --- a/flake8_pyi/visitor.py +++ b/flake8_pyi/visitor.py @@ -226,6 +226,7 @@ def _is_object(node: ast.AST | None, name: str, *, from_: Container[str]) -> boo ) _is_Generic = partial(_is_object, name="Generic", from_=_TYPING_MODULES) _is_Unpack = partial(_is_object, name="Unpack", from_=_TYPING_MODULES) +_is_override = partial(_is_object, name="override", from_=_TYPING_MODULES) def _is_union(node: ast.expr | None) -> TypeIs[ast.BinOp]: @@ -2032,6 +2033,12 @@ def check_protocol_param_kinds( pos_or_kw, errors.Y091.format(arg=pos_or_kw.arg, method=node.name) ) + def check_for_override(self, node: ast.FunctionDef | ast.AsyncFunctionDef) -> None: + for deco in node.decorator_list: + if _is_override(deco): + self.error(deco, errors.Y068) + return + @staticmethod def _is_positional_pre_570_argname(name: str) -> bool: # https://peps.python.org/pep-0484/#positional-only-arguments @@ -2094,6 +2101,7 @@ def _visit_function(self, node: ast.FunctionDef | ast.AsyncFunctionDef) -> None: self.check_self_typevars(node, decorator_names) if self.enclosing_class_ctx.is_protocol_class: self.check_protocol_param_kinds(node, decorator_names) + self.check_for_override(node) def visit_arg(self, node: ast.arg) -> None: if _is_NoReturn(node.annotation): diff --git a/tests/override.pyi b/tests/override.pyi new file mode 100644 index 0000000..34b4329 --- /dev/null +++ b/tests/override.pyi @@ -0,0 +1,24 @@ +import typing +import typing as t +from typing import override, override as over + +import typing_extensions + +class Foo: + def f(self) -> None: ... + def g(self) -> None: ... + def h(self) -> None: ... + def j(self) -> None: ... + def k(self) -> None: ... + +class Bar(Foo): + @override # Y068 Do not use "@override" in stub files. + def f(self) -> None: ... + @typing.override # Y068 Do not use "@override" in stub files. + def g(self) -> None: ... + @t.override # Ideally we'd catch this too, but the infrastructure is not in place. + def h(self) -> None: ... + @over # Ideally we'd catch this too, but the infrastructure is not in place. + def j(self) -> None: ... + @typing_extensions.override # Y068 Do not use "@override" in stub files. + def k(self) -> None: ...