|  | use std::ops::Range; | 
|  |  | 
|  | use parse::Position::ArgumentNamed; | 
|  | use rustc_ast::ptr::P; | 
|  | use rustc_ast::tokenstream::TokenStream; | 
|  | use rustc_ast::{ | 
|  | Expr, ExprKind, FormatAlignment, FormatArgPosition, FormatArgPositionKind, FormatArgs, | 
|  | FormatArgsPiece, FormatArgument, FormatArgumentKind, FormatArguments, FormatCount, | 
|  | FormatDebugHex, FormatOptions, FormatPlaceholder, FormatSign, FormatTrait, Recovered, StmtKind, | 
|  | token, | 
|  | }; | 
|  | use rustc_data_structures::fx::FxHashSet; | 
|  | use rustc_errors::{ | 
|  | Applicability, Diag, MultiSpan, PResult, SingleLabelManySpans, listify, pluralize, | 
|  | }; | 
|  | use rustc_expand::base::*; | 
|  | use rustc_lint_defs::builtin::NAMED_ARGUMENTS_USED_POSITIONALLY; | 
|  | use rustc_lint_defs::{BufferedEarlyLint, BuiltinLintDiag, LintId}; | 
|  | use rustc_parse::exp; | 
|  | use rustc_parse_format as parse; | 
|  | use rustc_span::{BytePos, ErrorGuaranteed, Ident, InnerSpan, Span, Symbol}; | 
|  |  | 
|  | use crate::errors; | 
|  | use crate::util::{ExprToSpannedString, expr_to_spanned_string}; | 
|  |  | 
|  | // The format_args!() macro is expanded in three steps: | 
|  | //  1. First, `parse_args` will parse the `(literal, arg, arg, name=arg, name=arg)` syntax, | 
|  | //     but doesn't parse the template (the literal) itself. | 
|  | //  2. Second, `make_format_args` will parse the template, the format options, resolve argument references, | 
|  | //     produce diagnostics, and turn the whole thing into a `FormatArgs` AST node. | 
|  | //  3. Much later, in AST lowering (rustc_ast_lowering), that `FormatArgs` structure will be turned | 
|  | //     into the expression of type `core::fmt::Arguments`. | 
|  |  | 
|  | // See rustc_ast/src/format.rs for the FormatArgs structure and glossary. | 
|  |  | 
|  | // Only used in parse_args and report_invalid_references, | 
|  | // to indicate how a referred argument was used. | 
|  | #[derive(Clone, Copy, Debug, PartialEq, Eq)] | 
|  | enum PositionUsedAs { | 
|  | Placeholder(Option<Span>), | 
|  | Precision, | 
|  | Width, | 
|  | } | 
|  | use PositionUsedAs::*; | 
|  |  | 
|  | #[derive(Debug)] | 
|  | struct MacroInput { | 
|  | fmtstr: P<Expr>, | 
|  | args: FormatArguments, | 
|  | /// Whether the first argument was a string literal or a result from eager macro expansion. | 
|  | /// If it's not a string literal, we disallow implicit argument capturing. | 
|  | /// | 
|  | /// This does not correspond to whether we can treat spans to the literal normally, as the whole | 
|  | /// invocation might be the result of another macro expansion, in which case this flag may still be true. | 
|  | /// | 
|  | /// See [RFC 2795] for more information. | 
|  | /// | 
|  | /// [RFC 2795]: https://rust-lang.github.io/rfcs/2795-format-args-implicit-identifiers.html#macro-hygiene | 
|  | is_direct_literal: bool, | 
|  | } | 
|  |  | 
|  | /// Parses the arguments from the given list of tokens, returning the diagnostic | 
|  | /// if there's a parse error so we can continue parsing other format! | 
|  | /// expressions. | 
|  | /// | 
|  | /// If parsing succeeds, the return value is: | 
|  | /// | 
|  | /// ```text | 
|  | /// Ok((fmtstr, parsed arguments)) | 
|  | /// ``` | 
|  | fn parse_args<'a>(ecx: &ExtCtxt<'a>, sp: Span, tts: TokenStream) -> PResult<'a, MacroInput> { | 
|  | let mut args = FormatArguments::new(); | 
|  |  | 
|  | let mut p = ecx.new_parser_from_tts(tts); | 
|  |  | 
|  | if p.token == token::Eof { | 
|  | return Err(ecx.dcx().create_err(errors::FormatRequiresString { span: sp })); | 
|  | } | 
|  |  | 
|  | let first_token = &p.token; | 
|  |  | 
|  | let fmtstr = if let token::Literal(lit) = first_token.kind | 
|  | && matches!(lit.kind, token::Str | token::StrRaw(_)) | 
|  | { | 
|  | // This allows us to properly handle cases when the first comma | 
|  | // after the format string is mistakenly replaced with any operator, | 
|  | // which cause the expression parser to eat too much tokens. | 
|  | p.parse_literal_maybe_minus()? | 
|  | } else { | 
|  | // Otherwise, we fall back to the expression parser. | 
|  | p.parse_expr()? | 
|  | }; | 
|  |  | 
|  | // Only allow implicit captures to be used when the argument is a direct literal | 
|  | // instead of a macro expanding to one. | 
|  | let is_direct_literal = matches!(fmtstr.kind, ExprKind::Lit(_)); | 
|  |  | 
|  | let mut first = true; | 
|  |  | 
|  | while p.token != token::Eof { | 
|  | if !p.eat(exp!(Comma)) { | 
|  | if first { | 
|  | p.clear_expected_token_types(); | 
|  | } | 
|  |  | 
|  | match p.expect(exp!(Comma)) { | 
|  | Err(err) => { | 
|  | if token::TokenKind::Comma.similar_tokens().contains(&p.token.kind) { | 
|  | // If a similar token is found, then it may be a typo. We | 
|  | // consider it as a comma, and continue parsing. | 
|  | err.emit(); | 
|  | p.bump(); | 
|  | } else { | 
|  | // Otherwise stop the parsing and return the error. | 
|  | return Err(err); | 
|  | } | 
|  | } | 
|  | Ok(Recovered::Yes(_)) => (), | 
|  | Ok(Recovered::No) => unreachable!(), | 
|  | } | 
|  | } | 
|  | first = false; | 
|  | if p.token == token::Eof { | 
|  | break; | 
|  | } // accept trailing commas | 
|  | match p.token.ident() { | 
|  | Some((ident, _)) if p.look_ahead(1, |t| *t == token::Eq) => { | 
|  | p.bump(); | 
|  | p.expect(exp!(Eq))?; | 
|  | let expr = p.parse_expr()?; | 
|  | if let Some((_, prev)) = args.by_name(ident.name) { | 
|  | ecx.dcx().emit_err(errors::FormatDuplicateArg { | 
|  | span: ident.span, | 
|  | prev: prev.kind.ident().unwrap().span, | 
|  | duplicate: ident.span, | 
|  | ident, | 
|  | }); | 
|  | continue; | 
|  | } | 
|  | args.add(FormatArgument { kind: FormatArgumentKind::Named(ident), expr }); | 
|  | } | 
|  | _ => { | 
|  | let expr = p.parse_expr()?; | 
|  | if !args.named_args().is_empty() { | 
|  | return Err(ecx.dcx().create_err(errors::PositionalAfterNamed { | 
|  | span: expr.span, | 
|  | args: args | 
|  | .named_args() | 
|  | .iter() | 
|  | .filter_map(|a| a.kind.ident().map(|ident| (a, ident))) | 
|  | .map(|(arg, n)| n.span.to(arg.expr.span)) | 
|  | .collect(), | 
|  | })); | 
|  | } | 
|  | args.add(FormatArgument { kind: FormatArgumentKind::Normal, expr }); | 
|  | } | 
|  | } | 
|  | } | 
|  | Ok(MacroInput { fmtstr, args, is_direct_literal }) | 
|  | } | 
|  |  | 
|  | fn make_format_args( | 
|  | ecx: &mut ExtCtxt<'_>, | 
|  | input: MacroInput, | 
|  | append_newline: bool, | 
|  | ) -> ExpandResult<Result<FormatArgs, ErrorGuaranteed>, ()> { | 
|  | let msg = "format argument must be a string literal"; | 
|  | let unexpanded_fmt_span = input.fmtstr.span; | 
|  |  | 
|  | let MacroInput { fmtstr: efmt, mut args, is_direct_literal } = input; | 
|  |  | 
|  | let ExprToSpannedString { | 
|  | symbol: fmt_str, | 
|  | span: fmt_span, | 
|  | style: fmt_style, | 
|  | uncooked_symbol: uncooked_fmt_str, | 
|  | } = { | 
|  | let ExpandResult::Ready(mac) = expr_to_spanned_string(ecx, efmt.clone(), msg) else { | 
|  | return ExpandResult::Retry(()); | 
|  | }; | 
|  | match mac { | 
|  | Ok(mut fmt) if append_newline => { | 
|  | fmt.symbol = Symbol::intern(&format!("{}\n", fmt.symbol)); | 
|  | fmt | 
|  | } | 
|  | Ok(fmt) => fmt, | 
|  | Err(err) => { | 
|  | let guar = match err { | 
|  | Ok((mut err, suggested)) => { | 
|  | if !suggested { | 
|  | if let ExprKind::Block(block, None) = &efmt.kind | 
|  | && let [stmt] = block.stmts.as_slice() | 
|  | && let StmtKind::Expr(expr) = &stmt.kind | 
|  | && let ExprKind::Path(None, path) = &expr.kind | 
|  | && path.segments.len() == 1 | 
|  | && path.segments[0].args.is_none() | 
|  | { | 
|  | err.multipart_suggestion( | 
|  | "quote your inlined format argument to use as string literal", | 
|  | vec![ | 
|  | (unexpanded_fmt_span.shrink_to_hi(), "\"".to_string()), | 
|  | (unexpanded_fmt_span.shrink_to_lo(), "\"".to_string()), | 
|  | ], | 
|  | Applicability::MaybeIncorrect, | 
|  | ); | 
|  | } else { | 
|  | // `{}` or `()` | 
|  | let should_suggest = |kind: &ExprKind| -> bool { | 
|  | match kind { | 
|  | ExprKind::Block(b, None) if b.stmts.is_empty() => true, | 
|  | ExprKind::Tup(v) if v.is_empty() => true, | 
|  | _ => false, | 
|  | } | 
|  | }; | 
|  |  | 
|  | let mut sugg_fmt = String::new(); | 
|  | for kind in std::iter::once(&efmt.kind) | 
|  | .chain(args.explicit_args().into_iter().map(|a| &a.expr.kind)) | 
|  | { | 
|  | sugg_fmt.push_str(if should_suggest(kind) { | 
|  | "{:?} " | 
|  | } else { | 
|  | "{} " | 
|  | }); | 
|  | } | 
|  | sugg_fmt = sugg_fmt.trim_end().to_string(); | 
|  | err.span_suggestion( | 
|  | unexpanded_fmt_span.shrink_to_lo(), | 
|  | "you might be missing a string literal to format with", | 
|  | format!("\"{sugg_fmt}\", "), | 
|  | Applicability::MaybeIncorrect, | 
|  | ); | 
|  | } | 
|  | } | 
|  | err.emit() | 
|  | } | 
|  | Err(guar) => guar, | 
|  | }; | 
|  | return ExpandResult::Ready(Err(guar)); | 
|  | } | 
|  | } | 
|  | }; | 
|  |  | 
|  | let str_style = match fmt_style { | 
|  | rustc_ast::StrStyle::Cooked => None, | 
|  | rustc_ast::StrStyle::Raw(raw) => Some(raw as usize), | 
|  | }; | 
|  |  | 
|  | let fmt_str = fmt_str.as_str(); // for the suggestions below | 
|  | let fmt_snippet = ecx.source_map().span_to_snippet(unexpanded_fmt_span).ok(); | 
|  | let mut parser = parse::Parser::new( | 
|  | fmt_str, | 
|  | str_style, | 
|  | fmt_snippet, | 
|  | append_newline, | 
|  | parse::ParseMode::Format, | 
|  | ); | 
|  |  | 
|  | let mut pieces = Vec::new(); | 
|  | while let Some(piece) = parser.next() { | 
|  | if !parser.errors.is_empty() { | 
|  | break; | 
|  | } else { | 
|  | pieces.push(piece); | 
|  | } | 
|  | } | 
|  |  | 
|  | let is_source_literal = parser.is_source_literal; | 
|  |  | 
|  | if !parser.errors.is_empty() { | 
|  | let err = parser.errors.remove(0); | 
|  | let sp = if is_source_literal { | 
|  | fmt_span.from_inner(InnerSpan::new(err.span.start, err.span.end)) | 
|  | } else { | 
|  | // The format string could be another macro invocation, e.g.: | 
|  | //     format!(concat!("abc", "{}"), 4); | 
|  | // However, `err.span` is an inner span relative to the *result* of | 
|  | // the macro invocation, which is why we would get a nonsensical | 
|  | // result calling `fmt_span.from_inner(err.span)` as above, and | 
|  | // might even end up inside a multibyte character (issue #86085). | 
|  | // Therefore, we conservatively report the error for the entire | 
|  | // argument span here. | 
|  | fmt_span | 
|  | }; | 
|  | let mut e = errors::InvalidFormatString { | 
|  | span: sp, | 
|  | note_: None, | 
|  | label_: None, | 
|  | sugg_: None, | 
|  | desc: err.description, | 
|  | label1: err.label, | 
|  | }; | 
|  | if let Some(note) = err.note { | 
|  | e.note_ = Some(errors::InvalidFormatStringNote { note }); | 
|  | } | 
|  | if let Some((label, span)) = err.secondary_label | 
|  | && is_source_literal | 
|  | { | 
|  | e.label_ = Some(errors::InvalidFormatStringLabel { | 
|  | span: fmt_span.from_inner(InnerSpan::new(span.start, span.end)), | 
|  | label, | 
|  | }); | 
|  | } | 
|  | match err.suggestion { | 
|  | parse::Suggestion::None => {} | 
|  | parse::Suggestion::UsePositional => { | 
|  | let captured_arg_span = | 
|  | fmt_span.from_inner(InnerSpan::new(err.span.start, err.span.end)); | 
|  | if let Ok(arg) = ecx.source_map().span_to_snippet(captured_arg_span) { | 
|  | let span = match args.unnamed_args().last() { | 
|  | Some(arg) => arg.expr.span, | 
|  | None => fmt_span, | 
|  | }; | 
|  | e.sugg_ = Some(errors::InvalidFormatStringSuggestion::UsePositional { | 
|  | captured: captured_arg_span, | 
|  | len: args.unnamed_args().len().to_string(), | 
|  | span: span.shrink_to_hi(), | 
|  | arg, | 
|  | }); | 
|  | } | 
|  | } | 
|  | parse::Suggestion::RemoveRawIdent(span) => { | 
|  | if is_source_literal { | 
|  | let span = fmt_span.from_inner(InnerSpan::new(span.start, span.end)); | 
|  | e.sugg_ = Some(errors::InvalidFormatStringSuggestion::RemoveRawIdent { span }) | 
|  | } | 
|  | } | 
|  | parse::Suggestion::ReorderFormatParameter(span, replacement) => { | 
|  | let span = fmt_span.from_inner(InnerSpan::new(span.start, span.end)); | 
|  | e.sugg_ = Some(errors::InvalidFormatStringSuggestion::ReorderFormatParameter { | 
|  | span, | 
|  | replacement, | 
|  | }); | 
|  | } | 
|  | } | 
|  | let guar = ecx.dcx().emit_err(e); | 
|  | return ExpandResult::Ready(Err(guar)); | 
|  | } | 
|  |  | 
|  | let to_span = |inner_span: Range<usize>| { | 
|  | is_source_literal.then(|| { | 
|  | fmt_span.from_inner(InnerSpan { start: inner_span.start, end: inner_span.end }) | 
|  | }) | 
|  | }; | 
|  |  | 
|  | let mut used = vec![false; args.explicit_args().len()]; | 
|  | let mut invalid_refs = Vec::new(); | 
|  | let mut numeric_references_to_named_arg = Vec::new(); | 
|  |  | 
|  | enum ArgRef<'a> { | 
|  | Index(usize), | 
|  | Name(&'a str, Option<Span>), | 
|  | } | 
|  | use ArgRef::*; | 
|  |  | 
|  | let mut unnamed_arg_after_named_arg = false; | 
|  |  | 
|  | let mut lookup_arg = |arg: ArgRef<'_>, | 
|  | span: Option<Span>, | 
|  | used_as: PositionUsedAs, | 
|  | kind: FormatArgPositionKind| | 
|  | -> FormatArgPosition { | 
|  | let index = match arg { | 
|  | Index(index) => { | 
|  | if let Some(arg) = args.by_index(index) { | 
|  | used[index] = true; | 
|  | if arg.kind.ident().is_some() { | 
|  | // This was a named argument, but it was used as a positional argument. | 
|  | numeric_references_to_named_arg.push((index, span, used_as)); | 
|  | } | 
|  | Ok(index) | 
|  | } else { | 
|  | // Doesn't exist as an explicit argument. | 
|  | invalid_refs.push((index, span, used_as, kind)); | 
|  | Err(index) | 
|  | } | 
|  | } | 
|  | Name(name, span) => { | 
|  | let name = Symbol::intern(name); | 
|  | if let Some((index, _)) = args.by_name(name) { | 
|  | // Name found in `args`, so we resolve it to its index. | 
|  | if index < args.explicit_args().len() { | 
|  | // Mark it as used, if it was an explicit argument. | 
|  | used[index] = true; | 
|  | } | 
|  | Ok(index) | 
|  | } else { | 
|  | // Name not found in `args`, so we add it as an implicitly captured argument. | 
|  | let span = span.unwrap_or(fmt_span); | 
|  | let ident = Ident::new(name, span); | 
|  | let expr = if is_direct_literal { | 
|  | ecx.expr_ident(span, ident) | 
|  | } else { | 
|  | // For the moment capturing variables from format strings expanded from macros is | 
|  | // disabled (see RFC #2795) | 
|  | let guar = ecx.dcx().emit_err(errors::FormatNoArgNamed { span, name }); | 
|  | unnamed_arg_after_named_arg = true; | 
|  | DummyResult::raw_expr(span, Some(guar)) | 
|  | }; | 
|  | Ok(args.add(FormatArgument { kind: FormatArgumentKind::Captured(ident), expr })) | 
|  | } | 
|  | } | 
|  | }; | 
|  | FormatArgPosition { index, kind, span } | 
|  | }; | 
|  |  | 
|  | let mut template = Vec::new(); | 
|  | let mut unfinished_literal = String::new(); | 
|  | let mut placeholder_index = 0; | 
|  |  | 
|  | for piece in &pieces { | 
|  | match piece.clone() { | 
|  | parse::Piece::Lit(s) => { | 
|  | unfinished_literal.push_str(s); | 
|  | } | 
|  | parse::Piece::NextArgument(box parse::Argument { position, position_span, format }) => { | 
|  | if !unfinished_literal.is_empty() { | 
|  | template.push(FormatArgsPiece::Literal(Symbol::intern(&unfinished_literal))); | 
|  | unfinished_literal.clear(); | 
|  | } | 
|  |  | 
|  | let span = | 
|  | parser.arg_places.get(placeholder_index).and_then(|s| to_span(s.clone())); | 
|  | placeholder_index += 1; | 
|  |  | 
|  | let position_span = to_span(position_span); | 
|  | let argument = match position { | 
|  | parse::ArgumentImplicitlyIs(i) => lookup_arg( | 
|  | Index(i), | 
|  | position_span, | 
|  | Placeholder(span), | 
|  | FormatArgPositionKind::Implicit, | 
|  | ), | 
|  | parse::ArgumentIs(i) => lookup_arg( | 
|  | Index(i), | 
|  | position_span, | 
|  | Placeholder(span), | 
|  | FormatArgPositionKind::Number, | 
|  | ), | 
|  | parse::ArgumentNamed(name) => lookup_arg( | 
|  | Name(name, position_span), | 
|  | position_span, | 
|  | Placeholder(span), | 
|  | FormatArgPositionKind::Named, | 
|  | ), | 
|  | }; | 
|  |  | 
|  | let alignment = match format.align { | 
|  | parse::AlignUnknown => None, | 
|  | parse::AlignLeft => Some(FormatAlignment::Left), | 
|  | parse::AlignRight => Some(FormatAlignment::Right), | 
|  | parse::AlignCenter => Some(FormatAlignment::Center), | 
|  | }; | 
|  |  | 
|  | let format_trait = match format.ty { | 
|  | "" => FormatTrait::Display, | 
|  | "?" => FormatTrait::Debug, | 
|  | "e" => FormatTrait::LowerExp, | 
|  | "E" => FormatTrait::UpperExp, | 
|  | "o" => FormatTrait::Octal, | 
|  | "p" => FormatTrait::Pointer, | 
|  | "b" => FormatTrait::Binary, | 
|  | "x" => FormatTrait::LowerHex, | 
|  | "X" => FormatTrait::UpperHex, | 
|  | _ => { | 
|  | invalid_placeholder_type_error(ecx, format.ty, format.ty_span, fmt_span); | 
|  | FormatTrait::Display | 
|  | } | 
|  | }; | 
|  |  | 
|  | let precision_span = format.precision_span.and_then(to_span); | 
|  | let precision = match format.precision { | 
|  | parse::CountIs(n) => Some(FormatCount::Literal(n)), | 
|  | parse::CountIsName(name, name_span) => Some(FormatCount::Argument(lookup_arg( | 
|  | Name(name, to_span(name_span)), | 
|  | precision_span, | 
|  | Precision, | 
|  | FormatArgPositionKind::Named, | 
|  | ))), | 
|  | parse::CountIsParam(i) => Some(FormatCount::Argument(lookup_arg( | 
|  | Index(i), | 
|  | precision_span, | 
|  | Precision, | 
|  | FormatArgPositionKind::Number, | 
|  | ))), | 
|  | parse::CountIsStar(i) => Some(FormatCount::Argument(lookup_arg( | 
|  | Index(i), | 
|  | precision_span, | 
|  | Precision, | 
|  | FormatArgPositionKind::Implicit, | 
|  | ))), | 
|  | parse::CountImplied => None, | 
|  | }; | 
|  |  | 
|  | let width_span = format.width_span.and_then(to_span); | 
|  | let width = match format.width { | 
|  | parse::CountIs(n) => Some(FormatCount::Literal(n)), | 
|  | parse::CountIsName(name, name_span) => Some(FormatCount::Argument(lookup_arg( | 
|  | Name(name, to_span(name_span)), | 
|  | width_span, | 
|  | Width, | 
|  | FormatArgPositionKind::Named, | 
|  | ))), | 
|  | parse::CountIsParam(i) => Some(FormatCount::Argument(lookup_arg( | 
|  | Index(i), | 
|  | width_span, | 
|  | Width, | 
|  | FormatArgPositionKind::Number, | 
|  | ))), | 
|  | parse::CountIsStar(_) => unreachable!(), | 
|  | parse::CountImplied => None, | 
|  | }; | 
|  |  | 
|  | template.push(FormatArgsPiece::Placeholder(FormatPlaceholder { | 
|  | argument, | 
|  | span, | 
|  | format_trait, | 
|  | format_options: FormatOptions { | 
|  | fill: format.fill, | 
|  | alignment, | 
|  | sign: format.sign.map(|s| match s { | 
|  | parse::Sign::Plus => FormatSign::Plus, | 
|  | parse::Sign::Minus => FormatSign::Minus, | 
|  | }), | 
|  | alternate: format.alternate, | 
|  | zero_pad: format.zero_pad, | 
|  | debug_hex: format.debug_hex.map(|s| match s { | 
|  | parse::DebugHex::Lower => FormatDebugHex::Lower, | 
|  | parse::DebugHex::Upper => FormatDebugHex::Upper, | 
|  | }), | 
|  | precision, | 
|  | width, | 
|  | }, | 
|  | })); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | if !unfinished_literal.is_empty() { | 
|  | template.push(FormatArgsPiece::Literal(Symbol::intern(&unfinished_literal))); | 
|  | } | 
|  |  | 
|  | if !invalid_refs.is_empty() { | 
|  | report_invalid_references(ecx, &invalid_refs, &template, fmt_span, &args, parser); | 
|  | } | 
|  |  | 
|  | let unused = used | 
|  | .iter() | 
|  | .enumerate() | 
|  | .filter(|&(_, used)| !used) | 
|  | .map(|(i, _)| { | 
|  | let named = matches!(args.explicit_args()[i].kind, FormatArgumentKind::Named(_)); | 
|  | (args.explicit_args()[i].expr.span, named) | 
|  | }) | 
|  | .collect::<Vec<_>>(); | 
|  |  | 
|  | let has_unused = !unused.is_empty(); | 
|  | if has_unused { | 
|  | // If there's a lot of unused arguments, | 
|  | // let's check if this format arguments looks like another syntax (printf / shell). | 
|  | let detect_foreign_fmt = unused.len() > args.explicit_args().len() / 2; | 
|  | report_missing_placeholders( | 
|  | ecx, | 
|  | unused, | 
|  | &used, | 
|  | &args, | 
|  | &pieces, | 
|  | detect_foreign_fmt, | 
|  | str_style, | 
|  | fmt_str, | 
|  | fmt_span, | 
|  | ); | 
|  | } | 
|  |  | 
|  | // Only check for unused named argument names if there are no other errors to avoid causing | 
|  | // too much noise in output errors, such as when a named argument is entirely unused. | 
|  | if invalid_refs.is_empty() && !has_unused && !unnamed_arg_after_named_arg { | 
|  | for &(index, span, used_as) in &numeric_references_to_named_arg { | 
|  | let (position_sp_to_replace, position_sp_for_msg) = match used_as { | 
|  | Placeholder(pspan) => (span, pspan), | 
|  | Precision => { | 
|  | // Strip the leading `.` for precision. | 
|  | let span = span.map(|span| span.with_lo(span.lo() + BytePos(1))); | 
|  | (span, span) | 
|  | } | 
|  | Width => (span, span), | 
|  | }; | 
|  | let arg_name = args.explicit_args()[index].kind.ident().unwrap(); | 
|  | ecx.buffered_early_lint.push(BufferedEarlyLint { | 
|  | span: Some(arg_name.span.into()), | 
|  | node_id: rustc_ast::CRATE_NODE_ID, | 
|  | lint_id: LintId::of(NAMED_ARGUMENTS_USED_POSITIONALLY), | 
|  | diagnostic: BuiltinLintDiag::NamedArgumentUsedPositionally { | 
|  | position_sp_to_replace, | 
|  | position_sp_for_msg, | 
|  | named_arg_sp: arg_name.span, | 
|  | named_arg_name: arg_name.name.to_string(), | 
|  | is_formatting_arg: matches!(used_as, Width | Precision), | 
|  | }, | 
|  | }); | 
|  | } | 
|  | } | 
|  |  | 
|  | ExpandResult::Ready(Ok(FormatArgs { | 
|  | span: fmt_span, | 
|  | template, | 
|  | arguments: args, | 
|  | uncooked_fmt_str, | 
|  | is_source_literal, | 
|  | })) | 
|  | } | 
|  |  | 
|  | fn invalid_placeholder_type_error( | 
|  | ecx: &ExtCtxt<'_>, | 
|  | ty: &str, | 
|  | ty_span: Option<Range<usize>>, | 
|  | fmt_span: Span, | 
|  | ) { | 
|  | let sp = ty_span.map(|sp| fmt_span.from_inner(InnerSpan::new(sp.start, sp.end))); | 
|  | let suggs = if let Some(sp) = sp { | 
|  | [ | 
|  | ("", "Display"), | 
|  | ("?", "Debug"), | 
|  | ("e", "LowerExp"), | 
|  | ("E", "UpperExp"), | 
|  | ("o", "Octal"), | 
|  | ("p", "Pointer"), | 
|  | ("b", "Binary"), | 
|  | ("x", "LowerHex"), | 
|  | ("X", "UpperHex"), | 
|  | ] | 
|  | .into_iter() | 
|  | .map(|(fmt, trait_name)| errors::FormatUnknownTraitSugg { span: sp, fmt, trait_name }) | 
|  | .collect() | 
|  | } else { | 
|  | vec![] | 
|  | }; | 
|  | ecx.dcx().emit_err(errors::FormatUnknownTrait { span: sp.unwrap_or(fmt_span), ty, suggs }); | 
|  | } | 
|  |  | 
|  | fn report_missing_placeholders( | 
|  | ecx: &ExtCtxt<'_>, | 
|  | unused: Vec<(Span, bool)>, | 
|  | used: &[bool], | 
|  | args: &FormatArguments, | 
|  | pieces: &[parse::Piece<'_>], | 
|  | detect_foreign_fmt: bool, | 
|  | str_style: Option<usize>, | 
|  | fmt_str: &str, | 
|  | fmt_span: Span, | 
|  | ) { | 
|  | let mut diag = if let &[(span, named)] = &unused[..] { | 
|  | ecx.dcx().create_err(errors::FormatUnusedArg { span, named }) | 
|  | } else { | 
|  | let unused_labels = | 
|  | unused.iter().map(|&(span, named)| errors::FormatUnusedArg { span, named }).collect(); | 
|  | let unused_spans = unused.iter().map(|&(span, _)| span).collect(); | 
|  | ecx.dcx().create_err(errors::FormatUnusedArgs { | 
|  | fmt: fmt_span, | 
|  | unused: unused_spans, | 
|  | unused_labels, | 
|  | }) | 
|  | }; | 
|  |  | 
|  | let placeholders = pieces | 
|  | .iter() | 
|  | .filter_map(|piece| { | 
|  | if let parse::Piece::NextArgument(argument) = piece | 
|  | && let ArgumentNamed(binding) = argument.position | 
|  | { | 
|  | let span = fmt_span.from_inner(InnerSpan::new( | 
|  | argument.position_span.start, | 
|  | argument.position_span.end, | 
|  | )); | 
|  | Some((span, binding)) | 
|  | } else { | 
|  | None | 
|  | } | 
|  | }) | 
|  | .collect::<Vec<_>>(); | 
|  |  | 
|  | if !placeholders.is_empty() { | 
|  | if let Some(new_diag) = report_redundant_format_arguments(ecx, args, used, placeholders) { | 
|  | diag.cancel(); | 
|  | new_diag.emit(); | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Used to ensure we only report translations for *one* kind of foreign format. | 
|  | let mut found_foreign = false; | 
|  |  | 
|  | // Decide if we want to look for foreign formatting directives. | 
|  | if detect_foreign_fmt { | 
|  | use super::format_foreign as foreign; | 
|  |  | 
|  | // The set of foreign substitutions we've explained. This prevents spamming the user | 
|  | // with `%d should be written as {}` over and over again. | 
|  | let mut explained = FxHashSet::default(); | 
|  |  | 
|  | macro_rules! check_foreign { | 
|  | ($kind:ident) => {{ | 
|  | let mut show_doc_note = false; | 
|  |  | 
|  | let mut suggestions = vec![]; | 
|  | // account for `"` and account for raw strings `r#` | 
|  | let padding = str_style.map(|i| i + 2).unwrap_or(1); | 
|  | for sub in foreign::$kind::iter_subs(fmt_str, padding) { | 
|  | let (trn, success) = match sub.translate() { | 
|  | Ok(trn) => (trn, true), | 
|  | Err(Some(msg)) => (msg, false), | 
|  |  | 
|  | // If it has no translation, don't call it out specifically. | 
|  | _ => continue, | 
|  | }; | 
|  |  | 
|  | let pos = sub.position(); | 
|  | if !explained.insert(sub.to_string()) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | if !found_foreign { | 
|  | found_foreign = true; | 
|  | show_doc_note = true; | 
|  | } | 
|  |  | 
|  | let sp = fmt_span.from_inner(pos); | 
|  |  | 
|  | if success { | 
|  | suggestions.push((sp, trn)); | 
|  | } else { | 
|  | diag.span_note( | 
|  | sp, | 
|  | format!("format specifiers use curly braces, and {}", trn), | 
|  | ); | 
|  | } | 
|  | } | 
|  |  | 
|  | if show_doc_note { | 
|  | diag.note(concat!( | 
|  | stringify!($kind), | 
|  | " formatting is not supported; see the documentation for `std::fmt`", | 
|  | )); | 
|  | } | 
|  | if suggestions.len() > 0 { | 
|  | diag.multipart_suggestion( | 
|  | "format specifiers use curly braces", | 
|  | suggestions, | 
|  | Applicability::MachineApplicable, | 
|  | ); | 
|  | } | 
|  | }}; | 
|  | } | 
|  |  | 
|  | check_foreign!(printf); | 
|  | if !found_foreign { | 
|  | check_foreign!(shell); | 
|  | } | 
|  | } | 
|  | if !found_foreign && unused.len() == 1 { | 
|  | diag.span_label(fmt_span, "formatting specifier missing"); | 
|  | } | 
|  |  | 
|  | diag.emit(); | 
|  | } | 
|  |  | 
|  | /// This function detects and reports unused format!() arguments that are | 
|  | /// redundant due to implicit captures (e.g. `format!("{x}", x)`). | 
|  | fn report_redundant_format_arguments<'a>( | 
|  | ecx: &ExtCtxt<'a>, | 
|  | args: &FormatArguments, | 
|  | used: &[bool], | 
|  | placeholders: Vec<(Span, &str)>, | 
|  | ) -> Option<Diag<'a>> { | 
|  | let mut fmt_arg_indices = vec![]; | 
|  | let mut args_spans = vec![]; | 
|  | let mut fmt_spans = vec![]; | 
|  |  | 
|  | for (i, unnamed_arg) in args.unnamed_args().iter().enumerate().rev() { | 
|  | let Some(ty) = unnamed_arg.expr.to_ty() else { continue }; | 
|  | let Some(argument_binding) = ty.kind.is_simple_path() else { continue }; | 
|  | let argument_binding = argument_binding.as_str(); | 
|  |  | 
|  | if used[i] { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | let matching_placeholders = placeholders | 
|  | .iter() | 
|  | .filter(|(_, inline_binding)| argument_binding == *inline_binding) | 
|  | .map(|(span, _)| span) | 
|  | .collect::<Vec<_>>(); | 
|  |  | 
|  | if !matching_placeholders.is_empty() { | 
|  | fmt_arg_indices.push(i); | 
|  | args_spans.push(unnamed_arg.expr.span); | 
|  | for span in &matching_placeholders { | 
|  | if fmt_spans.contains(*span) { | 
|  | continue; | 
|  | } | 
|  | fmt_spans.push(**span); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | if !args_spans.is_empty() { | 
|  | let multispan = MultiSpan::from(fmt_spans); | 
|  | let mut suggestion_spans = vec![]; | 
|  |  | 
|  | for (arg_span, fmt_arg_idx) in args_spans.iter().zip(fmt_arg_indices.iter()) { | 
|  | let span = if fmt_arg_idx + 1 == args.explicit_args().len() { | 
|  | *arg_span | 
|  | } else { | 
|  | arg_span.until(args.explicit_args()[*fmt_arg_idx + 1].expr.span) | 
|  | }; | 
|  |  | 
|  | suggestion_spans.push(span); | 
|  | } | 
|  |  | 
|  | let sugg = if args.named_args().len() == 0 { | 
|  | Some(errors::FormatRedundantArgsSugg { spans: suggestion_spans }) | 
|  | } else { | 
|  | None | 
|  | }; | 
|  |  | 
|  | return Some(ecx.dcx().create_err(errors::FormatRedundantArgs { | 
|  | n: args_spans.len(), | 
|  | span: MultiSpan::from(args_spans), | 
|  | note: multispan, | 
|  | sugg, | 
|  | })); | 
|  | } | 
|  |  | 
|  | None | 
|  | } | 
|  |  | 
|  | /// Handle invalid references to positional arguments. Output different | 
|  | /// errors for the case where all arguments are positional and for when | 
|  | /// there are named arguments or numbered positional arguments in the | 
|  | /// format string. | 
|  | fn report_invalid_references( | 
|  | ecx: &ExtCtxt<'_>, | 
|  | invalid_refs: &[(usize, Option<Span>, PositionUsedAs, FormatArgPositionKind)], | 
|  | template: &[FormatArgsPiece], | 
|  | fmt_span: Span, | 
|  | args: &FormatArguments, | 
|  | parser: parse::Parser<'_>, | 
|  | ) { | 
|  | let num_args_desc = match args.explicit_args().len() { | 
|  | 0 => "no arguments were given".to_string(), | 
|  | 1 => "there is 1 argument".to_string(), | 
|  | n => format!("there are {n} arguments"), | 
|  | }; | 
|  |  | 
|  | let mut e; | 
|  |  | 
|  | if template.iter().all(|piece| match piece { | 
|  | FormatArgsPiece::Placeholder(FormatPlaceholder { | 
|  | argument: FormatArgPosition { kind: FormatArgPositionKind::Number, .. }, | 
|  | .. | 
|  | }) => false, | 
|  | FormatArgsPiece::Placeholder(FormatPlaceholder { | 
|  | format_options: | 
|  | FormatOptions { | 
|  | precision: | 
|  | Some(FormatCount::Argument(FormatArgPosition { | 
|  | kind: FormatArgPositionKind::Number, | 
|  | .. | 
|  | })), | 
|  | .. | 
|  | } | 
|  | | FormatOptions { | 
|  | width: | 
|  | Some(FormatCount::Argument(FormatArgPosition { | 
|  | kind: FormatArgPositionKind::Number, | 
|  | .. | 
|  | })), | 
|  | .. | 
|  | }, | 
|  | .. | 
|  | }) => false, | 
|  | _ => true, | 
|  | }) { | 
|  | // There are no numeric positions. | 
|  | // Collect all the implicit positions: | 
|  | let mut spans = Vec::new(); | 
|  | let mut num_placeholders = 0; | 
|  | for piece in template { | 
|  | let mut placeholder = None; | 
|  | // `{arg:.*}` | 
|  | if let FormatArgsPiece::Placeholder(FormatPlaceholder { | 
|  | format_options: | 
|  | FormatOptions { | 
|  | precision: | 
|  | Some(FormatCount::Argument(FormatArgPosition { | 
|  | span, | 
|  | kind: FormatArgPositionKind::Implicit, | 
|  | .. | 
|  | })), | 
|  | .. | 
|  | }, | 
|  | .. | 
|  | }) = piece | 
|  | { | 
|  | placeholder = *span; | 
|  | num_placeholders += 1; | 
|  | } | 
|  | // `{}` | 
|  | if let FormatArgsPiece::Placeholder(FormatPlaceholder { | 
|  | argument: FormatArgPosition { kind: FormatArgPositionKind::Implicit, .. }, | 
|  | span, | 
|  | .. | 
|  | }) = piece | 
|  | { | 
|  | placeholder = *span; | 
|  | num_placeholders += 1; | 
|  | } | 
|  | // For `{:.*}`, we only push one span. | 
|  | spans.extend(placeholder); | 
|  | } | 
|  | let span = if spans.is_empty() { | 
|  | MultiSpan::from_span(fmt_span) | 
|  | } else { | 
|  | MultiSpan::from_spans(spans) | 
|  | }; | 
|  | e = ecx.dcx().create_err(errors::FormatPositionalMismatch { | 
|  | span, | 
|  | n: num_placeholders, | 
|  | desc: num_args_desc, | 
|  | highlight: SingleLabelManySpans { | 
|  | spans: args.explicit_args().iter().map(|arg| arg.expr.span).collect(), | 
|  | label: "", | 
|  | }, | 
|  | }); | 
|  | // Point out `{:.*}` placeholders: those take an extra argument. | 
|  | let mut has_precision_star = false; | 
|  | for piece in template { | 
|  | if let FormatArgsPiece::Placeholder(FormatPlaceholder { | 
|  | format_options: | 
|  | FormatOptions { | 
|  | precision: | 
|  | Some(FormatCount::Argument(FormatArgPosition { | 
|  | index, | 
|  | span: Some(span), | 
|  | kind: FormatArgPositionKind::Implicit, | 
|  | .. | 
|  | })), | 
|  | .. | 
|  | }, | 
|  | .. | 
|  | }) = piece | 
|  | { | 
|  | let (Ok(index) | Err(index)) = index; | 
|  | has_precision_star = true; | 
|  | e.span_label( | 
|  | *span, | 
|  | format!( | 
|  | "this precision flag adds an extra required argument at position {}, which is why there {} expected", | 
|  | index, | 
|  | if num_placeholders == 1 { | 
|  | "is 1 argument".to_string() | 
|  | } else { | 
|  | format!("are {num_placeholders} arguments") | 
|  | }, | 
|  | ), | 
|  | ); | 
|  | } | 
|  | } | 
|  | if has_precision_star { | 
|  | e.note("positional arguments are zero-based"); | 
|  | } | 
|  | } else { | 
|  | let mut indexes: Vec<_> = invalid_refs.iter().map(|&(index, _, _, _)| index).collect(); | 
|  | // Avoid `invalid reference to positional arguments 7 and 7 (there is 1 argument)` | 
|  | // for `println!("{7:7$}", 1);` | 
|  | indexes.sort(); | 
|  | indexes.dedup(); | 
|  | let span: MultiSpan = if !parser.is_source_literal || parser.arg_places.is_empty() { | 
|  | MultiSpan::from_span(fmt_span) | 
|  | } else { | 
|  | MultiSpan::from_spans(invalid_refs.iter().filter_map(|&(_, span, _, _)| span).collect()) | 
|  | }; | 
|  | let arg_list = format!( | 
|  | "argument{} {}", | 
|  | pluralize!(indexes.len()), | 
|  | listify(&indexes, |i: &usize| i.to_string()).unwrap_or_default() | 
|  | ); | 
|  | e = ecx.dcx().struct_span_err( | 
|  | span, | 
|  | format!("invalid reference to positional {arg_list} ({num_args_desc})"), | 
|  | ); | 
|  | e.note("positional arguments are zero-based"); | 
|  | } | 
|  |  | 
|  | if template.iter().any(|piece| match piece { | 
|  | FormatArgsPiece::Placeholder(FormatPlaceholder { format_options: f, .. }) => { | 
|  | *f != FormatOptions::default() | 
|  | } | 
|  | _ => false, | 
|  | }) { | 
|  | e.note("for information about formatting flags, visit https://doc.rust-lang.org/std/fmt/index.html"); | 
|  | } | 
|  |  | 
|  | e.emit(); | 
|  | } | 
|  |  | 
|  | fn expand_format_args_impl<'cx>( | 
|  | ecx: &'cx mut ExtCtxt<'_>, | 
|  | mut sp: Span, | 
|  | tts: TokenStream, | 
|  | nl: bool, | 
|  | ) -> MacroExpanderResult<'cx> { | 
|  | sp = ecx.with_def_site_ctxt(sp); | 
|  | ExpandResult::Ready(match parse_args(ecx, sp, tts) { | 
|  | Ok(input) => { | 
|  | let ExpandResult::Ready(mac) = make_format_args(ecx, input, nl) else { | 
|  | return ExpandResult::Retry(()); | 
|  | }; | 
|  | match mac { | 
|  | Ok(format_args) => { | 
|  | MacEager::expr(ecx.expr(sp, ExprKind::FormatArgs(P(format_args)))) | 
|  | } | 
|  | Err(guar) => MacEager::expr(DummyResult::raw_expr(sp, Some(guar))), | 
|  | } | 
|  | } | 
|  | Err(err) => { | 
|  | let guar = err.emit(); | 
|  | DummyResult::any(sp, guar) | 
|  | } | 
|  | }) | 
|  | } | 
|  |  | 
|  | pub(crate) fn expand_format_args<'cx>( | 
|  | ecx: &'cx mut ExtCtxt<'_>, | 
|  | sp: Span, | 
|  | tts: TokenStream, | 
|  | ) -> MacroExpanderResult<'cx> { | 
|  | expand_format_args_impl(ecx, sp, tts, false) | 
|  | } | 
|  |  | 
|  | pub(crate) fn expand_format_args_nl<'cx>( | 
|  | ecx: &'cx mut ExtCtxt<'_>, | 
|  | sp: Span, | 
|  | tts: TokenStream, | 
|  | ) -> MacroExpanderResult<'cx> { | 
|  | expand_format_args_impl(ecx, sp, tts, true) | 
|  | } |