@@ -10,7 +10,7 @@ pub use symbol::*;
10
10
11
11
use proc_macro:: TokenStream ;
12
12
use proc_macro2:: { Span , TokenStream as TokenStream2 } ;
13
- use quote:: quote;
13
+ use quote:: { format_ident , quote} ;
14
14
use std:: { env, path:: PathBuf } ;
15
15
use syn:: spanned:: Spanned ;
16
16
use toml:: { map:: Map , Value } ;
@@ -110,6 +110,7 @@ impl BevyManifest {
110
110
/// in the form e.g. `#[system_label(..)]`.
111
111
#[ derive( Default ) ]
112
112
struct LabelAttrs {
113
+ intern : Option < Span > ,
113
114
ignore_fields : Option < Span > ,
114
115
}
115
116
@@ -132,10 +133,14 @@ impl LabelAttrs {
132
133
// Parse the argument/s to the attribute.
133
134
attr. parse_args_with ( |input : syn:: parse:: ParseStream | {
134
135
loop {
136
+ syn:: custom_keyword!( intern) ;
135
137
syn:: custom_keyword!( ignore_fields) ;
136
138
137
139
let next = input. lookahead1 ( ) ;
138
- if next. peek ( ignore_fields) {
140
+ if next. peek ( intern) {
141
+ let kw: intern = input. parse ( ) ?;
142
+ this. intern = Some ( kw. span ) ;
143
+ } else if next. peek ( ignore_fields) {
139
144
let kw: ignore_fields = input. parse ( ) ?;
140
145
this. ignore_fields = Some ( kw. span ) ;
141
146
} else {
@@ -171,9 +176,14 @@ pub fn derive_label(
171
176
Err ( e) => return e. into_compile_error ( ) . into ( ) ,
172
177
} ;
173
178
174
- derive_named_label ( input, & item_attrs, trait_path, attr_name)
175
- . unwrap_or_else ( syn:: Error :: into_compile_error)
176
- . into ( )
179
+ // We use entirely different derives for interned and named labels.
180
+ if item_attrs. intern . is_some ( ) {
181
+ derive_interned_label ( input, trait_path, attr_name)
182
+ } else {
183
+ derive_named_label ( input, & item_attrs, trait_path, attr_name)
184
+ }
185
+ . unwrap_or_else ( syn:: Error :: into_compile_error)
186
+ . into ( )
177
187
}
178
188
179
189
fn with_static_bound ( where_clause : Option < & syn:: WhereClause > ) -> syn:: WhereClause {
@@ -209,6 +219,13 @@ fn derive_named_label(
209
219
) ;
210
220
return Err ( syn:: Error :: new ( attr, err_msg) ) ;
211
221
}
222
+ if let Some ( attr) = all_field_attrs. intern {
223
+ let err_msg = format ! (
224
+ r#"`#[{attr_name}(intern)]` cannot be applied to fields individually:
225
+ try adding it to the struct declaration"#
226
+ ) ;
227
+ return Err ( syn:: Error :: new ( attr, err_msg) ) ;
228
+ }
212
229
// Structs must either be fieldless, or explicitly ignore the fields.
213
230
let ignore_fields = item_attrs. ignore_fields . is_some ( ) ;
214
231
if d. fields . is_empty ( ) || ignore_fields {
@@ -217,7 +234,11 @@ fn derive_named_label(
217
234
let as_str = quote ! { write!( f, #lit) } ;
218
235
( data, as_str)
219
236
} else {
220
- let err_msg = format ! ( "Labels cannot contain data, unless explicitly ignored with `#[{attr_name}(ignore_fields)]`" ) ;
237
+ let err_msg = format ! (
238
+ r#"Simple labels cannot contain data, unless the whole type is boxed
239
+ by marking the type with `#[{attr_name}(intern)]`.
240
+ Alternatively, you can make this label behave as if it were fieldless with `#[{attr_name}(ignore_fields)]`."#
241
+ ) ;
221
242
return Err ( syn:: Error :: new ( d. fields . span ( ) , err_msg) ) ;
222
243
}
223
244
}
@@ -245,7 +266,11 @@ fn derive_named_label(
245
266
let lit = format ! ( "{ident}::{}" , v. ident. clone( ) ) ;
246
267
fmt_arms. push ( quote ! { #i => { write!( f, #lit) } } ) ;
247
268
} else {
248
- let err_msg = format ! ( "Label variants cannot contain data, unless explicitly ignored with `#[{attr_name}(ignore_fields)]`" ) ;
269
+ let err_msg = format ! (
270
+ r#"Simple labels only allow unit variants -- more complex types must be boxed
271
+ by marking the whole type with `#[{attr_name}(intern)]`.
272
+ Alternatively, you can make the variant act fieldless using `#[{attr_name}(ignore_fields)]`."#
273
+ ) ;
249
274
return Err ( syn:: Error :: new ( v. fields . span ( ) , err_msg) ) ;
250
275
}
251
276
}
@@ -302,3 +327,62 @@ fn derive_named_label(
302
327
}
303
328
} )
304
329
}
330
+
331
+ fn derive_interned_label (
332
+ input : syn:: DeriveInput ,
333
+ trait_path : & syn:: Path ,
334
+ _attr_name : & str ,
335
+ ) -> syn:: Result < TokenStream2 > {
336
+ let manifest = BevyManifest :: default ( ) ;
337
+
338
+ let ident = input. ident ;
339
+ let ( impl_generics, ty_generics, where_clause) = input. generics . split_for_impl ( ) ;
340
+ let mut where_clause = with_static_bound ( where_clause) ;
341
+ where_clause. predicates . push (
342
+ syn:: parse2 ( quote ! {
343
+ Self : :: std:: clone:: Clone + :: std:: cmp:: Eq + :: std:: hash:: Hash + :: std:: fmt:: Debug
344
+ + :: std:: marker:: Send + :: std:: marker:: Sync
345
+ } )
346
+ . unwrap ( ) ,
347
+ ) ;
348
+
349
+ let is_generic = !input. generics . params . is_empty ( ) ;
350
+
351
+ let interner_type_path = {
352
+ let mut path = manifest. get_path ( "bevy_utils" ) ;
353
+ // If the type is generic, we have to store all monomorphizations
354
+ // in the same global due to Rust restrictions.
355
+ if is_generic {
356
+ path. segments . push ( format_ident ! ( "AnyInterner" ) . into ( ) ) ;
357
+ } else {
358
+ path. segments . push ( format_ident ! ( "Interner" ) . into ( ) ) ;
359
+ }
360
+ path
361
+ } ;
362
+ let interner_type_expr = if is_generic {
363
+ quote ! { #interner_type_path }
364
+ } else {
365
+ quote ! { #interner_type_path <#ident> }
366
+ } ;
367
+ let guard_type_path = {
368
+ let mut path = manifest. get_path ( "bevy_utils" ) ;
369
+ path. segments . push ( format_ident ! ( "InternGuard" ) . into ( ) ) ;
370
+ path
371
+ } ;
372
+ let interner_ident = format_ident ! ( "{}_INTERN" , ident. to_string( ) . to_uppercase( ) ) ;
373
+
374
+ Ok ( quote ! {
375
+ static #interner_ident : #interner_type_expr = #interner_type_path:: new( ) ;
376
+
377
+ impl #impl_generics #trait_path for #ident #ty_generics #where_clause {
378
+ #[ inline]
379
+ fn data( & self ) -> u64 {
380
+ #interner_ident . intern( self ) as u64
381
+ }
382
+ fn fmt( idx: u64 , f: & mut :: std:: fmt:: Formatter ) -> :: std:: fmt:: Result {
383
+ let val: #guard_type_path <Self > = #interner_ident . get( idx as usize ) . ok_or( :: std:: fmt:: Error ) ?;
384
+ :: std:: fmt:: Debug :: fmt( & * val, f)
385
+ }
386
+ }
387
+ } )
388
+ }
0 commit comments