Skip to content
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

Tracking issue: Attribute refactor #131229

Open
7 of 8 tasks
jdonszelmann opened this issue Oct 4, 2024 · 6 comments
Open
7 of 8 tasks

Tracking issue: Attribute refactor #131229

jdonszelmann opened this issue Oct 4, 2024 · 6 comments
Assignees
Labels
A-ast Area: AST A-attributes Area: Attributes (`#[…]`, `#![…]`) C-discussion Category: Discussion or questions that doesn't represent real issues. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@jdonszelmann
Copy link
Contributor

jdonszelmann commented Oct 4, 2024

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 under compiler/ depend on rustc_ast just to use ast::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"] aka MetaNameValueStr. To be able to do that, the ast contains an enum AttrArgsEq that already has a variant for when eventually it is lowered:

pub enum AttrArgsEq {
Ast(P<Expr>),
Hir(MetaItemLit),
}

For one part of the compilation process, the Ast variant is always active and Hir 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:

unreachable!("in literal form when walking mac args eq: {:?}", lit)

Another case of partial lowering is the tokens field:

Which is later extensively defended against, making sure this really happened:

debug_assert!(!attr.ident().is_some_and(|ident| self.is_ignored_attr(ident.name)));
debug_assert!(!attr.is_doc_comment());
let ast::Attribute { kind, id: _, style, span } = attr;
if let ast::AttrKind::Normal(normal) = kind {
normal.item.hash_stable(self, hasher);
style.hash_stable(self, hasher);
span.hash_stable(self, hasher);
assert_matches!(
normal.tokens.as_ref(),
None,
"Tokens should have been removed during lowering!"
);
} else {

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, but 100 attributes is a lot

Image

But 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 MetaItems 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 parsing TokenStreams, something that you'd think we should abstract away a little after parsing.

An example of such custom parsing logic:

let get = |name| {
let Some(attr) = self.get_attr(def_id, name) else {
return Bound::Unbounded;
};
debug!("layout_scalar_valid_range: attr={:?}", attr);
if let Some(
&[
ast::NestedMetaItem::Lit(ast::MetaItemLit {
kind: ast::LitKind::Int(a, _),
..
}),
],
) = attr.meta_item_list().as_deref()
{
Bound::Included(a.get())
} else {
self.dcx().span_delayed_bug(
attr.span,
"invalid rustc_layout_scalar_valid_range attribute",
);
Bound::Unbounded
}
};

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 an ast::Attribute, or if it doesn't, you'd have to create more variants like AttrArgsEq::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

Lower attributes during `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 actual ast::Attributes from before lowering, while other functions validate new hir::Attributes. Bugs here seem easy to make, since even though currently these are the same type, they don't always contain the same fields....

The "real solution": parse, don't validate

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:

enum InlineKind {
    Always,
    Never,
    Normal
}

enum Attribute {
    Diagnostic {
        message: Symbol,
        name: Symbol,
        notes: Vec<Symbol>
    },
    Inline(InlineKind),
    Coverage(bool),
    // ...
}

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:

  • some errors might now be thrown in different stages of the compiler (earlier). I'd say this can actually be an advantage, but I've not talked to enough people to know whether this is a problem anywhere
  • A file with code to parse these attributes will contain code of many different features. I personally like that, but it also means that those features themselves become slightly less self-contained.
  • Validity of attributes still needs to be checked on-site. (like track_caller not being valid on closures, given certain feature flags)
  • Affects large parts of the compiler, also unstable parts, where people are actively opening merge requests and might run into conflicts.

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.


Everything above was my original issue, that changes were needed to attributes. Everything below is tracking the progress of these changes


Steps

Already completed

Future

introduce rustc_attr_validation

At this point, not much has changed as to validation. Next to rustc_attr_parsing and rustc_attr_data_structures, I intend to create rustc_attr_validation. This will represent all the logic after ast lowering, for when a tcx is available and we can run queries. Some of this currently happens in rustc_passes/check_attr.rs. However, even the fact that we will be able to exhaustively match on an enum of attributes will make mistakes harder. I intend to make more changes, such as forcing new attributes to list what kinds of targets they're valid on.

Document these changes

Of course, I'll already have documentation on all the previous changes in code. However, I intend to write a post on the dev guide as well to make sure that in the future, people know how to use the infrastructure for attributes

Port all attributes to this system

At this point, with all infrastructure in place, I expect a few PRs porting all attributes to be parsed in rustc_attr_parsing. I might ask others to help here, which is now possible when things are documented in the devguide.

Also introduce some parsed attributes in the AST

This is an idea of @oli-obk . It might be good to also make a smaller enum of parsed attributes in ast::Attribute. Especially for attributes that can be discarded when lowering, or which we need or need to validate earlier on. When we validate them while parsing, we can make fewer mistakes. These can then also contain fields that aren't just tokens to support for example resolving names like with the defines attribute.

Smaller TODOs

  • deprecate and remove tcx.get_attrs
  • partition AttributeKind, for example by making codegen attributes a separate enum for exhaustivity reasons
  • filter proc-macro-derive helper attributes

Related issues

I intend to solve these systematically, as in, by rewriting how attributes are handled these should not be issues anymore.

@rustbot rustbot added the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label Oct 4, 2024
@jieyouxu jieyouxu added T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. A-attributes Area: Attributes (`#[…]`, `#![…]`) labels Oct 4, 2024
@jieyouxu
Copy link
Member

jieyouxu commented Oct 4, 2024

@jieyouxu jieyouxu added C-discussion Category: Discussion or questions that doesn't represent real issues. and removed needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. labels Oct 4, 2024
@bjorn3
Copy link
Member

bjorn3 commented Oct 4, 2024

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.

@jdonszelmann
Copy link
Contributor Author

jdonszelmann commented Oct 4, 2024

hmm, that's a good question. I guess those specifically we couldn't parse and they should stay around as either ast::Attribute or some kind of lowered hir::Attribute a bit like how I describe in the first part of the ideas section. That still makes attributes a lot nicer in the majority of cases.

@jdonszelmann
Copy link
Contributor Author

Related: #131801

@jdonszelmann
Copy link
Contributor Author

@rustbot claim

workingjubilee added a commit to workingjubilee/rustc that referenced this issue Nov 4, 2024
…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
tgross35 added a commit to tgross35/rust that referenced this issue Nov 5, 2024
…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
workingjubilee added a commit to workingjubilee/rustc that referenced this issue Nov 5, 2024
…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
rust-timer added a commit to rust-lang-ci/rust that referenced this issue Nov 5, 2024
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
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this issue Dec 6, 2024
…es, r=notriddle

rustdoc: remove eq for clean::Attributes

This change removes the `PartialEq` and `Eq` implementations from `Attributes`. This implementation was not used, and whether the implementation is useful at all is questionable. I care about removing it, because I'm working rust-lang#131229. While simplifying the representation of attributes, I intend to remove attr ids from attributes where possible. They're actually rarely useful. This piece of code uses them, but for no real reason, so I think simply removing the implementation makes most sense. Let me know if there are major objections to this.
rust-timer added a commit to rust-lang-ci/rust that referenced this issue Dec 7, 2024
Rollup merge of rust-lang#133960 - jdonszelmann:remove-eq-on-attributes, r=notriddle

rustdoc: remove eq for clean::Attributes

This change removes the `PartialEq` and `Eq` implementations from `Attributes`. This implementation was not used, and whether the implementation is useful at all is questionable. I care about removing it, because I'm working rust-lang#131229. While simplifying the representation of attributes, I intend to remove attr ids from attributes where possible. They're actually rarely useful. This piece of code uses them, but for no real reason, so I think simply removing the implementation makes most sense. Let me know if there are major objections to this.
@jdonszelmann jdonszelmann changed the title Refactoring attributes in the compiler Tracking issue: Attribute refactor Dec 14, 2024
bors added a commit to rust-lang-ci/rust that referenced this issue Dec 15, 2024
…bk,petrochenkov

Hir attributes

This PR needs some explanation, it's somewhat large.

- This is step one as described in rust-lang/compiler-team#796. I've added a new `hir::Attribute` which is a lowered version of `ast::Attribute`. Right now, this has few concrete effects, however every place that after this PR parses a `hir::Attribute` should later get a pre-parsed attribute as described in rust-lang/compiler-team#796 and transitively rust-lang#131229.
- an extension trait `AttributeExt` is added, which is implemented for both `ast::Attribute` and `hir::Atribute`. This makes `hir::Attributes` mostly compatible with code that used to parse `ast::Attribute`. All its methods are also added as inherent methods to avoid having to import the trait everywhere in the compiler.
  - Incremental can not not hash `ast::Attribute` at all.
bors added a commit to rust-lang-ci/rust that referenced this issue Dec 15, 2024
…bk,petrochenkov

Hir attributes

This PR needs some explanation, it's somewhat large.

- This is step one as described in rust-lang/compiler-team#796. I've added a new `hir::Attribute` which is a lowered version of `ast::Attribute`. Right now, this has few concrete effects, however every place that after this PR parses a `hir::Attribute` should later get a pre-parsed attribute as described in rust-lang/compiler-team#796 and transitively rust-lang#131229.
- an extension trait `AttributeExt` is added, which is implemented for both `ast::Attribute` and `hir::Atribute`. This makes `hir::Attributes` mostly compatible with code that used to parse `ast::Attribute`. All its methods are also added as inherent methods to avoid having to import the trait everywhere in the compiler.
  - Incremental can not not hash `ast::Attribute` at all.
bors added a commit to rust-lang-ci/rust that referenced this issue Dec 17, 2024
…=oli-obk

Split up attribute parsing code and move data types to `rustc_attr_data_structures`

This change renames `rustc_attr` to `rustc_attr_parsing`, and splits up the parsing code. At the same time, all the data types used move to `rustc_attr_data_structures`. This is in preparation of also having a third crate: `rustc_attr_validation`

I initially envisioned this as two separate PRs, but I think doing it in one go reduces the number of ways others would have to rebase their changes on this. However, I can still split them.

r? `@oli-obk` (we already discussed how this is a first step in a larger plan)

For a more detailed plan on how attributes are going to change, see rust-lang#131229

Edit: this looks like a giant PR, but the changes are actually rather trivial. Each commit is reviewable on its own, and mostly moves code around. No new logic is added.
github-actions bot pushed a commit to rust-lang/miri that referenced this issue Dec 18, 2024
Split up attribute parsing code and move data types to `rustc_attr_data_structures`

This change renames `rustc_attr` to `rustc_attr_parsing`, and splits up the parsing code. At the same time, all the data types used move to `rustc_attr_data_structures`. This is in preparation of also having a third crate: `rustc_attr_validation`

I initially envisioned this as two separate PRs, but I think doing it in one go reduces the number of ways others would have to rebase their changes on this. However, I can still split them.

r? `@oli-obk` (we already discussed how this is a first step in a larger plan)

For a more detailed plan on how attributes are going to change, see rust-lang/rust#131229

Edit: this looks like a giant PR, but the changes are actually rather trivial. Each commit is reviewable on its own, and mostly moves code around. No new logic is added.
lnicola pushed a commit to lnicola/rust-analyzer that referenced this issue Dec 23, 2024
Split up attribute parsing code and move data types to `rustc_attr_data_structures`

This change renames `rustc_attr` to `rustc_attr_parsing`, and splits up the parsing code. At the same time, all the data types used move to `rustc_attr_data_structures`. This is in preparation of also having a third crate: `rustc_attr_validation`

I initially envisioned this as two separate PRs, but I think doing it in one go reduces the number of ways others would have to rebase their changes on this. However, I can still split them.

r? `@oli-obk` (we already discussed how this is a first step in a larger plan)

For a more detailed plan on how attributes are going to change, see rust-lang/rust#131229

Edit: this looks like a giant PR, but the changes are actually rather trivial. Each commit is reviewable on its own, and mostly moves code around. No new logic is added.
@safinaskar
Copy link
Contributor

I totally agree with "Parse, not validate" part. There are a lot of places in compiler, where we know statically that particular enum variant is forbidden. Ideally, we should use pattern types for enums ( #123646 ) for all such places

bors added a commit to rust-lang-ci/rust that referenced this issue Feb 22, 2025
New attribute parsing infrastructure

Another step in the plan outlined in rust-lang#131229

introduces infrastructure for structured parsers for attributes, as well as converting a couple of complex attributes to have such structured parsers.

This PR may prove too large to review. I left some of my own comments to guide it a little. Some general notes:

- The first commit is basically standalone. It just preps some mostly unrelated sources for the rest of the PR to work. It might not have enormous merit on its own, but not negative merit either. Could be merged alone, but also doesn't make the review a whole lot easier. (but it's only +274 -209)
- The second commit is the one that introduces new infrastructure. It's the important one to review.
- The 3rd commit uses the new infrastructure showing how some of the more complex attributes can be parsed using it. Theoretically can be split up, though the parsers in this commit are the ones that really test the new infrastructure and show that it all works.
- The 4th commit fixes up rustdoc and clippy. In the previous 2 they didn't compile yet while the compiler does. Separated them out to separate concerns and make the rest more palatable.
- The 5th commit blesses some test outputs. Sometimes that's just because a diagnostic happens slightly earlier than before, which I'd say is acceptable. Sometimes a diagnostic is now only emitted once where it would've been twice before (yay! fixed some bugs). One test I actually moved from crashes to fixed, because it simply doesn't crash anymore. That's why this PR  Closes rust-lang#132391. I think most choices I made here are generally reasonable, but let me know if you disagree anywhere.
- The 6th commit adds a derive to pretty print attributes
- The 7th removes smir apis for attributes, for the time being. The api will at some point be replaced by one based on `rustc_ast_data_structures::AttributeKind`

In general, a lot of the additions here are comments. I've found it very important to document new things in the 2nd commit well so other people can start using it.

Closes rust-lang#132391
Closes rust-lang#136717
bors added a commit to rust-lang-ci/rust that referenced this issue Feb 24, 2025
New attribute parsing infrastructure

Another step in the plan outlined in rust-lang#131229

introduces infrastructure for structured parsers for attributes, as well as converting a couple of complex attributes to have such structured parsers.

This PR may prove too large to review. I left some of my own comments to guide it a little. Some general notes:

- The first commit is basically standalone. It just preps some mostly unrelated sources for the rest of the PR to work. It might not have enormous merit on its own, but not negative merit either. Could be merged alone, but also doesn't make the review a whole lot easier. (but it's only +274 -209)
- The second commit is the one that introduces new infrastructure. It's the important one to review.
- The 3rd commit uses the new infrastructure showing how some of the more complex attributes can be parsed using it. Theoretically can be split up, though the parsers in this commit are the ones that really test the new infrastructure and show that it all works.
- The 4th commit fixes up rustdoc and clippy. In the previous 2 they didn't compile yet while the compiler does. Separated them out to separate concerns and make the rest more palatable.
- The 5th commit blesses some test outputs. Sometimes that's just because a diagnostic happens slightly earlier than before, which I'd say is acceptable. Sometimes a diagnostic is now only emitted once where it would've been twice before (yay! fixed some bugs). One test I actually moved from crashes to fixed, because it simply doesn't crash anymore. That's why this PR  Closes rust-lang#132391. I think most choices I made here are generally reasonable, but let me know if you disagree anywhere.
- The 6th commit adds a derive to pretty print attributes
- The 7th removes smir apis for attributes, for the time being. The api will at some point be replaced by one based on `rustc_ast_data_structures::AttributeKind`

