Skip to content

Commit 44c769f

Browse files
authored
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.
1 parent 21518de commit 44c769f

File tree

5 files changed

+75
-33
lines changed

5 files changed

+75
-33
lines changed

crates/bevy_reflect/bevy_reflect_derive/src/lib.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ mod type_uuid;
3030
mod utility;
3131

3232
use crate::derive_data::{ReflectDerive, ReflectMeta, ReflectStruct};
33-
use crate::type_uuid::gen_impl_type_uuid;
3433
use container_attributes::ReflectTraits;
3534
use derive_data::ReflectTypePath;
3635
use proc_macro::TokenStream;
@@ -39,7 +38,6 @@ use reflect_value::ReflectValueDef;
3938
use syn::spanned::Spanned;
4039
use syn::{parse_macro_input, DeriveInput};
4140
use type_path::NamedTypePathDef;
42-
use type_uuid::TypeUuidDef;
4341
use utility::WhereClauseOptions;
4442

4543
pub(crate) static REFLECT_ATTRIBUTE_NAME: &str = "reflect";
@@ -288,7 +286,10 @@ pub fn derive_type_path(input: TokenStream) -> TokenStream {
288286
// From https://github.com/randomPoison/type-uuid
289287
#[proc_macro_derive(TypeUuid, attributes(uuid))]
290288
pub fn derive_type_uuid(input: TokenStream) -> TokenStream {
289+
let input = parse_macro_input!(input as DeriveInput);
291290
type_uuid::type_uuid_derive(input)
291+
.unwrap_or_else(syn::Error::into_compile_error)
292+
.into()
292293
}
293294

294295
/// 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 {
588589
/// 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)]`.
589590
#[proc_macro]
590591
pub fn impl_type_uuid(input: TokenStream) -> TokenStream {
591-
let def = parse_macro_input!(input as TypeUuidDef);
592-
gen_impl_type_uuid(def)
592+
let def = parse_macro_input!(input as type_uuid::TypeUuidDef);
593+
type_uuid::gen_impl_type_uuid(def).into()
593594
}

crates/bevy_reflect/bevy_reflect_derive/src/type_uuid.rs

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,54 @@
1-
extern crate proc_macro;
2-
31
use bevy_macro_utils::BevyManifest;
2+
use proc_macro2::{Span, TokenStream};
43
use quote::quote;
54
use syn::parse::{Parse, ParseStream};
65
use syn::token::Comma;
7-
use syn::*;
6+
use syn::{DeriveInput, Expr, ExprLit, Generics, Ident, Lit, LitInt, LitStr, Meta};
87
use uuid::Uuid;
98

10-
/// Parses input from a derive of `TypeUuid`.
11-
pub(crate) fn type_uuid_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
12-
// Construct a representation of Rust code as a syntax tree
13-
// that we can manipulate
14-
let ast: DeriveInput = syn::parse(input).unwrap();
15-
// Build the trait implementation
16-
let type_ident = ast.ident;
17-
9+
pub(crate) fn type_uuid_derive(input: DeriveInput) -> syn::Result<TokenStream> {
1810
let mut uuid = None;
19-
for attribute in ast.attrs.iter().filter(|attr| attr.path().is_ident("uuid")) {
11+
for attribute in input
12+
.attrs
13+
.iter()
14+
.filter(|attr| attr.path().is_ident("uuid"))
15+
{
2016
let Meta::NameValue(ref name_value) = attribute.meta else {
2117
continue;
2218
};
2319

2420
let uuid_str = match &name_value.value {
2521
Expr::Lit(ExprLit{lit: Lit::Str(lit_str), ..}) => lit_str,
26-
_ => panic!("`uuid` attribute must take the form `#[uuid = \"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"`."),
22+
_ => return Err(syn::Error::new_spanned(attribute, "`uuid` attribute must take the form `#[uuid = \"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"]`.")),
2723
};
2824

29-
uuid = Some(
30-
Uuid::parse_str(&uuid_str.value())
31-
.expect("Value specified to `#[uuid]` attribute is not a valid UUID."),
32-
);
25+
uuid =
26+
Some(Uuid::parse_str(&uuid_str.value()).map_err(|err| {
27+
syn::Error::new_spanned(uuid_str, format!("Invalid UUID: {err}"))
28+
})?);
3329
}
3430

35-
let uuid =
36-
uuid.expect("No `#[uuid = \"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"` attribute found.");
37-
gen_impl_type_uuid(TypeUuidDef {
38-
type_ident,
39-
generics: ast.generics,
31+
let uuid = uuid.ok_or_else(|| {
32+
syn::Error::new(
33+
Span::call_site(),
34+
"No `#[uuid = \"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"]` attribute found.",
35+
)
36+
})?;
37+
38+
Ok(gen_impl_type_uuid(TypeUuidDef {
39+
type_ident: input.ident,
40+
generics: input.generics,
4041
uuid,
41-
})
42+
}))
4243
}
4344

