Skip to content

Conversation

@ntBre
Copy link
Contributor

@ntBre ntBre commented Jun 10, 2025

Summary

Fixes #18602 by:

  1. Avoiding a fix when *args are present
  2. Inserting the Generic base class right before the first keyword argument, if one is present

In an intermediate commit, I also had special handling to avoid a fix in the **kwargs case, but this is treated (roughly) as a normal keyword, and I believe handling it properly falls out of the other keyword fix.

I also updated the add_argument utility function to insert new arguments right before the keyword argument list instead of at the very end of the argument list. This changed a couple of snapshots unrelated to PYI059, but there shouldn't be any functional changes to other rules because all other calls to add_argument were adding a keyword argument anyway.

Test Plan

Existing PYI059 cases, plus new tests based on the issue

@ntBre ntBre requested a review from AlexWaygood as a code owner June 10, 2025 14:30
@ntBre ntBre added bug Something isn't working fixes Related to suggested fixes for violations labels Jun 10, 2025
@github-actions
Copy link
Contributor

github-actions bot commented Jun 10, 2025

ruff-ecosystem results

Linter (stable)

✅ ecosystem check detected no linter changes.

Linter (preview)

ℹ️ ecosystem check detected linter changes. (+0 -0 violations, +0 -26 fixes in 5 projects; 50 projects unchanged)

apache/airflow (+0 -0 violations, +0 -8 fixes)

ruff check --no-cache --exit-zero --ignore RUF9 --no-fix --output-format concise --preview --select ALL

- airflow-core/src/airflow/api_fastapi/auth/managers/base_auth_manager.py:78:22: PYI059 [*] `Generic[]` should always be the last base class
+ airflow-core/src/airflow/api_fastapi/auth/managers/base_auth_manager.py:78:22: PYI059 `Generic[]` should always be the last base class
- airflow-core/src/airflow/api_fastapi/common/exceptions.py:37:23: PYI059 [*] `Generic[]` should always be the last base class
+ airflow-core/src/airflow/api_fastapi/common/exceptions.py:37:23: PYI059 `Generic[]` should always be the last base class
- airflow-core/src/airflow/api_fastapi/core_api/base.py:52:16: PYI059 [*] `Generic[]` should always be the last base class
+ airflow-core/src/airflow/api_fastapi/core_api/base.py:52:16: PYI059 `Generic[]` should always be the last base class
- airflow-core/src/airflow/api_fastapi/core_api/services/public/common.py:37:18: PYI059 [*] `Generic[]` should always be the last base class
+ airflow-core/src/airflow/api_fastapi/core_api/services/public/common.py:37:18: PYI059 `Generic[]` should always be the last base class

langchain-ai/langchain (+0 -0 violations, +0 -8 fixes)

ruff check --no-cache --exit-zero --ignore RUF9 --no-fix --output-format concise --preview

- libs/core/langchain_core/output_parsers/base.py:31:26: PYI059 [*] `Generic[]` should always be the last base class
+ libs/core/langchain_core/output_parsers/base.py:31:26: PYI059 `Generic[]` should always be the last base class
- libs/core/langchain_core/prompts/base.py:45:25: PYI059 [*] `Generic[]` should always be the last base class
+ libs/core/langchain_core/prompts/base.py:45:25: PYI059 `Generic[]` should always be the last base class
- libs/core/langchain_core/runnables/base.py:111:15: PYI059 [*] `Generic[]` should always be the last base class
+ libs/core/langchain_core/runnables/base.py:111:15: PYI059 `Generic[]` should always be the last base class
- libs/core/langchain_core/stores.py:26:16: PYI059 [*] `Generic[]` should always be the last base class
+ libs/core/langchain_core/stores.py:26:16: PYI059 `Generic[]` should always be the last base class

latchbio/latch (+0 -0 violations, +0 -4 fixes)

