1 | //! This crate is the `cpp` procedural macro implementation. It is useless |
2 | //! without the companion crates `cpp`, and `cpp_build`. |
3 | //! |
4 | //! For more information, see the [`cpp` crate module level |
5 | //! documentation](https://docs.rs/cpp). |
6 | #![recursion_limit = "128" ] |
7 | |
8 | #[macro_use ] |
9 | extern crate syn; |
10 | extern crate proc_macro; |
11 | use proc_macro2::Span; |
12 | |
13 | use cpp_common::{flags, kw, RustInvocation, FILE_HASH, LIB_NAME, MSVC_LIB_NAME, OUT_DIR, VERSION}; |
14 | use std::collections::HashMap; |
15 | use std::iter::FromIterator; |
16 | use syn::parse::Parser; |
17 | use syn::Ident; |
18 | |
19 | use byteorder::{BigEndian, ByteOrder, LittleEndian, ReadBytesExt}; |
20 | use lazy_static::lazy_static; |
21 | use quote::{quote, quote_spanned}; |
22 | use std::fs::File; |
23 | use std::io::{self, BufReader, Read, Seek, SeekFrom}; |
24 | |
25 | struct MetaData { |
26 | size: usize, |
27 | align: usize, |
28 | flags: u64, |
29 | } |
30 | impl MetaData { |
31 | fn has_flag(&self, f: u32) -> bool { |
32 | self.flags & (1 << f) != 0 |
33 | } |
34 | } |
35 | |
36 | lazy_static! { |
37 | static ref METADATA: HashMap<u64, Vec<MetaData>> = { |
38 | let file = match open_lib_file() { |
39 | Ok(x) => x, |
40 | Err(e) => { |
41 | #[cfg (not(feature = "docs-only" ))] |
42 | panic!( |
43 | r#" |
44 | -- rust-cpp fatal error -- |
45 | |
46 | Failed to open the target library file. |
47 | NOTE: Did you make sure to add the rust-cpp build script? |
48 | {}"# , |
49 | e |
50 | ); |
51 | #[cfg (feature = "docs-only" )] |
52 | { |
53 | eprintln!("Error while opening target library: {}" , e); |
54 | return Default::default(); |
55 | }; |
56 | } |
57 | }; |
58 | |
59 | read_metadata(file).expect( |
60 | r#" |
61 | -- rust-cpp fatal error -- |
62 | |
63 | I/O error while reading metadata from target library file."# , |
64 | ) |
65 | }; |
66 | } |
67 | |
68 | /// NOTE: This panics when it can produce a better error message |
69 | fn read_metadata(file: File) -> io::Result<HashMap<u64, Vec<MetaData>>> { |
70 | let mut file = BufReader::new(file); |
71 | let end = { |
72 | const AUTO_KEYWORD: &[&[u8]] = &[&cpp_common::STRUCT_METADATA_MAGIC]; |
73 | let aut = aho_corasick::AhoCorasick::new(AUTO_KEYWORD).unwrap(); |
74 | let found = aut.stream_find_iter(&mut file).next().expect( |
75 | r#" |
76 | -- rust-cpp fatal error -- |
77 | |
78 | Struct metadata not present in target library file. |
79 | NOTE: Double-check that the version of cpp_build and cpp_macros match"# , |
80 | )?; |
81 | found.end() |
82 | }; |
83 | file.seek(SeekFrom::Start(end as u64))?; |
84 | |
85 | // Read & convert the version buffer into a string & compare with our |
86 | // version. |
87 | let mut version_buf = [0; 16]; |
88 | file.read_exact(&mut version_buf)?; |
89 | let version = |
90 | version_buf.iter().take_while(|b| **b != b' \0' ).map(|b| *b as char).collect::<String>(); |
91 | |
92 | assert_eq!( |
93 | version, VERSION, |
94 | r#" |
95 | -- rust-cpp fatal error -- |
96 | |
97 | Version mismatch between cpp_macros and cpp_build for same crate."# |
98 | ); |
99 | let endianness_check = file.read_u64::<LittleEndian>()?; |
100 | if endianness_check == 0xffef { |
101 | read_metadata_rest::<LittleEndian>(file) |
102 | } else if endianness_check == 0xefff000000000000 { |
103 | read_metadata_rest::<BigEndian>(file) |
104 | } else { |
105 | panic!("Endianness check value matches neither little nor big endian." ); |
106 | } |
107 | } |
108 | |
109 | fn read_metadata_rest<E: ByteOrder>( |
110 | mut file: BufReader<File>, |
111 | ) -> io::Result<HashMap<u64, Vec<MetaData>>> { |
112 | let length: u64 = file.read_u64::<E>()?; |
113 | let mut metadata: HashMap> = HashMap::new(); |
114 | for _ in 0..length { |
115 | let hash: u64 = file.read_u64::<E>()?; |
116 | let size: usize = file.read_u64::<E>()? as usize; |
117 | let align: usize = file.read_u64::<E>()? as usize; |
118 | let flags: u64 = file.read_u64::<E>()?; |
119 | |
120 | metadata.entry(hash).or_insert_with(default:Vec::new).push(MetaData { size, align, flags }); |
121 | } |
122 | Ok(metadata) |
123 | } |
124 | |
125 | /// Try to open a file handle to the lib file. This is used to scan it for |
126 | /// metadata. We check both `MSVC_LIB_NAME` and `LIB_NAME`, in case we are on |
127 | /// or are targeting Windows. |
128 | fn open_lib_file() -> io::Result<File> { |
129 | if let Ok(file: File) = File::open(path:OUT_DIR.join(MSVC_LIB_NAME)) { |
130 | Ok(file) |
131 | } else { |
132 | File::open(path:OUT_DIR.join(LIB_NAME)) |
133 | } |
134 | } |
135 | |
136 | fn find_all_rust_macro( |
137 | input: syn::parse::ParseStream, |
138 | ) -> Result<Vec<RustInvocation>, syn::parse::Error> { |
139 | let mut r = Vec::<RustInvocation>::new(); |
140 | while !input.is_empty() { |
141 | if input.peek(kw::rust) { |
142 | if let Ok(ri) = input.parse::<RustInvocation>() { |
143 | r.push(ri); |
144 | } |
145 | } else if input.peek(syn::token::Brace) { |
146 | let c; |
147 | braced!(c in input); |
148 | r.extend(find_all_rust_macro(&c)?); |
149 | } else if input.peek(syn::token::Paren) { |
150 | let c; |
151 | parenthesized!(c in input); |
152 | r.extend(find_all_rust_macro(&c)?); |
153 | } else if input.peek(syn::token::Bracket) { |
154 | let c; |
155 | bracketed!(c in input); |
156 | r.extend(find_all_rust_macro(&c)?); |
157 | } else { |
158 | input.parse::<proc_macro2::TokenTree>()?; |
159 | } |
160 | } |
161 | Ok(r) |
162 | } |
163 | |
164 | /// Find the occurrence of the `stringify!` macro within the macro derive |
165 | fn extract_original_macro(input: &syn::DeriveInput) -> Option<proc_macro2::TokenStream> { |
166 | #[derive (Default)] |
167 | struct Finder(Option<proc_macro2::TokenStream>); |
168 | impl<'ast> syn::visit::Visit<'ast> for Finder { |
169 | fn visit_macro(&mut self, mac: &'ast syn::Macro) { |
170 | if mac.path.segments.len() == 1 && mac.path.segments[0].ident == "stringify" { |
171 | self.0 = Some(mac.tokens.clone()); |
172 | } |
173 | } |
174 | } |
175 | let mut f: Finder = Finder::default(); |
176 | syn::visit::visit_derive_input(&mut f, node:input); |
177 | f.0 |
178 | } |
179 | |
180 | #[proc_macro_derive (__cpp_internal_closure)] |
181 | #[allow (clippy::cognitive_complexity)] |
182 | pub fn expand_internal(input: proc_macro::TokenStream) -> proc_macro::TokenStream { |
183 | assert_eq!( |
184 | env!("CARGO_PKG_VERSION" ), |
185 | VERSION, |
186 | "Internal Error: mismatched cpp_common and cpp_macros versions" |
187 | ); |
188 | |
189 | // Parse the macro input |
190 | let input = extract_original_macro(&parse_macro_input!(input as syn::DeriveInput)).unwrap(); |
191 | |
192 | let closure = match syn::parse2::<cpp_common::Closure>(input) { |
193 | Ok(x) => x, |
194 | Err(err) => return err.to_compile_error().into(), |
195 | }; |
196 | |
197 | // Get the size data compiled by the build macro |
198 | let size_data = match METADATA.get(&closure.sig.name_hash()) { |
199 | Some(x) => x, |
200 | None => { |
201 | #[cfg (not(feature = "docs-only" ))] |
202 | return quote!(compile_error! { |
203 | r#"This cpp! macro is not found in the library's rust-cpp metadata. |
204 | NOTE: Only cpp! macros found directly in the program source will be parsed - |
205 | NOTE: They cannot be generated by macro expansion."# }) |
206 | .into(); |
207 | #[cfg (feature = "docs-only" )] |
208 | { |
209 | return quote! { |
210 | macro_rules! __cpp_closure_impl { |
211 | ($($x:tt)*) => { panic!("docs-only" ); } |
212 | } |
213 | } |
214 | .into(); |
215 | }; |
216 | } |
217 | }; |
218 | |
219 | let mut extern_params = Vec::new(); |
220 | let mut tt_args = Vec::new(); |
221 | let mut call_args = Vec::new(); |
222 | for (i, capture) in closure.sig.captures.iter().enumerate() { |
223 | let written_name = &capture.name; |
224 | let span = written_name.span(); |
225 | let mac_name = Ident::new(&format!("var_ {}" , written_name), span); |
226 | let mac_cty = Ident::new(&format!("cty_ {}" , written_name), span); |
227 | |
228 | // Generate the assertion to check that the size and align of the types |
229 | // match before calling. |
230 | let MetaData { size, align, .. } = size_data[i + 1]; |
231 | let sizeof_msg = format!( |
232 | "size_of for argument ` {}` does not match between c++ and \ |
233 | rust" , |
234 | &capture.name |
235 | ); |
236 | let alignof_msg = format!( |
237 | "align_of for argument ` {}` does not match between c++ and \ |
238 | rust" , |
239 | &capture.name |
240 | ); |
241 | let assertion = quote_spanned! {span=> |
242 | // Perform a compile time check that the sizes match. This should be |
243 | // a no-op. |
244 | if false { |
245 | #[allow(clippy::transmute_num_to_bytes)] |
246 | ::core::mem::transmute::<_, [u8; #size]>( |
247 | ::core::ptr::read(&$#mac_name)); |
248 | } |
249 | |
250 | // NOTE: Both of these calls should be dead code in opt builds. |
251 | #[allow(clippy::size_of_ref)] { assert!(::core::mem::size_of_val(&$#mac_name) == #size, #sizeof_msg); }; |
252 | assert!(::core::mem::align_of_val(&$#mac_name) == #align, |
253 | #alignof_msg); |
254 | }; |
255 | |
256 | let mb_mut = if capture.mutable { quote_spanned!(span=> mut) } else { quote!() }; |
257 | let ptr = if capture.mutable { |
258 | quote_spanned!(span=> *mut) |
259 | } else { |
260 | quote_spanned!(span=> *const) |
261 | }; |
262 | |
263 | let arg_name = Ident::new(&format!("arg_ {}" , written_name), span); |
264 | |
265 | extern_params.push(quote_spanned!(span=> #arg_name : #ptr u8)); |
266 | |
267 | tt_args.push(quote_spanned!(span=> #mb_mut $#mac_name : ident as $#mac_cty : tt)); |
268 | |
269 | call_args.push(quote_spanned!(span=> { |
270 | #assertion |
271 | &#mb_mut $#mac_name as #ptr _ as #ptr u8 |
272 | })); |
273 | } |
274 | |
275 | let extern_name = closure.sig.extern_name(); |
276 | let ret_ty = &closure.sig.ret; |
277 | let MetaData { size: ret_size, align: ret_align, flags } = size_data[0]; |
278 | let is_void = closure.sig.cpp == "void" ; |
279 | |
280 | let decl = if is_void { |
281 | quote! { |
282 | fn #extern_name(#(#extern_params),*); |
283 | } |
284 | } else { |
285 | quote! { |
286 | fn #extern_name(#(#extern_params,)* _result: *mut #ret_ty); |
287 | } |
288 | }; |
289 | |
290 | let call = if is_void { |
291 | assert!(ret_size == 0, "`void` should have a size of 0!" ); |
292 | quote! { |
293 | #extern_name(#(#call_args),*); |
294 | #[allow(clippy::useless_transmute)] |
295 | ::core::mem::transmute::<(), (#ret_ty)>(()) |
296 | } |
297 | } else { |
298 | // static assert that the size and alignement are the same |
299 | let assert_size = quote! { |
300 | if false { |
301 | const _assert_size: [(); #ret_size] = [(); ::core::mem::size_of::<#ret_ty>()]; |
302 | const _assert_align: [(); #ret_align] = [(); ::core::mem::align_of::<#ret_ty>()]; |
303 | } |
304 | }; |
305 | quote!( |
306 | #assert_size |
307 | let mut result = ::core::mem::MaybeUninit::<#ret_ty>::uninit(); |
308 | #extern_name(#(#call_args,)* result.as_mut_ptr()); |
309 | result.assume_init() |
310 | ) |
311 | }; |
312 | |
313 | let input = proc_macro2::TokenStream::from_iter([closure.body].iter().cloned()); |
314 | let rust_invocations = find_all_rust_macro.parse2(input).expect("rust! macro" ); |
315 | let init_callbacks = if !rust_invocations.is_empty() { |
316 | let rust_cpp_callbacks = |
317 | Ident::new(&format!("rust_cpp_callbacks {}" , *FILE_HASH), Span::call_site()); |
318 | let offset = (flags >> 32) as isize; |
319 | let callbacks: Vec<Ident> = rust_invocations.iter().map(|x| x.id.clone()).collect(); |
320 | quote! { |
321 | use ::std::sync::Once; |
322 | static INIT_INVOCATIONS: Once = Once::new(); |
323 | INIT_INVOCATIONS.call_once(|| { |
324 | // #rust_cpp_callbacks is in fact an array. Since we cannot represent it in rust, |
325 | // we just are gonna take the pointer to it can offset from that. |
326 | extern "C" { |
327 | #[no_mangle] |
328 | static mut #rust_cpp_callbacks: *const ::std::os::raw::c_void; |
329 | } |
330 | let callbacks_array : *mut *const ::std::os::raw::c_void = &mut #rust_cpp_callbacks; |
331 | let mut offset = #offset; |
332 | #( |
333 | offset += 1; |
334 | *callbacks_array.offset(offset - 1) = #callbacks as *const ::std::os::raw::c_void; |
335 | )* |
336 | }); |
337 | } |
338 | } else { |
339 | quote!() |
340 | }; |
341 | |
342 | let result = quote! { |
343 | extern "C" { |
344 | #decl |
345 | } |
346 | |
347 | macro_rules! __cpp_closure_impl { |
348 | (#(#tt_args),*) => { |
349 | { |
350 | #init_callbacks |
351 | #call |
352 | } |
353 | } |
354 | } |
355 | }; |
356 | result.into() |
357 | } |
358 | |
359 | #[proc_macro_derive (__cpp_internal_class)] |
360 | pub fn expand_wrap_class(input: proc_macro::TokenStream) -> proc_macro::TokenStream { |
361 | // Parse the macro input |
362 | let input = extract_original_macro(&parse_macro_input!(input as syn::DeriveInput)).unwrap(); |
363 | |
364 | let class = match ::syn::parse2::<cpp_common::Class>(input) { |
365 | Ok(x) => x, |
366 | Err(err) => return err.to_compile_error().into(), |
367 | }; |
368 | |
369 | let hash = class.name_hash(); |
370 | let class_name = class.name.clone(); |
371 | |
372 | // Get the size data compiled by the build macro |
373 | let size_data = match METADATA.get(&hash) { |
374 | Some(x) => x, |
375 | None => { |
376 | #[cfg (not(feature = "docs-only" ))] |
377 | return quote!(compile_error! { |
378 | r#"This cpp_class! macro is not found in the library's rust-cpp metadata. |
379 | NOTE: Only cpp_class! macros found directly in the program source will be parsed - |
380 | NOTE: They cannot be generated by macro expansion."# }) |
381 | .into(); |
382 | #[cfg (feature = "docs-only" )] |
383 | { |
384 | let mut result = quote! { |
385 | #[doc(hidden)] |
386 | impl ::cpp::CppTrait for #class_name { |
387 | type BaseType = usize; |
388 | const ARRAY_SIZE: usize = 1; |
389 | const CPP_TYPE: &'static str = stringify!(#class_name); |
390 | } |
391 | #[doc = "NOTE: this trait will only be enabled if the C++ underlying type is trivially copyable" ] |
392 | impl ::core::marker::Copy for #class_name { } |
393 | #[doc = "NOTE: this trait will only be enabled if the C++ underlying type is copyable" ] |
394 | impl ::core::clone::Clone for #class_name { fn clone(&self) -> Self { panic!("docs-only" ) } } |
395 | #[doc = "NOTE: this trait will only be enabled if the C++ underlying type is default constructible" ] |
396 | impl ::core::default::Default for #class_name { fn default() -> Self { panic!("docs-only" ) } } |
397 | }; |
398 | if class.derives("PartialEq" ) { |
399 | result = quote! { #result |
400 | impl ::core::cmp::PartialEq for #class_name { |
401 | fn eq(&self, other: &#class_name) -> bool { panic!("docs-only" ) } |
402 | } |
403 | }; |
404 | } |
405 | if class.derives("PartialOrd" ) { |
406 | result = quote! { #result |
407 | impl ::core::cmp::PartialOrd for #class_name { |
408 | fn partial_cmp(&self, other: &#class_name) -> ::core::option::Option<::core::cmp::Ordering> { |
409 | panic!("docs-only" ) |
410 | } |
411 | } |
412 | }; |
413 | } |
414 | if class.derives("Ord" ) { |
415 | result = quote! { #result |
416 | impl ::core::cmp::Ord for #class_name { |
417 | fn cmp(&self, other: &#class_name) -> ::core::cmp::Ordering { |
418 | panic!("docs-only" ) |
419 | } |
420 | } |
421 | }; |
422 | } |
423 | return result.into(); |
424 | }; |
425 | } |
426 | }; |
427 | |
428 | let (size, align) = (size_data[0].size, size_data[0].align); |
429 | |
430 | let base_type = match align { |
431 | 1 => quote!(u8), |
432 | 2 => quote!(u16), |
433 | 4 => quote!(u32), |
434 | 8 => quote!(u64), |
435 | _ => panic!("unsupported alignment" ), |
436 | }; |
437 | |
438 | let destructor_name = Ident::new(&format!("__cpp_destructor_ {}" , hash), Span::call_site()); |
439 | let copyctr_name = Ident::new(&format!("__cpp_copy_ {}" , hash), Span::call_site()); |
440 | let defaultctr_name = Ident::new(&format!("__cpp_default_ {}" , hash), Span::call_site()); |
441 | |
442 | let mut result = quote! { |
443 | #[doc(hidden)] |
444 | impl ::cpp::CppTrait for #class_name { |
445 | type BaseType = #base_type; |
446 | const ARRAY_SIZE: usize = #size / #align; |
447 | const CPP_TYPE: &'static str = stringify!(#class_name); |
448 | } |
449 | }; |
450 | if !size_data[0].has_flag(flags::IS_TRIVIALLY_DESTRUCTIBLE) { |
451 | result = quote! { #result |
452 | impl ::core::ops::Drop for #class_name { |
453 | fn drop(&mut self) { |
454 | unsafe { |
455 | extern "C" { fn #destructor_name(_: *mut #class_name); } |
456 | #destructor_name(&mut *self); |
457 | } |
458 | } |
459 | } |
460 | }; |
461 | }; |
462 | |
463 | if size_data[0].has_flag(flags::IS_COPY_CONSTRUCTIBLE) { |
464 | if !size_data[0].has_flag(flags::IS_TRIVIALLY_COPYABLE) && !class.derives("Copy" ) { |
465 | let call_construct = quote!( |
466 | let mut result = ::core::mem::MaybeUninit::<Self>::uninit(); |
467 | #copyctr_name(& *self, result.as_mut_ptr()); |
468 | result.assume_init() |
469 | ); |
470 | result = quote! { #result |
471 | impl ::core::clone::Clone for #class_name { |
472 | fn clone(&self) -> Self { |
473 | unsafe { |
474 | extern "C" { fn #copyctr_name(src: *const #class_name, dst: *mut #class_name); } |
475 | #call_construct |
476 | } |
477 | } |
478 | } |
479 | }; |
480 | } else { |
481 | result = quote! { #result |
482 | impl ::core::marker::Copy for #class_name { } |
483 | impl ::core::clone::Clone for #class_name { |
484 | fn clone(&self) -> Self { *self } |
485 | } |
486 | }; |
487 | }; |
488 | } else if class.derives("Clone" ) { |
489 | panic!("C++ class is not copyable" ); |
490 | } |
491 | |
492 | if size_data[0].has_flag(flags::IS_DEFAULT_CONSTRUCTIBLE) { |
493 | let call_construct = quote!( |
494 | let mut result = ::core::mem::MaybeUninit::<Self>::uninit(); |
495 | #defaultctr_name(result.as_mut_ptr()); |
496 | result.assume_init() |
497 | ); |
498 | result = quote! { #result |
499 | impl ::core::default::Default for #class_name { |
500 | fn default() -> Self { |
501 | unsafe { |
502 | extern "C" { fn #defaultctr_name(dst: *mut #class_name); } |
503 | #call_construct |
504 | } |
505 | } |
506 | } |
507 | }; |
508 | } else if class.derives("Default" ) { |
509 | panic!("C++ class is not default constructible" ); |
510 | } |
511 | |
512 | if class.derives("PartialEq" ) { |
513 | let equal_name = Ident::new(&format!("__cpp_equal_ {}" , hash), Span::call_site()); |
514 | result = quote! { #result |
515 | impl ::core::cmp::PartialEq for #class_name { |
516 | fn eq(&self, other: &#class_name) -> bool { |
517 | unsafe { |
518 | extern "C" { fn #equal_name(a: *const #class_name, b: *const #class_name) -> bool; } |
519 | #equal_name(& *self, other) |
520 | } |
521 | } |
522 | } |
523 | }; |
524 | } |
525 | if class.derives("PartialOrd" ) { |
526 | let compare_name = Ident::new(&format!("__cpp_compare_ {}" , hash), Span::call_site()); |
527 | let f = |func, cmp| { |
528 | quote! { |
529 | fn #func(&self, other: &#class_name) -> bool { |
530 | unsafe { |
531 | extern "C" { fn #compare_name(a: *const #class_name, b: *const #class_name, cmp : i32) -> i32; } |
532 | #compare_name(& *self, other, #cmp) != 0 |
533 | } |
534 | } |
535 | } |
536 | }; |
537 | let lt = f(quote! {lt}, -2); |
538 | let gt = f(quote! {gt}, 2); |
539 | let le = f(quote! {le}, -1); |
540 | let ge = f(quote! {ge}, 1); |
541 | result = quote! { #result |
542 | impl ::core::cmp::PartialOrd for #class_name { |
543 | #lt #gt #le #ge |
544 | |
545 | fn partial_cmp(&self, other: &#class_name) -> ::core::option::Option<::core::cmp::Ordering> { |
546 | use ::core::cmp::Ordering; |
547 | unsafe { |
548 | extern "C" { fn #compare_name(a: *const #class_name, b: *const #class_name, cmp : i32) -> i32; } |
549 | ::core::option::Option::Some(match #compare_name(& *self, other, 0) { |
550 | -1 => Ordering::Less, |
551 | 0 => Ordering::Equal, |
552 | 1 => Ordering::Greater, |
553 | _ => panic!() |
554 | }) |
555 | } |
556 | } |
557 | } |
558 | }; |
559 | } |
560 | if class.derives("Ord" ) { |
561 | let compare_name = Ident::new(&format!("__cpp_compare_ {}" , hash), Span::call_site()); |
562 | result = quote! { #result |
563 | impl ::core::cmp::Ord for #class_name { |
564 | fn cmp(&self, other: &#class_name) -> ::core::cmp::Ordering { |
565 | unsafe { |
566 | use ::core::cmp::Ordering; |
567 | extern "C" { fn #compare_name(a: *const #class_name, b: *const #class_name, cmp : i32) -> i32; } |
568 | match #compare_name(& *self, other, 0) { |
569 | -1 => Ordering::Less, |
570 | 0 => Ordering::Equal, |
571 | 1 => Ordering::Greater, |
572 | _ => panic!() |
573 | } |
574 | } |
575 | } |
576 | } |
577 | }; |
578 | } |
579 | |
580 | if class.derives("Hash" ) { |
581 | panic!("Deriving from Hash is not implemented" ) |
582 | }; |
583 | if class.derives("Debug" ) { |
584 | panic!("Deriving from Debug is not implemented" ) |
585 | }; |
586 | |
587 | result.into() |
588 | } |
589 | |