In general, a lot of the additions here are comments. I've found it very important to document new things in the 2nd commit well so other people can start using it.

Closes rust-lang#132391
Closes rust-lang#136717
bors added a commit to rust-lang-ci/rust that referenced this issue Feb 24, 2025
New attribute parsing infrastructure

Another step in the plan outlined in rust-lang#131229

introduces infrastructure for structured parsers for attributes, as well as converting a couple of complex attributes to have such structured parsers.

This PR may prove too large to review. I left some of my own comments to guide it a little. Some general notes:

- The first commit is basically standalone. It just preps some mostly unrelated sources for the rest of the PR to work. It might not have enormous merit on its own, but not negative merit either. Could be merged alone, but also doesn't make the review a whole lot easier. (but it's only +274 -209)
- The second commit is the one that introduces new infrastructure. It's the important one to review.
- The 3rd commit uses the new infrastructure showing how some of the more complex attributes can be parsed using it. Theoretically can be split up, though the parsers in this commit are the ones that really test the new infrastructure and show that it all works.
- The 4th commit fixes up rustdoc and clippy. In the previous 2 they didn't compile yet while the compiler does. Separated them out to separate concerns and make the rest more palatable.
- The 5th commit blesses some test outputs. Sometimes that's just because a diagnostic happens slightly earlier than before, which I'd say is acceptable. Sometimes a diagnostic is now only emitted once where it would've been twice before (yay! fixed some bugs). One test I actually moved from crashes to fixed, because it simply doesn't crash anymore. That's why this PR  Closes rust-lang#132391. I think most choices I made here are generally reasonable, but let me know if you disagree anywhere.
- The 6th commit adds a derive to pretty print attributes
- The 7th removes smir apis for attributes, for the time being. The api will at some point be replaced by one based on `rustc_ast_data_structures::AttributeKind`

In general, a lot of the additions here are comments. I've found it very important to document new things in the 2nd commit well so other people can start using it.

Closes rust-lang#132391
Closes rust-lang#136717
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-ast Area: AST A-attributes Area: Attributes (`#[…]`, `#![…]`) C-discussion Category: Discussion or questions that doesn't represent real issues. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

6 participants