ruff check --no-cache --exit-zero --ignore RUF9 --no-fix --output-format concise --preview

- src/latch/types/metadata.py:440:25: PYI059 [*] `Generic[]` should always be the last base class
+ src/latch/types/metadata.py:440:25: PYI059 `Generic[]` should always be the last base class
- src/latch/types/metadata.py:489:24: PYI059 [*] `Generic[]` should always be the last base class
+ src/latch/types/metadata.py:489:24: PYI059 `Generic[]` should always be the last base class

python/typeshed (+0 -0 violations, +0 -2 fixes)

ruff check --no-cache --exit-zero --ignore RUF9 --no-fix --output-format concise --preview --select E,F,FA,I,PYI,RUF,UP,W

- stdlib/asyncio/queues.pyi:28:12: PYI059 [*] `Generic[]` should always be the last base class
+ stdlib/asyncio/queues.pyi:28:12: PYI059 `Generic[]` should always be the last base class

zulip/zulip (+0 -0 violations, +0 -4 fixes)

ruff check --no-cache --exit-zero --ignore RUF9 --no-fix --output-format concise --preview --select ALL

- zerver/lib/notes.py:12:16: PYI059 [*] `Generic[]` should always be the last base class
+ zerver/lib/notes.py:12:16: PYI059 `Generic[]` should always be the last base class
- zerver/lib/queue.py:35:18: PYI059 [*] `Generic[]` should always be the last base class
+ zerver/lib/queue.py:35:18: PYI059 `Generic[]` should always be the last base class

Changes by rule (1 rules affected)

code total + violation - violation + fix - fix
PYI059 26 0 0 0 26

Copy link
Member

@AlexWaygood AlexWaygood left a comment

Choose a reason for hiding this comment

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

Nice!

Comment on lines 139 to 147
// adapted from `add_argument`, which doesn't automatically handle inserting before the first
// keyword argument.
let insertion = if let Some(ast::Keyword { range, value, .. }) = arguments.keywords.first() {
let keyword = parenthesized_range(value.into(), arguments.into(), comment_ranges, source)
.unwrap_or(*range);
Edit::insertion(format!("{argument}, "), keyword.start())
} else {
add_argument(argument, arguments, comment_ranges, source)
};
Copy link
Member

Choose a reason for hiding this comment

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

should we be adding this logic to add_argument, so that it's also fixed for other rules that use that utility? If we don't want to do that in this PR, is it worth opening a followup issue for that?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I actually checked the other callers of add_argument, and they were all adding keyword arguments! So I think it makes sense to fix this or add a note to the docs at the very least.

I don't mind doing it here, but I guess it might churn some snapshots to move the existing calls to the front of the kwarg list.

Copy link
Member

@AlexWaygood AlexWaygood Jun 10, 2025

Choose a reason for hiding this comment

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

I'm happy with doing it here or postponing to a followup -- either is fine by me!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I went ahead and did it here. It might have even shortened the net diff!

You're welcome to review again of course, but otherwise I'll just merge this when CI finishes. Thanks for the help!

add_argument(argument, arguments, comment_ranges, source)
};

Ok(Fix::safe_edits(deletion, [insertion]))
Copy link
Member

Choose a reason for hiding this comment

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

