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
45 changes: 40 additions & 5 deletions compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,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;
pub(crate) mod on_unmatch_args;
Expand All @@ -42,6 +43,8 @@ pub(crate) enum Mode {
DiagnosticOnUnknown,
/// `#[diagnostic::on_unmatch_args]`
DiagnosticOnUnmatchArgs,
/// `#[diagnostic::on_type_error]`
DiagnosticOnTypeError,
}

impl Mode {
Expand All @@ -53,12 +56,15 @@ impl Mode {
Self::DiagnosticOnMove => "diagnostic::on_move",
Self::DiagnosticOnUnknown => "diagnostic::on_unknown",
Self::DiagnosticOnUnmatchArgs => "diagnostic::on_unmatch_args",
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 @@ -68,11 +74,14 @@ impl Mode {
Self::DiagnosticOnMove => DEFAULT,
Self::DiagnosticOnUnknown => DEFAULT,
Self::DiagnosticOnUnmatchArgs => 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 @@ -82,6 +91,7 @@ impl Mode {
Self::DiagnosticOnMove => DEFAULT,
Self::DiagnosticOnUnknown => DEFAULT,
Self::DiagnosticOnUnmatchArgs => DEFAULT,
Self::DiagnosticOnTypeError => DIAGNOSTIC_ON_TYPE_ERROR_ALLOWED_OPTIONS,
}
}

Expand All @@ -105,6 +115,9 @@ impl Mode {
Self::DiagnosticOnUnmatchArgs => {
"only `This` is allowed as a format argument, referring to the macro's name"
}
Self::DiagnosticOnTypeError => {
"only `note` is allowed as option for `diagnostic::on_type_error`"
}
}
}
}
Expand Down Expand Up @@ -294,15 +307,31 @@ fn parse_directive_items<'p>(
}
};
match (mode, name) {
(_, sym::message) => {
(
Mode::RustcOnUnimplemented
| Mode::DiagnosticOnUnimplemented
| Mode::DiagnosticOnConst
| Mode::DiagnosticOnMove
| Mode::DiagnosticOnUnknown
| Mode::DiagnosticOnUnmatchArgs,
sym::message,
) => {
let value = or_malformed!(value?);
if let Some(message) = &message {
duplicate!(name, message.0)
} else {
message = Some((item.span(), parse_format(value)));
}
}
(_, sym::label) => {
(
Mode::RustcOnUnimplemented
| Mode::DiagnosticOnUnimplemented
| Mode::DiagnosticOnConst
| Mode::DiagnosticOnMove
| Mode::DiagnosticOnUnknown
| Mode::DiagnosticOnUnmatchArgs,
sym::label,
) => {
let value = or_malformed!(value?);
if let Some(label) = &label {
duplicate!(name, label.0)
Expand Down Expand Up @@ -356,7 +385,6 @@ fn parse_directive_items<'p>(
malformed!();
}
}

_other => {
malformed!();
}
Expand Down Expand Up @@ -430,13 +458,19 @@ fn parse_arg(
_ => FormatArg::This,
},

(Mode::DiagnosticOnTypeError, sym::Found) => FormatArg::Found,
(Mode::DiagnosticOnTypeError, sym::Expected) => FormatArg::Expected,

// Some diagnostic attributes can use `{This}` to refer to the annotated item.
// For those that don't, we continue and maybe use it as a generic parameter.
//
// FIXME(mejrs) `DiagnosticOnUnimplemented` is intentionally not here;
// that requires lang approval which is best kept for a standalone PR.
(
Mode::DiagnosticOnUnknown | Mode::DiagnosticOnMove | Mode::DiagnosticOnUnmatchArgs,
Mode::DiagnosticOnUnknown
| Mode::DiagnosticOnMove
| Mode::DiagnosticOnUnmatchArgs
| Mode::DiagnosticOnTypeError,
sym::This,
) => FormatArg::This,

Expand All @@ -462,7 +496,8 @@ fn parse_arg(
Mode::RustcOnUnimplemented
| Mode::DiagnosticOnUnimplemented
| Mode::DiagnosticOnMove
| Mode::DiagnosticOnConst,
| Mode::DiagnosticOnConst
| Mode::DiagnosticOnTypeError,
generic_param,
) => FormatArg::GenericParam { generic_param, span },

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use rustc_feature::template;
use rustc_hir::attrs::AttributeKind;
use rustc_lint_defs::builtin::MISPLACED_DIAGNOSTIC_ATTRIBUTES;
use rustc_span::sym;

use crate::attributes::AttributeStability;
use crate::attributes::diagnostic::*;
use crate::attributes::prelude::*;
use crate::context::AcceptContext;
use crate::diagnostics::DiagnosticOnTypeErrorOnlyForAdt;
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>(&mut self, cx: &mut AcceptContext<'_, 'sess>, args: &ArgParser, mode: Mode) {
if !cx.features().diagnostic_on_type_error() {
return;
}

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.

You have to target check here, otherwise you can't warn if someone uses this on a macro invocation. Like this:

if !matches!(cx.target, Target::Enum | Target::Struct | Target::Union) {
cx.emit_lint(MISPLACED_DIAGNOSTIC_ATTRIBUTES, DiagnosticOnMoveOnlyForAdt, span);
return;

The generic param check has to stay in check_attr, it can't be done here.

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

if !matches!(cx.target, Target::Enum | Target::Struct | Target::Union) {
cx.emit_lint(MISPLACED_DIAGNOSTIC_ATTRIBUTES, DiagnosticOnTypeErrorOnlyForAdt, span);
return;
}

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 AttributeParser for OnTypeErrorParser {
const ATTRIBUTES: AcceptMapping<Self> = &[(
&[sym::diagnostic, sym::on_type_error],
template!(List: &[r#"note = "...""#]),
AttributeStability::Stable,
|this, cx, args| {
this.parse(cx, args, Mode::DiagnosticOnTypeError);
},
)];

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

fn finalize(self, _cx: &FinalizeContext<'_, '_>) -> 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 @@ -32,6 +32,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::diagnostic::on_unmatch_args::*;
Expand Down Expand Up @@ -144,6 +145,7 @@ attribute_parsers!(
NakedParser,
OnConstParser,
OnMoveParser,
OnTypeErrorParser,
OnUnimplementedParser,
OnUnknownParser,
OnUnmatchArgsParser,
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_attr_parsing/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,10 @@ pub(crate) struct DiagnosticOnUnknownOnlyForImports {
#[diag("`#[diagnostic::on_unmatch_args]` can only be applied to macro definitions")]
pub(crate) struct DiagnosticOnUnmatchArgsOnlyForMacros;

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

#[derive(Diagnostic)]
#[diag("`#[diagnostic::do_not_recommend]` can only be placed on trait implementations")]
pub(crate) struct IncorrectDoNotRecommendLocation {
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 @@ -510,6 +510,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 macros to customize macro argument matcher diagnostics.
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 @@ -1173,6 +1173,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 {
/// None if the directive was malformed in some way.
Expand Down
10 changes: 10 additions & 0 deletions compiler/rustc_hir/src/attrs/diagnostic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,10 @@ impl FormatString {
}
Piece::Arg(FormatArg::This) => ret.push_str(&args.this),

// only for on_type_error
Piece::Arg(FormatArg::Found) => ret.push_str(&args.found),
Piece::Arg(FormatArg::Expected) => ret.push_str(&args.expected),

// It's only `rustc_onunimplemented` from here
Piece::Arg(FormatArg::ThisPath) => ret.push_str(&args.this_path),
Piece::Arg(FormatArg::ThisResolved) => {
Expand Down Expand Up @@ -209,6 +213,8 @@ pub struct FormatArgs {
pub this: String,
pub this_resolved: String = String::new(),
pub this_path: String = String::new(),
pub found: String = String::new(),
pub expected: String = String::new(),
pub item_context: &'static str = "",
pub generic_args: Vec<(Symbol, String)> = Vec::new(),
}
Expand Down Expand Up @@ -238,6 +244,10 @@ pub enum FormatArg {
ItemContext,
/// What the user typed, if it doesn't match anything we can use.
AsIs(Symbol),
/// {Found} in diagnostic::on_type_error
Found,
/// {Expected} in diagnostic::on_type_error
Expected,
}

/// Represents the `on` filter in `#[rustc_on_unimplemented]`.
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,
OnUnmatchArgs { .. } => Yes,
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_lint_defs/src/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4523,6 +4523,7 @@ declare_lint! {
Warn,
"detects diagnostic attribute with malformed diagnostic format literals",
}

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
59 changes: 57 additions & 2 deletions compiler/rustc_passes/src/check_attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ use rustc_session::config::CrateType;
use rustc_session::errors::feature_err;
use rustc_session::lint;
use rustc_session::lint::builtin::{
CONFLICTING_REPR_HINTS, INVALID_DOC_ATTRIBUTES, MALFORMED_DIAGNOSTIC_FORMAT_LITERALS,
MISPLACED_DIAGNOSTIC_ATTRIBUTES, UNUSED_ATTRIBUTES,
CONFLICTING_REPR_HINTS, INVALID_DOC_ATTRIBUTES, MALFORMED_DIAGNOSTIC_ATTRIBUTES,
MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, MISPLACED_DIAGNOSTIC_ATTRIBUTES, UNUSED_ATTRIBUTES,
};
use rustc_span::edition::Edition;
use rustc_span::{DUMMY_SP, Ident, Span, Symbol, sym};
Expand Down Expand Up @@ -249,6 +249,9 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
AttributeKind::OnMove { directive } => {
self.check_diagnostic_on_move(hir_id, directive.as_deref())
}
AttributeKind::OnTypeError { directive, .. } => {
self.check_diagnostic_on_type_error(hir_id, directive.as_deref())
}

// All of the following attributes have no specific checks.
// tidy-alphabetical-start
Expand Down Expand Up @@ -599,6 +602,58 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
}
}

fn check_diagnostic_on_type_error(&self, hir_id: HirId, directive: Option<&Directive>) {
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(
MALFORMED_DIAGNOSTIC_ATTRIBUTES,
hir_id,
generics.span,
errors::OnTypeErrorNotExactlyOneGeneric { count: generic_count },
);
}
Comment thread
Unique-Usman marked this conversation as resolved.

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 @@ -1260,3 +1260,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 exactly one ADT generic parameter, but found `{$count}`"
)]
pub(crate) struct OnTypeErrorNotExactlyOneGeneric {
pub count: usize,
}
Loading
Loading