diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-07 05:48:48 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-07 05:48:48 +0000 |
commit | ef24de24a82fe681581cc130f342363c47c0969a (patch) | |
tree | 0d494f7e1a38b95c92426f58fe6eaa877303a86c /compiler/rustc_builtin_macros/src/format.rs | |
parent | Releasing progress-linux version 1.74.1+dfsg1-1~progress7.99u1. (diff) | |
download | rustc-ef24de24a82fe681581cc130f342363c47c0969a.tar.xz rustc-ef24de24a82fe681581cc130f342363c47c0969a.zip |
Merging upstream version 1.75.0+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'compiler/rustc_builtin_macros/src/format.rs')
-rw-r--r-- | compiler/rustc_builtin_macros/src/format.rs | 131 |
1 files changed, 122 insertions, 9 deletions
diff --git a/compiler/rustc_builtin_macros/src/format.rs b/compiler/rustc_builtin_macros/src/format.rs index 8397b5e42..214fed8e2 100644 --- a/compiler/rustc_builtin_macros/src/format.rs +++ b/compiler/rustc_builtin_macros/src/format.rs @@ -1,3 +1,4 @@ +use parse::Position::ArgumentNamed; use rustc_ast::ptr::P; use rustc_ast::tokenstream::TokenStream; use rustc_ast::{token, StmtKind}; @@ -7,7 +8,9 @@ use rustc_ast::{ FormatDebugHex, FormatOptions, FormatPlaceholder, FormatSign, FormatTrait, }; use rustc_data_structures::fx::FxHashSet; -use rustc_errors::{Applicability, MultiSpan, PResult, SingleLabelManySpans}; +use rustc_errors::{ + Applicability, DiagnosticBuilder, ErrorGuaranteed, MultiSpan, PResult, SingleLabelManySpans, +}; use rustc_expand::base::{self, *}; use rustc_parse_format as parse; use rustc_span::symbol::{Ident, Symbol}; @@ -73,7 +76,9 @@ fn parse_args<'a>(ecx: &mut ExtCtxt<'a>, sp: Span, tts: TokenStream) -> PResult< let first_token = &p.token; - let fmtstr = if let token::Literal(lit) = first_token.kind && matches!(lit.kind, token::Str | token::StrRaw(_)) { + 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. @@ -176,7 +181,7 @@ fn make_format_args( && block.stmts.len() == 1 && let StmtKind::Expr(expr) = &block.stmts[0].kind && let ExprKind::Path(None, path) = &expr.kind - && path.is_potential_trivial_const_arg() + && path.is_potential_trivial_const_arg() { err.multipart_suggestion( "quote your inlined format argument to use as string literal", @@ -184,7 +189,7 @@ fn make_format_args( (unexpanded_fmt_span.shrink_to_hi(), "\"".to_string()), (unexpanded_fmt_span.shrink_to_lo(), "\"".to_string()), ], - Applicability::MaybeIncorrect, + Applicability::MaybeIncorrect, ); } else { let sugg_fmt = match args.explicit_args().len() { @@ -257,8 +262,13 @@ fn make_format_args( 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 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 => {} @@ -357,8 +367,8 @@ fn make_format_args( let mut unfinished_literal = String::new(); let mut placeholder_index = 0; - for piece in pieces { - match piece { + for piece in &pieces { + match *piece { parse::Piece::String(s) => { unfinished_literal.push_str(s); } @@ -506,7 +516,17 @@ fn make_format_args( // 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); + 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 @@ -573,6 +593,9 @@ fn invalid_placeholder_type_error( fn report_missing_placeholders( ecx: &mut ExtCtxt<'_>, unused: Vec<(Span, bool)>, + used: &[bool], + args: &FormatArguments, + pieces: &[parse::Piece<'_>], detect_foreign_fmt: bool, str_style: Option<usize>, fmt_str: &str, @@ -591,6 +614,26 @@ fn report_missing_placeholders( }) }; + 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(mut 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; @@ -678,6 +721,76 @@ fn report_missing_placeholders( 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: &mut ExtCtxt<'a>, + args: &FormatArguments, + used: &[bool], + placeholders: Vec<(Span, &str)>, +) -> Option<DiagnosticBuilder<'a, ErrorGuaranteed>> { + 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.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 |