From 8d75f0cbfce45c7d258297f223e18c8ea9679131 Mon Sep 17 00:00:00 2001 From: yukang Date: Sun, 19 Apr 2026 20:57:37 +0800 Subject: [PATCH] add on_unmatch_args --- .../src/attributes/diagnostic/mod.rs | 10 +++- .../attributes/diagnostic/on_unmatch_args.rs | 58 +++++++++++++++++++ compiler/rustc_attr_parsing/src/context.rs | 2 + compiler/rustc_attr_parsing/src/errors.rs | 4 ++ compiler/rustc_expand/src/mbe/diagnostics.rs | 30 +++++++++- compiler/rustc_expand/src/mbe/macro_rules.rs | 37 ++++++++++-- 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 | 1 - compiler/rustc_passes/src/check_attr.rs | 19 +++++- compiler/rustc_passes/src/errors.rs | 7 +++ compiler/rustc_resolve/src/macros.rs | 1 + 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-unmatch-args.md | 53 +++++++++++++++++ .../on_unmatch_args/auxiliary/other.rs | 12 ++++ .../error_is_shown_in_downstream_crates.rs | 12 ++++ ...error_is_shown_in_downstream_crates.stderr | 16 +++++ .../on_unmatch_args/message_and_label.rs | 21 +++++++ .../on_unmatch_args/message_and_label.stderr | 19 ++++++ .../on_unmatch_args/notes_on_extra_args.rs | 21 +++++++ .../notes_on_extra_args.stderr | 19 ++++++ .../on_unmatch_args/on_unmatch_args.rs | 19 ++++++ .../on_unmatch_args/on_unmatch_args.stderr | 19 ++++++ .../other_match_macro_error.rs | 14 +++++ .../other_match_macro_error.stderr | 18 ++++++ .../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-unmatch-args.rs | 15 +++++ ...ure-gate-diagnostic-on-unmatch-args.stderr | 17 ++++++ .../invalid.next.stderr | 3 + .../invalid.old.stderr | 3 + tests/ui/offset-of/offset-of-arg-count.stderr | 3 + tests/ui/offset-of/offset-of-tuple.stderr | 1 + 45 files changed, 576 insertions(+), 11 deletions(-) create mode 100644 compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unmatch_args.rs create mode 100644 src/doc/unstable-book/src/language-features/diagnostic-on-unmatch-args.md create mode 100644 tests/ui/diagnostic_namespace/on_unmatch_args/auxiliary/other.rs create mode 100644 tests/ui/diagnostic_namespace/on_unmatch_args/error_is_shown_in_downstream_crates.rs create mode 100644 tests/ui/diagnostic_namespace/on_unmatch_args/error_is_shown_in_downstream_crates.stderr create mode 100644 tests/ui/diagnostic_namespace/on_unmatch_args/message_and_label.rs create mode 100644 tests/ui/diagnostic_namespace/on_unmatch_args/message_and_label.stderr create mode 100644 tests/ui/diagnostic_namespace/on_unmatch_args/notes_on_extra_args.rs create mode 100644 tests/ui/diagnostic_namespace/on_unmatch_args/notes_on_extra_args.stderr create mode 100644 tests/ui/diagnostic_namespace/on_unmatch_args/on_unmatch_args.rs create mode 100644 tests/ui/diagnostic_namespace/on_unmatch_args/on_unmatch_args.stderr create mode 100644 tests/ui/diagnostic_namespace/on_unmatch_args/other_match_macro_error.rs create mode 100644 tests/ui/diagnostic_namespace/on_unmatch_args/other_match_macro_error.stderr create mode 100644 tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_invalid_formats.rs create mode 100644 tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_invalid_formats.stderr create mode 100644 tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_invalid_meta_item_syntax.rs create mode 100644 tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_invalid_meta_item_syntax.stderr create mode 100644 tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_missing_options.rs create mode 100644 tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_missing_options.stderr create mode 100644 tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_non_macro.rs create mode 100644 tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_non_macro.stderr create mode 100644 tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_unknown_options.rs create mode 100644 tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_unknown_options.stderr create mode 100644 tests/ui/feature-gates/feature-gate-diagnostic-on-unmatch-args.rs create mode 100644 tests/ui/feature-gates/feature-gate-diagnostic-on-unmatch-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 ea5e81c3db81..32d73ff32361 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs @@ -25,6 +25,7 @@ pub(crate) mod on_move; pub(crate) mod on_unimplemented; pub(crate) mod on_unknown; +pub(crate) mod on_unmatch_args; #[derive(Copy, Clone)] pub(crate) enum Mode { @@ -38,6 +39,8 @@ pub(crate) enum Mode { DiagnosticOnMove, /// `#[diagnostic::on_unknown]` DiagnosticOnUnknown, + /// `#[diagnostic::on_unmatch_args]` + DiagnosticOnUnmatchArgs, } impl Mode { @@ -48,6 +51,7 @@ fn as_str(&self) -> &'static str { Self::DiagnosticOnConst => "diagnostic::on_const", Self::DiagnosticOnMove => "diagnostic::on_move", Self::DiagnosticOnUnknown => "diagnostic::on_unknown", + Self::DiagnosticOnUnmatchArgs => "diagnostic::on_unmatch_args", } } @@ -62,6 +66,7 @@ fn expected_options(&self) -> &'static str { Self::DiagnosticOnConst => DEFAULT, Self::DiagnosticOnMove => DEFAULT, Self::DiagnosticOnUnknown => DEFAULT, + Self::DiagnosticOnUnmatchArgs => DEFAULT, } } @@ -75,6 +80,7 @@ fn allowed_options(&self) -> &'static str { Self::DiagnosticOnConst => DEFAULT, Self::DiagnosticOnMove => DEFAULT, Self::DiagnosticOnUnknown => DEFAULT, + Self::DiagnosticOnUnmatchArgs => DEFAULT, } } } @@ -398,7 +404,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::DiagnosticOnUnmatchArgs, sym::This) => { + FormatArg::This + } (Mode::RustcOnUnimplemented { .. }, sym::Trait) => FormatArg::Trait, // Any attribute can use these (_, kw::SelfUpper) => FormatArg::SelfUpper, diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unmatch_args.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unmatch_args.rs new file mode 100644 index 000000000000..f541f631bf12 --- /dev/null +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unmatch_args.rs @@ -0,0 +1,58 @@ +use rustc_errors::Diagnostic; +use rustc_hir::attrs::diagnostic::Directive; +use rustc_session::lint::builtin::MISPLACED_DIAGNOSTIC_ATTRIBUTES; + +use crate::attributes::diagnostic::*; +use crate::attributes::prelude::*; +use crate::errors::DiagnosticOnUnmatchArgsOnlyForMacros; + +#[derive(Default)] +pub(crate) struct OnUnmatchArgsParser { + span: Option, + directive: Option<(Span, Directive)>, +} + +impl AttributeParser for OnUnmatchArgsParser { + const ATTRIBUTES: AcceptMapping = &[( + &[sym::diagnostic, sym::on_unmatch_args], + template!(List: &[r#"/*opt*/ message = "...", /*opt*/ label = "...", /*opt*/ note = "...""#]), + |this, cx, args| { + if !cx.features().diagnostic_on_unmatch_args() { + return; + } + + let span = cx.attr_span; + this.span = Some(span); + + if !matches!(cx.target, Target::MacroDef) { + cx.emit_dyn_lint( + MISPLACED_DIAGNOSTIC_ATTRIBUTES, + move |dcx, level| DiagnosticOnUnmatchArgsOnlyForMacros.into_diag(dcx, level), + span, + ); + return; + } + + let mode = Mode::DiagnosticOnUnmatchArgs; + let Some(items) = parse_list(cx, args, mode) else { return }; + + let Some(directive) = parse_directive_items(cx, mode, 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::OnUnmatchArgs { + 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 becdaee0f3d3..5ced5d5f975a 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -33,6 +33,7 @@ use crate::attributes::diagnostic::on_move::*; use crate::attributes::diagnostic::on_unimplemented::*; use crate::attributes::diagnostic::on_unknown::*; +use crate::attributes::diagnostic::on_unmatch_args::*; use crate::attributes::doc::*; use crate::attributes::dummy::*; use crate::attributes::inline::*; @@ -159,6 +160,7 @@ mod late { OnMoveParser, OnUnimplementedParser, OnUnknownParser, + OnUnmatchArgsParser, RustcAlignParser, RustcAlignStaticParser, RustcCguTestAttributeParser, diff --git a/compiler/rustc_attr_parsing/src/errors.rs b/compiler/rustc_attr_parsing/src/errors.rs index 96a4c473c3ae..d3e6845799e7 100644 --- a/compiler/rustc_attr_parsing/src/errors.rs +++ b/compiler/rustc_attr_parsing/src/errors.rs @@ -315,6 +315,10 @@ pub(crate) struct DiagnosticOnUnknownOnlyForImports { pub target_span: Span, } +#[derive(Diagnostic)] +#[diag("`#[diagnostic::on_unmatch_args]` can only be applied to macro definitions")] +pub(crate) struct DiagnosticOnUnmatchArgsOnlyForMacros; + #[derive(Diagnostic)] #[diag("`#[diagnostic::do_not_recommend]` can only be placed on trait implementations")] pub(crate) struct IncorrectDoNotRecommendLocation { diff --git a/compiler/rustc_expand/src/mbe/diagnostics.rs b/compiler/rustc_expand/src/mbe/diagnostics.rs index b8040c3d9c1a..4e7e51c0a43c 100644 --- a/compiler/rustc_expand/src/mbe/diagnostics.rs +++ b/compiler/rustc_expand/src/mbe/diagnostics.rs @@ -3,6 +3,7 @@ use rustc_ast::token::{self, Token}; use rustc_ast::tokenstream::TokenStream; use rustc_errors::{Applicability, Diag, DiagCtxtHandle, DiagMessage}; +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; @@ -32,6 +33,7 @@ pub(super) fn failed_to_match_macro( args: FailedMacro<'_>, body: &TokenStream, rules: &[MacroRule], + on_unmatch_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,30 @@ 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, .. + } = { + let macro_name = name.to_string(); + on_unmatch_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() + }; - 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 +109,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(_)) diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs index fd5dac3cd926..eed13a13fa91 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_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_unmatch_args: Option, transparency: Transparency, kinds: MacroKinds, rules: Vec, @@ -194,7 +196,8 @@ pub fn expand_derive( ) -> 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 @@ pub fn expand_derive( 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 @@ pub fn expand_derive( FailedMacro::Derive, body, rules, + self.on_unmatch_args.as_ref(), ); cx.macro_error_and_trace_macros_diag(); Err(guar) @@ -260,6 +264,7 @@ fn expand<'cx, 'a: 'cx>( self.transparency, input, &self.rules, + self.on_unmatch_args.as_ref(), )) } } @@ -294,6 +299,7 @@ fn expand_with_safety( args, body, &self.rules, + self.on_unmatch_args.as_ref(), ) } } @@ -355,7 +361,7 @@ fn description() -> &'static str { } /// 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_unmatch_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_unmatch_args: Option<&Directive>, ) -> Box { let psess = &cx.sess.psess; @@ -423,6 +430,7 @@ fn expand_macro<'cx, 'a: 'cx>( FailedMacro::Func, &arg, rules, + on_unmatch_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_unmatch_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_unmatch_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_unmatch_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_unmatch_args = find_attr!( + attrs, + OnUnmatchArgs { directive, .. } => directive.clone() + ) + .flatten() + .map(|directive| *directive); + + let exp = MacroRulesMacroExpander { + name: ident, + kinds, + span, + node_id, + on_unmatch_args, + transparency, + rules, + }; (mk_syn_ext(SyntaxExtensionKind::MacroRules(Arc::new(exp))), nrules) } diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index 82402b3c7e31..c175c3adc2f0 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -480,6 +480,8 @@ pub fn internal(&self, feature: Symbol) -> bool { (unstable, diagnostic_on_move, "1.96.0", Some(154181)), /// 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. + (unstable, diagnostic_on_unmatch_args, "CURRENT_RUSTC_VERSION", Some(155642)), /// Allows `#[doc(cfg(...))]`. (unstable, doc_cfg, "1.21.0", Some(43781)), /// Allows `#[doc(masked)]`. diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index 05f398058eca..78ce2f019a39 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -1208,6 +1208,13 @@ pub enum AttributeKind { directive: Option>, }, + /// Represents `#[diagnostic::on_unmatch_args]`. + OnUnmatchArgs { + span: Span, + /// None if the directive was malformed in some way. + directive: Option>, + }, + /// Represents `#[optimize(size|speed)]` Optimize(OptimizeAttr, Span), diff --git a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs index ad4d0728888b..41e466a9d16b 100644 --- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs +++ b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs @@ -80,6 +80,7 @@ pub fn encode_cross_crate(&self) -> EncodeCrossCrate { OnMove { .. } => Yes, OnUnimplemented { .. } => Yes, OnUnknown { .. } => Yes, + OnUnmatchArgs { .. } => Yes, Optimize(..) => No, PanicRuntime => No, PatchableFunctionEntry { .. } => Yes, diff --git a/compiler/rustc_lint/src/early/diagnostics.rs b/compiler/rustc_lint/src/early/diagnostics.rs index 60bfdd689609..92c474848327 100644 --- a/compiler/rustc_lint/src/early/diagnostics.rs +++ b/compiler/rustc_lint/src/early/diagnostics.rs @@ -52,7 +52,6 @@ fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, ()> { lints::MalFormedDiagnosticAttributeLint { attribute, options, span } .into_diag(dcx, level) } - AttributeLintKind::MalformedDiagnosticFormat { warning } => match warning { FormatWarning::PositionalArgument { .. } => { lints::DisallowedPositionalArgument.into_diag(dcx, level) diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 06dd8cbc089b..d11825016baf 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -200,7 +200,10 @@ fn check_attributes( self.check_rustc_must_implement_one_of(*attr_span, fn_names, hir_id,target) }, Attribute::Parsed(AttributeKind::OnUnimplemented{directive,..}) => {self.check_diagnostic_on_unimplemented(hir_id, 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::OnUnmatchArgs { directive, .. }) => { + self.check_diagnostic_on_unmatch_args(hir_id, directive.as_deref()) + }, Attribute::Parsed(AttributeKind::OnMove { directive , .. }) => { self.check_diagnostic_on_move(hir_id, directive.as_deref()) }, @@ -559,6 +562,20 @@ fn check_diagnostic_on_const( // ...whose generics would that be, anyway? The traits' or the impls'? } + /// Checks use of generic formatting parameters in `#[diagnostic::on_unmatch_args]`. + fn check_diagnostic_on_unmatch_args(&self, hir_id: HirId, directive: Option<&Directive>) { + if 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::OnUnmatchArgsMalformedFormatLiterals { name: argument_name }, + ) + }); + } + } + /// Checks use of generic formatting parameters in `#[diagnostic::on_move]` fn check_diagnostic_on_move(&self, hir_id: HirId, directive: Option<&Directive>) { if let Some(directive) = directive { diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs index a916b4670fde..5300267573e6 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -1303,3 +1303,10 @@ pub(crate) struct UnknownFormatParameterForOnUnimplementedAttr { pub(crate) struct OnMoveMalformedFormatLiterals { pub name: Symbol, } + +#[derive(Diagnostic)] +#[diag("unknown parameter `{$name}`")] +#[help(r#"use {"`{This}`"} to refer to the macro name"#)] +pub(crate) struct OnUnmatchArgsMalformedFormatLiterals { + pub name: Symbol, +} diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs index c53763f92046..dc20d4fcf36b 100644 --- a/compiler/rustc_resolve/src/macros.rs +++ b/compiler/rustc_resolve/src/macros.rs @@ -710,6 +710,7 @@ fn smart_resolve_macro_path( (sym::on_move, Some(sym::diagnostic_on_move)), (sym::on_const, Some(sym::diagnostic_on_const)), (sym::on_unknown, Some(sym::diagnostic_on_unknown)), + (sym::on_unmatch_args, Some(sym::diagnostic_on_unmatch_args)), ]; if res == Res::NonMacroAttr(NonMacroAttrKind::Tool) diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 981bfed363dc..e7574eaed5b2 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -803,6 +803,7 @@ diagnostic_on_const, diagnostic_on_move, diagnostic_on_unknown, + diagnostic_on_unmatch_args, dialect, direct, discriminant_kind, @@ -1427,6 +1428,7 @@ on_move, on_unimplemented, on_unknown, + on_unmatch_args, opaque, opaque_module_name_placeholder: "", ops, diff --git a/library/core/src/field.rs b/library/core/src/field.rs index 0e537e2f92fc..120d7a9f4760 100644 --- a/library/core/src/field.rs +++ b/library/core/src/field.rs @@ -45,6 +45,9 @@ fn clone(&self) -> Self { /// 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_unmatch_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 bdc1c48f70df..2b4ac66212e5 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_unmatch_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 91934cfceb4e..2fbb5842ef77 100644 --- a/library/core/src/mem/mod.rs +++ b/library/core/src/mem/mod.rs @@ -1567,6 +1567,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_unmatch_args( + note = "this macro expects a container type and a (nested) field 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-unmatch-args.md b/src/doc/unstable-book/src/language-features/diagnostic-on-unmatch-args.md new file mode 100644 index 000000000000..514ad1969bb0 --- /dev/null +++ b/src/doc/unstable-book/src/language-features/diagnostic-on-unmatch-args.md @@ -0,0 +1,53 @@ +# `diagnostic_on_unmatch_args` + +The tracking issue for this feature is: [#155642] + +[#155642]: https://github.com/rust-lang/rust/issues/155642 + +------------------------ + +The `diagnostic_on_unmatch_args` feature adds the +`#[diagnostic::on_unmatch_args(...)]` attribute for declarative macros. +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 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_unmatch_args)] + +#[diagnostic::on_unmatch_args( + message = "invalid arguments to {This} macro invocation", + label = "expected a type and value here", + note = "this macro expects a type and a value, like `pair!(u8, 0)`", + note = "see ", +)] +macro_rules! pair { + ($ty:ty, $value:expr) => {}; +} + +pair!(u8); +``` + +This emits output like: + +```text +error: invalid arguments to pair macro invocation + --> example.rs:13:9 + | +9 | macro_rules! pair { + | ----------------- when calling this macro +... +13 | pair!(u8); + | ^ expected a type and value here + | +note: while trying to match `,` + --> example.rs:10:12 + | +10 | ($ty:ty, $value:expr) => {}; + | ^ + = note: this macro expects a type and a value, like `pair!(u8, 0)` + = note: see +``` diff --git a/tests/ui/diagnostic_namespace/on_unmatch_args/auxiliary/other.rs b/tests/ui/diagnostic_namespace/on_unmatch_args/auxiliary/other.rs new file mode 100644 index 000000000000..2dd032ecf41c --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unmatch_args/auxiliary/other.rs @@ -0,0 +1,12 @@ +#![feature(diagnostic_on_unmatch_args)] + +#[macro_export] +#[diagnostic::on_unmatch_args( + message = "invalid arguments to {This} macro invocation", + label = "expected a type and value here", + note = "this macro expects a type and a value, like `pair!(u8, 0)`", + note = "see the macro documentation for accepted forms", +)] +macro_rules! pair { + ($ty:ty, $value:expr) => {}; +} diff --git a/tests/ui/diagnostic_namespace/on_unmatch_args/error_is_shown_in_downstream_crates.rs b/tests/ui/diagnostic_namespace/on_unmatch_args/error_is_shown_in_downstream_crates.rs new file mode 100644 index 000000000000..0d5f17f5c5c2 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unmatch_args/error_is_shown_in_downstream_crates.rs @@ -0,0 +1,12 @@ +//@ aux-build:other.rs + +extern crate other; + +fn main() { + other::pair!(u8); + //~^ ERROR invalid arguments to pair macro invocation + //~| NOTE expected a type and value here + //~| NOTE while trying to match `,` + //~| NOTE this macro expects a type and a value, like `pair!(u8, 0)` + //~| NOTE see the macro documentation for accepted forms +} diff --git a/tests/ui/diagnostic_namespace/on_unmatch_args/error_is_shown_in_downstream_crates.stderr b/tests/ui/diagnostic_namespace/on_unmatch_args/error_is_shown_in_downstream_crates.stderr new file mode 100644 index 000000000000..c09595835046 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unmatch_args/error_is_shown_in_downstream_crates.stderr @@ -0,0 +1,16 @@ +error: invalid arguments to pair macro invocation + --> $DIR/error_is_shown_in_downstream_crates.rs:6:20 + | +LL | other::pair!(u8); + | ^ expected a type and value here + | +note: while trying to match `,` + --> $DIR/auxiliary/other.rs:11:12 + | +LL | ($ty:ty, $value:expr) => {}; + | ^ + = note: this macro expects a type and a value, like `pair!(u8, 0)` + = note: see the macro documentation for accepted forms + +error: aborting due to 1 previous error + diff --git a/tests/ui/diagnostic_namespace/on_unmatch_args/message_and_label.rs b/tests/ui/diagnostic_namespace/on_unmatch_args/message_and_label.rs new file mode 100644 index 000000000000..26a8b07bd6e4 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unmatch_args/message_and_label.rs @@ -0,0 +1,21 @@ +#![feature(diagnostic_on_unmatch_args)] + +#[diagnostic::on_unmatch_args( + message = "invalid arguments to {This} macro invocation", + label = "expected a type and value here", + note = "this macro expects a type and a value, like `pair!(u8, 0)`", + note = "see the macro documentation for accepted forms", +)] +macro_rules! pair { + //~^ NOTE when calling this macro + ($ty:ty, $value:expr) => {}; + //~^ NOTE while trying to match `,` +} + +fn main() { + pair!(u8); + //~^ ERROR invalid arguments to pair macro invocation + //~| NOTE expected a type and value here + //~| NOTE this macro expects a type and a value, like `pair!(u8, 0)` + //~| NOTE see the macro documentation for accepted forms +} diff --git a/tests/ui/diagnostic_namespace/on_unmatch_args/message_and_label.stderr b/tests/ui/diagnostic_namespace/on_unmatch_args/message_and_label.stderr new file mode 100644 index 000000000000..704479a1b1fa --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unmatch_args/message_and_label.stderr @@ -0,0 +1,19 @@ +error: invalid arguments to pair macro invocation + --> $DIR/message_and_label.rs:16:13 + | +LL | macro_rules! pair { + | ----------------- when calling this macro +... +LL | pair!(u8); + | ^ expected a type and value here + | +note: while trying to match `,` + --> $DIR/message_and_label.rs:11:12 + | +LL | ($ty:ty, $value:expr) => {}; + | ^ + = note: this macro expects a type and a value, like `pair!(u8, 0)` + = note: see the macro documentation for accepted forms + +error: aborting due to 1 previous error + diff --git a/tests/ui/diagnostic_namespace/on_unmatch_args/notes_on_extra_args.rs b/tests/ui/diagnostic_namespace/on_unmatch_args/notes_on_extra_args.rs new file mode 100644 index 000000000000..083445c7f969 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unmatch_args/notes_on_extra_args.rs @@ -0,0 +1,21 @@ +#![feature(diagnostic_on_unmatch_args)] + +#[diagnostic::on_unmatch_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_unmatch_args/notes_on_extra_args.stderr b/tests/ui/diagnostic_namespace/on_unmatch_args/notes_on_extra_args.stderr new file mode 100644 index 000000000000..e10b8ef46bac --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unmatch_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_unmatch_args/on_unmatch_args.rs b/tests/ui/diagnostic_namespace/on_unmatch_args/on_unmatch_args.rs new file mode 100644 index 000000000000..a4fc1460b60e --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unmatch_args/on_unmatch_args.rs @@ -0,0 +1,19 @@ +#![feature(diagnostic_on_unmatch_args)] + +#[diagnostic::on_unmatch_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_unmatch_args/on_unmatch_args.stderr b/tests/ui/diagnostic_namespace/on_unmatch_args/on_unmatch_args.stderr new file mode 100644 index 000000000000..9d3c4a5392cd --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unmatch_args/on_unmatch_args.stderr @@ -0,0 +1,19 @@ +error: unexpected end of macro invocation + --> $DIR/on_unmatch_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_unmatch_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_unmatch_args/other_match_macro_error.rs b/tests/ui/diagnostic_namespace/on_unmatch_args/other_match_macro_error.rs new file mode 100644 index 000000000000..36197997b3e6 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unmatch_args/other_match_macro_error.rs @@ -0,0 +1,14 @@ +#![feature(diagnostic_on_unmatch_args)] + +#[diagnostic::on_unmatch_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_unmatch_args/other_match_macro_error.stderr b/tests/ui/diagnostic_namespace/on_unmatch_args/other_match_macro_error.stderr new file mode 100644 index 000000000000..5f6c59e79287 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unmatch_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/diagnostic_namespace/on_unmatch_args/report_warning_on_invalid_formats.rs b/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_invalid_formats.rs new file mode 100644 index 000000000000..65cf18168638 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_invalid_formats.rs @@ -0,0 +1,14 @@ +//@ check-pass +#![feature(diagnostic_on_unmatch_args)] + +#[diagnostic::on_unmatch_args( + message = "{T}! is missing arguments", + //~^ WARN unknown parameter `T` +)] +macro_rules! pair { + ($ty:ty, $value:expr) => {}; +} + +fn main() { + pair!(u8, 0); +} diff --git a/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_invalid_formats.stderr b/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_invalid_formats.stderr new file mode 100644 index 000000000000..2f631337c441 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_invalid_formats.stderr @@ -0,0 +1,11 @@ +warning: unknown parameter `T` + --> $DIR/report_warning_on_invalid_formats.rs:5:17 + | +LL | message = "{T}! is missing arguments", + | ^ + | + = 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 + diff --git a/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_invalid_meta_item_syntax.rs b/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_invalid_meta_item_syntax.rs new file mode 100644 index 000000000000..aa5371de07c0 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_invalid_meta_item_syntax.rs @@ -0,0 +1,12 @@ +//@ check-pass +#![feature(diagnostic_on_unmatch_args)] + +#[diagnostic::on_unmatch_args = "foo"] +//~^ WARN malformed `diagnostic::on_unmatch_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_unmatch_args/report_warning_on_invalid_meta_item_syntax.stderr b/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_invalid_meta_item_syntax.stderr new file mode 100644 index 000000000000..51f25f1165d6 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_invalid_meta_item_syntax.stderr @@ -0,0 +1,11 @@ +warning: malformed `diagnostic::on_unmatch_args` attribute + --> $DIR/report_warning_on_invalid_meta_item_syntax.rs:4:1 + | +LL | #[diagnostic::on_unmatch_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_unmatch_args/report_warning_on_missing_options.rs b/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_missing_options.rs new file mode 100644 index 000000000000..13eec1834d7c --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_missing_options.rs @@ -0,0 +1,12 @@ +//@ check-pass +#![feature(diagnostic_on_unmatch_args)] + +#[diagnostic::on_unmatch_args] +//~^ WARN missing options for `diagnostic::on_unmatch_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_unmatch_args/report_warning_on_missing_options.stderr b/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_missing_options.stderr new file mode 100644 index 000000000000..bbd8dba4e620 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_missing_options.stderr @@ -0,0 +1,11 @@ +warning: missing options for `diagnostic::on_unmatch_args` attribute + --> $DIR/report_warning_on_missing_options.rs:4:1 + | +LL | #[diagnostic::on_unmatch_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_unmatch_args/report_warning_on_non_macro.rs b/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_non_macro.rs new file mode 100644 index 000000000000..6f8629d679a0 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_non_macro.rs @@ -0,0 +1,10 @@ +//@ check-pass +#![feature(diagnostic_on_unmatch_args)] + +#[diagnostic::on_unmatch_args(message = "not allowed here")] +//~^ WARN `#[diagnostic::on_unmatch_args]` can only be applied to macro definitions +struct Foo; + +fn main() { + let _ = Foo; +} diff --git a/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_non_macro.stderr b/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_non_macro.stderr new file mode 100644 index 000000000000..c6d1b620c44b --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_non_macro.stderr @@ -0,0 +1,10 @@ +warning: `#[diagnostic::on_unmatch_args]` can only be applied to macro definitions + --> $DIR/report_warning_on_non_macro.rs:4:1 + | +LL | #[diagnostic::on_unmatch_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_unmatch_args/report_warning_on_unknown_options.rs b/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_unknown_options.rs new file mode 100644 index 000000000000..b1e50256dc88 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_unknown_options.rs @@ -0,0 +1,12 @@ +//@ check-pass +#![feature(diagnostic_on_unmatch_args)] + +#[diagnostic::on_unmatch_args(unsupported = "foo")] +//~^ WARN malformed `diagnostic::on_unmatch_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_unmatch_args/report_warning_on_unknown_options.stderr b/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_unknown_options.stderr new file mode 100644 index 000000000000..2e897a6b180d --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_unknown_options.stderr @@ -0,0 +1,11 @@ +warning: malformed `diagnostic::on_unmatch_args` attribute + --> $DIR/report_warning_on_unknown_options.rs:4:31 + | +LL | #[diagnostic::on_unmatch_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-unmatch-args.rs b/tests/ui/feature-gates/feature-gate-diagnostic-on-unmatch-args.rs new file mode 100644 index 000000000000..72686d100327 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-diagnostic-on-unmatch-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_unmatch_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-unmatch-args.stderr b/tests/ui/feature-gates/feature-gate-diagnostic-on-unmatch-args.stderr new file mode 100644 index 000000000000..39eecc8322b2 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-diagnostic-on-unmatch-args.stderr @@ -0,0 +1,17 @@ +error: unexpected end of macro invocation + --> $DIR/feature-gate-diagnostic-on-unmatch-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-unmatch-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 2a5e884a5e3f..611077a57022 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 @@ -23,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 2a5e884a5e3f..611077a57022 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 @@ -23,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 0772bb18e0c6..ca6db9ecf097 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 (nested) field 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 (nested) field path, like `offset_of!(Type, field)` error: no rules expected `too` --> $DIR/offset-of-arg-count.rs:6:34 @@ -23,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 (nested) field 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 33dea9918cac..f90f2db1c6c3 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 (nested) field path, like `offset_of!(Type, field)` error: offset_of expects dot-separated field and variant names --> $DIR/offset-of-tuple.rs:7:26