-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Unify OldDiagnostic
and Message
#18391
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
// diagnostic refactor, but if it still exists in this form at the end of the refactor, we | ||
// should just update the call sites. | ||
#[expect(clippy::needless_pass_by_value)] | ||
pub fn new<T: Violation>(kind: T, range: TextRange, file: &SourceFile) -> Self { |
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 might be time to update the call sites.
With the exception of this method itself, I think the rest of this hunk was moved from diagnostic.rs
with no changes.
CodSpeed Instrumentation Performance ReportMerging #18391 will not alter performanceComparing Summary
|
|
Well the benchmark results are disappointing, but we're still ahead of where we were before #18234:
I think I already went through all of the |
I haven't reviewed this but I think we should remove the rule method if possible (not as part of this pr) |
12e4da0
to
d04d40d
Compare
Alright, we're finally back under the codspeed threshold, but the macro changes in the last commit feel potentially hacky to me. There are also still some ~2% perf regressions, but I think I handled all of the hot spots. The remaining new code in the flamegraphs looks pretty broadly distributed, which I think means it's just from the new One big thing I noticed is that all of the rules in I'll leave this in draft because I'm a bit uncertain about the proc macro changes, especially, but I think it's ready for at least a quick review. For a little more context on the proc macro changes, my goal was to make the conversion of ruff/crates/ruff_linter/src/registry.rs Lines 18 to 26 in 87a15f7
which both requires formatting a
|
I skimmed through the changes and had two thoughts:
|
Thanks for having a look!
I tried that first, but my attempts turned out to be much larger refactors than expected or didn't help the performance like I hoped. For example, this commit was a slight regression from the commit before it and also made
Ah I did forget about that one. I tried using I can give both of these another try, but I'll probably start in a separate PR since I think they can be handled separately. |
87a15f7
to
f8a9009
Compare
## Summary As the title says, this PR removes the `Message::to_rule` method by replacing related uses of `Rule` with `NoqaCode` (or the rule's name in the case of the cache). Where it seemed a `Rule` was really needed, we convert back to the `Rule` by parsing either the rule name (with `str::parse`) or the `NoqaCode` (with `Rule::from_code`). I thought this was kind of like cheating and that it might not resolve this part of Micha's [comment](#18391 (comment)): > because we can't add Rule to Diagnostic or **have it anywhere in our shared rendering logic** but after looking again, the only remaining `Rule` conversion in rendering code is for the SARIF output format. The other two non-test `Rule` conversions are for caching and writing a fix summary, which I don't think fall into the shared rendering logic. That leaves the SARIF format as the only real problem, but maybe we can delay that for now. The motivation here is that we won't be able to store a `Rule` on the new `Diagnostic` type, but we should be able to store a `NoqaCode`, likely as a string. ## Test Plan Existing tests ## [Benchmarks](https://codspeed.io/astral-sh/ruff/branches/brent%2Fremove-to-rule) Almost no perf regression, only -1% on `linter/default-rules[large/dataset.py]`. --------- Co-authored-by: Micha Reiser <[email protected]>
ec38bad
to
00c7690
Compare
I spent a while today looking into refactoring Even with a const I think I need to do one more pass here to check the sites of Footnotes
|
00c7690
to
b4930e4
Compare
I rebased on main and reverted the proc macro changes to see if they really help the benchmarks now or not. Edit: then I dropped the revert again since reverting caused an 8.7% perf regression. |
b4930e4
to
3655543
Compare
With #18736 on hold for now, I think this is ready for review. |
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.
I think this is a step in the right direction. What's unclear to me is how we can avoid the noqa_code.rule()
calls entirely (or in most cases). I left a few suggestions but that might require some more larger refactors (or maybe not?).
The reason why I think we should avoid NoqaCode::rule
is because: a) it's slow and b) I don't think we should store a NoqaCode
on ruff_db::Diagnostic
(we can, but I would prefer not to)
pub diagnostic: db::Diagnostic, | ||
|
||
// these fields are specific to rule violations | ||
pub fix: Option<Fix>, | ||
pub parent: Option<TextSize>, | ||
pub(crate) noqa_offset: Option<TextSize>, | ||
noqa_code: Option<NoqaCode>, | ||
pub(crate) noqa_code: Option<NoqaCode>, |
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.
One challenge with the approach you've taken here is that I don't think we can store a NoqaCode
on ruff_db::Diagnostic
. Ideally, the type on the ruff_db::Diagnostic
would just be a String
or an otherwise mostly agnostic type.
I don't know how much that changes to your refactor. I guess we could call Rule::from_code
in the places where we need to go back from code to Rule
. I sort of would prefer this but I suspect that this would be slower because that requires actual parsing (and not just matching).
if diagnostic | ||
.noqa_code() | ||
.and_then(|code| code.rule()) | ||
.is_none_or(|rule| !settings.rules.should_fix(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.
This would be a larger refactor, I think. But it would be somewhat nice if diagnostic.build_fix
would return an Option<FixBuilder>
that's only Some
if we should generate a fix. But that's probably a larger refactor too. This would have the advantage that we still have a reference to Rule
inside of DiagnosticBuilder
. But I fear this would be another large and very painful refactor and I'm not sure if its' worth it.
Actually. I don't think that would work because we always want to generate a fix to show it to users even when it shouldn't get applied.... Although, looking at the code here I'm not sure if that's how Ruff currently works. Either way, I think this coudl simplify things actually...
Instead of what I wrote above. DiagnosticBuilder::with_fix
or set_fix
could still check if the fix should be applied and then wrap the Fix
in a FixWithFixability
which is an enum with two variants: Fix(Fix)
, DisplayOnly(Fix)
where DisplayOnly
is a fix that we only want to show but not count in statistics or apply when applying code fixes.
Either way. I think that would be a separate refactor
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 certainly be wrong, but my reading of this section of code is that it's removing fixes that the user explicitly marked as unfixable. Should we still generate a fix for display in those cases?
If not, I think I came up with a very simple solution, just applying both the applicability from your other comment and this should_fix
check in DiagnosticGuard::set_fix
. I'll open a PR soon to see what you think.
crates/ruff_linter/src/linter.rs
Outdated
.fix_safety | ||
.resolve_applicability(diagnostic.rule(), fix.applicability()); | ||
diagnostic.set_fix(fix.with_applicability(fixed_applicability)); | ||
if let Ok(rule) = diagnostic.name().parse() { |
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 guess, we could also eagerly resolve the applibabily during construction instead of doing it here.
Should this use noqa_code
instead of name().parse()
?
path: &Path, | ||
source: &SourceKind, | ||
) -> String { | ||
diagnostics.retain(|msg| !msg.is_syntax_error()); |
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 so confusing ... looking forward to have a single print_diagnostics
function that prints all diagnostics.
@@ -340,6 +350,16 @@ See also https://github.com/astral-sh/ruff/issues/2186. | |||
} | |||
} | |||
} | |||
|
|||
impl NoqaCode { |
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.
By how much does this blow up ruff's code size?
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 guess if there's one NoqaCode
per rule and ~900 rules, this adds ~900 constants (which might get inlined?) and ~900 match
arms. Is that a lot? I don't really have a good intuition for the effects of these kinds of changes on code size.
Anyway, I'm still hoping we can revert this part of the PR if we can recover the perf hits in other ways, hopefully from your other suggestions.
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 suggest building ruff and comparing the binary sizes. You can also use https://github.com/RazrFalcon/cargo-bloat to get an understanding of the compiled code
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 got these results from du -b target/release/ruff
:
41,559,344 bytes for main
41,669,840 bytes for this branch
so I guess this one is 110 kilobytes bigger. NoqaCode::rule
does make an appearance in cargo-bloat too. Rule::from_str
is present in both, so it would be nice if we ever get to remove that too.
cargo-bloat output
this branch:
File .text Size Crate Name
0.2% 0.3% 76.0KiB ruff_linter ruff_linter::checkers::ast::analyze::expression::expression
0.1% 0.3% 72.1KiB ruff <ruff::args::CheckCommand as clap_builder::derive::Args>::augment_args
0.1% 0.2% 51.4KiB ruff_workspace? <ruff_workspace::options::_::<impl serde::de::Deserialize for ruff_workspace::options::LintOptionsWire>::deserialize::__Visitor as serde::de::Visitor>::visit_map
0.1% 0.2% 51.4KiB ruff_workspace? <ruff_workspace::options::_::<impl serde::de::Deserialize for ruff_workspace::options::LintOptionsWire>::deserialize::__Visitor as serde::de::Visitor>::visit_map
0.1% 0.2% 51.4KiB ruff_workspace? <ruff_workspace::options::_::<impl serde::de::Deserialize for ruff_workspace::options::LintOptionsWire>::deserialize::__Visitor as serde::de::Visitor>::visit_map
0.1% 0.2% 43.8KiB ruff_linter? <ruff_linter::codes::Rule as core::str::traits::FromStr>::from_str
0.1% 0.2% 39.8KiB ruff_linter ruff_linter::checkers::ast::analyze::statement::statement
0.1% 0.2% 35.7KiB ruff_workspace? <ruff_workspace::options::_::<impl serde::de::Deserialize for ruff_workspace::options::LintOptionsWire>::deserialize::__Visitor as serde::de::Visitor>::visit_map
0.1% 0.2% 35.7KiB ruff_workspace? <ruff_workspace::options::_::<impl serde::de::Deserialize for ruff_workspace::options::LintOptionsWire>::deserialize::__Visitor as serde::de::Visitor>::visit_map
0.1% 0.2% 35.3KiB ruff_linter <alloc::vec::Vec<T,A> as core::clone::Clone>::clone
0.1% 0.2% 35.1KiB ruff_python_codegen ruff_python_codegen::generator::Generator::unparse_stmt
0.1% 0.1% 32.4KiB ruff_workspace ruff_workspace::configuration::LintConfiguration::as_rule_table
0.1% 0.1% 32.4KiB ruff_workspace ruff_workspace::configuration::Configuration::into_settings
0.1% 0.1% 31.3KiB ruff_linter ruff_linter::checkers::ast::analyze::definitions::definitions
0.1% 0.1% 31.3KiB ruff_linter ruff_linter::checkers::ast::analyze::deferred_scopes::deferred_scopes
0.1% 0.1% 31.2KiB ruff_linter ruff_linter::codes::NoqaCode::rule
0.1% 0.1% 30.8KiB ruff_workspace ruff_workspace::configuration::Configuration::from_options
0.1% 0.1% 29.8KiB libcst_native libcst_native::parser::grammar::python::__parse_compound_stmt
0.1% 0.1% 29.5KiB ruff_server <toml::value::Value as serde::de::Deserializer>::deserialize_any
0.1% 0.1% 29.5KiB ruff <toml::value::Value as serde::de::Deserializer>::deserialize_any
44.5% 95.4% 21.3MiB And 33811 smaller methods. Use -n N to show more.
46.7% 100.0% 22.4MiB .text section size, the file size is 47.9MiB
main:
File .text Size Crate Name
0.1% 0.3% 72.1KiB ruff <ruff::args::CheckCommand as clap_builder::derive::Args>::augment_args
0.1% 0.2% 51.4KiB ruff_workspace? <ruff_workspace::options::_::<impl serde::de::Deserialize for ruff_workspace::options::LintOptionsWire>::deserialize::__Visitor as serde::de::Visitor>::visit_map
0.1% 0.2% 51.4KiB ruff_workspace? <ruff_workspace::options::_::<impl serde::de::Deserialize for ruff_workspace::options::LintOptionsWire>::deserialize::__Visitor as serde::de::Visitor>::visit_map
0.1% 0.2% 51.4KiB ruff_workspace? <ruff_workspace::options::_::<impl serde::de::Deserialize for ruff_workspace::options::LintOptionsWire>::deserialize::__Visitor as serde::de::Visitor>::visit_map
0.1% 0.2% 46.2KiB ruff_linter ruff_linter::checkers::ast::analyze::statement::statement
0.1% 0.2% 43.8KiB ruff_linter? <ruff_linter::codes::Rule as core::str::traits::FromStr>::from_str
0.1% 0.2% 37.8KiB ruff_linter ruff_linter::checkers::ast::analyze::expression::expression
0.1% 0.2% 35.7KiB ruff_workspace? <ruff_workspace::options::_::<impl serde::de::Deserialize for ruff_workspace::options::LintOptionsWire>::deserialize::__Visitor as serde::de::Visitor>::visit_map
0.1% 0.2% 35.7KiB ruff_workspace? <ruff_workspace::options::_::<impl serde::de::Deserialize for ruff_workspace::options::LintOptionsWire>::deserialize::__Visitor as serde::de::Visitor>::visit_map
0.1% 0.2% 35.3KiB ruff_linter <alloc::vec::Vec<T,A> as core::clone::Clone>::clone
0.1% 0.2% 35.1KiB ruff_python_codegen ruff_python_codegen::generator::Generator::unparse_stmt
0.1% 0.1% 32.4KiB ruff_workspace ruff_workspace::configuration::LintConfiguration::as_rule_table
0.1% 0.1% 32.4KiB ruff_workspace ruff_workspace::configuration::Configuration::into_settings
0.1% 0.1% 30.8KiB ruff_workspace ruff_workspace::configuration::Configuration::from_options
0.1% 0.1% 29.8KiB libcst_native libcst_native::parser::grammar::python::__parse_compound_stmt
0.1% 0.1% 29.5KiB ruff_server <toml::value::Value as serde::de::Deserializer>::deserialize_any
0.1% 0.1% 29.5KiB ruff <toml::value::Value as serde::de::Deserializer>::deserialize_any
0.1% 0.1% 28.7KiB libcst_native? <libcst_native::nodes::expression::Expression as core::clone::Clone>::clone
0.1% 0.1% 28.7KiB ruff_linter ruff_linter::checkers::ast::analyze::deferred_scopes::deferred_scopes
0.1% 0.1% 27.9KiB ruff <ruff::args::FormatCommand as clap_builder::derive::Args>::augment_args
44.7% 95.5% 21.3MiB And 33578 smaller methods. Use -n N to show more.
46.7% 100.0% 22.3MiB .text section size, the file size is 47.8MiB
Summary -- This PR unifies the remaining differences between `OldDiagnostic` and `Message` (`OldDiagnostic` was only missing an optional `noqa_offset` field) and replaces `Message` with `OldDiagnostic`. The biggest functional difference is that the combined `OldDiagnostic` kind no longer implements `AsRule` for an infallible conversion to `Rule`. This was pretty easy to work around with `is_some_and` and `is_none_or` in the few places it was needed. In `LintContext::report_diagnostic_if_enabled` we can just use the new `Violation::rule` method, which takes care of most cases. Test Plan -- Existing tests Future Work -- I think it's time to start shifting some of these fields to the new `Diagnostic` kind. I believe we want `Fix` for sure, but I'm less sure about the others. We may want to keep a thin wrapper type here anyway to implement a `rule` method, so we could leave some of these fields on that too.
it's not exactly direct because we're still matching on a bunch of &'static strs, but it's better than having to call Rule::from_code, which has to format a string and then reparse the NoqaCode into pieces
3655543
to
08a227e
Compare
By the way. I don't think any of those other refactors should block this PR. We can merge it and then try to reduce the places where we use |
* main: (68 commits) Unify `OldDiagnostic` and `Message` (#18391) [`pylint`] Detect more exotic NaN literals in `PLW0177` (#18630) [`flake8-async`] Mark autofix for `ASYNC115` as unsafe if the call expression contains comments (#18753) [`flake8-bugbear`] Mark autofix for `B004` as unsafe if the `hasattr` call expr contains comments (#18755) Enforce `pytest` import for decorators (#18779) [`flake8-comprehension`] Mark autofix for `C420` as unsafe if there's comments inside the dict comprehension (#18768) [flake8-async] fix detection for large integer sleep durations in `ASYNC116` rule (#18767) Update dependency ruff to v0.12.0 (#18790) Update taiki-e/install-action action to v2.53.2 (#18789) Add lint rule for calling chmod with non-octal integers (#18541) Mark `RET501` fix unsafe if comments are inside (#18780) Use `LintContext::report_diagnostic_if_enabled` in `check_tokens` (#18769) [UP008]: use `super()`, not `__super__` in error messages (#18743) Use Depot Windows runners for `cargo test` (#18754) Run ty benchmarks when `ruff_benchmark` changes (#18758) Disallow newlines in format specifiers of single quoted f- or t-strings (#18708) [ty] Add more benchmarks (#18714) [ty] Anchor all exclude patterns (#18685) Include changelog reference for other major versions (#18745) Use updated pre-commit id (#18718) ...
## Summary This PR avoids one of the three calls to `NoqaCode::rule` from #18391 by applying per-file ignores in the `LintContext`. To help with this, it also replaces all direct uses of `LinterSettings.rules.enabled` with a `LintContext::enabled` (or `Checker::enabled`, which defers to its context) method. There are still some direct accesses to `settings.rules`, but as far as I can tell these are not in a part of the code where we can really access a `LintContext`. I believe all of the code reachable from `check_path`, where the replaced per-file ignore code was, should be converted to the new methods. ## Test Plan Existing tests, with a single snapshot updated for RUF100, which I think actually shows a more accurate diagnostic message now.
…ard` and remove `NoqaCode::rule` (#18834) ## Summary This PR removes the last two places we were using `NoqaCode::rule` in `linter.rs` (see #18391 (comment) and #18391 (comment)) by checking whether fixes are actually desired before adding them to a `DiagnosticGuard`. I implemented this by storing a `Violation`'s `Rule` on the `DiagnosticGuard` so that we could check if it was enabled in the embedded `LinterSettings` when trying to set a fix. All of the corresponding `set_fix` methods on `OldDiagnostic` were now unused (except in tests where I just set `.fix` directly), so I moved these to the guard instead of keeping both sets. The very last place where we were using `NoqaCode::rule` was in the cache. I just reverted this to parsing the `Rule` from the name. I had forgotten to update the comment there anyway. Hopefully this doesn't cause too much of a perf hit. In terms of binary size, we're back down almost to where `main` was two days ago (#18391 (comment)): ``` 41,559,344 bytes for main 2 days ago 41,669,840 bytes for #18391 41,653,760 bytes for main now (after #18391 merged) 41,602,224 bytes for this branch ``` Only 43 kb up, but that shouldn't all be me this time :) ## Test Plan Existing tests and benchmarks on this PR
Summary
This PR unifies the remaining differences between
OldDiagnostic
andMessage
(OldDiagnostic
was only missing an optionalnoqa_offset
field) andreplaces
Message
withOldDiagnostic
.The biggest functional difference is that the combined
OldDiagnostic
kind nolonger implements
AsRule
for an infallible conversion toRule
. This waspretty easy to work around with
is_some_and
andis_none_or
in the few placesit was needed. In
LintContext::report_diagnostic_if_enabled
we can just usethe new
Violation::rule
method, which takes care of most cases.Most of the interesting changes are in this range before I started renaming.
Test Plan
Existing tests
Future Work
I think it's time to start shifting some of these fields to the new
Diagnostic
kind. I believe we want
Fix
for sure, but I'm less sure about the others. Wemay want to keep a thin wrapper type here anyway to implement a
rule
method,so we could leave some of these fields on that too.