diff options
| author | Folkert de Vries <folkert@folkertdev.nl> | 2025-04-29 22:43:46 +0200 | 
|---|---|---|
| committer | Folkert de Vries <folkert@folkertdev.nl> | 2025-05-18 11:28:19 +0200 | 
| commit | de8e305ba8feae17e5bc1281647865dc3f1deadf (patch) | |
| tree | b32355328b11b283e261702567689888aca1e784 | |
| parent | 5af9652e5c733eb14c9a28b92c7a2608cbf7ea59 (diff) | |
| download | rust-de8e305ba8feae17e5bc1281647865dc3f1deadf.tar.gz rust-de8e305ba8feae17e5bc1281647865dc3f1deadf.zip  | |
a new parser generating the exact same error messages
Co-authored-by: Travis Cross <tc@traviscross.com>
| -rw-r--r-- | compiler/rustc_builtin_macros/src/asm.rs | 376 | 
1 files changed, 226 insertions, 150 deletions
diff --git a/compiler/rustc_builtin_macros/src/asm.rs b/compiler/rustc_builtin_macros/src/asm.rs index 3afa2d3dd8e..be6f6b60136 100644 --- a/compiler/rustc_builtin_macros/src/asm.rs +++ b/compiler/rustc_builtin_macros/src/asm.rs @@ -4,7 +4,7 @@ use rustc_ast::ptr::P; use rustc_ast::tokenstream::TokenStream; use rustc_ast::{AsmMacro, token}; use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; -use rustc_errors::PResult; +use rustc_errors::{DiagCtxtHandle, PResult}; use rustc_expand::base::*; use rustc_index::bit_set::GrowableBitSet; use rustc_parse::exp; @@ -18,6 +18,21 @@ use {rustc_ast as ast, rustc_parse_format as parse}; use crate::errors; use crate::util::{ExprToSpannedString, expr_to_spanned_string}; +/// An argument to one of the `asm!` macros. The argument is syntactically valid, but is otherwise +/// not validated at all. +pub struct RawAsmArg { + pub kind: RawAsmArgKind, + pub span: Span, +} + +pub enum RawAsmArgKind { + Template(P<ast::Expr>), + Operand(Option<Symbol>, ast::InlineAsmOperand), + Options(Vec<(Symbol, ast::InlineAsmOptions, Span, Span)>), + ClobberAbi(Vec<(Symbol, Span)>), +} + +/// Validated assembly arguments, ready for macro expansion. pub struct AsmArgs { pub templates: Vec<P<ast::Expr>>, pub operands: Vec<(ast::InlineAsmOperand, Span)>, @@ -59,16 +74,6 @@ fn eat_operand_keyword<'a>( } } -fn parse_args<'a>( - ecx: &ExtCtxt<'a>, - sp: Span, - tts: TokenStream, - asm_macro: AsmMacro, -) -> PResult<'a, AsmArgs> { - let mut p = ecx.new_parser_from_tts(tts); - parse_asm_args(&mut p, sp, asm_macro) -} - fn parse_asm_operand<'a>( p: &mut Parser<'a>, asm_macro: AsmMacro, @@ -139,31 +144,28 @@ fn parse_asm_operand<'a>( })) } -// Primarily public for rustfmt consumption. -// Internal consumers should continue to leverage `expand_asm`/`expand__global_asm` -pub fn parse_asm_args<'a>( +// Public for rustfmt. +pub fn parse_raw_asm_args<'a>( p: &mut Parser<'a>, sp: Span, asm_macro: AsmMacro, -) -> PResult<'a, AsmArgs> { +) -> PResult<'a, Vec<RawAsmArg>> { let dcx = p.dcx(); if p.token == token::Eof { return Err(dcx.create_err(errors::AsmRequiresTemplate { span: sp })); } + let mut args = Vec::new(); + let first_template = p.parse_expr()?; - let mut args = AsmArgs { - templates: vec![first_template], - operands: vec![], - named_args: Default::default(), - reg_args: Default::default(), - clobber_abis: Vec::new(), - options: ast::InlineAsmOptions::empty(), - options_spans: vec![], - }; + args.push(RawAsmArg { + span: first_template.span, + kind: RawAsmArgKind::Template(first_template), + }); let mut allow_templates = true; + while p.token != token::Eof { if !p.eat(exp!(Comma)) { if allow_templates { @@ -174,27 +176,39 @@ pub fn parse_asm_args<'a>( return Err(p.expect(exp!(Comma)).err().unwrap()); } } + + // Accept trailing commas. if p.token == token::Eof { break; - } // accept trailing commas + } + + let span_start = p.token.span; - // Parse clobber_abi + // Parse `clobber_abi`. if p.eat_keyword(exp!(ClobberAbi)) { - parse_clobber_abi(p, &mut args)?; allow_templates = false; + + args.push(RawAsmArg { + kind: RawAsmArgKind::ClobberAbi(parse_clobber_abi(p)?), + span: span_start.to(p.prev_token.span), + }); + continue; } - // Parse options + // Parse `options`. if p.eat_keyword(exp!(Options)) { - parse_options(p, &mut args, asm_macro)?; allow_templates = false; + + args.push(RawAsmArg { + kind: RawAsmArgKind::Options(parse_options(p, asm_macro)?), + span: span_start.to(p.prev_token.span), + }); + continue; } - let span_start = p.token.span; - - // Parse operand names + // Parse operand names. let name = if p.token.is_ident() && p.look_ahead(1, |t| *t == token::Eq) { let (ident, _) = p.token.ident().unwrap(); p.bump(); @@ -205,60 +219,172 @@ pub fn parse_asm_args<'a>( None }; - let Some(op) = parse_asm_operand(p, asm_macro)? else { - if allow_templates { - let template = p.parse_expr()?; - // If it can't possibly expand to a string, provide diagnostics here to include other - // things it could have been. - match template.kind { - ast::ExprKind::Lit(token_lit) - if matches!( - token_lit.kind, - token::LitKind::Str | token::LitKind::StrRaw(_) - ) => {} - ast::ExprKind::MacCall(..) => {} - _ => { - let err = dcx.create_err(errors::AsmExpectedOther { - span: template.span, - is_inline_asm: matches!(asm_macro, AsmMacro::Asm), - }); - return Err(err); + if let Some(op) = parse_asm_operand(p, asm_macro)? { + allow_templates = false; + + args.push(RawAsmArg { + span: span_start.to(p.prev_token.span), + kind: RawAsmArgKind::Operand(name, op), + }); + } else if allow_templates { + let template = p.parse_expr()?; + // If it can't possibly expand to a string, provide diagnostics here to include other + // things it could have been. + match template.kind { + ast::ExprKind::Lit(token_lit) + if matches!( + token_lit.kind, + token::LitKind::Str | token::LitKind::StrRaw(_) + ) => {} + ast::ExprKind::MacCall(..) => {} + _ => { + let err = dcx.create_err(errors::AsmExpectedOther { + span: template.span, + is_inline_asm: matches!(asm_macro, AsmMacro::Asm), + }); + return Err(err); + } + } + + args.push(RawAsmArg { span: template.span, kind: RawAsmArgKind::Template(template) }); + } else { + p.unexpected_any()? + } + } + + Ok(args) +} + +fn parse_args<'a>( + ecx: &ExtCtxt<'a>, + sp: Span, + tts: TokenStream, + asm_macro: AsmMacro, +) -> PResult<'a, AsmArgs> { + let mut p = ecx.new_parser_from_tts(tts); + parse_asm_args(&mut p, sp, asm_macro) +} + +// public for use in rustfmt +// FIXME: use `RawAsmArg` in the formatting code instead. +pub fn parse_asm_args<'a>( + p: &mut Parser<'a>, + sp: Span, + asm_macro: AsmMacro, +) -> PResult<'a, AsmArgs> { + let raw_args = parse_raw_asm_args(p, sp, asm_macro)?; + validate_raw_asm_args(p.dcx(), asm_macro, raw_args) +} + +pub fn validate_raw_asm_args<'a>( + dcx: DiagCtxtHandle<'a>, + asm_macro: AsmMacro, + raw_args: Vec<RawAsmArg>, +) -> PResult<'a, AsmArgs> { + let mut args = AsmArgs { + templates: vec![], + operands: vec![], + named_args: Default::default(), + reg_args: Default::default(), + clobber_abis: Vec::new(), + options: ast::InlineAsmOptions::empty(), + options_spans: vec![], + }; + + let mut allow_templates = true; + + for arg in raw_args { + match arg.kind { + RawAsmArgKind::Template(template) => { + // The error for the first template is delayed. + if !allow_templates { + match template.kind { + ast::ExprKind::Lit(token_lit) + if matches!( + token_lit.kind, + token::LitKind::Str | token::LitKind::StrRaw(_) + ) => {} + ast::ExprKind::MacCall(..) => {} + _ => { + let err = dcx.create_err(errors::AsmExpectedOther { + span: template.span, + is_inline_asm: matches!(asm_macro, AsmMacro::Asm), + }); + return Err(err); + } } } + args.templates.push(template); - continue; - } else { - p.unexpected_any()? } - }; + RawAsmArgKind::Operand(name, op) => { + allow_templates = false; - let explicit_reg = matches!(op.reg(), Some(ast::InlineAsmRegOrRegClass::Reg(_))); + let explicit_reg = matches!(op.reg(), Some(ast::InlineAsmRegOrRegClass::Reg(_))); + let span = arg.span; + let slot = args.operands.len(); + args.operands.push((op, span)); - allow_templates = false; - let span = span_start.to(p.prev_token.span); - let slot = args.operands.len(); - args.operands.push((op, span)); + // Validate the order of named, positional & explicit register operands and + // clobber_abi/options. We do this at the end once we have the full span + // of the argument available. - // Validate the order of named, positional & explicit register operands and - // clobber_abi/options. We do this at the end once we have the full span - // of the argument available. + if explicit_reg { + if name.is_some() { + dcx.emit_err(errors::AsmExplicitRegisterName { span }); + } + args.reg_args.insert(slot); + } else if let Some(name) = name { + if let Some(&prev) = args.named_args.get(&name) { + dcx.emit_err(errors::AsmDuplicateArg { + span, + name, + prev: args.operands[prev].1, + }); + continue; + } + args.named_args.insert(name, slot); + } else if !args.named_args.is_empty() || !args.reg_args.is_empty() { + let named = args.named_args.values().map(|p| args.operands[*p].1).collect(); + let explicit = args.reg_args.iter().map(|p| args.operands[p].1).collect(); - if explicit_reg { - if name.is_some() { - dcx.emit_err(errors::AsmExplicitRegisterName { span }); - } - args.reg_args.insert(slot); - } else if let Some(name) = name { - if let Some(&prev) = args.named_args.get(&name) { - dcx.emit_err(errors::AsmDuplicateArg { span, name, prev: args.operands[prev].1 }); - continue; + dcx.emit_err(errors::AsmPositionalAfter { span, named, explicit }); + } } - args.named_args.insert(name, slot); - } else if !args.named_args.is_empty() || !args.reg_args.is_empty() { - let named = args.named_args.values().map(|p| args.operands[*p].1).collect(); - let explicit = args.reg_args.iter().map(|p| args.operands[p].1).collect(); + RawAsmArgKind::Options(new_options) => { + allow_templates = false; + + for (symbol, option, span, full_span) in new_options { + if !asm_macro.is_supported_option(option) { + /* + // Tool-only output + p.dcx().emit_err(errors::AsmUnsupportedOption { + span, + symbol, + full_span, + macro_name: asm_macro.macro_name(), + }); + */ + } else if args.options.contains(option) { + // Tool-only output. + dcx.emit_err(errors::AsmOptAlreadyprovided { span, symbol, full_span }); + } else { + args.options |= option; + } + } - dcx.emit_err(errors::AsmPositionalAfter { span, named, explicit }); + args.options_spans.push(arg.span); + } + RawAsmArgKind::ClobberAbi(new_abis) => { + allow_templates = false; + + match &new_abis[..] { + // This should have errored above during parsing. + [] => unreachable!(), + [(abi, _span)] => args.clobber_abis.push((*abi, arg.span)), + _ => args.clobber_abis.extend(new_abis), + } + } } } @@ -348,61 +474,14 @@ pub fn parse_asm_args<'a>( Ok(args) } -/// Report a duplicate option error. -/// -/// This function must be called immediately after the option token is parsed. -/// Otherwise, the suggestion will be incorrect. -fn err_duplicate_option(p: &Parser<'_>, symbol: Symbol, span: Span) { - // Tool-only output - let full_span = if p.token == token::Comma { span.to(p.token.span) } else { span }; - p.dcx().emit_err(errors::AsmOptAlreadyprovided { span, symbol, full_span }); -} - -/// Report an invalid option error. -/// -/// This function must be called immediately after the option token is parsed. -/// Otherwise, the suggestion will be incorrect. -fn err_unsupported_option(p: &Parser<'_>, asm_macro: AsmMacro, symbol: Symbol, span: Span) { - // Tool-only output - let full_span = if p.token == token::Comma { span.to(p.token.span) } else { span }; - p.dcx().emit_err(errors::AsmUnsupportedOption { - span, - symbol, - full_span, - macro_name: asm_macro.macro_name(), - }); -} - -/// Try to set the provided option in the provided `AsmArgs`. -/// If it is already set, report a duplicate option error. -/// -/// This function must be called immediately after the option token is parsed. -/// Otherwise, the error will not point to the correct spot. -fn try_set_option<'a>( - p: &Parser<'a>, - args: &mut AsmArgs, - asm_macro: AsmMacro, - symbol: Symbol, - option: ast::InlineAsmOptions, -) { - if !asm_macro.is_supported_option(option) { - err_unsupported_option(p, asm_macro, symbol, p.prev_token.span); - } else if args.options.contains(option) { - err_duplicate_option(p, symbol, p.prev_token.span); - } else { - args.options |= option; - } -} - fn parse_options<'a>( p: &mut Parser<'a>, - args: &mut AsmArgs, asm_macro: AsmMacro, -) -> PResult<'a, ()> { - let span_start = p.prev_token.span; - +) -> PResult<'a, Vec<(Symbol, ast::InlineAsmOptions, Span, Span)>> { p.expect(exp!(OpenParen))?; + let mut options = Vec::new(); + while !p.eat(exp!(CloseParen)) { const OPTIONS: [(ExpKeywordPair, ast::InlineAsmOptions); ast::InlineAsmOptions::COUNT] = [ (exp!(Pure), ast::InlineAsmOptions::PURE), @@ -418,6 +497,7 @@ fn parse_options<'a>( 'blk: { for (exp, option) in OPTIONS { + // Gives a more accurate list of expected next tokens. let kw_matched = if asm_macro.is_supported_option(option) { p.eat_keyword(exp) } else { @@ -425,30 +505,39 @@ fn parse_options<'a>( }; if kw_matched { - try_set_option(p, args, asm_macro, exp.kw, option); + let span = p.prev_token.span; + let full_span = + if p.token == token::Comma { span.to(p.token.span) } else { span }; + + if !asm_macro.is_supported_option(option) { + // Tool-only output. + p.dcx().emit_err(errors::AsmUnsupportedOption { + span, + symbol: exp.kw, + full_span, + macro_name: asm_macro.macro_name(), + }); + } + + options.push((exp.kw, option, span, full_span)); break 'blk; } } - return p.unexpected(); + return p.unexpected_any(); } - // Allow trailing commas + // Allow trailing commas. if p.eat(exp!(CloseParen)) { break; } p.expect(exp!(Comma))?; } - let new_span = span_start.to(p.prev_token.span); - args.options_spans.push(new_span); - - Ok(()) + Ok(options) } -fn parse_clobber_abi<'a>(p: &mut Parser<'a>, args: &mut AsmArgs) -> PResult<'a, ()> { - let span_start = p.prev_token.span; - +fn parse_clobber_abi<'a>(p: &mut Parser<'a>) -> PResult<'a, Vec<(Symbol, Span)>> { p.expect(exp!(OpenParen))?; if p.eat(exp!(CloseParen)) { @@ -474,20 +563,7 @@ fn parse_clobber_abi<'a>(p: &mut Parser<'a>, args: &mut AsmArgs) -> PResult<'a, p.expect(exp!(Comma))?; } - let full_span = span_start.to(p.prev_token.span); - - match &new_abis[..] { - // should have errored above during parsing - [] => unreachable!(), - [(abi, _span)] => args.clobber_abis.push((*abi, full_span)), - abis => { - for (abi, span) in abis { - args.clobber_abis.push((*abi, *span)); - } - } - } - - Ok(()) + Ok(new_abis) } fn parse_reg<'a>(p: &mut Parser<'a>) -> PResult<'a, ast::InlineAsmRegOrRegClass> {  | 
