Skip to content

Commit ceb876b

Browse files
authored
[flake8-pyi] Fix inconsistent handling of forward references for __new__, __enter__, __aenter__ in PYI034 (#22798)
1 parent c5b4ee6 commit ceb876b

3 files changed

Lines changed: 88 additions & 2 deletions

File tree

crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI034.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,3 +385,10 @@ def __new__(cls, /, name: str, bases: tuple[object, ...], attrs: dict[str, objec
385385
class MetaclassInWhichSelfCannotBeUsed8(django.db.models.base.ModelBase):
386386
def __new__(cls, name: builtins.str, bases: tuple, attributes: dict, /, **kw) -> MetaclassInWhichSelfCannotBeUsed8:
387387
...
388+
389+
390+
class UsesStringizedForwardReferences:
391+
def __new__(cls) -> "UsesStringizedForwardReferences": ... # PYI034
392+
def __enter__(self) -> "UsesStringizedForwardReferences": ... # PYI034
393+
async def __aenter__(self) -> "UsesStringizedForwardReferences": ... # PYI034
394+
def __iadd__(self, other) -> "UsesStringizedForwardReferences": ... # PYI034

crates/ruff_linter/src/rules/flake8_pyi/rules/non_self_return_type.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ pub(crate) fn non_self_return_type(
182182

183183
if is_async {
184184
if name == "__aenter__"
185-
&& is_name(returns, &class_def.name)
185+
&& is_name_or_stringized_name(returns, &class_def.name, checker)
186186
&& !is_final(&class_def.decorator_list, semantic)
187187
{
188188
add_diagnostic(checker, stmt, returns, class_def, name);
@@ -198,7 +198,7 @@ pub(crate) fn non_self_return_type(
198198
return;
199199
}
200200

201-
if is_name(returns, &class_def.name) {
201+
if is_name_or_stringized_name(returns, &class_def.name, checker) {
202202
if matches!(name, "__enter__" | "__new__") && !is_final(&class_def.decorator_list, semantic)
203203
{
204204
add_diagnostic(checker, stmt, returns, class_def, name);
@@ -327,6 +327,11 @@ fn is_name(expr: &ast::Expr, name: &str) -> bool {
327327
id.as_str() == name
328328
}
329329

330+
/// Return `true` if the given expression resolves to the given name,
331+
fn is_name_or_stringized_name(expr: &ast::Expr, name: &str, checker: &Checker) -> bool {
332+
checker.match_maybe_stringized_annotation(expr, |expr| is_name(expr, name))
333+
}
334+
330335
/// Return `true` if the given expression resolves to `typing.Self`.
331336
fn is_self(expr: &ast::Expr, checker: &Checker) -> bool {
332337
checker.match_maybe_stringized_annotation(expr, |expr| {

crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI034_PYI034.py.snap

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,3 +472,77 @@ help: Use `Self` as return type
472472
362 |
473473
363 | # Test cases based on issue #20781 - metaclasses that triggers IsMetaclass::Maybe
474474
note: This is an unsafe fix and may change runtime behavior
475+
476+
PYI034 [*] `__new__` methods in classes like `UsesStringizedForwardReferences` usually return `self` at runtime
477+
--> PYI034.py:391:9
478+
|
479+
390 | class UsesStringizedForwardReferences:
480+
391 | def __new__(cls) -> "UsesStringizedForwardReferences": ... # PYI034
481+
| ^^^^^^^
482+
392 | def __enter__(self) -> "UsesStringizedForwardReferences": ... # PYI034
483+
393 | async def __aenter__(self) -> "UsesStringizedForwardReferences": ... # PYI034
484+
|
485+
help: Use `Self` as return type
486+
388 |
487+
389 |
488+
390 | class UsesStringizedForwardReferences:
489+
- def __new__(cls) -> "UsesStringizedForwardReferences": ... # PYI034
490+
391 + def __new__(cls) -> typing.Self: ... # PYI034
491+
392 | def __enter__(self) -> "UsesStringizedForwardReferences": ... # PYI034
492+
393 | async def __aenter__(self) -> "UsesStringizedForwardReferences": ... # PYI034
493+
394 | def __iadd__(self, other) -> "UsesStringizedForwardReferences": ... # PYI034
494+
note: This is an unsafe fix and may change runtime behavior
495+
496+
PYI034 [*] `__enter__` methods in classes like `UsesStringizedForwardReferences` usually return `self` at runtime
497+
--> PYI034.py:392:9
498+
|
499+
390 | class UsesStringizedForwardReferences:
500+
391 | def __new__(cls) -> "UsesStringizedForwardReferences": ... # PYI034
501+
392 | def __enter__(self) -> "UsesStringizedForwardReferences": ... # PYI034
502+
| ^^^^^^^^^
503+
393 | async def __aenter__(self) -> "UsesStringizedForwardReferences": ... # PYI034
504+
394 | def __iadd__(self, other) -> "UsesStringizedForwardReferences": ... # PYI034
505+
|
506+
help: Use `Self` as return type
507+
389 |
508+
390 | class UsesStringizedForwardReferences:
509+
391 | def __new__(cls) -> "UsesStringizedForwardReferences": ... # PYI034
510+
- def __enter__(self) -> "UsesStringizedForwardReferences": ... # PYI034
511+
392 + def __enter__(self) -> typing.Self: ... # PYI034
512+
393 | async def __aenter__(self) -> "UsesStringizedForwardReferences": ... # PYI034
513+
394 | def __iadd__(self, other) -> "UsesStringizedForwardReferences": ... # PYI034
514+
note: This is an unsafe fix and may change runtime behavior
515+
516+
PYI034 [*] `__aenter__` methods in classes like `UsesStringizedForwardReferences` usually return `self` at runtime
517+
--> PYI034.py:393:15
518+
|
519+
391 | def __new__(cls) -> "UsesStringizedForwardReferences": ... # PYI034
520+
392 | def __enter__(self) -> "UsesStringizedForwardReferences": ... # PYI034
521+
393 | async def __aenter__(self) -> "UsesStringizedForwardReferences": ... # PYI034
522+
| ^^^^^^^^^^
523+
394 | def __iadd__(self, other) -> "UsesStringizedForwardReferences": ... # PYI034
524+
|
525+
help: Use `Self` as return type
526+
390 | class UsesStringizedForwardReferences:
527+
391 | def __new__(cls) -> "UsesStringizedForwardReferences": ... # PYI034
528+
392 | def __enter__(self) -> "UsesStringizedForwardReferences": ... # PYI034
529+
- async def __aenter__(self) -> "UsesStringizedForwardReferences": ... # PYI034
530+
393 + async def __aenter__(self) -> typing.Self: ... # PYI034
531+
394 | def __iadd__(self, other) -> "UsesStringizedForwardReferences": ... # PYI034
532+
note: This is an unsafe fix and may change runtime behavior
533+
534+
PYI034 [*] `__iadd__` methods in classes like `UsesStringizedForwardReferences` usually return `self` at runtime
535+
--> PYI034.py:394:9
536+
|
537+
392 | def __enter__(self) -> "UsesStringizedForwardReferences": ... # PYI034
538+
393 | async def __aenter__(self) -> "UsesStringizedForwardReferences": ... # PYI034
539+
394 | def __iadd__(self, other) -> "UsesStringizedForwardReferences": ... # PYI034
540+
| ^^^^^^^^
541+
|
542+
help: Use `Self` as return type
543+
391 | def __new__(cls) -> "UsesStringizedForwardReferences": ... # PYI034
544+
392 | def __enter__(self) -> "UsesStringizedForwardReferences": ... # PYI034
545+
393 | async def __aenter__(self) -> "UsesStringizedForwardReferences": ... # PYI034
546+
- def __iadd__(self, other) -> "UsesStringizedForwardReferences": ... # PYI034
547+
394 + def __iadd__(self, other) -> typing.Self: ... # PYI034
548+
note: This is an unsafe fix and may change runtime behavior

0 commit comments

Comments
 (0)