Don't panic in macro shape validation (#3647)

# Objective
Emitting compile errors produces cleaner messages than panicking in a proc-macro.

## Solution
- Replace match-with-panic code with call to new `bevy_macro_utils::get_named_struct_fields` function
- Replace one use of match-with-panic for enums with inline match

_Aside:_ I'm also the maintainer of [`darling`](https://docs.rs/darling), a crate which provides a serde-like API for parsing macro inputs. I avoided using it here because it seemed like overkill, but if there are plans to add lots more attributes/macros then that might be a good way of offloading macro error handling.
This commit is contained in:
Ted Driggs 2022-01-15 22:14:43 +00:00
parent c16d0c5a39
commit 8e1f660e1d
6 changed files with 46 additions and 33 deletions

View File

@ -1,17 +1,14 @@
use bevy_macro_utils::get_named_struct_fields;
use proc_macro2::{Literal, TokenStream}; use proc_macro2::{Literal, TokenStream};
use quote::quote; use quote::quote;
use syn::{parse_quote, Data, DeriveInput, Fields, Path}; use syn::{parse_quote, DeriveInput, Path};
pub fn emit(input: DeriveInput) -> TokenStream { pub fn emit(input: DeriveInput) -> TokenStream {
let bevy_crevice_path = crate::bevy_crevice_path(); let bevy_crevice_path = crate::bevy_crevice_path();
let fields = match &input.data { let fields = match get_named_struct_fields(&input.data) {
Data::Struct(data) => match &data.fields { Ok(fields) => fields,
Fields::Named(fields) => fields, Err(e) => return e.into_compile_error(),
Fields::Unnamed(_) => panic!("Tuple structs are not supported"),
Fields::Unit => panic!("Unit structs are not supported"),
},
Data::Enum(_) | Data::Union(_) => panic!("Only structs are supported"),
}; };
let base_trait_path: Path = parse_quote!(#bevy_crevice_path::glsl::Glsl); let base_trait_path: Path = parse_quote!(#bevy_crevice_path::glsl::Glsl);

View File

@ -1,6 +1,7 @@
use bevy_macro_utils::get_named_struct_fields;
use proc_macro2::{Span, TokenStream}; use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote}; use quote::{format_ident, quote};
use syn::{parse_quote, Data, DeriveInput, Fields, Ident, Path, Type}; use syn::{parse_quote, DeriveInput, Ident, Path, Type};
pub fn emit( pub fn emit(
input: DeriveInput, input: DeriveInput,
@ -32,13 +33,9 @@ pub fn emit(
// Crevice's derive only works on regular structs. We could potentially // Crevice's derive only works on regular structs. We could potentially
// support transparent tuple structs in the future. // support transparent tuple structs in the future.
let fields: Vec<_> = match &input.data { let fields: Vec<_> = match get_named_struct_fields(&input.data) {
Data::Struct(data) => match &data.fields { Ok(fields) => fields.named.iter().collect(),
Fields::Named(fields) => fields.named.iter().collect(), Err(e) => return e.into_compile_error(),
Fields::Unnamed(_) => panic!("Tuple structs are not supported"),
Fields::Unit => panic!("Unit structs are not supported"),
},
Data::Enum(_) | Data::Union(_) => panic!("Only structs are supported"),
}; };
// Gives the layout-specific version of the given type. // Gives the layout-specific version of the given type.

View File

@ -1,5 +1,5 @@
use bevy_macro_utils::BevyManifest; use bevy_macro_utils::BevyManifest;
use proc_macro::TokenStream; use proc_macro::{Span, TokenStream};
use quote::quote; use quote::quote;
use syn::{parse_macro_input, Data, DeriveInput}; use syn::{parse_macro_input, Data, DeriveInput};
@ -7,7 +7,11 @@ pub fn derive_enum_variant_meta(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput); let ast = parse_macro_input!(input as DeriveInput);
let variants = match &ast.data { let variants = match &ast.data {
Data::Enum(v) => &v.variants, Data::Enum(v) => &v.variants,
_ => panic!("Expected an enum."), _ => {
return syn::Error::new(Span::call_site().into(), "Only enums are supported")
.into_compile_error()
.into()
}
}; };
let bevy_util_path = BevyManifest::default().get_path(crate::modules::BEVY_UTILS); let bevy_util_path = BevyManifest::default().get_path(crate::modules::BEVY_UTILS);

View File

@ -2,7 +2,7 @@ extern crate proc_macro;
mod component; mod component;
use bevy_macro_utils::{derive_label, BevyManifest}; use bevy_macro_utils::{derive_label, get_named_struct_fields, BevyManifest};
use proc_macro::TokenStream; use proc_macro::TokenStream;
use proc_macro2::Span; use proc_macro2::Span;
use quote::{format_ident, quote}; use quote::{format_ident, quote};
@ -11,8 +11,7 @@ use syn::{
parse_macro_input, parse_macro_input,
punctuated::Punctuated, punctuated::Punctuated,
token::Comma, token::Comma,
Data, DataStruct, DeriveInput, Field, Fields, GenericParam, Ident, Index, LitInt, Result, DeriveInput, Field, GenericParam, Ident, Index, LitInt, Result, Token,
Token,
}; };
struct AllTuples { struct AllTuples {
@ -86,12 +85,9 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput); let ast = parse_macro_input!(input as DeriveInput);
let ecs_path = bevy_ecs_path(); let ecs_path = bevy_ecs_path();
let named_fields = match &ast.data { let named_fields = match get_named_struct_fields(&ast.data) {
Data::Struct(DataStruct { Ok(fields) => &fields.named,
fields: Fields::Named(fields), Err(e) => return e.into_compile_error().into(),
..
}) => &fields.named,
_ => panic!("Expected a struct with named fields."),
}; };
let is_bundle = named_fields let is_bundle = named_fields
@ -304,12 +300,9 @@ static SYSTEM_PARAM_ATTRIBUTE_NAME: &str = "system_param";
#[proc_macro_derive(SystemParam, attributes(system_param))] #[proc_macro_derive(SystemParam, attributes(system_param))]
pub fn derive_system_param(input: TokenStream) -> TokenStream { pub fn derive_system_param(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput); let ast = parse_macro_input!(input as DeriveInput);
let fields = match &ast.data { let fields = match get_named_struct_fields(&ast.data) {
Data::Struct(DataStruct { Ok(fields) => &fields.named,
fields: Fields::Named(fields), Err(e) => return e.into_compile_error().into(),
..
}) => &fields.named,
_ => panic!("Expected a struct with named fields."),
}; };
let path = bevy_ecs_path(); let path = bevy_ecs_path();

View File

@ -1,9 +1,11 @@
extern crate proc_macro; extern crate proc_macro;
mod attrs; mod attrs;
mod shape;
mod symbol; mod symbol;
pub use attrs::*; pub use attrs::*;
pub use shape::*;
pub use symbol::*; pub use symbol::*;
use cargo_manifest::{DepsSet, Manifest}; use cargo_manifest::{DepsSet, Manifest};

View File

@ -0,0 +1,20 @@
use proc_macro::Span;
use syn::{Data, DataStruct, Error, Fields, FieldsNamed};
/// Get the fields of a data structure if that structure is a struct with named fields;
/// otherwise, return a compile error that points to the site of the macro invocation.
pub fn get_named_struct_fields(data: &syn::Data) -> syn::Result<&FieldsNamed> {
match data {
Data::Struct(DataStruct {
fields: Fields::Named(fields),
..
}) => Ok(fields),
_ => Err(Error::new(
// This deliberately points to the call site rather than the structure
// body; marking the entire body as the source of the error makes it
// impossible to figure out which `derive` has a problem.
Span::call_site().into(),
"Only structs with named fields are supported",
)),
}
}