Skip to content
Draft
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
14 changes: 12 additions & 2 deletions 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,
}

fn merge_directives<S: Stage>(
Expand Down Expand Up @@ -132,6 +135,13 @@ fn parse_directive_items<'p, S: Stage>(
span,
);
}
Mode::DiagnosticOnTypeError => {
cx.emit_lint(
MALFORMED_DIAGNOSTIC_ATTRIBUTES,
AttributeLintKind::MalformedOnTypeErrorAttr { span },
span,
);
}
}
continue;
}}
Expand All @@ -149,8 +159,8 @@ fn parse_directive_items<'p, S: Stage>(
match mode {
Mode::RustcOnUnimplemented => {
cx.emit_err(NoValueInOnUnimplemented { span: item.span() });
}
Mode::DiagnosticOnUnimplemented |Mode::DiagnosticOnConst | Mode::DiagnosticOnMove | Mode::DiagnosticOnUnknown => {
},
Mode::DiagnosticOnUnimplemented |Mode::DiagnosticOnConst | Mode::DiagnosticOnMove | Mode::DiagnosticOnUnknown | Mode::DiagnosticOnTypeError => {
cx.emit_lint(
MALFORMED_DIAGNOSTIC_ATTRIBUTES,
AttributeLintKind::IgnoredDiagnosticOption {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
use rustc_feature::template;
use rustc_hir::attrs::AttributeKind;
use rustc_hir::lints::AttributeLintKind;
use rustc_session::lint::builtin::MALFORMED_DIAGNOSTIC_ATTRIBUTES;
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() {
return;
}

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

let Some(list) = args.list() else {
cx.emit_lint(
MALFORMED_DIAGNOSTIC_ATTRIBUTES,
AttributeLintKind::MissingOptionsForOnTypeError,
span,
);
return;
};

if list.is_empty() {
cx.emit_lint(
MALFORMED_DIAGNOSTIC_ATTRIBUTES,
AttributeLintKind::OnTypeErrorMalformedAttrExpectedLiteralOrDelimiter,
list.span,
);
return;
}

if let Some(directive) = parse_directive_items(cx, mode, list.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#"/*opt*/ message = "...", /*opt*/ label = "...", /*opt*/ note = "...""#]),
|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 @@ -154,6 +155,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,10 +509,12 @@ 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");
Some(infcx.err_ctxt().report_and_explain_type_error(
*trace,
infcx.tcx.param_env(generic_param_scope),
TypeError::RegionsPlaceholderMismatch,
None,
))
} else {
None
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, "CURRENT_RUSTC_VERSION", Some(154181)),
/// Allows giving custom types diagnostic messages on type erros
(unstable, diagnostic_on_type_error, "CURRENT_RUSTC_VERSION", Some(155382)),
/// Allows giving unresolved imports a custom diagnostic message
(unstable, diagnostic_on_unknown, "CURRENT_RUSTC_VERSION", 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
18 changes: 17 additions & 1 deletion compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1997,6 +1997,7 @@ impl<'a, 'b, 'tcx> FnCallDiagCtxt<'a, 'b, 'tcx> {
),
self.param_env,
terr,
None,
);
let call_name = self.call_metadata.call_name;
err.span_label(
Expand Down Expand Up @@ -2116,6 +2117,7 @@ impl<'a, 'b, 'tcx> FnCallDiagCtxt<'a, 'b, 'tcx> {
trace,
self.arg_matching_ctxt.param_env,
*e,
None,
);
self.arg_matching_ctxt.suggest_confusable(&mut err);
reported = Some(err.emit());
Expand All @@ -2135,7 +2137,21 @@ impl<'a, 'b, 'tcx> FnCallDiagCtxt<'a, 'b, 'tcx> {
let (formal_ty, expected_ty) = self.formal_and_expected_inputs[expected_idx];
let (provided_ty, provided_arg_span) = self.provided_arg_tys[provided_idx];
let trace = self.mk_trace(provided_arg_span, (formal_ty, expected_ty), provided_ty);
let mut err = self.err_ctxt().report_and_explain_type_error(trace, self.param_env, err);

let def_site_ty = if let Some(constructor_def_id) = self.fn_def_id {
let struct_def_id = self.tcx.parent(constructor_def_id);
let parent_ty = self.tcx.type_of(struct_def_id).skip_binder();

if let ty::Adt(_, _) = parent_ty.kind() { Some(parent_ty) } else { None }
} else {
None
};
Copy link
Copy Markdown
Contributor

@estebank estebank Apr 14, 2026

Choose a reason for hiding this comment

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

View changes since the review

as discussed we shouldn't do this now, only deal with the simple case that can be gleaned from only the expected/found mismatch.

let mut err = self.err_ctxt().report_and_explain_type_error(
trace,
self.param_env,
err,
def_site_ty,
);
self.emit_coerce_suggestions(
&mut err,
self.provided_args[provided_idx],
Expand Down
12 changes: 12 additions & 0 deletions compiler/rustc_lint/src/early/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,18 @@ impl<'a> Diagnostic<'a, ()> for DecorateAttrLint<'_, '_, '_> {
&AttributeLintKind::MissingOptionsForOnUnknown => {
lints::MissingOptionsForOnUnknownAttr.into_diag(dcx, level)
}
&AttributeLintKind::MalformedOnTypeErrorAttr { span } => {
lints::MalformedOnTypeErrorAttrLint { span }.into_diag(dcx, level)
}
&AttributeLintKind::OnTypeErrorMalformedFormatLiterals { name } => {
lints::OnTypeErrorMalformedFormatLiterals { name }.into_diag(dcx, level)
}
&AttributeLintKind::OnTypeErrorMalformedAttrExpectedLiteralOrDelimiter => {
lints::OnTypeErrorMalformedAttrExpectedLiteralOrDelimiter.into_diag(dcx, level)
}
&AttributeLintKind::MissingOptionsForOnTypeError => {
lints::MissingOptionsForOnTypeErrorAttr.into_diag(dcx, level)
}
}
}
}
29 changes: 29 additions & 0 deletions compiler/rustc_lint/src/lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3603,6 +3603,11 @@ pub(crate) struct MissingOptionsForOnConstAttr;
#[help("at least one of the `message`, `note` and `label` options are expected")]
pub(crate) struct MissingOptionsForOnMoveAttr;

#[derive(Diagnostic)]
#[diag("missing options for `on_type_error` attribute")]
#[help("at least one of the `message`, `note` and `label` options are expected")]
pub(crate) struct MissingOptionsForOnTypeErrorAttr;

#[derive(Diagnostic)]
#[diag("malformed `on_unimplemented` attribute")]
#[help("only `message`, `note` and `label` are allowed as options")]
Expand Down Expand Up @@ -3642,16 +3647,40 @@ pub(crate) struct MalformedOnMoveAttrLint {
pub span: Span,
}

#[derive(Diagnostic)]
#[diag("unknown or malformed `on_type_error` attribute")]
#[help(
"only `message`, `note` and `label` are allowed as options. Their values must be string literals"
)]
pub(crate) struct MalformedOnTypeErrorAttrLint {
#[label("invalid option found here")]
pub span: Span,
}

#[derive(Diagnostic)]
#[diag("unknown parameter `{$name}`")]
#[help("expect `Self` as format argument")]
pub(crate) struct OnMoveMalformedFormatLiterals {
pub name: Symbol,
}

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

#[derive(Diagnostic)]
#[diag("expected a literal or missing delimiter")]
#[help(
"only literals are allowed as values for the `message`, `note` and `label` options. These options must be separated by a comma"
)]
pub(crate) struct OnMoveMalformedAttrExpectedLiteralOrDelimiter;

