From b7358efa39fb0272b49f33d5f2b19ce9058f8e2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Thu, 6 Aug 2026 16:59:59 +0000 Subject: [PATCH] Detect bad number of generics caused by bad derive When a derive macro expands the annotated item's name directly using `quote!`, it keep the item's Span context (instead of having a new context). This means that the generic Span context machinery which provides feedback that an error happened due to a derive doesn't kick in. If a derive macro isn't written to take into account the existence of type parameters, an error for "mismatched number of type parameters" will be emitted. We now detect the case when this happens due to the derive macro, and customize the output to point that out, as well as avoid giving suggestions that will always be wrong. --- .../wrong_number_of_generic_args.rs | 30 ++++++++++++++- .../src/hir_ty_lowering/generics.rs | 2 + .../bar.rs | 10 +++++ .../bar.stderr | 26 +++++++++++++ .../foo.rs | 37 +++++++++++++++++++ .../rmake.rs | 10 +++++ 6 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 tests/run-make/derive-macro-unsupported-type-params/bar.rs create mode 100644 tests/run-make/derive-macro-unsupported-type-params/bar.stderr create mode 100644 tests/run-make/derive-macro-unsupported-type-params/foo.rs create mode 100644 tests/run-make/derive-macro-unsupported-type-params/rmake.rs diff --git a/compiler/rustc_hir_analysis/src/diagnostics/wrong_number_of_generic_args.rs b/compiler/rustc_hir_analysis/src/diagnostics/wrong_number_of_generic_args.rs index c80c63b7c0188..e66e4fb566701 100644 --- a/compiler/rustc_hir_analysis/src/diagnostics/wrong_number_of_generic_args.rs +++ b/compiler/rustc_hir_analysis/src/diagnostics/wrong_number_of_generic_args.rs @@ -3,7 +3,7 @@ use rustc_errors::codes::*; use rustc_errors::{Applicability, Diag, Diagnostic, EmissionGuarantee, MultiSpan, pluralize}; use rustc_hir as hir; use rustc_middle::ty::{self as ty, AssocItem, AssocItems, TyCtxt}; -use rustc_span::def_id::DefId; +use rustc_span::def_id::{DefId, LocalDefId}; use tracing::debug; /// Handles the `wrong number of type / lifetime / ... arguments` family of error messages. @@ -30,6 +30,9 @@ pub(crate) struct WrongNumberOfGenericArgs<'a, 'tcx> { /// DefId of the generic type pub(crate) def_id: DefId, + + /// DefId of the generic type + pub(crate) cx_def_id: LocalDefId, } // Provides information about the kind of arguments that were provided for @@ -83,6 +86,7 @@ pub(crate) enum GenericArgsInfo { // if synthetic type arguments (e.g. `impl Trait`) are specified synth_provided: bool, }, + // BadDerive, } impl<'a, 'tcx> WrongNumberOfGenericArgs<'a, 'tcx> { @@ -94,6 +98,7 @@ impl<'a, 'tcx> WrongNumberOfGenericArgs<'a, 'tcx> { params_offset: usize, gen_args: &'a hir::GenericArgs<'a>, def_id: DefId, + cx_def_id: LocalDefId, ) -> Self { let angle_brackets = if gen_args.span_ext().is_none() { if gen_args.is_empty() { AngleBrackets::Missing } else { AngleBrackets::Implied } @@ -110,6 +115,7 @@ impl<'a, 'tcx> WrongNumberOfGenericArgs<'a, 'tcx> { params_offset, gen_args, def_id, + cx_def_id, } } @@ -542,6 +548,25 @@ impl<'a, 'tcx> WrongNumberOfGenericArgs<'a, 'tcx> { } } + fn bad_derive(&self, err: &mut Diag<'_, impl EmissionGuarantee>) -> bool { + if let Some(ident) = self.tcx.opt_item_ident(self.def_id) + && self.def_id.is_local() + && self.path_segment.ident.span.source_equal(ident.span) + && self.tcx.is_automatically_derived(self.cx_def_id.into()) + { + // Very likely this is a botched `derive` which passes the iten name straight + // through, but doesn't support type parameters. + err.span_label( + self.tcx.def_span(self.cx_def_id), + "it looks like this derive macro might not support annotating items with type \ + parameters", + ); + + return true; + } + false + } + /// Builds the `expected 1 type argument / supplied 2 type arguments` message. fn notify(&self, err: &mut Diag<'_, impl EmissionGuarantee>) { let (quantifier, bound) = self.get_quantifier_and_bound(); @@ -1157,6 +1182,9 @@ impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for WrongNumberOfGenericArgs<'_ err.code(E0107); err.span(self.path_segment.ident.span); + if self.bad_derive(&mut err) { + return err; + } self.notify(&mut err); self.suggest(&mut err); self.show_definition(&mut err); diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/generics.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/generics.rs index 45c2ed205c74d..1b27c4a9509fc 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/generics.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/generics.rs @@ -471,6 +471,7 @@ pub(crate) fn check_generic_arg_count( has_self as usize, gen_args, def_id, + cx.item_def_id(), )); Err(reported) @@ -585,6 +586,7 @@ pub(crate) fn check_generic_arg_count( params_offset, gen_args, def_id, + cx.item_def_id(), )) .emit_unless_delay(all_params_are_binded) }); diff --git a/tests/run-make/derive-macro-unsupported-type-params/bar.rs b/tests/run-make/derive-macro-unsupported-type-params/bar.rs new file mode 100644 index 0000000000000..bcf9dc0d7d2f8 --- /dev/null +++ b/tests/run-make/derive-macro-unsupported-type-params/bar.rs @@ -0,0 +1,10 @@ +#![no_std] +#![crate_type = "lib"] + +#[macro_use] +extern crate foo; + +#[derive(A)] +enum A { + Variant(T), +} diff --git a/tests/run-make/derive-macro-unsupported-type-params/bar.stderr b/tests/run-make/derive-macro-unsupported-type-params/bar.stderr new file mode 100644 index 0000000000000..6b391f860dd7d --- /dev/null +++ b/tests/run-make/derive-macro-unsupported-type-params/bar.stderr @@ -0,0 +1,26 @@ +error[E0425]: cannot find type `T` in this scope + --> bar.rs:9:13 + | +9 | Variant(T), + | ^ not found in this scope + +error[E0107]: missing generics for enum `A` + --> bar.rs:8:6 + | +7 | #[derive(A)] + | - it looks like this derive macro might not support annotating items with type parameters +8 | enum A { + | ^ + +error[E0107]: missing generics for enum `A` + --> bar.rs:8:6 + | +7 | #[derive(A)] + | - it looks like this derive macro might not support annotating items with type parameters +8 | enum A { + | ^ + +error: aborting due to 3 previous errors + +Some errors have detailed explanations: E0107, E0425. +For more information about an error, try `rustc --explain E0107`. diff --git a/tests/run-make/derive-macro-unsupported-type-params/foo.rs b/tests/run-make/derive-macro-unsupported-type-params/foo.rs new file mode 100644 index 0000000000000..5ce42a8023608 --- /dev/null +++ b/tests/run-make/derive-macro-unsupported-type-params/foo.rs @@ -0,0 +1,37 @@ +#![crate_type = "proc-macro"] +#![feature(proc_macro_quote)] + +extern crate proc_macro; + +use proc_macro::{TokenStream, TokenTree, quote}; + +#[proc_macro_derive(A)] +pub fn derive(item: TokenStream) -> TokenStream { + let mut tokens = item.into_iter(); + let _enum = tokens.next(); + let name = tokens.next().unwrap(); + let _ = tokens.next().unwrap(); + let _ = tokens.next().unwrap(); + let _ = tokens.next().unwrap(); + let TokenTree::Group(group) = tokens.next().unwrap() else { panic!() }; + let mut group = group.stream().into_iter(); + let variant = group.next().unwrap(); + let TokenTree::Group(args) = group.next().unwrap() else { panic!() }; + let arg = args.stream().into_iter().next().unwrap(); + let tokens = quote! { + trait X {} + #[automatically_derived] + impl X for $name {} + + #[automatically_derived] + impl $name { + fn foo(&self) { + if let Self :: $variant(val) = self { + let _: $arg = val; + } + } + } + + }; + tokens +} diff --git a/tests/run-make/derive-macro-unsupported-type-params/rmake.rs b/tests/run-make/derive-macro-unsupported-type-params/rmake.rs new file mode 100644 index 0000000000000..b0bba4eab796f --- /dev/null +++ b/tests/run-make/derive-macro-unsupported-type-params/rmake.rs @@ -0,0 +1,10 @@ +//@ ignore-cross-compile +//@ needs-crate-type: proc-macro + +use run_make_support::{diff, rustc, target}; + +fn main() { + rustc().input("foo.rs").edition("2024").run(); + let out = rustc().input("bar.rs").edition("2024").run_fail().stderr_utf8(); + diff().expected_file("bar.stderr").actual_text("actual-bar-stderr", out).run(); +}