-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Add Checker::import_from_typing
#17340
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
Summary -- This PR replaces uses of version-dependent imports from `typing` or `typing_extensions` with a centralized `Checker::import_from_typing` method. The idea here is to make the fix for #9761 (whatever it ends up being) applicable to all of the rules performing similar checks. I found these with a pretty naive grep for `"typing_extensions"` in the `ruff_linter/src/rules` directory, so if there are any rules I missed, please let me know. @AlexWaygood mentioned "lots of rules" [here](#9761 (comment)), so I think this is likely. Test Plan -- Existing tests for FAST002, PYI019, and PYI034.
|
Is the goal to replace all edit generation from
You can go through the references of For example, ruff/crates/ruff_linter/src/rules/flake8_annotations/helpers.rs Lines 129 to 142 in 410aa4b
|
My goal was just to replace imports that might come from
Yeah this is a good example, it looks like these are both in The one downside of replacing all |
|
I went through all of the references for
ruff/crates/ruff_linter/src/rules/flake8_pyi/rules/bad_generator_return_type.rs Lines 128 to 142 in 8b2727c
I believe the same is true for ruff/crates/ruff_python_stdlib/src/typing.rs Lines 340 to 355 in 8b2727c
I actually accepted new snapshot results for |
| /// | ||
| /// On Python <`python_version`, `member` is imported from `typing_extensions`, while on Python | ||
| /// >=`python_version`, it is imported from `typing`. | ||
| pub(crate) fn import_from_typing( |
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.
Should this be a method on the Importer instead? This is, I think, the main interface to generate import edits:
ruff/crates/ruff_linter/src/checkers/ast/mod.rs
Lines 434 to 437 in 172af7b
| /// The [`Importer`] for the current file, which enables importing of other modules. | |
| pub(crate) const fn importer(&self) -> &Importer<'a> { | |
| &self.importer | |
| } |
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.
It could be, but we get the Python version from Checker and also the SemanticModel for Importer::get_or_import_symbol, so we'd have to pass a couple more arguments if it were an Importer method.
I also think the Checker will be more likely to contain any additional information we need to fix #9761, such as the LinterSettings, if we add a configuration option.
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.
The Checker creates the Importer so we can just update the Importer to also contain a reference to the SemanticModel but I trust your judgement about the issue that needs to be fixed so we can leave it as is for now and do a follow-up change if required.
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.
Oh interesting, I see. I think I'll lean toward leaving it like this for now, but I'll keep this in mind if we don't end up using any more code from the Checker. It would logically make more sense on the Importer, as you said.
MichaReiser
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.
We can more freely change behavior of fixes because they aren't observed as "breaking" for users unlike rules that can trigger new lint violations. The main reason for gating fixes behind preview is to get more feedback on the fixe's correctness.
AlexWaygood
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.
This is great, thank you!
| member: &str, | ||
| position: TextSize, | ||
| python_version: PythonVersion, | ||
| ) -> Result<(Edit, String), ResolutionError> { |
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.
not really something to be addressed in this PR, but FWIW I don't love the return type of get_or_import_symbol. It seems too easy to misuse it by throwing away the second element of the tuple when you get an Ok(Edit, String) returned. E.g. #15853
It also seems like it's too easy to call .ok() on the Result returned, when what you're meant to do is to use it in combination with try_set_fix() so that the information from the error is logged to the terminal when --verbose is specified on the CLI
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.
Agreed, I thought that return type looked a bit suspicious (especially when I considered adding a third element to the tuple 😆)
I'll keep an eye on this and see if I can come up with anything.
* main: [red-knot] Specialize `str.startswith` for string literals (#17351) [syntax-errors] `yield`, `yield from`, and `await` outside functions (#17298) [red-knot] Refresh diagnostics when changing related files (#17350) Add `Checker::import_from_typing` (#17340) Don't add chaperone space after escaped quote in triple quote (#17216) [red-knot] Silence errors in unreachable type annotations / class bases (#17342)
|
Can this be merged with the logic for the import upgrading rules in PYUP as that should have version info for all these import calls. |
Do you have any specific pyupgrade rules in mind? I looked through them briefly and didn't see any obvious ones. I thought most of these rules only run at all when the target Python version is high enough, but it's certainly plausible that this logic could be generalized and used elsewhere. I guess the idea here was that |
|
|
|
Ah yes, thank you! Those did not come up in my searches but look very relevant. |
Summary -- This PR resolves #9761 by adding a linter configuration option to disable `typing_extensions` imports. As mentioned [here], it would be ideal if we could detect whether or not `typing_extensions` is available as a dependency automatically, but this seems like a much easier fix in the meantime. The default for the new option `disable-typing-extensions` is `false`, preserving the current behavior. Setting it to `true` will bail out of the new `Checker::import_from_typing` method (#17340) with a new `TypingImportError::TypingExtensionsDisabled` error, which should log an explanatory message whenever `Diagnostic::try_set_fix` is used. [here]: #9761 (comment) Test Plan -- New linter tests exercising several combinations of Python versions and the new config option for PYI019. We could also test other rules, but any rule using `import_from_typing` should behave the same way.
Summary -- This PR resolves #9761 by adding a linter configuration option to disable `typing_extensions` imports. As mentioned [here], it would be ideal if we could detect whether or not `typing_extensions` is available as a dependency automatically, but this seems like a much easier fix in the meantime. The default for the new option `disable-typing-extensions` is `false`, preserving the current behavior. Setting it to `true` will bail out of the new `Checker::import_from_typing` method (#17340) with a new `TypingImportError::TypingExtensionsDisabled` error, which should log an explanatory message whenever `Diagnostic::try_set_fix` is used. [here]: #9761 (comment) Test Plan -- New linter tests exercising several combinations of Python versions and the new config option for PYI019. We could also test other rules, but any rule using `import_from_typing` should behave the same way.
Summary -- This PR resolves #9761 by adding a linter configuration option to disable `typing_extensions` imports. As mentioned [here], it would be ideal if we could detect whether or not `typing_extensions` is available as a dependency automatically, but this seems like a much easier fix in the meantime. The default for the new option, `typing-extensions`, is `true`, preserving the current behavior. Setting it to `false` will bail out of the new `Checker::typing_importer` method, which has been refactored from the `Checker::import_from_typing` method in #17340), with `None`, which is then handled specially by each rule that calls it. I considered some alternatives to a config option, such as checking if `typing_extensions` has been imported or checking for a `TYPE_CHECKING` block we could use, but I think defaulting to allowing `typing_extensions` imports and allowing the user to disable this with an option is both simple to implement and pretty intuitive. [here]: #9761 (comment) Test Plan -- New linter tests exercising several combinations of Python versions and the new config option for PYI019. I also added tests for the other affected rules, but only in the case where the new config option is enabled. The rules' existing tests also cover the default case.
Summary
This PR replaces uses of version-dependent imports from
typingortyping_extensionswith a centralizedChecker::import_from_typingmethod.The idea here is to make the fix for #9761 (whatever it ends up being) applicable to all of the rules performing similar checks.
I found these with a pretty naive grep for
"typing_extensions"in theruff_linter/src/rulesdirectory, so if there are any rules I missed, please let me know. @AlexWaygood mentioned "lots of rules" here, so I think this is likely.Test Plan
Existing tests for FAST002, PYI019, and PYI034.