Skip to content

Commit 9db50ff

Browse files
committed
Parse postfix macro calls but still forbid their expansion
This improves error messages and enables proc macros to work with the postfix macro syntax.
1 parent f7801d6 commit 9db50ff

File tree

16 files changed

+276
-6
lines changed

16 files changed

+276
-6
lines changed

compiler/rustc_ast/src/ast.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1415,6 +1415,7 @@ pub struct MacCall {
14151415
pub path: Path,
14161416
pub args: P<MacArgs>,
14171417
pub prior_type_ascription: Option<(Span, bool)>,
1418+
pub postfix_self_arg: Option<P<Expr>>,
14181419
}
14191420

14201421
impl MacCall {

compiler/rustc_ast/src/mut_visit.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -599,7 +599,10 @@ pub fn noop_visit_attribute<T: MutVisitor>(attr: &mut Attribute, vis: &mut T) {
599599
}
600600

601601
pub fn noop_visit_mac<T: MutVisitor>(mac: &mut MacCall, vis: &mut T) {
602-
let MacCall { path, args, prior_type_ascription: _ } = mac;
602+
let MacCall { path, args, prior_type_ascription: _, postfix_self_arg } = mac;
603+
if let Some(postfix_self_arg) = postfix_self_arg {
604+
vis.visit_expr(postfix_self_arg);
605+
}
603606
vis.visit_path(path);
604607
visit_mac_args(args, vis);
605608
}

compiler/rustc_ast_pretty/src/pprust/state.rs

+4
Original file line numberDiff line numberDiff line change
@@ -1642,6 +1642,10 @@ impl<'a> State<'a> {
16421642
}
16431643

16441644
crate fn print_mac(&mut self, m: &ast::MacCall) {
1645+
if let Some(arg) = &m.postfix_self_arg {
1646+
self.print_expr(&*arg);
1647+
self.s.word(".");
1648+
}
16451649
self.print_mac_common(
16461650
Some(MacHeader::Path(&m.path)),
16471651
true,

compiler/rustc_builtin_macros/src/assert.rs

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ pub fn expand_assert<'cx>(
4444
path: Path::from_ident(Ident::new(sym::panic, sp)),
4545
args,
4646
prior_type_ascription: None,
47+
postfix_self_arg: None,
4748
};
4849
let if_expr = cx.expr_if(
4950
sp,

compiler/rustc_expand/src/expand.rs

+33-2
Original file line numberDiff line numberDiff line change
@@ -1146,6 +1146,26 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
11461146
}
11471147
}
11481148
}
1149+
1150+
// Postfix macro calls can be parsed to allow proc macros to support the syntax,
1151+
// but they may not be expanded.
1152+
fn check_postfix_mac_call(
1153+
&mut self,
1154+
call: &mut ast::MacCall,
1155+
span: Span,
1156+
) -> Option<P<ast::Expr>> {
1157+
let postfix_self_arg = call.postfix_self_arg.take();
1158+
if postfix_self_arg.is_some() {
1159+
let mut err = self.cx.struct_span_err(
1160+
span,
1161+
&format!("forbidden postfix macro call `{}`", pprust::path_to_string(&call.path)),
1162+
);
1163+
err.span_label(call.path.span, "macros can't be called in postfix position");
1164+
1165+
err.emit();
1166+
}
1167+
postfix_self_arg
1168+
}
11491169
}
11501170