Hmm... it's unrelated to your PR, but I'm not sure that this should be a safe fix :-(

Moving the position of Generic[] in the bases tuple could change the behaviour of the code by changing the class's MRO, and it's possible that the code was actually working before the fix. (Having Generic[] as not-the-last base class can sometimes work, it's just... never advisable.)

Even if we're okay with the fix changing the behaviour of the code, is there a possibility it could delete comments in some situations? We discussed this in the original PR so I think we have good test coverage for it, but at that point in time we didn't have as rigorous a policy for whether a fix should be deemed to be unsafe if it removed comments: #11233 (comment)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was wondering about that too, thanks for bringing it up.

It definitely removes comments (playground) trailing the Generic argument.

I'd be fine marking this always unsafe and adding a ## Fix safety section saying that it can change the MRO and also delete comments, if that sounds good to you.

This seems like a lot of changes for a rule about to be stabilized. How are you feeling about that? I'm definitely fine leaving this in preview for another cycle.

Copy link
Member

Choose a reason for hiding this comment

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

I'd be fine marking this always unsafe and adding a ## Fix safety section saying that it can change the MRO and also delete comments, if that sounds good to you.

That sounds good to me, yeah!

This seems like a lot of changes for a rule about to be stabilized. How are you feeling about that? I'm definitely fine leaving this in preview for another cycle.

Agreed -- let's make the changes proposed in this PR and the docs updates proposed in #18601, but leave it in preview for now?

Copy link
Member

@AlexWaygood AlexWaygood left a comment

Choose a reason for hiding this comment

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

Excellent!

@ntBre ntBre merged commit 6051a11 into main Jun 10, 2025
34 checks passed
@ntBre ntBre deleted the brent/fix-pyi059-syntax-error branch June 10, 2025 16:27
dcreager added a commit that referenced this pull request Jun 10, 2025
* main:
  [`pylint`] De-emphasize `__hash__ = Parent.__hash__` (`PLW1641`) (#18613)
  [`flake8-pyi`] Avoid syntax error in the case of starred and keyword arguments (`PYI059`) (#18611)
  [ty] Add support for global __debug__ constant (#18540)
  [`ruff`] Preserve parentheses around `deque` in fix for `unnecessary-empty-iterable-within-deque-call` (`RUF037`) (#18598)
  [`refurb`] Parenthesize lambda and ternary expressions in iter (`FURB122`, `FURB142`) (#18592)
ntBre added a commit that referenced this pull request Jun 12, 2025
## Summary

Fixes #18612 by:
- Bailing out without a fix in the case of `*args`, which I don't think
we can fix reliably
- Using an `Edit::deletion` from `remove_argument` instead of an
`Edit::range_replacement` in the presence of unrecognized keyword
arguments

I thought we could always switch to the `Edit::deletion` approach
initially, but it caused problems when `maxlen` was passed positionally,
which we didn't have any existing tests for.

The replacement fix can easily delete comments, so I also marked the fix
unsafe in these cases and updated the docs accordingly.

## Test Plan

New test cases derived from the issue.

## Stabilization

These are pretty significant changes, much like those to PYI059 in
#18611 (and based a bit on the
implementation there!), so I think it probably makes sense to
un-stabilize this for the 0.12 release, but I'm open to other thoughts
there.
ntBre added a commit that referenced this pull request Sep 4, 2025
Tests and docs look good

We nearly stabilized this last
time (#18601) but it needed one more bug
fix and a documentation improvement (#18611)
ntBre added a commit that referenced this pull request Sep 4, 2025
Tests and docs look good

We nearly stabilized this last time
(#18601), but it needed one more
bug fix and a documentation improvement
(#18611)
ntBre added a commit that referenced this pull request Sep 8, 2025
Tests and docs look good

We nearly stabilized this last time
(#18601), but it needed one more
bug fix and a documentation improvement
(#18611)
ntBre added a commit that referenced this pull request Sep 10, 2025
Tests and docs look good

We nearly stabilized this last time
(#18601), but it needed one more
bug fix and a documentation improvement
(#18611)
ntBre added a commit that referenced this pull request Sep 10, 2025
Tests and docs look good

We nearly stabilized this last time
(#18601), but it needed one more
bug fix and a documentation improvement
(#18611)
ntBre added a commit that referenced this pull request Sep 10, 2025
Tests and docs look good

We nearly stabilized this last time
(#18601), but it needed one more
bug fix and a documentation improvement
(#18611)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working fixes Related to suggested fixes for violations

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PYI059 fix moves Generic after keyword arguments

3 participants