Skip to content

Commit 8e1f660

Browse files
committed
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.
1 parent c16d0c5 commit 8e1f660

File tree

6 files changed

+46
-33
lines changed

6 files changed

+46
-33
lines changed

crates/bevy_crevice/bevy-crevice-derive/src/glsl.rs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
1+
use bevy_macro_utils::get_named_struct_fields;
12
use proc_macro2::{Literal, TokenStream};
23
use quote::quote;
3-
use syn::{parse_quote, Data, DeriveInput, Fields, Path};
4+
use syn::{parse_quote, DeriveInput, Path};
45

56
pub fn emit(input: DeriveInput) -> TokenStream {
67
let bevy_crevice_path = crate::bevy_crevice_path();
78

8-
let fields = match &input.data {
9-
Data::Struct(data) => match &data.fields {
10-
Fields::Named(fields) => fields,
11-
Fields::Unnamed(_) => panic!("Tuple structs are not supported"),
12-
Fields::Unit => panic!("Unit structs are not supported"),
13-
},
14-
Data::Enum(_) | Data::Union(_) => panic!("Only structs are supported"),
9+
let fields = match get_named_struct_fields(&input.data) {
10+
Ok(fields) => fields,
11+
Err(e) => return e.into_compile_error(),
1512
};
1613

1714
let base_trait_path: Path = parse_quote!(#bevy_crevice_path::glsl::Glsl);

crates/bevy_crevice/bevy-crevice-derive/src/layout.rs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
use bevy_macro_utils::get_named_struct_fields;
12
use proc_macro2::{Span, TokenStream};
23
use quote::{format_ident, quote};
3-
use syn::{parse_quote, Data, DeriveInput, Fields, Ident, Path, Type};
4+
use syn::{parse_quote, DeriveInput, Ident, Path, Type};
45

56
pub fn emit(
67
input: DeriveInput,
@@ -32,13 +33,9 @@ pub fn emit(
3233

3334
// Crevice's derive only works on regular structs. We could potentially
3435
// support transparent tuple structs in the future.
35-
let fields: Vec<_> = match &input.data {
36-
Data::Struct(data) => match &data.fields {
37-
Fields::Named(fields) => fields.named.iter().collect(),
38-
Fields::Unnamed(_) => panic!("Tuple structs are not supported"),
39-
Fields::Unit => panic!("Unit structs are not supported"),
40-
},
41-
Data::Enum(_) | Data::Union(_) => panic!("Only structs are supported"),
36+
let fields: Vec<_> = match get_named_struct_fields(&input.data) {
37+
Ok(fields) => fields.named.iter().collect(),
38+
Err(e) => return e.into_compile_error(),
4239
};
4340

4441
// Gives the layout-specific version of the given type.

crates/bevy_derive/src/enum_variant_meta.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
use bevy_macro_utils::BevyManifest;
2-
use proc_macro::TokenStream;
2+
use proc_macro::{Span, TokenStream};
33
use quote::quote;
44
use syn::{parse_macro_input, Data, DeriveInput};
55

66
pub fn derive_enum_variant_meta(input: TokenStream) -> TokenStream {
77
let ast = parse_macro_input!(input as DeriveInput);
88
let variants = match &ast.data {
99
Data::Enum(v) => &v.variants,
10-
_ => panic!("Expected an enum."),
10+
_ => {
11+
return syn::Error::new(Span::call_site().into(), "Only enums are supported")
12+
.into_compile_error()
13+
.into()
14+
}
1115
};
1216

1317
let bevy_util_path = BevyManifest::default().get_path(crate::modules::BEVY_UTILS);

crates/bevy_ecs/macros/src/lib.rs

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ extern crate proc_macro;
22

33
mod component;
44

5-
use bevy_macro_utils::{derive_label, BevyManifest};
5+
use bevy_macro_utils::{derive_label, get_named_struct_fields, BevyManifest};
66
use proc_macro::TokenStream;
77
use proc_macro2::Span;
88
use quote::{format_ident, quote};
@@ -11,8 +11,7 @@ use syn::{
1111
parse_macro_input,
1212
punctuated::Punctuated,
1313
token::Comma,
14-
Data, DataStruct, DeriveInput, Field, Fields, GenericParam, Ident, Index, LitInt, Result,
15-
Token,
14+
DeriveInput, Field, GenericParam, Ident, Index, LitInt, Result, Token,
1615
};
1716

1817
struct AllTuples {
@@ -86,12 +85,9 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
8685
let ast = parse_macro_input!(input as DeriveInput);
8786
let ecs_path = bevy_ecs_path();
8887

89-
let named_fields = match &ast.data {
90-
Data::Struct(DataStruct {
91-
fields: Fields::Named(fields),
92-
..
93-
}) => &fields.named,
94-
_ => panic!("Expected a struct with named fields."),
88+
let named_fields = match get_named_struct_fields(&ast.data) {
89+
Ok(fields) => &fields.named,
90+
Err(e) => return e.into_compile_error().into(),
9591
};
9692

9793
let is_bundle = named_fields
@@ -304,12 +300,9 @@ static SYSTEM_PARAM_ATTRIBUTE_NAME: &str = "system_param";
304300
#[proc_macro_derive(SystemParam, attributes(system_param))]
305301
pub fn derive_system_param(input: TokenStream) -> TokenStream {
306302
let ast = parse_macro_input!(input as DeriveInput);
307-
let fields = match &ast.data {
308-
Data::Struct(DataStruct {
309-
fields: Fields::Named(fields),
310-
..
311-
}) => &fields.named,
312-
_ => panic!("Expected a struct with named fields."),
303+
let fields = match get_named_struct_fields(&ast.data) {
304+
Ok(fields) => &fields.named,
305+
Err(e) => return e.into_compile_error().into(),
313306
};
314307
let path = bevy_ecs_path();
315308

crates/bevy_macro_utils/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
extern crate proc_macro;
22

33
mod attrs;
4+
mod shape;
45
mod symbol;
56

67
pub use attrs::*;
8+
pub use shape::*;
79
pub use symbol::*;
810

911
use cargo_manifest::{DepsSet, Manifest};

crates/bevy_macro_utils/src/shape.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
use proc_macro::Span;
2+
use syn::{Data, DataStruct, Error, Fields, FieldsNamed};
3+
4+
/// Get the fields of a data structure if that structure is a struct with named fields;
5+
/// otherwise, return a compile error that points to the site of the macro invocation.
6+
pub fn get_named_struct_fields(data: &syn::Data) -> syn::Result<&FieldsNamed> {
7+
match data {
8+
Data::Struct(DataStruct {
9+
fields: Fields::Named(fields),
10+
..
11+
}) => Ok(fields),
12+
_ => Err(Error::new(
13+
// This deliberately points to the call site rather than the structure
14+
// body; marking the entire body as the source of the error makes it
15+
// impossible to figure out which `derive` has a problem.
16+
Span::call_site().into(),
17+
"Only structs with named fields are supported",
18+
)),
19+
}
20+
}

0 commit comments

Comments
 (0)