Skip to content

[pyupgrade] Add from __future__ import annotations automatically (UP006)#23260

Merged
ntBre merged 18 commits intoastral-sh:mainfrom
11happy:import_annotation_for_UP006
Mar 11, 2026
Merged

[pyupgrade] Add from __future__ import annotations automatically (UP006)#23260
ntBre merged 18 commits intoastral-sh:mainfrom
11happy:import_annotation_for_UP006

Conversation

@11happy
Copy link
Copy Markdown
Contributor

@11happy 11happy commented Feb 13, 2026

Summary

part of #19359

Test Plan

added tests & snapshots

@astral-sh-bot
Copy link
Copy Markdown

astral-sh-bot bot commented Feb 13, 2026

ruff-ecosystem results

Linter (stable)

✅ ecosystem check detected no linter changes.

Linter (preview)

✅ ecosystem check detected no linter changes.

@11happy 11happy changed the title Import annotation for up006 [ruff] Add import annotation for UP006 Feb 13, 2026
Copy link
Copy Markdown
Contributor

@ntBre ntBre left a comment

Choose a reason for hiding this comment

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

We need to be a bit more careful here to preserve the existing behavior of both FA100 and UP006 on stable and isolate the change to preview mode. We also need to check whether or not the from __future__ import annotations fix has been enabled in the user's configuration.

Comment on lines +2097 to +2102
success: false
exit_code: 1
----- stdout -----
All checks passed!
test.py:1:31: UP006 Use `list` instead of `List` for type annotation
Found 1 error.
No fixes available (1 hidden fix can be enabled with the `--unsafe-fixes` option).
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't think this is testing what it's meant to test now, but I think I'm going to have another comment farther down about why that is.

Comment on lines -304 to -313
if checker.is_rule_enabled(Rule::FutureRewritableTypeAnnotation) {
if !checker.semantic.future_annotations_or_stub()
&& checker.target_version() < PythonVersion::PY39
&& checker.target_version() >= PythonVersion::PY37
if checker.source_type.is_stub()
|| checker.target_version() >= PythonVersion::PY39
|| (checker.target_version() >= PythonVersion::PY37
&& checker.semantic.in_annotation()
&& !checker.settings().pyupgrade.keep_runtime_typing
{
flake8_future_annotations::rules::future_rewritable_type_annotation(checker, expr);
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We need to preserve the existing FA100 behavior for now and only make this a preview change, so we can't just delete the FutureRewritableTypeAnnotation check.

We also need to check if the future-annotations setting is enabled, otherwise UP006 shouldn't apply this fix.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What happens if FA100 is also enabled in preview? I don't think we needed to revert all changes to this code, we needed to make FA100 only fire when preview is disabled.

@ntBre ntBre added rule Implementing or modifying a lint rule preview Related to preview mode features labels Feb 20, 2026
11happy added 5 commits March 5, 2026 13:23
Signed-off-by: Bhuminjay <bhuminjaysoni@gmail.com>
Signed-off-by: Bhuminjay <bhuminjaysoni@gmail.com>
Signed-off-by: Bhuminjay <bhuminjaysoni@gmail.com>
Signed-off-by: Bhuminjay <bhuminjaysoni@gmail.com>
Signed-off-by: Bhuminjay <bhuminjaysoni@gmail.com>
@11happy 11happy force-pushed the import_annotation_for_UP006 branch from b1a99c8 to b5b53eb Compare March 5, 2026 07:57
@11happy
Copy link
Copy Markdown
Contributor Author

11happy commented Mar 5, 2026

Done, I have adressed the feedback & currently converted it to preview. Thank you for having patience with this PR.
: )

11happy and others added 4 commits March 5, 2026 13:32
Signed-off-by: Bhuminjay <bhuminjaysoni@gmail.com>
…rules__pyupgrade__tests__UP006_future.py.snap
Signed-off-by: Bhuminjay <bhuminjaysoni@gmail.com>
@ntBre
Copy link
Copy Markdown
Contributor

ntBre commented Mar 5, 2026

Could you double-check the preview gate again? The ecosystem report is still showing a ton of stable changes.

11happy added 2 commits March 6, 2026 18:19
Signed-off-by: Bhuminjay <bhuminjaysoni@gmail.com>
Signed-off-by: Bhuminjay <bhuminjaysoni@gmail.com>
ntBre
ntBre previously requested changes Mar 6, 2026
Copy link
Copy Markdown
Contributor

@ntBre ntBre left a comment

Choose a reason for hiding this comment

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

Thanks for your continued work on this, but I still don't think we've gotten the preview checks exactly right. We need to handle both of these cases:

  • preserve the behavior of both FA100 and UP006 on stable
  • disable FA100 in preview when the preview fix for UP006 will add the __future__ import (this includes checking future-annotations)
    • the FA100 diagnostic is otherwise redundant

Along these lines, I think we should make sure that we have preview and non-preview tests covering the case where both rules are enabled.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Do we need a new test file, or could we just run one or more of the existing UP006 test fixtures with future-annotations enabled?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

oh sure, I use previous one.

Comment on lines +321 to +323
|| is_up006_future_annotations_fix_enabled(
checker.settings(),
))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should we also check whether future-annotations is enabled here? I think we have to check it again in the rule itself, but if the setting is disabled, I don't think the preview check matters, i.e.:

Suggested change
|| is_up006_future_annotations_fix_enabled(
checker.settings(),
))
|| (is_up006_future_annotations_fix_enabled(
checker.settings(),
) && checker.settings().future_annotations))

}