4445
/// Generates an implementation of `TypeUuid`. If there any generics, the `TYPE_UUID` will be a composite of the generic types' `TYPE_UUID`.
45-
pub(crate) fn gen_impl_type_uuid(def: TypeUuidDef) -> proc_macro::TokenStream {
46+
pub(crate) fn gen_impl_type_uuid(def: TypeUuidDef) -> TokenStream {
4647
let uuid = def.uuid;
4748
let mut generics = def.generics;
4849
let ty = def.type_ident;
4950

50-
let bevy_reflect_path: Path = BevyManifest::default().get_path("bevy_reflect");
51+
let bevy_reflect_path = BevyManifest::default().get_path("bevy_reflect");
5152

5253
generics.type_params_mut().for_each(|param| {
5354
param
@@ -74,12 +75,11 @@ pub(crate) fn gen_impl_type_uuid(def: TypeUuidDef) -> proc_macro::TokenStream {
7475
}
7576
});
7677

77-
let gen = quote! {
78+
quote! {
7879
impl #impl_generics #bevy_reflect_path::TypeUuid for #ty #type_generics #where_clause {
7980
const TYPE_UUID: #bevy_reflect_path::Uuid = #type_uuid;
8081
}
81-
};
82-
gen.into()
82+
}
8383
}
8484

8585
/// 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 {
9090
}
9191

9292
impl Parse for TypeUuidDef {
93-
fn parse(input: ParseStream) -> Result<Self> {
93+
fn parse(input: ParseStream) -> syn::Result<Self> {
9494
let type_ident = input.parse::<Ident>()?;
9595
let generics = input.parse::<Generics>()?;
9696
input.parse::<Comma>()?;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#[test]
2+
fn test() {
3+
let t = trybuild::TestCases::new();
4+
t.compile_fail("tests/type_uuid_derive/*.rs");
5+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
use bevy_reflect::TypeUuid;
2+
3+
fn main() {}
4+
5+
// Missing #[uuid] attribute
6+
#[derive(TypeUuid)]
7+
struct A;
8+
9+
// Malformed attribute
10+
#[derive(TypeUuid)]
11+
#[uuid = 42]
12+
struct B;
13+
14+
// UUID parse fail
15+
#[derive(TypeUuid)]
16+
#[uuid = "000"]
17+
struct C;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
error: No `#[uuid = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"]` attribute found.
2+
--> tests/type_uuid_derive/derive_type_uuid.rs:6:10
3+
|
4+
6 | #[derive(TypeUuid)]
5+
| ^^^^^^^^
6+
|
7+
= note: this error originates in the derive macro `TypeUuid` (in Nightly builds, run with -Z macro-backtrace for more info)
8+
9+
error: `uuid` attribute must take the form `#[uuid = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"]`.
10+
--> tests/type_uuid_derive/derive_type_uuid.rs:11:1
11+
|
12+
11 | #[uuid = 42]
13+
| ^^^^^^^^^^^^
14+
15+
error: Invalid UUID: invalid length: expected length 32 for simple format, found 3
16+
--> tests/type_uuid_derive/derive_type_uuid.rs:16:10
17+
|
18+
16 | #[uuid = "000"]
19+
| ^^^^^

0 commit comments

Comments
 (0)