diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index f13d67b9c1584..7f2b50b9456cc 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -1415,6 +1415,7 @@ pub struct MacCall { pub path: Path, pub args: P, pub prior_type_ascription: Option<(Span, bool)>, + pub postfix_self_arg: Option>, } impl MacCall { diff --git a/compiler/rustc_ast/src/mut_visit.rs b/compiler/rustc_ast/src/mut_visit.rs index fe9ad58c9ac84..438fb767ec1c5 100644 --- a/compiler/rustc_ast/src/mut_visit.rs +++ b/compiler/rustc_ast/src/mut_visit.rs @@ -599,7 +599,10 @@ pub fn noop_visit_attribute(attr: &mut Attribute, vis: &mut T) { } pub fn noop_visit_mac(mac: &mut MacCall, vis: &mut T) { - let MacCall { path, args, prior_type_ascription: _ } = mac; + let MacCall { path, args, prior_type_ascription: _, postfix_self_arg } = mac; + if let Some(postfix_self_arg) = postfix_self_arg { + vis.visit_expr(postfix_self_arg); + } vis.visit_path(path); visit_mac_args(args, vis); } diff --git a/compiler/rustc_ast_pretty/src/pprust/state.rs b/compiler/rustc_ast_pretty/src/pprust/state.rs index 9fcba90244330..2682e47bb5b46 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state.rs @@ -1642,6 +1642,10 @@ impl<'a> State<'a> { } crate fn print_mac(&mut self, m: &ast::MacCall) { + if let Some(arg) = &m.postfix_self_arg { + self.print_expr(&*arg); + self.s.word("."); + } self.print_mac_common( Some(MacHeader::Path(&m.path)), true, diff --git a/compiler/rustc_builtin_macros/src/assert.rs b/compiler/rustc_builtin_macros/src/assert.rs index 5bfd8a2bf561c..9ac8fb5aceee3 100644 --- a/compiler/rustc_builtin_macros/src/assert.rs +++ b/compiler/rustc_builtin_macros/src/assert.rs @@ -44,6 +44,7 @@ pub fn expand_assert<'cx>( path: Path::from_ident(Ident::new(sym::panic, sp)), args, prior_type_ascription: None, + postfix_self_arg: None, }; let if_expr = cx.expr_if( sp, diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index 3e5762ab992f4..143020511ac58 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -689,6 +689,12 @@ impl<'a, 'b> MacroExpander<'a, 'b> { return ExpandResult::Ready(invoc.fragment_kind.dummy(invoc.span())); } + // Sanity check: ensure that no postfix macro slips through + // and accidentially gets expanded. + if let InvocationKind::Bang { mac, .. } = &invoc.kind { + mac.postfix_self_arg.as_ref().expect_none("postfix macro survived until expansion"); + } + let (fragment_kind, span) = (invoc.fragment_kind, invoc.span()); ExpandResult::Ready(match invoc.kind { InvocationKind::Bang { mac, .. } => match ext { @@ -1146,6 +1152,26 @@ impl<'a, 'b> InvocationCollector<'a, 'b> { } } } + + // Postfix macro calls can be parsed to allow proc macros to support the syntax, + // but they may not be expanded. + fn check_postfix_mac_call( + &mut self, + call: &mut ast::MacCall, + span: Span, + ) -> Option> { + let postfix_self_arg = call.postfix_self_arg.take(); + if postfix_self_arg.is_some() { + let mut err = self.cx.struct_span_err( + span, + &format!("forbidden postfix macro call `{}`", pprust::path_to_string(&call.path)), + ); + err.span_label(call.path.span, "macros can't be called in postfix position"); + + err.emit(); + } + postfix_self_arg + } } impl<'a, 'b> MutVisitor for InvocationCollector<'a, 'b> { @@ -1175,8 +1201,13 @@ impl<'a, 'b> MutVisitor for InvocationCollector<'a, 'b> { .into_inner(); } - if let ast::ExprKind::MacCall(mac) = expr.kind { + if let ast::ExprKind::MacCall(mut mac) = expr.kind { self.check_attributes(&expr.attrs); + if let Some(postfix_self_arg) = self.check_postfix_mac_call(&mut mac, expr.span) { + let mut self_arg = postfix_self_arg.into_inner(); + ensure_sufficient_stack(|| noop_visit_expr(&mut self_arg, self)); + return self_arg; + } self.collect_bang(mac, expr.span, AstFragmentKind::Expr).make_expr().into_inner() } else { ensure_sufficient_stack(|| noop_visit_expr(&mut expr, self)); @@ -1322,8 +1353,14 @@ impl<'a, 'b> MutVisitor for InvocationCollector<'a, 'b> { .map(|expr| expr.into_inner()); } - if let ast::ExprKind::MacCall(mac) = expr.kind { + if let ast::ExprKind::MacCall(mut mac) = expr.kind { self.check_attributes(&expr.attrs); + + if let Some(postfix_self_arg) = self.check_postfix_mac_call(&mut mac, expr.span) { + let mut self_arg = postfix_self_arg.into_inner(); + noop_visit_expr(&mut self_arg, self); + return Some(self_arg); + } self.collect_bang(mac, expr.span, AstFragmentKind::OptExpr) .make_opt_expr() .map(|expr| expr.into_inner()) diff --git a/compiler/rustc_expand/src/lib.rs b/compiler/rustc_expand/src/lib.rs index 47247294f5dc6..9b093e3de076d 100644 --- a/compiler/rustc_expand/src/lib.rs +++ b/compiler/rustc_expand/src/lib.rs @@ -5,6 +5,7 @@ #![feature(proc_macro_diagnostic)] #![feature(proc_macro_internals)] #![feature(proc_macro_span)] +#![feature(option_expect_none)] #![feature(try_blocks)] #[macro_use] diff --git a/compiler/rustc_expand/src/placeholders.rs b/compiler/rustc_expand/src/placeholders.rs index 0cffca1727124..0b7f0dc614eed 100644 --- a/compiler/rustc_expand/src/placeholders.rs +++ b/compiler/rustc_expand/src/placeholders.rs @@ -21,6 +21,7 @@ pub fn placeholder( path: ast::Path { span: DUMMY_SP, segments: Vec::new(), tokens: None }, args: P(ast::MacArgs::Empty), prior_type_ascription: None, + postfix_self_arg: None, } } diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index c2a13d4b0dec1..6c574d3ccf22d 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -991,6 +991,21 @@ impl<'a> Parser<'a> { let fn_span = fn_span_lo.to(self.prev_token.span); let span = lo.to(self.prev_token.span); Ok(self.mk_expr(span, ExprKind::MethodCall(segment, args, fn_span), AttrVec::new())) + } else if self.eat(&token::Not) { + // Postfix macro call + let path = ast::Path { + segments: vec![segment], + span: fn_span_lo.to(self.prev_token.span), + tokens: None, + }; + let mac = MacCall { + path, + args: self.parse_mac_args()?, + prior_type_ascription: self.last_type_ascription, + postfix_self_arg: Some(self_arg), + }; + let span = lo.to(self.prev_token.span); + Ok(self.mk_expr(span, ExprKind::MacCall(mac), AttrVec::new())) } else { // Field access `expr.f` if let Some(args) = segment.args { @@ -1215,6 +1230,7 @@ impl<'a> Parser<'a> { path, args: self.parse_mac_args()?, prior_type_ascription: self.last_type_ascription, + postfix_self_arg: None, }; (self.prev_token.span, ExprKind::MacCall(mac)) } else if self.check(&token::OpenDelim(token::Brace)) { diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index 5954b370e6d98..adb75f4068745 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -410,7 +410,12 @@ impl<'a> Parser<'a> { let args = self.parse_mac_args()?; // `( .. )` or `[ .. ]` (followed by `;`), or `{ .. }`. self.eat_semi_for_macro_if_needed(&args); self.complain_if_pub_macro(vis, false); - Ok(MacCall { path, args, prior_type_ascription: self.last_type_ascription }) + Ok(MacCall { + path, + args, + prior_type_ascription: self.last_type_ascription, + postfix_self_arg: None, + }) } /// Recover if we parsed attributes and expected an item but there was none. diff --git a/compiler/rustc_parse/src/parser/pat.rs b/compiler/rustc_parse/src/parser/pat.rs index 196790a0ab323..746fa062e8b0a 100644 --- a/compiler/rustc_parse/src/parser/pat.rs +++ b/compiler/rustc_parse/src/parser/pat.rs @@ -627,7 +627,12 @@ impl<'a> Parser<'a> { fn parse_pat_mac_invoc(&mut self, path: Path) -> PResult<'a, PatKind> { self.bump(); let args = self.parse_mac_args()?; - let mac = MacCall { path, args, prior_type_ascription: self.last_type_ascription }; + let mac = MacCall { + path, + args, + prior_type_ascription: self.last_type_ascription, + postfix_self_arg: None, + }; Ok(PatKind::MacCall(mac)) } diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs index 131ff1ae6b3da..ed58a3134c807 100644 --- a/compiler/rustc_parse/src/parser/stmt.rs +++ b/compiler/rustc_parse/src/parser/stmt.rs @@ -103,7 +103,12 @@ impl<'a> Parser<'a> { let style = if delim == token::Brace { MacStmtStyle::Braces } else { MacStmtStyle::NoBraces }; - let mac = MacCall { path, args, prior_type_ascription: self.last_type_ascription }; + let mac = MacCall { + path, + args, + prior_type_ascription: self.last_type_ascription, + postfix_self_arg: None, + }; let kind = if delim == token::Brace || self.token == token::Semi || self.token == token::Eof { diff --git a/compiler/rustc_parse/src/parser/ty.rs b/compiler/rustc_parse/src/parser/ty.rs index 7a6ebca4e1541..fc50fdc270dac 100644 --- a/compiler/rustc_parse/src/parser/ty.rs +++ b/compiler/rustc_parse/src/parser/ty.rs @@ -407,6 +407,7 @@ impl<'a> Parser<'a> { path, args: self.parse_mac_args()?, prior_type_ascription: self.last_type_ascription, + postfix_self_arg: None, })) } else if allow_plus == AllowPlus::Yes && self.check_plus() { // `Trait1 + Trait2 + 'a` diff --git a/src/test/ui/parser/postfix-macros-pass.rs b/src/test/ui/parser/postfix-macros-pass.rs new file mode 100644 index 0000000000000..794fb8df18faf --- /dev/null +++ b/src/test/ui/parser/postfix-macros-pass.rs @@ -0,0 +1,25 @@ +// check-pass + +// Basically a clone of postfix-macros.rs, but with the offending +// code behind a `#[cfg(FALSE)]`. Rust still parses this code, +// but doesn't do anything beyond with it. + +fn main() {} + +#[cfg(FALSE)] +fn foo() { + "Hello, world!".to_string().println!(); + + "Hello, world!".println!(); + + false.assert!(); + + Some(42).assert_eq!(None); + + std::iter::once(42) + .map(|v| v + 3) + .dbg!() + .max() + .unwrap() + .dbg!(); +} diff --git a/src/test/ui/parser/postfix-macros.rs b/src/test/ui/parser/postfix-macros.rs new file mode 100644 index 0000000000000..5aa92bdd2b749 --- /dev/null +++ b/src/test/ui/parser/postfix-macros.rs @@ -0,0 +1,17 @@ +fn main() { + "Hello, world!".to_string().println!(); //~ ERROR forbidden postfix macro + + "Hello, world!".println!(); //~ ERROR forbidden postfix macro + + false.assert!(); //~ ERROR forbidden postfix macro + + Some(42).assert_eq!(None); //~ ERROR forbidden postfix macro + + std::iter::once(42) //~ ERROR forbidden postfix macro + //~^ ERROR forbidden postfix macro + .map(|v| v + 3) + .dbg!() + .max() + .unwrap() + .dbg!(); +} diff --git a/src/test/ui/parser/postfix-macros.stderr b/src/test/ui/parser/postfix-macros.stderr new file mode 100644 index 0000000000000..56c5ee4d1e93e --- /dev/null +++ b/src/test/ui/parser/postfix-macros.stderr @@ -0,0 +1,59 @@ +error: forbidden postfix macro call `println` + --> $DIR/postfix-macros.rs:2:5 + | +LL | "Hello, world!".to_string().println!(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^--------^^ + | | + | macros can't be called in postfix position + +error: forbidden postfix macro call `println` + --> $DIR/postfix-macros.rs:4:5 + | +LL | "Hello, world!".println!(); + | ^^^^^^^^^^^^^^^^--------^^ + | | + | macros can't be called in postfix position + +error: forbidden postfix macro call `assert` + --> $DIR/postfix-macros.rs:6:5 + | +LL | false.assert!(); + | ^^^^^^-------^^ + | | + | macros can't be called in postfix position + +error: forbidden postfix macro call `assert_eq` + --> $DIR/postfix-macros.rs:8:5 + | +LL | Some(42).assert_eq!(None); + | ^^^^^^^^^----------^^^^^^ + | | + | macros can't be called in postfix position + +error: forbidden postfix macro call `dbg` + --> $DIR/postfix-macros.rs:10:5 + | +LL | / std::iter::once(42) +LL | | +LL | | .map(|v| v + 3) +LL | | .dbg!() +LL | | .max() +LL | | .unwrap() +LL | | .dbg!(); + | |__________----_^ + | | + | macros can't be called in postfix position + +error: forbidden postfix macro call `dbg` + --> $DIR/postfix-macros.rs:10:5 + | +LL | / std::iter::once(42) +LL | | +LL | | .map(|v| v + 3) +LL | | .dbg!() + | |__________----_^ + | | + | macros can't be called in postfix position + +error: aborting due to 6 previous errors + diff --git a/src/test/ui/proc-macro/auxiliary/demacroify.rs b/src/test/ui/proc-macro/auxiliary/demacroify.rs new file mode 100644 index 0000000000000..c0ac07602f90a --- /dev/null +++ b/src/test/ui/proc-macro/auxiliary/demacroify.rs @@ -0,0 +1,74 @@ +// force-host +// no-prefer-dynamic + +// An attr proc macro that removes all postfix macros, +// to test that parsing postfix macros is allowed. + +#![crate_type = "proc-macro"] + +extern crate proc_macro; + +use proc_macro::{Delimiter, Group, Punct, Spacing, TokenStream, TokenTree as Tt}; + +#[proc_macro_attribute] +pub fn demacroify(_attrs: TokenStream, input: TokenStream) -> TokenStream { + let mut vis = Visitor; + let res = vis.visit_stream(input); + res +} + +struct Visitor; + +impl Visitor { + fn visit_stream(&mut self, stream: TokenStream) -> TokenStream { + let mut res = Vec::new(); + let mut stream_iter = stream.into_iter(); + while let Some(tt) = stream_iter.next() { + match tt { + Tt::Group(group) => { + let mut postfix_macro = false; + { + let last_three = res.rchunks(3).next(); + if let Some(&[Tt::Punct(ref p1), Tt::Ident(_), Tt::Punct(ref p2)]) = + last_three + { + if (p1.as_char(), p1.spacing(), p2.as_char(), p2.spacing()) + == ('.', Spacing::Alone, '!', Spacing::Alone) + { + postfix_macro = true; + } + } + } + if postfix_macro { + // Remove the ! and macro ident + let _mac_bang = res.pop().unwrap(); + let _mac = res.pop().unwrap(); + // Remove the . before the macro + let _dot = res.pop().unwrap(); + } else { + let tt = Tt::Group(self.visit_group(group)); + res.push(tt); + } + } + Tt::Ident(id) => { + res.push(Tt::Ident(id)); + } + Tt::Punct(p) => { + res.push(Tt::Punct(p)); + } + Tt::Literal(lit) => { + res.push(Tt::Literal(lit)); + } + } + } + res.into_iter().collect() + } + fn visit_group(&mut self, group: Group) -> Group { + let delim = group.delimiter(); + let span = group.span(); + let stream = self.visit_stream(group.stream()); + let mut gr = Group::new(delim, stream); + gr.set_span(span); + gr + } +} diff --git a/src/test/ui/proc-macro/postfix-macros.rs b/src/test/ui/proc-macro/postfix-macros.rs new file mode 100644 index 0000000000000..ab1756466c447 --- /dev/null +++ b/src/test/ui/proc-macro/postfix-macros.rs @@ -0,0 +1,22 @@ +// run-pass +// aux-build:demacroify.rs + +extern crate demacroify; + +#[demacroify::demacroify] +fn main() { + "Hello, world!".to_string().println!(); + + "Hello, world!".println!(); + + false.assert!(); + + Some(42).assert_eq!(None); + + std::iter::once(42) + .map(|v| v + 3) + .dbg!() + .max() + .unwrap() + .dbg!(); +}