11511171
impl<'a, 'b> MutVisitor for InvocationCollector<'a, 'b> {
@@ -1175,8 +1195,13 @@ impl<'a, 'b> MutVisitor for InvocationCollector<'a, 'b> {
11751195
.into_inner();
11761196
}
11771197

1178-
if let ast::ExprKind::MacCall(mac) = expr.kind {
1198+
if let ast::ExprKind::MacCall(mut mac) = expr.kind {
11791199
self.check_attributes(&expr.attrs);
1200+
if let Some(postfix_self_arg) = self.check_postfix_mac_call(&mut mac, expr.span) {
1201+
let mut self_arg = postfix_self_arg.into_inner();
1202+
ensure_sufficient_stack(|| noop_visit_expr(&mut self_arg, self));
1203+
return self_arg;
1204+
}
11801205
self.collect_bang(mac, expr.span, AstFragmentKind::Expr).make_expr().into_inner()
11811206
} else {
11821207
ensure_sufficient_stack(|| noop_visit_expr(&mut expr, self));
@@ -1322,8 +1347,14 @@ impl<'a, 'b> MutVisitor for InvocationCollector<'a, 'b> {
13221347
.map(|expr| expr.into_inner());
13231348
}
13241349

1325-
if let ast::ExprKind::MacCall(mac) = expr.kind {
1350+
if let ast::ExprKind::MacCall(mut mac) = expr.kind {
13261351
self.check_attributes(&expr.attrs);
1352+
1353+
if let Some(postfix_self_arg) = self.check_postfix_mac_call(&mut mac, expr.span) {
1354+
let mut self_arg = postfix_self_arg.into_inner();
1355+
noop_visit_expr(&mut self_arg, self);
1356+
return Some(self_arg);
1357+
}
13271358
self.collect_bang(mac, expr.span, AstFragmentKind::OptExpr)
13281359
.make_opt_expr()
13291360
.map(|expr| expr.into_inner())

compiler/rustc_expand/src/placeholders.rs

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ pub fn placeholder(
2121
path: ast::Path { span: DUMMY_SP, segments: Vec::new(), tokens: None },
2222
args: P(ast::MacArgs::Empty),
2323
prior_type_ascription: None,
24+
postfix_self_arg: None,
2425
}
2526
}
2627

compiler/rustc_parse/src/parser/expr.rs

+16
Original file line numberDiff line numberDiff line change
@@ -991,6 +991,21 @@ impl<'a> Parser<'a> {
991991
let fn_span = fn_span_lo.to(self.prev_token.span);
992992
let span = lo.to(self.prev_token.span);
993993
Ok(self.mk_expr(span, ExprKind::MethodCall(segment, args, fn_span), AttrVec::new()))
994+
} else if self.eat(&token::Not) {
995+
// Postfix macro call
996+
let path = ast::Path {
997+
segments: vec![segment],
998+
span: fn_span_lo.to(self.prev_token.span),
999+
tokens: None,
1000+
};
1001+
let mac = MacCall {
1002+
path,
1003+
args: self.parse_mac_args()?,
1004+
prior_type_ascription: self.last_type_ascription,
1005+
postfix_self_arg: Some(self_arg),
1006+
};
1007+
let span = lo.to(self.prev_token.span);
1008+
Ok(self.mk_expr(span, ExprKind::MacCall(mac), AttrVec::new()))
9941009
} else {
9951010
// Field access `expr.f`
9961011
if let Some(args) = segment.args {
@@ -1215,6 +1230,7 @@ impl<'a> Parser<'a> {
12151230
path,
12161231
args: self.parse_mac_args()?,
12171232
prior_type_ascription: self.last_type_ascription,
1233+
postfix_self_arg: None,
12181234
};
12191235
(self.prev_token.span, ExprKind::MacCall(mac))
12201236
} else if self.check(&token::OpenDelim(token::Brace)) {

compiler/rustc_parse/src/parser/item.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -410,7 +410,12 @@ impl<'a> Parser<'a> {
410410
let args = self.parse_mac_args()?; // `( .. )` or `[ .. ]` (followed by `;`), or `{ .. }`.
411411
self.eat_semi_for_macro_if_needed(&args);
412412
self.complain_if_pub_macro(vis, false);
413-
Ok(MacCall { path, args, prior_type_ascription: self.last_type_ascription })
413+
Ok(MacCall {
414+
path,
415+
args,
416+
prior_type_ascription: self.last_type_ascription,
417+
postfix_self_arg: None,
418+
})
414419
}
415420

416421
/// Recover if we parsed attributes and expected an item but there was none.

compiler/rustc_parse/src/parser/pat.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -627,7 +627,12 @@ impl<'a> Parser<'a> {
627627
fn parse_pat_mac_invoc(&mut self, path: Path) -> PResult<'a, PatKind> {
628628
self.bump();
629629
let args = self.parse_mac_args()?;
630-
let mac = MacCall { path, args, prior_type_ascription: self.last_type_ascription };
630+
let mac = MacCall {
631+
path,
632+
args,
633+
prior_type_ascription: self.last_type_ascription,
634+
postfix_self_arg: None,
635+
};
631636
Ok(PatKind::MacCall(mac))
632637
}
633638

compiler/rustc_parse/src/parser/stmt.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,12 @@ impl<'a> Parser<'a> {
103103
let style =
104104
if delim == token::Brace { MacStmtStyle::Braces } else { MacStmtStyle::NoBraces };
105105

106-
let mac = MacCall { path, args, prior_type_ascription: self.last_type_ascription };
106+
let mac = MacCall {
107+
path,
108+
args,
109+
prior_type_ascription: self.last_type_ascription,
110+
postfix_self_arg: None,
111+
};
107112

108113
let kind = if delim == token::Brace || self.token == token::Semi || self.token == token::Eof
109114
{

compiler/rustc_parse/src/parser/ty.rs

+1
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,7 @@ impl<'a> Parser<'a> {
407407
path,
408408
args: self.parse_mac_args()?,
409409
prior_type_ascription: self.last_type_ascription,
410+
postfix_self_arg: None,
410411
}))
411412
} else if allow_plus == AllowPlus::Yes && self.check_plus() {
412413
// `Trait1 + Trait2 + 'a`
+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// check-pass
2+
3+
// Basically a clone of postfix-macros.rs, but with the offending
4+
// code behind a `#[cfg(FALSE)]`. Rust still parses this code,
5+
// but doesn't do anything beyond with it.
6+
7+
fn main() {}
8+
9+
#[cfg(FALSE)]
10+
fn foo() {
11+
"Hello, world!".to_string().println!();
12+
13+
"Hello, world!".println!();
14+
15+
false.assert!();
16+
17+
Some(42).assert_eq!(None);
18+
19+
std::iter::once(42)
20+
.map(|v| v + 3)
21+
.dbg!()
22+
.max()
23+
.unwrap()
24+
.dbg!();
25+
}

src/test/ui/parser/postfix-macros.rs

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
fn main() {
2+
"Hello, world!".to_string().println!(); //~ ERROR forbidden postfix macro
3+
4+
"Hello, world!".println!(); //~ ERROR forbidden postfix macro
5+
6+
false.assert!(); //~ ERROR forbidden postfix macro
7+
8+
Some(42).assert_eq!(None); //~ ERROR forbidden postfix macro
9+
10+
std::iter::once(42) //~ ERROR forbidden postfix macro
11+
//~^ ERROR forbidden postfix macro
12+
.map(|v| v + 3)
13+
.dbg!()
14+
.max()
15+
.unwrap()
16+
.dbg!();
17+
}
+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
error: forbidden postfix macro call `println`
2+
--> $DIR/postfix-macros.rs:2:5
3+
|
4+
LL | "Hello, world!".to_string().println!();
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^--------^^
6+
| |
7+
| macros can't be called in postfix position
8+
9+
error: forbidden postfix macro call `println`
10+
--> $DIR/postfix-macros.rs:4:5
11+
|
12+
LL | "Hello, world!".println!();
13+
| ^^^^^^^^^^^^^^^^--------^^
14+
| |
15+
| macros can't be called in postfix position
16+
17+
error: forbidden postfix macro call `assert`
18+
--> $DIR/postfix-macros.rs:6:5
19+
|
20+
LL | false.assert!();
21+
| ^^^^^^-------^^
22+
| |
23+
| macros can't be called in postfix position
24+
25+
error: forbidden postfix macro call `assert_eq`
26+
--> $DIR/postfix-macros.rs:8:5
27+
|
28+
LL | Some(42).assert_eq!(None);
29+
| ^^^^^^^^^----------^^^^^^
30+
| |
31+
| macros can't be called in postfix position
32+
33+
error: forbidden postfix macro call `dbg`
34+
--> $DIR/postfix-macros.rs:10:5
35+
|
36+
LL | / std::iter::once(42)
37+
LL | |
38+
LL | | .map(|v| v + 3)
39+
LL | | .dbg!()
40+
LL | | .max()
41+
LL | | .unwrap()
42+
LL | | .dbg!();
43+
| |__________----_^
44+
| |
45+
| macros can't be called in postfix position
46+
47+
error: forbidden postfix macro call `dbg`
48+
--> $DIR/postfix-macros.rs:10:5
49+
|
50+
LL | / std::iter::once(42)
51+
LL | |
52+
LL | | .map(|v| v + 3)
53+
LL | | .dbg!()
54+
| |__________----_^
55+
| |
56+
| macros can't be called in postfix position
57+
58+
error: aborting due to 6 previous errors
59+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// force-host
2+
// no-prefer-dynamic
3+
4+
// An attr proc macro that removes all postfix macros,
5+
// to test that parsing postfix macros is allowed.
6+
7+
#![crate_type = "proc-macro"]
8+
9+
extern crate proc_macro;
10+
11+
use proc_macro::{Delimiter, Group, Punct, Spacing, TokenStream, TokenTree as Tt};
12+
13+
#[proc_macro_attribute]
14+
pub fn demacroify(_attrs: TokenStream, input: TokenStream) -> TokenStream {
15+
let mut vis = Visitor;
16+
let res = vis.visit_stream(input);
17+
res
18+
}
19+
20+
struct Visitor;
21+
22+
impl Visitor {
23+
fn visit_stream(&mut self, stream: TokenStream) -> TokenStream {
24+
let mut res = Vec::new();
25+
let mut stream_iter = stream.into_iter();
26+
while let Some(tt) = stream_iter.next() {
27+
match tt {
28+
Tt::Group(group) => {
29+
let mut postfix_macro = false;
30+
{
31+
let last_three = res.rchunks(3).next();
32+
if let Some(&[Tt::Punct(ref p1), Tt::Ident(_), Tt::Punct(ref p2)]) =
33+
last_three
34+
{
35+
if (p1.as_char(), p1.spacing(), p2.as_char(), p2.spacing())
36+
== ('.', Spacing::Alone, '!', Spacing::Alone)
37+
{
38+
postfix_macro = true;
39+
}
40+
}
41+
}
42+
if postfix_macro {
43+
// Remove the ! and macro ident
44+
let _mac_bang = res.pop().unwrap();
45+
let _mac = res.pop().unwrap();
46+
// Remove the . before the macro
47+
let _dot = res.pop().unwrap();
48+
} else {
49+
let tt = Tt::Group(self.visit_group(group));
50+
res.push(tt);
51+
}
52+
}
53+
Tt::Ident(id) => {
54+
res.push(Tt::Ident(id));
55+
}
56+
Tt::Punct(p) => {
57+
res.push(Tt::Punct(p));
58+
}
59+
Tt::Literal(lit) => {
60+
res.push(Tt::Literal(lit));
61+
}
62+
}
63+
}
64+
res.into_iter().collect()
65+
}
66+
fn visit_group(&mut self, group: Group) -> Group {
67+
let delim = group.delimiter();
68+
let span = group.span();
69+
let stream = self.visit_stream(group.stream());
70+
let mut gr = Group::new(delim, stream);
71+
gr.set_span(span);
72+
gr
73+
}
74+
}
+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// run-pass
2+
// aux-build:demacroify.rs
3+
4+
extern crate demacroify;
5+
6+
#[demacroify::demacroify]
7+
fn main() {
8+
"Hello, world!".to_string().println!();
9+
10+
"Hello, world!".println!();
11+
12+
false.assert!();
13+
14+
Some(42).assert_eq!(None);
15+
16+
std::iter::once(42)
17+
.map(|v| v + 3)
18+
.dbg!()
19+
.max()
20+
.unwrap()
21+
.dbg!();
22+
}

0 commit comments

Comments
 (0)