Skip to content

proc_macro when used in conjunction with missing generics causes erroneous rustc error #149939

@SciMind2460

Description

@SciMind2460

Code

#[proc_macro_derive(MyFrom)]
pub fn my_derive(input: TokenStream) -> TokenStream {
    let derived_input = parse_macro_input!(input as DeriveInput);
    let ident = &derived_input.ident;
    if let Data::Struct(data) = derived_input.data {
        match data.fields {
            Fields::Named(fields) => {
                if fields.named.len() != 1 {
                    return syn::Error::new(fields.span(), "#[derive(MyFrom)] cannot be implemented on anything other than a struct with one field").to_compile_error().into();
                }
                let field = fields.named.first().unwrap();
                let name = field.ident.as_ref().unwrap();
                let field_type = field.ty.clone();
                if derived_input.generics.params.is_empty() {
                    quote! {
                        impl From<#field_type> for #ident {
                            fn from(value: #field_type) -> Self {
                                Self {
                                    #name: value
                                }
                            }
                        }
                    }.into()
                } else {
                    let generics = derived_input.generics.params;
                    if let Some(where_clause) = derived_input.generics.where_clause {
                        quote! {
                            impl<#generics> From<#field_type> for #ident<#generics> #where_clause {
                                fn from(value: #field_type) -> Self {
                                    Self {
                                        #name: value
                                    }
                                }
                            }
                        }.into()
                    } else {
                        quote! {
                            impl<#generics> From<#field_type> for #ident<#generics> {
                                fn from(value: #field_type) -> Self {
                                    Self {
                                        #name: value
                                    }
                                }
                            }
                        }.into()
                    }
                }
            },
            Fields::Unnamed(fields) => {
                if fields.unnamed.len() != 1 {
                    return syn::Error::new(fields.span(), "#[derive(MyFrom)] cannot be implemented on anything other than a struct with one field").to_compile_error().into();
                }
                let field = fields.unnamed.first().unwrap();
                let field_type = field.ty.clone();
                if derived_input.generics.params.is_empty() {
                    quote! {
                        impl From<#field_type> for #ident {
                            fn from(value: #field_type) -> Self {
                                Self(value)
                            }
                        }
                    }.into()
                } else {
                    let generics = derived_input.generics.params;
                    if let Some(where_clause) = derived_input.generics.where_clause {
                        quote! {
                            impl<#generics> From<#field_type> for #ident<#generics> #where_clause {
                                fn from(value: #field_type) -> Self {
                                    Self(value)
                                }
                            }
                        }.into()
                    } else {
                        quote! {
                            impl<#generics> From<#field_type> for #ident<#generics> {
                                fn from(value: #field_type) -> Self {
                                    Self(value)
                                }
                            }
                        }.into()
                    }
                }
            },
            Fields::Unit => {
                syn::Error::new(ident.span(), "#[derive(MyFrom)] cannot be implemented on anything other than a struct with one field").to_compile_error().into()
            }
        }
    } else {
        syn::Error::new(ident.span(), "#[derive(MyFrom)] cannot be implemented on anything other than a struct with one field").to_compile_error().into()
    }
}


and

#[derive(MyFrom)]
struct MyTupleGeneric<T> {
    name: T
}

Current output

error[E0107]: missing generics for struct `MyTupleGeneric`                                                                                                                      
  --> derive_from\tests\from_works.rs:12:8
   |
12 | struct MyTupleGeneric<T> {
   |        ^^^^^^^^^^^^^^ expected 1 generic argument
   |
note: struct defined here, with 1 generic parameter: `T`
  --> derive_from\tests\from_works.rs:12:8
   |
12 | struct MyTupleGeneric<T> {
   |        ^^^^^^^^^^^^^^ -
help: add missing generic argument
   |
12 | struct MyTupleGeneric<T><T> {

Desired output

error[E0107]: missing generics for struct `MyTupleGeneric`                                                                                                                      
  --> derive_from\tests\from_works.rs:12:8
   |
12 | struct MyTupleGeneric<T> {
   |        ^^^^^^^^^^^^^^ expected 1 generic argument
   |
note: struct defined here, with 1 generic parameter: `T`
  --> derive_from\tests\from_works.rs:12:8
   |
12 | struct MyTupleGeneric<T> {
   |        ^^^^^^^^^^^^^^ -
help: add missing generic parameter to trait impl
   |
   | impl Tr
   |

Rationale and extra context

In proc_macros, when a generic argument is missing, the code errors and erroneously suggests that the user of the macro is responsible.
I believe this must be changed to ensure that the proc_macro itself is shown to be in error.

Other cases

Rust Version

rustc 1.92.0 (ded5c06cf 2025-12-08)
binary: rustc
commit-hash: ded5c06cf21d2b93bffd5d884aa6e96934ee4234
commit-date: 2025-12-08
host: x86_64-pc-windows-msvc
release: 1.92.0
LLVM version: 21.1.3

Anything else?

I wish that this code doesn't produce the erroneous output but rather a complete explanation of where the code errors.

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-diagnosticsArea: Messages for errors, warnings, and lintsT-compilerRelevant to the compiler team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions