mirror of
				https://github.com/rust-lang/rust.git
				synced 2025-10-31 21:16:44 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			868 lines
		
	
	
		
			33 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			868 lines
		
	
	
		
			33 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| use rustc_ast::ptr::P;
 | |
| use rustc_ast::token;
 | |
| use rustc_ast::tokenstream::TokenStream;
 | |
| use rustc_ast::{
 | |
|     Expr, ExprKind, FormatAlignment, FormatArgPosition, FormatArgPositionKind, FormatArgs,
 | |
|     FormatArgsPiece, FormatArgument, FormatArgumentKind, FormatArguments, FormatCount,
 | |
|     FormatDebugHex, FormatOptions, FormatPlaceholder, FormatSign, FormatTrait,
 | |
| };
 | |
| use rustc_data_structures::fx::FxHashSet;
 | |
| use rustc_errors::{Applicability, MultiSpan, PResult, SingleLabelManySpans};
 | |
| use rustc_expand::base::{self, *};
 | |
| use rustc_parse_format as parse;
 | |
| use rustc_span::symbol::{Ident, Symbol};
 | |
| use rustc_span::{BytePos, InnerSpan, Span};
 | |
| 
 | |
| use rustc_lint_defs::builtin::NAMED_ARGUMENTS_USED_POSITIONALLY;
 | |
| use rustc_lint_defs::{BufferedEarlyLint, BuiltinLintDiagnostics, LintId};
 | |
| 
 | |
| // 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::*;
 | |
| 
 | |
| use crate::errors;
 | |
| 
 | |
| 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: &mut 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.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(&token::Comma) {
 | |
|             if first {
 | |
|                 p.clear_expected_tokens();
 | |
|             }
 | |
| 
 | |
|             match p.expect(&token::Comma) {
 | |
|                 Err(mut err) => {
 | |
|                     match token::TokenKind::Comma.similar_tokens() {
 | |
|                         Some(tks) if tks.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();
 | |
|                         }
 | |
|                         // Otherwise stop the parsing and return the error.
 | |
|                         _ => return Err(err),
 | |
|                     }
 | |
|                 }
 | |
|                 Ok(recovered) => {
 | |
|                     assert!(recovered);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         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(&token::Eq)?;
 | |
|                 let expr = p.parse_expr()?;
 | |
|                 if let Some((_, prev)) = args.by_name(ident.name) {
 | |
|                     ecx.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() {
 | |
|                     ecx.emit_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,
 | |
| ) -> Result<FormatArgs, ()> {
 | |
|     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 (fmt_str, fmt_style, fmt_span) = match expr_to_spanned_string(ecx, efmt, msg) {
 | |
|         Ok(mut fmt) if append_newline => {
 | |
|             fmt.0 = Symbol::intern(&format!("{}\n", fmt.0));
 | |
|             fmt
 | |
|         }
 | |
|         Ok(fmt) => fmt,
 | |
|         Err(err) => {
 | |
|             if let Some((mut err, suggested)) = err {
 | |
|                 let sugg_fmt = match args.explicit_args().len() {
 | |
|                     0 => "{}".to_string(),
 | |
|                     _ => format!("{}{{}}", "{} ".repeat(args.explicit_args().len())),
 | |
|                 };
 | |
|                 if !suggested {
 | |
|                     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();
 | |
|             }
 | |
|             return Err(());
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     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 } );
 | |
|         }
 | |
|         if err.should_be_replaced_with_positional_argument {
 | |
|             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 {
 | |
|                     captured: captured_arg_span,
 | |
|                     len: args.unnamed_args().len().to_string(),
 | |
|                     span: span.shrink_to_hi(),
 | |
|                     arg,
 | |
|                 });
 | |
|             }
 | |
|         }
 | |
|         ecx.emit_err(e);
 | |
|         return Err(());
 | |
|     }
 | |
| 
 | |
|     let to_span = |inner_span: rustc_parse_format::InnerSpan| {
 | |
|         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_refences_to_named_arg = Vec::new();
 | |
| 
 | |
|     enum ArgRef<'a> {
 | |
|         Index(usize),
 | |
|         Name(&'a str, Option<Span>),
 | |
|     }
 | |
|     use ArgRef::*;
 | |
| 
 | |
|     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_refences_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)
 | |
|                         ecx.emit_err(errors::FormatNoArgNamed { span, name });
 | |
|                         DummyResult::raw_expr(span, true)
 | |
|                     };
 | |
|                     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 {
 | |
|             parse::Piece::String(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));
 | |
|                 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<_>>();
 | |
| 
 | |
|     if !unused.is_empty() {
 | |
|         // 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, 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() && ecx.sess.err_count() == 0 {
 | |
|         for &(index, span, used_as) in &numeric_refences_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: arg_name.span.into(),
 | |
|                 msg: format!("named argument `{}` is not used by name", arg_name.name).into(),
 | |
|                 node_id: rustc_ast::CRATE_NODE_ID,
 | |
|                 lint_id: LintId::of(&NAMED_ARGUMENTS_USED_POSITIONALLY),
 | |
|                 diagnostic: BuiltinLintDiagnostics::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),
 | |
