Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Change Log

New error codes:
* Introduce Y068: Don't use `@override` in stub files.

## 5.5.0

New error codes:
Expand Down
4 changes: 2 additions & 2 deletions ERRORCODES.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ The following warnings are currently emitted by default:
| <a id="Y064" href="#Y064">Y064</a> | Use simpler syntax to define final literal types. For example, use `x: Final = 42` instead of `x: Final[Literal[42]]`. | Style
| <a id="Y065" href="#Y065">Y065</a> | 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
| <a id="Y066" href="#Y066">Y066</a> | When using if/else with `sys.version_info`, put the code for new Python versions first. | Style
| <a id="Y067" href="#Y067">Y067</a> | Don't use `Incomplete | None = None` in
argument annotations. Instead, just use `=None`. | Style
| <a id="Y067" href="#Y067">Y067</a> | Don't use `Incomplete \| None = None` in argument annotations. Instead, just use `=None`. | Style
| <a id="Y068" href="#Y068">Y068</a> | 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

Expand Down
2 changes: 2 additions & 0 deletions flake8_pyi/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}". '
Expand Down
8 changes: 8 additions & 0 deletions flake8_pyi/visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]:
Expand Down Expand Up @@ -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
Comment on lines +2036 to +2040
Copy link
Collaborator

@AlexWaygood AlexWaygood Sep 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or we could just ban it from being imported by adding a branch to _check_import_or_attribute, rather than emitting a separate diagnostic on each usage in every stub file?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about it, but I like this direct approach a bit more: While it may spam a bit in case someone uses it, it's more "direct". "Here's your error", instead of "You're importing something here that's going to be a problem later." It still allows overriding the warning on a case-by-cases basis (although I'm not sure why you would need that). No strong opinion, though.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like emitting the diagnostic at the location of the import (or attribute access) is more in keeping with our other rules such as Y024 and Y057... but I also don't have a strong opinion :-)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, then let's laziness win and not change it?


@staticmethod
def _is_positional_pre_570_argname(name: str) -> bool:
# https://peps.python.org/pep-0484/#positional-only-arguments
Expand Down Expand Up @@ -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):
Expand Down
24 changes: 24 additions & 0 deletions tests/override.pyi
Original file line number Diff line number Diff line change
@@ -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: ...