diff --git a/compiler/rustc_ast/src/format.rs b/compiler/rustc_ast/src/format.rs index 356b9bb6371e2..fd25a8c8d76a3 100644 --- a/compiler/rustc_ast/src/format.rs +++ b/compiler/rustc_ast/src/format.rs @@ -65,6 +65,7 @@ pub struct FormatArguments { num_unnamed_args: usize, num_explicit_args: usize, names: FxHashMap, + implicit_args: FxHashMap<(Symbol, FormatTrait), usize>, } // FIXME: Rustdoc has trouble proving Send/Sync for this. See #106930. @@ -78,20 +79,34 @@ impl FormatArguments { Self { arguments: Vec::new(), names: FxHashMap::default(), + implicit_args: FxHashMap::default(), num_unnamed_args: 0, num_explicit_args: 0, } } - pub fn add(&mut self, arg: FormatArgument) -> usize { + pub fn add( + &mut self, + arg: FormatArgument, + implicit_arg_format_trait: Option, + ) -> 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; + + match (arg.kind.ident(), implicit_arg_format_trait) { + (Some(ident), Some(format_trait)) => { + self.implicit_args.insert((ident.name, format_trait), index); + } + (Some(ident), None) => { + self.names.insert(ident.name, index); + } + _ 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. @@ -111,6 +126,15 @@ impl FormatArguments { Some((i, &self.arguments[i])) } + pub fn by_implicit_arg( + &self, + name: Symbol, + format_trait: FormatTrait, + ) -> Option<(usize, &FormatArgument)> { + let i = *self.implicit_args.get(&(name, format_trait))?; + Some((i, &self.arguments[i])) + } + pub fn by_index(&self, i: usize) -> Option<&FormatArgument> { (i < self.num_explicit_args).then(|| &self.arguments[i]) } diff --git a/compiler/rustc_builtin_macros/src/format.rs b/compiler/rustc_builtin_macros/src/format.rs index db2ef7fba4b8e..15da9cddd0b69 100644 --- a/compiler/rustc_builtin_macros/src/format.rs +++ b/compiler/rustc_builtin_macros/src/format.rs @@ -130,7 +130,7 @@ fn parse_args<'a>(ecx: &mut ExtCtxt<'a>, sp: Span, tts: TokenStream) -> PResult< .emit(); continue; } - args.add(FormatArgument { kind: FormatArgumentKind::Named(ident), expr }); + args.add(FormatArgument { kind: FormatArgumentKind::Named(ident), expr }, None); } _ => { let expr = p.parse_expr()?; @@ -150,7 +150,7 @@ fn parse_args<'a>(ecx: &mut ExtCtxt<'a>, sp: Span, tts: TokenStream) -> PResult< } err.emit(); } - args.add(FormatArgument { kind: FormatArgumentKind::Normal, expr }); + args.add(FormatArgument { kind: FormatArgumentKind::Normal, expr }, None); } } } @@ -283,7 +283,8 @@ fn make_format_args( let mut lookup_arg = |arg: ArgRef<'_>, span: Option, used_as: PositionUsedAs, - kind: FormatArgPositionKind| + kind: FormatArgPositionKind, + fmt_trait: Option| -> FormatArgPosition { let index = match arg { Index(index) => { @@ -309,6 +310,9 @@ fn make_format_args( used[index] = true; } Ok(index) + } else if let Some(tr) = fmt_trait + && let Some((index, _)) = args.by_implicit_arg(name, tr) { + Ok(index) } else { // Name not found in `args`, so we add it as an implicitly captured argument. let span = span.unwrap_or(fmt_span); @@ -324,7 +328,7 @@ fn make_format_args( .emit(); DummyResult::raw_expr(span, true) }; - Ok(args.add(FormatArgument { kind: FormatArgumentKind::Captured(ident), expr })) + Ok(args.add(FormatArgument { kind: FormatArgumentKind::Captured(ident), expr }, fmt_trait)) } } }; @@ -350,24 +354,44 @@ fn make_format_args( placeholder_index += 1; let position_span = to_span(position_span); + + 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 argument = match position { parse::ArgumentImplicitlyIs(i) => lookup_arg( Index(i), position_span, Placeholder(span), FormatArgPositionKind::Implicit, + Some(format_trait), ), parse::ArgumentIs(i) => lookup_arg( Index(i), position_span, Placeholder(span), FormatArgPositionKind::Number, + Some(format_trait), ), parse::ArgumentNamed(name) => lookup_arg( Name(name, position_span), position_span, Placeholder(span), FormatArgPositionKind::Named, + Some(format_trait), ), }; @@ -378,22 +402,6 @@ fn make_format_args( parse::AlignCenter => Some(FormatAlignment::Center), }; - let format_trait = match format.ty { - "" => FormatTrait::Display, - "?" => FormatTrait::Debug, - "e" => FormatTrait::LowerExp, - "E" => FormatTrait::UpperExp, - "o" => FormatTrait::Octal, - "p" => FormatTrait::Pointer, - "b" => FormatTrait::Binary, - "x" => FormatTrait::LowerHex, - "X" => FormatTrait::UpperHex, - _ => { - invalid_placeholder_type_error(ecx, format.ty, format.ty_span, fmt_span); - FormatTrait::Display - } - }; - let precision_span = format.precision_span.and_then(to_span); let precision = match format.precision { parse::CountIs(n) => Some(FormatCount::Literal(n)), @@ -402,18 +410,21 @@ fn make_format_args( precision_span, Precision, FormatArgPositionKind::Named, + None, ))), parse::CountIsParam(i) => Some(FormatCount::Argument(lookup_arg( Index(i), precision_span, Precision, FormatArgPositionKind::Number, + None, ))), parse::CountIsStar(i) => Some(FormatCount::Argument(lookup_arg( Index(i), precision_span, Precision, FormatArgPositionKind::Implicit, + None, ))), parse::CountImplied => None, }; @@ -426,12 +437,14 @@ fn make_format_args( width_span, Width, FormatArgPositionKind::Named, + None, ))), parse::CountIsParam(i) => Some(FormatCount::Argument(lookup_arg( Index(i), width_span, Width, FormatArgPositionKind::Number, + None, ))), parse::CountIsStar(_) => unreachable!(), parse::CountImplied => None, diff --git a/tests/ui/fmt/format-args-point-to-correct-arg-expansion.rs b/tests/ui/fmt/format-args-point-to-correct-arg-expansion.rs new file mode 100644 index 0000000000000..4a933f3f0ab6b --- /dev/null +++ b/tests/ui/fmt/format-args-point-to-correct-arg-expansion.rs @@ -0,0 +1,8 @@ +// compile-flags: -Z unpretty=expanded +// check-pass +struct X; + +fn main() { + let x = X; + println!("test: {x} {x:?}"); +} diff --git a/tests/ui/fmt/format-args-point-to-correct-arg-expansion.stdout b/tests/ui/fmt/format-args-point-to-correct-arg-expansion.stdout new file mode 100644 index 0000000000000..0d16752c4eb34 --- /dev/null +++ b/tests/ui/fmt/format-args-point-to-correct-arg-expansion.stdout @@ -0,0 +1,14 @@ +#![feature(prelude_import)] +#![no_std] +#[prelude_import] +use ::std::prelude::rust_2015::*; +#[macro_use] +extern crate std; +// compile-flags: -Z unpretty=expanded +// check-pass +struct X; + +fn main() { + let x = X; + { ::std::io::_print(format_args!("test: {0} {1:?}\n", x, x)); }; +} diff --git a/tests/ui/fmt/format-args-point-to-correct-arg.rs b/tests/ui/fmt/format-args-point-to-correct-arg.rs new file mode 100644 index 0000000000000..fbb14363bb454 --- /dev/null +++ b/tests/ui/fmt/format-args-point-to-correct-arg.rs @@ -0,0 +1,13 @@ +struct X; + +impl std::fmt::Display for X { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "x") + } +} + +fn main() { + let x = X; + println!("test: {x} {x:?}"); + //~^ ERROR: `X` doesn't implement `Debug` +} diff --git a/tests/ui/fmt/format-args-point-to-correct-arg.stderr b/tests/ui/fmt/format-args-point-to-correct-arg.stderr new file mode 100644 index 0000000000000..1c6b11c2452eb --- /dev/null +++ b/tests/ui/fmt/format-args-point-to-correct-arg.stderr @@ -0,0 +1,17 @@ +error[E0277]: `X` doesn't implement `Debug` + --> $DIR/format-args-point-to-correct-arg.rs:11:26 + | +LL | println!("test: {x} {x:?}"); + | ^ `X` cannot be formatted using `{:?}` + | + = help: the trait `Debug` is not implemented for `X` + = note: add `#[derive(Debug)]` to `X` or manually `impl Debug for X` + = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info) +help: consider annotating `X` with `#[derive(Debug)]` + | +LL | #[derive(Debug)] + | + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0277`.