|                 },
 | |
|             });
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     Ok(FormatArgs { span: fmt_span, template, arguments: args })
 | |
| }
 | |
| 
 | |
| fn invalid_placeholder_type_error(
 | |
|     ecx: &ExtCtxt<'_>,
 | |
|     ty: &str,
 | |
|     ty_span: Option<rustc_parse_format::InnerSpan>,
 | |
|     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.emit_err(errors::FormatUnknownTrait { span: sp.unwrap_or(fmt_span), ty, suggs });
 | |
| }
 | |
| 
 | |
| fn report_missing_placeholders(
 | |
|     ecx: &mut ExtCtxt<'_>,
 | |
|     unused: Vec<(Span, bool)>,
 | |
|     detect_foreign_fmt: bool,
 | |
|     str_style: Option<usize>,
 | |
|     fmt_str: &str,
 | |
|     fmt_span: Span,
 | |
| ) {
 | |
|     let mut diag = if let &[(span, named)] = &unused[..] {
 | |
|         ecx.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.create_err(errors::FormatUnusedArgs {
 | |
|             fmt: fmt_span,
 | |
|             unused: unused_spans,
 | |
|             unused_labels,
 | |
|         })
 | |
|     };
 | |
| 
 | |
|     // 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();
 | |
|                     let sub = String::from(sub.as_str());
 | |
|                     if explained.contains(&sub) {
 | |
|                         continue;
 | |
|                     }
 | |
|                     explained.insert(sub.clone());
 | |
| 
 | |
|                     if !found_foreign {
 | |
|                         found_foreign = true;
 | |
|                         show_doc_note = true;
 | |
|                     }
 | |
| 
 | |
|                     if let Some(inner_sp) = pos {
 | |
|                         let sp = fmt_span.from_inner(inner_sp);
 | |
| 
 | |
|                         if success {
 | |
|                             suggestions.push((sp, trn));
 | |
|                         } else {
 | |
|                             diag.span_note(
 | |
|                                 sp,
 | |
|                                 format!("format specifiers use curly braces, and {}", trn),
 | |
|                             );
 | |
|                         }
 | |
|                     } else {
 | |
|                         if success {
 | |
|                             diag.help(format!("`{}` should be written as `{}`", sub, trn));
 | |
|                         } else {
 | |
|                             diag.note(format!("`{}` should use curly braces, and {}", sub, 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();
 | |
| }
 | |
| 
 | |
| /// 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: &mut 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 {} arguments", n),
 | |
|     };
 | |
| 
 | |
|     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.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: "",
 | |
|                 kind: rustc_errors::LabelKind::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 {} arguments", num_placeholders)
 | |
|                         },
 | |
|                     ),
 | |
|                 );
 | |
|             }
 | |
|         }
 | |
|         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 = if let &[index] = &indexes[..] {
 | |
|             format!("argument {index}")
 | |
|         } else {
 | |
|             let tail = indexes.pop().unwrap();
 | |
|             format!(
 | |
|                 "arguments {head} and {tail}",
 | |
|                 head = indexes.into_iter().map(|i| i.to_string()).collect::<Vec<_>>().join(", ")
 | |
|             )
 | |
|         };
 | |
|         e = ecx.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,
 | |
| ) -> Box<dyn base::MacResult + 'cx> {
 | |
|     sp = ecx.with_def_site_ctxt(sp);
 | |
|     match parse_args(ecx, sp, tts) {
 | |
|         Ok(input) => {
 | |
|             if let Ok(format_args) = make_format_args(ecx, input, nl) {
 | |
|                 MacEager::expr(ecx.expr(sp, ExprKind::FormatArgs(P(format_args))))
 | |
|             } else {
 | |
|                 MacEager::expr(DummyResult::raw_expr(sp, true))
 | |
|             }
 | |
|         }
 | |
|         Err(mut err) => {
 | |
|             err.emit();
 | |
|             DummyResult::any(sp)
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| pub fn expand_format_args<'cx>(
 | |
|     ecx: &'cx mut ExtCtxt<'_>,
 | |
|     sp: Span,
 | |
|     tts: TokenStream,
 | |
| ) -> Box<dyn base::MacResult + 'cx> {
 | |
|     expand_format_args_impl(ecx, sp, tts, false)
 | |
| }
 | |
| 
 | |
| pub fn expand_format_args_nl<'cx>(
 | |
|     ecx: &'cx mut ExtCtxt<'_>,
 | |
|     sp: Span,
 | |
|     tts: TokenStream,
 | |
| ) -> Box<dyn base::MacResult + 'cx> {
 | |
|     expand_format_args_impl(ecx, sp, tts, true)
 | |
| }
 | 
