-
Notifications
You must be signed in to change notification settings - Fork 1.7k
[pyupgrade] Avoid reporting __future__ features as unnecessary when they are used (UP010)
#19769
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
f11b18d to
31bbfe8
Compare
|
190d13b to
c243b8d
Compare
Note: This requires gating behind preview because it changes the rule's suppression ranges. |
|
Thanks Micha, that makes sense. Hopefully the preview-gating isn't too involved, so I think I'll still take a look at this. Your special case is also very interesting. Apparently assigning to >>> from __future__ import print_function
>>> import __future__
>>> __future__.print_function
_Feature((2, 6, 0, 'alpha', 2), (3, 0, 0, 'alpha', 0), 1048576)
>>> print_function = 1
>>> __future__.print_function
_Feature((2, 6, 0, 'alpha', 2), (3, 0, 0, 'alpha', 0), 1048576)
>>> __future__.print_function = 1
>>> __future__.print_function
1
>>> from __future__ import print_function
>>> print_function
1I'm not sure if we need this to be covered by F401, though, since F811 flags it as |
ntBre
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, this looks good to me in general. I just had a few minor suggestions on the code itself.
More broadly, I think we should probably split this into two PRs. Converting to a binding-based rule and checking for uses of the __future__ imports is a bug fix that resolves the linked issue. But emitting separate diagnostics for each unused import and updating the ranges accordingly are conceptually separate and need to be preview-gated as Micha said. I think they're separate enough that it would be easier to review them in a separate PR.
I do think these changes are nice and worth having, just a better fit for a separate patch, in my opinion.
| .iter() | ||
| .map(|alias| &alias.name) | ||
| .map(ast::Identifier::as_str), | ||
| vec![alias.name.as_str()].into_iter(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we could use std::iter::once here to avoid allocating a Vec just to call into_iter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Noticed. Replaced with std::iter::once
crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_future_import.rs
Show resolved
Hide resolved
crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_future_import.rs
Show resolved
Hide resolved
| if unused_imports.is_empty() { | ||
| /// Process unused future import statements and generate diagnostics with fixes. | ||
| /// Filters out required isort imports and creates removal fixes for unused aliases. | ||
| fn process_unused_future_import(checker: &Checker, stmt: &Stmt, alias: &Alias, node_id: NodeId) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not totally sold on splitting off this helper function. I think with judicious use of let-else we could put this back since it won't be too indented. But I don't feel too strongly either way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed helper function.
moved UnnecessaryFutureImport rule from `statement.rs` to `deferred_scopes.rs` reworked unused future rules determination, now checks bindings added appropriate test cases and snapshots
c243b8d to
f2b99b9
Compare
|
@ntBre Thanks for your review.
Sorry, but I don't understand how to split the PR into two independent PRs. If I fix the bug which implies determining used names from the only |
|
I think you can update the range of the fix without updating the range of the diagnostic itself. The diagnostic range is used for the noqa code and needs to be modified only in Lines 109 to 116 in f0b03c3
For the bug fix, I think you'd need to preserve the old behavior of gathering all of the unused imports in the same import line and emitting a single diagnostic. This is work we have to do one way or another because even if we keep the changes in the same PR, we have to preserve the old behavior when preview is disabled. One way of implementing this might be grouping the diagnostics by parent statement, which you're already computing. Hopefully it's not too much trouble. |
changed RuleGroup to Preview
CodSpeed Instrumentation Performance ReportMerging #19769 will not alter performanceComparing Summary
|
revert diagnosis report changes
It was very helpful, thanks. I believe I implemented what you suggested. So
I will create a second PR with Preview changes when you approve the current PR. |
ntBre
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Excellent, thank you!
I'll look forward to the preview PR (if you're still interested, no pressure!). The ranges highlighting only the specific unused import were really nice :) but this neatly avoids a breaking change for now.
pyupgrade] Reworked to not report future features as unnecessary when they are used (UP010)pyupgrade] Avoid reporting __future__ features as unnecessary when they are used (UP010)
* main: (29 commits) [ty] add docstrings to completions based on type (#20008) [`pyupgrade`] Avoid reporting `__future__` features as unnecessary when they are used (`UP010`) (#19769) [`flake8-use-pathlib`] Add fixes for `PTH102` and `PTH103` (#19514) [ty] correctly ignore field specifiers when not specified (#20002) `Option::unwrap` is now const (#20007) [ty] Re-arrange "list modules" implementation for Salsa caching [ty] Test "list modules" versus "resolve module" in every mdtest [ty] Wire up "list modules" API to make module completions work [ty] Tweak some completion tests [ty] Add "list modules" implementation [ty] Lightly expose `FileModule` and `NamespacePackage` fields [ty] Add some more helper routines to `ModulePath` [ty] Fix a bug when converting `ModulePath` to `ModuleName` [ty] Split out another constructor for `ModuleName` [ty] Add stub-file tests to existing module resolver [ty] Expose some routines in the module resolver [ty] Add more path helper functions [`flake8-annotations`] Remove unused import in example (`ANN401`) (#20000) [ty] distinguish base conda from child conda (#19990) [ty] Fix server hang (#19991) ...
…en they are used (`UP010`) (astral-sh#19769) ## Summary Resolves astral-sh#19561 Fixes the [unnecessary-future-import (UP010)](https://docs.astral.sh/ruff/rules/unnecessary-future-import/) rule to correctly identify when imported __future__ modules are actually used in the code, preventing false positives. I assume there is no way to check usage in `analyze::statements`, because we don't have any usage bindings for imports. To determine unused imports, we have to fully scan the file to create bindings and then check usage, similar to [unused-import (F401)](https://docs.astral.sh/ruff/rules/unused-import/#unused-import-f401). So, `Rule::UnnecessaryFutureImport` was moved from the `analyze::statements` to the `analyze::deferred_scopes` stage. This caused the need to change the logic of future import handling to a bindings-based approach. Also, the diagnostic report was changed. Before ``` | 1 | from __future__ import nested_scopes, generators | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP010 ``` after ``` | 1 | from __future__ import nested_scopes, generators | ^^^^^^^^^^^^^ UP010 ``` I believe this is the correct way, because `generators` may be used, but `nested_scopes` is not. ### Special case I've found out about some specific case. ```python from __future__ import nested_scopes nested_scopes = 1 ``` Here we can treat `nested_scopes` as an unused import because the variable `nested_scopes` shadows it and we can safely remove the future import (my fix does it). But [F401](https://docs.astral.sh/ruff/rules/unused-import/#unused-import-f401) not triggered for such case ([sandbox](https://play.ruff.rs/296d9c7e-0f02-4659-b0c0-78cc21f3de76)) ``` from foo import print_function print_function = 1 ``` In my mind, `print_function` here is an unused import and should be deleted (my IDE highlight it). What do you think? ## Test Plan Added test cases and snapshots: - Split test file into separate _0 and _1 files for appropriate checks. - Added test cases to verify fixes when future module are used. --------- Co-authored-by: Igor Drokin <drokinii1017@gmail.com>
Summary
Resolves #19561
Fixes the unnecessary-future-import (UP010) rule to correctly identify when imported future modules are actually used in the code, preventing false positives.
I assume there is no way to check usage in
analyze::statements, because we don't have any usage bindings for imports. To determine unused imports, we have to fully scan the file to create bindings and then check usage, similar to unused-import (F401). So,Rule::UnnecessaryFutureImportwas moved from theanalyze::statementsto theanalyze::deferred_scopesstage. This caused the need to change the logic of future import handling to a bindings-based approach.Also, the diagnostic report was changed.
Before
after
I believe this is the correct way, because
generatorsmay be used, butnested_scopesis not.Special case
I've found out about some specific case.
Here we can treat
nested_scopesas an unused import because the variablenested_scopesshadows it and we can safely remove the future import (my fix does it).But F401 not triggered for such case (sandbox)
In my mind,
print_functionhere is an unused import and should be deleted (my IDE highlight it). What do you think?Test Plan
Added test cases and snapshots: