Skip to content

Implement declarative (macro_rules!) attribute macros (RFC 3697) #144579

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

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
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
2 changes: 1 addition & 1 deletion compiler/rustc_errors/src/emitter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@ pub trait Emitter {
if !redundant_span || always_backtrace {
let msg: Cow<'static, _> = match trace.kind {
ExpnKind::Macro(MacroKind::Attr, _) => {
"this procedural macro expansion".into()
"this attribute macro expansion".into()
}
ExpnKind::Macro(MacroKind::Derive, _) => {
"this derive macro expansion".into()
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_expand/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ expand_invalid_fragment_specifier =
invalid fragment specifier `{$fragment}`
.help = {$help}

expand_macro_args_bad_delim = macro attribute argument matchers require parentheses
expand_macro_args_bad_delim_sugg = the delimiters should be `(` and `)`

expand_macro_body_stability =
macros cannot have body stability attributes
.label = invalid body stability attribute
Expand Down
18 changes: 18 additions & 0 deletions compiler/rustc_expand/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -482,3 +482,21 @@ mod metavar_exprs {
pub key: MacroRulesNormalizedIdent,
}
}

#[derive(Diagnostic)]
#[diag(expand_macro_args_bad_delim)]
pub(crate) struct MacroArgsBadDelim {
#[primary_span]
pub span: Span,
#[subdiagnostic]
pub sugg: MacroArgsBadDelimSugg,
}

#[derive(Subdiagnostic)]
#[multipart_suggestion(expand_macro_args_bad_delim_sugg, applicability = "machine-applicable")]
pub(crate) struct MacroArgsBadDelimSugg {
#[suggestion_part(code = "(")]
pub open: Span,
#[suggestion_part(code = ")")]
pub close: Span,
}
50 changes: 36 additions & 14 deletions compiler/rustc_expand/src/mbe/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,40 @@ use rustc_macros::Subdiagnostic;
use rustc_parse::parser::{Parser, Recovery, token_descr};
use rustc_session::parse::ParseSess;
use rustc_span::source_map::SourceMap;
use rustc_span::{ErrorGuaranteed, Ident, Span};
use rustc_span::{DUMMY_SP, ErrorGuaranteed, Ident, Span};
use tracing::debug;

use super::macro_rules::{MacroRule, NoopTracker, parser_from_cx};
use crate::expand::{AstFragmentKind, parse_ast_fragment};
use crate::mbe::macro_parser::ParseResult::*;
use crate::mbe::macro_parser::{MatcherLoc, NamedParseResult, TtParser};
use crate::mbe::macro_rules::{Tracker, try_match_macro};
use crate::mbe::macro_rules::{Tracker, try_match_macro, try_match_macro_attr};

pub(super) fn failed_to_match_macro(
psess: &ParseSess,
sp: Span,
def_span: Span,
name: Ident,
arg: TokenStream,
attr_args: Option<&TokenStream>,
body: &TokenStream,
rules: &[MacroRule],
) -> (Span, ErrorGuaranteed) {
debug!("failed to match macro");
let def_head_span = if !def_span.is_dummy() && !psess.source_map().is_imported(def_span) {
psess.source_map().guess_head_span(def_span)
} else {
DUMMY_SP
};

// An error occurred, try the expansion again, tracking the expansion closely for better
// diagnostics.
let mut tracker = CollectTrackerAndEmitter::new(psess.dcx(), sp);

let try_success_result = try_match_macro(psess, name, &arg, rules, &mut tracker);
let try_success_result = if let Some(attr_args) = attr_args {
try_match_macro_attr(psess, name, attr_args, body, rules, &mut tracker)
} else {
try_match_macro(psess, name, body, rules, &mut tracker)
};

if try_success_result.is_ok() {
// Nonterminal parser recovery might turn failed matches into successful ones,
Expand All @@ -47,15 +58,22 @@ pub(super) fn failed_to_match_macro(

let Some(BestFailure { token, msg: label, remaining_matcher, .. }) = tracker.best_failure
else {
if attr_args.is_none() && !rules.iter().any(|rule| matches!(rule, MacroRule::Func { .. })) {
let mut err = psess.dcx().struct_span_err(sp, "invoked macro has no invocation rules");
if !def_head_span.is_dummy() {
err.span_label(def_head_span, "this macro has no rules to invoke");
}
return (sp, err.emit());
}
return (sp, psess.dcx().span_delayed_bug(sp, "failed to match a macro"));
};

let span = token.span.substitute_dummy(sp);

let mut err = psess.dcx().struct_span_err(span, parse_failure_msg(&token, None));
err.span_label(span, label);
if !def_span.is_dummy() && !psess.source_map().is_imported(def_span) {
err.span_label(psess.source_map().guess_head_span(def_span), "when calling this macro");
if !def_head_span.is_dummy() {
err.span_label(def_head_span, "when calling this macro");
}

annotate_doc_comment(&mut err, psess.source_map(), span);
Expand All @@ -79,13 +97,16 @@ pub(super) fn failed_to_match_macro(
}

// Check whether there's a missing comma in this macro call, like `println!("{}" a);`
if let Some((arg, comma_span)) = arg.add_comma() {
if attr_args.is_none()
&& let Some((body, comma_span)) = body.add_comma()
{
for rule in rules {
let parser = parser_from_cx(psess, arg.clone(), Recovery::Allowed);
let MacroRule::Func { lhs, .. } = rule else { continue };
let parser = parser_from_cx(psess, body.clone(), Recovery::Allowed);
let mut tt_parser = TtParser::new(name);

if let Success(_) =
tt_parser.parse_tt(&mut Cow::Borrowed(&parser), &rule.lhs, &mut NoopTracker)
tt_parser.parse_tt(&mut Cow::Borrowed(&parser), lhs, &mut NoopTracker)
{
if comma_span.is_dummy() {
err.note("you might be missing a comma");
Expand Down Expand Up @@ -116,13 +137,13 @@ struct CollectTrackerAndEmitter<'dcx, 'matcher> {

struct BestFailure {
token: Token,
position_in_tokenstream: u32,
position_in_tokenstream: (bool, u32),
msg: &'static str,
remaining_matcher: MatcherLoc,
}

impl BestFailure {
fn is_better_position(&self, position: u32) -> bool {
fn is_better_position(&self, position: (bool, u32)) -> bool {
position > self.position_in_tokenstream
}
}
Expand All @@ -142,7 +163,7 @@ impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'match
}
}

fn after_arm(&mut self, result: &NamedParseResult<Self::Failure>) {
fn after_arm(&mut self, in_body: bool, result: &NamedParseResult<Self::Failure>) {
match result {
Success(_) => {
// Nonterminal parser recovery might turn failed matches into successful ones,
Expand All @@ -155,14 +176,15 @@ impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'match
Failure((token, approx_position, msg)) => {
debug!(?token, ?msg, "a new failure of an arm");

let position_in_tokenstream = (in_body, *approx_position);
if self
.best_failure
.as_ref()
.is_none_or(|failure| failure.is_better_position(*approx_position))
.is_none_or(|failure| failure.is_better_position(position_in_tokenstream))
{
self.best_failure = Some(BestFailure {
token: *token,
position_in_tokenstream: *approx_position,
position_in_tokenstream,
msg,
remaining_matcher: self
.remaining_matcher
Expand Down
6 changes: 5 additions & 1 deletion compiler/rustc_expand/src/mbe/macro_check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,15 +193,19 @@ struct MacroState<'a> {
/// Arguments:
/// - `psess` is used to emit diagnostics and lints
/// - `node_id` is used to emit lints
/// - `lhs` and `rhs` represent the rule
/// - `args`, `lhs`, and `rhs` represent the rule
pub(super) fn check_meta_variables(
psess: &ParseSess,
node_id: NodeId,
args: Option<&TokenTree>,
lhs: &TokenTree,
rhs: &TokenTree,
) -> Result<(), ErrorGuaranteed> {
let mut guar = None;
let mut binders = Binders::default();
if let Some(args) = args {
check_binders(psess, node_id, args, &Stack::Empty, &mut binders, &Stack::Empty, &mut guar);
}
check_binders(psess, node_id, lhs, &Stack::Empty, &mut binders, &Stack::Empty, &mut guar);
check_occurrences(psess, node_id, rhs, &Stack::Empty, &binders, &Stack::Empty, &mut guar);
guar.map_or(Ok(()), Err)
Expand Down
Loading
Loading