Skip to content

Commit 097efa9

Browse files
committed
Auto merge of #49124 - abonander:attr-macro-stmt-expr, r=abonander
Expand Attributes on Statements and Expressions This enables attribute-macro expansion on statements and expressions while retaining the `stmt_expr_attributes` feature requirement for attributes on expressions. closes #41475 cc #38356 @petrochenkov @jseyfried r? @nrc
2 parents 135f334 + 7c0124d commit 097efa9

File tree

16 files changed

+334
-33
lines changed

16 files changed

+334
-33
lines changed

src/libsyntax/ast.rs

+7
Original file line numberDiff line numberDiff line change
@@ -837,6 +837,13 @@ impl Stmt {
837837
_ => false,
838838
}
839839
}
840+
841+
pub fn is_expr(&self) -> bool {
842+
match self.node {
843+
StmtKind::Expr(_) => true,
844+
_ => false,
845+
}
846+
}
840847
}
841848

842849
impl fmt::Debug for Stmt {

src/libsyntax/config.rs

+17-10
Original file line numberDiff line numberDiff line change
@@ -149,17 +149,24 @@ impl<'a> StripUnconfigured<'a> {
149149
fn visit_expr_attrs(&mut self, attrs: &[ast::Attribute]) {
150150
// flag the offending attributes
151151
for attr in attrs.iter() {
152-
if !self.features.map(|features| features.stmt_expr_attributes).unwrap_or(true) {
153-
let mut err = feature_err(self.sess,
154-
"stmt_expr_attributes",
155-
attr.span,
156-
GateIssue::Language,
157-
EXPLAIN_STMT_ATTR_SYNTAX);
158-
if attr.is_sugared_doc {
159-
err.help("`///` is for documentation comments. For a plain comment, use `//`.");
160-
}
161-
err.emit();
152+
self.maybe_emit_expr_attr_err(attr);
153+
}
154+
}
155+
156+
/// If attributes are not allowed on expressions, emit an error for `attr`
157+
pub fn maybe_emit_expr_attr_err(&self, attr: &ast::Attribute) {
158+
if !self.features.map(|features| features.stmt_expr_attributes).unwrap_or(true) {
159+
let mut err = feature_err(self.sess,
160+
"stmt_expr_attributes",
161+
attr.span,
162+
GateIssue::Language,
163+
EXPLAIN_STMT_ATTR_SYNTAX);
164+
165+
if attr.is_sugared_doc {
166+
err.help("`///` is for documentation comments. For a plain comment, use `//`.");
162167
}
168+
169+
err.emit();
163170
}
164171
}
165172

src/libsyntax/ext/base.rs

+8
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ pub enum Annotatable {
3838
Item(P<ast::Item>),
3939
TraitItem(P<ast::TraitItem>),
4040
ImplItem(P<ast::ImplItem>),
41+
Stmt(P<ast::Stmt>),
42+
Expr(P<ast::Expr>),
4143
}
4244

4345
impl HasAttrs for Annotatable {
@@ -46,6 +48,8 @@ impl HasAttrs for Annotatable {
4648
Annotatable::Item(ref item) => &item.attrs,
4749
Annotatable::TraitItem(ref trait_item) => &trait_item.attrs,
4850
Annotatable::ImplItem(ref impl_item) => &impl_item.attrs,
51+
Annotatable::Stmt(ref stmt) => stmt.attrs(),
52+
Annotatable::Expr(ref expr) => &expr.attrs,
4953
}
5054
}
5155

@@ -54,6 +58,8 @@ impl HasAttrs for Annotatable {
5458
Annotatable::Item(item) => Annotatable::Item(item.map_attrs(f)),
5559
Annotatable::TraitItem(trait_item) => Annotatable::TraitItem(trait_item.map_attrs(f)),
5660
Annotatable::ImplItem(impl_item) => Annotatable::ImplItem(impl_item.map_attrs(f)),
61+
Annotatable::Stmt(stmt) => Annotatable::Stmt(stmt.map_attrs(f)),
62+
Annotatable::Expr(expr) => Annotatable::Expr(expr.map_attrs(f)),
5763
}
5864
}
5965
}
@@ -64,6 +70,8 @@ impl Annotatable {
6470
Annotatable::Item(ref item) => item.span,
6571
Annotatable::TraitItem(ref trait_item) => trait_item.span,
6672
Annotatable::ImplItem(ref impl_item) => impl_item.span,
73+
Annotatable::Stmt(ref stmt) => stmt.span,
74+
Annotatable::Expr(ref expr) => expr.span,
6775
}
6876
}
6977

