From d8ad8ad9cdba061923fed1a3af9afc69e1d9f477 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Sun, 10 May 2026 15:49:42 +0000 Subject: [PATCH] [WIP] Support `#[rustc_on_unimplemented]` on methods https://rust-lang.zulipchat.com/#narrow/channel/147480-t-compiler.2Fdiagnostics/topic/.60.23.5Bdiagnostic.3A.3Aon_unimplemented.5D.60.20on.20trait.20methods.3F --- .../attributes/diagnostic/on_unimplemented.rs | 20 +++++---- compiler/rustc_hir/src/attrs/diagnostic.rs | 16 +++++++ compiler/rustc_passes/src/check_attr.rs | 27 ++++++----- .../traits/on_unimplemented.rs | 33 +++++++++++--- library/core/src/iter/traits/collect.rs | 7 +++ ...ons_of_the_internal_rustc_attribute.stderr | 10 +---- ...t_fail_parsing_on_invalid_options_1.stderr | 10 +---- .../on_unimplemented/on_impl_trait.stderr | 16 +++---- .../ui/trait-bounds/vec-extend-unmet-bound.rs | 23 ++++++++++ .../vec-extend-unmet-bound.stderr | 45 +++++++++++++++++++ 10 files changed, 153 insertions(+), 54 deletions(-) create mode 100644 tests/ui/trait-bounds/vec-extend-unmet-bound.rs create mode 100644 tests/ui/trait-bounds/vec-extend-unmet-bound.stderr diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unimplemented.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unimplemented.rs index 82876c41de605..dee1f811d3083 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unimplemented.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unimplemented.rs @@ -16,14 +16,18 @@ impl OnUnimplementedParser { let span = cx.attr_span; self.span = Some(span); - if !matches!(cx.target, Target::Trait) { - cx.emit_lint( - MISPLACED_DIAGNOSTIC_ATTRIBUTES, - DiagnosticOnUnimplementedOnlyForTraits, - span, - ); - return; - } + // match cx.target { + // Target::Trait => {} + // Target::Fn => {} + // _ => { + // cx.emit_lint( + // MISPLACED_DIAGNOSTIC_ATTRIBUTES, + // DiagnosticOnUnimplementedOnlyForTraits, + // span, + // ); + // return; + // } + // } let Some(items) = parse_list(cx, args, mode) else { return }; diff --git a/compiler/rustc_hir/src/attrs/diagnostic.rs b/compiler/rustc_hir/src/attrs/diagnostic.rs index 7cbb0ea45b969..4beaeb37fe657 100644 --- a/compiler/rustc_hir/src/attrs/diagnostic.rs +++ b/compiler/rustc_hir/src/attrs/diagnostic.rs @@ -105,6 +105,20 @@ impl CustomDiagnostic { self.notes.extend(di.notes.iter().map(|n| n.format(args))) } + + pub fn merge(&mut self, other: Self) { + if other.message.is_some() { + self.message = other.message; + } + if other.label.is_some() { + self.label = other.label; + } + if other.parent_label.is_some() { + self.parent_label = other.parent_label; + } + + self.notes.extend(other.notes); + } } /// Like [std::fmt::Arguments] this is a string that has been parsed into "pieces", @@ -248,6 +262,7 @@ impl Filter { FlagOrNv::Flag(b) => options.has_flag(*b), FlagOrNv::NameValue(NameValue { name, value }) => { let value = value.format(&options.generic_args); + tracing::info!(?value); options.contains(*name, value) } }) @@ -454,6 +469,7 @@ impl FilterOptions { } } pub fn contains(&self, name: Name, value: String) -> bool { + tracing::info!(?name, ?value); match name { Name::SelfUpper => self.self_types.contains(&value), Name::FromDesugaring => self.from_desugaring.is_some_and(|ds| ds.matches(&value)), diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index f1596a0685768..55560111516dd 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -153,7 +153,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { Attribute::Parsed(AttributeKind::Deprecated { span: attr_span, .. }) => { self.check_deprecated(hir_id, *attr_span, target) } - Attribute::Parsed(AttributeKind::TargetFeature{ attr_span, ..}) => { + Attribute::Parsed(AttributeKind::TargetFeature{ attr_span, .. }) => { self.check_target_feature(hir_id, *attr_span, target, attrs) } Attribute::Parsed(AttributeKind::RustcDumpObjectLifetimeDefaults) => { @@ -182,28 +182,32 @@ impl<'tcx> CheckAttrVisitor<'tcx> { } &Attribute::Parsed(AttributeKind::Sanitize { on_set, off_set, rtsan: _, span: attr_span}) => { self.check_sanitize(attr_span, on_set | off_set, span, target); - }, + } Attribute::Parsed(AttributeKind::Link(_, attr_span)) => { self.check_link(hir_id, *attr_span, span, target) - }, + } Attribute::Parsed(AttributeKind::MacroExport { span, .. }) => { self.check_macro_export(hir_id, *span, target) - }, + } Attribute::Parsed(AttributeKind::RustcLegacyConstGenerics{attr_span, fn_indexes}) => { self.check_rustc_legacy_const_generics(item, *attr_span, fn_indexes) - }, + } Attribute::Parsed(AttributeKind::Doc(attr)) => self.check_doc_attrs(attr, hir_id, target), Attribute::Parsed(AttributeKind::EiiImpls(impls)) => { self.check_eii_impl(impls, target) - }, + } Attribute::Parsed(AttributeKind::RustcMustImplementOneOf { attr_span, fn_names }) => { self.check_rustc_must_implement_one_of(*attr_span, fn_names, hir_id,target) - }, - Attribute::Parsed(AttributeKind::OnUnimplemented{directive}) => {self.check_diagnostic_on_unimplemented(hir_id, directive.as_deref())}, - Attribute::Parsed(AttributeKind::OnConst{span, ..}) => {self.check_diagnostic_on_const(*span, hir_id, target, item)}, + } + Attribute::Parsed(AttributeKind::OnUnimplemented { directive } ) => { + self.check_diagnostic_on_unimplemented(hir_id, directive.as_deref()) + } + Attribute::Parsed(AttributeKind::OnConst{ span, .. }) => { + self.check_diagnostic_on_const(*span, hir_id, target, item) + } Attribute::Parsed(AttributeKind::OnMove { directive }) => { self.check_diagnostic_on_move(hir_id, directive.as_deref()) - }, + } Attribute::Parsed( // tidy-alphabetical-start AttributeKind::RustcAllowIncoherentImpl(..) @@ -521,6 +525,9 @@ impl<'tcx> CheckAttrVisitor<'tcx> { ) } }) + } else { + let node = self.tcx.hir_node(hir_id); + tracing::info!(?node); } } } 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 b15f528b7261e..50272bb8aa59b 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 @@ -37,19 +37,37 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { obligation: &PredicateObligation<'tcx>, long_ty_path: &mut Option, ) -> CustomDiagnostic { + let mut custom = CustomDiagnostic::default(); if trait_pred.polarity() != ty::PredicatePolarity::Positive { - return CustomDiagnostic::default(); + return custom; } let (filter_options, format_args) = self.on_unimplemented_components(trait_pred, obligation, long_ty_path); - if let Some(command) = find_attr!(self.tcx, trait_pred.def_id(), OnUnimplemented {directive, ..} => directive.as_deref()).flatten() { - command.eval( - Some(&filter_options), - &format_args, + if let Some(command) = find_attr!( + self.tcx, + trait_pred.def_id(), + OnUnimplemented { directive, .. } => directive.as_deref() + ) + .flatten() + { + custom = command.eval(Some(&filter_options), &format_args) + } + if let ObligationCauseCode::FunctionArg { parent_code, .. } = obligation.cause.code() + && let ObligationCauseCode::WhereClauseInExpr(def_id, _, _, _) = &**parent_code + && let Some(command) = find_attr!( + self.tcx, + *def_id, + OnUnimplemented { directive, .. } => directive.as_deref() ) - } else { - CustomDiagnostic::default() + .flatten() + { + tracing::info!("{:#?}", obligation.cause.code()); + let x = command.eval(Some(&filter_options), &format_args); + tracing::info!(?x); + custom.merge(x); } + tracing::info!(?custom); + custom } pub(crate) fn on_unimplemented_components( @@ -250,6 +268,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { .collect(); let format_args = FormatArgs { this, this_sugared, generic_args, item_context }; + tracing::info!("{filter_options:#?}"); (filter_options, format_args) } } diff --git a/library/core/src/iter/traits/collect.rs b/library/core/src/iter/traits/collect.rs index 9c3edfd4192d5..1dad813971670 100644 --- a/library/core/src/iter/traits/collect.rs +++ b/library/core/src/iter/traits/collect.rs @@ -413,6 +413,13 @@ pub trait Extend { /// assert_eq!("abcdef", &message); /// ``` #[stable(feature = "rust1", since = "1.0.0")] + #[rustc_on_unimplemented( + message = "can't extend `{Self}` with an iterator of `{A}` items", + on( + all(Self = "alloc::vec::Vec<&str>", A = "alloc::string::String"), + note = "you might have meant to extend a `Vec` or pass in an `Iterator`" + ) + )] fn extend>(&mut self, iter: T); /// Extends a collection with exactly one element. diff --git a/tests/ui/diagnostic_namespace/on_unimplemented/do_not_accept_options_of_the_internal_rustc_attribute.stderr b/tests/ui/diagnostic_namespace/on_unimplemented/do_not_accept_options_of_the_internal_rustc_attribute.stderr index 9ed72d334b271..f3b2165784c9b 100644 --- a/tests/ui/diagnostic_namespace/on_unimplemented/do_not_accept_options_of_the_internal_rustc_attribute.stderr +++ b/tests/ui/diagnostic_namespace/on_unimplemented/do_not_accept_options_of_the_internal_rustc_attribute.stderr @@ -144,14 +144,6 @@ LL | #[diagnostic::on_unimplemented = "Message"] | = help: only `message`, `note` and `label` are allowed as options -warning: `#[diagnostic::on_unimplemented]` can only be applied to trait definitions - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:22:1 - | -LL | #[diagnostic::on_unimplemented(message = "Not allowed to apply it on a impl")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: `#[warn(misplaced_diagnostic_attributes)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default - error[E0277]: trait has `()` and `i32` as params --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:53:15 | @@ -212,6 +204,6 @@ note: required by a bound in `takes_baz` LL | fn takes_baz(_: impl Baz) {} | ^^^ required by this bound in `takes_baz` -error: aborting due to 3 previous errors; 19 warnings emitted +error: aborting due to 3 previous errors; 18 warnings emitted For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/diagnostic_namespace/on_unimplemented/do_not_fail_parsing_on_invalid_options_1.stderr b/tests/ui/diagnostic_namespace/on_unimplemented/do_not_fail_parsing_on_invalid_options_1.stderr index d5c7f25efbc29..8dc3e40c3d5ee 100644 --- a/tests/ui/diagnostic_namespace/on_unimplemented/do_not_fail_parsing_on_invalid_options_1.stderr +++ b/tests/ui/diagnostic_namespace/on_unimplemented/do_not_fail_parsing_on_invalid_options_1.stderr @@ -16,14 +16,6 @@ LL | #[diagnostic::on_unimplemented(unsupported = "foo")] = help: only `message`, `note` and `label` are allowed as options = note: `#[warn(malformed_diagnostic_attributes)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default -warning: `#[diagnostic::on_unimplemented]` can only be applied to trait definitions - --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:7:1 - | -LL | #[diagnostic::on_unimplemented(message = "Baz")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: `#[warn(misplaced_diagnostic_attributes)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default - warning: malformed `diagnostic::on_unimplemented` attribute --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:11:50 | @@ -159,6 +151,6 @@ note: required by a bound in `take_test` LL | fn take_test(_: impl Test) {} | ^^^^ required by this bound in `take_test` -error: aborting due to 5 previous errors; 8 warnings emitted +error: aborting due to 5 previous errors; 7 warnings emitted For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/diagnostic_namespace/on_unimplemented/on_impl_trait.stderr b/tests/ui/diagnostic_namespace/on_unimplemented/on_impl_trait.stderr index e9d0c6e5daf55..ad9d6af8822d4 100644 --- a/tests/ui/diagnostic_namespace/on_unimplemented/on_impl_trait.stderr +++ b/tests/ui/diagnostic_namespace/on_unimplemented/on_impl_trait.stderr @@ -1,19 +1,13 @@ -warning: `#[diagnostic::on_unimplemented]` can only be applied to trait definitions - --> $DIR/on_impl_trait.rs:8:1 - | -LL | #[diagnostic::on_unimplemented(message = "blah", label = "blah", note = "blah")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: `#[warn(misplaced_diagnostic_attributes)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default - -error[E0277]: the trait bound `{integer}: Alias` is not satisfied +error[E0277]: blah --> $DIR/on_impl_trait.rs:16:9 | LL | foo(&1); - | --- ^^ the trait `Test` is not implemented for `{integer}` + | --- ^^ blah | | | required by a bound introduced by this call | + = help: the trait `Test` is not implemented for `{integer}` + = note: blah help: this trait has no implementations, consider adding one --> $DIR/on_impl_trait.rs:6:1 | @@ -26,6 +20,6 @@ note: required by a bound in `foo` LL | fn foo(v: &T) {} | ^^^^^ required by this bound in `foo` -error: aborting due to 1 previous error; 1 warning emitted +error: aborting due to 1 previous error For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/trait-bounds/vec-extend-unmet-bound.rs b/tests/ui/trait-bounds/vec-extend-unmet-bound.rs new file mode 100644 index 0000000000000..ba44104ae2efa --- /dev/null +++ b/tests/ui/trait-bounds/vec-extend-unmet-bound.rs @@ -0,0 +1,23 @@ +fn main() { + let mut x = vec!["a", "b", "c"]; + x.extend([String::from("z")]); + //~^ ERROR can't extend `Vec<&str>` with an iterator of `String` items + //~| NOTE the trait `Extend` is not implemented for `Vec<&str>` + //~| NOTE you might have meant to extend a `Vec` or pass in an `Iterator` + //~| HELP `Vec` implements trait `Extend` + x.extend(String::from("z")); + //~^ ERROR `String` is not an iterator + //~| NOTE `String` is not an iterator; try calling `.chars()` or `.bytes()` + //~| NOTE required by a bound introduced by this call + //~| HELP the trait `Iterator` is not implemented for `String` + //~| NOTE required for `String` to implement `IntoIterator` + //~| NOTE required by a bound in `extend` + x.extend(1); + //~^ ERROR `{integer}` is not an iterator + //~| NOTE `{integer}` is not an iterator + //~| NOTE required by a bound introduced by this call + //~| HELP the trait `Iterator` is not implemented for `{integer}` + //~| NOTE required for `{integer}` to implement `IntoIterator` + //~| NOTE required by a bound in `extend` + //~| NOTE if you want to iterate between `start` until a value `end` +} diff --git a/tests/ui/trait-bounds/vec-extend-unmet-bound.stderr b/tests/ui/trait-bounds/vec-extend-unmet-bound.stderr new file mode 100644 index 0000000000000..ad88f81d615db --- /dev/null +++ b/tests/ui/trait-bounds/vec-extend-unmet-bound.stderr @@ -0,0 +1,45 @@ +error[E0277]: can't extend `Vec<&str>` with an iterator of `String` items + --> $DIR/vec-extend-unmet-bound.rs:3:7 + | +LL | x.extend([String::from("z")]); + | ^^^^^^ the trait `Extend` is not implemented for `Vec<&str>` + | + = note: you might have meant to extend a `Vec` or pass in an `Iterator` +help: `Vec` implements trait `Extend` + --> $SRC_DIR/alloc/src/vec/mod.rs:LL:COL + | + = note: `Extend` + ::: $SRC_DIR/alloc/src/vec/mod.rs:LL:COL + | + = note: `Extend<&T>` + +error[E0277]: `String` is not an iterator + --> $DIR/vec-extend-unmet-bound.rs:8:14 + | +LL | x.extend(String::from("z")); + | ------ ^^^^^^^^^^^^^^^^^ `String` is not an iterator; try calling `.chars()` or `.bytes()` + | | + | required by a bound introduced by this call + | + = help: the trait `Iterator` is not implemented for `String` + = note: required for `String` to implement `IntoIterator` +note: required by a bound in `extend` + --> $SRC_DIR/core/src/iter/traits/collect.rs:LL:COL + +error[E0277]: `{integer}` is not an iterator + --> $DIR/vec-extend-unmet-bound.rs:15:14 + | +LL | x.extend(1); + | ------ ^ `{integer}` is not an iterator + | | + | required by a bound introduced by this call + | + = help: the trait `Iterator` is not implemented for `{integer}` + = note: if you want to iterate between `start` until a value `end`, use the exclusive range syntax `start..end` or the inclusive range syntax `start..=end` + = note: required for `{integer}` to implement `IntoIterator` +note: required by a bound in `extend` + --> $SRC_DIR/core/src/iter/traits/collect.rs:LL:COL + +error: aborting due to 3 previous errors + +For more information about this error, try `rustc --explain E0277`.