Skip to content

Commit 6366fc0

Browse files
committed
Introduce #[diagnostic::on_type_error(note)]
Suggested-by: Esteban Küber <esteban@kuber.com.ar> Signed-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>
1 parent 0204aca commit 6366fc0

36 files changed

Lines changed: 951 additions & 6 deletions

compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use crate::parser::{ArgParser, MetaItemListParser, MetaItemOrLitParser, MetaItem
2323
pub(crate) mod do_not_recommend;
2424
pub(crate) mod on_const;
2525
pub(crate) mod on_move;
26+
pub(crate) mod on_type_error;
2627
pub(crate) mod on_unimplemented;
2728
pub(crate) mod on_unknown;
2829

@@ -38,6 +39,8 @@ pub(crate) enum Mode {
3839
DiagnosticOnMove,
3940
/// `#[diagnostic::on_unknown]`
4041
DiagnosticOnUnknown,
42+
/// `#[diagnostic::on_type_error]`
43+
DiagnosticOnTypeError,
4144
}
4245

4346
impl Mode {
@@ -48,12 +51,15 @@ impl Mode {
4851
Self::DiagnosticOnConst => "diagnostic::on_const",
4952
Self::DiagnosticOnMove => "diagnostic::on_move",
5053
Self::DiagnosticOnUnknown => "diagnostic::on_unknown",
54+
Self::DiagnosticOnTypeError => "diagnostic::on_type_error",
5155
}
5256
}
5357

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

6875
fn allowed_options(&self) -> &'static str {
6976
const DEFAULT: &str = "only `message`, `note` and `label` are allowed as options";
77+
const DIAGNOSTIC_ON_TYPE_ERROR_ALLOWED_OPTIONS: &str =
78+
"only `note` is allowed as option for `diagnostic::on_type_error`";
7079
match self {
7180
Self::RustcOnUnimplemented => {
7281
"see <https://rustc-dev-guide.rust-lang.org/diagnostics.html#rustc_on_unimplemented>"
@@ -75,6 +84,7 @@ impl Mode {
7584
Self::DiagnosticOnConst => DEFAULT,
7685
Self::DiagnosticOnMove => DEFAULT,
7786
Self::DiagnosticOnUnknown => DEFAULT,
87+
Self::DiagnosticOnTypeError => DIAGNOSTIC_ON_TYPE_ERROR_ALLOWED_OPTIONS,
7888
}
7989
}
8090
}
@@ -268,6 +278,10 @@ fn parse_directive_items<'p, S: Stage>(
268278
}
269279
};
270280
match (mode, name) {
281+
(Mode::DiagnosticOnTypeError, sym::message)
282+
| (Mode::DiagnosticOnTypeError, sym::label) => {
283+
malformed!()
284+
}
271285
(_, sym::message) => {
272286
let value = or_malformed!(value?);
273287
if let Some(message) = &message {
@@ -332,7 +346,6 @@ fn parse_directive_items<'p, S: Stage>(
332346
malformed!();
333347
}
334348
}
335-
336349
_other => {
337350
malformed!();
338351
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
use rustc_feature::template;
2+
use rustc_hir::attrs::AttributeKind;
3+
use rustc_span::sym;
4+
5+
use crate::attributes::diagnostic::*;
6+
use crate::attributes::prelude::*;
7+
use crate::context::{AcceptContext, Stage};
8+
use crate::parser::ArgParser;
9+
use crate::target_checking::{ALL_TARGETS, AllowedTargets};
10+
11+
#[derive(Default)]
12+
pub(crate) struct OnTypeErrorParser {
13+
span: Option<Span>,
14+
directive: Option<(Span, Directive)>,
15+
}
16+
17+
impl OnTypeErrorParser {
18+
fn parse<'sess, S: Stage>(
19+
&mut self,
20+
cx: &mut AcceptContext<'_, 'sess, S>,
21+
args: &ArgParser,
22+
mode: Mode,
23+
) {
24+
if !cx.features().diagnostic_on_type_error() {
25+
// `UnknownDiagnosticAttribute` is emitted in rustc_resolve/macros.rs
26+
return;
27+
}
28+
29+
let span = cx.attr_span;
30+
self.span = Some(span);
31+
32+
let Some(items) = parse_list(cx, args, mode) else { return };
33+
34+
if let Some(directive) = parse_directive_items(cx, mode, items.mixed(), true) {
35+
merge_directives(cx, &mut self.directive, (span, directive));
36+
}
37+
}
38+
}
39+
40+
impl<S: Stage> AttributeParser<S> for OnTypeErrorParser {
41+
const ATTRIBUTES: AcceptMapping<Self, S> = &[(
42+
&[sym::diagnostic, sym::on_type_error],
43+
template!(List: &[r#" note = "...""#]),
44+
|this, cx, args| {
45+
this.parse(cx, args, Mode::DiagnosticOnTypeError);
46+
},
47+
)];
48+
49+
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS);
50+
51+
fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
52+
if let Some(span) = self.span {
53+
Some(AttributeKind::OnTypeError {
54+
span,
55+
directive: self.directive.map(|d| Box::new(d.1)),
56+
})
57+
} else {
58+
None
59+
}
60+
}
61+
}

compiler/rustc_attr_parsing/src/context.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ use crate::attributes::deprecation::*;
3131
use crate::attributes::diagnostic::do_not_recommend::*;
3232
use crate::attributes::diagnostic::on_const::*;
3333
use crate::attributes::diagnostic::on_move::*;
34+
use crate::attributes::diagnostic::on_type_error::*;
3435
use crate::attributes::diagnostic::on_unimplemented::*;
3536
use crate::attributes::diagnostic::on_unknown::*;
3637
use crate::attributes::doc::*;
@@ -154,6 +155,7 @@ attribute_parsers!(
154155
NakedParser,
155156
OnConstParser,
156157
OnMoveParser,
158+
OnTypeErrorParser,
157159
OnUnimplementedParser,
158160
OnUnknownParser,
159161
RustcAlignParser,

compiler/rustc_borrowck/src/diagnostics/bound_region_errors.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,7 @@ fn try_extract_error_from_region_constraints<'a, 'tcx>(
509509
.try_report_from_nll()
510510
.or_else(|| {
511511
if let SubregionOrigin::Subtype(trace) = cause {
512+
tracing::info!("borrow checker");
512513
Some(infcx.err_ctxt().report_and_explain_type_error(
513514
*trace,
514515
infcx.tcx.param_env(generic_param_scope),

compiler/rustc_feature/src/unstable.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,8 @@ declare_features! (
476476
(unstable, diagnostic_on_const, "1.93.0", Some(143874)),
477477
/// Allows giving on-move borrowck custom diagnostic messages for a type
478478
(unstable, diagnostic_on_move, "CURRENT_RUSTC_VERSION", Some(154181)),
479+
/// Allows giving custom types diagnostic messages on type erros
480+
(unstable, diagnostic_on_type_error, "CURRENT_RUSTC_VERSION", Some(155382)),
479481
/// Allows giving unresolved imports a custom diagnostic message
480482
(unstable, diagnostic_on_unknown, "CURRENT_RUSTC_VERSION", Some(152900)),
481483
/// Allows `#[doc(cfg(...))]`.

compiler/rustc_hir/src/attrs/data_structures.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1194,6 +1194,12 @@ pub enum AttributeKind {
11941194
directive: Option<Box<Directive>>,
11951195
},
11961196

1197+
/// Represents`#[diagnostic::on_type_error]`.
1198+
OnTypeError {
1199+
span: Span,
1200+
directive: Option<Box<Directive>>,
1201+
},
1202+
11971203
/// Represents `#[rustc_on_unimplemented]` and `#[diagnostic::on_unimplemented]`.
11981204
OnUnimplemented {
11991205
span: Span,

compiler/rustc_hir/src/attrs/encode_cross_crate.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ impl AttributeKind {
7878
NonExhaustive(..) => Yes, // Needed for rustdoc
7979
OnConst { .. } => Yes,
8080
OnMove { .. } => Yes,
81+
OnTypeError { .. } => Yes,
8182
OnUnimplemented { .. } => Yes,
8283
OnUnknown { .. } => Yes,
8384
Optimize(..) => No,

compiler/rustc_lint_defs/src/builtin.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ declare_lint_pass! {
8181
NEVER_TYPE_FALLBACK_FLOWING_INTO_UNSAFE,
8282
NON_CONTIGUOUS_RANGE_ENDPOINTS,
8383
NON_EXHAUSTIVE_OMITTED_PATTERNS,
84+
ON_TYPE_ERROR_MULTIPLE_GENERICS,
8485
OUT_OF_SCOPE_MACRO_CALLS,
8586
OVERLAPPING_RANGE_ENDPOINTS,
8687
PATTERNS_IN_FNS_WITHOUT_BODY,
@@ -4575,6 +4576,31 @@ declare_lint! {
45754576
Warn,
45764577
"detects diagnostic attribute with malformed diagnostic format literals",
45774578
}
4579+
4580+
declare_lint! {
4581+
/// The `on_type_error_multiple_generics` lint detects when
4582+
/// `#[diagnostic::on_type_error]` is used on items with more than one generic parameter.
4583+
///
4584+
/// ### Example
4585+
///
4586+
/// ```rust
4587+
/// #[diagnostic::on_type_error(note = "Expected {T}")]
4588+
/// struct Foo<T, U>(T, U);
4589+
/// ```
4590+
///
4591+
/// {{produces}}
4592+
///
4593+
/// ### Explanation
4594+
///
4595+
/// The `#[diagnostic::on_type_error]` attribute currently supports at most one
4596+
/// generic parameter.
4597+
///
4598+
/// [reference]: https://doc.rust-lang.org/nightly/reference/attributes/diagnostics.html#the-diagnostic-tool-attribute-namespace
4599+
pub ON_TYPE_ERROR_MULTIPLE_GENERICS,
4600+
Warn,
4601+
"detects use of #[diagnostic::on_type_error] with multiple generic parameters",
4602+
}
4603+
45784604
declare_lint! {
45794605
/// The `ambiguous_glob_imports` lint detects glob imports that should report ambiguity
45804606
/// errors, but previously didn't do that due to rustc bugs.

compiler/rustc_passes/src/check_attr.rs

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ use rustc_session::config::CrateType;
3939
use rustc_session::lint;
4040
use rustc_session::lint::builtin::{
4141
CONFLICTING_REPR_HINTS, INVALID_DOC_ATTRIBUTES, MALFORMED_DIAGNOSTIC_FORMAT_LITERALS,
42-
MISPLACED_DIAGNOSTIC_ATTRIBUTES, UNUSED_ATTRIBUTES,
42+
MISPLACED_DIAGNOSTIC_ATTRIBUTES, ON_TYPE_ERROR_MULTIPLE_GENERICS, UNUSED_ATTRIBUTES,
4343
};
4444
use rustc_session::parse::feature_err;
4545
use rustc_span::edition::Edition;
@@ -79,6 +79,10 @@ struct DiagnosticOnUnknownOnlyForImports {
7979
item_span: Span,
8080
}
8181

82+
#[derive(Diagnostic)]
83+
#[diag("`#[diagnostic::on_type_error]` can only be applied to enums, structs or unions")]
84+
struct DiagnosticOnTypeErrorOnlyForAdt;
85+
8286
fn target_from_impl_item<'tcx>(tcx: TyCtxt<'tcx>, impl_item: &hir::ImplItem<'_>) -> Target {
8387
match impl_item.kind {
8488
hir::ImplItemKind::Const(..) => Target::AssocConst,
@@ -228,6 +232,9 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
228232
Attribute::Parsed(AttributeKind::OnMove { span, directive }) => {
229233
self.check_diagnostic_on_move(*span, hir_id, target, directive.as_deref())
230234
},
235+
Attribute::Parsed(AttributeKind::OnTypeError{ span, directive }) => {
236+
self.check_diagnostic_on_type_error(*span, hir_id, target, directive.as_deref())
237+
},
231238
Attribute::Parsed(
232239
// tidy-alphabetical-start
233240
AttributeKind::RustcAllowIncoherentImpl(..)
@@ -687,6 +694,74 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
687694
}
688695
}
689696

697+
/// Checks if `#[diagnostic::on_type_error]` is applied to an ADT definition
698+
fn check_diagnostic_on_type_error(
699+
&self,
700+
attr_span: Span,
701+
hir_id: HirId,
702+
target: Target,
703+
directive: Option<&Directive>,
704+
) {
705+
if !matches!(target, Target::Enum | Target::Struct | Target::Union) {
706+
self.tcx.emit_node_span_lint(
707+
MISPLACED_DIAGNOSTIC_ATTRIBUTES,
708+
hir_id,
709+
attr_span,
710+
DiagnosticOnTypeErrorOnlyForAdt,
711+
);
712+
}
713+
714+
if let Some(directive) = directive {
715+
if let Node::Item(Item {
716+
kind:
717+
ItemKind::Struct(_, generics, _)
718+
| ItemKind::Enum(_, generics, _)
719+
| ItemKind::Union(_, generics, _),
720+
..
721+
}) = self.tcx.hir_node(hir_id)
722+
{
723+
let generic_count = generics
724+
.params
725+
.iter()
726+
.filter(|p| !matches!(p.kind, GenericParamKind::Lifetime { .. }))
727+
.count();
728+
729+
// Enforce: at most one generic
730+
if generic_count > 1 {
731+
self.tcx.emit_node_span_lint(
732+
ON_TYPE_ERROR_MULTIPLE_GENERICS,
733+
hir_id,
734+
generics.span,
735+
errors::OnTypeErrorMultipleGenerics { count: generic_count },
736+
);
737+
}
738+
739+
directive.visit_params(&mut |argument_name, span| {
740+
let has_generic = generics.params.iter().any(|p| {
741+
if !matches!(p.kind, GenericParamKind::Lifetime { .. })
742+
&& let ParamName::Plain(name) = p.name
743+
&& name.name == argument_name
744+
{
745+
true
746+
} else {
747+
false
748+
}
749+
});
750+
751+
let is_allowed = argument_name == sym::Expected || argument_name == sym::Found;
752+
if !(has_generic | is_allowed) {
753+
self.tcx.emit_node_span_lint(
754+
MALFORMED_DIAGNOSTIC_FORMAT_LITERALS,
755+
hir_id,
756+
span,
757+
errors::OnTypeErrorMalformedFormatLiterals { name: argument_name },
758+
)
759+
}
760+
});
761+
}
762+
}
763+
}
764+
690765
/// Checks if an `#[inline]` is applied to a function or a closure.
691766
fn check_inline(&self, hir_id: HirId, attr_span: Span, kind: &InlineAttr, target: Target) {
692767
match target {

compiler/rustc_passes/src/errors.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1307,3 +1307,18 @@ pub(crate) struct UnknownFormatParameterForOnUnimplementedAttr {
13071307
pub(crate) struct OnMoveMalformedFormatLiterals {
13081308
pub name: Symbol,
13091309
}
1310+
1311+
#[derive(Diagnostic)]
1312+
#[diag("unknown parameter `{$name}`")]
1313+
#[help(r#"expect either a generic argument name, {"`{Self}`"}, {"`{Expected}`"} or {"`{Found}`"} as format argument"#)]
1314+
pub(crate) struct OnTypeErrorMalformedFormatLiterals {
1315+
pub name: Symbol,
1316+
}
1317+
1318+
#[derive(Diagnostic)]
1319+
#[diag(
1320+
"`#[diagnostic::on_type_error]` only supports one ADT generic parameter, but found `{$count}`"
1321+
)]
1322+
pub(crate) struct OnTypeErrorMultipleGenerics {
1323+
pub count: usize,
1324+
}

0 commit comments

Comments
 (0)