-
Notifications
You must be signed in to change notification settings - Fork 1.9k
internal: Rewrite attribute handling #20316
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
internal: Rewrite attribute handling #20316
Conversation
e69ea8b to
b2ecf39
Compare
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.
You're killing me with these big PRs 😬 ..
I have some thought to note down on the EditionedFileId stuff but that will have to wait for later
| ModPath { kind, segments: SmallVec::new_const() } | ||
| } | ||
|
|
||
| pub fn from_tokens( |
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.
Ugh now we have 3 ways to parse these ...
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.
And all subtly different... Yeah I agree.
| // FIXME: We probably should use this in more places. | ||
| /// This is used to avoid interning the whole `AttrDefId`, so we intern just modules and not everything. | ||
| #[salsa_macros::interned(debug, no_lifetime)] | ||
| pub struct InternedModuleId { | ||
| pub loc: ModuleId, | ||
| } |
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.
Sounds like #20001
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.
Yeah, except using interneds and not tracked structs, and being localized to attrs. At least currently, analysis doesn't create this type, only IDE needs to resolve attributes on modules, so the memory impact is low.
#20001 is that right path forward though.
| /// This can be empty if the path is not of 1 or 2 segments exactly. | ||
| pub segments: ArrayVec<SyntaxToken, 2>, | ||
| pub range: TextRange, | ||
| pub is_test: 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.
This doesn't really make sense, we cannot know whether this is the test attribute at this layer, we have no name resolution here after all?
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 have a FIXME for this in hir-def/src/attrs.rs. The general point is that yes, we need proper name resolution for these, but it's hard to do. The reason I put this in Meta is because macros often expand to the full name ::core::prelude::vX::test, which is longer than 2 segments. I could make segments 4-length, but that may somewhat hurt perf. I don't feel strongly about that 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.
I added a FIXME here too.
Sorry! This one could probably be broken (at least to a rewrite of ast-based expand_cfg_attr then to flags-based attrs), but I didn't thought about that until it was too late... |
b2ecf39 to
fccfb8c
Compare
fccfb8c to
c42bd8d
Compare
|
Should we wait with this until the trait solver lands? It looks like it will cause a lot of conflicts |
|
The changes in hir-ty here are minimal, but I can wait until the switch to the next trait solver lands. |
c42bd8d to
541d210
Compare
|
Rebased. This should be unblocked now. |
541d210 to
db870ed
Compare
db870ed to
3a58f2d
Compare
|
Rebased. @Veykril I think this should be ready for review now. |
|
Will take a look tomorrow 👍 |
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 see I never got back to my follow up on this part. I really do not like this, for one it feels wrong to bundle crates into this. And secondly, the crate being ignored for comparison and such gives me the fear that we might rely on the crate somewhere where we suddenly get a different editioned file id that compares the same but has the a different crate. Is there no way for us to go ab out things differently where we need this?
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 dislike that too. In fact I doubted it is the best idea, and I am still not sure it is, and if there is an alternative I will eagerly adopt it. I did it anyway, because this simplifies the code a lot.
Basically, the (almost) only place we use this is to retrieve the cfg when lowering the item tree, to eager-expand #[cfg] and #[cfg_attr]. The alternative is that the item tree will not be crate-aware, and we will instead lazily expand cfgs on the def map. The problems with this approach are:
- It requires us to maintain two codes for cfg expansion, both at AST-level and at tt level (item tree attrs)
- Worse, they are extremely tightly coupled. Basically, they are required to match the behavior of each other precisely, including when they error. This is because of
AttrId- we use the attribute index to refer to the attribute, and this is post cfg-expansion (it must be, since cfg_attr can expand to multiple attrs). That means that any mismatch between the two parsing codes will create very hard to detect bugs.
Also note that the crate is not ignored for comparisons. This is important. We ignore the crate in two places:
- In hash checking, because we support ignoring the crate in comparisons, as per the next bullet. This shouldn't impact interning perf since usually each file belongs to only one crate.
- In some handlers (those that use
from_span_guess_origin()), at the following conditions:- The hasher is latency-sensitive (e.g. on enter), so it cannot afford checking the crate from the def map.
- The hasher operates on AST only (this correlates with the previous point), so the crate doesn't matter
| let mut process = true; | ||
|
|
||
| // Process other crate-level attributes. | ||
| for attr in &*attrs { | ||
| if let Some(cfg) = attr.cfg() | ||
| && self.cfg_options.check(&cfg) == Some(false) | ||
| { | ||
| process = false; | ||
| break; | ||
| } |
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 still needs to be reverted #20316 (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.
No? When hitting CfgDisabled we still process the attrs.
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 process too many, once we hit the cfg we need to stop. That is
#![attr1]
#![cfg(false)]
#![attr2]in this setup, rust only processes attr1, not attr2
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 attributes in the CfgDisabled is only up to the cfg, it's even documented in the variant. This is because collect_item_tree_attrs() early-breaks on #[cfg].
Basically, we switch to expanding cfg_attr in AST form, filter irrelevant attributes from the item tree, and move hir-def attributes (non-item-tree) to be flag-based. The main motivation is memory usage, although this also simplifies the code, and fixes some bugs around handling of `cfg_attr`s.
3a58f2d to
455ca02
Compare
|
@Veykril do you want to merge this, or do you still want to review this? |
|
good to go, I don;t have a better idea for the editioned file id stuff and I don't want to block it on that either. |
|
This horribly broke something in the type system https://rust-analyzer.github.io/metrics/?start=2025-10-15&end= |
|
Both speed and type mismatches regression. Type mismatches is probably some thing I forgot - I'll try to investigate, speed is weirder as this didn't have any measurable effect on speed when I benchmarked, although that was before the trait solver migration. Will check that too. |
|
Okay, I identified the problem for both: for some reason, attribute macros are run twice. This is likely related to the def map changes - I'm reviewing them carefully now. |
|
Okay, I found the problem. The old behavior was subtly wrong, the new behavior is also wrong. I need to look at rustc now to see what it does. |
|
FWIW rustc's attribute handling has been rewritten recently. cc rust-lang/rust#146389 |
|
changelog skip |
Basically, we switch to expanding cfg_attr in AST form, filter irrelevant attributes from the item tree, and move hir-def attributes (non-item-tree) to be flag-based.
The main motivation is memory usage, although this also simplifies the code, and fixes some bugs around handling of
cfg_attrs.This saves 120mb on
analysis-stats .🎉Fixes #10110 and maybe some other issues too.
Fixes #8434.