-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
Refactoring attributes in the compiler #131229
Comments
How will this work with custom tool attributes? These are registered using an attribute inside the crate that uses the attribute on it'w own code. |
hmm, that's a good question. I guess those specifically we couldn't parse and they should stay around as either |
Related: #131801 |
@rustbot claim |
…r=xFrednet Clippy: Move some attribute lints to be early pass (post expansion) r? `@xFrednet` As a side effect it removes a duplicated warning on line 53 of the `allow_attributes` test. I discussed this with `@xFrednet` , and it's mainly to support the attribute rework rust-lang#131229
…r=xFrednet Clippy: Move some attribute lints to be early pass (post expansion) r? ``@xFrednet`` As a side effect it removes a duplicated warning on line 53 of the `allow_attributes` test. I discussed this with ``@xFrednet`` , and it's mainly to support the attribute rework rust-lang#131229
…r=xFrednet Clippy: Move some attribute lints to be early pass (post expansion) r? ```@xFrednet``` As a side effect it removes a duplicated warning on line 53 of the `allow_attributes` test. I discussed this with ```@xFrednet``` , and it's mainly to support the attribute rework rust-lang#131229
Rollup merge of rust-lang#132598 - jdonszelmann:move-lints-to-early, r=xFrednet Clippy: Move some attribute lints to be early pass (post expansion) r? ```@xFrednet``` As a side effect it removes a duplicated warning on line 53 of the `allow_attributes` test. I discussed this with ```@xFrednet``` , and it's mainly to support the attribute rework rust-lang#131229
While working on #125418 with @m-ou-se, I've interacted quite a bit with attributes in the compiler. I've got some thoughts about the way they currently work. I'm posting this as a mix between an explanation of the status quo and why I think that's an issue, in addition to also serving as a kind of tracking issue for these changes if I've convinced you that this is a problem.
Quick Overview
From the ground up: There are several syntaxes for macros, one of those syntaxes is attributes which can have several forms. Attributes can be expanded, either as a user defined attribute macro, or as an "active" built in attribute like
#[test]
. However, some attributes are kept around for the entire compilation lifecycle.These built-in attributes are never expanded. Instead, they are kept around and serve as markers or metadata to guide the compilation process at various stages. There are currently around
100
of these.The problem
While most of what is parsed, is later lowered during
rustc_ast_lowering
, attributes are not, mostly. Many crates undercompiler/
depend onrustc_ast
just to useast::Attribute
. Let's see what that means:Partial lowering and impossible states
One part of attributes actually is lowered, attributes of the form
#[key = "value"]
akaMetaNameValueStr
. To be able to do that, the ast contains an enumAttrArgsEq
that already has a variant for when eventually it is lowered:rust/compiler/rustc_ast/src/ast.rs
Lines 1697 to 1700 in 11ee3a8
For one part of the compilation process, the
Ast
variant is always active andHir
is completely unused, while later in the compiler the reverse is true. In some places people didn't realize this and they provided implementations for both cases while only one could occur,while in other places they are marked as unreachable, like here:
rust/compiler/rustc_ast/src/visit.rs
Line 1241 in 11ee3a8
Another case of partial lowering is the tokens field:
rust/compiler/rustc_ast_lowering/src/lib.rs
Line 948 in 11ee3a8
Which is later extensively defended against, making sure this really happened:
rust/compiler/rustc_query_system/src/ich/impls_syntax.rs
Lines 41 to 54 in 11ee3a8
Parse, don't validate.
I'm a big fan of the blog post Parse, don't validate. Generally rust's type system makes this pattern the most obvious thing to do and it's what I teach my university students every year. However, that is exactly what we aren't doing with attributes. In
rustc_passes/check_attr.rs
we first validate extensively, and emit various diagnostics. However, every single attribute is later parsed again where it is needed. I started making a small overview, but100
attributes is a lotBut basically, of the first 19 attributes I looked at, 5 are
Word
attributes and trivial, a few are parsed together, but in total I've found 11 completely distinct and custom parsing logics, not reusing any parts, spread over as many files and compiler crates.I lied a little there, the parsing does reuse some things. For example, the attributes are turned into
MetaItem
s using common logic. However, that doesn't change the fact that attributes are effectively re-validated scattered around the compiler, and many of these places have more diagnostics of their own, that could've happened during the earlier validation. It also means that at a very late stage in the compiler, we are still dealing with parsingTokenStream
s, something that you'd think we should abstract away a little after parsing.An example of such custom parsing logic:
rust/compiler/rustc_middle/src/ty/context.rs
Lines 1447 to 1469 in 11ee3a8
Flexibility
Finally, though I have fewer concrete examples of this, sticking to
ast::Attribute
throughout the compiler removes quite some flexibility. Everything has to fit into anast::Attribute
, or if it doesn't, you'd have to create more variants likeAttrArgsEq::Hir
to support something in the ast that shouldn't even be part of the ast, forcing you to add a myriad of exceptions in parts of the compiler where such an extra variant isn't relevant yet. Specifically, for #125418 we noticed this because we wanted to do some limited form of name resolution for a path stored in an attribute, which proved next to impossible.Ideas
rustc_ast_lowering
.I've got 90% of a commit ready to do this, and it's what sparked the idea for this issue. It leads to some code duplication. I'm a little unhappy about it, because it forces a lot of changes across the entire compiler, exactly because attribute parsing now happens in so many places. However, it already means that a lot of assertions can be removed because at some part of the compiler, the fact that an
Attribute
can't have certain fields and values anymore becomes encoded in the type system. I'll open a PR for this soon, and we can discuss whether we think this is a good first step.What also doesn't help is that
rustc_attr
currently has logic to validate attributes, but these functions are called in wildly different parts of the compiler. Some functions here validate actualast::Attribute
s from before lowering, while other functions validate newhir::Attribute
s. Bugs here seem easy to make, since even though currently these are the same type, they don't always contain the same fields....As I see it, what would make attributes so much nicer to work with, is if there was a place in the compiler (something like the
rustc_attr
crate, but actually good) where all attributes are turned from their ast tokeny representation into some specific attribute representation. Something like the following, based on the examples I've looked at in the table I showed a little higher up:This structure contains only the information necessary to use each attribute, and all the diagnostics happen while parsing into this structure. That has the added benefit that this datastructure itself serves as great documentation as to what values an attribute allows. It's super clear here that a
#[diagnostic]
attributes contains a message, name and some notes. Currently, you'd have to make sure the written documentation for this attribute is up-to-date enough.The translation from
ast::Attribute
to this new parsed attribute should, I think, happen during AST to HIR lowering.I think the advantages of this should be pretty obvious, based on the examples I've given of the problems with the current approach. However, I could think of some potential blockers people might care about:
track_caller
not being valid on closures, given certain feature flags)Part two I have not worked on personally. I might, if I find enough time, but if someone feels very inspired to pick this up or lead this (or tell my why this is a dumb idea) feel free to.
Progress
hir_stats.rs
remove attribute ids from hir stats (they're simply not needed) #132576rustc_attr
The text was updated successfully, but these errors were encountered: