| 
 | 1 | +use std::borrow::Cow;  | 
 | 2 | + | 
 | 3 | +use crate::base::{DummyResult, ExtCtxt, MacResult};  | 
 | 4 | +use crate::expand::{parse_ast_fragment, AstFragmentKind};  | 
 | 5 | +use crate::mbe::{  | 
 | 6 | +    macro_parser::{MatcherLoc, NamedParseResult, ParseResult::*, TtParser},  | 
 | 7 | +    macro_rules::{try_match_macro, Tracker},  | 
 | 8 | +};  | 
 | 9 | +use rustc_ast::token::{self, Token};  | 
 | 10 | +use rustc_ast::tokenstream::TokenStream;  | 
 | 11 | +use rustc_ast_pretty::pprust;  | 
 | 12 | +use rustc_errors::{Applicability, Diagnostic, DiagnosticBuilder, DiagnosticMessage};  | 
 | 13 | +use rustc_parse::parser::{Parser, Recovery};  | 
 | 14 | +use rustc_span::source_map::SourceMap;  | 
 | 15 | +use rustc_span::symbol::Ident;  | 
 | 16 | +use rustc_span::Span;  | 
 | 17 | + | 
 | 18 | +use super::macro_rules::{parser_from_cx, NoopTracker};  | 
 | 19 | + | 
 | 20 | +pub(super) fn failed_to_match_macro<'cx>(  | 
 | 21 | +    cx: &'cx mut ExtCtxt<'_>,  | 
 | 22 | +    sp: Span,  | 
 | 23 | +    def_span: Span,  | 
 | 24 | +    name: Ident,  | 
 | 25 | +    arg: TokenStream,  | 
 | 26 | +    lhses: &[Vec<MatcherLoc>],  | 
 | 27 | +) -> Box<dyn MacResult + 'cx> {  | 
 | 28 | +    let sess = &cx.sess.parse_sess;  | 
 | 29 | + | 
 | 30 | +    // An error occurred, try the expansion again, tracking the expansion closely for better diagnostics.  | 
 | 31 | +    let mut tracker = CollectTrackerAndEmitter::new(cx, sp);  | 
 | 32 | + | 
 | 33 | +    let try_success_result = try_match_macro(sess, name, &arg, lhses, &mut tracker);  | 
 | 34 | + | 
 | 35 | +    if try_success_result.is_ok() {  | 
 | 36 | +        // Nonterminal parser recovery might turn failed matches into successful ones,  | 
 | 37 | +        // but for that it must have emitted an error already  | 
 | 38 | +        tracker.cx.sess.delay_span_bug(sp, "Macro matching returned a success on the second try");  | 
 | 39 | +    }  | 
 | 40 | + | 
 | 41 | +    if let Some(result) = tracker.result {  | 
 | 42 | +        // An irrecoverable error occurred and has been emitted.  | 
 | 43 | +        return result;  | 
 | 44 | +    }  | 
 | 45 | + | 
 | 46 | +    let Some((token, label, remaining_matcher)) = tracker.best_failure else {  | 
 | 47 | +        return DummyResult::any(sp);  | 
 | 48 | +    };  | 
 | 49 | + | 
 | 50 | +    let span = token.span.substitute_dummy(sp);  | 
 | 51 | + | 
 | 52 | +    let mut err = cx.struct_span_err(span, &parse_failure_msg(&token));  | 
 | 53 | +    err.span_label(span, label);  | 
 | 54 | +    if !def_span.is_dummy() && !cx.source_map().is_imported(def_span) {  | 
 | 55 | +        err.span_label(cx.source_map().guess_head_span(def_span), "when calling this macro");  | 
 | 56 | +    }  | 
 | 57 | + | 
 | 58 | +    annotate_doc_comment(&mut err, sess.source_map(), span);  | 
 | 59 | + | 
 | 60 | +    if let Some(span) = remaining_matcher.span() {  | 
 | 61 | +        err.span_note(span, format!("while trying to match {remaining_matcher}"));  | 
 | 62 | +    } else {  | 
 | 63 | +        err.note(format!("while trying to match {remaining_matcher}"));  | 
 | 64 | +    }  | 
 | 65 | + | 
 | 66 | +    // Check whether there's a missing comma in this macro call, like `println!("{}" a);`  | 
 | 67 | +    if let Some((arg, comma_span)) = arg.add_comma() {  | 
 | 68 | +        for lhs in lhses {  | 
 | 69 | +            let parser = parser_from_cx(sess, arg.clone(), Recovery::Allowed);  | 
 | 70 | +            let mut tt_parser = TtParser::new(name);  | 
 | 71 | + | 
 | 72 | +            if let Success(_) =  | 
 | 73 | +                tt_parser.parse_tt(&mut Cow::Borrowed(&parser), lhs, &mut NoopTracker)  | 
 | 74 | +            {  | 
 | 75 | +                if comma_span.is_dummy() {  | 
 | 76 | +                    err.note("you might be missing a comma");  | 
 | 77 | +                } else {  | 
 | 78 | +                    err.span_suggestion_short(  | 
 | 79 | +                        comma_span,  | 
 | 80 | +                        "missing comma here",  | 
 | 81 | +                        ", ",  | 
 | 82 | +                        Applicability::MachineApplicable,  | 
 | 83 | +                    );  | 
 | 84 | +                }  | 
 | 85 | +            }  | 
 | 86 | +        }  | 
 | 87 | +    }  | 
 | 88 | +    err.emit();  | 
 | 89 | +    cx.trace_macros_diag();  | 
 | 90 | +    DummyResult::any(sp)  | 
 | 91 | +}  | 
 | 92 | + | 
 | 93 | +/// The tracker used for the slow error path that collects useful info for diagnostics.  | 
 | 94 | +struct CollectTrackerAndEmitter<'a, 'cx, 'matcher> {  | 
 | 95 | +    cx: &'a mut ExtCtxt<'cx>,  | 
 | 96 | +    remaining_matcher: Option<&'matcher MatcherLoc>,  | 
 | 97 | +    /// Which arm's failure should we report? (the one furthest along)  | 
 | 98 | +    best_failure: Option<(Token, &'static str, MatcherLoc)>,  | 
 | 99 | +    root_span: Span,  | 
 | 100 | +    result: Option<Box<dyn MacResult + 'cx>>,  | 
 | 101 | +}  | 
 | 102 | + | 
 | 103 | +impl<'a, 'cx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'a, 'cx, 'matcher> {  | 
 | 104 | +    fn before_match_loc(&mut self, parser: &TtParser, matcher: &'matcher MatcherLoc) {  | 
 | 105 | +        if self.remaining_matcher.is_none()  | 
 | 106 | +            || (parser.has_no_remaining_items_for_step() && *matcher != MatcherLoc::Eof)  | 
 | 107 | +        {  | 
 | 108 | +            self.remaining_matcher = Some(matcher);  | 
 | 109 | +        }  | 
 | 110 | +    }  | 
 | 111 | + | 
 | 112 | +    fn after_arm(&mut self, result: &NamedParseResult) {  | 
 | 113 | +        match result {  | 
 | 114 | +            Success(_) => {  | 
 | 115 | +                // Nonterminal parser recovery might turn failed matches into successful ones,  | 
 | 116 | +                // but for that it must have emitted an error already  | 
 | 117 | +                self.cx.sess.delay_span_bug(  | 
 | 118 | +                    self.root_span,  | 
 | 119 | +                    "should not collect detailed info for successful macro match",  | 
 | 120 | +                );  | 
 | 121 | +            }  | 
 | 122 | +            Failure(token, msg) => match self.best_failure {  | 
 | 123 | +                Some((ref best_token, _, _)) if best_token.span.lo() >= token.span.lo() => {}  | 
 | 124 | +                _ => {  | 
 | 125 | +                    self.best_failure = Some((  | 
 | 126 | +                        token.clone(),  | 
 | 127 | +                        msg,  | 
 | 128 | +                        self.remaining_matcher  | 
 | 129 | +                            .expect("must have collected matcher already")  | 
 | 130 | +                            .clone(),  | 
 | 131 | +                    ))  | 
 | 132 | +                }  | 
 | 133 | +            },  | 
 | 134 | +            Error(err_sp, msg) => {  | 
 | 135 | +                let span = err_sp.substitute_dummy(self.root_span);  | 
 | 136 | +                self.cx.struct_span_err(span, msg).emit();  | 
 | 137 | +                self.result = Some(DummyResult::any(span));  | 
 | 138 | +            }  | 
 | 139 | +            ErrorReported(_) => self.result = Some(DummyResult::any(self.root_span)),  | 
 | 140 | +        }  | 
 | 141 | +    }  | 
 | 142 | + | 
 | 143 | +    fn description() -> &'static str {  | 
 | 144 | +        "detailed"  | 
 | 145 | +    }  | 
 | 146 | + | 
 | 147 | +    fn recovery() -> Recovery {  | 
 | 148 | +        Recovery::Allowed  | 
 | 149 | +    }  | 
 | 150 | +}  | 
 | 151 | + | 
 | 152 | +impl<'a, 'cx> CollectTrackerAndEmitter<'a, 'cx, '_> {  | 
 | 153 | +    fn new(cx: &'a mut ExtCtxt<'cx>, root_span: Span) -> Self {  | 
 | 154 | +        Self { cx, remaining_matcher: None, best_failure: None, root_span, result: None }  | 
 | 155 | +    }  | 
 | 156 | +}  | 
 | 157 | + | 
 | 158 | +pub(super) fn emit_frag_parse_err(  | 
 | 159 | +    mut e: DiagnosticBuilder<'_, rustc_errors::ErrorGuaranteed>,  | 
 | 160 | +    parser: &Parser<'_>,  | 
 | 161 | +    orig_parser: &mut Parser<'_>,  | 
 | 162 | +    site_span: Span,  | 
 | 163 | +    arm_span: Span,  | 
 | 164 | +    kind: AstFragmentKind,  | 
 | 165 | +) {  | 
 | 166 | +    // FIXME(davidtwco): avoid depending on the error message text  | 
 | 167 | +    if parser.token == token::Eof  | 
 | 168 | +        && let DiagnosticMessage::Str(message) = &e.message[0].0  | 
 | 169 | +        && message.ends_with(", found `<eof>`")  | 
 | 170 | +    {  | 
 | 171 | +        let msg = &e.message[0];  | 
 | 172 | +        e.message[0] = (  | 
 | 173 | +            DiagnosticMessage::Str(format!(  | 
 | 174 | +                "macro expansion ends with an incomplete expression: {}",  | 
 | 175 | +                message.replace(", found `<eof>`", ""),  | 
 | 176 | +            )),  | 
 | 177 | +            msg.1,  | 
 | 178 | +        );  | 
 | 179 | +        if !e.span.is_dummy() {  | 
 | 180 | +            // early end of macro arm (#52866)  | 
 | 181 | +            e.replace_span_with(parser.token.span.shrink_to_hi());  | 
 | 182 | +        }  | 
 | 183 | +    }  | 
 | 184 | +    if e.span.is_dummy() {  | 
 | 185 | +        // Get around lack of span in error (#30128)  | 
 | 186 | +        e.replace_span_with(site_span);  | 
 | 187 | +        if !parser.sess.source_map().is_imported(arm_span) {  | 
 | 188 | +            e.span_label(arm_span, "in this macro arm");  | 
 | 189 | +        }  | 
 | 190 | +    } else if parser.sess.source_map().is_imported(parser.token.span) {  | 
 | 191 | +        e.span_label(site_span, "in this macro invocation");  | 
 | 192 | +    }  | 
 | 193 | +    match kind {  | 
 | 194 | +        // Try a statement if an expression is wanted but failed and suggest adding `;` to call.  | 
 | 195 | +        AstFragmentKind::Expr => match parse_ast_fragment(orig_parser, AstFragmentKind::Stmts) {  | 
 | 196 | +            Err(err) => err.cancel(),  | 
 | 197 | +            Ok(_) => {  | 
 | 198 | +                e.note(  | 
 | 199 | +                    "the macro call doesn't expand to an expression, but it can expand to a statement",  | 
 | 200 | +                );  | 
 | 201 | +                e.span_suggestion_verbose(  | 
 | 202 | +                    site_span.shrink_to_hi(),  | 
 | 203 | +                    "add `;` to interpret the expansion as a statement",  | 
 | 204 | +                    ";",  | 
 | 205 | +                    Applicability::MaybeIncorrect,  | 
 | 206 | +                );  | 
 | 207 | +            }  | 
 | 208 | +        },  | 
 | 209 | +        _ => annotate_err_with_kind(&mut e, kind, site_span),  | 
 | 210 | +    };  | 
 | 211 | +    e.emit();  | 
 | 212 | +}  | 
 | 213 | + | 
 | 214 | +pub(crate) fn annotate_err_with_kind(err: &mut Diagnostic, kind: AstFragmentKind, span: Span) {  | 
 | 215 | +    match kind {  | 
 | 216 | +        AstFragmentKind::Ty => {  | 
 | 217 | +            err.span_label(span, "this macro call doesn't expand to a type");  | 
 | 218 | +        }  | 
 | 219 | +        AstFragmentKind::Pat => {  | 
 | 220 | +            err.span_label(span, "this macro call doesn't expand to a pattern");  | 
 | 221 | +        }  | 
 | 222 | +        _ => {}  | 
 | 223 | +    };  | 
 | 224 | +}  | 
 | 225 | + | 
 | 226 | +#[derive(Subdiagnostic)]  | 
 | 227 | +enum ExplainDocComment {  | 
 | 228 | +    #[label(expand_explain_doc_comment_inner)]  | 
 | 229 | +    Inner {  | 
 | 230 | +        #[primary_span]  | 
 | 231 | +        span: Span,  | 
 | 232 | +    },  | 
 | 233 | +    #[label(expand_explain_doc_comment_outer)]  | 
 | 234 | +    Outer {  | 
 | 235 | +        #[primary_span]  | 
 | 236 | +        span: Span,  | 
 | 237 | +    },  | 
 | 238 | +}  | 
 | 239 | + | 
 | 240 | +pub(super) fn annotate_doc_comment(err: &mut Diagnostic, sm: &SourceMap, span: Span) {  | 
 | 241 | +    if let Ok(src) = sm.span_to_snippet(span) {  | 
 | 242 | +        if src.starts_with("///") || src.starts_with("/**") {  | 
 | 243 | +            err.subdiagnostic(ExplainDocComment::Outer { span });  | 
 | 244 | +        } else if src.starts_with("//!") || src.starts_with("/*!") {  | 
 | 245 | +            err.subdiagnostic(ExplainDocComment::Inner { span });  | 
 | 246 | +        }  | 
 | 247 | +    }  | 
 | 248 | +}  | 
 | 249 | + | 
 | 250 | +/// Generates an appropriate parsing failure message. For EOF, this is "unexpected end...". For  | 
 | 251 | +/// other tokens, this is "unexpected token...".  | 
 | 252 | +pub(super) fn parse_failure_msg(tok: &Token) -> String {  | 
 | 253 | +    match tok.kind {  | 
 | 254 | +        token::Eof => "unexpected end of macro invocation".to_string(),  | 
 | 255 | +        _ => format!("no rules expected the token `{}`", pprust::token_to_string(tok),),  | 
 | 256 | +    }  | 
 | 257 | +}  | 
0 commit comments