diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 7ae6005e9bc98..863e4d88872c9 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -233,8 +233,8 @@ impl<'tcx> CheckAttrVisitor<'tcx> { AttributeKind::OnUnimplemented { directive } => { self.check_diagnostic_on_unimplemented(hir_id, directive.as_deref()) } - AttributeKind::OnConst { span, .. } => { - self.check_diagnostic_on_const(*span, hir_id, target, item) + AttributeKind::OnConst { span, directive } => { + self.check_diagnostic_on_const(*span, hir_id, target, item, directive.as_deref()) } AttributeKind::OnMove { directive } => { self.check_diagnostic_on_move(hir_id, directive.as_deref()) @@ -545,10 +545,36 @@ impl<'tcx> CheckAttrVisitor<'tcx> { hir_id: HirId, target: Target, item: Option>, + directive: Option<&Directive>, ) { // We only check the non-constness here. A diagnostic for use // on not-trait impl items is issued during attribute parsing. if target == (Target::Impl { of_trait: true }) { + if let Some(directive) = directive + && let Node::Item(Item { kind: ItemKind::Impl(hir::Impl { generics, .. }), .. }) = + self.tcx.hir_node(hir_id) + { + directive.visit_params(&mut |argument_name, span| { + let has_generic = generics.params.iter().any(|p| { + if !matches!(p.kind, GenericParamKind::Lifetime { .. }) + && let ParamName::Plain(name) = p.name + && name.name == argument_name + { + true + } else { + false + } + }); + if !has_generic { + self.tcx.emit_node_span_lint( + MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, + hir_id, + span, + diagnostics::OnConstMalformedFormatLiterals { name: argument_name }, + ) + } + }); + } match item.unwrap() { ItemLike::Item(it) => match it.expect_impl().constness { Constness::Const { .. } => { @@ -566,9 +592,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> { ItemLike::ForeignItem => {} } } - // FIXME(#155570) Can we do something with generic args here? - // regardless, we don't check the validity of generic args here - // ...whose generics would that be, anyway? The traits' or the impls'? } /// Checks use of generic formatting parameters in `#[diagnostic::on_move]` diff --git a/compiler/rustc_passes/src/diagnostics.rs b/compiler/rustc_passes/src/diagnostics.rs index 92562cc462b87..605d54ac0511a 100644 --- a/compiler/rustc_passes/src/diagnostics.rs +++ b/compiler/rustc_passes/src/diagnostics.rs @@ -1196,6 +1196,13 @@ pub(crate) struct OnMoveMalformedFormatLiterals { pub name: Symbol, } +#[derive(Diagnostic)] +#[diag("unknown parameter `{$name}`")] +#[help(r#"expect either a generic argument name or {"`{Self}`"} as format argument"#)] +pub(crate) struct OnConstMalformedFormatLiterals { + pub name: Symbol, +} + #[derive(Diagnostic)] #[diag("unused target expression is specified for glob or list delegation")] pub(crate) struct GlobOrListDelegationUnusedTargetExpr { diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs index 617ec6c4ac229..b38c933151eb2 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs @@ -29,8 +29,8 @@ use rustc_middle::ty::print::{ PrintTraitRefExt as _, with_forced_trimmed_paths, }; use rustc_middle::ty::{ - self, GenericArgKind, TraitRef, Ty, TyCtxt, TypeFoldable, TypeFolder, TypeSuperFoldable, - TypeVisitableExt, Unnormalized, Upcast, + self, GenericArgKind, GenericParamDefKind, TraitRef, Ty, TyCtxt, TypeFoldable, TypeFolder, + TypeSuperFoldable, TypeVisitableExt, Unnormalized, Upcast, }; use rustc_middle::{bug, span_bug}; use rustc_span::def_id::CrateNum; @@ -916,11 +916,25 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { find_attr!(self.tcx, impl_did, OnConst {directive, ..} => directive.as_deref()) .flatten() { - let (_, format_args) = self.on_unimplemented_components( + let (_, mut format_args) = self.on_unimplemented_components( trait_ref, main_obligation, diag.long_ty_path(), + false, ); + if let ty::Adt(def, args) = trait_ref.self_ty().skip_binder().kind() { + for param in self.tcx.generics_of(def.did()).own_params.iter() { + match param.kind { + GenericParamDefKind::Type { .. } + | GenericParamDefKind::Const { .. } => { + format_args + .generic_args + .push((param.name, args[param.index as usize].to_string())); + } + _ => continue, + } + } + } let CustomDiagnostic { message, label, notes, parent_label: _ } = command.eval(None, &format_args); diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs index 138c53d013ebc..f7e4ec6164c99 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs @@ -46,7 +46,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { return CustomDiagnostic::default(); } let (filter_options, format_args) = - self.on_unimplemented_components(trait_pred, obligation, long_ty_path); + self.on_unimplemented_components(trait_pred, obligation, long_ty_path, true); if let Some(command) = find_attr!(self.tcx, trait_pred.def_id(), OnUnimplemented {directive, ..} => directive.as_deref()).flatten() { command.eval( Some(&filter_options), @@ -62,6 +62,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { trait_pred: ty::PolyTraitPredicate<'tcx>, obligation: &PredicateObligation<'tcx>, long_ty_path: &mut Option, + print_infer_ty_var: bool, ) -> (FilterOptions, FormatArgs) { let (def_id, args) = (trait_pred.def_id(), trait_pred.skip_binder().trait_ref.args); let trait_pred = trait_pred.skip_binder(); @@ -244,7 +245,13 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { GenericParamDefKind::Type { .. } | GenericParamDefKind::Const { .. } => { if let Some(ty) = trait_pred.trait_ref.args[param.index as usize].as_type() { - self.tcx.short_string(ty, long_ty_path) + if print_infer_ty_var == false + && let ty::Infer(ty::TyVar(_)) = ty.kind() + { + format!("{}", param.name) + } else { + self.tcx.short_string(ty, long_ty_path) + } } else { trait_pred.trait_ref.args[param.index as usize].to_string() } diff --git a/tests/ui/diagnostic_namespace/on_const/generic_parameters_handling.current.stderr b/tests/ui/diagnostic_namespace/on_const/generic_parameters_handling.current.stderr new file mode 100644 index 0000000000000..f9e33ff4fac58 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_const/generic_parameters_handling.current.stderr @@ -0,0 +1,11 @@ +error[E0277]: the trait bound `X: const Y<_>` is not satisfied + --> $DIR/generic_parameters_handling.rs:22:6 + | +LL | .blah(); + | ^^^^ + | + = note: Self = X, Z = Z, A = u8, B = &str + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/diagnostic_namespace/on_const/generic_parameters_handling.next.stderr b/tests/ui/diagnostic_namespace/on_const/generic_parameters_handling.next.stderr new file mode 100644 index 0000000000000..f9e33ff4fac58 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_const/generic_parameters_handling.next.stderr @@ -0,0 +1,11 @@ +error[E0277]: the trait bound `X: const Y<_>` is not satisfied + --> $DIR/generic_parameters_handling.rs:22:6 + | +LL | .blah(); + | ^^^^ + | + = note: Self = X, Z = Z, A = u8, B = &str + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/diagnostic_namespace/on_const/generic_parameters_handling.rs b/tests/ui/diagnostic_namespace/on_const/generic_parameters_handling.rs new file mode 100644 index 0000000000000..32a0caf96988a --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_const/generic_parameters_handling.rs @@ -0,0 +1,25 @@ +//@ revisions: current next +//@ ignore-compare-mode-next-solver (explicit revisions) +//@[next] compile-flags: -Znext-solver +#![crate_type = "lib"] +#![feature(diagnostic_on_const, const_trait_impl)] + +pub struct X { + field: (A, B), +} + +pub const trait Y { + fn blah(&self) {} +} + +#[diagnostic::on_const(note = "Self = {Self}, Z = {Z}, A = {A}, B = {B}")] +impl Y for X {} + +const _: () = { + X { + field: (42_u8, "hello"), + } + .blah(); + //~^ ERROR the trait bound `X: const Y<_>` is not satisfied [E0277] + //~| NOTE Self = X, Z = Z, A = u8, B = &str +}; diff --git a/tests/ui/diagnostic_namespace/on_const/malformed_format_literals.rs b/tests/ui/diagnostic_namespace/on_const/malformed_format_literals.rs new file mode 100644 index 0000000000000..0c36ce25febb5 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_const/malformed_format_literals.rs @@ -0,0 +1,25 @@ +#![crate_type = "lib"] +#![feature(diagnostic_on_const)] +#![feature(const_trait_impl)] + +pub struct X; + +const trait Y { + fn blah(&self) {} +} + +#[diagnostic::on_const( + message = "my message {Foo}", + //~^ WARN unknown parameter `Foo` + label = "my label {Bar}", + //~^ WARN unknown parameter `Bar` + note = "my label {Baz}", + //~^ WARN unknown parameter `Baz` +)] +impl Y for X {} + +const _: () = { + X {}.blah(); + //~^ ERROR my message {Foo} [E0277] + +}; diff --git a/tests/ui/diagnostic_namespace/on_const/malformed_format_literals.stderr b/tests/ui/diagnostic_namespace/on_const/malformed_format_literals.stderr new file mode 100644 index 0000000000000..48bdc2c239a85 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_const/malformed_format_literals.stderr @@ -0,0 +1,36 @@ +warning: unknown parameter `Foo` + --> $DIR/malformed_format_literals.rs:12:28 + | +LL | message = "my message {Foo}", + | ^^^ + | + = help: expect either a generic argument name or `{Self}` as format argument + = note: `#[warn(malformed_diagnostic_format_literals)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default + +warning: unknown parameter `Bar` + --> $DIR/malformed_format_literals.rs:14:24 + | +LL | label = "my label {Bar}", + | ^^^ + | + = help: expect either a generic argument name or `{Self}` as format argument + +warning: unknown parameter `Baz` + --> $DIR/malformed_format_literals.rs:16:23 + | +LL | note = "my label {Baz}", + | ^^^ + | + = help: expect either a generic argument name or `{Self}` as format argument + +error[E0277]: my message {Foo} + --> $DIR/malformed_format_literals.rs:22:10 + | +LL | X {}.blah(); + | ^^^^ my label {Bar} + | + = note: my label {Baz} + +error: aborting due to 1 previous error; 3 warnings emitted + +For more information about this error, try `rustc --explain E0277`.