#[derive(Diagnostic)]
#[diag("expected a literal or missing delimiter")]
#[help(
"only literals are allowed as values for the `message`, `note` and `label` options. These options must be separated by a comma"
)]
pub(crate) struct OnTypeErrorMalformedAttrExpectedLiteralOrDelimiter;
8 changes: 8 additions & 0 deletions compiler/rustc_lint_defs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,9 @@ pub enum AttributeLintKind {
MalformedOnMoveAttr {
span: Span,
},
MalformedOnTypeErrorAttr {
span: Span,
},
MalformedDiagnosticFormat {
warning: FormatWarning,
},
Expand All @@ -762,10 +765,15 @@ pub enum AttributeLintKind {
MissingOptionsForOnConst,
MissingOptionsForOnUnknown,
MissingOptionsForOnMove,
MissingOptionsForOnTypeError,
OnMoveMalformedFormatLiterals {
name: Symbol,
},
OnTypeErrorMalformedFormatLiterals {
name: Symbol,
},
OnMoveMalformedAttrExpectedLiteralOrDelimiter,
OnTypeErrorMalformedAttrExpectedLiteralOrDelimiter,
}

#[derive(Debug, Clone, HashStable_Generic)]
Expand Down
57 changes: 57 additions & 0 deletions compiler/rustc_passes/src/check_attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ struct DiagnosticOnUnknownOnlyForImports {
item_span: Span,
}

#[derive(Diagnostic)]
#[diag("`#[diagnostic::on_move]` 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 @@ -687,6 +694,56 @@ 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)
{
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,
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
Loading
Loading