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

#### After

---
## Changelog
- `#[derive(TypeUuid)]` provide better error messages.
This commit is contained in:
parent
21518de0de
commit
44c769f7b9
@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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>()?;
|
||||||
|
|||||||
@ -0,0 +1,5 @@
|
|||||||
|
#[test]
|
||||||
|
fn test() {
|
||||||
|
let t = trybuild::TestCases::new();
|
||||||
|
t.compile_fail("tests/type_uuid_derive/*.rs");
|
||||||
|
}
|
||||||
@ -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;
|
||||||
@ -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"]
|
||||||
|
| ^^^^^
|
||||||
Loading…
Reference in New Issue
Block a user