-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
[flake8-type-checking
] Apply TC008
more eagerly in TYPE_CHECKING
blocks and disapply it in stubs
#15180
[flake8-type-checking
] Apply TC008
more eagerly in TYPE_CHECKING
blocks and disapply it in stubs
#15180
Conversation
a quoted type expression.
The stub test case will fail until #15179 is merged, due to the circularity issues with TC007, when TC007 isn't skipped in stub files. |
|
code | total | + violation | - violation | + fix | - fix |
---|---|---|---|---|---|
TC008 | 4 | 4 | 0 | 0 | 0 |
This comment was marked as resolved.
This comment was marked as resolved.
The remaining ecosystem hits look correct to me. |
Actually, we probably need to carve out an additional exception for But that's getting out of scope, since it has nothing to do with execution context. I'll create a follow-up PR for that. |
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.
Thank you.
Do you have any references that I could read why it is okay to apply TC008 more eagerly in typing only context? Having a more detailed explanation might also be useful for users reading the Changelog
The exceptions TC008 currently carves out for attribute and subscript access only are there because some types will only be subscriptable at type checking time (E.g. a class is made generic in a Neither of these cases are relevant in typing only contexts like type checking blocks or stub files. Since they will never be evaluated by CPython, only by static analysis tools, which includes most if not all runtime type checkers. So we only really need to adhere to what type checkers consider correct. |
As for references. I don't think this is something that's explicitly specced out anywhere, except maybe in some specific type checker's documentation. These are just some nuances/heuristics I have learned over the course of many hundreds of hours of working with mypy and to some degree pyright and pytype via typeshed contributions. Once ruff performs deeper analysis it might be possible to detect some of these errors without having to carve out large sweeping exceptions like these two. Although it would involve analyzing both the stubs and the actual source for modules with external stubs. |
Hmm okay, thanks for the explanation. This does make sense but I think it might be better to wait for @AlexWaygood or @carljm to review this change. I was interested whether this would impact runtime type checkers but it seems they replace all types declared in |
@Daverball before I review this PR, could you clarify this a little bit for me:
Does this PR introduce a false positive that you then plan to fix in #15201? Or does this false positive that you identify here already exist on If the former, then I think the false positive should be fixed in this PR, because otherwise there's a chance that we review and merge this PR, then look at your other PR, decide it's not the correct fix, and we're stuck with a false positive on |
@AlexWaygood Yes, the false positive already exists on main, otherwise I would've fixed it in this PR. I just happened to notice the bug while I was implementing this. |
Great, thanks! |
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 preview rule seems to already overlap with our stable rule PYI020
. If you run uvx ruff check --select=PYI,TC --preview
on this snippet saved as a .pyi
file, you already get two diagnostics (one from PYI20, one from TC008) using the latest version of Ruff:
type Y = "list[str]"
This PR makes the problem worse; with this PR branch, running cargo run -- check foo.pyi --select=TC,PYI --preview --no-cache
means that the problem is extended to old-style type aliases. Now we get duplicate diagnostics on this as well in a stub-file context:
from typing import TypeAlias
X: TypeAlias = "list[str]"
We'll need to decide how to resolve the overlap between the two rules before we can consider stabilising TC008
. I think PYI020` is likely to be a rule that stub authors will already have enabled (since it's already stable, and since the whole category is focussed on writing better stubs), so my instinct is to say that it would be best if TC008 did not emit any lints at all regarding stub files. That feels like the way of resolving the overlap between the rules that will be least disruptive to users.
This is obviously not a perfect solution. In an ideal world, I think we'd have a single rule (probably TC008
) that just adjusted its behaviour depending on whether it was analyzing a stub file or a runtime file (what you're doing now). Perhaps that's something we could do as part of rule recategorisation. As it stands, however, this would require deprecating PYI020. That feels unnecessarily disruptive to me, considering that it's a stable rule with no known problems in its implementation.
@AlexWaygood I'm fine with disabling this rule in stub files, if the use-case is already served by |
@AlexWaygood Maybe this means we want to disable TC010 in stubs as well. Although the overlap there should be much smaller, if there is one. It only seems to apply to type expressions that are contained within value expressions, like a |
flake8-type-checking
] Apply TC008
more eagerly in typing contextflake8-type-checking
] Apply TC008
more eagerly in TYPE_CHECKING
blocks
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 for updating this! It overall looks reasonable to me now
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.
Could we also add a note to the docs for this rule pointing users to PYI020 for stub files? (And we could similarly add a note to the PYI020 docs pointing users to this rule for runtime files)
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.
@AlexWaygood Do you happen to know how to cross reference rules from another rule? I couldn't find any examples. Does linking to the corresponding enum variant 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.
Hmmm... no, I'm not sure what the best solution is there. @MichaReiser, do you have any guidance/insight?
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 chatted to @MichaReiser this morning and we concluded that neither of us knows of a great way to do this :-) hardcoding a link to https://docs.astral.sh/ruff/rules/quoted-annotation-in-stub/ might be best for now
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 just double checked and yes, there's no custom handling in
ruff/crates/ruff_dev/src/generate_docs.rs
Lines 118 to 187 in bb12fe9
fn process_documentation(documentation: &str, out: &mut String, rule_name: &str) { | |
let mut in_options = false; | |
let mut after = String::new(); | |
let mut referenced_options = HashSet::new(); | |
// HACK: This is an ugly regex hack that's necessary because mkdocs uses | |
// a non-CommonMark-compliant Markdown parser, which doesn't support code | |
// tags in link definitions | |
// (see https://github.com/Python-Markdown/markdown/issues/280). | |
let documentation = Regex::new(r"\[`([^`]*?)`]($|[^\[(])").unwrap().replace_all( | |
documentation, | |
|caps: &Captures| { | |
format!( | |
"[`{option}`][{option}]{sep}", | |
option = &caps[1], | |
sep = &caps[2] | |
) | |
}, | |
); | |
for line in documentation.split_inclusive('\n') { | |
if line.starts_with("## ") { | |
in_options = line == "## Options\n"; | |
} else if in_options { | |
if let Some(rest) = line.strip_prefix("- `") { | |
let option = rest.trim_end().trim_end_matches('`'); | |
match Options::metadata().find(option) { | |
Some(OptionEntry::Field(field)) => { | |
if field.deprecated.is_some() { | |
eprintln!("Rule {rule_name} references deprecated option {option}."); | |
} | |
} | |
Some(_) => {} | |
None => { | |
panic!("Unknown option {option} referenced by rule {rule_name}"); | |
} | |
} | |
let anchor = option.replace('.', "_"); | |
out.push_str(&format!("- [`{option}`][{option}]\n")); | |
after.push_str(&format!("[{option}]: ../settings.md#{anchor}\n")); | |
referenced_options.insert(option); | |
continue; | |
} | |
} | |
out.push_str(line); | |
} | |
let re = Regex::new(r"\[`([^`]*?)`]\[(.*?)]").unwrap(); | |
for (_, [option, _]) in re.captures_iter(&documentation).map(|c| c.extract()) { | |
if let Some(OptionEntry::Field(field)) = Options::metadata().find(option) { | |
if referenced_options.insert(option) { | |
let anchor = option.replace('.', "_"); | |
after.push_str(&format!("[{option}]: ../settings.md#{anchor}\n")); | |
} | |
if field.deprecated.is_some() { | |
eprintln!("Rule {rule_name} references deprecated option {option}."); | |
} | |
} | |
} | |
if !after.is_empty() { | |
out.push('\n'); | |
out.push('\n'); | |
out.push_str(&after); | |
} | |
} |
It might be possible to use relative links as we do in the linter documentation
Lines 165 to 167 in 68eb0a2
For example, [`unnecessary-iterable-allocation-for-first-element`](rules/unnecessary-iterable-allocation-for-first-element.md) | |
(`RUF015`) is a rule which checks for potentially unperformant use of `list(...)[0]`. The fix | |
replaces this pattern with `next(iter(...))` which can result in a drastic speedup: |
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 could probably also link /rules/quoted-annotation-in-stub.md
so it's only semi-hardlinked, I had to do something similar in a setting to link to a rule.
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.
@AlexWaygood I think a reference from TC008 to PYI020 is sufficient. A reference the other way doesn't make that much sense to me, since most PYI rules are so specific to stub files.
crates/ruff_linter/src/rules/ruff/rules/missing_fstring_syntax.rs
Outdated
Show resolved
Hide resolved
PYI020 bans all quotes in all type expressions in stubs (with very narrow exceptions for |
Co-authored-by: Alex Waygood <[email protected]>
@AlexWaygood Bumping this in case it slipped through the cracks. |
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!
flake8-type-checking
] Apply TC008
more eagerly in TYPE_CHECKING
blocksflake8-type-checking
] Apply TC008
more eagerly in TYPE_CHECKING
blocks and disapply it in stubs
* main: [`pylint`] Fix `unreachable` infinite loop (`PLW0101`) (#15278) fix invalid syntax in workflow file (#15357) [`pycodestyle`] Avoid false positives related to type aliases (`E252`) (#15356) [`flake8-builtins`] Disapply `A005` to stub files (#15350) Improve logging system using `logLevel`, avoid trace value (#15232) [`flake8-builtins`] Rename `A005` and improve its error message (#15348) Spruce up docs for pydoclint rules (#15325) [`flake8-type-checking`] Apply `TC008` more eagerly in `TYPE_CHECKING` blocks and disapply it in stubs (#15180) [red-knot] `knot_extensions` Python API (#15103) Display Union of Literals as a Literal (#14993) [red-knot] all types are assignable to object (#15332) [`ruff`] Parenthesize arguments to `int` when removing `int` would change semantics in `unnecessary-cast-to-int` (`RUF046`) (#15277) [`eradicate`] Correctly handle metadata blocks directly followed by normal blocks (`ERA001`) (#15330) Narrowing for class patterns in match statements (#15223) [red-knot] add call checking (#15200) Spruce up docs for `slice-to-remove-prefix-or-suffix` (`FURB188`) (#15328) [`internal`] Return statements in finally block point to end block for `unreachable` (`PLW0101`) (#15276) [`ruff`] Treat `)` as a regex metacharacter (`RUF043`, `RUF055`) (#15318) Use uv consistently throughout the documentation (#15302)
Summary
quoted-type-alias
(TC008
) can be applied more liberally for annotated type aliases defined inside aif TYPE_CHECKING
block.This also disables
TC008
in stub files, since that use-case is already covered byquoted-annotation-in-stub
(PYI020
).Test Plan
cargo nextest run