fn fix_applicability(checker: &Checker) -> (Applicability, Option<Edit>) {
if checker.target_version() >= PythonVersion::PY39 {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The two call sites this was factored out of used 3.10, not 3.9. Is that right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I have it simplified, however this wrt the original change totally removing the FA100 hence used 39

if checker.target_version() >= PythonVersion::PY39 {
return (Applicability::Safe, None);
}
if checker.settings().pyupgrade.keep_runtime_typing {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What is this check for? I don't see this in the original code. We've already checked this when deciding whether or not to apply the rule in crates/ruff_linter/src/checkers/ast/analyze/expression.rs.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I had when I removed the FA100 totaly,

Comment on lines +129 to +134
let (applicability, future_import) = fix_applicability(checker);
let mut secondary_edit = Vec::new();
secondary_edit.push(import_edit);
if let Some(future) = future_import {
secondary_edit.push(future);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We could probably use some iterator tricks here like:

std::iter::once(import_edit).chain(future_import)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

sure

Comment on lines +153 to +160
if checker.settings().future_annotations
&& is_up006_future_annotations_fix_enabled(checker.settings())
{
if checker.future_annotations_or_stub() {
(Applicability::Safe, None)
} else {
let future_import = checker.importer().add_future_import();
(Applicability::Safe, Some(future_import))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm honestly a bit confused by this whole helper function. This doesn't seem like a natural boundary to factor out and couple the applicability and fix. This is also the only case that returns Some(fix), and I don't think the future import fix is supposed to be safe anyway because it changes the behavior of all annotations in the file.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes I agree, I have simplified it. & added the tests

Comment on lines -304 to -313
if checker.is_rule_enabled(Rule::FutureRewritableTypeAnnotation) {
if !checker.semantic.future_annotations_or_stub()
&& checker.target_version() < PythonVersion::PY39
&& checker.target_version() >= PythonVersion::PY37
if checker.source_type.is_stub()
|| checker.target_version() >= PythonVersion::PY39
|| (checker.target_version() >= PythonVersion::PY37
&& checker.semantic.in_annotation()
&& !checker.settings().pyupgrade.keep_runtime_typing
{
flake8_future_annotations::rules::future_rewritable_type_annotation(checker, expr);
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What happens if FA100 is also enabled in preview? I don't think we needed to revert all changes to this code, we needed to make FA100 only fire when preview is disabled.

11happy and others added 2 commits March 9, 2026 22:14
Signed-off-by: Bhuminjay <bhuminjaysoni@gmail.com>
…rules__pyupgrade__tests__UP006_future.py__preview.snap
@11happy
Copy link
Copy Markdown
Contributor Author

11happy commented Mar 9, 2026

Thanks for your continued work on this, but I still don't think we've gotten the preview checks exactly right. We need to handle both of these cases:

  • preserve the behavior of both FA100 and UP006 on stable

  • disable FA100 in preview when the preview fix for UP006 will add the __future__ import (this includes checking future-annotations)

    • the FA100 diagnostic is otherwise redundant

Along these lines, I think we should make sure that we have preview and non-preview tests covering the case where both rules are enabled.

Thank you for the detailed feedback , I have addressed both the cases added tests accordingly & also the stable ecosystem changes are reflected as when future import is already there marking the fix safe.

@ntBre
Copy link
Copy Markdown
Contributor

ntBre commented Mar 9, 2026

Thank you! I pushed a couple of commits addressing my remaining concerns. I'm hoping that clears up the stable ecosystem report, and then I'll take another quick look over the whole diff tomorrow.

ntBre added 3 commits March 9, 2026 19:48
this should fully preserve the stable behavior of a safe fix for Python versions
after 3.10
@ntBre
Copy link
Copy Markdown
Contributor

ntBre commented Mar 10, 2026

Well that's an interesting quirk of the ecosystem check. I guess we don't get any changes even in preview since the future-annotations setting is off by default. That is the correct behavior, though.

@ntBre ntBre requested a review from amyreese March 10, 2026 18:25
@ntBre ntBre changed the title [ruff] Add import annotation for UP006 [pyupgrade] Add from __future__ import annotations automatically (UP006) Mar 10, 2026
@ntBre ntBre dismissed their stale review March 10, 2026 18:28

Feedback has been addressed

@amyreese
Copy link
Copy Markdown
Member

The message for FA100 is super vague, but I know that's not what you're trying to fix here. 😅

@ntBre
Copy link
Copy Markdown
Contributor

ntBre commented Mar 11, 2026

I guess it is kind of what we're trying to fix. This is a step toward getting rid of FA100 entirely! Thanks for taking a look!

@ntBre ntBre merged commit 7d54068 into astral-sh:main Mar 11, 2026
43 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

preview Related to preview mode features rule Implementing or modifying a lint rule

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants