From 3d05d0d719b2dec28cecf088ad17dacd425d3726 Mon Sep 17 00:00:00 2001 From: Jonathan Dung Date: Wed, 8 Apr 2026 07:54:23 +0800 Subject: [PATCH 1/6] Fix #539 --- flake8_pyi/visitor.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/flake8_pyi/visitor.py b/flake8_pyi/visitor.py index 059f846..5e1fca7 100644 --- a/flake8_pyi/visitor.py +++ b/flake8_pyi/visitor.py @@ -366,6 +366,10 @@ def _has_bad_hardcoded_returns( if class_ctx.is_metaclass: return False + # Return false if in a protocol (class inheriting from typing.Protocol); see #539 + if class_ctx.is_protocol_class: + return False + # Much too complex for our purposes to worry # about overloaded functions or abstractmethods if any( From 54893e34eb62347f9ccc9ba604153d006d14408c Mon Sep 17 00:00:00 2001 From: Jonathan Dung Date: Wed, 8 Apr 2026 08:00:30 +0800 Subject: [PATCH 2/6] Add test case for #539 Y034 now is not supposed to apply in a protocol class, so add a test for this behaviour. --- tests/classdefs.pyi | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/classdefs.pyi b/tests/classdefs.pyi index 29e640d..04c0ff2 100644 --- a/tests/classdefs.pyi +++ b/tests/classdefs.pyi @@ -35,6 +35,9 @@ class Bad(object): # Y040 Do not inherit from "object" explicitly, as it is red class AlsoBad(int, builtins.object): ... # Y040 Do not inherit from "object" explicitly, as it is redundant in Python 3 +class Acceptable(typing.Protocol): + def __iadd__(self, other: Self, /) -> object: ... + class Good: def __new__(cls: type[Self], *args: Any, **kwargs: Any) -> Self: ... @abstractmethod From aaf65822eb119c2e4e50dbbc92a29ed33ed758cf Mon Sep 17 00:00:00 2001 From: Jonathan Dung Date: Wed, 8 Apr 2026 15:12:49 +0800 Subject: [PATCH 3/6] Move test cases for Y034 close to each other --- tests/classdefs.pyi | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/classdefs.pyi b/tests/classdefs.pyi index 04c0ff2..0654520 100644 --- a/tests/classdefs.pyi +++ b/tests/classdefs.pyi @@ -35,9 +35,6 @@ class Bad(object): # Y040 Do not inherit from "object" explicitly, as it is red class AlsoBad(int, builtins.object): ... # Y040 Do not inherit from "object" explicitly, as it is redundant in Python 3 -class Acceptable(typing.Protocol): - def __iadd__(self, other: Self, /) -> object: ... - class Good: def __new__(cls: type[Self], *args: Any, **kwargs: Any) -> Self: ... @abstractmethod @@ -92,6 +89,9 @@ class InvalidButPluginDoesNotCrash: def __enter__() -> InvalidButPluginDoesNotCrash: ... async def __aenter__() -> InvalidButPluginDoesNotCrash: ... +class Acceptable(typing.Protocol): + def __iadd__(self, other: Self, /) -> object: ... + class BadIterator1(Iterator[int]): def __iter__(self) -> Iterator[int]: ... # Y034 "__iter__" methods in classes like "BadIterator1" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator1.__iter__", e.g. "def __iter__(self) -> Self: ..." From 5ae919f331dc61b9560094c8cd508978fb6ea866 Mon Sep 17 00:00:00 2001 From: Jonathan Dung Date: Wed, 8 Apr 2026 19:41:42 +0800 Subject: [PATCH 4/6] More descriptive test case name Co-authored-by: Sebastian Rittau --- tests/classdefs.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/classdefs.pyi b/tests/classdefs.pyi index 0654520..2c225de 100644 --- a/tests/classdefs.pyi +++ b/tests/classdefs.pyi @@ -89,7 +89,7 @@ class InvalidButPluginDoesNotCrash: def __enter__() -> InvalidButPluginDoesNotCrash: ... async def __aenter__() -> InvalidButPluginDoesNotCrash: ... -class Acceptable(typing.Protocol): +class ProtocolsAreExemptedFromY034(typing.Protocol): def __iadd__(self, other: Self, /) -> object: ... class BadIterator1(Iterator[int]): From 20797bc524b6d1feaf0a127e274e344a9466773f Mon Sep 17 00:00:00 2001 From: Jonathan Dung Date: Wed, 8 Apr 2026 19:44:10 +0800 Subject: [PATCH 5/6] Exclude protocol methods in Y034 --- ERRORCODES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ERRORCODES.md b/ERRORCODES.md index db2565c..0c8f984 100644 --- a/ERRORCODES.md +++ b/ERRORCODES.md @@ -47,7 +47,7 @@ The following warnings are currently emitted by default: | Y031 | `TypedDict`s should use class-based syntax instead of assignment-based syntax wherever possible. (In situations where this is not possible, such as if a field is a Python keyword or an invalid identifier, this error will not be emitted.) | Style | Y032 | The second argument of an `__eq__` or `__ne__` method should usually be annotated with `object` rather than `Any`. | Correctness | Y033 | Do not use type comments (e.g. `x = ... # type: int`) in stubs. Always use annotations instead (e.g. `x: int`). | Style -| Y034 | Y034 detects common errors where certain methods are annotated as having a fixed return type, despite returning `self` at runtime. Such methods should be annotated with `typing_extensions.Self`. This check looks for:

  **1.**  Any in-place BinOp dunder methods (`__iadd__`, `__ior__`, etc.) that do not return `Self`.
  **2.**  `__new__`, `__enter__` and `__aenter__` methods that return the class's name unparameterised.
  **3.**  `__iter__` methods that return `Iterator`, even if the class inherits directly from `Iterator`.
  **4.**  `__aiter__` methods that return `AsyncIterator`, even if the class inherits directly from `AsyncIterator`.

This check excludes methods decorated with `@overload` or `@abstractmethod`. | Correctness +| Y034 | Y034 detects common errors where certain methods are annotated as having a fixed return type, despite returning `self` at runtime. Such methods should be annotated with `typing_extensions.Self`. This check looks for:

  **1.**  Any in-place BinOp dunder methods (`__iadd__`, `__ior__`, etc.) that do not return `Self`.
  **2.**  `__new__`, `__enter__` and `__aenter__` methods that return the class's name unparameterised.
  **3.**  `__iter__` methods that return `Iterator`, even if the class inherits directly from `Iterator`.
  **4.**  `__aiter__` methods that return `AsyncIterator`, even if the class inherits directly from `AsyncIterator`.

Note that this check excludes methods decorated with `@overload` or `@abstractmethod`, and those in the bodies of protocol classes. | Correctness | Y035 | `__all__`, `__match_args__` and `__slots__` in a stub file should always have values, as these special variables in a `.pyi` file have identical semantics in a stub as at runtime. E.g. write `__all__ = ["foo", "bar"]` instead of `__all__: list[str]`. | Correctness | Y036 | Y036 detects common errors in `__exit__` and `__aexit__` methods. For example, the first argument in an `__exit__` method should either be annotated with `object`, `_typeshed.Unused` (a special alias for `object`) or `type[BaseException] \| None`. | Correctness | Y037 | Use PEP 604 syntax instead of `typing(_extensions).Union` and `typing(_extensions).Optional`. E.g. use `str \| int` instead of `Union[str, int]`, and use `str \| None` instead of `Optional[str]`. | Style From 0a80571733db31284e77b9046664d637ef6b1380 Mon Sep 17 00:00:00 2001 From: Jonathan Dung Date: Thu, 9 Apr 2026 10:00:45 +0800 Subject: [PATCH 6/6] Add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0303ad0..e48e564 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ flake8-pyi uses Calendar Versioning (CalVer). avoid false positives in stub files. This monkey patch has been removed. Instead, we now recommend to disable F821 when running flake8 on stub files. * Remove the now unnecessary `--no-pyi-aware-file-checker` option. +* Y034 no longer triggers in protocol class definitions. ### New Error Codes