diff --git a/compiler/rustc_builtin_macros/src/format.rs b/compiler/rustc_builtin_macros/src/format.rs index 2100487107517..b15e2d084ef7f 100644 --- a/compiler/rustc_builtin_macros/src/format.rs +++ b/compiler/rustc_builtin_macros/src/format.rs @@ -1,271 +1,42 @@ -use ArgumentType::*; -use Position::*; - -use rustc_ast as ast; use rustc_ast::ptr::P; +use rustc_ast::token; use rustc_ast::tokenstream::TokenStream; -use rustc_ast::visit::{self, Visitor}; -use rustc_ast::{token, BlockCheckMode, UnsafeSource}; -use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_ast::Expr; +use rustc_data_structures::fx::FxHashSet; use rustc_errors::{pluralize, Applicability, MultiSpan, PResult}; use rustc_expand::base::{self, *}; use rustc_parse_format as parse; -use rustc_span::symbol::{sym, Ident, Symbol}; +use rustc_span::symbol::{Ident, Symbol}; use rustc_span::{BytePos, InnerSpan, Span}; -use smallvec::SmallVec; use rustc_lint_defs::builtin::NAMED_ARGUMENTS_USED_POSITIONALLY; use rustc_lint_defs::{BufferedEarlyLint, BuiltinLintDiagnostics, LintId}; -use rustc_parse_format::Count; -use std::borrow::Cow; -use std::collections::hash_map::Entry; - -#[derive(PartialEq)] -enum ArgumentType { - Placeholder(&'static str), - Count, -} - -enum Position { - Exact(usize), - Capture(usize), - Named(Symbol, InnerSpan), -} - -/// Indicates how positional named argument (i.e. an named argument which is used by position -/// instead of by name) is used in format string -/// * `Arg` is the actual argument to print -/// * `Width` is width format argument -/// * `Precision` is precion format argument -/// Example: `{Arg:Width$.Precision$} -#[derive(Debug, Eq, PartialEq)] -enum PositionalNamedArgType { - Arg, - Width, - Precision, -} - -/// Contains information necessary to create a lint for a positional named argument -#[derive(Debug)] -struct PositionalNamedArg { - ty: PositionalNamedArgType, - /// The piece of the using this argument (multiple pieces can use the same argument) - cur_piece: usize, - /// The InnerSpan for in the string to be replaced with the named argument - /// This will be None when the position is implicit - inner_span_to_replace: Option, - /// The name to use instead of the position - replacement: Symbol, - /// The span for the positional named argument (so the lint can point a message to it) - positional_named_arg_span: Span, - has_formatting: bool, -} - -impl PositionalNamedArg { - /// Determines: - /// 1) span to be replaced with the name of the named argument and - /// 2) span to be underlined for error messages - fn get_positional_arg_spans(&self, cx: &Context<'_, '_>) -> (Option, Option) { - if let Some(inner_span) = &self.inner_span_to_replace { - let span = - cx.fmtsp.from_inner(InnerSpan { start: inner_span.start, end: inner_span.end }); - (Some(span), Some(span)) - } else if self.ty == PositionalNamedArgType::Arg { - // In the case of a named argument whose position is implicit, if the argument *has* - // formatting, there will not be a span to replace. Instead, we insert the name after - // the `{`, which will be the first character of arg_span. If the argument does *not* - // have formatting, there may or may not be a span to replace. This is because - // whitespace is allowed in arguments without formatting (such as `format!("{ }", 1);`) - // but is not allowed in arguments with formatting (an error will be generated in cases - // like `format!("{ :1.1}", 1.0f32);`. - // For the message span, if there is formatting, we want to use the opening `{` and the - // next character, which will the `:` indicating the start of formatting. If there is - // not any formatting, we want to underline the entire span. - cx.arg_spans.get(self.cur_piece).map_or((None, None), |arg_span| { - if self.has_formatting { - ( - Some(arg_span.with_lo(arg_span.lo() + BytePos(1)).shrink_to_lo()), - Some(arg_span.with_hi(arg_span.lo() + BytePos(2))), - ) - } else { - let replace_start = arg_span.lo() + BytePos(1); - let replace_end = arg_span.hi() - BytePos(1); - let to_replace = arg_span.with_lo(replace_start).with_hi(replace_end); - (Some(to_replace), Some(*arg_span)) - } - }) - } else { - (None, None) - } - } -} - -/// Encapsulates all the named arguments that have been used positionally -#[derive(Debug)] -struct PositionalNamedArgsLint { - positional_named_args: Vec, -} - -impl PositionalNamedArgsLint { - /// For a given positional argument, check if the index is for a named argument. - /// - /// Since positional arguments are required to come before named arguments, if the positional - /// index is greater than or equal to the start of named arguments, we know it's a named - /// argument used positionally. - /// - /// Example: - /// println!("{} {} {2}", 0, a=1, b=2); - /// - /// In this case, the first piece (`{}`) would be ArgumentImplicitlyIs with an index of 0. The - /// total number of arguments is 3 and the number of named arguments is 2, so the start of named - /// arguments is index 1. Therefore, the index of 0 is okay. - /// - /// The second piece (`{}`) would be ArgumentImplicitlyIs with an index of 1, which is the start - /// of named arguments, and so we should add a lint to use the named argument `a`. - /// - /// The third piece (`{2}`) would be ArgumentIs with an index of 2, which is greater than the - /// start of named arguments, and so we should add a lint to use the named argument `b`. - /// - /// This same check also works for width and precision formatting when either or both are - /// CountIsParam, which contains an index into the arguments. - fn maybe_add_positional_named_arg( - &mut self, - arg: Option<&FormatArg>, - ty: PositionalNamedArgType, - cur_piece: usize, - inner_span_to_replace: Option, - has_formatting: bool, - ) { - if let Some(arg) = arg { - if let Some(name) = arg.name { - self.push(name, ty, cur_piece, inner_span_to_replace, has_formatting) - } - } - } - /// Construct a PositionalNamedArg struct and push it into the vec of positional - /// named arguments. - fn push( - &mut self, - arg_name: Ident, - ty: PositionalNamedArgType, - cur_piece: usize, - inner_span_to_replace: Option, - has_formatting: bool, - ) { - // In FormatSpec, `precision_span` starts at the leading `.`, which we want to keep in - // the lint suggestion, so increment `start` by 1 when `PositionalArgumentType` is - // `Precision`. - let inner_span_to_replace = if ty == PositionalNamedArgType::Precision { - inner_span_to_replace - .map(|is| rustc_parse_format::InnerSpan { start: is.start + 1, end: is.end }) - } else { - inner_span_to_replace - }; - self.positional_named_args.push(PositionalNamedArg { - ty, - cur_piece, - inner_span_to_replace, - replacement: arg_name.name, - positional_named_arg_span: arg_name.span, - has_formatting, - }); - } -} - -struct Context<'a, 'b> { - ecx: &'a mut ExtCtxt<'b>, - /// The macro's call site. References to unstable formatting internals must - /// use this span to pass the stability checker. - macsp: Span, - /// The span of the format string literal. - fmtsp: Span, - - /// List of parsed argument expressions. - /// Named expressions are resolved early, and are appended to the end of - /// argument expressions. - /// - /// Example showing the various data structures in motion: - /// - /// * Original: `"{foo:o} {:o} {foo:x} {0:x} {1:o} {:x} {1:x} {0:o}"` - /// * Implicit argument resolution: `"{foo:o} {0:o} {foo:x} {0:x} {1:o} {1:x} {1:x} {0:o}"` - /// * Name resolution: `"{2:o} {0:o} {2:x} {0:x} {1:o} {1:x} {1:x} {0:o}"` - /// * `arg_types` (in JSON): `[[0, 1, 0], [0, 1, 1], [0, 1]]` - /// * `arg_unique_types` (in simplified JSON): `[["o", "x"], ["o", "x"], ["o", "x"]]` - /// * `names` (in JSON): `{"foo": 2}` - args: Vec, - /// The number of arguments that were added by implicit capturing. - num_captured_args: usize, - /// Placeholder slot numbers indexed by argument. - arg_types: Vec>, - /// Unique format specs seen for each argument. - arg_unique_types: Vec>, - /// Map from named arguments to their resolved indices. - names: FxHashMap, - - /// The latest consecutive literal strings, or empty if there weren't any. - literal: String, +mod ast; +use ast::*; - /// Collection of the compiled `rt::Argument` structures - pieces: Vec>, - /// Collection of string literals - str_pieces: Vec>, - /// Stays `true` if all formatting parameters are default (as in "{}{}"). - all_pieces_simple: bool, +mod expand; +use expand::expand_parsed_format_args; - /// Mapping between positional argument references and indices into the - /// final generated static argument array. We record the starting indices - /// corresponding to each positional argument, and number of references - /// consumed so far for each argument, to facilitate correct `Position` - /// mapping in `build_piece`. In effect this can be seen as a "flattened" - /// version of `arg_unique_types`. - /// - /// Again with the example described above in docstring for `args`: - /// - /// * `arg_index_map` (in JSON): `[[0, 1, 0], [2, 3, 3], [4, 5]]` - arg_index_map: Vec>, +// 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` structure. +// 3. Finally, `expand_parsed_format_args` will turn that `FormatArgs` structure +// into the expression that the macro expands to. - /// Starting offset of count argument slots. - count_args_index_offset: usize, +// See format/ast.rs for the FormatArgs structure and glossary. - /// Count argument slots and tracking data structures. - /// Count arguments are separately tracked for de-duplication in case - /// multiple references are made to one argument. For example, in this - /// format string: - /// - /// * Original: `"{:.*} {:.foo$} {1:.*} {:.0$}"` - /// * Implicit argument resolution: `"{1:.0$} {2:.foo$} {1:.3$} {4:.0$}"` - /// * Name resolution: `"{1:.0$} {2:.5$} {1:.3$} {4:.0$}"` - /// * `count_positions` (in JSON): `{0: 0, 5: 1, 3: 2}` - /// * `count_args`: `vec![0, 5, 3]` - count_args: Vec, - /// Relative slot numbers for count arguments. - count_positions: FxHashMap, - /// Number of count slots assigned. - count_positions_count: usize, - - /// Current position of the implicit positional arg pointer, as if it - /// still existed in this phase of processing. - /// Used only for `all_pieces_simple` tracking in `build_piece`. - curarg: usize, - /// Current piece being evaluated, used for error reporting. - curpiece: usize, - /// Keep track of invalid references to positional arguments. - invalid_refs: Vec<(usize, usize)>, - /// Spans of all the formatting arguments, in order. - arg_spans: Vec, - /// All the formatting arguments that have formatting flags set, in order for diagnostics. - arg_with_formatting: Vec>, - - /// Whether this format string came from a string literal, as opposed to a macro. - is_literal: bool, - unused_names_lint: PositionalNamedArgsLint, -} - -pub struct FormatArg { - expr: P, - name: Option, +// 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), + Precision, + Width, } +use PositionUsedAs::*; /// 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! @@ -274,15 +45,14 @@ pub struct FormatArg { /// If parsing succeeds, the return value is: /// /// ```text -/// Some((fmtstr, parsed arguments, index map for named arguments)) +/// Ok((fmtstr, parsed arguments)) /// ``` fn parse_args<'a>( ecx: &mut ExtCtxt<'a>, sp: Span, tts: TokenStream, -) -> PResult<'a, (P, Vec, FxHashMap)> { - let mut args = Vec::::new(); - let mut names = FxHashMap::::default(); +) -> PResult<'a, (P, FormatArguments)> { + let mut args = FormatArguments::new(); let mut p = ecx.new_parser_from_tts(tts); @@ -311,7 +81,6 @@ fn parse_args<'a>( }; let mut first = true; - let mut named = false; while p.token != token::Eof { if !p.eat(&token::Comma) { @@ -343,879 +112,54 @@ fn parse_args<'a>( } // accept trailing commas match p.token.ident() { Some((ident, _)) if p.look_ahead(1, |t| *t == token::Eq) => { - named = true; p.bump(); p.expect(&token::Eq)?; - let e = p.parse_expr()?; - if let Some(&prev) = names.get(&ident.name) { - ecx.struct_span_err(e.span, &format!("duplicate argument named `{}`", ident)) - .span_label(args[prev].expr.span, "previously here") - .span_label(e.span, "duplicate argument") - .emit(); + let expr = p.parse_expr()?; + if let Some((_, prev)) = args.by_name(ident.name) { + ecx.struct_span_err( + ident.span, + &format!("duplicate argument named `{}`", ident), + ) + .span_label(prev.kind.ident().unwrap().span, "previously here") + .span_label(ident.span, "duplicate argument") + .emit(); continue; } - - // Resolve names into slots early. - // Since all the positional args are already seen at this point - // if the input is valid, we can simply append to the positional - // args. And remember the names. - let slot = args.len(); - names.insert(ident.name, slot); - args.push(FormatArg { expr: e, name: Some(ident) }); + args.add(FormatArgument { kind: FormatArgumentKind::Named(ident), expr }); } _ => { - let e = p.parse_expr()?; - if named { + let expr = p.parse_expr()?; + if !args.named_args().is_empty() { let mut err = ecx.struct_span_err( - e.span, + expr.span, "positional arguments cannot follow named arguments", ); - err.span_label(e.span, "positional arguments must be before named arguments"); - for &pos in names.values() { - err.span_label(args[pos].expr.span, "named argument"); + err.span_label( + expr.span, + "positional arguments must be before named arguments", + ); + for arg in args.named_args() { + if let Some(name) = arg.kind.ident() { + err.span_label(name.span.to(arg.expr.span), "named argument"); + } } err.emit(); } - args.push(FormatArg { expr: e, name: None }); + args.add(FormatArgument { kind: FormatArgumentKind::Normal, expr }); } } } - Ok((fmtstr, args, names)) + Ok((fmtstr, args)) } -impl<'a, 'b> Context<'a, 'b> { - /// The number of arguments that were explicitly given. - fn num_args(&self) -> usize { - self.args.len() - self.num_captured_args - } - - fn resolve_name_inplace(&mut self, p: &mut parse::Piece<'_>) { - // NOTE: the `unwrap_or` branch is needed in case of invalid format - // arguments, e.g., `format_args!("{foo}")`. - let lookup = |s: &str| self.names.get(&Symbol::intern(s)).copied().unwrap_or(0); - - match *p { - parse::String(_) => {} - parse::NextArgument(ref mut arg) => { - if let parse::ArgumentNamed(s) = arg.position { - arg.position = parse::ArgumentIs(lookup(s)); - } - if let parse::CountIsName(s, _) = arg.format.width { - arg.format.width = parse::CountIsParam(lookup(s)); - } - if let parse::CountIsName(s, _) = arg.format.precision { - arg.format.precision = parse::CountIsParam(lookup(s)); - } - } - } - } - - /// Verifies one piece of a parse string, and remembers it if valid. - /// All errors are not emitted as fatal so we can continue giving errors - /// about this and possibly other format strings. - fn verify_piece(&mut self, p: &parse::Piece<'a>) { - match *p { - parse::String(..) => {} - parse::NextArgument(ref arg) => { - // width/precision first, if they have implicit positional - // parameters it makes more sense to consume them first. - self.verify_count( - arg.format.width, - &arg.format.width_span, - PositionalNamedArgType::Width, - ); - self.verify_count( - arg.format.precision, - &arg.format.precision_span, - PositionalNamedArgType::Precision, - ); - - let has_precision = arg.format.precision != Count::CountImplied; - let has_width = arg.format.width != Count::CountImplied; - - if has_precision || has_width { - // push before named params are resolved to aid diagnostics - self.arg_with_formatting.push(arg.format); - } - - // argument second, if it's an implicit positional parameter - // it's written second, so it should come after width/precision. - let pos = match arg.position { - parse::ArgumentIs(i) => { - self.unused_names_lint.maybe_add_positional_named_arg( - self.args.get(i), - PositionalNamedArgType::Arg, - self.curpiece, - Some(arg.position_span), - has_precision || has_width, - ); - - Exact(i) - } - parse::ArgumentImplicitlyIs(i) => { - self.unused_names_lint.maybe_add_positional_named_arg( - self.args.get(i), - PositionalNamedArgType::Arg, - self.curpiece, - None, - has_precision || has_width, - ); - Exact(i) - } - parse::ArgumentNamed(s) => { - let symbol = Symbol::intern(s); - let span = arg.position_span; - Named(symbol, InnerSpan::new(span.start, span.end)) - } - }; - - let ty = Placeholder(match arg.format.ty { - "" => "Display", - "?" => "Debug", - "e" => "LowerExp", - "E" => "UpperExp", - "o" => "Octal", - "p" => "Pointer", - "b" => "Binary", - "x" => "LowerHex", - "X" => "UpperHex", - _ => { - let fmtsp = self.fmtsp; - let sp = arg - .format - .ty_span - .map(|sp| fmtsp.from_inner(InnerSpan::new(sp.start, sp.end))); - let mut err = self.ecx.struct_span_err( - sp.unwrap_or(fmtsp), - &format!("unknown format trait `{}`", arg.format.ty), - ); - err.note( - "the only appropriate formatting traits are:\n\ - - ``, which uses the `Display` trait\n\ - - `?`, which uses the `Debug` trait\n\ - - `e`, which uses the `LowerExp` trait\n\ - - `E`, which uses the `UpperExp` trait\n\ - - `o`, which uses the `Octal` trait\n\ - - `p`, which uses the `Pointer` trait\n\ - - `b`, which uses the `Binary` trait\n\ - - `x`, which uses the `LowerHex` trait\n\ - - `X`, which uses the `UpperHex` trait", - ); - if let Some(sp) = sp { - for (fmt, name) in &[ - ("", "Display"), - ("?", "Debug"), - ("e", "LowerExp"), - ("E", "UpperExp"), - ("o", "Octal"), - ("p", "Pointer"), - ("b", "Binary"), - ("x", "LowerHex"), - ("X", "UpperHex"), - ] { - // FIXME: rustfix (`run-rustfix`) fails to apply suggestions. - // > "Cannot replace slice of data that was already replaced" - err.tool_only_span_suggestion( - sp, - &format!("use the `{}` trait", name), - *fmt, - Applicability::MaybeIncorrect, - ); - } - } - err.emit(); - "" - } - }); - self.verify_arg_type(pos, ty); - self.curpiece += 1; - } - } - } - - fn verify_count( - &mut self, - c: parse::Count<'_>, - inner_span: &Option, - named_arg_type: PositionalNamedArgType, - ) { - match c { - parse::CountImplied | parse::CountIs(..) => {} - parse::CountIsParam(i) | parse::CountIsStar(i) => { - self.unused_names_lint.maybe_add_positional_named_arg( - self.args.get(i), - named_arg_type, - self.curpiece, - *inner_span, - true, - ); - self.verify_arg_type(Exact(i), Count); - } - parse::CountIsName(s, span) => { - self.verify_arg_type( - Named(Symbol::intern(s), InnerSpan::new(span.start, span.end)), - Count, - ); - } - } - } - - fn describe_num_args(&self) -> Cow<'_, str> { - match self.num_args() { - 0 => "no arguments were given".into(), - 1 => "there is 1 argument".into(), - x => format!("there are {} arguments", x).into(), - } - } - - /// 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(&self, numbered_position_args: bool) { - let mut e; - let sp = if !self.arg_spans.is_empty() { - // Point at the formatting arguments. - MultiSpan::from_spans(self.arg_spans.clone()) - } else { - MultiSpan::from_span(self.fmtsp) - }; - let refs = - self.invalid_refs.iter().map(|(r, pos)| (r.to_string(), self.arg_spans.get(*pos))); - - let mut zero_based_note = false; - - let count = self.pieces.len() - + self - .arg_with_formatting - .iter() - .filter(|fmt| matches!(fmt.precision, parse::CountIsStar(_))) - .count(); - if self.names.is_empty() && !numbered_position_args && count != self.num_args() { - e = self.ecx.struct_span_err( - sp, - &format!( - "{} positional argument{} in format string, but {}", - count, - pluralize!(count), - self.describe_num_args(), - ), - ); - for arg in &self.args { - // Point at the arguments that will be formatted. - e.span_label(arg.expr.span, ""); - } - } else { - let (mut refs, spans): (Vec<_>, Vec<_>) = refs.unzip(); - // Avoid `invalid reference to positional arguments 7 and 7 (there is 1 argument)` - // for `println!("{7:7$}", 1);` - refs.sort(); - refs.dedup(); - let spans: Vec<_> = spans.into_iter().filter_map(|sp| sp.copied()).collect(); - let sp = if self.arg_spans.is_empty() || spans.is_empty() { - MultiSpan::from_span(self.fmtsp) - } else { - MultiSpan::from_spans(spans) - }; - let arg_list = if refs.len() == 1 { - format!("argument {}", refs[0]) - } else { - let reg = refs.pop().unwrap(); - format!("arguments {head} and {tail}", head = refs.join(", "), tail = reg) - }; - - e = self.ecx.struct_span_err( - sp, - &format!( - "invalid reference to positional {} ({})", - arg_list, - self.describe_num_args() - ), - ); - zero_based_note = true; - }; - - for fmt in &self.arg_with_formatting { - if let Some(span) = fmt.precision_span { - let span = self.fmtsp.from_inner(InnerSpan::new(span.start, span.end)); - match fmt.precision { - parse::CountIsParam(pos) if pos >= self.num_args() => { - e.span_label( - span, - &format!( - "this precision flag expects an `usize` argument at position {}, \ - but {}", - pos, - self.describe_num_args(), - ), - ); - zero_based_note = true; - } - parse::CountIsStar(pos) => { - let count = self.pieces.len() - + self - .arg_with_formatting - .iter() - .filter(|fmt| matches!(fmt.precision, parse::CountIsStar(_))) - .count(); - e.span_label( - span, - &format!( - "this precision flag adds an extra required argument at position {}, \ - which is why there {} expected", - pos, - if count == 1 { - "is 1 argument".to_string() - } else { - format!("are {} arguments", count) - }, - ), - ); - if let Some(arg) = self.args.get(pos) { - e.span_label( - arg.expr.span, - "this parameter corresponds to the precision flag", - ); - } - zero_based_note = true; - } - _ => {} - } - } - if let Some(span) = fmt.width_span { - let span = self.fmtsp.from_inner(InnerSpan::new(span.start, span.end)); - match fmt.width { - parse::CountIsParam(pos) if pos >= self.num_args() => { - e.span_label( - span, - &format!( - "this width flag expects an `usize` argument at position {}, \ - but {}", - pos, - self.describe_num_args(), - ), - ); - zero_based_note = true; - } - _ => {} - } - } - } - if zero_based_note { - e.note("positional arguments are zero-based"); - } - if !self.arg_with_formatting.is_empty() { - e.note( - "for information about formatting flags, visit \ - https://doc.rust-lang.org/std/fmt/index.html", - ); - } - - e.emit(); - } - - /// Actually verifies and tracks a given format placeholder - /// (a.k.a. argument). - fn verify_arg_type(&mut self, arg: Position, ty: ArgumentType) { - if let Exact(arg) = arg { - if arg >= self.num_args() { - self.invalid_refs.push((arg, self.curpiece)); - return; - } - } - - match arg { - Exact(arg) | Capture(arg) => { - match ty { - Placeholder(_) => { - // record every (position, type) combination only once - let seen_ty = &mut self.arg_unique_types[arg]; - let i = seen_ty.iter().position(|x| *x == ty).unwrap_or_else(|| { - let i = seen_ty.len(); - seen_ty.push(ty); - i - }); - self.arg_types[arg].push(i); - } - Count => { - if let Entry::Vacant(e) = self.count_positions.entry(arg) { - let i = self.count_positions_count; - e.insert(i); - self.count_args.push(arg); - self.count_positions_count += 1; - } - } - } - } - - Named(name, span) => { - match self.names.get(&name) { - Some(&idx) => { - // Treat as positional arg. - self.verify_arg_type(Capture(idx), ty) - } - None => { - // For the moment capturing variables from format strings expanded from macros is - // disabled (see RFC #2795) - if self.is_literal { - // Treat this name as a variable to capture from the surrounding scope - let idx = self.args.len(); - self.arg_types.push(Vec::new()); - self.arg_unique_types.push(Vec::new()); - let span = if self.is_literal { - self.fmtsp.from_inner(span) - } else { - self.fmtsp - }; - self.num_captured_args += 1; - self.args.push(FormatArg { - expr: self.ecx.expr_ident(span, Ident::new(name, span)), - name: Some(Ident::new(name, span)), - }); - self.names.insert(name, idx); - self.verify_arg_type(Capture(idx), ty) - } else { - let msg = format!("there is no argument named `{}`", name); - let sp = if self.is_literal { - self.fmtsp.from_inner(span) - } else { - self.fmtsp - }; - let mut err = self.ecx.struct_span_err(sp, &msg); - - err.note(&format!( - "did you intend to capture a variable `{}` from \ - the surrounding scope?", - name - )); - err.note( - "to avoid ambiguity, `format_args!` cannot capture variables \ - when the format string is expanded from a macro", - ); - - err.emit(); - } - } - } - } - } - } - - /// Builds the mapping between format placeholders and argument objects. - fn build_index_map(&mut self) { - // NOTE: Keep the ordering the same as `into_expr`'s expansion would do! - let args_len = self.args.len(); - self.arg_index_map.reserve(args_len); - - let mut sofar = 0usize; - - // Map the arguments - for i in 0..args_len { - let arg_types = &self.arg_types[i]; - let arg_offsets = arg_types.iter().map(|offset| sofar + *offset).collect::>(); - self.arg_index_map.push(arg_offsets); - sofar += self.arg_unique_types[i].len(); - } - - // Record starting index for counts, which appear just after arguments - self.count_args_index_offset = sofar; - } - - fn rtpath(ecx: &ExtCtxt<'_>, s: Symbol) -> Vec { - ecx.std_path(&[sym::fmt, sym::rt, sym::v1, s]) - } - - fn build_count(&self, c: parse::Count<'_>) -> P { - let sp = self.macsp; - let count = |c, arg| { - let mut path = Context::rtpath(self.ecx, sym::Count); - path.push(Ident::new(c, sp)); - match arg { - Some(arg) => self.ecx.expr_call_global(sp, path, vec![arg]), - None => self.ecx.expr_path(self.ecx.path_global(sp, path)), - } - }; - match c { - parse::CountIs(i) => count(sym::Is, Some(self.ecx.expr_usize(sp, i))), - parse::CountIsParam(i) | parse::CountIsStar(i) => { - // This needs mapping too, as `i` is referring to a macro - // argument. If `i` is not found in `count_positions` then - // the error had already been emitted elsewhere. - let i = self.count_positions.get(&i).cloned().unwrap_or(0) - + self.count_args_index_offset; - count(sym::Param, Some(self.ecx.expr_usize(sp, i))) - } - parse::CountImplied => count(sym::Implied, None), - // should never be the case, names are already resolved - parse::CountIsName(..) => panic!("should never happen"), - } - } - - /// Build a literal expression from the accumulated string literals - fn build_literal_string(&mut self) -> P { - let sp = self.fmtsp; - let s = Symbol::intern(&self.literal); - self.literal.clear(); - self.ecx.expr_str(sp, s) - } - - /// Builds a static `rt::Argument` from a `parse::Piece` or append - /// to the `literal` string. - fn build_piece( - &mut self, - piece: &parse::Piece<'a>, - arg_index_consumed: &mut Vec, - ) -> Option> { - let sp = self.macsp; - match *piece { - parse::String(s) => { - self.literal.push_str(s); - None - } - parse::NextArgument(ref arg) => { - // Build the position - let pos = { - match arg.position { - parse::ArgumentIs(i, ..) | parse::ArgumentImplicitlyIs(i) => { - // Map to index in final generated argument array - // in case of multiple types specified - let arg_idx = match arg_index_consumed.get_mut(i) { - None => 0, // error already emitted elsewhere - Some(offset) => { - let idx_map = &self.arg_index_map[i]; - // unwrap_or branch: error already emitted elsewhere - let arg_idx = *idx_map.get(*offset).unwrap_or(&0); - *offset += 1; - arg_idx - } - }; - self.ecx.expr_usize(sp, arg_idx) - } - - // should never be the case, because names are already - // resolved. - parse::ArgumentNamed(..) => panic!("should never happen"), - } - }; - - let simple_arg = parse::Argument { - position: { - // We don't have ArgumentNext any more, so we have to - // track the current argument ourselves. - let i = self.curarg; - self.curarg += 1; - parse::ArgumentIs(i) - }, - position_span: arg.position_span, - format: parse::FormatSpec { - fill: None, - align: parse::AlignUnknown, - flags: 0, - precision: parse::CountImplied, - precision_span: arg.format.precision_span, - width: parse::CountImplied, - width_span: arg.format.width_span, - ty: arg.format.ty, - ty_span: arg.format.ty_span, - }, - }; - - let fill = arg.format.fill.unwrap_or(' '); - let pos_simple = arg.position.index() == simple_arg.position.index(); - - if !pos_simple || arg.format != simple_arg.format { - self.all_pieces_simple = false; - } - - // Build the format - let fill = self.ecx.expr_char(sp, fill); - let align = |name| { - let mut p = Context::rtpath(self.ecx, sym::Alignment); - p.push(Ident::new(name, sp)); - self.ecx.path_global(sp, p) - }; - let align = match arg.format.align { - parse::AlignLeft => align(sym::Left), - parse::AlignRight => align(sym::Right), - parse::AlignCenter => align(sym::Center), - parse::AlignUnknown => align(sym::Unknown), - }; - let align = self.ecx.expr_path(align); - let flags = self.ecx.expr_u32(sp, arg.format.flags); - let prec = self.build_count(arg.format.precision); - let width = self.build_count(arg.format.width); - let path = self.ecx.path_global(sp, Context::rtpath(self.ecx, sym::FormatSpec)); - let fmt = self.ecx.expr_struct( - sp, - path, - vec![ - self.ecx.field_imm(sp, Ident::new(sym::fill, sp), fill), - self.ecx.field_imm(sp, Ident::new(sym::align, sp), align), - self.ecx.field_imm(sp, Ident::new(sym::flags, sp), flags), - self.ecx.field_imm(sp, Ident::new(sym::precision, sp), prec), - self.ecx.field_imm(sp, Ident::new(sym::width, sp), width), - ], - ); - - let path = self.ecx.path_global(sp, Context::rtpath(self.ecx, sym::Argument)); - Some(self.ecx.expr_struct( - sp, - path, - vec![ - self.ecx.field_imm(sp, Ident::new(sym::position, sp), pos), - self.ecx.field_imm(sp, Ident::new(sym::format, sp), fmt), - ], - )) - } - } - } - - /// Actually builds the expression which the format_args! block will be - /// expanded to. - fn into_expr(self) -> P { - let mut original_args = self.args; - let mut fmt_args = Vec::with_capacity( - self.arg_unique_types.iter().map(|v| v.len()).sum::() + self.count_args.len(), - ); - - // First, build up the static array which will become our precompiled - // format "string" - let pieces = self.ecx.expr_array_ref(self.fmtsp, self.str_pieces); - - // We need to construct a &[ArgumentV1] to pass into the fmt::Arguments - // constructor. In general the expressions in this slice might be - // permuted from their order in original_args (such as in the case of - // "{1} {0}"), or may have multiple entries referring to the same - // element of original_args ("{0} {0}"). - // - // The following vector has one item per element of our output slice, - // identifying the index of which element of original_args it's passing, - // and that argument's type. - let mut fmt_arg_index_and_ty = SmallVec::<[(usize, &ArgumentType); 8]>::new(); - for (i, unique_types) in self.arg_unique_types.iter().enumerate() { - fmt_arg_index_and_ty.extend(unique_types.iter().map(|ty| (i, ty))); - } - fmt_arg_index_and_ty.extend(self.count_args.iter().map(|&i| (i, &Count))); - - // Figure out whether there are permuted or repeated elements. If not, - // we can generate simpler code. - // - // The sequence has no indices out of order or repeated if: for every - // adjacent pair of elements, the first one's index is less than the - // second one's index. - let nicely_ordered = - fmt_arg_index_and_ty.array_windows().all(|[(i, _i_ty), (j, _j_ty)]| i < j); - - // We want to emit: - // - // [ArgumentV1::new(&$arg0, …), ArgumentV1::new(&$arg1, …), …] - // - // However, it's only legal to do so if $arg0, $arg1, … were written in - // exactly that order by the programmer. When arguments are permuted, we - // want them evaluated in the order written by the programmer, not in - // the order provided to fmt::Arguments. When arguments are repeated, we - // want the expression evaluated only once. - // - // Further, if any arg _after the first one_ contains a yield point such - // as `await` or `yield`, the above short form is inconvenient for the - // caller because it would keep a temporary of type ArgumentV1 alive - // across the yield point. ArgumentV1 can't implement Send since it - // holds a type-erased arbitrary type. - // - // Thus in the not nicely ordered case, and in the yielding case, we - // emit the following instead: - // - // match (&$arg0, &$arg1, …) { - // args => [ArgumentV1::new(args.$i, …), ArgumentV1::new(args.$j, …), …] - // } - // - // for the sequence of indices $i, $j, … governed by fmt_arg_index_and_ty. - // This more verbose representation ensures that all arguments are - // evaluated a single time each, in the order written by the programmer, - // and that the surrounding future/generator (if any) is Send whenever - // possible. - let no_need_for_match = nicely_ordered - && !original_args.iter().skip(1).any(|arg| may_contain_yield_point(&arg.expr)); - - for (arg_index, arg_ty) in fmt_arg_index_and_ty { - let e = &mut original_args[arg_index].expr; - let span = e.span; - let arg = if no_need_for_match { - let expansion_span = e.span.with_ctxt(self.macsp.ctxt()); - // The indices are strictly ordered so e has not been taken yet. - self.ecx.expr_addr_of(expansion_span, P(e.take())) - } else { - let def_site = self.ecx.with_def_site_ctxt(span); - let args_tuple = self.ecx.expr_ident(def_site, Ident::new(sym::args, def_site)); - let member = Ident::new(sym::integer(arg_index), def_site); - self.ecx.expr(def_site, ast::ExprKind::Field(args_tuple, member)) - }; - fmt_args.push(Context::format_arg(self.ecx, self.macsp, span, arg_ty, arg)); - } - - let args_array = self.ecx.expr_array(self.macsp, fmt_args); - let args_slice = self.ecx.expr_addr_of( - self.macsp, - if no_need_for_match { - args_array - } else { - // In the !no_need_for_match case, none of the exprs were moved - // away in the previous loop. - // - // This uses the arg span for `&arg` so that borrowck errors - // point to the specific expression passed to the macro (the - // span is otherwise unavailable in the MIR used by borrowck). - let heads = original_args - .into_iter() - .map(|arg| { - self.ecx.expr_addr_of(arg.expr.span.with_ctxt(self.macsp.ctxt()), arg.expr) - }) - .collect(); - - let pat = self.ecx.pat_ident(self.macsp, Ident::new(sym::args, self.macsp)); - let arm = self.ecx.arm(self.macsp, pat, args_array); - let head = self.ecx.expr(self.macsp, ast::ExprKind::Tup(heads)); - self.ecx.expr_match(self.macsp, head, vec![arm]) - }, - ); - - // Now create the fmt::Arguments struct with all our locals we created. - let (fn_name, fn_args) = if self.all_pieces_simple { - ("new_v1", vec![pieces, args_slice]) - } else { - // Build up the static array which will store our precompiled - // nonstandard placeholders, if there are any. - let fmt = self.ecx.expr_array_ref(self.macsp, self.pieces); - - let path = self.ecx.std_path(&[sym::fmt, sym::UnsafeArg, sym::new]); - let unsafe_arg = self.ecx.expr_call_global(self.macsp, path, Vec::new()); - let unsafe_expr = self.ecx.expr_block(P(ast::Block { - stmts: vec![self.ecx.stmt_expr(unsafe_arg)], - id: ast::DUMMY_NODE_ID, - rules: BlockCheckMode::Unsafe(UnsafeSource::CompilerGenerated), - span: self.macsp, - tokens: None, - could_be_bare_literal: false, - })); - - ("new_v1_formatted", vec![pieces, args_slice, fmt, unsafe_expr]) - }; - - let path = self.ecx.std_path(&[sym::fmt, sym::Arguments, Symbol::intern(fn_name)]); - self.ecx.expr_call_global(self.macsp, path, fn_args) - } - - fn format_arg( - ecx: &ExtCtxt<'_>, - macsp: Span, - mut sp: Span, - ty: &ArgumentType, - arg: P, - ) -> P { - sp = ecx.with_def_site_ctxt(sp); - let trait_ = match *ty { - Placeholder(trait_) if trait_ == "" => return DummyResult::raw_expr(sp, true), - Placeholder(trait_) => trait_, - Count => { - let path = ecx.std_path(&[sym::fmt, sym::ArgumentV1, sym::from_usize]); - return ecx.expr_call_global(macsp, path, vec![arg]); - } - }; - let new_fn_name = match trait_ { - "Display" => "new_display", - "Debug" => "new_debug", - "LowerExp" => "new_lower_exp", - "UpperExp" => "new_upper_exp", - "Octal" => "new_octal", - "Pointer" => "new_pointer", - "Binary" => "new_binary", - "LowerHex" => "new_lower_hex", - "UpperHex" => "new_upper_hex", - _ => unreachable!(), - }; - - let path = ecx.std_path(&[sym::fmt, sym::ArgumentV1, Symbol::intern(new_fn_name)]); - ecx.expr_call_global(sp, path, vec![arg]) - } -} - -fn expand_format_args_impl<'cx>( - ecx: &'cx mut ExtCtxt<'_>, - mut sp: Span, - tts: TokenStream, - nl: bool, -) -> Box { - sp = ecx.with_def_site_ctxt(sp); - match parse_args(ecx, sp, tts) { - Ok((efmt, args, names)) => { - MacEager::expr(expand_preparsed_format_args(ecx, sp, efmt, args, names, nl)) - } - Err(mut err) => { - err.emit(); - DummyResult::any(sp) - } - } -} - -pub fn expand_format_args<'cx>( - ecx: &'cx mut ExtCtxt<'_>, - sp: Span, - tts: TokenStream, -) -> Box { - expand_format_args_impl(ecx, sp, tts, false) -} - -pub fn expand_format_args_nl<'cx>( - ecx: &'cx mut ExtCtxt<'_>, - sp: Span, - tts: TokenStream, -) -> Box { - expand_format_args_impl(ecx, sp, tts, true) -} - -fn create_lints_for_named_arguments_used_positionally(cx: &mut Context<'_, '_>) { - for named_arg in &cx.unused_names_lint.positional_named_args { - let (position_sp_to_replace, position_sp_for_msg) = named_arg.get_positional_arg_spans(cx); - - let msg = format!("named argument `{}` is not used by name", named_arg.replacement); - - cx.ecx.buffered_early_lint.push(BufferedEarlyLint { - span: MultiSpan::from_span(named_arg.positional_named_arg_span), - msg: msg.into(), - node_id: 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: named_arg.positional_named_arg_span, - named_arg_name: named_arg.replacement.to_string(), - is_formatting_arg: named_arg.ty != PositionalNamedArgType::Arg, - }, - }); - } -} - -/// Take the various parts of `format_args!(efmt, args..., name=names...)` -/// and construct the appropriate formatting expression. -pub fn expand_preparsed_format_args( +pub fn make_format_args( ecx: &mut ExtCtxt<'_>, - sp: Span, - efmt: P, - args: Vec, - names: FxHashMap, + efmt: P, + mut args: FormatArguments, append_newline: bool, -) -> P { - // NOTE: this verbose way of initializing `Vec>` is because - // `ArgumentType` does not derive `Clone`. - let arg_types: Vec<_> = (0..args.len()).map(|_| Vec::new()).collect(); - let arg_unique_types: Vec<_> = (0..args.len()).map(|_| Vec::new()).collect(); - - let mut macsp = ecx.call_site(); - macsp = ecx.with_def_site_ctxt(macsp); - +) -> Result { let msg = "format argument must be a string literal"; - let fmt_sp = efmt.span; - let efmt_kind_is_lit: bool = matches!(efmt.kind, ast::ExprKind::Lit(_)); + let fmt_span = efmt.span; 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)); @@ -1224,13 +168,13 @@ pub fn expand_preparsed_format_args( Ok(fmt) => fmt, Err(err) => { if let Some((mut err, suggested)) = err { - let sugg_fmt = match args.len() { + let sugg_fmt = match args.explicit_args().len() { 0 => "{}".to_string(), - _ => format!("{}{{}}", "{} ".repeat(args.len())), + _ => format!("{}{{}}", "{} ".repeat(args.explicit_args().len())), }; if !suggested { err.span_suggestion( - fmt_sp.shrink_to_lo(), + fmt_span.shrink_to_lo(), "you might be missing a string literal to format with", format!("\"{}\", ", sugg_fmt), Applicability::MaybeIncorrect, @@ -1238,17 +182,17 @@ pub fn expand_preparsed_format_args( } err.emit(); } - return DummyResult::raw_expr(sp, true); + return Err(()); } }; let str_style = match fmt_style { - ast::StrStyle::Cooked => None, - ast::StrStyle::Raw(raw) => Some(raw as usize), + 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(fmt_sp).ok(); + let fmt_snippet = ecx.source_map().span_to_snippet(fmt_span).ok(); let mut parser = parse::Parser::new( fmt_str, str_style, @@ -1257,18 +201,20 @@ pub fn expand_preparsed_format_args( parse::ParseMode::Format, ); - let mut unverified_pieces = Vec::new(); + let mut pieces = Vec::new(); while let Some(piece) = parser.next() { if !parser.errors.is_empty() { break; } else { - unverified_pieces.push(piece); + pieces.push(piece); } } + let is_literal = parser.is_literal; + if !parser.errors.is_empty() { let err = parser.errors.remove(0); - let sp = if efmt_kind_is_lit { + let sp = if is_literal { fmt_span.from_inner(InnerSpan::new(err.span.start, err.span.end)) } else { // The format string could be another macro invocation, e.g.: @@ -1286,25 +232,21 @@ pub fn expand_preparsed_format_args( if let Some(note) = err.note { e.note(¬e); } - if let Some((label, span)) = err.secondary_label { - if efmt_kind_is_lit { - e.span_label(fmt_span.from_inner(InnerSpan::new(span.start, span.end)), label); - } + if let Some((label, span)) = err.secondary_label && is_literal { + e.span_label(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)); - let n_positional_args = - args.iter().rposition(|arg| arg.name.is_none()).map_or(0, |i| i + 1); if let Ok(arg) = ecx.source_map().span_to_snippet(captured_arg_span) { - let span = match args[..n_positional_args].last() { + let span = match args.unnamed_args().last() { Some(arg) => arg.expr.span, - None => fmt_sp, + None => fmt_span, }; e.multipart_suggestion_verbose( "consider using a positional formatting argument instead", vec![ - (captured_arg_span, n_positional_args.to_string()), + (captured_arg_span, args.unnamed_args().len().to_string()), (span.shrink_to_hi(), format!(", {}", arg)), ], Applicability::MachineApplicable, @@ -1312,241 +254,626 @@ pub fn expand_preparsed_format_args( } } e.emit(); - return DummyResult::raw_expr(sp, true); + return Err(()); } - let arg_spans = parser - .arg_places - .iter() - .map(|span| fmt_span.from_inner(InnerSpan::new(span.start, span.end))) - .collect(); + let to_span = |inner_span: rustc_parse_format::InnerSpan| { + is_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(); - let mut cx = Context { - ecx, - args, - num_captured_args: 0, - arg_types, - arg_unique_types, - names, - curarg: 0, - curpiece: 0, - arg_index_map: Vec::new(), - count_args: Vec::new(), - count_positions: FxHashMap::default(), - count_positions_count: 0, - count_args_index_offset: 0, - literal: String::new(), - pieces: Vec::with_capacity(unverified_pieces.len()), - str_pieces: Vec::with_capacity(unverified_pieces.len()), - all_pieces_simple: true, - macsp, - fmtsp: fmt_span, - invalid_refs: Vec::new(), - arg_spans, - arg_with_formatting: Vec::new(), - is_literal: parser.is_literal, - unused_names_lint: PositionalNamedArgsLint { positional_named_args: vec![] }, + enum ArgRef<'a> { + Index(usize), + Name(&'a str, Option), + } + use ArgRef::*; + + let mut lookup_arg = |arg: ArgRef<'_>, + span: Option, + 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_literal { + ecx.expr_ident(span, ident) + } else { + // For the moment capturing variables from format strings expanded from macros is + // disabled (see RFC #2795) + ecx.struct_span_err(span, &format!("there is no argument named `{name}`")) + .note(format!("did you intend to capture a variable `{name}` from the surrounding scope?")) + .note("to avoid ambiguity, `format_args!` cannot capture variables when the format string is expanded from a macro") + .emit(); + DummyResult::raw_expr(span, true) + }; + Ok(args.add(FormatArgument { kind: FormatArgumentKind::Captured(ident), expr })) + } + } + }; + FormatArgPosition { index, kind, span } }; - // This needs to happen *after* the Parser has consumed all pieces to create all the spans - let pieces = unverified_pieces - .into_iter() - .map(|mut piece| { - cx.verify_piece(&piece); - cx.resolve_name_inplace(&mut piece); - piece - }) - .collect::>(); + let mut template = Vec::new(); + let mut unfinished_literal = String::new(); + let mut placeholder_index = 0; - let numbered_position_args = pieces.iter().any(|arg: &parse::Piece<'_>| match *arg { - parse::String(_) => false, - parse::NextArgument(arg) => matches!(arg.position, parse::Position::ArgumentIs(..)), - }); + for piece in pieces { + match piece { + parse::Piece::String(s) => { + unfinished_literal.push_str(s); + } + parse::Piece::NextArgument(parse::Argument { position, position_span, format }) => { + if !unfinished_literal.is_empty() { + template.push(FormatArgsPiece::Literal(Symbol::intern(&unfinished_literal))); + unfinished_literal.clear(); + } - cx.build_index_map(); + 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 mut arg_index_consumed = vec![0usize; cx.arg_index_map.len()]; + let alignment = match format.align { + parse::AlignUnknown => None, + parse::AlignLeft => Some(FormatAlignment::Left), + parse::AlignRight => Some(FormatAlignment::Right), + parse::AlignCenter => Some(FormatAlignment::Center), + }; - for piece in pieces { - if let Some(piece) = cx.build_piece(&piece, &mut arg_index_consumed) { - let s = cx.build_literal_string(); - cx.str_pieces.push(s); - cx.pieces.push(piece); + 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, + flags: format.flags, + precision, + width, + }, + })); + } } } - if !cx.literal.is_empty() { - let s = cx.build_literal_string(); - cx.str_pieces.push(s); + if !unfinished_literal.is_empty() { + template.push(FormatArgsPiece::Literal(Symbol::intern(&unfinished_literal))); } - if !cx.invalid_refs.is_empty() { - cx.report_invalid_references(numbered_position_args); + if !invalid_refs.is_empty() { + report_invalid_references(ecx, &invalid_refs, &template, fmt_span, &args, parser); } - // Make sure that all arguments were used and all arguments have types. - let errs = cx - .arg_types + let unused = used .iter() .enumerate() - .filter(|(i, ty)| ty.is_empty() && !cx.count_positions.contains_key(&i)) + .filter(|&(_, used)| !used) .map(|(i, _)| { - let msg = if cx.args[i].name.is_some() { + let msg = if let FormatArgumentKind::Named(_) = args.explicit_args()[i].kind { "named argument never used" } else { "argument never used" }; - (cx.args[i].expr.span, msg) + (args.explicit_args()[i].expr.span, msg) }) .collect::>(); - let errs_len = errs.len(); - if !errs.is_empty() { - let args_used = cx.arg_types.len() - errs_len; - let args_unused = errs_len; + 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); + } - let mut diag = { - if let [(sp, msg)] = &errs[..] { - let mut diag = cx.ecx.struct_span_err(*sp, *msg); - diag.span_label(*sp, *msg); - diag - } else { - let mut diag = cx.ecx.struct_span_err( - errs.iter().map(|&(sp, _)| sp).collect::>(), - "multiple unused formatting arguments", - ); - diag.span_label(cx.fmtsp, "multiple missing formatting specifiers"); - for (sp, msg) in errs { - diag.span_label(sp, msg); + // 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) } - diag - } - }; + 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), + }, + }); + } + } - // 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 args_used < args_unused { - use super::format_foreign as foreign; + Ok(FormatArgs { span: fmt_span, template, arguments: args }) +} - // 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(); +fn invalid_placeholder_type_error( + ecx: &ExtCtxt<'_>, + ty: &str, + ty_span: Option, + fmt_span: Span, +) { + let sp = ty_span.map(|sp| fmt_span.from_inner(InnerSpan::new(sp.start, sp.end))); + let mut err = + ecx.struct_span_err(sp.unwrap_or(fmt_span), &format!("unknown format trait `{}`", ty)); + err.note( + "the only appropriate formatting traits are:\n\ + - ``, which uses the `Display` trait\n\ + - `?`, which uses the `Debug` trait\n\ + - `e`, which uses the `LowerExp` trait\n\ + - `E`, which uses the `UpperExp` trait\n\ + - `o`, which uses the `Octal` trait\n\ + - `p`, which uses the `Pointer` trait\n\ + - `b`, which uses the `Binary` trait\n\ + - `x`, which uses the `LowerHex` trait\n\ + - `X`, which uses the `UpperHex` trait", + ); + if let Some(sp) = sp { + for (fmt, name) in &[ + ("", "Display"), + ("?", "Debug"), + ("e", "LowerExp"), + ("E", "UpperExp"), + ("o", "Octal"), + ("p", "Pointer"), + ("b", "Binary"), + ("x", "LowerHex"), + ("X", "UpperHex"), + ] { + err.tool_only_span_suggestion( + sp, + &format!("use the `{}` trait", name), + *fmt, + Applicability::MaybeIncorrect, + ); + } + } + err.emit(); +} - macro_rules! check_foreign { - ($kind:ident) => {{ - let mut show_doc_note = false; +fn report_missing_placeholders( + ecx: &mut ExtCtxt<'_>, + unused: Vec<(Span, &str)>, + detect_foreign_fmt: bool, + str_style: Option, + fmt_str: &str, + fmt_span: Span, +) { + let mut diag = if let &[(span, msg)] = &unused[..] { + let mut diag = ecx.struct_span_err(span, msg); + diag.span_label(span, msg); + diag + } else { + let mut diag = ecx.struct_span_err( + unused.iter().map(|&(sp, _)| sp).collect::>(), + "multiple unused formatting arguments", + ); + diag.span_label(fmt_span, "multiple missing formatting specifiers"); + for &(span, msg) in &unused { + diag.span_label(span, msg); + } + diag + }; - 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), + // 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 it has no translation, don't call it out specifically. - _ => continue, - }; + if !found_foreign { + found_foreign = true; + show_doc_note = true; + } - let pos = sub.position(); - let sub = String::from(sub.as_str()); - if explained.contains(&sub) { - continue; - } - explained.insert(sub.clone()); + if let Some(inner_sp) = pos { + let sp = fmt_span.from_inner(inner_sp); - if !found_foreign { - found_foreign = true; - show_doc_note = true; + if success { + suggestions.push((sp, trn)); + } else { + diag.span_note( + sp, + &format!("format specifiers use curly braces, and {}", trn), + ); } - - if let Some(inner_sp) = pos { - let sp = fmt_sp.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 { - if success { - diag.help(&format!("`{}` should be written as `{}`", sub, trn)); - } else { - diag.note(&format!( - "`{}` should use curly braces, and {}", - sub, trn - )); - } + diag.note(&format!("`{}` should use curly braces, and {}", sub, trn)); } } + } - if show_doc_note { - diag.note(concat!( - stringify!($kind), - " formatting 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 && errs_len == 1 { - diag.span_label(cx.fmtsp, "formatting specifier missing"); + if show_doc_note { + diag.note(concat!( + stringify!($kind), + " formatting not supported; see the documentation for `std::fmt`", + )); + } + if suggestions.len() > 0 { + diag.multipart_suggestion( + "format specifiers use curly braces", + suggestions, + Applicability::MachineApplicable, + ); + } + }}; } - diag.emit(); - } else if cx.invalid_refs.is_empty() && cx.ecx.sess.err_count() == 0 { - // 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. - create_lints_for_named_arguments_used_positionally(&mut cx); + check_foreign!(printf); + if !found_foreign { + check_foreign!(shell); + } + } + if !found_foreign && unused.len() == 1 { + diag.span_label(fmt_span, "formatting specifier missing"); } - cx.into_expr() + diag.emit(); } -fn may_contain_yield_point(e: &ast::Expr) -> bool { - struct MayContainYieldPoint(bool); +/// 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, 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; - impl Visitor<'_> for MayContainYieldPoint { - fn visit_expr(&mut self, e: &ast::Expr) { - if let ast::ExprKind::Await(_) | ast::ExprKind::Yield(_) = e.kind { - self.0 = true; - } else { - visit::walk_expr(self, 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); } - - fn visit_mac_call(&mut self, _: &ast::MacCall) { - self.0 = true; + let span = if spans.is_empty() { + MultiSpan::from_span(fmt_span) + } else { + MultiSpan::from_spans(spans) + }; + e = ecx.struct_span_err( + span, + &format!( + "{} positional argument{} in format string, but {}", + num_placeholders, + pluralize!(num_placeholders), + num_args_desc, + ), + ); + for arg in args.explicit_args() { + e.span_label(arg.expr.span, ""); + } + // 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_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::>().join(", ") + ) + }; + e = ecx.struct_span_err( + span, + &format!("invalid reference to positional {} ({})", arg_list, num_args_desc), + ); + e.note("positional arguments are zero-based"); + } - fn visit_attribute(&mut self, _: &ast::Attribute) { - // Conservatively assume this may be a proc macro attribute in - // expression position. - self.0 = true; + 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 visit_item(&mut self, _: &ast::Item) { - // Do not recurse into nested items. +fn expand_format_args_impl<'cx>( + ecx: &'cx mut ExtCtxt<'_>, + mut sp: Span, + tts: TokenStream, + nl: bool, +) -> Box { + sp = ecx.with_def_site_ctxt(sp); + match parse_args(ecx, sp, tts) { + Ok((efmt, args)) => { + if let Ok(format_args) = make_format_args(ecx, efmt, args, nl) { + MacEager::expr(expand_parsed_format_args(ecx, 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 { + expand_format_args_impl(ecx, sp, tts, false) +} - let mut visitor = MayContainYieldPoint(false); - visitor.visit_expr(e); - visitor.0 +pub fn expand_format_args_nl<'cx>( + ecx: &'cx mut ExtCtxt<'_>, + sp: Span, + tts: TokenStream, +) -> Box { + expand_format_args_impl(ecx, sp, tts, true) } diff --git a/compiler/rustc_builtin_macros/src/format/ast.rs b/compiler/rustc_builtin_macros/src/format/ast.rs new file mode 100644 index 0000000000000..01dbffa21b8aa --- /dev/null +++ b/compiler/rustc_builtin_macros/src/format/ast.rs @@ -0,0 +1,240 @@ +use rustc_ast::ptr::P; +use rustc_ast::Expr; +use rustc_data_structures::fx::FxHashMap; +use rustc_span::symbol::{Ident, Symbol}; +use rustc_span::Span; + +// Definitions: +// +// format_args!("hello {abc:.xyz$}!!", abc="world"); +// └──────────────────────────────────────────────┘ +// FormatArgs +// +// format_args!("hello {abc:.xyz$}!!", abc="world"); +// └─────────┘ +// argument +// +// format_args!("hello {abc:.xyz$}!!", abc="world"); +// └───────────────────┘ +// template +// +// format_args!("hello {abc:.xyz$}!!", abc="world"); +// └────┘└─────────┘└┘ +// pieces +// +// format_args!("hello {abc:.xyz$}!!", abc="world"); +// └────┘ └┘ +// literal pieces +// +// format_args!("hello {abc:.xyz$}!!", abc="world"); +// └─────────┘ +// placeholder +// +// format_args!("hello {abc:.xyz$}!!", abc="world"); +// └─┘ └─┘ +// positions (could be names, numbers, empty, or `*`) + +/// (Parsed) format args. +/// +/// Basically the "AST" for a complete `format_args!()`. +/// +/// E.g., `format_args!("hello {name}");`. +#[derive(Clone, Debug)] +pub struct FormatArgs { + pub span: Span, + pub template: Vec, + pub arguments: FormatArguments, +} + +/// A piece of a format template string. +/// +/// E.g. "hello" or "{name}". +#[derive(Clone, Debug)] +pub enum FormatArgsPiece { + Literal(Symbol), + Placeholder(FormatPlaceholder), +} + +/// The arguments to format_args!(). +/// +/// E.g. `1, 2, name="ferris", n=3`, +/// but also implicit captured arguments like `x` in `format_args!("{x}")`. +#[derive(Clone, Debug)] +pub struct FormatArguments { + arguments: Vec, + num_unnamed_args: usize, + num_explicit_args: usize, + names: FxHashMap, +} + +impl FormatArguments { + pub fn new() -> Self { + Self { + arguments: Vec::new(), + names: FxHashMap::default(), + num_unnamed_args: 0, + num_explicit_args: 0, + } + } + + pub fn add(&mut self, arg: FormatArgument) -> usize { + let index = self.arguments.len(); + if let Some(name) = arg.kind.ident() { + self.names.insert(name.name, index); + } else if self.names.is_empty() { + // Only count the unnamed args before the first named arg. + // (Any later ones are errors.) + self.num_unnamed_args += 1; + } + if !matches!(arg.kind, FormatArgumentKind::Captured(..)) { + // This is an explicit argument. + // Make sure that all arguments so far are explcit. + assert_eq!( + self.num_explicit_args, + self.arguments.len(), + "captured arguments must be added last" + ); + self.num_explicit_args += 1; + } + self.arguments.push(arg); + index + } + + pub fn by_name(&self, name: Symbol) -> Option<(usize, &FormatArgument)> { + let i = *self.names.get(&name)?; + Some((i, &self.arguments[i])) + } + + pub fn by_index(&self, i: usize) -> Option<&FormatArgument> { + (i < self.num_explicit_args).then(|| &self.arguments[i]) + } + + pub fn unnamed_args(&self) -> &[FormatArgument] { + &self.arguments[..self.num_unnamed_args] + } + + pub fn named_args(&self) -> &[FormatArgument] { + &self.arguments[self.num_unnamed_args..self.num_explicit_args] + } + + pub fn explicit_args(&self) -> &[FormatArgument] { + &self.arguments[..self.num_explicit_args] + } + + pub fn into_vec(self) -> Vec { + self.arguments + } +} + +#[derive(Clone, Debug)] +pub struct FormatArgument { + pub kind: FormatArgumentKind, + pub expr: P, +} + +#[derive(Clone, Debug)] +pub enum FormatArgumentKind { + /// `format_args(…, arg)` + Normal, + /// `format_args(…, arg = 1)` + Named(Ident), + /// `format_args("… {arg} …")` + Captured(Ident), +} + +impl FormatArgumentKind { + pub fn ident(&self) -> Option { + match self { + &Self::Normal => None, + &Self::Named(id) => Some(id), + &Self::Captured(id) => Some(id), + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct FormatPlaceholder { + /// Index into [`FormatArgs::arguments`]. + pub argument: FormatArgPosition, + /// The span inside the format string for the full `{…}` placeholder. + pub span: Option, + /// `{}`, `{:?}`, or `{:x}`, etc. + pub format_trait: FormatTrait, + /// `{}` or `{:.5}` or `{:-^20}`, etc. + pub format_options: FormatOptions, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct FormatArgPosition { + /// Which argument this position refers to (Ok), + /// or would've referred to if it existed (Err). + pub index: Result, + /// What kind of position this is. See [`FormatArgPositionKind`]. + pub kind: FormatArgPositionKind, + /// The span of the name or number. + pub span: Option, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum FormatArgPositionKind { + /// `{}` or `{:.*}` + Implicit, + /// `{1}` or `{:1$}` or `{:.1$}` + Number, + /// `{a}` or `{:a$}` or `{:.a$}` + Named, +} + +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +pub enum FormatTrait { + /// `{}` + Display, + /// `{:?}` + Debug, + /// `{:e}` + LowerExp, + /// `{:E}` + UpperExp, + /// `{:o}` + Octal, + /// `{:p}` + Pointer, + /// `{:b}` + Binary, + /// `{:x}` + LowerHex, + /// `{:X}` + UpperHex, +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct FormatOptions { + /// The width. E.g. `{:5}` or `{:width$}`. + pub width: Option, + /// The precision. E.g. `{:.5}` or `{:.precision$}`. + pub precision: Option, + /// The alignment. E.g. `{:>}` or `{:<}` or `{:^}`. + pub alignment: Option, + /// The fill character. E.g. the `.` in `{:.>10}`. + pub fill: Option, + /// The `+`, `-`, `0`, `#`, `x?` and `X?` flags. + pub flags: u32, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum FormatAlignment { + /// `{:<}` + Left, + /// `{:>}` + Right, + /// `{:^}` + Center, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum FormatCount { + /// `{:5}` or `{:.5}` + Literal(usize), + /// `{:.*}`, `{:.5$}`, or `{:a$}`, etc. + Argument(FormatArgPosition), +} diff --git a/compiler/rustc_builtin_macros/src/format/expand.rs b/compiler/rustc_builtin_macros/src/format/expand.rs new file mode 100644 index 0000000000000..9dde5efcb28b7 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/format/expand.rs @@ -0,0 +1,353 @@ +use super::*; +use rustc_ast as ast; +use rustc_ast::visit::{self, Visitor}; +use rustc_ast::{BlockCheckMode, UnsafeSource}; +use rustc_data_structures::fx::FxIndexSet; +use rustc_span::{sym, symbol::kw}; + +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +enum ArgumentType { + Format(FormatTrait), + Usize, +} + +fn make_argument(ecx: &ExtCtxt<'_>, sp: Span, arg: P, ty: ArgumentType) -> P { + // Generate: + // ::core::fmt::ArgumentV1::new_…(arg) + use ArgumentType::*; + use FormatTrait::*; + ecx.expr_call_global( + sp, + ecx.std_path(&[ + sym::fmt, + sym::ArgumentV1, + match ty { + Format(Display) => sym::new_display, + Format(Debug) => sym::new_debug, + Format(LowerExp) => sym::new_lower_exp, + Format(UpperExp) => sym::new_upper_exp, + Format(Octal) => sym::new_octal, + Format(Pointer) => sym::new_pointer, + Format(Binary) => sym::new_binary, + Format(LowerHex) => sym::new_lower_hex, + Format(UpperHex) => sym::new_upper_hex, + Usize => sym::from_usize, + }, + ]), + vec![arg], + ) +} + +fn make_count( + ecx: &ExtCtxt<'_>, + sp: Span, + count: &Option, + argmap: &mut FxIndexSet<(usize, ArgumentType)>, +) -> P { + // Generate: + // ::core::fmt::rt::v1::Count::…(…) + match count { + Some(FormatCount::Literal(n)) => ecx.expr_call_global( + sp, + ecx.std_path(&[sym::fmt, sym::rt, sym::v1, sym::Count, sym::Is]), + vec![ecx.expr_usize(sp, *n)], + ), + Some(FormatCount::Argument(arg)) => { + if let Ok(arg_index) = arg.index { + let (i, _) = argmap.insert_full((arg_index, ArgumentType::Usize)); + ecx.expr_call_global( + sp, + ecx.std_path(&[sym::fmt, sym::rt, sym::v1, sym::Count, sym::Param]), + vec![ecx.expr_usize(sp, i)], + ) + } else { + DummyResult::raw_expr(sp, true) + } + } + None => ecx.expr_path(ecx.path_global( + sp, + ecx.std_path(&[sym::fmt, sym::rt, sym::v1, sym::Count, sym::Implied]), + )), + } +} + +fn make_format_spec( + ecx: &ExtCtxt<'_>, + sp: Span, + placeholder: &FormatPlaceholder, + argmap: &mut FxIndexSet<(usize, ArgumentType)>, +) -> P { + // Generate: + // ::core::fmt::rt::v1::Argument { + // position: 0usize, + // format: ::core::fmt::rt::v1::FormatSpec { + // fill: ' ', + // align: ::core::fmt::rt::v1::Alignment::Unknown, + // flags: 0u32, + // precision: ::core::fmt::rt::v1::Count::Implied, + // width: ::core::fmt::rt::v1::Count::Implied, + // }, + // } + let position = match placeholder.argument.index { + Ok(arg_index) => { + let (i, _) = + argmap.insert_full((arg_index, ArgumentType::Format(placeholder.format_trait))); + ecx.expr_usize(sp, i) + } + Err(_) => DummyResult::raw_expr(sp, true), + }; + let fill = ecx.expr_char(sp, placeholder.format_options.fill.unwrap_or(' ')); + let align = ecx.expr_path(ecx.path_global( + sp, + ecx.std_path(&[ + sym::fmt, + sym::rt, + sym::v1, + sym::Alignment, + match placeholder.format_options.alignment { + Some(FormatAlignment::Left) => sym::Left, + Some(FormatAlignment::Right) => sym::Right, + Some(FormatAlignment::Center) => sym::Center, + None => sym::Unknown, + }, + ]), + )); + let flags = ecx.expr_u32(sp, placeholder.format_options.flags); + let prec = make_count(ecx, sp, &placeholder.format_options.precision, argmap); + let width = make_count(ecx, sp, &placeholder.format_options.width, argmap); + ecx.expr_struct( + sp, + ecx.path_global(sp, ecx.std_path(&[sym::fmt, sym::rt, sym::v1, sym::Argument])), + vec![ + ecx.field_imm(sp, Ident::new(sym::position, sp), position), + ecx.field_imm( + sp, + Ident::new(sym::format, sp), + ecx.expr_struct( + sp, + ecx.path_global( + sp, + ecx.std_path(&[sym::fmt, sym::rt, sym::v1, sym::FormatSpec]), + ), + vec![ + ecx.field_imm(sp, Ident::new(sym::fill, sp), fill), + ecx.field_imm(sp, Ident::new(sym::align, sp), align), + ecx.field_imm(sp, Ident::new(sym::flags, sp), flags), + ecx.field_imm(sp, Ident::new(sym::precision, sp), prec), + ecx.field_imm(sp, Ident::new(sym::width, sp), width), + ], + ), + ), + ], + ) +} + +pub fn expand_parsed_format_args(ecx: &mut ExtCtxt<'_>, fmt: FormatArgs) -> P { + let macsp = ecx.with_def_site_ctxt(ecx.call_site()); + + let lit_pieces = ecx.expr_array_ref( + fmt.span, + fmt.template + .iter() + .enumerate() + .filter_map(|(i, piece)| match piece { + &FormatArgsPiece::Literal(s) => Some(ecx.expr_str(fmt.span, s)), + &FormatArgsPiece::Placeholder(_) => { + // Inject empty string before placeholders when not already preceded by a literal piece. + if i == 0 || matches!(fmt.template[i - 1], FormatArgsPiece::Placeholder(_)) { + Some(ecx.expr_str(fmt.span, kw::Empty)) + } else { + None + } + } + }) + .collect(), + ); + + // Whether we'll use the `Arguments::new_v1_formatted` form (true), + // or the `Arguments::new_v1` form (false). + let mut use_format_options = false; + + // Create a list of all _unique_ (argument, format trait) combinations. + // E.g. "{0} {0:x} {0} {1}" -> [(0, Display), (0, LowerHex), (1, Display)] + let mut argmap = FxIndexSet::default(); + for piece in &fmt.template { + let FormatArgsPiece::Placeholder(placeholder) = piece else { continue }; + if placeholder.format_options != Default::default() { + // Can't use basic form if there's any formatting options. + use_format_options = true; + } + if let Ok(index) = placeholder.argument.index { + if !argmap.insert((index, ArgumentType::Format(placeholder.format_trait))) { + // Duplicate (argument, format trait) combination, + // which we'll only put once in the args array. + use_format_options = true; + } + } + } + + let format_options = use_format_options.then(|| { + // Generate: + // &[format_spec_0, format_spec_1, format_spec_2] + ecx.expr_array_ref( + macsp, + fmt.template + .iter() + .filter_map(|piece| { + let FormatArgsPiece::Placeholder(placeholder) = piece else { return None }; + Some(make_format_spec(ecx, macsp, placeholder, &mut argmap)) + }) + .collect(), + ) + }); + + let arguments = fmt.arguments.into_vec(); + + // If the args array contains exactly all the original arguments once, + // in order, we can use a simple array instead of a `match` construction. + // However, if there's a yield point in any argument except the first one, + // we don't do this, because an ArgumentV1 cannot be kept across yield points. + let use_simple_array = argmap.len() == arguments.len() + && argmap.iter().enumerate().all(|(i, &(j, _))| i == j) + && arguments.iter().skip(1).all(|arg| !may_contain_yield_point(&arg.expr)); + + let args = if use_simple_array { + // Generate: + // &[ + // ::core::fmt::ArgumentV1::new_display(&arg0), + // ::core::fmt::ArgumentV1::new_lower_hex(&arg1), + // ::core::fmt::ArgumentV1::new_debug(&arg2), + // ] + ecx.expr_array_ref( + macsp, + arguments + .into_iter() + .zip(argmap) + .map(|(arg, (_, ty))| { + let sp = arg.expr.span.with_ctxt(macsp.ctxt()); + make_argument(ecx, sp, ecx.expr_addr_of(sp, arg.expr), ty) + }) + .collect(), + ) + } else { + // Generate: + // match (&arg0, &arg1, &arg2) { + // args => &[ + // ::core::fmt::ArgumentV1::new_display(args.0), + // ::core::fmt::ArgumentV1::new_lower_hex(args.1), + // ::core::fmt::ArgumentV1::new_debug(args.0), + // ] + // } + let args_ident = Ident::new(sym::args, macsp); + let args = argmap + .iter() + .map(|&(arg_index, ty)| { + if let Some(arg) = arguments.get(arg_index) { + let sp = arg.expr.span.with_ctxt(macsp.ctxt()); + make_argument( + ecx, + sp, + ecx.expr_field( + sp, + ecx.expr_ident(macsp, args_ident), + Ident::new(sym::integer(arg_index), macsp), + ), + ty, + ) + } else { + DummyResult::raw_expr(macsp, true) + } + }) + .collect(); + ecx.expr_addr_of( + macsp, + ecx.expr_match( + macsp, + ecx.expr_tuple( + macsp, + arguments + .into_iter() + .map(|arg| { + ecx.expr_addr_of(arg.expr.span.with_ctxt(macsp.ctxt()), arg.expr) + }) + .collect(), + ), + vec![ecx.arm(macsp, ecx.pat_ident(macsp, args_ident), ecx.expr_array(macsp, args))], + ), + ) + }; + + if let Some(format_options) = format_options { + // Generate: + // ::core::fmt::Arguments::new_v1_formatted( + // lit_pieces, + // args, + // format_options, + // unsafe { ::core::fmt::UnsafeArg::new() } + // ) + ecx.expr_call_global( + macsp, + ecx.std_path(&[sym::fmt, sym::Arguments, sym::new_v1_formatted]), + vec![ + lit_pieces, + args, + format_options, + ecx.expr_block(P(ast::Block { + stmts: vec![ecx.stmt_expr(ecx.expr_call_global( + macsp, + ecx.std_path(&[sym::fmt, sym::UnsafeArg, sym::new]), + Vec::new(), + ))], + id: ast::DUMMY_NODE_ID, + rules: BlockCheckMode::Unsafe(UnsafeSource::CompilerGenerated), + span: macsp, + tokens: None, + could_be_bare_literal: false, + })), + ], + ) + } else { + // Generate: + // ::core::fmt::Arguments::new_v1( + // lit_pieces, + // args, + // ) + ecx.expr_call_global( + macsp, + ecx.std_path(&[sym::fmt, sym::Arguments, sym::new_v1]), + vec![lit_pieces, args], + ) + } +} + +fn may_contain_yield_point(e: &ast::Expr) -> bool { + struct MayContainYieldPoint(bool); + + impl Visitor<'_> for MayContainYieldPoint { + fn visit_expr(&mut self, e: &ast::Expr) { + if let ast::ExprKind::Await(_) | ast::ExprKind::Yield(_) = e.kind { + self.0 = true; + } else { + visit::walk_expr(self, e); + } + } + + fn visit_mac_call(&mut self, _: &ast::MacCall) { + self.0 = true; + } + + fn visit_attribute(&mut self, _: &ast::Attribute) { + // Conservatively assume this may be a proc macro attribute in + // expression position. + self.0 = true; + } + + fn visit_item(&mut self, _: &ast::Item) { + // Do not recurse into nested items. + } + } + + let mut visitor = MayContainYieldPoint(false); + visitor.visit_expr(e); + visitor.0 +} diff --git a/compiler/rustc_builtin_macros/src/lib.rs b/compiler/rustc_builtin_macros/src/lib.rs index 0de27d3d4070e..f058503064bf1 100644 --- a/compiler/rustc_builtin_macros/src/lib.rs +++ b/compiler/rustc_builtin_macros/src/lib.rs @@ -7,6 +7,7 @@ #![feature(box_patterns)] #![feature(decl_macro)] #![feature(if_let_guard)] +#![feature(is_some_with)] #![feature(is_sorted)] #![feature(let_chains)] #![feature(proc_macro_internals)] diff --git a/compiler/rustc_expand/src/build.rs b/compiler/rustc_expand/src/build.rs index 50d2be3cee5e0..0952e65cfee3d 100644 --- a/compiler/rustc_expand/src/build.rs +++ b/compiler/rustc_expand/src/build.rs @@ -252,6 +252,10 @@ impl<'a> ExtCtxt<'a> { self.expr_ident(span, Ident::with_dummy_span(kw::SelfLower)) } + pub fn expr_field(&self, span: Span, expr: P, field: Ident) -> P { + self.expr(span, ast::ExprKind::Field(expr, field)) + } + pub fn expr_binary( &self, sp: Span, diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 502ef67fc6767..7657f9486c227 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -991,7 +991,18 @@ symbols! { never_type, never_type_fallback, new, + new_binary, + new_debug, + new_display, + new_lower_exp, + new_lower_hex, + new_octal, + new_pointer, new_unchecked, + new_upper_exp, + new_upper_hex, + new_v1, + new_v1_formatted, next, nll, no, diff --git a/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr b/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr index 68602640a24f2..21a402c7b9da8 100644 --- a/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr +++ b/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr @@ -237,21 +237,19 @@ LL | #[suggestion(typeck::suggestion, code = "{name}")] | ^^^^^^^^ error: invalid format string: expected `'}'` but string was terminated - --> $DIR/diagnostic-derive.rs:175:16 + --> $DIR/diagnostic-derive.rs:175:10 | LL | #[derive(Diagnostic)] - | - ^ expected `'}'` in format string - | | - | because of this opening brace + | ^^^^^^^^^^ expected `'}'` in format string | = note: if you intended to print `{`, you can escape it using `{{` = note: this error originates in the derive macro `Diagnostic` (in Nightly builds, run with -Z macro-backtrace for more info) error: invalid format string: unmatched `}` found - --> $DIR/diagnostic-derive.rs:185:15 + --> $DIR/diagnostic-derive.rs:185:10 | LL | #[derive(Diagnostic)] - | ^ unmatched `}` in format string + | ^^^^^^^^^^ unmatched `}` in format string | = note: if you intended to print `}`, you can escape it using `}}` = note: this error originates in the derive macro `Diagnostic` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/src/test/ui/fmt/format-args-capture-issue-93378.rs b/src/test/ui/fmt/format-args-capture-issue-93378.rs index 6744444426472..9d722a0287a5c 100644 --- a/src/test/ui/fmt/format-args-capture-issue-93378.rs +++ b/src/test/ui/fmt/format-args-capture-issue-93378.rs @@ -3,9 +3,9 @@ fn main() { let b = "b"; println!("{a} {b} {} {} {c} {}", c = "c"); - //~^ ERROR: invalid reference to positional arguments 1 and 2 (there is 1 argument) + //~^ ERROR: 3 positional arguments in format string, but there is 1 argument let n = 1; println!("{a:.n$} {b:.*}"); - //~^ ERROR: invalid reference to positional argument 0 (no arguments were given) + //~^ ERROR: 1 positional argument in format string, but no arguments were given } diff --git a/src/test/ui/fmt/format-args-capture-issue-93378.stderr b/src/test/ui/fmt/format-args-capture-issue-93378.stderr index b8e2b2afb3867..6429b0d46f6af 100644 --- a/src/test/ui/fmt/format-args-capture-issue-93378.stderr +++ b/src/test/ui/fmt/format-args-capture-issue-93378.stderr @@ -1,19 +1,14 @@ -error: invalid reference to positional arguments 1 and 2 (there is 1 argument) - --> $DIR/format-args-capture-issue-93378.rs:5:26 +error: 3 positional arguments in format string, but there is 1 argument + --> $DIR/format-args-capture-issue-93378.rs:5:23 | LL | println!("{a} {b} {} {} {c} {}", c = "c"); - | ^^ ^^ - | - = note: positional arguments are zero-based + | ^^ ^^ ^^ --- -error: invalid reference to positional argument 0 (no arguments were given) - --> $DIR/format-args-capture-issue-93378.rs:9:23 +error: 1 positional argument in format string, but no arguments were given + --> $DIR/format-args-capture-issue-93378.rs:9:26 | LL | println!("{a:.n$} {b:.*}"); - | - ^^^--^ - | | | - | | this precision flag adds an extra required argument at position 0, which is why there are 3 arguments expected - | this parameter corresponds to the precision flag + | ^^ this precision flag adds an extra required argument at position 0, which is why there is 1 argument expected | = note: positional arguments are zero-based = note: for information about formatting flags, visit https://doc.rust-lang.org/std/fmt/index.html diff --git a/src/test/ui/fmt/ifmt-bad-arg.rs b/src/test/ui/fmt/ifmt-bad-arg.rs index f00cb05c9ebc3..68861d7bf3faf 100644 --- a/src/test/ui/fmt/ifmt-bad-arg.rs +++ b/src/test/ui/fmt/ifmt-bad-arg.rs @@ -20,9 +20,9 @@ fn main() { //~^ ERROR: invalid reference to positional argument 2 (there are 2 arguments) format!("{} {value} {} {}", 1, value=2); - //~^ ERROR: invalid reference to positional argument 2 (there are 2 arguments) + //~^ ERROR: 3 positional arguments in format string, but there are 2 arguments format!("{name} {value} {} {} {} {} {} {}", 0, name=1, value=2); - //~^ ERROR: invalid reference to positional arguments 3, 4 and 5 (there are 3 arguments) + //~^ ERROR: 6 positional arguments in format string, but there are 3 arguments format!("{} {foo} {} {bar} {}", 1, 2, 3); //~^ ERROR: cannot find value `foo` in this scope @@ -79,7 +79,7 @@ tenth number: {}", //~^ ERROR 4 positional arguments in format string, but there are 3 arguments //~| ERROR mismatched types println!("{} {:07$.*} {}", 1, 3.2, 4); - //~^ ERROR 4 positional arguments in format string, but there are 3 arguments + //~^ ERROR invalid reference to positional arguments 3 and 7 (there are 3 arguments) //~| ERROR mismatched types println!("{} {:07$} {}", 1, 3.2, 4); //~^ ERROR invalid reference to positional argument 7 (there are 3 arguments) @@ -95,5 +95,5 @@ tenth number: {}", println!("{:.*}"); //~^ ERROR 2 positional arguments in format string, but no arguments were given println!("{:.0$}"); - //~^ ERROR 1 positional argument in format string, but no arguments were given + //~^ ERROR invalid reference to positional argument 0 (no arguments were given) } diff --git a/src/test/ui/fmt/ifmt-bad-arg.stderr b/src/test/ui/fmt/ifmt-bad-arg.stderr index dbb4bc6d9370e..1b595a50e9984 100644 --- a/src/test/ui/fmt/ifmt-bad-arg.stderr +++ b/src/test/ui/fmt/ifmt-bad-arg.stderr @@ -5,10 +5,10 @@ LL | format!("{}"); | ^^ error: invalid reference to positional argument 1 (there is 1 argument) - --> $DIR/ifmt-bad-arg.rs:9:14 + --> $DIR/ifmt-bad-arg.rs:9:15 | LL | format!("{1}", 1); - | ^^^ + | ^ | = note: positional arguments are zero-based @@ -27,36 +27,32 @@ LL | format!("{} {}"); | ^^ ^^ error: invalid reference to positional argument 1 (there is 1 argument) - --> $DIR/ifmt-bad-arg.rs:16:18 + --> $DIR/ifmt-bad-arg.rs:16:19 | LL | format!("{0} {1}", 1); - | ^^^ + | ^ | = note: positional arguments are zero-based error: invalid reference to positional argument 2 (there are 2 arguments) - --> $DIR/ifmt-bad-arg.rs:19:22 + --> $DIR/ifmt-bad-arg.rs:19:23 | LL | format!("{0} {1} {2}", 1, 2); - | ^^^ + | ^ | = note: positional arguments are zero-based -error: invalid reference to positional argument 2 (there are 2 arguments) - --> $DIR/ifmt-bad-arg.rs:22:28 +error: 3 positional arguments in format string, but there are 2 arguments + --> $DIR/ifmt-bad-arg.rs:22:14 | LL | format!("{} {value} {} {}", 1, value=2); - | ^^ - | - = note: positional arguments are zero-based + | ^^ ^^ ^^ - - -error: invalid reference to positional arguments 3, 4 and 5 (there are 3 arguments) - --> $DIR/ifmt-bad-arg.rs:24:38 +error: 6 positional arguments in format string, but there are 3 arguments + --> $DIR/ifmt-bad-arg.rs:24:29 | LL | format!("{name} {value} {} {} {} {} {} {}", 0, name=1, value=2); - | ^^ ^^ ^^ - | - = note: positional arguments are zero-based + | ^^ ^^ ^^ ^^ ^^ ^^ - - - error: multiple unused formatting arguments --> $DIR/ifmt-bad-arg.rs:32:17 @@ -117,20 +113,20 @@ LL | format!("{} {}", 1, 2, foo=1, bar=2); | multiple missing formatting specifiers error: duplicate argument named `foo` - --> $DIR/ifmt-bad-arg.rs:40:33 + --> $DIR/ifmt-bad-arg.rs:40:29 | LL | format!("{foo}", foo=1, foo=2); - | - ^ duplicate argument - | | - | previously here + | --- ^^^ duplicate argument + | | + | previously here error: positional arguments cannot follow named arguments --> $DIR/ifmt-bad-arg.rs:41:35 | LL | format!("{foo} {} {}", foo=1, 2); - | - ^ positional arguments must be before named arguments - | | - | named argument + | ----- ^ positional arguments must be before named arguments + | | + | named argument error: named argument never used --> $DIR/ifmt-bad-arg.rs:45:51 @@ -191,33 +187,26 @@ error: 4 positional arguments in format string, but there are 3 arguments | LL | println!("{} {:.*} {}", 1, 3.2, 4); | ^^ ^^--^ ^^ - --- - - | | | - | | this parameter corresponds to the precision flag + | | | this precision flag adds an extra required argument at position 1, which is why there are 4 arguments expected | = note: positional arguments are zero-based = note: for information about formatting flags, visit https://doc.rust-lang.org/std/fmt/index.html -error: 4 positional arguments in format string, but there are 3 arguments - --> $DIR/ifmt-bad-arg.rs:81:15 +error: invalid reference to positional arguments 3 and 7 (there are 3 arguments) + --> $DIR/ifmt-bad-arg.rs:81:21 | LL | println!("{} {:07$.*} {}", 1, 3.2, 4); - | ^^ ^^^----^ ^^ - --- - - | | | | - | | | this parameter corresponds to the precision flag - | | this precision flag adds an extra required argument at position 1, which is why there are 4 arguments expected - | this width flag expects an `usize` argument at position 7, but there are 3 arguments + | ^^ ^ | = note: positional arguments are zero-based = note: for information about formatting flags, visit https://doc.rust-lang.org/std/fmt/index.html error: invalid reference to positional argument 7 (there are 3 arguments) - --> $DIR/ifmt-bad-arg.rs:84:18 + --> $DIR/ifmt-bad-arg.rs:84:21 | LL | println!("{} {:07$} {}", 1, 3.2, 4); - | ^^^--^ - | | - | this width flag expects an `usize` argument at position 7, but there are 3 arguments + | ^^ | = note: positional arguments are zero-based = note: for information about formatting flags, visit https://doc.rust-lang.org/std/fmt/index.html @@ -240,24 +229,19 @@ LL | println!("{:foo}", 1); - `X`, which uses the `UpperHex` trait error: invalid reference to positional arguments 4, 5, 6 and 7 (there is 1 argument) - --> $DIR/ifmt-bad-arg.rs:87:15 + --> $DIR/ifmt-bad-arg.rs:87:16 | LL | println!("{5} {:4$} {6:7$}", 1); - | ^^^ ^^--^ ^^^--^ - | | | - | | this width flag expects an `usize` argument at position 7, but there is 1 argument - | this width flag expects an `usize` argument at position 4, but there is 1 argument + | ^ ^^ ^ ^^ | = note: positional arguments are zero-based = note: for information about formatting flags, visit https://doc.rust-lang.org/std/fmt/index.html error: invalid reference to positional argument 0 (no arguments were given) - --> $DIR/ifmt-bad-arg.rs:90:15 + --> $DIR/ifmt-bad-arg.rs:90:20 | LL | println!("{foo:0$}"); - | ^^^^^--^ - | | - | this width flag expects an `usize` argument at position 0, but no arguments were given + | ^^ | = note: positional arguments are zero-based = note: for information about formatting flags, visit https://doc.rust-lang.org/std/fmt/index.html @@ -273,13 +257,11 @@ LL | println!("{:.*}"); = note: positional arguments are zero-based = note: for information about formatting flags, visit https://doc.rust-lang.org/std/fmt/index.html -error: 1 positional argument in format string, but no arguments were given - --> $DIR/ifmt-bad-arg.rs:97:15 +error: invalid reference to positional argument 0 (no arguments were given) + --> $DIR/ifmt-bad-arg.rs:97:16 | LL | println!("{:.0$}"); - | ^^---^ - | | - | this precision flag expects an `usize` argument at position 0, but no arguments were given + | ^^^^ | = note: positional arguments are zero-based = note: for information about formatting flags, visit https://doc.rust-lang.org/std/fmt/index.html @@ -318,10 +300,10 @@ error[E0308]: mismatched types --> $DIR/ifmt-bad-arg.rs:78:32 | LL | println!("{} {:.*} {}", 1, 3.2, 4); - | ---------------------------^^^---- - | | | - | | expected `usize`, found floating-point number - | arguments to this function are incorrect + | ^^^ + | | + | expected `usize`, found floating-point number + | arguments to this function are incorrect | = note: expected reference `&usize` found reference `&{float}` @@ -336,10 +318,10 @@ error[E0308]: mismatched types --> $DIR/ifmt-bad-arg.rs:81:35 | LL | println!("{} {:07$.*} {}", 1, 3.2, 4); - | ------------------------------^^^---- - | | | - | | expected `usize`, found floating-point number - | arguments to this function are incorrect + | ^^^ + | | + | expected `usize`, found floating-point number + | arguments to this function are incorrect | = note: expected reference `&usize` found reference `&{float}` diff --git a/src/test/ui/issues/issue-75307.rs b/src/test/ui/issues/issue-75307.rs index 2fe112a3b95d4..cffa6bea8ed38 100644 --- a/src/test/ui/issues/issue-75307.rs +++ b/src/test/ui/issues/issue-75307.rs @@ -1,3 +1,3 @@ fn main() { - format!(r"{}{}{}", named_arg=1); //~ ERROR invalid reference to positional arguments 1 and 2 + format!(r"{}{}{}", named_arg=1); //~ ERROR 3 positional arguments in format string, but there is 1 argument } diff --git a/src/test/ui/issues/issue-75307.stderr b/src/test/ui/issues/issue-75307.stderr index 10c952006c208..c5b0b11e7d099 100644 --- a/src/test/ui/issues/issue-75307.stderr +++ b/src/test/ui/issues/issue-75307.stderr @@ -1,10 +1,8 @@ -error: invalid reference to positional arguments 1 and 2 (there is 1 argument) - --> $DIR/issue-75307.rs:2:17 +error: 3 positional arguments in format string, but there is 1 argument + --> $DIR/issue-75307.rs:2:15 | LL | format!(r"{}{}{}", named_arg=1); - | ^^^^ - | - = note: positional arguments are zero-based + | ^^^^^^ - error: aborting due to previous error diff --git a/src/test/ui/macros/format-parse-errors.stderr b/src/test/ui/macros/format-parse-errors.stderr index 1a7578e6076eb..f9ea4c63377b0 100644 --- a/src/test/ui/macros/format-parse-errors.stderr +++ b/src/test/ui/macros/format-parse-errors.stderr @@ -22,7 +22,7 @@ error: positional arguments cannot follow named arguments --> $DIR/format-parse-errors.rs:10:9 | LL | foo = foo, - | --- named argument + | --------- named argument LL | bar, | ^^^ positional arguments must be before named arguments diff --git a/src/test/ui/macros/issue-99265.stderr b/src/test/ui/macros/issue-99265.stderr index 2bfeedd7d0737..9185dbff61ee0 100644 --- a/src/test/ui/macros/issue-99265.stderr +++ b/src/test/ui/macros/issue-99265.stderr @@ -77,18 +77,18 @@ help: use the named argument by name to avoid ambiguity LL | println!("Hello {:width$}!", "x", width = 5); | ~~~~~~ -warning: named argument `width` is not used by name - --> $DIR/issue-99265.rs:23:46 +warning: named argument `f` is not used by name + --> $DIR/issue-99265.rs:23:33 | LL | println!("Hello {:1$.2$}!", f = 0.02f32, width = 5, precision = 2); - | -- ^^^^^ this named argument is referred to by position in formatting string - | | - | this formatting argument uses named argument `width` by position + | -------- ^ this named argument is referred to by position in formatting string + | | + | this formatting argument uses named argument `f` by position | help: use the named argument by name to avoid ambiguity | -LL | println!("Hello {:width$.2$}!", f = 0.02f32, width = 5, precision = 2); - | ~~~~~~ +LL | println!("Hello {f:1$.2$}!", f = 0.02f32, width = 5, precision = 2); + | + warning: named argument `precision` is not used by name --> $DIR/issue-99265.rs:23:57 @@ -103,31 +103,31 @@ help: use the named argument by name to avoid ambiguity LL | println!("Hello {:1$.precision$}!", f = 0.02f32, width = 5, precision = 2); | ~~~~~~~~~~ -warning: named argument `f` is not used by name - --> $DIR/issue-99265.rs:23:33 +warning: named argument `width` is not used by name + --> $DIR/issue-99265.rs:23:46 | LL | println!("Hello {:1$.2$}!", f = 0.02f32, width = 5, precision = 2); - | -- ^ this named argument is referred to by position in formatting string - | | - | this formatting argument uses named argument `f` by position + | -- ^^^^^ this named argument is referred to by position in formatting string + | | + | this formatting argument uses named argument `width` by position | help: use the named argument by name to avoid ambiguity | -LL | println!("Hello {f:1$.2$}!", f = 0.02f32, width = 5, precision = 2); - | + +LL | println!("Hello {:width$.2$}!", f = 0.02f32, width = 5, precision = 2); + | ~~~~~~ -warning: named argument `width` is not used by name - --> $DIR/issue-99265.rs:31:47 +warning: named argument `f` is not used by name + --> $DIR/issue-99265.rs:31:34 | LL | println!("Hello {0:1$.2$}!", f = 0.02f32, width = 5, precision = 2); - | -- ^^^^^ this named argument is referred to by position in formatting string - | | - | this formatting argument uses named argument `width` by position + | --------- ^ this named argument is referred to by position in formatting string + | | + | this formatting argument uses named argument `f` by position | help: use the named argument by name to avoid ambiguity | -LL | println!("Hello {0:width$.2$}!", f = 0.02f32, width = 5, precision = 2); - | ~~~~~~ +LL | println!("Hello {f:1$.2$}!", f = 0.02f32, width = 5, precision = 2); + | ~ warning: named argument `precision` is not used by name --> $DIR/issue-99265.rs:31:58 @@ -142,32 +142,32 @@ help: use the named argument by name to avoid ambiguity LL | println!("Hello {0:1$.precision$}!", f = 0.02f32, width = 5, precision = 2); | ~~~~~~~~~~ -warning: named argument `f` is not used by name - --> $DIR/issue-99265.rs:31:34 +warning: named argument `width` is not used by name + --> $DIR/issue-99265.rs:31:47 | LL | println!("Hello {0:1$.2$}!", f = 0.02f32, width = 5, precision = 2); - | - ^ this named argument is referred to by position in formatting string - | | - | this formatting argument uses named argument `f` by position + | -- ^^^^^ this named argument is referred to by position in formatting string + | | + | this formatting argument uses named argument `width` by position | help: use the named argument by name to avoid ambiguity | -LL | println!("Hello {f:1$.2$}!", f = 0.02f32, width = 5, precision = 2); - | ~ +LL | println!("Hello {0:width$.2$}!", f = 0.02f32, width = 5, precision = 2); + | ~~~~~~ -warning: named argument `width` is not used by name - --> $DIR/issue-99265.rs:52:9 +warning: named argument `f` is not used by name + --> $DIR/issue-99265.rs:49:9 | LL | "{}, Hello {1:2$.3$} {4:5$.6$}! {1}", - | -- this formatting argument uses named argument `width` by position + | --------- this formatting argument uses named argument `f` by position ... -LL | width = 5, - | ^^^^^ this named argument is referred to by position in formatting string +LL | f = 0.02f32, + | ^ this named argument is referred to by position in formatting string | help: use the named argument by name to avoid ambiguity | -LL | "{}, Hello {1:width$.3$} {4:5$.6$}! {1}", - | ~~~~~~ +LL | "{}, Hello {f:2$.3$} {4:5$.6$}! {1}", + | ~ warning: named argument `precision` is not used by name --> $DIR/issue-99265.rs:54:9 @@ -183,33 +183,33 @@ help: use the named argument by name to avoid ambiguity LL | "{}, Hello {1:2$.precision$} {4:5$.6$}! {1}", | ~~~~~~~~~~ -warning: named argument `f` is not used by name - --> $DIR/issue-99265.rs:49:9 +warning: named argument `width` is not used by name + --> $DIR/issue-99265.rs:52:9 | LL | "{}, Hello {1:2$.3$} {4:5$.6$}! {1}", - | - this formatting argument uses named argument `f` by position + | -- this formatting argument uses named argument `width` by position ... -LL | f = 0.02f32, - | ^ this named argument is referred to by position in formatting string +LL | width = 5, + | ^^^^^ this named argument is referred to by position in formatting string | help: use the named argument by name to avoid ambiguity | -LL | "{}, Hello {f:2$.3$} {4:5$.6$}! {1}", - | ~ +LL | "{}, Hello {1:width$.3$} {4:5$.6$}! {1}", + | ~~~~~~ -warning: named argument `width2` is not used by name - --> $DIR/issue-99265.rs:58:9 +warning: named argument `g` is not used by name + --> $DIR/issue-99265.rs:56:9 | LL | "{}, Hello {1:2$.3$} {4:5$.6$}! {1}", - | -- this formatting argument uses named argument `width2` by position + | --------- this formatting argument uses named argument `g` by position ... -LL | width2 = 5, - | ^^^^^^ this named argument is referred to by position in formatting string +LL | g = 0.02f32, + | ^ this named argument is referred to by position in formatting string | help: use the named argument by name to avoid ambiguity | -LL | "{}, Hello {1:2$.3$} {4:width2$.6$}! {1}", - | ~~~~~~~ +LL | "{}, Hello {1:2$.3$} {g:5$.6$}! {1}", + | ~ warning: named argument `precision2` is not used by name --> $DIR/issue-99265.rs:60:9 @@ -225,25 +225,25 @@ help: use the named argument by name to avoid ambiguity LL | "{}, Hello {1:2$.3$} {4:5$.precision2$}! {1}", | ~~~~~~~~~~~ -warning: named argument `g` is not used by name - --> $DIR/issue-99265.rs:56:9 +warning: named argument `width2` is not used by name + --> $DIR/issue-99265.rs:58:9 | LL | "{}, Hello {1:2$.3$} {4:5$.6$}! {1}", - | - this formatting argument uses named argument `g` by position + | -- this formatting argument uses named argument `width2` by position ... -LL | g = 0.02f32, - | ^ this named argument is referred to by position in formatting string +LL | width2 = 5, + | ^^^^^^ this named argument is referred to by position in formatting string | help: use the named argument by name to avoid ambiguity | -LL | "{}, Hello {1:2$.3$} {g:5$.6$}! {1}", - | ~ +LL | "{}, Hello {1:2$.3$} {4:width2$.6$}! {1}", + | ~~~~~~~ warning: named argument `f` is not used by name --> $DIR/issue-99265.rs:49:9 | LL | "{}, Hello {1:2$.3$} {4:5$.6$}! {1}", - | - this formatting argument uses named argument `f` by position + | --- this formatting argument uses named argument `f` by position ... LL | f = 0.02f32, | ^ this named argument is referred to by position in formatting string @@ -257,7 +257,7 @@ warning: named argument `f` is not used by name --> $DIR/issue-99265.rs:64:31 | LL | println!("Hello {:0.1}!", f = 0.02f32); - | -- ^ this named argument is referred to by position in formatting string + | ------ ^ this named argument is referred to by position in formatting string | | | this formatting argument uses named argument `f` by position | @@ -270,15 +270,28 @@ warning: named argument `f` is not used by name --> $DIR/issue-99265.rs:68:32 | LL | println!("Hello {0:0.1}!", f = 0.02f32); - | - ^ this named argument is referred to by position in formatting string - | | - | this formatting argument uses named argument `f` by position + | ------- ^ this named argument is referred to by position in formatting string + | | + | this formatting argument uses named argument `f` by position | help: use the named argument by name to avoid ambiguity | LL | println!("Hello {f:0.1}!", f = 0.02f32); | ~ +warning: named argument `v` is not used by name + --> $DIR/issue-99265.rs:79:23 + | +LL | println!("{:0$}", v = val); + | ----- ^ this named argument is referred to by position in formatting string + | | + | this formatting argument uses named argument `v` by position + | +help: use the named argument by name to avoid ambiguity + | +LL | println!("{v:0$}", v = val); + | + + warning: named argument `v` is not used by name --> $DIR/issue-99265.rs:79:23 | @@ -293,17 +306,17 @@ LL | println!("{:v$}", v = val); | ~~ warning: named argument `v` is not used by name - --> $DIR/issue-99265.rs:79:23 + --> $DIR/issue-99265.rs:84:24 | -LL | println!("{:0$}", v = val); - | -- ^ this named argument is referred to by position in formatting string +LL | println!("{0:0$}", v = val); + | ------ ^ this named argument is referred to by position in formatting string | | | this formatting argument uses named argument `v` by position | help: use the named argument by name to avoid ambiguity | LL | println!("{v:0$}", v = val); - | + + | ~ warning: named argument `v` is not used by name --> $DIR/issue-99265.rs:84:24 @@ -318,31 +331,18 @@ help: use the named argument by name to avoid ambiguity LL | println!("{0:v$}", v = val); | ~~ -warning: named argument `v` is not used by name - --> $DIR/issue-99265.rs:84:24 - | -LL | println!("{0:0$}", v = val); - | - ^ this named argument is referred to by position in formatting string - | | - | this formatting argument uses named argument `v` by position - | -help: use the named argument by name to avoid ambiguity - | -LL | println!("{v:0$}", v = val); - | ~ - warning: named argument `v` is not used by name --> $DIR/issue-99265.rs:89:26 | LL | println!("{:0$.0$}", v = val); - | -- ^ this named argument is referred to by position in formatting string - | | - | this formatting argument uses named argument `v` by position + | -------- ^ this named argument is referred to by position in formatting string + | | + | this formatting argument uses named argument `v` by position | help: use the named argument by name to avoid ambiguity | -LL | println!("{:v$.0$}", v = val); - | ~~ +LL | println!("{v:0$.0$}", v = val); + | + warning: named argument `v` is not used by name --> $DIR/issue-99265.rs:89:26 @@ -361,27 +361,27 @@ warning: named argument `v` is not used by name --> $DIR/issue-99265.rs:89:26 | LL | println!("{:0$.0$}", v = val); - | -- ^ this named argument is referred to by position in formatting string - | | - | this formatting argument uses named argument `v` by position + | -- ^ this named argument is referred to by position in formatting string + | | + | this formatting argument uses named argument `v` by position | help: use the named argument by name to avoid ambiguity | -LL | println!("{v:0$.0$}", v = val); - | + +LL | println!("{:v$.0$}", v = val); + | ~~ warning: named argument `v` is not used by name --> $DIR/issue-99265.rs:96:27 | LL | println!("{0:0$.0$}", v = val); - | -- ^ this named argument is referred to by position in formatting string - | | - | this formatting argument uses named argument `v` by position + | --------- ^ this named argument is referred to by position in formatting string + | | + | this formatting argument uses named argument `v` by position | help: use the named argument by name to avoid ambiguity | -LL | println!("{0:v$.0$}", v = val); - | ~~ +LL | println!("{v:0$.0$}", v = val); + | ~ warning: named argument `v` is not used by name --> $DIR/issue-99265.rs:96:27 @@ -400,14 +400,14 @@ warning: named argument `v` is not used by name --> $DIR/issue-99265.rs:96:27 | LL | println!("{0:0$.0$}", v = val); - | - ^ this named argument is referred to by position in formatting string - | | - | this formatting argument uses named argument `v` by position + | -- ^ this named argument is referred to by position in formatting string + | | + | this formatting argument uses named argument `v` by position | help: use the named argument by name to avoid ambiguity | -LL | println!("{v:0$.0$}", v = val); - | ~ +LL | println!("{0:v$.0$}", v = val); + | ~~ warning: named argument `a` is not used by name --> $DIR/issue-99265.rs:104:28 @@ -426,28 +426,28 @@ warning: named argument `a` is not used by name --> $DIR/issue-99265.rs:104:28 | LL | println!("{} {a} {0}", a = 1); - | - ^ this named argument is referred to by position in formatting string - | | - | this formatting argument uses named argument `a` by position + | --- ^ this named argument is referred to by position in formatting string + | | + | this formatting argument uses named argument `a` by position | help: use the named argument by name to avoid ambiguity | LL | println!("{} {a} {a}", a = 1); | ~ -warning: named argument `b` is not used by name - --> $DIR/issue-99265.rs:115:23 +warning: named argument `a` is not used by name + --> $DIR/issue-99265.rs:115:14 | LL | {:1$.2$}", - | -- this formatting argument uses named argument `b` by position + | -------- this formatting argument uses named argument `a` by position ... LL | a = 1.0, b = 1, c = 2, - | ^ this named argument is referred to by position in formatting string + | ^ this named argument is referred to by position in formatting string | help: use the named argument by name to avoid ambiguity | -LL | {:b$.2$}", - | ~~ +LL | {a:1$.2$}", + | + warning: named argument `c` is not used by name --> $DIR/issue-99265.rs:115:30 @@ -463,33 +463,33 @@ help: use the named argument by name to avoid ambiguity LL | {:1$.c$}", | ~~ -warning: named argument `a` is not used by name - --> $DIR/issue-99265.rs:115:14 +warning: named argument `b` is not used by name + --> $DIR/issue-99265.rs:115:23 | LL | {:1$.2$}", - | -- this formatting argument uses named argument `a` by position + | -- this formatting argument uses named argument `b` by position ... LL | a = 1.0, b = 1, c = 2, - | ^ this named argument is referred to by position in formatting string + | ^ this named argument is referred to by position in formatting string | help: use the named argument by name to avoid ambiguity | -LL | {a:1$.2$}", - | + +LL | {:b$.2$}", + | ~~ -warning: named argument `b` is not used by name - --> $DIR/issue-99265.rs:126:23 +warning: named argument `a` is not used by name + --> $DIR/issue-99265.rs:126:14 | LL | {0:1$.2$}", - | -- this formatting argument uses named argument `b` by position + | --------- this formatting argument uses named argument `a` by position ... LL | a = 1.0, b = 1, c = 2, - | ^ this named argument is referred to by position in formatting string + | ^ this named argument is referred to by position in formatting string | help: use the named argument by name to avoid ambiguity | -LL | {0:b$.2$}", - | ~~ +LL | {a:1$.2$}", + | ~ warning: named argument `c` is not used by name --> $DIR/issue-99265.rs:126:30 @@ -505,32 +505,32 @@ help: use the named argument by name to avoid ambiguity LL | {0:1$.c$}", | ~~ -warning: named argument `a` is not used by name - --> $DIR/issue-99265.rs:126:14 +warning: named argument `b` is not used by name + --> $DIR/issue-99265.rs:126:23 | LL | {0:1$.2$}", - | - this formatting argument uses named argument `a` by position + | -- this formatting argument uses named argument `b` by position ... LL | a = 1.0, b = 1, c = 2, - | ^ this named argument is referred to by position in formatting string + | ^ this named argument is referred to by position in formatting string | help: use the named argument by name to avoid ambiguity | -LL | {a:1$.2$}", - | ~ +LL | {0:b$.2$}", + | ~~ -warning: named argument `width` is not used by name - --> $DIR/issue-99265.rs:132:39 +warning: named argument `x` is not used by name + --> $DIR/issue-99265.rs:132:30 | LL | println!("{{{:1$.2$}}}", x = 1.0, width = 3, precision = 2); - | -- ^^^^^ this named argument is referred to by position in formatting string - | | - | this formatting argument uses named argument `width` by position + | -------- ^ this named argument is referred to by position in formatting string + | | + | this formatting argument uses named argument `x` by position | help: use the named argument by name to avoid ambiguity | -LL | println!("{{{:width$.2$}}}", x = 1.0, width = 3, precision = 2); - | ~~~~~~ +LL | println!("{{{x:1$.2$}}}", x = 1.0, width = 3, precision = 2); + | + warning: named argument `precision` is not used by name --> $DIR/issue-99265.rs:132:50 @@ -545,18 +545,18 @@ help: use the named argument by name to avoid ambiguity LL | println!("{{{:1$.precision$}}}", x = 1.0, width = 3, precision = 2); | ~~~~~~~~~~ -warning: named argument `x` is not used by name - --> $DIR/issue-99265.rs:132:30 +warning: named argument `width` is not used by name + --> $DIR/issue-99265.rs:132:39 | LL | println!("{{{:1$.2$}}}", x = 1.0, width = 3, precision = 2); - | -- ^ this named argument is referred to by position in formatting string - | | - | this formatting argument uses named argument `x` by position + | -- ^^^^^ this named argument is referred to by position in formatting string + | | + | this formatting argument uses named argument `width` by position | help: use the named argument by name to avoid ambiguity | -LL | println!("{{{x:1$.2$}}}", x = 1.0, width = 3, precision = 2); - | + +LL | println!("{{{:width$.2$}}}", x = 1.0, width = 3, precision = 2); + | ~~~~~~ warning: 42 warnings emitted diff --git a/src/test/ui/macros/issue-99907.stderr b/src/test/ui/macros/issue-99907.stderr index 4786ce003b4c2..eefb28dee35b8 100644 --- a/src/test/ui/macros/issue-99907.stderr +++ b/src/test/ui/macros/issue-99907.stderr @@ -2,7 +2,7 @@ warning: named argument `f` is not used by name --> $DIR/issue-99907.rs:5:30 | LL | println!("Hello {:.1}!", f = 0.02f32); - | -- ^ this named argument is referred to by position in formatting string + | ----- ^ this named argument is referred to by position in formatting string | | | this formatting argument uses named argument `f` by position | @@ -16,7 +16,7 @@ warning: named argument `f` is not used by name --> $DIR/issue-99907.rs:9:31 | LL | println!("Hello {:1.1}!", f = 0.02f32); - | -- ^ this named argument is referred to by position in formatting string + | ------ ^ this named argument is referred to by position in formatting string | | | this formatting argument uses named argument `f` by position |