Skip to content

Commit 4f8ea98

Browse files
committed
Auto merge of #155200 - Unique-Usman:ua/diagnostic_on_type_error, r=mejrs
Introduce #[diagnostic::on_type_error(message)]
2 parents 9ae5f52 + 78d7543 commit 4f8ea98

47 files changed

Lines changed: 1152 additions & 10 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

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

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ use crate::parser::{ArgParser, MetaItemListParser, MetaItemOrLitParser, MetaItem
2424
pub(crate) mod do_not_recommend;
2525
pub(crate) mod on_const;
2626
pub(crate) mod on_move;
27+
pub(crate) mod on_type_error;
2728
pub(crate) mod on_unimplemented;
2829
pub(crate) mod on_unknown;
2930
pub(crate) mod on_unmatch_args;
@@ -42,6 +43,8 @@ pub(crate) enum Mode {
4243
DiagnosticOnUnknown,
4344
/// `#[diagnostic::on_unmatch_args]`
4445
DiagnosticOnUnmatchArgs,
46+
/// `#[diagnostic::on_type_error]`
47+
DiagnosticOnTypeError,
4548
}
4649

4750
impl Mode {
@@ -53,12 +56,15 @@ impl Mode {
5356
Self::DiagnosticOnMove => "diagnostic::on_move",
5457
Self::DiagnosticOnUnknown => "diagnostic::on_unknown",
5558
Self::DiagnosticOnUnmatchArgs => "diagnostic::on_unmatch_args",
59+
Self::DiagnosticOnTypeError => "diagnostic::on_type_error",
5660
}
5761
}
5862

5963
fn expected_options(&self) -> &'static str {
6064
const DEFAULT: &str =
6165
"at least one of the `message`, `note` and `label` options are expected";
66+
const DIAGNOSTIC_ON_TYPE_ERROR_EXPECTED_OPTIONS: &str =
67+
"at least a single `note` option is expected";
6268
match self {
6369
Self::RustcOnUnimplemented => {
6470
"see <https://rustc-dev-guide.rust-lang.org/diagnostics.html#rustc_on_unimplemented>"
@@ -68,11 +74,14 @@ impl Mode {
6874
Self::DiagnosticOnMove => DEFAULT,
6975
Self::DiagnosticOnUnknown => DEFAULT,
7076
Self::DiagnosticOnUnmatchArgs => DEFAULT,
77+
Self::DiagnosticOnTypeError => DIAGNOSTIC_ON_TYPE_ERROR_EXPECTED_OPTIONS,
7178
}
7279
}
7380

7481
fn allowed_options(&self) -> &'static str {
7582
const DEFAULT: &str = "only `message`, `note` and `label` are allowed as options";
83+
const DIAGNOSTIC_ON_TYPE_ERROR_ALLOWED_OPTIONS: &str =
84+
"only `note` is allowed as option for `diagnostic::on_type_error`";
7685
match self {
7786
Self::RustcOnUnimplemented => {
7887
"see <https://rustc-dev-guide.rust-lang.org/diagnostics.html#rustc_on_unimplemented>"
@@ -82,6 +91,7 @@ impl Mode {
8291
Self::DiagnosticOnMove => DEFAULT,
8392
Self::DiagnosticOnUnknown => DEFAULT,
8493
Self::DiagnosticOnUnmatchArgs => DEFAULT,
94+
Self::DiagnosticOnTypeError => DIAGNOSTIC_ON_TYPE_ERROR_ALLOWED_OPTIONS,
8595
}
8696
}
8797

@@ -105,6 +115,9 @@ impl Mode {
105115
Self::DiagnosticOnUnmatchArgs => {
106116
"only `This` is allowed as a format argument, referring to the macro's name"
107117
}
118+
Self::DiagnosticOnTypeError => {
119+
"only `note` is allowed as option for `diagnostic::on_type_error`"
120+
}
108121
}
109122
}
110123
}
@@ -294,15 +307,31 @@ fn parse_directive_items<'p>(
294307
}
295308
};
296309
match (mode, name) {
297-
(_, sym::message) => {
310+
(
311+
Mode::RustcOnUnimplemented
312+
| Mode::DiagnosticOnUnimplemented
313+
| Mode::DiagnosticOnConst
314+
| Mode::DiagnosticOnMove
315+
| Mode::DiagnosticOnUnknown
316+
| Mode::DiagnosticOnUnmatchArgs,
317+
sym::message,
318+
) => {
298319
let value = or_malformed!(value?);
299320
if let Some(message) = &message {
300321
duplicate!(name, message.0)
301322
} else {
302323
message = Some((item.span(), parse_format(value)));
303324
}
304325
}
305-
(_, sym::label) => {
326+
(
327+
Mode::RustcOnUnimplemented
328+
| Mode::DiagnosticOnUnimplemented
329+
| Mode::DiagnosticOnConst
330+
| Mode::DiagnosticOnMove
331+
| Mode::DiagnosticOnUnknown
332+
| Mode::DiagnosticOnUnmatchArgs,
333+
sym::label,
334+
) => {
306335
let value = or_malformed!(value?);
307336
if let Some(label) = &label {
308337
duplicate!(name, label.0)
@@ -356,7 +385,6 @@ fn parse_directive_items<'p>(
356385
malformed!();
357386
}
358387
}
359-
360388
_other => {
361389
malformed!();
362390
}
@@ -430,13 +458,19 @@ fn parse_arg(
430458
_ => FormatArg::This,
431459
},
432460

461+
(Mode::DiagnosticOnTypeError, sym::Found) => FormatArg::Found,
462+
(Mode::DiagnosticOnTypeError, sym::Expected) => FormatArg::Expected,
463+
433464
// Some diagnostic attributes can use `{This}` to refer to the annotated item.
434465
// For those that don't, we continue and maybe use it as a generic parameter.
435466
//
436467
// FIXME(mejrs) `DiagnosticOnUnimplemented` is intentionally not here;
437468
// that requires lang approval which is best kept for a standalone PR.
438469
(
439-
Mode::DiagnosticOnUnknown | Mode::DiagnosticOnMove | Mode::DiagnosticOnUnmatchArgs,
470+
Mode::DiagnosticOnUnknown
471+
| Mode::DiagnosticOnMove
472+
| Mode::DiagnosticOnUnmatchArgs
473+
| Mode::DiagnosticOnTypeError,
440474
sym::This,
441475
) => FormatArg::This,
442476

@@ -462,7 +496,8 @@ fn parse_arg(
462496
Mode::RustcOnUnimplemented
463497
| Mode::DiagnosticOnUnimplemented
464498
| Mode::DiagnosticOnMove
465-
| Mode::DiagnosticOnConst,
499+
| Mode::DiagnosticOnConst
500+
| Mode::DiagnosticOnTypeError,
466501
generic_param,
467502
) => FormatArg::GenericParam { generic_param, span },
468503

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
use rustc_hir::attrs::AttributeKind;
2+
use rustc_lint_defs::builtin::MISPLACED_DIAGNOSTIC_ATTRIBUTES;
3+
use rustc_span::sym;
4+
5+
use crate::attributes::AttributeStability;
6+
use crate::attributes::diagnostic::*;
7+
use crate::attributes::prelude::*;
8+
use crate::context::AcceptContext;
9+
use crate::diagnostics::DiagnosticOnTypeErrorOnlyForAdt;
10+
use crate::parser::ArgParser;
11+
use crate::target_checking::{ALL_TARGETS, AllowedTargets};
12+
use crate::template;
13+
14+
#[derive(Default)]
15+
pub(crate) struct OnTypeErrorParser {
16+
span: Option<Span>,
17+
directive: Option<(Span, Directive)>,
18+
}
19+
20+
impl OnTypeErrorParser {
21+
fn parse<'sess>(&mut self, cx: &mut AcceptContext<'_, 'sess>, args: &ArgParser, mode: Mode) {
22+
if !cx.features().diagnostic_on_type_error() {
23+
return;
24+
}
25+
26+
let span = cx.attr_span;
27+
self.span = Some(span);
28+
29+
if !matches!(cx.target, Target::Enum | Target::Struct | Target::Union) {
30+
cx.emit_lint(MISPLACED_DIAGNOSTIC_ATTRIBUTES, DiagnosticOnTypeErrorOnlyForAdt, span);
31+
return;
32+
}
33+
34+
let Some(items) = parse_list(cx, args, mode) else { return };
35+
36+
if let Some(directive) = parse_directive_items(cx, mode, items.mixed(), true) {
37+
merge_directives(cx, &mut self.directive, (span, directive));
38+
}
39+
}
40+
}
41+
42+
impl AttributeParser for OnTypeErrorParser {
43+
const ATTRIBUTES: AcceptMapping<Self> = &[(
44+
&[sym::diagnostic, sym::on_type_error],
45+
template!(List: &[r#"note = "...""#]),
46+
AttributeStability::Stable,
47+
|this, cx, args| {
48+
this.parse(cx, args, Mode::DiagnosticOnTypeError);
49+
},
50+
)];
51+
52+
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS);
53+
54+
fn finalize(self, _cx: &FinalizeContext<'_, '_>) -> Option<AttributeKind> {
55+
if let Some(span) = self.span {
56+
Some(AttributeKind::OnTypeError {
57+
span,
58+
directive: self.directive.map(|d| Box::new(d.1)),
59+
})
60+
} else {
61+
None
62+
}
63+
}
64+
}

compiler/rustc_attr_parsing/src/context.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ use crate::attributes::deprecation::*;
3232
use crate::attributes::diagnostic::do_not_recommend::*;
3333
use crate::attributes::diagnostic::on_const::*;
3434
use crate::attributes::diagnostic::on_move::*;
35+
use crate::attributes::diagnostic::on_type_error::*;
3536
use crate::attributes::diagnostic::on_unimplemented::*;
3637
use crate::attributes::diagnostic::on_unknown::*;
3738
use crate::attributes::diagnostic::on_unmatch_args::*;
@@ -145,6 +146,7 @@ attribute_parsers!(
145146
NakedParser,
146147
OnConstParser,
147148
OnMoveParser,
149+
OnTypeErrorParser,
148150
OnUnimplementedParser,
149151
OnUnknownParser,
150152
OnUnmatchArgsParser,

compiler/rustc_attr_parsing/src/diagnostics.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,10 @@ pub(crate) struct DiagnosticOnUnknownOnlyForImports {
305305
#[diag("`#[diagnostic::on_unmatch_args]` can only be applied to macro definitions")]
306306
pub(crate) struct DiagnosticOnUnmatchArgsOnlyForMacros;
307307

308+
#[derive(Diagnostic)]
309+
#[diag("`#[diagnostic::on_type_error]` can only be applied to enums, structs or unions")]
310+
pub(crate) struct DiagnosticOnTypeErrorOnlyForAdt;
311+
308312
#[derive(Diagnostic)]
309313
#[diag("`#[diagnostic::do_not_recommend]` can only be placed on trait implementations")]
310314
pub(crate) struct IncorrectDoNotRecommendLocation {

compiler/rustc_feature/src/unstable.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,8 @@ declare_features! (
510510
(unstable, diagnostic_on_const, "1.93.0", Some(143874)),
511511
/// Allows giving on-move borrowck custom diagnostic messages for a type
512512
(unstable, diagnostic_on_move, "1.96.0", Some(154181)),
513+
/// Allows giving custom types diagnostic messages on type errors
514+
(unstable, diagnostic_on_type_error, "CURRENT_RUSTC_VERSION", Some(155382)),
513515
/// Allows giving unresolved imports a custom diagnostic message
514516
(unstable, diagnostic_on_unknown, "1.96.0", Some(152900)),
515517
/// Allows macros to customize macro argument matcher diagnostics.

compiler/rustc_hir/src/attrs/data_structures.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1173,6 +1173,12 @@ pub enum AttributeKind {
11731173
directive: Option<Box<Directive>>,
11741174
},
11751175

1176+
/// Represents`#[diagnostic::on_type_error]`.
1177+
OnTypeError {
1178+
span: Span,
1179+
directive: Option<Box<Directive>>,
1180+
},
1181+
11761182
/// Represents `#[rustc_on_unimplemented]` and `#[diagnostic::on_unimplemented]`.
11771183
OnUnimplemented {
11781184
/// None if the directive was malformed in some way.

compiler/rustc_hir/src/attrs/diagnostic.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,10 @@ impl FormatString {
149149
}
150150
Piece::Arg(FormatArg::This) => ret.push_str(&args.this),
151151

152+
// only for on_type_error
153+
Piece::Arg(FormatArg::Found) => ret.push_str(&args.found),
154+
Piece::Arg(FormatArg::Expected) => ret.push_str(&args.expected),
155+
152156
// It's only `rustc_onunimplemented` from here
153157
Piece::Arg(FormatArg::ThisPath) => ret.push_str(&args.this_path),
154158
Piece::Arg(FormatArg::ThisResolved) => {
@@ -209,6 +213,8 @@ pub struct FormatArgs {
209213
pub this: String,
210214
pub this_resolved: String = String::new(),
211215
pub this_path: String = String::new(),
216+
pub found: String = String::new(),
217+
pub expected: String = String::new(),
212218
pub item_context: &'static str = "",
213219
pub generic_args: Vec<(Symbol, String)> = Vec::new(),
214220
}
@@ -238,6 +244,10 @@ pub enum FormatArg {
238244
ItemContext,
239245
/// What the user typed, if it doesn't match anything we can use.
240246
AsIs(Symbol),
247+
/// {Found} in diagnostic::on_type_error
248+
Found,
249+
/// {Expected} in diagnostic::on_type_error
250+
Expected,
241251
}
242252

243253
/// Represents the `on` filter in `#[rustc_on_unimplemented]`.

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
OnUnmatchArgs { .. } => Yes,

compiler/rustc_lint_defs/src/builtin.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4451,6 +4451,7 @@ declare_lint! {
44514451
Warn,
44524452
"detects diagnostic attribute with malformed diagnostic format literals",
44534453
}
4454+
44544455
declare_lint! {
44554456
/// The `ambiguous_glob_imports` lint detects glob imports that should report ambiguity
44564457
/// errors, but previously didn't do that due to rustc bugs.

compiler/rustc_passes/src/check_attr.rs

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ use rustc_session::config::CrateType;
3939
use rustc_session::errors::feature_err;
4040
use rustc_session::lint;
4141
use rustc_session::lint::builtin::{
42-
CONFLICTING_REPR_HINTS, INVALID_DOC_ATTRIBUTES, MALFORMED_DIAGNOSTIC_FORMAT_LITERALS,
43-
MISPLACED_DIAGNOSTIC_ATTRIBUTES, UNUSED_ATTRIBUTES,
42+
CONFLICTING_REPR_HINTS, INVALID_DOC_ATTRIBUTES, MALFORMED_DIAGNOSTIC_ATTRIBUTES,
43+
MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, MISPLACED_DIAGNOSTIC_ATTRIBUTES, UNUSED_ATTRIBUTES,
4444
};
4545
use rustc_span::edition::Edition;
4646
use rustc_span::{DUMMY_SP, Ident, Span, Symbol, sym};
@@ -249,6 +249,9 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
249249
AttributeKind::OnMove { directive } => {
250250
self.check_diagnostic_on_move(hir_id, directive.as_deref())
251251
}
252+
AttributeKind::OnTypeError { directive, .. } => {
253+
self.check_diagnostic_on_type_error(hir_id, directive.as_deref())
254+
}
252255

253256
// All of the following attributes have no specific checks.
254257
// tidy-alphabetical-start
@@ -601,6 +604,58 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
601604
}
602605
}
603606

607+
fn check_diagnostic_on_type_error(&self, hir_id: HirId, directive: Option<&Directive>) {
608+
if let Some(directive) = directive {
609+
if let Node::Item(Item {
610+
kind:
611+
ItemKind::Struct(_, generics, _)
612+
| ItemKind::Enum(_, generics, _)
613+
| ItemKind::Union(_, generics, _),
614+
..
615+
}) = self.tcx.hir_node(hir_id)
616+
{
617+
let generic_count = generics
618+
.params
619+
.iter()
620+
.filter(|p| !matches!(p.kind, GenericParamKind::Lifetime { .. }))
621+
.count();
622+
623+
// Enforce: at most one generic
624+
if generic_count != 1 {
625+
self.tcx.emit_node_span_lint(
626+
MALFORMED_DIAGNOSTIC_ATTRIBUTES,
627+
hir_id,
628+
generics.span,
629+
errors::OnTypeErrorNotExactlyOneGeneric { count: generic_count },
630+
);
631+
}
632+
633+
directive.visit_params(&mut |argument_name, span| {
634+
let has_generic = generics.params.iter().any(|p| {
635+
if !matches!(p.kind, GenericParamKind::Lifetime { .. })
636+
&& let ParamName::Plain(name) = p.name
637+
&& name.name == argument_name
638+
{
639+
true
640+
} else {
641+
false
642+
}
643+
});
644+
645+
let is_allowed = argument_name == sym::Expected || argument_name == sym::Found;
646+
if !(has_generic | is_allowed) {
647+
self.tcx.emit_node_span_lint(
648+
MALFORMED_DIAGNOSTIC_FORMAT_LITERALS,
649+
hir_id,
650+
span,
651+
errors::OnTypeErrorMalformedFormatLiterals { name: argument_name },
652+
)
653+
}
654+
});
655+
}
656+
}
657+
}
658+
604659
/// Checks if an `#[inline]` is applied to a function or a closure.
605660
fn check_inline(&self, hir_id: HirId, attr_span: Span, kind: &InlineAttr, target: Target) {
606661
match target {

0 commit comments

Comments
 (0)