Skip to content

Commit 9c9f406

Browse files
committed
Invoke attributes on the statement for statement items
1 parent e9546bd commit 9c9f406

11 files changed

+574
-9
lines changed

compiler/rustc_expand/src/base.rs

+9
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,15 @@ impl Annotatable {
234234

235235
pub fn derive_allowed(&self) -> bool {
236236
match *self {
237+
Annotatable::Stmt(ref stmt) => match stmt.kind {
238+
ast::StmtKind::Item(ref item) => match item.kind {
239+
ast::ItemKind::Struct(..)
240+
| ast::ItemKind::Enum(..)
241+
| ast::ItemKind::Union(..) => true,
242+
_ => false,
243+
},
244+
_ => false,
245+
},
237246
Annotatable::Item(ref item) => match item.kind {
238247
ast::ItemKind::Struct(..) | ast::ItemKind::Enum(..) | ast::ItemKind::Union(..) => {
239248
true

compiler/rustc_expand/src/expand.rs

+21-4
Original file line numberDiff line numberDiff line change
@@ -795,7 +795,14 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
795795
| Annotatable::TraitItem(_)
796796
| Annotatable::ImplItem(_)
797797
| Annotatable::ForeignItem(_) => return,
798-
Annotatable::Stmt(_) => "statements",
798+
Annotatable::Stmt(stmt) => {
799+
// Attributes are stable on item statements,
800+
// but unstable on all other kinds of statements
801+
if stmt.is_item() {
802+
return;
803+
}
804+
"statements"
805+
}
799806
Annotatable::Expr(_) => "expressions",
800807
Annotatable::Arm(..)
801808
| Annotatable::Field(..)
@@ -1266,9 +1273,19 @@ impl<'a, 'b> MutVisitor for InvocationCollector<'a, 'b> {
12661273

12671274
// we'll expand attributes on expressions separately
12681275
if !stmt.is_expr() {
1269-
// FIXME: Handle custom attributes on statements (#15701).
1270-
let attr =
1271-
if stmt.is_item() { None } else { self.take_first_attr_no_derive(&mut stmt) };
1276+
let attr = if stmt.is_item() {
1277+
// FIXME: Implement proper token collection for statements
1278+
if let StmtKind::Item(item) = &mut stmt.kind {
1279+
stmt.tokens = item.tokens.take()
1280+
} else {
1281+
unreachable!()
1282+
};
1283+
self.take_first_attr(&mut stmt)
1284+
} else {
1285+
// Ignore derives on non-item statements for backwards compatibility.
1286+
// This will result in a unused attribute warning
1287+
self.take_first_attr_no_derive(&mut stmt)
1288+
};
12721289

12731290
if let Some(attr) = attr {
12741291
return self

compiler/rustc_expand/src/proc_macro.rs

+20-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::base::{self, *};
22
use crate::proc_macro_server;
33

4+
use rustc_ast::ptr::P;
45
use rustc_ast::token;
56
use rustc_ast::tokenstream::{TokenStream, TokenTree};
67
use rustc_ast::{self as ast, *};
@@ -74,8 +75,20 @@ impl MultiItemModifier for ProcMacroDerive {
7475
_meta_item: &ast::MetaItem,
7576
item: Annotatable,
7677
) -> ExpandResult<Vec<Annotatable>, Annotatable> {
78+
// We need special handling for statement items
79+
// (e.g. `fn foo() { #[derive(Debug)] struct Bar; }`)
80+
let mut is_stmt = false;
7781
let item = match item {
7882
Annotatable::Item(item) => token::NtItem(item),
83+
Annotatable::Stmt(stmt) => {
84+
is_stmt = true;
85+
assert!(stmt.is_item());
86+
87+
// A proc macro can't observe the fact that we're passing
88+
// them an `NtStmt` - it can only see the underlying tokens
89+
// of the wrapped item
90+
token::NtStmt(stmt.into_inner())
91+
}
7992
_ => unreachable!(),
8093
};
8194
let input = if item.pretty_printing_compatibility_hack() {
@@ -106,7 +119,13 @@ impl MultiItemModifier for ProcMacroDerive {
106119
loop {
107120
match parser.parse_item() {
108121
Ok(None) => break,
109-
Ok(Some(item)) => items.push(Annotatable::Item(item)),
122+
Ok(Some(item)) => {
123+
if is_stmt {
124+
items.push(Annotatable::Stmt(P(ecx.stmt_item(span, item))));
125+
} else {
126+
items.push(Annotatable::Item(item));
127+
}
128+
}
110129
Err(mut err) => {
111130
err.emit();
112131
break;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// aux-build:attr-stmt-expr.rs
2+
// aux-build:test-macros.rs
3+
// compile-flags: -Z span-debug
4+
// check-pass
5+
6+
#![feature(proc_macro_hygiene)]
7+
#![feature(stmt_expr_attributes)]
8+
#![feature(rustc_attrs)]
9+
#![allow(dead_code)]
10+
11+
#![no_std] // Don't load unnecessary hygiene information from std
12+
extern crate std;
13+
14+
extern crate attr_stmt_expr;
15+
extern crate test_macros;
16+
use attr_stmt_expr::{expect_let, expect_print_stmt, expect_expr, expect_print_expr};
17+
use test_macros::print_attr;
18+
use std::println;
19+
20+
fn print_str(string: &'static str) {
21+
// macros are handled a bit differently
22+
#[expect_print_expr]
23+
println!("{}", string)
24+
}
25+
26+
macro_rules! make_stmt {
27+
($stmt:stmt) => {
28+
$stmt
29+
}
30+
}
31+
32+
macro_rules! second_make_stmt {
33+
($stmt:stmt) => {
34+
make_stmt!($stmt);
35+
}
36+
}
37+
38+
39+
fn main() {
40+
make_stmt!(struct Foo {});
41+
42+
#[print_attr]
43+
#[expect_let]
44+
let string = "Hello, world!";
45+
46+
#[print_attr]
47+
#[expect_print_stmt]
48+
println!("{}", string);
49+
50+
#[print_attr]
51+
second_make_stmt!(#[allow(dead_code)] struct Bar {});
52+
53+
#[print_attr]
54+
#[rustc_dummy]
55+
struct Other {};
56+
57+
#[expect_expr]
58+
print_str("string")
59+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
PRINT-ATTR INPUT (DISPLAY): #[expect_let] let string = "Hello, world!" ;
2+
PRINT-ATTR INPUT (DEBUG): TokenStream [
3+
Punct {
4+
ch: '#',
5+
spacing: Alone,
6+
span: $DIR/allowed-attr-stmt-expr.rs:1:1: 1:1 (#0),
7+
},
8+
Group {
9+
delimiter: Bracket,
10+
stream: TokenStream [
11+
Ident {
12+
ident: "expect_let",
13+
span: $DIR/allowed-attr-stmt-expr.rs:1:1: 1:1 (#0),
14+
},
15+
],
16+
span: $DIR/allowed-attr-stmt-expr.rs:1:1: 1:1 (#0),
17+
},
18+
Ident {
19+
ident: "let",
20+
span: $DIR/allowed-attr-stmt-expr.rs:1:1: 1:1 (#0),
21+
},
22+
Ident {
23+
ident: "string",
24+
span: $DIR/allowed-attr-stmt-expr.rs:1:1: 1:1 (#0),
25+
},
26+
Punct {
27+
ch: '=',
28+
spacing: Alone,
29+
span: $DIR/allowed-attr-stmt-expr.rs:1:1: 1:1 (#0),
30+
},
31+
Literal {
32+
kind: Str,
33+
symbol: "Hello, world!",
34+
suffix: None,
35+
span: $DIR/allowed-attr-stmt-expr.rs:1:1: 1:1 (#0),
36+
},
37+
Punct {
38+
ch: ';',
39+
spacing: Alone,
40+
span: $DIR/allowed-attr-stmt-expr.rs:1:1: 1:1 (#0),
41+
},
42+
]
43+
PRINT-ATTR INPUT (DISPLAY): #[expect_print_stmt] println ! ("{}", string) ;
44+
PRINT-ATTR INPUT (DEBUG): TokenStream [
45+
Punct {
46+
ch: '#',
47+
spacing: Alone,
48+
span: $DIR/allowed-attr-stmt-expr.rs:1:1: 1:1 (#0),
49+
},
50+
Group {
51+
delimiter: Bracket,
52+
stream: TokenStream [
53+
Ident {
54+
ident: "expect_print_stmt",
55+
span: $DIR/allowed-attr-stmt-expr.rs:1:1: 1:1 (#0),
56+
},
57+
],
58+
span: $DIR/allowed-attr-stmt-expr.rs:1:1: 1:1 (#0),
59+
},
60+
Ident {
61+
ident: "println",
62+
span: $DIR/allowed-attr-stmt-expr.rs:1:1: 1:1 (#0),
63+
},
64+
Punct {
65+
ch: '!',
66+
spacing: Alone,
67+
span: $DIR/allowed-attr-stmt-expr.rs:1:1: 1:1 (#0),
68+
},
69+
Group {
70+
delimiter: Parenthesis,
71+
stream: TokenStream [
72+
Literal {
73+
kind: Str,
74+
symbol: "{}",
75+
suffix: None,
76+
span: $DIR/allowed-attr-stmt-expr.rs:1:1: 1:1 (#0),
77+
},
78+
Punct {
79+
ch: ',',
80+
spacing: Alone,
81+
span: $DIR/allowed-attr-stmt-expr.rs:1:1: 1:1 (#0),
82+
},
83+
Ident {
84+
ident: "string",
85+
span: $DIR/allowed-attr-stmt-expr.rs:1:1: 1:1 (#0),
86+
},
87+
],
88+
span: $DIR/allowed-attr-stmt-expr.rs:1:1: 1:1 (#0),
89+
},
90+
Punct {
91+
ch: ';',
92+
spacing: Alone,
93+
span: $DIR/allowed-attr-stmt-expr.rs:1:1: 1:1 (#0),
94+
},
95+
]
96+
PRINT-ATTR INPUT (DISPLAY): second_make_stmt ! (#[allow(dead_code)] struct Bar { }) ;
97+
PRINT-ATTR INPUT (DEBUG): TokenStream [
98+
Ident {
99+
ident: "second_make_stmt",
100+
span: $DIR/allowed-attr-stmt-expr.rs:1:1: 1:1 (#0),
101+
},
102+
Punct {
103+
ch: '!',
104+
spacing: Alone,
105+
span: $DIR/allowed-attr-stmt-expr.rs:1:1: 1:1 (#0),
106+
},
107+
Group {
108+
delimiter: Parenthesis,
109+
stream: TokenStream [
110+
Punct {
111+
ch: '#',
112+
spacing: Alone,
113+
span: $DIR/allowed-attr-stmt-expr.rs:1:1: 1:1 (#0),
114+
},
115+
Group {
116+
delimiter: Bracket,
117+
stream: TokenStream [
118+
Ident {
119+
ident: "allow",
120+
span: $DIR/allowed-attr-stmt-expr.rs:1:1: 1:1 (#0),
121+
},
122+
Group {
123+
delimiter: Parenthesis,
124+
stream: TokenStream [
125+
Ident {
126+
ident: "dead_code",
127+
span: $DIR/allowed-attr-stmt-expr.rs:1:1: 1:1 (#0),
128+
},
129+
],
130+
span: $DIR/allowed-attr-stmt-expr.rs:1:1: 1:1 (#0),
131+
},
132+
],
133+
span: $DIR/allowed-attr-stmt-expr.rs:1:1: 1:1 (#0),
134+
},
135+
Ident {
136+
ident: "struct",
137+
span: $DIR/allowed-attr-stmt-expr.rs:1:1: 1:1 (#0),
138+
},
139+
Ident {
140+
ident: "Bar",
141+
span: $DIR/allowed-attr-stmt-expr.rs:1:1: 1:1 (#0),
142+
},
143+
Group {
144+
delimiter: Brace,
145+
stream: TokenStream [],
146+
span: $DIR/allowed-attr-stmt-expr.rs:1:1: 1:1 (#0),
147+
},
148+
],
149+
span: $DIR/allowed-attr-stmt-expr.rs:1:1: 1:1 (#0),
150+
},
151+
Punct {
152+
ch: ';',
153+
spacing: Alone,
154+
span: $DIR/allowed-attr-stmt-expr.rs:1:1: 1:1 (#0),
155+
},
156+
]
157+
PRINT-ATTR INPUT (DISPLAY): #[rustc_dummy] struct Other { }
158+
PRINT-ATTR INPUT (DEBUG): TokenStream [
159+
Punct {
160+
ch: '#',
161+
spacing: Alone,
162+
span: $DIR/allowed-attr-stmt-expr.rs:1:1: 1:1 (#0),
163+
},
164+
Group {
165+
delimiter: Bracket,
166+
stream: TokenStream [
167+
Ident {
168+
ident: "rustc_dummy",
169+
span: $DIR/allowed-attr-stmt-expr.rs:1:1: 1:1 (#0),
170+
},
171+
],
172+
span: $DIR/allowed-attr-stmt-expr.rs:1:1: 1:1 (#0),
173+
},
174+
Ident {
175+
ident: "struct",
176+
span: $DIR/allowed-attr-stmt-expr.rs:1:1: 1:1 (#0),
177+
},
178+
Ident {
179+
ident: "Other",
180+
span: $DIR/allowed-attr-stmt-expr.rs:1:1: 1:1 (#0),
181+
},
182+
Group {
183+
delimiter: Brace,
184+
stream: TokenStream [],
185+
span: $DIR/allowed-attr-stmt-expr.rs:1:1: 1:1 (#0),
186+
},
187+
]

0 commit comments

Comments
 (0)