Improve TypeUuid's derive macro error messages (#9315)

# Objective

- Better error message
- More idiomatic code

## Solution

Refactorize `TypeUuid` macros to use `syn::Result` instead of panic.

## Before/After error messages

### Missing `#[uuid]` attribtue

#### Before
```
error: proc-macro derive panicked
 --> src\main.rs:1:10
  |
1 | #[derive(TypeUuid)]
  |          ^^^^^^^^
  |
  = help: message: No `#[uuid = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"` attribute found.
```

#### After
```
error: No `#[uuid = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"]` attribute found.
 --> src\main.rs:3:10
  |
3 | #[derive(TypeUuid)]
  |          ^^^^^^^^
  |
  = note: this error originates in the derive macro `TypeUuid` (in Nightly builds, run with -Z macro-backtrace for more info)
```

### Malformed attribute

#### Before

```
error: proc-macro derive panicked
 --> src\main.rs:3:10
  |
3 | #[derive(TypeUuid)]
  |          ^^^^^^^^
  |
  = help: message: `uuid` attribute must take the form `#[uuid = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"`.
```

#### After

```
error: `uuid` attribute must take the form `#[uuid = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"]`.
 --> src\main.rs:4:1
  |
4 | #[uuid = 42]
  | ^^^^^^^^^^^^
```

### UUID parse fail

#### Before
```
error: proc-macro derive panicked
 --> src\main.rs:3:10
  |
3 | #[derive(TypeUuid)]
  |          ^^^^^^^^
  |
  = help: message: Value specified to `#[uuid]` attribute is not a valid UUID.: Error(SimpleLength { len: 3 })
```

#### After

```
error: Invalid UUID: invalid length: expected length 32 for simple format, found 3
 --> src\main.rs:4:10
  |
4 | #[uuid = "000"]
  |          ^^^^^
```

### With [Error
Lens](https://marketplace.visualstudio.com/items?itemName=usernamehw.errorlens)

#### Before

![image](https://github.com/bevyengine/bevy/assets/33934311/415247fa-ff5c-4513-8012-7a9ff77445fb)

#### After

![image](https://github.com/bevyengine/bevy/assets/33934311/d124eeaa-9213-49e0-8860-539ad0218a40)


---

## Changelog

- `#[derive(TypeUuid)]` provide better error messages.
This commit is contained in:
Tristan Guichaoua 2023-10-02 14:42:01 +02:00 committed by GitHub
parent 21518de0de
commit 44c769f7b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 75 additions and 33 deletions

View File

