Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use crate::parser::{ArgParser, MetaItemListParser, MetaItemOrLitParser, MetaItem
pub(crate) mod do_not_recommend;
pub(crate) mod on_const;
pub(crate) mod on_move;
pub(crate) mod on_type_error;
pub(crate) mod on_unimplemented;
pub(crate) mod on_unknown;

Expand All @@ -38,6 +39,8 @@ pub(crate) enum Mode {
DiagnosticOnMove,
/// `#[diagnostic::on_unknown]`
DiagnosticOnUnknown,
/// `#[diagnostic::on_type_error]`
DiagnosticOnTypeError,
}

impl Mode {
Expand All @@ -48,12 +51,15 @@ impl Mode {
Self::DiagnosticOnConst => "diagnostic::on_const",
Self::DiagnosticOnMove => "diagnostic::on_move",
Self::DiagnosticOnUnknown => "diagnostic::on_unknown",
Self::DiagnosticOnTypeError => "diagnostic::on_type_error",
}
}

fn expected_options(&self) -> &'static str {
const DEFAULT: &str =
"at least one of the `message`, `note` and `label` options are expected";
const DIAGNOSTIC_ON_TYPE_ERROR_EXPECTED_OPTIONS: &str =
"at least a single `note` option is expected";
match self {
Self::RustcOnUnimplemented => {
"see <https://rustc-dev-guide.rust-lang.org/diagnostics.html#rustc_on_unimplemented>"
Expand All @@ -62,11 +68,14 @@ impl Mode {
Self::DiagnosticOnConst => DEFAULT,
Self::DiagnosticOnMove => DEFAULT,
Self::DiagnosticOnUnknown => DEFAULT,
Self::DiagnosticOnTypeError => DIAGNOSTIC_ON_TYPE_ERROR_EXPECTED_OPTIONS,
}
}

fn allowed_options(&self) -> &'static str {
const DEFAULT: &str = "only `message`, `note` and `label` are allowed as options";
const DIAGNOSTIC_ON_TYPE_ERROR_ALLOWED_OPTIONS: &str =
"only `note` is allowed as option for `diagnostic::on_type_error`";
match self {
Self::RustcOnUnimplemented => {
"see <https://rustc-dev-guide.rust-lang.org/diagnostics.html#rustc_on_unimplemented>"
Expand All @@ -75,6 +84,7 @@ impl Mode {
Self::DiagnosticOnConst => DEFAULT,
Self::DiagnosticOnMove => DEFAULT,
Self::DiagnosticOnUnknown => DEFAULT,
Self::DiagnosticOnTypeError => DIAGNOSTIC_ON_TYPE_ERROR_ALLOWED_OPTIONS,
}
}
}
Expand Down Expand Up @@ -268,6 +278,10 @@ fn parse_directive_items<'p, S: Stage>(
}
};
match (mode, name) {
(Mode::DiagnosticOnTypeError, sym::message)
| (Mode::DiagnosticOnTypeError, sym::label) => {
malformed!()
}
Comment on lines +281 to +284
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
(Mode::DiagnosticOnTypeError, sym::message)
| (Mode::DiagnosticOnTypeError, sym::label) => {
malformed!()
}

Keep in mind that the complexity of this entire match will grow over time as more attributes and options get added. Can you instead change the existing arms to match on the diagnostic attributes that do allow message and label?

With that, I mean:

        match (mode, name) {
            (Mode::DiagnosticOnConst | Mode::DiagnosticOnMove | /* etc */ , sym::message) => {
                   // ...
            }

etc.

While it is more wordy, it is easier to understand from a control flow perspective. You can also do use Mode::*; inside this function to use the enum variant names by themselves.

(_, sym::message) => {
let value = or_malformed!(value?);
if let Some(message) = &message {
Expand Down Expand Up @@ -332,7 +346,6 @@ fn parse_directive_items<'p, S: Stage>(
malformed!();
}
}

_other => {
malformed!();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use rustc_feature::template;
use rustc_hir::attrs::AttributeKind;
use rustc_span::sym;

use crate::attributes::diagnostic::*;
use crate::attributes::prelude::*;
use crate::context::{AcceptContext, Stage};
use crate::parser::ArgParser;
use crate::target_checking::{ALL_TARGETS, AllowedTargets};

#[derive(Default)]
pub(crate) struct OnTypeErrorParser {
span: Option<Span>,
directive: Option<(Span, Directive)>,
}

impl OnTypeErrorParser {
fn parse<'sess, S: Stage>(
&mut self,
cx: &mut AcceptContext<'_, 'sess, S>,
args: &ArgParser,
mode: Mode,
) {
if !cx.features().diagnostic_on_type_error() {
// `UnknownDiagnosticAttribute` is emitted in rustc_resolve/macros.rs
return;
}

let span = cx.attr_span;
self.span = Some(span);

let Some(items) = parse_list(cx, args, mode) else { return };

if let Some(directive) = parse_directive_items(cx, mode, items.mixed(), true) {
merge_directives(cx, &mut self.directive, (span, directive));
}
}
}

impl<S: Stage> AttributeParser<S> for OnTypeErrorParser {
const ATTRIBUTES: AcceptMapping<Self, S> = &[(
&[sym::diagnostic, sym::on_type_error],
template!(List: &[r#" note = "...""#]),
Copy link
Copy Markdown
Contributor

@estebank estebank Apr 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
template!(List: &[r#" note = "...""#]),
template!(List: &[r#"note = "...""#]),

View changes since the review

|this, cx, args| {
this.parse(cx, args, Mode::DiagnosticOnTypeError);
},
)];

const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS);

fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
if let Some(span) = self.span {
Some(AttributeKind::OnTypeError {
span,
directive: self.directive.map(|d| Box::new(d.1)),
})
} else {
None
}
}
}
2 changes: 2 additions & 0 deletions compiler/rustc_attr_parsing/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ use crate::attributes::deprecation::*;
use crate::attributes::diagnostic::do_not_recommend::*;
use crate::attributes::diagnostic::on_const::*;
use crate::attributes::diagnostic::on_move::*;
use crate::attributes::diagnostic::on_type_error::*;
use crate::attributes::diagnostic::on_unimplemented::*;
use crate::attributes::diagnostic::on_unknown::*;
use crate::attributes::doc::*;
Expand Down Expand Up @@ -156,6 +157,7 @@ attribute_parsers!(
NakedParser,
OnConstParser,
OnMoveParser,
OnTypeErrorParser,
OnUnimplementedParser,
OnUnknownParser,
RustcAlignParser,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,7 @@ fn try_extract_error_from_region_constraints<'a, 'tcx>(
.try_report_from_nll()
.or_else(|| {
if let SubregionOrigin::Subtype(trace) = cause {
tracing::info!("borrow checker");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
tracing::info!("borrow checker");

if it's useful to you then leave it for now, but it should be removed at some point

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is noise, will remove it.

Some(infcx.err_ctxt().report_and_explain_type_error(
*trace,
infcx.tcx.param_env(generic_param_scope),
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_feature/src/unstable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,8 @@ declare_features! (
(unstable, diagnostic_on_const, "1.93.0", Some(143874)),
/// Allows giving on-move borrowck custom diagnostic messages for a type
(unstable, diagnostic_on_move, "1.96.0", Some(154181)),
/// Allows giving custom types diagnostic messages on type errors
(unstable, diagnostic_on_type_error, "CURRENT_RUSTC_VERSION", Some(155382)),
/// Allows giving unresolved imports a custom diagnostic message
(unstable, diagnostic_on_unknown, "1.96.0", Some(152900)),
/// Allows `#[doc(cfg(...))]`.
Expand Down
6 changes: 6 additions & 0 deletions compiler/rustc_hir/src/attrs/data_structures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1194,6 +1194,12 @@ pub enum AttributeKind {
directive: Option<Box<Directive>>,
},

/// Represents`#[diagnostic::on_type_error]`.
OnTypeError {
span: Span,
directive: Option<Box<Directive>>,
},

/// Represents `#[rustc_on_unimplemented]` and `#[diagnostic::on_unimplemented]`.
OnUnimplemented {
span: Span,
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_hir/src/attrs/encode_cross_crate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ impl AttributeKind {
NonExhaustive(..) => Yes, // Needed for rustdoc
OnConst { .. } => Yes,
OnMove { .. } => Yes,
OnTypeError { .. } => Yes,
OnUnimplemented { .. } => Yes,
OnUnknown { .. } => Yes,
Optimize(..) => No,
Expand Down
38 changes: 38 additions & 0 deletions compiler/rustc_lint_defs/src/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ declare_lint_pass! {
NEVER_TYPE_FALLBACK_FLOWING_INTO_UNSAFE,
NON_CONTIGUOUS_RANGE_ENDPOINTS,
NON_EXHAUSTIVE_OMITTED_PATTERNS,
ON_TYPE_ERROR_MULTIPLE_GENERICS,
OUT_OF_SCOPE_MACRO_CALLS,
OVERLAPPING_RANGE_ENDPOINTS,
PATTERNS_IN_FNS_WITHOUT_BODY,
Expand Down Expand Up @@ -4574,6 +4575,43 @@ declare_lint! {
Warn,
"detects diagnostic attribute with malformed diagnostic format literals",
}

declare_lint! {
/// The `on_type_error_multiple_generics` lint detects when
/// `#[diagnostic::on_type_error]` is used on items with more than one generic parameter.
///
/// ### Example
///
/// ```rust,ignore (requires nightly feature)
/// #![feature(diagnostic_on_type_error)]
/// #[diagnostic::on_type_error(note = "too many generics")]
/// struct TooMany<T, U>(T, U);
/// ```
///
/// This will produce:
///
/// ```text
/// warning: `#[diagnostic::on_type_error]` only supports one ADT generic parameter, but found `2`
/// --> lint_example.rs:3:15
/// |
/// 3 | struct TooMany<T, U>(T, U);
/// | ^^^^^^
/// |
/// = note: `#[warn(on_type_error_multiple_generics)]` on by default
/// ```
///
/// ### Explanation
///
/// The `#[diagnostic::on_type_error]` attribute currently only supports items
/// with a single generic parameter. Using it on an item with multiple generic
/// parameters will cause the attribute to be ignored.
///
/// [reference]: https://doc.rust-lang.org/nightly/reference/attributes/diagnostics.html#the-diagnostic-tool-attribute-namespace
pub ON_TYPE_ERROR_MULTIPLE_GENERICS,
Warn,
"detects use of #[diagnostic::on_type_error] with multiple generic parameters",
}

Comment on lines +4579 to +4602
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This lint feels like an overly specific case of "you're using the attribute wrong". I guess we don't really have a specific lint for that, maybe MALFORMED_DIAGNOSTIC_ATTRIBUTES?

It also looks like it isn't going to age well when we start expanding what on_type_error can do.

I propose we drop the lint and just use MALFORMED_DIAGNOSTIC_ATTRIBUTES for now. Adding a more general "you're using it wrong" lint is out of scope of this PR and also requires lang team discussion etc, so let's not do that here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that is valid. Will remove.

declare_lint! {
/// The `ambiguous_glob_imports` lint detects glob imports that should report ambiguity
/// errors, but previously didn't do that due to rustc bugs.
Expand Down
77 changes: 76 additions & 1 deletion compiler/rustc_passes/src/check_attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ use rustc_session::config::CrateType;
use rustc_session::lint;
use rustc_session::lint::builtin::{
CONFLICTING_REPR_HINTS, INVALID_DOC_ATTRIBUTES, MALFORMED_DIAGNOSTIC_FORMAT_LITERALS,
MISPLACED_DIAGNOSTIC_ATTRIBUTES, UNUSED_ATTRIBUTES,
MISPLACED_DIAGNOSTIC_ATTRIBUTES, ON_TYPE_ERROR_MULTIPLE_GENERICS, UNUSED_ATTRIBUTES,
};
use rustc_session::parse::feature_err;
use rustc_span::edition::Edition;
Expand Down Expand Up @@ -79,6 +79,10 @@ struct DiagnosticOnUnknownOnlyForImports {
item_span: Span,
}

#[derive(Diagnostic)]
#[diag("`#[diagnostic::on_type_error]` can only be applied to enums, structs or unions")]
struct DiagnosticOnTypeErrorOnlyForAdt;

fn target_from_impl_item<'tcx>(tcx: TyCtxt<'tcx>, impl_item: &hir::ImplItem<'_>) -> Target {
match impl_item.kind {
hir::ImplItemKind::Const(..) => Target::AssocConst,
Expand Down Expand Up @@ -228,6 +232,9 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
Attribute::Parsed(AttributeKind::OnMove { span, directive }) => {
self.check_diagnostic_on_move(*span, hir_id, target, directive.as_deref())
},
Attribute::Parsed(AttributeKind::OnTypeError{ span, directive }) => {
self.check_diagnostic_on_type_error(*span, hir_id, target, directive.as_deref())
},
Attribute::Parsed(
// tidy-alphabetical-start
AttributeKind::RustcAllowIncoherentImpl(..)
Expand Down Expand Up @@ -688,6 +695,74 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
}
}

/// Checks if `#[diagnostic::on_type_error]` is applied to an ADT definition
fn check_diagnostic_on_type_error(
&self,
attr_span: Span,
hir_id: HirId,
target: Target,
directive: Option<&Directive>,
) {
if !matches!(target, Target::Enum | Target::Struct | Target::Union) {
self.tcx.emit_node_span_lint(
MISPLACED_DIAGNOSTIC_ATTRIBUTES,
hir_id,
attr_span,
DiagnosticOnTypeErrorOnlyForAdt,
);
}

if let Some(directive) = directive {
if let Node::Item(Item {
kind:
ItemKind::Struct(_, generics, _)
| ItemKind::Enum(_, generics, _)
| ItemKind::Union(_, generics, _),
..
}) = self.tcx.hir_node(hir_id)
{
let generic_count = generics
.params
.iter()
.filter(|p| !matches!(p.kind, GenericParamKind::Lifetime { .. }))
.count();

// Enforce: at most one generic
if generic_count > 1 {
self.tcx.emit_node_span_lint(
ON_TYPE_ERROR_MULTIPLE_GENERICS,
hir_id,
generics.span,
errors::OnTypeErrorMultipleGenerics { count: generic_count },
);
}
Copy link
Copy Markdown
Contributor Author

@Unique-Usman Unique-Usman Apr 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be exactly one ?

View changes since the review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes please :) Also can you rename the error type...to something like NotExactlyOneGeneric perhaps?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will do.


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
}
});

let is_allowed = argument_name == sym::Expected || argument_name == sym::Found;
if !(has_generic | is_allowed) {
self.tcx.emit_node_span_lint(
MALFORMED_DIAGNOSTIC_FORMAT_LITERALS,
hir_id,
span,
errors::OnTypeErrorMalformedFormatLiterals { name: argument_name },
)
}
});
}
}
}

/// Checks if an `#[inline]` is applied to a function or a closure.
fn check_inline(&self, hir_id: HirId, attr_span: Span, kind: &InlineAttr, target: Target) {
match target {
Expand Down
15 changes: 15 additions & 0 deletions compiler/rustc_passes/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1307,3 +1307,18 @@ pub(crate) struct UnknownFormatParameterForOnUnimplementedAttr {
pub(crate) struct OnMoveMalformedFormatLiterals {
pub name: Symbol,
}

#[derive(Diagnostic)]
#[diag("unknown parameter `{$name}`")]
#[help(r#"expect either a generic argument name, {"`{Self}`"}, {"`{Expected}`"} or {"`{Found}`"} as format argument"#)]
pub(crate) struct OnTypeErrorMalformedFormatLiterals {
pub name: Symbol,
}

#[derive(Diagnostic)]
#[diag(
"`#[diagnostic::on_type_error]` only supports one ADT generic parameter, but found `{$count}`"
)]
pub(crate) struct OnTypeErrorMultipleGenerics {
pub count: usize,
}
1 change: 1 addition & 0 deletions compiler/rustc_resolve/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
(sym::on_move, Some(sym::diagnostic_on_move)),
(sym::on_const, Some(sym::diagnostic_on_const)),
(sym::on_unknown, Some(sym::diagnostic_on_unknown)),
(sym::on_type_error, Some(sym::diagnostic_on_type_error)),
];

if res == Res::NonMacroAttr(NonMacroAttrKind::Tool)
Expand Down
Loading
Loading