-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Add config option to disable typing_extensions imports
#17611
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 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.
this has more to do with the python version than the config option. changing the option doesn't actually have any effect here. the version must be validated somewhere else along the way
|
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.
This looks good to me. I agree with you, the way typing_import works today is a bit awkward where it's very easy to omit the fix instead of omitting the diagnostic. I added an inline suggestion on what you could try to improve the ergonomics.
| pub(crate) fn import_from_typing( | ||
| &self, | ||
| member: &str, | ||
| position: TextSize, | ||
| version_added_to_typing: PythonVersion, | ||
| ) -> Result<(Edit, String), ResolutionError> { | ||
| ) -> Result<Option<(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.
We could use a pattern similar to @BurntSushi's InferContext::report_lint where the inital function returns an Option<Builder> and the builder allows you to perform the typing import.
pub(crate) fn typing_importer(&self, version_added_to_typing: PythonVersion) -> Option<TypingImporte> {
let source_module = if self.target_version() >= version_added_to_typing { "typing"
} else if self.settings.disable_typing_extensions {
return None;
} else {
"typing_extensions"
};
Some(TypingImporter { checker: self, source_module })
}
pub struct TypingImporter<'a> {
checker: &'a Checker,
source_module: &'static src
}
impl TypingImporter<'_> {
pub fn import(member: &str, position: TextSize) -> Result<(Edit, String), ResolutionError> {
...
}
}I'm a bit undecided if member should be passed to typing_importer to avoid accidential reuse.
A simpler alternative would be to return Option<Result<...>, but that's a bit less common but could be fine too.
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, I like that idea, I'll give it a try!
I vaguely remember trying Option<Result> and not liking it for some reason, but maybe that was when I thought try_set_optional_fix was still going to work.
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 feel like we do want member passed to typing_importer because it's the piece that needs to be tied most closely to the PythonVersion. I don't remember seeing any places where intentional reuse would be very helpful, so avoiding the accidental cases makes sense to me.
| /// parameters to fix the issue. In the latter case, the lint is unactionable because | ||
| /// `typing_extensions` imports have been explicitly disallowed, and we should return without a | ||
| /// `Diagnostic`. | ||
| enum Fixable { |
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.
Do we need to update the rule's fix availability documentation (same for other rules) to explain that the fix isn't available if typing extensions isn't available?
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.
Yes, very good point, thanks!
crates/ruff_workspace/src/options.rs
Outdated
| disable-typing-extensions = true | ||
| "# | ||
| )] | ||
| pub disable_typing_extensions: Option<bool>, |
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.
bikeshedding: The disable prefix is uncommon in our existing settings and simply typing_extensions would be more idiomatic or allow_typing_extensions.
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.
Ah, I always like options that default to false so that I can use bool::default 😆 but either of those names is fine with me
|
Thanks again for the suggestions! I've updated the code to:
|
crates/ruff_linter/src/rules/fastapi/rules/fastapi_non_annotated_dependency.rs
Outdated
Show resolved
Hide resolved
| }; | ||
| let (no_return_edit, binding) = checker | ||
| .import_from_typing(member, at, PythonVersion::lowest()) | ||
| .typing_importer(member, PythonVersion::lowest())? |
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.
Is it okay that we treat "typing-extensions" not being available the same as the import failing (will this suppress the diagnostic or only fail to generate the fix?)
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 believe all of the calls in this file are okay because they use PythonVersion::lowest. I think we could even expect or unwrap instead of the first ?, if we wanted, because we should always get a TypingImporter for any supported Python version.
crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_for_self.rs
Outdated
Show resolved
Hide resolved
These were only very nearly duplicates before, but after passing in the `importer` they would have become exact duplicates.
|
I went through all of the changed rules and made changes similar to your suggestions above to restructure the code to call It also made two functions that were only very similar before now identical: The only change we might not is in the very last commit, where I used Thanks for catching these additional benefits of the new API! |
* main: (37 commits) [red-knot] Revert blanket `clippy::too_many_arguments` allow (#17688) Add config option to disable `typing_extensions` imports (#17611) ruff_db: render file paths in diagnostics as relative paths if possible Bump mypy_primer pin (#17685) red_knot_python_semantic: improve `not-iterable` diagnostic [red-knot] Allow all callables to be assignable to @Todo-signatures (#17680) [`refurb`] Mark fix as safe for `readlines-in-for` (`FURB129`) (#17644) Collect preview lint behaviors in separate module (#17646) Upgrade Salsa to a more recent commit (#17678) [red-knot] TypedDict: No errors for introspection dunder attributes (#17677) [`flake8-pyi`] Ensure `Literal[None,] | Literal[None,]` is not autofixed to `None | None` (`PYI061`) (#17659) [red-knot] No errors for definitions of `TypedDict`s (#17674) Update actions/download-artifact digest to d3f86a1 (#17664) [red-knot] Use 101 exit code when there's at least one diagnostic with severity 'fatal' (#17640) [`pycodestyle`] Fix duplicated diagnostic in `E712` (#17651) [airflow] fix typos `AIR312` (#17673) [red-knot] Don't ignore hidden files by default (#17655) Update pre-commit hook astral-sh/ruff-pre-commit to v0.11.7 (#17670) Update docker/build-push-action digest to 14487ce (#17665) Update taiki-e/install-action digest to ab3728c (#17666) ...
Summary
This PR resolves #9761 by adding a linter configuration option to disable
typing_extensionsimports. As mentioned here, it would be ideal if we coulddetect whether or not
typing_extensionsis available as a dependencyautomatically, but this seems like a much easier fix in the meantime.
The default for the new option,
typing-extensions, istrue,preserving the current behavior. Setting it to
falsewill bail out of the newChecker::typing_importermethod, which has been refactored from theChecker::import_from_typingmethod 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_extensionshas been imported or checking for aTYPE_CHECKINGblock we could use, but I think defaulting to allowingtyping_extensionsimports and allowing the user to disable this with an option is both simple to implement and pretty intuitive.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.