@ -30,7 +30,6 @@ mod type_uuid;
mod utility; mod utility;
use crate::derive_data::{ReflectDerive, ReflectMeta, ReflectStruct}; use crate::derive_data::{ReflectDerive, ReflectMeta, ReflectStruct};
use crate::type_uuid::gen_impl_type_uuid;
use container_attributes::ReflectTraits; use container_attributes::ReflectTraits;
use derive_data::ReflectTypePath; use derive_data::ReflectTypePath;
use proc_macro::TokenStream; use proc_macro::TokenStream;
@ -39,7 +38,6 @@ use reflect_value::ReflectValueDef;
use syn::spanned::Spanned; use syn::spanned::Spanned;
use syn::{parse_macro_input, DeriveInput}; use syn::{parse_macro_input, DeriveInput};
use type_path::NamedTypePathDef; use type_path::NamedTypePathDef;
use type_uuid::TypeUuidDef;
use utility::WhereClauseOptions; use utility::WhereClauseOptions;
pub(crate) static REFLECT_ATTRIBUTE_NAME: &str = "reflect"; pub(crate) static REFLECT_ATTRIBUTE_NAME: &str = "reflect";
@ -288,7 +286,10 @@ pub fn derive_type_path(input: TokenStream) -> TokenStream {
// From https://github.com/randomPoison/type-uuid // From https://github.com/randomPoison/type-uuid
#[proc_macro_derive(TypeUuid, attributes(uuid))] #[proc_macro_derive(TypeUuid, attributes(uuid))]
pub fn derive_type_uuid(input: TokenStream) -> TokenStream { pub fn derive_type_uuid(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
type_uuid::type_uuid_derive(input) type_uuid::type_uuid_derive(input)
.unwrap_or_else(syn::Error::into_compile_error)
.into()
} }
/// A macro that automatically generates type data for traits, which their implementors can then register. /// A macro that automatically generates type data for traits, which their implementors can then register.
@ -588,6 +589,6 @@ pub fn impl_type_path(input: TokenStream) -> TokenStream {
/// Derives `TypeUuid` for the given type. This is used internally to implement `TypeUuid` on foreign types, such as those in the std. This macro should be used in the format of `<[Generic Params]> [Type (Path)], [Uuid (String Literal)]`. /// Derives `TypeUuid` for the given type. This is used internally to implement `TypeUuid` on foreign types, such as those in the std. This macro should be used in the format of `<[Generic Params]> [Type (Path)], [Uuid (String Literal)]`.
#[proc_macro] #[proc_macro]
pub fn impl_type_uuid(input: TokenStream) -> TokenStream { pub fn impl_type_uuid(input: TokenStream) -> TokenStream {
let def = parse_macro_input!(input as TypeUuidDef); let def = parse_macro_input!(input as type_uuid::TypeUuidDef);
gen_impl_type_uuid(def) type_uuid::gen_impl_type_uuid(def).into()
} }

View File

@ -1,53 +1,54 @@
extern crate proc_macro;
use bevy_macro_utils::BevyManifest; use bevy_macro_utils::BevyManifest;
use proc_macro2::{Span, TokenStream};
use quote::quote; use quote::quote;
use syn::parse::{Parse, ParseStream}; use syn::parse::{Parse, ParseStream};
use syn::token::Comma; use syn::token::Comma;
use syn::*; use syn::{DeriveInput, Expr, ExprLit, Generics, Ident, Lit, LitInt, LitStr, Meta};
use uuid::Uuid; use uuid::Uuid;
/// Parses input from a derive of `TypeUuid`. pub(crate) fn type_uuid_derive(input: DeriveInput) -> syn::Result<TokenStream> {
pub(crate) fn type_uuid_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
// Construct a representation of Rust code as a syntax tree
// that we can manipulate
let ast: DeriveInput = syn::parse(input).unwrap();
// Build the trait implementation
let type_ident = ast.ident;
let mut uuid = None; let mut uuid = None;
for attribute in ast.attrs.iter().filter(|attr| attr.path().is_ident("uuid")) { for attribute in input
.attrs
.iter()
.filter(|attr| attr.path().is_ident("uuid"))
{
let Meta::NameValue(ref name_value) = attribute.meta else { let Meta::NameValue(ref name_value) = attribute.meta else {
continue; continue;
}; };
let uuid_str = match &name_value.value { let uuid_str = match &name_value.value {
Expr::Lit(ExprLit{lit: Lit::Str(lit_str), ..}) => lit_str, Expr::Lit(ExprLit{lit: Lit::Str(lit_str), ..}) => lit_str,
_ => panic!("`uuid` attribute must take the form `#[uuid = \"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"`."), _ => return Err(syn::Error::new_spanned(attribute, "`uuid` attribute must take the form `#[uuid = \"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"]`.")),
}; };
uuid = Some( uuid =
Uuid::parse_str(&uuid_str.value()) Some(Uuid::parse_str(&uuid_str.value()).map_err(|err| {
.expect("Value specified to `#[uuid]` attribute is not a valid UUID."), syn::Error::new_spanned(uuid_str, format!("Invalid UUID: {err}"))
); })?);
} }
let uuid = let uuid = uuid.ok_or_else(|| {
uuid.expect("No `#[uuid = \"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"` attribute found."); syn::Error::new(
gen_impl_type_uuid(TypeUuidDef { Span::call_site(),
type_ident, "No `#[uuid = \"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"]` attribute found.",
generics: ast.generics, )
})?;
Ok(gen_impl_type_uuid(TypeUuidDef {
type_ident: input.ident,
generics: input.generics,
uuid, uuid,
}) }))
} }
/// Generates an implementation of `TypeUuid`. If there any generics, the `TYPE_UUID` will be a composite of the generic types' `TYPE_UUID`. /// Generates an implementation of `TypeUuid`. If there any generics, the `TYPE_UUID` will be a composite of the generic types' `TYPE_UUID`.
pub(crate) fn gen_impl_type_uuid(def: TypeUuidDef) -> proc_macro::TokenStream { pub(crate) fn gen_impl_type_uuid(def: TypeUuidDef) -> TokenStream {
let uuid = def.uuid; let uuid = def.uuid;
let mut generics = def.generics; let mut generics = def.generics;
let ty = def.type_ident; let ty = def.type_ident;
let bevy_reflect_path: Path = BevyManifest::default().get_path("bevy_reflect"); let bevy_reflect_path = BevyManifest::default().get_path("bevy_reflect");
generics.type_params_mut().for_each(|param| { generics.type_params_mut().for_each(|param| {
param param
@ -74,12 +75,11 @@ pub(crate) fn gen_impl_type_uuid(def: TypeUuidDef) -> proc_macro::TokenStream {
} }
}); });
let gen = quote! { quote! {
impl #impl_generics #bevy_reflect_path::TypeUuid for #ty #type_generics #where_clause { impl #impl_generics #bevy_reflect_path::TypeUuid for #ty #type_generics #where_clause {
const TYPE_UUID: #bevy_reflect_path::Uuid = #type_uuid; const TYPE_UUID: #bevy_reflect_path::Uuid = #type_uuid;
} }
}; }
gen.into()
} }
/// A struct containing the data required to generate an implementation of `TypeUuid`. This can be generated by either [`impl_type_uuid!`][crate::impl_type_uuid!] or [`type_uuid_derive`]. /// A struct containing the data required to generate an implementation of `TypeUuid`. This can be generated by either [`impl_type_uuid!`][crate::impl_type_uuid!] or [`type_uuid_derive`].
@ -90,7 +90,7 @@ pub(crate) struct TypeUuidDef {
} }
impl Parse for TypeUuidDef { impl Parse for TypeUuidDef {
fn parse(input: ParseStream) -> Result<Self> { fn parse(input: ParseStream) -> syn::Result<Self> {
let type_ident = input.parse::<Ident>()?; let type_ident = input.parse::<Ident>()?;
let generics = input.parse::<Generics>()?; let generics = input.parse::<Generics>()?;
input.parse::<Comma>()?; input.parse::<Comma>()?;

View File

@ -0,0 +1,5 @@
#[test]
fn test() {
let t = trybuild::TestCases::new();
t.compile_fail("tests/type_uuid_derive/*.rs");
}

View File

@ -0,0 +1,17 @@
use bevy_reflect::TypeUuid;
fn main() {}
// Missing #[uuid] attribute
#[derive(TypeUuid)]
struct A;
// Malformed attribute
#[derive(TypeUuid)]
#[uuid = 42]
struct B;
// UUID parse fail
#[derive(TypeUuid)]
#[uuid = "000"]
struct C;

View File

@ -0,0 +1,19 @@
error: No `#[uuid = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"]` attribute found.
--> tests/type_uuid_derive/derive_type_uuid.rs:6:10
|
6 | #[derive(TypeUuid)]
| ^^^^^^^^
|
= note: this error originates in the derive macro `TypeUuid` (in Nightly builds, run with -Z macro-backtrace for more info)
error: `uuid` attribute must take the form `#[uuid = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"]`.
--> tests/type_uuid_derive/derive_type_uuid.rs:11:1
|
11 | #[uuid = 42]
| ^^^^^^^^^^^^
error: Invalid UUID: invalid length: expected length 32 for simple format, found 3
--> tests/type_uuid_derive/derive_type_uuid.rs:16:10
|
16 | #[uuid = "000"]
| ^^^^^