-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Return DiagnosticGuard
from Checker::report_diagnostic
#18232
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 adds a `DiagnosticGuard` type to ruff that is adapted from the `DiagnosticGuard` and `LintDiagnosticGuard` types from ty. This guard is returned by `Checker::report_diagnostic` and derefs to a `ruff_diagnostics::Diagnostic` (`OldDiagnostic`), allowing methods like `OldDiagnostic::set_fix` to be called on the result. On `Drop` the `DiagnosticGuard` pushes its contained `OldDiagnostic` to the `Checker`. The main motivation for this is to make a following PR adding a `SourceFile` to each diagnostic easier. For every rule where a `Checker` is available, this will now only require modifying `Checker::report_diagnostic` rather than all the rules. In the few cases where we need to create a diagnostic before we know if we actually want to emit it, there is a `DiagnosticGuard::defuse` method, which consumes the guard without emitting the diagnostic. I was able to restructure about half of the rules that naively called this to avoid calling it, but a handful of rules still need it. One of the fairly common patterns where `defuse` was needed initially was something like ```rust let diagnostic = Diagnostic::new(DiagnosticKind, range); if !checker.enabled(diagnostic.rule()) { return; } ``` So I also added a `Checker::checked_report_diagnostic` method that handles this check internally. That helped to avoid some additional `defuse` calls. The name is a bit repetitive, so I'm definitely open to suggestions there. I included a warning against using it in the docs since, as we've seen, the conversion from a diagnostic to a rule is actually pretty expensive. Test Plan -- Existing tests
|
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. I didn't review all rule changes (there are too many).
I think it would be good to add rule
to Violation
to avoid the need to go through diagnostic.rule
only to check if the rule is enabled (which could lead to a somewhat significant perf regression).
range: TextRange, | ||
) -> Option<DiagnosticGuard<'chk, 'a>> { | ||
let diagnostic = Diagnostic::new(kind, range); | ||
if self.enabled(diagnostic.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.
It's unfortunate that this has to go through diagnostic.rule
which requires a lookup. I'm leaning towards adding a rule
method to ViolationMetadata
. That should be easy enough to derive and does make sense to me. This will also allow us to resolve the noqa code in a future version.
That makes me wonder if report_diagnostic
should always to the self::enabled
call but I suspect that handling with the Option
return type is too annoying in many cases that it isn't worth it.
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 nice, I'll try to clean #18234 up for review at some point then. That makes sense to resolve the NoqaCode
and store that on the Diagnostic
instead but use the Rule
directly here.
It does sound pretty appealing to always do the enabled
check if it's cheap enough too. I think in most cases it should just be a let-else
that returns if None
, but it will be another big refactor to apply that everywhere for sure. I'm getting better at using ast-grep for these, though 😄
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 does sound pretty appealing to always do the enabled check if it's cheap enough too. I think in most cases it should just be a let-else that returns if None, but it will be another big refactor to apply that everywhere for sure. I'm getting better at using ast-grep for these, though 😄
Let's keep it at what we have for now. We can consider this if it proves necessary
if is_guarded_by_try_except(expr, module, name, semantic) { | ||
diagnostic.defuse(); | ||
return; | ||
} |
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 would be nice if we could avoid creating the diagnsotic by moving this check before the report_diagnostic
call. Diagnostics are rather heavy weight and creating them for what's valid code is non ideal.
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 reworked this a bit to avoid the defuse
call. I also stored a QualifiedName
directly on the rule struct to avoid a to_string
call, but I needed to pass along a lifetime parameter in the derive macro using split_for_impl
too. I could revert that part if we wanted, but it seemed like it could be helpful in general.
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.
On second thought, I could definitely revert this. 2/4 airflow rules I updated actually needed String
s, so I couldn't change those to QualifiedName
and the mix is a bit inconsistent.
crates/ruff_linter/src/rules/airflow/rules/suggested_to_update_3_0.rs
Outdated
Show resolved
Hide resolved
crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_function_call.rs
Outdated
Show resolved
Hide resolved
I avoided this initially, but it will be cheap enough once #18234 lands
* main: [ty] Support ephemeral uv virtual environments (#18335) Add a `ViolationMetadata::rule` method (#18234) Return `DiagnosticGuard` from `Checker::report_diagnostic` (#18232) [flake8_use_pathlib]: Replace os.symlink with Path.symlink_to (PTH211) (#18337) [ty] Support cancellation and retry in the server (#18273) [ty] Synthetic function-like callables (#18242) [ty] Support publishing diagnostics in the server (#18309) Add Autofix for ISC003 (#18256) [`pyupgrade`]: new rule UP050 (`useless-class-metaclass-type`) (#18334) [pycodestyle] Make `E712` suggestion not assume a context (#18328)
* main: (246 commits) [ty] Simplify signature types, use them in `CallableType` (astral-sh#18344) [ty] Support ephemeral uv virtual environments (astral-sh#18335) Add a `ViolationMetadata::rule` method (astral-sh#18234) Return `DiagnosticGuard` from `Checker::report_diagnostic` (astral-sh#18232) [flake8_use_pathlib]: Replace os.symlink with Path.symlink_to (PTH211) (astral-sh#18337) [ty] Support cancellation and retry in the server (astral-sh#18273) [ty] Synthetic function-like callables (astral-sh#18242) [ty] Support publishing diagnostics in the server (astral-sh#18309) Add Autofix for ISC003 (astral-sh#18256) [`pyupgrade`]: new rule UP050 (`useless-class-metaclass-type`) (astral-sh#18334) [pycodestyle] Make `E712` suggestion not assume a context (astral-sh#18328) put similar dunder-call tests next to each other (astral-sh#18343) [ty] Derive `PartialOrd, Ord` for `KnownInstanceType` (astral-sh#18340) [ty] Simplify `Type::try_bool()` (astral-sh#18342) [ty] Simplify `Type::normalized` slightly (astral-sh#18339) [ty] Move arviz off the list of selected primer projects (astral-sh#18336) [ty] Add --config-file CLI arg (astral-sh#18083) [ty] Tell the user why we inferred a certain Python version when reporting version-specific syntax errors (astral-sh#18295) [ty] Implement implicit inheritance from `Generic[]` for PEP-695 generic classes (astral-sh#18283) [ty] Add hint if async context manager is used in non-async with statement (astral-sh#18299) ...
Summary
This PR adds a
DiagnosticGuard
type to ruff that is adapted from theDiagnosticGuard
andLintDiagnosticGuard
types from ty. This guard is returned byChecker::report_diagnostic
and derefs to aruff_diagnostics::Diagnostic
(OldDiagnostic
), allowing methods likeOldDiagnostic::set_fix
to be called on the result. OnDrop
theDiagnosticGuard
pushes its containedOldDiagnostic
to theChecker
.The main motivation for this is to make a following PR adding a
SourceFile
to each diagnostic easier. For every rule where aChecker
is available, this will now only require modifyingChecker::report_diagnostic
rather than all the rules.In the few cases where we need to create a diagnostic before we know if we actually want to emit it, there is a
DiagnosticGuard::defuse
method, which consumes the guard without emitting the diagnostic. I was able to restructure about half of the rules that naively called this to avoid calling it, but a handful of rules still need it.One of the fairly common patterns where
defuse
was needed initially was something likeSo I also added a
Checker::checked_report_diagnostic
method that handles this check internally. That helped to avoid some additionaldefuse
calls. The name is a bit repetitive, so I'm definitely open to suggestions there. I included a warning against using it in the docs since, as we've seen, the conversion from a diagnostic to a rule is actually pretty expensive.Test Plan
Existing tests