From 1dd8769d42db7812dcf88eed03d2f685af8f245e Mon Sep 17 00:00:00 2001 From: yukang Date: Sat, 4 Apr 2026 18:32:08 +0800 Subject: [PATCH 1/8] add on_missing_args --- .../src/attributes/diagnostic/mod.rs | 15 +++- .../attributes/diagnostic/on_missing_args.rs | 72 +++++++++++++++++++ compiler/rustc_attr_parsing/src/context.rs | 2 + compiler/rustc_expand/src/mbe/diagnostics.rs | 36 +++++++++- compiler/rustc_expand/src/mbe/macro_rules.rs | 37 ++++++++-- compiler/rustc_feature/src/builtin_attrs.rs | 1 + compiler/rustc_feature/src/unstable.rs | 2 + .../rustc_hir/src/attrs/data_structures.rs | 7 ++ .../rustc_hir/src/attrs/encode_cross_crate.rs | 1 + compiler/rustc_lint/src/early/diagnostics.rs | 6 ++ compiler/rustc_lint/src/lints.rs | 13 ++++ compiler/rustc_lint_defs/src/lib.rs | 4 ++ compiler/rustc_passes/src/check_attr.rs | 45 +++++++++++- compiler/rustc_passes/src/errors.rs | 7 ++ compiler/rustc_resolve/src/macros.rs | 9 ++- compiler/rustc_span/src/symbol.rs | 2 + library/core/src/field.rs | 3 + library/core/src/lib.rs | 1 + library/core/src/mem/mod.rs | 3 + .../diagnostic-on-missing-args.md | 31 ++++++++ .../on_missing_args/auxiliary/other.rs | 11 +++ .../error_is_shown_in_downstream_crates.rs | 11 +++ ...error_is_shown_in_downstream_crates.stderr | 15 ++++ .../on_missing_args/message_and_label.rs | 19 +++++ .../on_missing_args/message_and_label.stderr | 18 +++++ .../on_missing_args/on_missing_args.rs | 19 +++++ .../on_missing_args/on_missing_args.stderr | 19 +++++ .../report_warning_on_invalid_formats.rs | 14 ++++ .../report_warning_on_invalid_formats.stderr | 11 +++ ...ort_warning_on_invalid_meta_item_syntax.rs | 12 ++++ ...warning_on_invalid_meta_item_syntax.stderr | 11 +++ .../report_warning_on_missing_options.rs | 12 ++++ .../report_warning_on_missing_options.stderr | 11 +++ .../report_warning_on_non_macro.rs | 10 +++ .../report_warning_on_non_macro.stderr | 10 +++ .../report_warning_on_unknown_options.rs | 12 ++++ .../report_warning_on_unknown_options.stderr | 11 +++ ...feature-gate-diagnostic-on-missing-args.rs | 15 ++++ ...ure-gate-diagnostic-on-missing-args.stderr | 17 +++++ .../invalid.next.stderr | 2 + .../invalid.old.stderr | 2 + tests/ui/offset-of/offset-of-arg-count.stderr | 2 + 42 files changed, 549 insertions(+), 12 deletions(-) create mode 100644 compiler/rustc_attr_parsing/src/attributes/diagnostic/on_missing_args.rs create mode 100644 src/doc/unstable-book/src/language-features/diagnostic-on-missing-args.md create mode 100644 tests/ui/diagnostic_namespace/on_missing_args/auxiliary/other.rs create mode 100644 tests/ui/diagnostic_namespace/on_missing_args/error_is_shown_in_downstream_crates.rs create mode 100644 tests/ui/diagnostic_namespace/on_missing_args/error_is_shown_in_downstream_crates.stderr create mode 100644 tests/ui/diagnostic_namespace/on_missing_args/message_and_label.rs create mode 100644 tests/ui/diagnostic_namespace/on_missing_args/message_and_label.stderr create mode 100644 tests/ui/diagnostic_namespace/on_missing_args/on_missing_args.rs create mode 100644 tests/ui/diagnostic_namespace/on_missing_args/on_missing_args.stderr create mode 100644 tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_invalid_formats.rs create mode 100644 tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_invalid_formats.stderr create mode 100644 tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_invalid_meta_item_syntax.rs create mode 100644 tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_invalid_meta_item_syntax.stderr create mode 100644 tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_missing_options.rs create mode 100644 tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_missing_options.stderr create mode 100644 tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_non_macro.rs create mode 100644 tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_non_macro.stderr create mode 100644 tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_unknown_options.rs create mode 100644 tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_unknown_options.stderr create mode 100644 tests/ui/feature-gates/feature-gate-diagnostic-on-missing-args.rs create mode 100644 tests/ui/feature-gates/feature-gate-diagnostic-on-missing-args.stderr diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs index e63baf77c0852..29c91bb75b5dd 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs @@ -22,6 +22,7 @@ use crate::parser::{ArgParser, MetaItemListParser, MetaItemOrLitParser, MetaItem pub(crate) mod do_not_recommend; pub(crate) mod on_const; +pub(crate) mod on_missing_args; pub(crate) mod on_move; pub(crate) mod on_unimplemented; @@ -33,6 +34,8 @@ pub(crate) enum Mode { DiagnosticOnUnimplemented, /// `#[diagnostic::on_const]` DiagnosticOnConst, + /// `#[diagnostic::on_missing_args]` + DiagnosticOnMissingArgs, /// `#[diagnostic::on_move]` DiagnosticOnMove, } @@ -115,6 +118,13 @@ fn parse_directive_items<'p, S: Stage>( span, ); } + Mode::DiagnosticOnMissingArgs => { + cx.emit_lint( + MALFORMED_DIAGNOSTIC_ATTRIBUTES, + AttributeLintKind::MalformedOnMissingArgsAttr { span }, + span, + ); + } Mode::DiagnosticOnMove => { cx.emit_lint( MALFORMED_DIAGNOSTIC_ATTRIBUTES, @@ -140,7 +150,10 @@ fn parse_directive_items<'p, S: Stage>( Mode::RustcOnUnimplemented => { cx.emit_err(NoValueInOnUnimplemented { span: item.span() }); } - Mode::DiagnosticOnUnimplemented |Mode::DiagnosticOnConst | Mode::DiagnosticOnMove => { + Mode::DiagnosticOnUnimplemented + | Mode::DiagnosticOnConst + | Mode::DiagnosticOnMissingArgs + | Mode::DiagnosticOnMove => { cx.emit_lint( MALFORMED_DIAGNOSTIC_ATTRIBUTES, AttributeLintKind::IgnoredDiagnosticOption { diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_missing_args.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_missing_args.rs new file mode 100644 index 0000000000000..cad0b862056fb --- /dev/null +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_missing_args.rs @@ -0,0 +1,72 @@ +use rustc_hir::attrs::diagnostic::Directive; +use rustc_hir::lints::AttributeLintKind; +use rustc_session::lint::builtin::MALFORMED_DIAGNOSTIC_ATTRIBUTES; + +use crate::attributes::diagnostic::*; +use crate::attributes::prelude::*; + +#[derive(Default)] +pub(crate) struct OnMissingArgsParser { + span: Option, + directive: Option<(Span, Directive)>, +} + +impl AttributeParser for OnMissingArgsParser { + const ATTRIBUTES: AcceptMapping = &[( + &[sym::diagnostic, sym::on_missing_args], + template!(List: &[r#"/*opt*/ message = "...", /*opt*/ label = "...", /*opt*/ note = "...""#]), + |this, cx, args| { + if !cx.features().diagnostic_on_missing_args() { + return; + } + + let span = cx.attr_span; + this.span = Some(span); + + // Lint emitted in `check_attr.rs`. + if !matches!(cx.target, Target::MacroDef) { + return; + } + + let items = match args { + ArgParser::List(items) if items.len() != 0 => items, + ArgParser::NoArgs | ArgParser::List(_) => { + cx.emit_lint( + MALFORMED_DIAGNOSTIC_ATTRIBUTES, + AttributeLintKind::MissingOptionsForOnMissingArgs, + span, + ); + return; + } + ArgParser::NameValue(_) => { + cx.emit_lint( + MALFORMED_DIAGNOSTIC_ATTRIBUTES, + AttributeLintKind::MalformedOnMissingArgsAttr { span }, + span, + ); + return; + } + }; + + let Some(directive) = + parse_directive_items(cx, Mode::DiagnosticOnMissingArgs, items.mixed(), true) + else { + return; + }; + merge_directives(cx, &mut this.directive, (span, directive)); + }, + )]; + + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS); + + fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option { + if let Some(span) = self.span { + Some(AttributeKind::OnMissingArgs { + span, + directive: self.directive.map(|d| Box::new(d.1)), + }) + } else { + None + } + } +} diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index b87a71bcbd92b..e77d3567035ff 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -30,6 +30,7 @@ use crate::attributes::debugger::*; use crate::attributes::deprecation::*; use crate::attributes::diagnostic::do_not_recommend::*; use crate::attributes::diagnostic::on_const::*; +use crate::attributes::diagnostic::on_missing_args::*; use crate::attributes::diagnostic::on_move::*; use crate::attributes::diagnostic::on_unimplemented::*; use crate::attributes::doc::*; @@ -154,6 +155,7 @@ attribute_parsers!( MacroUseParser, NakedParser, OnConstParser, + OnMissingArgsParser, OnMoveParser, OnUnimplementedParser, RustcAlignParser, diff --git a/compiler/rustc_expand/src/mbe/diagnostics.rs b/compiler/rustc_expand/src/mbe/diagnostics.rs index fd2e4e3ec39fe..6a62f9e4ab47a 100644 --- a/compiler/rustc_expand/src/mbe/diagnostics.rs +++ b/compiler/rustc_expand/src/mbe/diagnostics.rs @@ -3,6 +3,7 @@ use std::borrow::Cow; use rustc_ast::token::{self, Token}; use rustc_ast::tokenstream::TokenStream; use rustc_errors::{Applicability, Diag, DiagCtxtHandle, DiagMessage}; +use rustc_hir::attrs::diagnostic::{Directive, FormatArgs, OnUnimplementedNote}; use rustc_macros::Subdiagnostic; use rustc_parse::parser::{Parser, Recovery, token_descr}; use rustc_session::parse::ParseSess; @@ -32,6 +33,7 @@ pub(super) fn failed_to_match_macro( args: FailedMacro<'_>, body: &TokenStream, rules: &[MacroRule], + on_missing_args: Option<&Directive>, ) -> (Span, ErrorGuaranteed) { debug!("failed to match macro"); let def_head_span = if !def_span.is_dummy() && !psess.source_map().is_imported(def_span) { @@ -72,9 +74,20 @@ pub(super) fn failed_to_match_macro( }; let span = token.span.substitute_dummy(sp); + let custom = if matches!(token.kind, token::Eof) { + on_missing_args.map(on_missing_args_diagnostic) + } else { + None + }; + let custom_message = custom.as_ref().and_then(|diag| diag.message.clone()); + let custom_label = custom.as_ref().and_then(|diag| diag.label.clone()); + let custom_notes = custom.map_or_else(Vec::new, |diag| diag.notes); - let mut err = psess.dcx().struct_span_err(span, parse_failure_msg(&token, None)); - err.span_label(span, label); + let mut err = match custom_message { + Some(message) => psess.dcx().struct_span_err(span, message), + None => psess.dcx().struct_span_err(span, parse_failure_msg(&token, None)), + }; + err.span_label(span, custom_label.unwrap_or_else(|| label.to_string())); if !def_head_span.is_dummy() { err.span_label(def_head_span, "when calling this macro"); } @@ -86,6 +99,9 @@ pub(super) fn failed_to_match_macro( } else { err.note(format!("while trying to match {remaining_matcher}")); } + for note in custom_notes { + err.note(note); + } if let MatcherLoc::Token { token: expected_token } = &remaining_matcher && (matches!(expected_token.kind, token::OpenInvisible(_)) @@ -128,6 +144,22 @@ pub(super) fn failed_to_match_macro( (sp, guar) } +fn on_missing_args_diagnostic(directive: &Directive) -> OnUnimplementedNote { + let args = FormatArgs { + this: String::new(), + trait_sugared: String::new(), + item_context: "macro invocation", + generic_args: Vec::new(), + }; + + OnUnimplementedNote { + message: directive.message.as_ref().map(|(_, message)| message.format(&args)), + label: directive.label.as_ref().map(|(_, label)| label.format(&args)), + notes: directive.notes.iter().map(|note| note.format(&args)).collect(), + ..Default::default() + } +} + /// The tracker used for the slow error path that collects useful info for diagnostics. struct CollectTrackerAndEmitter<'dcx, 'matcher> { dcx: DiagCtxtHandle<'dcx>, diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs index fd5dac3cd9263..6d27988934d60 100644 --- a/compiler/rustc_expand/src/mbe/macro_rules.rs +++ b/compiler/rustc_expand/src/mbe/macro_rules.rs @@ -14,6 +14,7 @@ use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; use rustc_errors::{Applicability, Diag, ErrorGuaranteed, MultiSpan}; use rustc_feature::Features; use rustc_hir as hir; +use rustc_hir::attrs::diagnostic::Directive; use rustc_hir::def::MacroKinds; use rustc_hir::find_attr; use rustc_lint_defs::builtin::{ @@ -164,6 +165,7 @@ pub struct MacroRulesMacroExpander { node_id: NodeId, name: Ident, span: Span, + on_missing_args: Option, transparency: Transparency, kinds: MacroKinds, rules: Vec, @@ -194,7 +196,8 @@ impl MacroRulesMacroExpander { ) -> Result { // This is similar to `expand_macro`, but they have very different signatures, and will // diverge further once derives support arguments. - let Self { name, ref rules, node_id, .. } = *self; + let name = self.name; + let rules = &self.rules; let psess = &cx.sess.psess; if cx.trace_macros() { @@ -220,8 +223,8 @@ impl MacroRulesMacroExpander { trace_macros_note(&mut cx.expansions, sp, msg); } - if is_defined_in_current_crate(node_id) { - cx.resolver.record_macro_rule_usage(node_id, rule_index); + if is_defined_in_current_crate(self.node_id) { + cx.resolver.record_macro_rule_usage(self.node_id, rule_index); } Ok(tts) @@ -236,6 +239,7 @@ impl MacroRulesMacroExpander { FailedMacro::Derive, body, rules, + self.on_missing_args.as_ref(), ); cx.macro_error_and_trace_macros_diag(); Err(guar) @@ -260,6 +264,7 @@ impl TTMacroExpander for MacroRulesMacroExpander { self.transparency, input, &self.rules, + self.on_missing_args.as_ref(), )) } } @@ -294,6 +299,7 @@ impl AttrProcMacro for MacroRulesMacroExpander { args, body, &self.rules, + self.on_missing_args.as_ref(), ) } } @@ -355,7 +361,7 @@ impl<'matcher> Tracker<'matcher> for NoopTracker { } /// Expands the rules based macro defined by `rules` for a given input `arg`. -#[instrument(skip(cx, transparency, arg, rules))] +#[instrument(skip(cx, transparency, arg, rules, on_missing_args))] fn expand_macro<'cx, 'a: 'cx>( cx: &'cx mut ExtCtxt<'_>, sp: Span, @@ -365,6 +371,7 @@ fn expand_macro<'cx, 'a: 'cx>( transparency: Transparency, arg: TokenStream, rules: &'a [MacroRule], + on_missing_args: Option<&Directive>, ) -> Box { let psess = &cx.sess.psess; @@ -423,6 +430,7 @@ fn expand_macro<'cx, 'a: 'cx>( FailedMacro::Func, &arg, rules, + on_missing_args, ); cx.macro_error_and_trace_macros_diag(); DummyResult::any(span, guar) @@ -431,7 +439,7 @@ fn expand_macro<'cx, 'a: 'cx>( } /// Expands the rules based macro defined by `rules` for a given attribute `args` and `body`. -#[instrument(skip(cx, transparency, args, body, rules))] +#[instrument(skip(cx, transparency, args, body, rules, on_missing_args))] fn expand_macro_attr( cx: &mut ExtCtxt<'_>, sp: Span, @@ -443,6 +451,7 @@ fn expand_macro_attr( args: TokenStream, body: TokenStream, rules: &[MacroRule], + on_missing_args: Option<&Directive>, ) -> Result { let psess = &cx.sess.psess; // Macros defined in the current crate have a real node id, @@ -507,6 +516,7 @@ fn expand_macro_attr( FailedMacro::Attr(&args), &body, rules, + on_missing_args, ); cx.trace_macros_diag(); Err(guar) @@ -849,7 +859,22 @@ pub fn compile_declarative_macro( // Return the number of rules for unused rule linting, if this is a local macro. let nrules = if is_defined_in_current_crate(node_id) { rules.len() } else { 0 }; - let exp = MacroRulesMacroExpander { name: ident, kinds, span, node_id, transparency, rules }; + let on_missing_args = find_attr!( + attrs, + OnMissingArgs { directive, .. } => directive.clone() + ) + .flatten() + .map(|directive| *directive); + + let exp = MacroRulesMacroExpander { + name: ident, + kinds, + span, + node_id, + on_missing_args, + transparency, + rules, + }; (mk_syn_ext(SyntaxExtensionKind::MacroRules(Arc::new(exp))), nrules) } diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index acbcba90fbcc0..621581b82198e 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -1587,6 +1587,7 @@ pub fn is_stable_diagnostic_attribute(sym: Symbol, features: &Features) -> bool match sym { sym::on_unimplemented | sym::do_not_recommend => true, sym::on_const => features.diagnostic_on_const(), + sym::on_missing_args => features.diagnostic_on_missing_args(), sym::on_move => features.diagnostic_on_move(), _ => false, } diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index 859a1ad391cb9..d33cc2f6faf6b 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -472,6 +472,8 @@ declare_features! ( (unstable, derive_from, "1.91.0", Some(144889)), /// Allows giving non-const impls custom diagnostic messages if attempted to be used as const (unstable, diagnostic_on_const, "1.93.0", Some(143874)), + /// Allows macros to customize incomplete-argument diagnostics. + (unstable, diagnostic_on_missing_args, "CURRENT_RUSTC_VERSION", Some(152494)), /// Allows giving on-move borrowck custom diagnostic messages for a type (unstable, diagnostic_on_move, "CURRENT_RUSTC_VERSION", Some(154181)), /// Allows `#[doc(cfg(...))]`. diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index f18d5a1f190a2..0741f1c3fcfc9 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -1322,6 +1322,13 @@ pub enum AttributeKind { directive: Option>, }, + /// Represents `#[diagnostic::on_missing_args]`. + OnMissingArgs { + span: Span, + /// None if the directive was malformed in some way. + directive: Option>, + }, + /// Represents `#[diagnostic::on_move]` OnMove { span: Span, diff --git a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs index 6612ebd6135b8..5eed6ca7d67df 100644 --- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs +++ b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs @@ -78,6 +78,7 @@ impl AttributeKind { NoStd(..) => No, NonExhaustive(..) => Yes, // Needed for rustdoc OnConst { .. } => Yes, + OnMissingArgs { .. } => Yes, OnMove { .. } => Yes, OnUnimplemented { .. } => Yes, Optimize(..) => No, diff --git a/compiler/rustc_lint/src/early/diagnostics.rs b/compiler/rustc_lint/src/early/diagnostics.rs index c1779909e67d0..6fa395347ff8a 100644 --- a/compiler/rustc_lint/src/early/diagnostics.rs +++ b/compiler/rustc_lint/src/early/diagnostics.rs @@ -281,6 +281,9 @@ impl<'a> Diagnostic<'a, ()> for DecorateAttrLint<'_, '_, '_> { &AttributeLintKind::MalformedOnConstAttr { span } => { lints::MalformedOnConstAttrLint { span }.into_diag(dcx, level) } + &AttributeLintKind::MalformedOnMissingArgsAttr { span } => { + lints::MalformedOnMissingArgsAttrLint { span }.into_diag(dcx, level) + } AttributeLintKind::MalformedDiagnosticFormat { warning } => match warning { FormatWarning::PositionalArgument { .. } => { lints::DisallowedPositionalArgument.into_diag(dcx, level) @@ -302,6 +305,9 @@ impl<'a> Diagnostic<'a, ()> for DecorateAttrLint<'_, '_, '_> { &AttributeLintKind::MissingOptionsForOnConst => { lints::MissingOptionsForOnConstAttr.into_diag(dcx, level) } + &AttributeLintKind::MissingOptionsForOnMissingArgs => { + lints::MissingOptionsForOnMissingArgsAttr.into_diag(dcx, level) + } &AttributeLintKind::MalformedOnMoveAttr { span } => { lints::MalformedOnMoveAttrLint { span }.into_diag(dcx, level) } diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index 107f31260b9e2..1d4baffb31bf6 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -3646,6 +3646,11 @@ pub(crate) struct MissingOptionsForOnUnimplementedAttr; #[help("at least one of the `message`, `note` and `label` options are expected")] pub(crate) struct MissingOptionsForOnConstAttr; +#[derive(Diagnostic)] +#[diag("missing options for `on_missing_args` attribute")] +#[help("at least one of the `message`, `note` and `label` options are expected")] +pub(crate) struct MissingOptionsForOnMissingArgsAttr; + #[derive(Diagnostic)] #[diag("missing options for `on_move` attribute")] #[help("at least one of the `message`, `note` and `label` options are expected")] @@ -3667,6 +3672,14 @@ pub(crate) struct MalformedOnConstAttrLint { pub span: Span, } +#[derive(Diagnostic)] +#[diag("malformed `on_missing_args` attribute")] +#[help("only `message`, `note` and `label` are allowed as options")] +pub(crate) struct MalformedOnMissingArgsAttrLint { + #[label("invalid option found here")] + pub span: Span, +} + #[derive(Diagnostic)] #[diag("`Eq::assert_receiver_is_total_eq` should never be implemented by hand")] #[note("this method was used to add checks to the `Eq` derive macro")] diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs index 2ebbe633ecd1a..111e866ade2a9 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -767,6 +767,9 @@ pub enum AttributeLintKind { MalformedOnConstAttr { span: Span, }, + MalformedOnMissingArgsAttr { + span: Span, + }, MalformedOnMoveAttr { span: Span, }, @@ -785,6 +788,7 @@ pub enum AttributeLintKind { }, MissingOptionsForOnUnimplemented, MissingOptionsForOnConst, + MissingOptionsForOnMissingArgs, MissingOptionsForOnMove, OnMoveMalformedFormatLiterals { name: Symbol, diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 12b583d8fee15..1d5e1bcdfa999 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -62,6 +62,10 @@ struct DiagnosticOnConstOnlyForTraitImpls { item_span: Span, } +#[derive(Diagnostic)] +#[diag("`#[diagnostic::on_missing_args]` can only be applied to macro definitions")] +struct DiagnosticOnMissingArgsOnlyForMacros; + #[derive(Diagnostic)] #[diag("`#[diagnostic::on_const]` can only be applied to non-const trait impls")] struct DiagnosticOnConstOnlyForNonConstTraitImpls { @@ -217,7 +221,15 @@ impl<'tcx> CheckAttrVisitor<'tcx> { }, Attribute::Parsed(AttributeKind::DoNotRecommend{attr_span}) => {self.check_do_not_recommend(*attr_span, hir_id, target, item)}, Attribute::Parsed(AttributeKind::OnUnimplemented{span, directive}) => {self.check_diagnostic_on_unimplemented(*span, hir_id, target,directive.as_deref())}, - Attribute::Parsed(AttributeKind::OnConst{span, ..}) => {self.check_diagnostic_on_const(*span, hir_id, target, item)} + Attribute::Parsed(AttributeKind::OnConst{span, ..}) => {self.check_diagnostic_on_const(*span, hir_id, target, item)}, + Attribute::Parsed(AttributeKind::OnMissingArgs { span, directive }) => { + self.check_diagnostic_on_missing_args( + *span, + hir_id, + target, + directive.as_deref(), + ) + } Attribute::Parsed(AttributeKind::OnMove { span, directive }) => { self.check_diagnostic_on_move(*span, hir_id, target, directive.as_deref()) }, @@ -665,6 +677,37 @@ impl<'tcx> CheckAttrVisitor<'tcx> { // The traits' or the impls'? } + /// Checks if `#[diagnostic::on_missing_args]` is applied to a macro definition. + fn check_diagnostic_on_missing_args( + &self, + attr_span: Span, + hir_id: HirId, + target: Target, + directive: Option<&Directive>, + ) { + if !matches!(target, Target::MacroDef) { + self.tcx.emit_node_span_lint( + MISPLACED_DIAGNOSTIC_ATTRIBUTES, + hir_id, + attr_span, + DiagnosticOnMissingArgsOnlyForMacros, + ); + } + + if matches!(target, Target::MacroDef) + && let Some(directive) = directive + { + directive.visit_params(&mut |argument_name, span| { + self.tcx.emit_node_span_lint( + MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, + hir_id, + span, + errors::OnMissingArgsMalformedFormatLiterals { name: argument_name }, + ) + }); + } + } + /// Checks if `#[diagnostic::on_move]` is applied to an ADT definition fn check_diagnostic_on_move( &self, diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs index 628d0b0c961a1..8ff1bd9451aae 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -1356,3 +1356,10 @@ pub(crate) struct UnknownFormatParameterForOnUnimplementedAttr { pub(crate) struct OnMoveMalformedFormatLiterals { pub name: Symbol, } + +#[derive(Diagnostic)] +#[diag("unknown parameter `{$name}`")] +#[help("format parameters are not supported by `#[diagnostic::on_missing_args]`")] +pub(crate) struct OnMissingArgsMalformedFormatLiterals { + pub name: Symbol, +} diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs index 619e61211984d..d2def1ea0a7d9 100644 --- a/compiler/rustc_resolve/src/macros.rs +++ b/compiler/rustc_resolve/src/macros.rs @@ -712,8 +712,13 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { feature_err(&self.tcx.sess, sym::custom_inner_attributes, path.span, msg).emit(); } - const DIAG_ATTRS: &[Symbol] = - &[sym::on_unimplemented, sym::do_not_recommend, sym::on_const, sym::on_move]; + const DIAG_ATTRS: &[Symbol] = &[ + sym::on_unimplemented, + sym::do_not_recommend, + sym::on_const, + sym::on_missing_args, + sym::on_move, + ]; if res == Res::NonMacroAttr(NonMacroAttrKind::Tool) && let [namespace, attribute, ..] = &*path.segments diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index aab4aef43a53c..bfa5ab75da932 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -798,6 +798,7 @@ symbols! { diagnostic, diagnostic_namespace, diagnostic_on_const, + diagnostic_on_missing_args, diagnostic_on_move, dialect, direct, @@ -1412,6 +1413,7 @@ symbols! { omit_gdb_pretty_printer_section, on, on_const, + on_missing_args, on_move, on_unimplemented, opaque, diff --git a/library/core/src/field.rs b/library/core/src/field.rs index e8ef309b9c84f..1811ddbd6d7b9 100644 --- a/library/core/src/field.rs +++ b/library/core/src/field.rs @@ -42,6 +42,9 @@ impl Clone /// variant must also be specified. Only a single field is supported. #[unstable(feature = "field_projections", issue = "145383")] #[allow_internal_unstable(field_representing_type_raw, builtin_syntax)] +#[diagnostic::on_missing_args( + note = "this macro expects a container type and a field path, like `field_of!(Type, field)` or `field_of!(Enum, Variant.field)`" +)] // NOTE: when stabilizing this macro, we can never add new trait impls for `FieldRepresentingType`, // since it is `#[fundamental]` and thus could break users of this macro, since the compiler expands // it to `FieldRepresentingType<...>`. Thus stabilizing this requires careful thought about the diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs index 312295154dc52..14fd517a0a04f 100644 --- a/library/core/src/lib.rs +++ b/library/core/src/lib.rs @@ -131,6 +131,7 @@ #![feature(deprecated_suggestion)] #![feature(derive_const)] #![feature(diagnostic_on_const)] +#![feature(diagnostic_on_missing_args)] #![feature(doc_cfg)] #![feature(doc_notable_trait)] #![feature(extern_types)] diff --git a/library/core/src/mem/mod.rs b/library/core/src/mem/mod.rs index a987970c9bcc3..7b7042f4a7234 100644 --- a/library/core/src/mem/mod.rs +++ b/library/core/src/mem/mod.rs @@ -1470,6 +1470,9 @@ impl SizedTypeProperties for T {} /// [`offset_of_enum`]: https://doc.rust-lang.org/nightly/unstable-book/language-features/offset-of-enum.html /// [`offset_of_slice`]: https://doc.rust-lang.org/nightly/unstable-book/language-features/offset-of-slice.html #[stable(feature = "offset_of", since = "1.77.0")] +#[diagnostic::on_missing_args( + note = "this macro expects a container type and a dot-separated field or variant path, like `offset_of!(Type, field)`" +)] #[allow_internal_unstable(builtin_syntax, core_intrinsics)] pub macro offset_of($Container:ty, $($fields:expr)+ $(,)?) { // The `{}` is for better error messages diff --git a/src/doc/unstable-book/src/language-features/diagnostic-on-missing-args.md b/src/doc/unstable-book/src/language-features/diagnostic-on-missing-args.md new file mode 100644 index 0000000000000..3103b374b11ee --- /dev/null +++ b/src/doc/unstable-book/src/language-features/diagnostic-on-missing-args.md @@ -0,0 +1,31 @@ +# `diagnostic_on_missing_args` + +The tracking issue for this feature is: [#152494] + +[#152494]: https://github.com/rust-lang/rust/issues/152494 + +------------------------ + +The `diagnostic_on_missing_args` feature adds the +`#[diagnostic::on_missing_args(...)]` attribute for declarative macros. +It lets a macro definition customize the diagnostic that is emitted when an invocation ends before +all required arguments were provided. + +This attribute currently applies to declarative macros such as `macro_rules!` and `pub macro`. +It only affects diagnostics for incomplete invocations; other matcher failures continue to use the +usual macro diagnostics. + +```rust,compile_fail +#![feature(diagnostic_on_missing_args)] + +#[diagnostic::on_missing_args( + message = "pair! is missing its second argument", + label = "add the missing value here", + note = "this macro expects a type and a value, like `pair!(u8, 0)`", +)] +macro_rules! pair { + ($ty:ty, $value:expr) => {}; +} + +pair!(u8); +``` diff --git a/tests/ui/diagnostic_namespace/on_missing_args/auxiliary/other.rs b/tests/ui/diagnostic_namespace/on_missing_args/auxiliary/other.rs new file mode 100644 index 0000000000000..0b5b190626d71 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_missing_args/auxiliary/other.rs @@ -0,0 +1,11 @@ +#![feature(diagnostic_on_missing_args)] + +#[macro_export] +#[diagnostic::on_missing_args( + message = "pair! is missing its second argument", + label = "add the missing value here", + note = "this macro expects a type and a value, like `pair!(u8, 0)`", +)] +macro_rules! pair { + ($ty:ty, $value:expr) => {}; +} diff --git a/tests/ui/diagnostic_namespace/on_missing_args/error_is_shown_in_downstream_crates.rs b/tests/ui/diagnostic_namespace/on_missing_args/error_is_shown_in_downstream_crates.rs new file mode 100644 index 0000000000000..59bc0569d51ae --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_missing_args/error_is_shown_in_downstream_crates.rs @@ -0,0 +1,11 @@ +//@ aux-build:other.rs + +extern crate other; + +fn main() { + other::pair!(u8); + //~^ ERROR pair! is missing its second argument + //~| NOTE add the missing value here + //~| NOTE while trying to match `,` + //~| NOTE this macro expects a type and a value, like `pair!(u8, 0)` +} diff --git a/tests/ui/diagnostic_namespace/on_missing_args/error_is_shown_in_downstream_crates.stderr b/tests/ui/diagnostic_namespace/on_missing_args/error_is_shown_in_downstream_crates.stderr new file mode 100644 index 0000000000000..7e3c4a6f128b7 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_missing_args/error_is_shown_in_downstream_crates.stderr @@ -0,0 +1,15 @@ +error: pair! is missing its second argument + --> $DIR/error_is_shown_in_downstream_crates.rs:6:20 + | +LL | other::pair!(u8); + | ^ add the missing value here + | +note: while trying to match `,` + --> $DIR/auxiliary/other.rs:10:12 + | +LL | ($ty:ty, $value:expr) => {}; + | ^ + = note: this macro expects a type and a value, like `pair!(u8, 0)` + +error: aborting due to 1 previous error + diff --git a/tests/ui/diagnostic_namespace/on_missing_args/message_and_label.rs b/tests/ui/diagnostic_namespace/on_missing_args/message_and_label.rs new file mode 100644 index 0000000000000..253aabf837559 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_missing_args/message_and_label.rs @@ -0,0 +1,19 @@ +#![feature(diagnostic_on_missing_args)] + +#[diagnostic::on_missing_args( + message = "pair! is missing its second argument", + label = "add the missing value here", + note = "this macro expects a type and a value, like `pair!(u8, 0)`", +)] +macro_rules! pair { + //~^ NOTE when calling this macro + ($ty:ty, $value:expr) => {}; + //~^ NOTE while trying to match `,` +} + +fn main() { + pair!(u8); + //~^ ERROR pair! is missing its second argument + //~| NOTE add the missing value here + //~| NOTE this macro expects a type and a value, like `pair!(u8, 0)` +} diff --git a/tests/ui/diagnostic_namespace/on_missing_args/message_and_label.stderr b/tests/ui/diagnostic_namespace/on_missing_args/message_and_label.stderr new file mode 100644 index 0000000000000..3c05128da423d --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_missing_args/message_and_label.stderr @@ -0,0 +1,18 @@ +error: pair! is missing its second argument + --> $DIR/message_and_label.rs:15:13 + | +LL | macro_rules! pair { + | ----------------- when calling this macro +... +LL | pair!(u8); + | ^ add the missing value here + | +note: while trying to match `,` + --> $DIR/message_and_label.rs:10:12 + | +LL | ($ty:ty, $value:expr) => {}; + | ^ + = note: this macro expects a type and a value, like `pair!(u8, 0)` + +error: aborting due to 1 previous error + diff --git a/tests/ui/diagnostic_namespace/on_missing_args/on_missing_args.rs b/tests/ui/diagnostic_namespace/on_missing_args/on_missing_args.rs new file mode 100644 index 0000000000000..266f0c213c983 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_missing_args/on_missing_args.rs @@ -0,0 +1,19 @@ +#![feature(diagnostic_on_missing_args)] + +#[diagnostic::on_missing_args( + note = "this macro expects a type and a value, like `pair!(u8, 0)`", + note = "make sure to pass both arguments", +)] +macro_rules! pair { + //~^ NOTE when calling this macro + ($ty:ty, $value:expr) => {}; + //~^ NOTE while trying to match `,` +} + +fn main() { + pair!(u8); + //~^ ERROR unexpected end of macro invocation + //~| NOTE missing tokens in macro arguments + //~| NOTE this macro expects a type and a value, like `pair!(u8, 0)` + //~| NOTE make sure to pass both arguments +} diff --git a/tests/ui/diagnostic_namespace/on_missing_args/on_missing_args.stderr b/tests/ui/diagnostic_namespace/on_missing_args/on_missing_args.stderr new file mode 100644 index 0000000000000..f4021115fbac3 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_missing_args/on_missing_args.stderr @@ -0,0 +1,19 @@ +error: unexpected end of macro invocation + --> $DIR/on_missing_args.rs:14:13 + | +LL | macro_rules! pair { + | ----------------- when calling this macro +... +LL | pair!(u8); + | ^ missing tokens in macro arguments + | +note: while trying to match `,` + --> $DIR/on_missing_args.rs:9:12 + | +LL | ($ty:ty, $value:expr) => {}; + | ^ + = note: this macro expects a type and a value, like `pair!(u8, 0)` + = note: make sure to pass both arguments + +error: aborting due to 1 previous error + diff --git a/tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_invalid_formats.rs b/tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_invalid_formats.rs new file mode 100644 index 0000000000000..e383e2b4c03fb --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_invalid_formats.rs @@ -0,0 +1,14 @@ +//@ check-pass +#![feature(diagnostic_on_missing_args)] + +#[diagnostic::on_missing_args( + message = "pair! is missing {T}", + //~^ WARN unknown parameter `T` +)] +macro_rules! pair { + ($ty:ty, $value:expr) => {}; +} + +fn main() { + pair!(u8, 0); +} diff --git a/tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_invalid_formats.stderr b/tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_invalid_formats.stderr new file mode 100644 index 0000000000000..de2296f15813e --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_invalid_formats.stderr @@ -0,0 +1,11 @@ +warning: unknown parameter `T` + --> $DIR/report_warning_on_invalid_formats.rs:5:34 + | +LL | message = "pair! is missing {T}", + | ^ + | + = help: format parameters are not supported by `#[diagnostic::on_missing_args]` + = note: `#[warn(malformed_diagnostic_format_literals)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default + +warning: 1 warning emitted + diff --git a/tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_invalid_meta_item_syntax.rs b/tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_invalid_meta_item_syntax.rs new file mode 100644 index 0000000000000..1bb8d258d3f90 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_invalid_meta_item_syntax.rs @@ -0,0 +1,12 @@ +//@ check-pass +#![feature(diagnostic_on_missing_args)] + +#[diagnostic::on_missing_args = "foo"] +//~^ WARN malformed `on_missing_args` attribute [malformed_diagnostic_attributes] +macro_rules! pair { + ($ty:ty, $value:expr) => {}; +} + +fn main() { + pair!(u8, 0); +} diff --git a/tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_invalid_meta_item_syntax.stderr b/tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_invalid_meta_item_syntax.stderr new file mode 100644 index 0000000000000..0b87ab8f36345 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_invalid_meta_item_syntax.stderr @@ -0,0 +1,11 @@ +warning: malformed `on_missing_args` attribute + --> $DIR/report_warning_on_invalid_meta_item_syntax.rs:4:1 + | +LL | #[diagnostic::on_missing_args = "foo"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ invalid option found here + | + = help: only `message`, `note` and `label` are allowed as options + = note: `#[warn(malformed_diagnostic_attributes)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default + +warning: 1 warning emitted + diff --git a/tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_missing_options.rs b/tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_missing_options.rs new file mode 100644 index 0000000000000..ac61c251f6db0 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_missing_options.rs @@ -0,0 +1,12 @@ +//@ check-pass +#![feature(diagnostic_on_missing_args)] + +#[diagnostic::on_missing_args] +//~^ WARN missing options for `on_missing_args` attribute [malformed_diagnostic_attributes] +macro_rules! pair { + ($ty:ty, $value:expr) => {}; +} + +fn main() { + pair!(u8, 0); +} diff --git a/tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_missing_options.stderr b/tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_missing_options.stderr new file mode 100644 index 0000000000000..d0ae9430af9e7 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_missing_options.stderr @@ -0,0 +1,11 @@ +warning: missing options for `on_missing_args` attribute + --> $DIR/report_warning_on_missing_options.rs:4:1 + | +LL | #[diagnostic::on_missing_args] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: at least one of the `message`, `note` and `label` options are expected + = note: `#[warn(malformed_diagnostic_attributes)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default + +warning: 1 warning emitted + diff --git a/tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_non_macro.rs b/tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_non_macro.rs new file mode 100644 index 0000000000000..8d66763391b05 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_non_macro.rs @@ -0,0 +1,10 @@ +//@ check-pass +#![feature(diagnostic_on_missing_args)] + +#[diagnostic::on_missing_args(message = "not allowed here")] +//~^ WARN `#[diagnostic::on_missing_args]` can only be applied to macro definitions +struct Foo; + +fn main() { + let _ = Foo; +} diff --git a/tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_non_macro.stderr b/tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_non_macro.stderr new file mode 100644 index 0000000000000..fcd23391eec18 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_non_macro.stderr @@ -0,0 +1,10 @@ +warning: `#[diagnostic::on_missing_args]` can only be applied to macro definitions + --> $DIR/report_warning_on_non_macro.rs:4:1 + | +LL | #[diagnostic::on_missing_args(message = "not allowed here")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `#[warn(misplaced_diagnostic_attributes)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default + +warning: 1 warning emitted + diff --git a/tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_unknown_options.rs b/tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_unknown_options.rs new file mode 100644 index 0000000000000..392567fdbfb61 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_unknown_options.rs @@ -0,0 +1,12 @@ +//@ check-pass +#![feature(diagnostic_on_missing_args)] + +#[diagnostic::on_missing_args(unsupported = "foo")] +//~^ WARN malformed `on_missing_args` attribute [malformed_diagnostic_attributes] +macro_rules! pair { + ($ty:ty, $value:expr) => {}; +} + +fn main() { + pair!(u8, 0); +} diff --git a/tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_unknown_options.stderr b/tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_unknown_options.stderr new file mode 100644 index 0000000000000..e2df1eeff6033 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_unknown_options.stderr @@ -0,0 +1,11 @@ +warning: malformed `on_missing_args` attribute + --> $DIR/report_warning_on_unknown_options.rs:4:31 + | +LL | #[diagnostic::on_missing_args(unsupported = "foo")] + | ^^^^^^^^^^^^^^^^^^^ invalid option found here + | + = help: only `message`, `note` and `label` are allowed as options + = note: `#[warn(malformed_diagnostic_attributes)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default + +warning: 1 warning emitted + diff --git a/tests/ui/feature-gates/feature-gate-diagnostic-on-missing-args.rs b/tests/ui/feature-gates/feature-gate-diagnostic-on-missing-args.rs new file mode 100644 index 0000000000000..6d9843516e67a --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-diagnostic-on-missing-args.rs @@ -0,0 +1,15 @@ +//! This is an unusual feature gate test, as it doesn't test the feature +//! gate, but the fact that not adding the feature gate will cause the +//! diagnostic to not emit the custom diagnostic message. +#[diagnostic::on_missing_args(note = "custom note")] +macro_rules! pair { + //~^ NOTE when calling this macro + ($ty:ty, $value:expr) => {}; + //~^ NOTE while trying to match `,` +} + +fn main() { + pair!(u8); + //~^ ERROR unexpected end of macro invocation + //~| NOTE missing tokens in macro arguments +} diff --git a/tests/ui/feature-gates/feature-gate-diagnostic-on-missing-args.stderr b/tests/ui/feature-gates/feature-gate-diagnostic-on-missing-args.stderr new file mode 100644 index 0000000000000..84d5b58152a74 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-diagnostic-on-missing-args.stderr @@ -0,0 +1,17 @@ +error: unexpected end of macro invocation + --> $DIR/feature-gate-diagnostic-on-missing-args.rs:12:13 + | +LL | macro_rules! pair { + | ----------------- when calling this macro +... +LL | pair!(u8); + | ^ missing tokens in macro arguments + | +note: while trying to match `,` + --> $DIR/feature-gate-diagnostic-on-missing-args.rs:7:12 + | +LL | ($ty:ty, $value:expr) => {}; + | ^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/field_representing_types/invalid.next.stderr b/tests/ui/field_representing_types/invalid.next.stderr index 2a5e884a5e3f9..a519cc1dda635 100644 --- a/tests/ui/field_representing_types/invalid.next.stderr +++ b/tests/ui/field_representing_types/invalid.next.stderr @@ -6,6 +6,7 @@ LL | let _: field_of!(Struct); | note: while trying to match `,` --> $SRC_DIR/core/src/field.rs:LL:COL + = note: this macro expects a container type and a field path, like `field_of!(Type, field)` or `field_of!(Enum, Variant.field)` error: unexpected end of macro invocation --> $DIR/invalid.rs:24:29 @@ -15,6 +16,7 @@ LL | let _: field_of!(Struct,); | note: while trying to match meta-variable `$fields:expr` --> $SRC_DIR/core/src/field.rs:LL:COL + = note: this macro expects a container type and a field path, like `field_of!(Type, field)` or `field_of!(Enum, Variant.field)` error: no rules expected `extra` --> $DIR/invalid.rs:25:37 diff --git a/tests/ui/field_representing_types/invalid.old.stderr b/tests/ui/field_representing_types/invalid.old.stderr index 2a5e884a5e3f9..a519cc1dda635 100644 --- a/tests/ui/field_representing_types/invalid.old.stderr +++ b/tests/ui/field_representing_types/invalid.old.stderr @@ -6,6 +6,7 @@ LL | let _: field_of!(Struct); | note: while trying to match `,` --> $SRC_DIR/core/src/field.rs:LL:COL + = note: this macro expects a container type and a field path, like `field_of!(Type, field)` or `field_of!(Enum, Variant.field)` error: unexpected end of macro invocation --> $DIR/invalid.rs:24:29 @@ -15,6 +16,7 @@ LL | let _: field_of!(Struct,); | note: while trying to match meta-variable `$fields:expr` --> $SRC_DIR/core/src/field.rs:LL:COL + = note: this macro expects a container type and a field path, like `field_of!(Type, field)` or `field_of!(Enum, Variant.field)` error: no rules expected `extra` --> $DIR/invalid.rs:25:37 diff --git a/tests/ui/offset-of/offset-of-arg-count.stderr b/tests/ui/offset-of/offset-of-arg-count.stderr index 0772bb18e0c62..747b64c183196 100644 --- a/tests/ui/offset-of/offset-of-arg-count.stderr +++ b/tests/ui/offset-of/offset-of-arg-count.stderr @@ -6,6 +6,7 @@ LL | offset_of!(NotEnoughArguments); | note: while trying to match `,` --> $SRC_DIR/core/src/mem/mod.rs:LL:COL + = note: this macro expects a container type and a dot-separated field or variant path, like `offset_of!(Type, field)` error: unexpected end of macro invocation --> $DIR/offset-of-arg-count.rs:5:45 @@ -15,6 +16,7 @@ LL | offset_of!(NotEnoughArgumentsWithAComma, ); | note: while trying to match meta-variable `$fields:expr` --> $SRC_DIR/core/src/mem/mod.rs:LL:COL + = note: this macro expects a container type and a dot-separated field or variant path, like `offset_of!(Type, field)` error: no rules expected `too` --> $DIR/offset-of-arg-count.rs:6:34 From d99a72cc8160d9383e47a2b6636738d62613d1e0 Mon Sep 17 00:00:00 2001 From: yukang Date: Mon, 6 Apr 2026 21:26:03 +0800 Subject: [PATCH 2/8] stabilize check-cfg suggestions --- compiler/rustc_lint/src/early/diagnostics/check_cfg.rs | 8 +++++--- tests/ui/cfg/suggest-alternative-name-on-target.rs | 2 +- tests/ui/cfg/suggest-alternative-name-on-target.stderr | 9 ++++----- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_lint/src/early/diagnostics/check_cfg.rs b/compiler/rustc_lint/src/early/diagnostics/check_cfg.rs index a211a1aaa6bdf..9fcabfa623d80 100644 --- a/compiler/rustc_lint/src/early/diagnostics/check_cfg.rs +++ b/compiler/rustc_lint/src/early/diagnostics/check_cfg.rs @@ -438,10 +438,10 @@ pub(super) fn unexpected_cfg_value( } } -/// Ordering of the output is not stable, use this only in diagnostic code. fn possible_well_known_names_for_cfg_value(sess: &Session, value: Symbol) -> Vec { #[allow(rustc::potential_query_instability)] - sess.psess + let mut names = sess + .psess .check_config .well_known_names .iter() @@ -454,5 +454,7 @@ fn possible_well_known_names_for_cfg_value(sess: &Session, value: Symbol) -> Vec .unwrap_or_default() }) .copied() - .collect() + .collect::>(); + names.sort_by(|a, b| a.as_str().cmp(b.as_str())); + names } diff --git a/tests/ui/cfg/suggest-alternative-name-on-target.rs b/tests/ui/cfg/suggest-alternative-name-on-target.rs index e3810dbbc9c36..297321a0e2fe0 100644 --- a/tests/ui/cfg/suggest-alternative-name-on-target.rs +++ b/tests/ui/cfg/suggest-alternative-name-on-target.rs @@ -32,8 +32,8 @@ struct D; #[cfg(target_abi = "windows")] //~^ ERROR unexpected `cfg` condition value: //~| NOTE see for more information about checking conditional configuration -help: `windows` is an expected value for `target_os` +help: `windows` is an expected value for `target_family` | LL - #[cfg(target_abi = "windows")] -LL + #[cfg(target_os = "windows")] +LL + #[cfg(target_family = "windows")] | -help: `windows` is an expected value for `target_family` +help: `windows` is an expected value for `target_os` | LL - #[cfg(target_abi = "windows")] -LL + #[cfg(target_family = "windows")] +LL + #[cfg(target_os = "windows")] | error: aborting due to 5 previous errors - From 489850131115dcfb5c53603837621917d411c118 Mon Sep 17 00:00:00 2001 From: yukang Date: Mon, 6 Apr 2026 21:42:49 +0800 Subject: [PATCH 3/8] fix diagnostic API drift --- compiler/rustc_expand/src/mbe/diagnostics.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_expand/src/mbe/diagnostics.rs b/compiler/rustc_expand/src/mbe/diagnostics.rs index 6a62f9e4ab47a..994bd62923876 100644 --- a/compiler/rustc_expand/src/mbe/diagnostics.rs +++ b/compiler/rustc_expand/src/mbe/diagnostics.rs @@ -3,7 +3,7 @@ use std::borrow::Cow; use rustc_ast::token::{self, Token}; use rustc_ast::tokenstream::TokenStream; use rustc_errors::{Applicability, Diag, DiagCtxtHandle, DiagMessage}; -use rustc_hir::attrs::diagnostic::{Directive, FormatArgs, OnUnimplementedNote}; +use rustc_hir::attrs::diagnostic::{CustomDiagnostic, Directive, FormatArgs}; use rustc_macros::Subdiagnostic; use rustc_parse::parser::{Parser, Recovery, token_descr}; use rustc_session::parse::ParseSess; @@ -144,20 +144,15 @@ pub(super) fn failed_to_match_macro( (sp, guar) } -fn on_missing_args_diagnostic(directive: &Directive) -> OnUnimplementedNote { +fn on_missing_args_diagnostic(directive: &Directive) -> CustomDiagnostic { let args = FormatArgs { this: String::new(), - trait_sugared: String::new(), + this_sugared: String::new(), item_context: "macro invocation", generic_args: Vec::new(), }; - OnUnimplementedNote { - message: directive.message.as_ref().map(|(_, message)| message.format(&args)), - label: directive.label.as_ref().map(|(_, label)| label.format(&args)), - notes: directive.notes.iter().map(|note| note.format(&args)).collect(), - ..Default::default() - } + directive.eval(None, &args) } /// The tracker used for the slow error path that collects useful info for diagnostics. From 1877190cc099a6c32f386ee31e758f34dfac81f9 Mon Sep 17 00:00:00 2001 From: yukang Date: Tue, 7 Apr 2026 09:24:00 +0800 Subject: [PATCH 4/8] support {This} in on_missing_args --- .../src/attributes/diagnostic/mod.rs | 4 ++- compiler/rustc_expand/src/mbe/diagnostics.rs | 35 ++++++++++--------- compiler/rustc_passes/src/errors.rs | 4 ++- .../on_missing_args/message_and_label.rs | 2 +- .../report_warning_on_invalid_formats.stderr | 2 +- 5 files changed, 26 insertions(+), 21 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs index 29c91bb75b5dd..c2e247a83bbc5 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs @@ -345,7 +345,9 @@ fn parse_arg( Position::ArgumentNamed(name) => match (mode, Symbol::intern(name)) { // Only `#[rustc_on_unimplemented]` can use these (Mode::RustcOnUnimplemented { .. }, sym::ItemContext) => FormatArg::ItemContext, - (Mode::RustcOnUnimplemented { .. }, sym::This) => FormatArg::This, + (Mode::RustcOnUnimplemented { .. } | Mode::DiagnosticOnMissingArgs, sym::This) => { + FormatArg::This + } (Mode::RustcOnUnimplemented { .. }, sym::Trait) => FormatArg::Trait, // Any attribute can use these (_, kw::SelfUpper) => FormatArg::SelfUpper, diff --git a/compiler/rustc_expand/src/mbe/diagnostics.rs b/compiler/rustc_expand/src/mbe/diagnostics.rs index 994bd62923876..1eb2cecafc873 100644 --- a/compiler/rustc_expand/src/mbe/diagnostics.rs +++ b/compiler/rustc_expand/src/mbe/diagnostics.rs @@ -74,14 +74,26 @@ pub(super) fn failed_to_match_macro( }; let span = token.span.substitute_dummy(sp); - let custom = if matches!(token.kind, token::Eof) { - on_missing_args.map(on_missing_args_diagnostic) + let CustomDiagnostic { + message: custom_message, label: custom_label, notes: custom_notes, .. + } = if matches!(token.kind, token::Eof) { + let macro_name = name.to_string(); + on_missing_args + .map(|directive| { + directive.eval( + None, + &FormatArgs { + this: macro_name.clone(), + this_sugared: macro_name, + item_context: "macro invocation", + generic_args: Vec::new(), + }, + ) + }) + .unwrap_or_default() } else { - None + CustomDiagnostic::default() }; - let custom_message = custom.as_ref().and_then(|diag| diag.message.clone()); - let custom_label = custom.as_ref().and_then(|diag| diag.label.clone()); - let custom_notes = custom.map_or_else(Vec::new, |diag| diag.notes); let mut err = match custom_message { Some(message) => psess.dcx().struct_span_err(span, message), @@ -144,17 +156,6 @@ pub(super) fn failed_to_match_macro( (sp, guar) } -fn on_missing_args_diagnostic(directive: &Directive) -> CustomDiagnostic { - let args = FormatArgs { - this: String::new(), - this_sugared: String::new(), - item_context: "macro invocation", - generic_args: Vec::new(), - }; - - directive.eval(None, &args) -} - /// The tracker used for the slow error path that collects useful info for diagnostics. struct CollectTrackerAndEmitter<'dcx, 'matcher> { dcx: DiagCtxtHandle<'dcx>, diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs index 8ff1bd9451aae..68b20e2dc55b1 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -1359,7 +1359,9 @@ pub(crate) struct OnMoveMalformedFormatLiterals { #[derive(Diagnostic)] #[diag("unknown parameter `{$name}`")] -#[help("format parameters are not supported by `#[diagnostic::on_missing_args]`")] +#[help( + r#"only {"`{This}`"} is supported as a format parameter in `#[diagnostic::on_missing_args]`"# +)] pub(crate) struct OnMissingArgsMalformedFormatLiterals { pub name: Symbol, } diff --git a/tests/ui/diagnostic_namespace/on_missing_args/message_and_label.rs b/tests/ui/diagnostic_namespace/on_missing_args/message_and_label.rs index 253aabf837559..6326536206026 100644 --- a/tests/ui/diagnostic_namespace/on_missing_args/message_and_label.rs +++ b/tests/ui/diagnostic_namespace/on_missing_args/message_and_label.rs @@ -1,7 +1,7 @@ #![feature(diagnostic_on_missing_args)] #[diagnostic::on_missing_args( - message = "pair! is missing its second argument", + message = "{This}! is missing its second argument", label = "add the missing value here", note = "this macro expects a type and a value, like `pair!(u8, 0)`", )] diff --git a/tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_invalid_formats.stderr b/tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_invalid_formats.stderr index de2296f15813e..27d4d15755dda 100644 --- a/tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_invalid_formats.stderr +++ b/tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_invalid_formats.stderr @@ -4,7 +4,7 @@ warning: unknown parameter `T` LL | message = "pair! is missing {T}", | ^ | - = help: format parameters are not supported by `#[diagnostic::on_missing_args]` + = help: only `{This}` is supported as a format parameter in `#[diagnostic::on_missing_args]` = note: `#[warn(malformed_diagnostic_format_literals)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default warning: 1 warning emitted From 889390c5706c7173927308822656613913ca1aad Mon Sep 17 00:00:00 2001 From: yukang Date: Tue, 7 Apr 2026 09:28:52 +0800 Subject: [PATCH 5/8] tweak on_missing_args help wording --- compiler/rustc_passes/src/errors.rs | 2 +- .../on_missing_args/report_warning_on_invalid_formats.stderr | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs index 68b20e2dc55b1..4880e5cd2ce30 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -1360,7 +1360,7 @@ pub(crate) struct OnMoveMalformedFormatLiterals { #[derive(Diagnostic)] #[diag("unknown parameter `{$name}`")] #[help( - r#"only {"`{This}`"} is supported as a format parameter in `#[diagnostic::on_missing_args]`"# + r#"the only supported format parameter for `#[diagnostic::on_missing_args]` is {"`{This}`"}"# )] pub(crate) struct OnMissingArgsMalformedFormatLiterals { pub name: Symbol, diff --git a/tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_invalid_formats.stderr b/tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_invalid_formats.stderr index 27d4d15755dda..f23a8ca19fa77 100644 --- a/tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_invalid_formats.stderr +++ b/tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_invalid_formats.stderr @@ -4,7 +4,7 @@ warning: unknown parameter `T` LL | message = "pair! is missing {T}", | ^ | - = help: only `{This}` is supported as a format parameter in `#[diagnostic::on_missing_args]` + = help: the only supported format parameter for `#[diagnostic::on_missing_args]` is `{This}` = note: `#[warn(malformed_diagnostic_format_literals)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default warning: 1 warning emitted From 9dc729e05e2ad60fd328c4a8e6f01e6f5951f897 Mon Sep 17 00:00:00 2001 From: yukang Date: Tue, 7 Apr 2026 09:29:46 +0800 Subject: [PATCH 6/8] clarify on_missing_args help text --- compiler/rustc_passes/src/errors.rs | 4 +--- .../on_missing_args/report_warning_on_invalid_formats.rs | 2 +- .../report_warning_on_invalid_formats.stderr | 8 ++++---- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs index 4880e5cd2ce30..cc9b69fd7d36d 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -1359,9 +1359,7 @@ pub(crate) struct OnMoveMalformedFormatLiterals { #[derive(Diagnostic)] #[diag("unknown parameter `{$name}`")] -#[help( - r#"the only supported format parameter for `#[diagnostic::on_missing_args]` is {"`{This}`"}"# -)] +#[help(r#"use {"`{This}`"} to refer to the macro name"#)] pub(crate) struct OnMissingArgsMalformedFormatLiterals { pub name: Symbol, } diff --git a/tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_invalid_formats.rs b/tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_invalid_formats.rs index e383e2b4c03fb..e838528c4cfea 100644 --- a/tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_invalid_formats.rs +++ b/tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_invalid_formats.rs @@ -2,7 +2,7 @@ #![feature(diagnostic_on_missing_args)] #[diagnostic::on_missing_args( - message = "pair! is missing {T}", + message = "{T}! is missing arguments", //~^ WARN unknown parameter `T` )] macro_rules! pair { diff --git a/tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_invalid_formats.stderr b/tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_invalid_formats.stderr index f23a8ca19fa77..2f631337c4416 100644 --- a/tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_invalid_formats.stderr +++ b/tests/ui/diagnostic_namespace/on_missing_args/report_warning_on_invalid_formats.stderr @@ -1,10 +1,10 @@ warning: unknown parameter `T` - --> $DIR/report_warning_on_invalid_formats.rs:5:34 + --> $DIR/report_warning_on_invalid_formats.rs:5:17 | -LL | message = "pair! is missing {T}", - | ^ +LL | message = "{T}! is missing arguments", + | ^ | - = help: the only supported format parameter for `#[diagnostic::on_missing_args]` is `{This}` + = help: use `{This}` to refer to the macro name = note: `#[warn(malformed_diagnostic_format_literals)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default warning: 1 warning emitted From 80b274398624191c01acd8e3fd993df12095c004 Mon Sep 17 00:00:00 2001 From: yukang Date: Tue, 7 Apr 2026 01:50:23 +0000 Subject: [PATCH 7/8] fix cfg ci fail --- tests/ui/cfg/suggest-alternative-name-on-target.stderr | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/ui/cfg/suggest-alternative-name-on-target.stderr b/tests/ui/cfg/suggest-alternative-name-on-target.stderr index 16e76d51fc0b2..4bc6c6fabe3dc 100644 --- a/tests/ui/cfg/suggest-alternative-name-on-target.stderr +++ b/tests/ui/cfg/suggest-alternative-name-on-target.stderr @@ -74,3 +74,4 @@ LL + #[cfg(target_os = "windows")] | error: aborting due to 5 previous errors + From 2e0c59544e1bbbf52f2f50950b8b366d7afcfc32 Mon Sep 17 00:00:00 2001 From: yukang Date: Tue, 7 Apr 2026 10:22:39 +0800 Subject: [PATCH 8/8] extend on_missing_args to no-match --- compiler/rustc_expand/src/mbe/diagnostics.rs | 4 +--- .../diagnostic-on-missing-args.md | 8 +++---- .../on_missing_args/notes_on_extra_args.rs | 21 +++++++++++++++++++ .../notes_on_extra_args.stderr | 19 +++++++++++++++++ .../other_match_macro_error.rs | 14 +++++++++++++ .../other_match_macro_error.stderr | 18 ++++++++++++++++ .../invalid.next.stderr | 1 + .../invalid.old.stderr | 1 + tests/ui/offset-of/offset-of-arg-count.stderr | 1 + tests/ui/offset-of/offset-of-tuple.stderr | 1 + 10 files changed, 81 insertions(+), 7 deletions(-) create mode 100644 tests/ui/diagnostic_namespace/on_missing_args/notes_on_extra_args.rs create mode 100644 tests/ui/diagnostic_namespace/on_missing_args/notes_on_extra_args.stderr create mode 100644 tests/ui/diagnostic_namespace/on_missing_args/other_match_macro_error.rs create mode 100644 tests/ui/diagnostic_namespace/on_missing_args/other_match_macro_error.stderr diff --git a/compiler/rustc_expand/src/mbe/diagnostics.rs b/compiler/rustc_expand/src/mbe/diagnostics.rs index 1eb2cecafc873..4f6dab43d15e8 100644 --- a/compiler/rustc_expand/src/mbe/diagnostics.rs +++ b/compiler/rustc_expand/src/mbe/diagnostics.rs @@ -76,7 +76,7 @@ pub(super) fn failed_to_match_macro( let span = token.span.substitute_dummy(sp); let CustomDiagnostic { message: custom_message, label: custom_label, notes: custom_notes, .. - } = if matches!(token.kind, token::Eof) { + } = { let macro_name = name.to_string(); on_missing_args .map(|directive| { @@ -91,8 +91,6 @@ pub(super) fn failed_to_match_macro( ) }) .unwrap_or_default() - } else { - CustomDiagnostic::default() }; let mut err = match custom_message { diff --git a/src/doc/unstable-book/src/language-features/diagnostic-on-missing-args.md b/src/doc/unstable-book/src/language-features/diagnostic-on-missing-args.md index 3103b374b11ee..0cb29bd74aa8c 100644 --- a/src/doc/unstable-book/src/language-features/diagnostic-on-missing-args.md +++ b/src/doc/unstable-book/src/language-features/diagnostic-on-missing-args.md @@ -8,12 +8,12 @@ The tracking issue for this feature is: [#152494] The `diagnostic_on_missing_args` feature adds the `#[diagnostic::on_missing_args(...)]` attribute for declarative macros. -It lets a macro definition customize the diagnostic that is emitted when an invocation ends before -all required arguments were provided. +It lets a macro definition customize diagnostics for matcher failures after all arms have been +tried, such as incomplete invocations or trailing extra arguments. This attribute currently applies to declarative macros such as `macro_rules!` and `pub macro`. -It only affects diagnostics for incomplete invocations; other matcher failures continue to use the -usual macro diagnostics. +It is currently used for errors emitted by declarative macro matching itself; fragment parser +errors still use their existing diagnostics. ```rust,compile_fail #![feature(diagnostic_on_missing_args)] diff --git a/tests/ui/diagnostic_namespace/on_missing_args/notes_on_extra_args.rs b/tests/ui/diagnostic_namespace/on_missing_args/notes_on_extra_args.rs new file mode 100644 index 0000000000000..c43cde8e9299d --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_missing_args/notes_on_extra_args.rs @@ -0,0 +1,21 @@ +#![feature(diagnostic_on_missing_args)] + +#[diagnostic::on_missing_args( + message = "{This}! expects exactly two arguments", + label = "unexpected extra input starts here", + note = "this macro expects a type and a value, like `pair!(u8, 0)`", + note = "make sure to pass both arguments", +)] +macro_rules! pair { + //~^ NOTE when calling this macro + ($ty:ty, $value:expr) => {}; + //~^ NOTE while trying to match meta-variable `$value:expr` +} + +fn main() { + pair!(u8, 0, 42); + //~^ ERROR pair! expects exactly two arguments + //~| NOTE unexpected extra input starts here + //~| NOTE this macro expects a type and a value, like `pair!(u8, 0)` + //~| NOTE make sure to pass both arguments +} diff --git a/tests/ui/diagnostic_namespace/on_missing_args/notes_on_extra_args.stderr b/tests/ui/diagnostic_namespace/on_missing_args/notes_on_extra_args.stderr new file mode 100644 index 0000000000000..e10b8ef46bac0 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_missing_args/notes_on_extra_args.stderr @@ -0,0 +1,19 @@ +error: pair! expects exactly two arguments + --> $DIR/notes_on_extra_args.rs:16:16 + | +LL | macro_rules! pair { + | ----------------- when calling this macro +... +LL | pair!(u8, 0, 42); + | ^ unexpected extra input starts here + | +note: while trying to match meta-variable `$value:expr` + --> $DIR/notes_on_extra_args.rs:11:14 + | +LL | ($ty:ty, $value:expr) => {}; + | ^^^^^^^^^^^ + = note: this macro expects a type and a value, like `pair!(u8, 0)` + = note: make sure to pass both arguments + +error: aborting due to 1 previous error + diff --git a/tests/ui/diagnostic_namespace/on_missing_args/other_match_macro_error.rs b/tests/ui/diagnostic_namespace/on_missing_args/other_match_macro_error.rs new file mode 100644 index 0000000000000..d08ef3d62e29d --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_missing_args/other_match_macro_error.rs @@ -0,0 +1,14 @@ +#![feature(diagnostic_on_missing_args)] + +#[diagnostic::on_missing_args( + message = "invalid route method", + note = "this macro expects a action, like `{This}!(get \"/hello\")`" +)] +macro_rules! route { + (get $path:literal) => {}; +} + +fn main() { + route!(post "/"); + //~^ ERROR invalid route method +} diff --git a/tests/ui/diagnostic_namespace/on_missing_args/other_match_macro_error.stderr b/tests/ui/diagnostic_namespace/on_missing_args/other_match_macro_error.stderr new file mode 100644 index 0000000000000..5f6c59e79287d --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_missing_args/other_match_macro_error.stderr @@ -0,0 +1,18 @@ +error: invalid route method + --> $DIR/other_match_macro_error.rs:12:12 + | +LL | macro_rules! route { + | ------------------ when calling this macro +... +LL | route!(post "/"); + | ^^^^ no rules expected this token in macro call + | +note: while trying to match `get` + --> $DIR/other_match_macro_error.rs:8:6 + | +LL | (get $path:literal) => {}; + | ^^^ + = note: this macro expects a action, like `route!(get "/hello")` + +error: aborting due to 1 previous error + diff --git a/tests/ui/field_representing_types/invalid.next.stderr b/tests/ui/field_representing_types/invalid.next.stderr index a519cc1dda635..611077a570229 100644 --- a/tests/ui/field_representing_types/invalid.next.stderr +++ b/tests/ui/field_representing_types/invalid.next.stderr @@ -25,6 +25,7 @@ LL | let _: field_of!(Struct, field, extra); | ^^^^^ no rules expected this token in macro call | = note: while trying to match sequence end + = note: this macro expects a container type and a field path, like `field_of!(Type, field)` or `field_of!(Enum, Variant.field)` error: offset_of expects dot-separated field and variant names --> $DIR/invalid.rs:27:28 diff --git a/tests/ui/field_representing_types/invalid.old.stderr b/tests/ui/field_representing_types/invalid.old.stderr index a519cc1dda635..611077a570229 100644 --- a/tests/ui/field_representing_types/invalid.old.stderr +++ b/tests/ui/field_representing_types/invalid.old.stderr @@ -25,6 +25,7 @@ LL | let _: field_of!(Struct, field, extra); | ^^^^^ no rules expected this token in macro call | = note: while trying to match sequence end + = note: this macro expects a container type and a field path, like `field_of!(Type, field)` or `field_of!(Enum, Variant.field)` error: offset_of expects dot-separated field and variant names --> $DIR/invalid.rs:27:28 diff --git a/tests/ui/offset-of/offset-of-arg-count.stderr b/tests/ui/offset-of/offset-of-arg-count.stderr index 747b64c183196..9e7047d0971dc 100644 --- a/tests/ui/offset-of/offset-of-arg-count.stderr +++ b/tests/ui/offset-of/offset-of-arg-count.stderr @@ -25,6 +25,7 @@ LL | offset_of!(Container, field, too many arguments); | ^^^ no rules expected this token in macro call | = note: while trying to match sequence end + = note: this macro expects a container type and a dot-separated field or variant path, like `offset_of!(Type, field)` error: unexpected token: `)` --> $DIR/offset-of-arg-count.rs:9:21 diff --git a/tests/ui/offset-of/offset-of-tuple.stderr b/tests/ui/offset-of/offset-of-tuple.stderr index 33dea9918cac2..7687e617ecbf9 100644 --- a/tests/ui/offset-of/offset-of-tuple.stderr +++ b/tests/ui/offset-of/offset-of-tuple.stderr @@ -72,6 +72,7 @@ LL | offset_of!((u8, u8), +1); | note: while trying to match meta-variable `$fields:expr` --> $SRC_DIR/core/src/mem/mod.rs:LL:COL + = note: this macro expects a container type and a dot-separated field or variant path, like `offset_of!(Type, field)` error: offset_of expects dot-separated field and variant names --> $DIR/offset-of-tuple.rs:7:26