Skip to content

Syntactically permit postfix macros to reject them later #78849

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

Closed
wants to merge 2 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions compiler/rustc_ast/src/ast.rs
Original file line number Diff line number Diff line change
@@ -1415,6 +1415,7 @@ pub struct MacCall {
pub path: Path,
pub args: P<MacArgs>,
pub prior_type_ascription: Option<(Span, bool)>,
pub postfix_self_arg: Option<P<Expr>>,
}

impl MacCall {
5 changes: 4 additions & 1 deletion compiler/rustc_ast/src/mut_visit.rs
Original file line number Diff line number Diff line change
@@ -599,7 +599,10 @@ pub fn noop_visit_attribute<T: MutVisitor>(attr: &mut Attribute, vis: &mut T) {
}

pub fn noop_visit_mac<T: MutVisitor>(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);
}
4 changes: 4 additions & 0 deletions compiler/rustc_ast_pretty/src/pprust/state.rs
Original file line number Diff line number Diff line change
@@ -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,
1 change: 1 addition & 0 deletions compiler/rustc_builtin_macros/src/assert.rs
Original file line number Diff line number Diff line change
@@ -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,
41 changes: 39 additions & 2 deletions compiler/rustc_expand/src/expand.rs
Original file line number Diff line number Diff line change
@@ -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<P<ast::Expr>> {
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())
1 change: 1 addition & 0 deletions compiler/rustc_expand/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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]
1 change: 1 addition & 0 deletions compiler/rustc_expand/src/placeholders.rs
Original file line number Diff line number Diff line change
@@ -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,
}
}

16 changes: 16 additions & 0 deletions compiler/rustc_parse/src/parser/expr.rs
Original file line number Diff line number Diff line change
@@ -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)) {
7 changes: 6 additions & 1 deletion compiler/rustc_parse/src/parser/item.rs
Original file line number Diff line number Diff line change
@@ -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.
7 changes: 6 additions & 1 deletion compiler/rustc_parse/src/parser/pat.rs
Original file line number Diff line number Diff line change
@@ -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))
}

7 changes: 6 additions & 1 deletion compiler/rustc_parse/src/parser/stmt.rs
Original file line number Diff line number Diff line change
@@ -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
{
1 change: 1 addition & 0 deletions compiler/rustc_parse/src/parser/ty.rs
Original file line number Diff line number Diff line change
@@ -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`
25 changes: 25 additions & 0 deletions src/test/ui/parser/postfix-macros-pass.rs
Original file line number Diff line number Diff line change
@@ -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!();
}
17 changes: 17 additions & 0 deletions src/test/ui/parser/postfix-macros.rs
Original file line number Diff line number Diff line change
@@ -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!();
}
59 changes: 59 additions & 0 deletions src/test/ui/parser/postfix-macros.stderr
Original file line number Diff line number Diff line change
@@ -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

74 changes: 74 additions & 0 deletions src/test/ui/proc-macro/auxiliary/demacroify.rs
Original file line number Diff line number Diff line change
@@ -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
}
}
Loading