src/libsyntax/ext/expand.rs

+70-19
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,12 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
435435
Annotatable::ImplItem(item) => {
436436
Annotatable::ImplItem(item.map(|item| cfg.fold_impl_item(item).pop().unwrap()))
437437
}
438+
Annotatable::Stmt(stmt) => {
439+
Annotatable::Stmt(stmt.map(|stmt| cfg.fold_stmt(stmt).pop().unwrap()))
440+
}
441+
Annotatable::Expr(expr) => {
442+
Annotatable::Expr(cfg.fold_expr(expr))
443+
}
438444
}
439445
}
440446

@@ -503,6 +509,8 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
503509
Annotatable::Item(item) => token::NtItem(item),
504510
Annotatable::TraitItem(item) => token::NtTraitItem(item.into_inner()),
505511
Annotatable::ImplItem(item) => token::NtImplItem(item.into_inner()),
512+
Annotatable::Stmt(stmt) => token::NtStmt(stmt.into_inner()),
513+
Annotatable::Expr(expr) => token::NtExpr(expr),
506514
})).into();
507515
let tok_result = mac.expand(self.cx, attr.span, attr.tokens, item_tok);
508516
self.parse_expansion(tok_result, kind, &attr.path, attr.span)
@@ -751,6 +759,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
751759
Some(expansion)
752760
}
753761
Err(mut err) => {
762+
err.set_span(span);
754763
err.emit();
755764
self.cx.trace_macros_diag();
756765
kind.dummy(span)
@@ -796,7 +805,13 @@ impl<'a> Parser<'a> {
796805
Expansion::Stmts(stmts)
797806
}
798807
ExpansionKind::Expr => Expansion::Expr(self.parse_expr()?),
799-
ExpansionKind::OptExpr => Expansion::OptExpr(Some(self.parse_expr()?)),
808+
ExpansionKind::OptExpr => {
809+
if self.token != token::Eof {
810+
Expansion::OptExpr(Some(self.parse_expr()?))
811+
} else {
812+
Expansion::OptExpr(None)
813+
}
814+
},
800815
ExpansionKind::Ty => Expansion::Ty(self.parse_ty()?),
801816
ExpansionKind::Pat => Expansion::Pat(self.parse_pat()?),
802817
})
@@ -904,6 +919,18 @@ impl<'a, 'b> Folder for InvocationCollector<'a, 'b> {
904919
let mut expr = self.cfg.configure_expr(expr).into_inner();
905920
expr.node = self.cfg.configure_expr_kind(expr.node);
906921

922+
let (attr, derives, expr) = self.classify_item(expr);
923+
924+
if attr.is_some() || !derives.is_empty() {
925+
// collect the invoc regardless of whether or not attributes are permitted here
926+
// expansion will eat the attribute so it won't error later
927+
attr.as_ref().map(|a| self.cfg.maybe_emit_expr_attr_err(a));
928+
929+
// ExpansionKind::Expr requires the macro to emit an expression
930+
return self.collect_attr(attr, derives, Annotatable::Expr(P(expr)), ExpansionKind::Expr)
931+
.make_expr();
932+
}
933+
907934
if let ast::ExprKind::Mac(mac) = expr.node {
908935
self.check_attributes(&expr.attrs);
909936
self.collect_bang(mac, expr.span, ExpansionKind::Expr).make_expr()
@@ -916,6 +943,16 @@ impl<'a, 'b> Folder for InvocationCollector<'a, 'b> {
916943
let mut expr = configure!(self, expr).into_inner();
917944
expr.node = self.cfg.configure_expr_kind(expr.node);
918945

946+
let (attr, derives, expr) = self.classify_item(expr);
947+
948+
if attr.is_some() || !derives.is_empty() {
949+
attr.as_ref().map(|a| self.cfg.maybe_emit_expr_attr_err(a));
950+
951+
return self.collect_attr(attr, derives, Annotatable::Expr(P(expr)),
952+
ExpansionKind::OptExpr)
953+
.make_opt_expr();
954+
}
955+
919956
if let ast::ExprKind::Mac(mac) = expr.node {
920957
self.check_attributes(&expr.attrs);
921958
self.collect_bang(mac, expr.span, ExpansionKind::OptExpr).make_opt_expr()
@@ -938,33 +975,47 @@ impl<'a, 'b> Folder for InvocationCollector<'a, 'b> {
938975
}
939976

940977
fn fold_stmt(&mut self, stmt: ast::Stmt) -> SmallVector<ast::Stmt> {
941-
let stmt = match self.cfg.configure_stmt(stmt) {
978+
let mut stmt = match self.cfg.configure_stmt(stmt) {
942979
Some(stmt) => stmt,
943980
None => return SmallVector::new(),
944981
};
945982

946-
let (mac, style, attrs) = if let StmtKind::Mac(mac) = stmt.node {
947-
mac.into_inner()
948-
} else {
949-
// The placeholder expander gives ids to statements, so we avoid folding the id here.
950-
let ast::Stmt { id, node, span } = stmt;
951-
return noop_fold_stmt_kind(node, self).into_iter().map(|node| {
952-
ast::Stmt { id: id, node: node, span: span }
953-
}).collect()
954-
};
983+
// we'll expand attributes on expressions separately
984+
if !stmt.is_expr() {
985+
let (attr, derives, stmt_) = self.classify_item(stmt);
986+
987+
if attr.is_some() || !derives.is_empty() {
988+
return self.collect_attr(attr, derives,
989+
Annotatable::Stmt(P(stmt_)), ExpansionKind::Stmts)
990+
.make_stmts();
991+
}
955992

956-
self.check_attributes(&attrs);
957-
let mut placeholder = self.collect_bang(mac, stmt.span, ExpansionKind::Stmts).make_stmts();
993+
stmt = stmt_;
994+
}
958995

959-
// If this is a macro invocation with a semicolon, then apply that
960-
// semicolon to the final statement produced by expansion.
961-
if style == MacStmtStyle::Semicolon {
962-
if let Some(stmt) = placeholder.pop() {
963-
placeholder.push(stmt.add_trailing_semicolon());
996+
if let StmtKind::Mac(mac) = stmt.node {
997+
let (mac, style, attrs) = mac.into_inner();
998+
self.check_attributes(&attrs);
999+
let mut placeholder = self.collect_bang(mac, stmt.span, ExpansionKind::Stmts)
1000+
.make_stmts();
1001+
1002+
// If this is a macro invocation with a semicolon, then apply that
1003+
// semicolon to the final statement produced by expansion.
1004+
if style == MacStmtStyle::Semicolon {
1005+
if let Some(stmt) = placeholder.pop() {
1006+
placeholder.push(stmt.add_trailing_semicolon());
1007+
}
9641008
}
1009+
1010+
return placeholder;
9651011
}
9661012

967-
placeholder
1013+
// The placeholder expander gives ids to statements, so we avoid folding the id here.
1014+
let ast::Stmt { id, node, span } = stmt;
1015+
noop_fold_stmt_kind(node, self).into_iter().map(|node| {
1016+
ast::Stmt { id, node, span }
1017+
}).collect()
1018+
9681019
}
9691020

9701021
fn fold_block(&mut self, block: P<Block>) -> P<Block> {

src/libsyntax/feature_gate.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1250,7 +1250,7 @@ const EXPLAIN_BOX_SYNTAX: &'static str =
12501250
"box expression syntax is experimental; you can call `Box::new` instead.";
12511251

12521252
pub const EXPLAIN_STMT_ATTR_SYNTAX: &'static str =
1253-
"attributes on non-item statements and expressions are experimental.";
1253+
"attributes on expressions are experimental.";
12541254

12551255
pub const EXPLAIN_ASM: &'static str =
12561256
"inline assembly is not stable enough for use and is subject to change";

src/libsyntax/parse/parser.rs

+3
Original file line numberDiff line numberDiff line change
@@ -4601,6 +4601,9 @@ impl<'a> Parser<'a> {
46014601

46024602
/// Parse a statement, including the trailing semicolon.
46034603
pub fn parse_full_stmt(&mut self, macro_legacy_warnings: bool) -> PResult<'a, Option<Stmt>> {
4604+
// skip looking for a trailing semicolon when we have an interpolated statement
4605+
maybe_whole!(self, NtStmt, |x| Some(x));
4606+
46044607
let mut stmt = match self.parse_stmt_without_recovery(macro_legacy_warnings)? {
46054608
Some(stmt) => stmt,
46064609
None => return Ok(None),

src/libsyntax_ext/deriving/custom.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,9 @@ impl MultiItemModifier for ProcMacroDerive {
5454
let item = match item {
5555
Annotatable::Item(item) => item,
5656
Annotatable::ImplItem(_) |
57-
Annotatable::TraitItem(_) => {
57+
Annotatable::TraitItem(_) |
58+
Annotatable::Stmt(_) |
59+
Annotatable::Expr(_) => {
5860
ecx.span_err(span, "proc-macro derives may only be \
5961
applied to struct/enum items");
6062
return Vec::new()

src/test/compile-fail-fulldeps/auxiliary/macro_crate_test.rs

+4
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ fn expand_into_foo_multi(cx: &mut ExtCtxt,
9393
}
9494
})
9595
}
96+
// these are covered in proc_macro/attr-stmt-expr.rs
97+
Annotatable::Stmt(_) | Annotatable::Expr(_) => panic!("expected item")
9698
}
9799
}
98100

@@ -145,6 +147,8 @@ fn expand_duplicate(cx: &mut ExtCtxt,
145147
new_it.ident = copy_name;
146148
push(Annotatable::TraitItem(P(new_it)));
147149
}
150+
// covered in proc_macro/attr-stmt-expr.rs
151+
Annotatable::Stmt(_) | Annotatable::Expr(_) => panic!("expected item")
148152
}
149153
}
150154

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
// aux-build:attr-stmt-expr.rs
12+
// ignore-stage1
13+
14+
//! Attributes producing expressions in invalid locations
15+
16+
#![feature(proc_macro, stmt_expr_attributes)]
17+
18+
extern crate attr_stmt_expr;
19+
use attr_stmt_expr::{duplicate, no_output};
20+
21+
fn main() {
22+
let _ = #[no_output] "Hello, world!";
23+
//~^ ERROR expected expression, found `<eof>`
24+
25+
let _ = #[duplicate] "Hello, world!";
26+
//~^ ERROR macro expansion ignores token `,` and any following
27+
28+
let _ = {
29+
#[no_output]
30+
"Hello, world!"
31+
};
32+
33+
let _ = {
34+
#[duplicate]
35+
//~^ ERROR macro expansion ignores token `,` and any following
36+
"Hello, world!"
37+
};
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
// aux-build:attr-stmt-expr.rs
12+
// ignore-stage1
13+
14+
#![feature(proc_macro)]
15+
16+
extern crate attr_stmt_expr;
17+
use attr_stmt_expr::{expect_let, expect_print_stmt, expect_expr, expect_print_expr};
18+
19+
fn print_str(string: &'static str) {
20+
// macros are handled a bit differently
21+
#[expect_print_expr]
22+
//~^ ERROR attributes on expressions are experimental
23+
//~| HELP add #![feature(stmt_expr_attributes)] to the crate attributes to enable
24+
println!("{}", string)
25+
}
26+
27+
fn main() {
28+
#[expect_let]
29+
let string = "Hello, world!";
30+
31+
#[expect_print_stmt]
32+
println!("{}", string);
33+
34+
#[expect_expr]
35+
//~^ ERROR attributes on expressions are experimental
36+
//~| HELP add #![feature(stmt_expr_attributes)] to the crate attributes to enable
37+
print_str("string")
38+
}

0 commit comments

Comments
 (0)