├── .gitignore ├── macro ├── src │ ├── lib.rs │ ├── expand.rs │ └── vtable.rs └── Cargo.toml ├── Cargo.toml ├── src └── lib.rs └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /macro/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | 3 | pub(crate) mod expand; 4 | pub(crate) mod vtable; 5 | 6 | #[proc_macro_error::proc_macro_error] 7 | #[proc_macro_attribute] 8 | pub fn vtable(attr: TokenStream, item: TokenStream) -> TokenStream { 9 | vtable::make_vtable(attr, item) 10 | } 11 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cpp-class" 3 | version = "0.1.0" 4 | authors = ["ZOTTCE "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | cpp-class-macro = { path = "macro" } 9 | link-cplusplus = { version = "1.0", features = ["libstdc++"] } 10 | 11 | [workspace] 12 | members = ["macro"] 13 | -------------------------------------------------------------------------------- /macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cpp-class-macro" 3 | version = "0.1.0" 4 | authors = ["ZOTTCE "] 5 | edition = "2018" 6 | 7 | [lib] 8 | proc-macro = true 9 | 10 | [dependencies] 11 | syn = { version = "1.0", features = ["full", "extra-traits"] } 12 | quote = "1.0" 13 | proc-macro-error = "1.0.4" 14 | proc-macro-crate = "1.0.0" 15 | proc-macro2 = "1.0" 16 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub use cpp_class_macro::*; 2 | 3 | #[repr(C)] 4 | pub struct GenericTable { 5 | pub offset: isize, 6 | pub type_info: &'static Ty, 7 | pub vtable: Table, 8 | } 9 | 10 | #[repr(C)] 11 | pub struct BaseTypeInfo { 12 | pub vtable: &'static u8, 13 | pub name: *const u8, 14 | } 15 | 16 | unsafe impl Send for BaseTypeInfo {} 17 | unsafe impl Sync for BaseTypeInfo {} 18 | 19 | #[repr(C)] 20 | pub struct Base { 21 | pub base: &'static BaseTypeInfo, 22 | pub offset_flags: usize, 23 | } 24 | 25 | #[repr(C)] 26 | pub struct MultipleBasesTypeInfo { 27 | pub vtable: &'static u8, 28 | pub name: *const u8, 29 | pub flags: u32, 30 | pub bases_count: u32, 31 | pub bases: [Base; COUNT], 32 | } 33 | 34 | #[repr(C)] 35 | pub struct CppTi { 36 | base_vtable: usize, 37 | ti: usize, 38 | pub vtable: u8, 39 | } 40 | 41 | unsafe impl Send for MultipleBasesTypeInfo {} 42 | unsafe impl Sync for MultipleBasesTypeInfo {} 43 | 44 | extern "C" { 45 | #[link_name = "_ZTVN10__cxxabiv121__vmi_class_type_infoE"] 46 | pub static vmi_class_type_info: CppTi; 47 | 48 | #[link_name = "_ZTVN10__cxxabiv117__class_type_infoE"] 49 | pub static class_type_info: CppTi; 50 | } 51 | 52 | // _ZTVN10__cxxabiv121__vmi_class_type_infoE@@CXXABI_1.3 53 | // _ZTVN10__cxxabiv117__class_type_infoE@@CXXABI_1.3 54 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## cpp-class 2 | adds ability to create structs and traits compatible with c++ virtual classes. 3 | 4 | currently supports only multiple inheritance classes without any data. only virtual functions. 5 | 6 | gcc only? 7 | 8 | ## example 9 | this has been written to support ragemp cpp sdk to develop plugins. 10 | 11 | sdk repo at: https://github.com/ragemultiplayer/ragemp-cppsdk 12 | 13 | ```c++ 14 | class IEventHandler 15 | { 16 | public: 17 | virtual IEntityHandler *GetEntityHandler(); 18 | virtual IPlayerHandler *GetPlayerHandler(); 19 | virtual IVehicleHandler *GetVehicleHandler(); 20 | virtual IColshapeHandler *GetColshapeHandler(); 21 | virtual ICheckpointHandler *GetCheckpointHandler(); 22 | virtual IMarkerHandler *GetMarkerHandler(); 23 | virtual IPickupHandler *GetPickupHandler(); 24 | virtual ITickHandler *GetTickHandler(); 25 | virtual ILocalEventHandler *GetLocalEventHandler(); 26 | virtual IConnectionHandler *GetConnectionHandler(); 27 | virtual IDebugHandler *GetDebugHandler(); 28 | virtual IServerHandler *GetServerHandler(); 29 | virtual IRpcHandler *GetRpcHandler(); 30 | }; 31 | 32 | class ITickHandler 33 | { 34 | public: 35 | virtual void Tick(); 36 | }; 37 | 38 | // it would be like 39 | class EventHandler: public IEventHandler, public ITickHandler { 40 | public: 41 | virtual ITickHandler *GetTickHandler() { 42 | return this; 43 | } 44 | 45 | // ..... 46 | 47 | virtual void Tick() { std::cout << "tick!!!!" << std::endl; } 48 | } 49 | 50 | RAGE_API rage::IPlugin *InitializePlugin(rage::IMultiplayer *mp) 51 | { 52 | mp->AddEventHandler(new EventHandler); 53 | return new rage::IPlugin; 54 | } 55 | ``` 56 | 57 | rust version 58 | 59 | ```rust 60 | use cpp_class::vtable; 61 | 62 | // supports this: 63 | // __cxxabiv1::__vmi_class_type_info 64 | // __cxxabiv1::__class_type_info 65 | #[vtable] 66 | pub mod handler { 67 | 68 | // list of parents, it should be 2+ 69 | // your struct can contain any data with any repr 70 | #[vtable::derive(IEventHandler, ITickHandler)] 71 | pub struct Handler { 72 | pub my_data: String, 73 | pub counter: u32, 74 | } 75 | 76 | // default abi is fastcall 77 | // type_name is a name of a class name defined at headers (or in a executable) (nul char is append by the macro) 78 | // no data 79 | #[vtable::virtual_class(abi = fastcall, type_name = "N4rage13IEventHandlerE")] 80 | trait IEventHandler { 81 | // no default impl at this moment 82 | // also destructors are not supported 83 | // returns bool because the return value is not used by ragemp (simple check if it is not 0) 84 | fn entity_handler(&mut self) -> bool; 85 | fn player_handler(&mut self) -> bool; 86 | fn vehicle_handler(&mut self) -> bool; 87 | fn colshape_handler(&mut self) -> bool; 88 | fn checkpoint_handler(&mut self) -> bool; 89 | fn unk_0(&mut self) -> bool; 90 | fn unk_1(&mut self) -> bool; 91 | fn tick_handler(&mut self) -> bool; 92 | fn local_event_handler(&mut self) -> bool; 93 | fn connection_handler(&mut self) -> bool; 94 | fn debug_handler(&mut self) -> bool; 95 | fn server_handler(&mut self) -> bool; 96 | fn rpc_handler(&mut self) -> bool; 97 | } 98 | 99 | #[vtable::virtual_class(abi = fastcall, type_name = "N4rage12ITickHandlerE")] 100 | trait ITickHandler { 101 | fn tick(&mut self); 102 | } 103 | 104 | impl IEventHandler for Handler { 105 | fn tick_handler(&mut self) -> bool { 106 | true 107 | } 108 | 109 | // ... 110 | } 111 | 112 | impl ITickHandler for Handler { 113 | fn tick(&mut self) { 114 | println!("tick! my_data {:?} prev_count {}", self.my_data, self.counter); 115 | self.counter += 1; 116 | println!("new {}", self.counter); 117 | } 118 | } 119 | } 120 | 121 | // definition of rage::PluginManager is omitted 122 | #[no_mangle] 123 | pub extern "C" fn InitializePlugin(mp: *mut rage::PluginManager) -> u64 { 124 | let object = handler::Handler { 125 | my_data: String::from("what?"), 126 | counter: 0, 127 | }; 128 | 129 | // cpp_class::vtable generates make_boxed and from_boxed functions 130 | // from_boxed should be used to destroy an object 131 | // returns `RefHandler` pointer that contains vtables and points at the start 132 | let raw = handler::make_boxed(object); 133 | 134 | unsafe { 135 | ((*(*mp).vftable).add_event_handler)(mp, raw as *mut _); 136 | } 137 | 138 | return 1; 139 | } 140 | 141 | ``` 142 | -------------------------------------------------------------------------------- /macro/src/expand.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | 4 | use crate::vtable::{Attrs, VTableDefinition}; 5 | 6 | pub fn expand(attrs: Attrs, vtable: VTableDefinition) -> TokenStream { 7 | let name = &vtable.module.ident; 8 | let vis = &vtable.module.vis; 9 | let ref_struct = make_ref_struct(&vtable); 10 | let vtables = make_vtables(&attrs, &vtable); 11 | let mod_items = make_mod_items(&vtable); 12 | let ti_structs = make_typeinfo_structs(&attrs, &vtable); 13 | let utils = make_utils(&vtable); 14 | 15 | quote::quote! { 16 | #vis mod #name { 17 | #ref_struct 18 | #vtables 19 | #mod_items 20 | #ti_structs 21 | #utils 22 | } 23 | } 24 | } 25 | 26 | fn make_vtables(_attrs: &Attrs, vtable: &VTableDefinition) -> TokenStream { 27 | let krate = crate_name("cpp-class"); 28 | let child = quote::format_ident!("Ref{}", vtable.child.ident); 29 | // let base_ti_vtable = attrs.known_tables[1]; 30 | let mut items = Vec::new(); 31 | 32 | for (offset, base) in vtable.bases.iter().enumerate() { 33 | let abi = &base.abi; 34 | let offset = offset as isize; 35 | let mod_ident = quote::format_ident!("__{}", &base.ident); 36 | let base_ident = &base.ident; 37 | 38 | let mut table_fields = Vec::new(); 39 | let mut funcs = Vec::new(); 40 | 41 | for func in &base.funcs { 42 | let fn_name = &func.ident; 43 | let args = func.inputs.iter().skip(1); 44 | let retval = &func.output; 45 | 46 | let with_offset = if offset > 0 { 47 | quote! { 48 | this.offset(- ( #offset * ::std::mem::size_of::() as isize )) 49 | } 50 | } else { 51 | quote! { 52 | this 53 | } 54 | }; 55 | 56 | let call_input = func 57 | .inputs 58 | .iter() 59 | .map(|arg| match arg { 60 | syn::FnArg::Typed(pat) => Some(&pat.pat), 61 | _ => None, 62 | }) 63 | .flatten(); 64 | 65 | let f = quote! { 66 | pub unsafe extern #abi fn #fn_name(this: *mut u8, #(#args),*) #retval { 67 | let this = #with_offset as *mut super::#child; 68 | 69 | (*this).body.#fn_name(#(#call_input),*) 70 | } 71 | }; 72 | 73 | let args = func.inputs.iter().skip(1); 74 | 75 | let table_field = quote! { 76 | pub #fn_name: unsafe extern #abi fn(this: *mut u8, #(#args),*) #retval, 77 | }; 78 | 79 | table_fields.push(table_field); 80 | funcs.push(f); 81 | } 82 | 83 | let type_name = byte_str(base.type_name.value(), proc_macro2::Span::call_site()); 84 | 85 | let item = quote! { 86 | #[allow(non_snake_case)] 87 | mod #mod_ident { 88 | use super::#base_ident; 89 | 90 | #[allow(non_camel_case_types)] 91 | pub struct vtable { 92 | #(#table_fields)* 93 | } 94 | 95 | pub static TYPE_INFO: #krate::BaseTypeInfo = #krate::BaseTypeInfo { 96 | // vtable: #base_ti_vtable, 97 | vtable: unsafe { &#krate::class_type_info.vtable }, 98 | name: #type_name.as_ptr(), 99 | }; 100 | 101 | #(#funcs)* 102 | } 103 | }; 104 | 105 | items.push(item); 106 | } 107 | 108 | quote! { 109 | #(#items)* 110 | } 111 | } 112 | 113 | fn make_ref_struct(vtable: &VTableDefinition) -> TokenStream { 114 | let child = &vtable.child.ident; 115 | let ref_child = quote::format_ident!("Ref{}", vtable.child.ident); 116 | let global_vtable = quote::format_ident!("__{}_VTABLE", vtable.child.ident); 117 | 118 | let tables = vtable.child.parents.iter().enumerate().map(|(idx, _)| { 119 | let field = quote::format_ident!("vtable_{}", idx); 120 | quote! { #field: *const (), } 121 | }); 122 | 123 | let table_ptrs = vtable.child.parents.iter().enumerate().map(|(idx, _)| { 124 | let field = quote::format_ident!("vtable_{}", idx); 125 | 126 | quote! { 127 | #field: ::std::ptr::addr_of!(#global_vtable.#field.vtable) as *const _, 128 | } 129 | }); 130 | 131 | quote! { 132 | #[repr(C)] 133 | pub struct #ref_child { 134 | #(#tables)* 135 | body: #child, 136 | } 137 | 138 | impl #ref_child { 139 | pub fn new_boxed(object: #child) -> *mut #ref_child { 140 | let object = #ref_child { 141 | #(#table_ptrs)* 142 | body: object, 143 | }; 144 | 145 | ::std::boxed::Box::into_raw(::std::boxed::Box::new(object)) 146 | } 147 | } 148 | } 149 | } 150 | 151 | fn make_mod_items(vtable: &VTableDefinition) -> TokenStream { 152 | let mut items = Vec::new(); 153 | 154 | for item in &vtable.module.content.as_ref().unwrap().1 { 155 | items.push(item); 156 | } 157 | 158 | quote! { 159 | #(#items)* 160 | } 161 | } 162 | 163 | fn make_typeinfo_structs(_attrs: &Attrs, vtable: &VTableDefinition) -> TokenStream { 164 | let krate = crate_name("cpp-class"); 165 | let ti = quote::format_ident!("__{}_TYPEINFO", vtable.child.ident); 166 | let ti_vtable = quote::format_ident!("{}VTable", vtable.child.ident); 167 | let global_vtable = quote::format_ident!("__{}_VTABLE", vtable.child.ident); 168 | let bases_count = vtable.child.parents.len(); 169 | 170 | let mut bases = Vec::new(); 171 | let mut vtable_fields = Vec::new(); 172 | let mut vtable_impl = Vec::new(); 173 | 174 | for (idx, parent) in vtable.child.parents.iter().enumerate() { 175 | if let Some(base) = vtable.bases.iter().find(|base| base.ident == *parent) { 176 | let mod_ident = quote::format_ident!("__{}", base.ident); 177 | let field = quote::format_ident!("vtable_{}", idx); 178 | let offset = idx as isize; 179 | 180 | let base_struct = quote! { 181 | #krate::Base { 182 | base: &#mod_ident :: TYPE_INFO, 183 | offset_flags: ((#idx * ::std::mem::size_of::()) << 8) + 2, 184 | } 185 | }; 186 | 187 | let v_field = quote! { 188 | #field: #krate::GenericTable<#mod_ident::vtable, #krate::MultipleBasesTypeInfo<#bases_count>>, 189 | }; 190 | 191 | let v_fields = base.funcs.iter().map(|sig| { 192 | let ident = &sig.ident; 193 | quote! { #ident: #mod_ident::#ident, } 194 | }); 195 | 196 | let v_impl = quote! { 197 | #field: #krate::GenericTable { 198 | offset: - #offset * ::std::mem::size_of::() as isize, 199 | type_info: &#ti, 200 | vtable: #mod_ident::vtable { 201 | #(#v_fields)* 202 | }, 203 | }, 204 | }; 205 | 206 | bases.push(base_struct); 207 | vtable_fields.push(v_field); 208 | vtable_impl.push(v_impl); 209 | } else { 210 | proc_macro_error::abort!(parent.span(), format!("No virtual table for {}", parent)); 211 | } 212 | } 213 | 214 | let name = vtable.child.ident.to_string(); 215 | let ns = vtable.module.ident.to_string(); 216 | let ti_name = byte_str( 217 | format!("N{}{}{}{}E", ns.len(), ns, name.len(), name), 218 | proc_macro2::Span::call_site(), 219 | ); 220 | 221 | // let vtable = attrs.known_tables[0]; 222 | let bases_count_u32 = bases_count as u32; 223 | 224 | quote! { 225 | #[repr(C)] 226 | struct #ti_vtable { 227 | #(#vtable_fields)* 228 | } 229 | 230 | #[allow(non_upper_case_globals)] 231 | static #ti: #krate::MultipleBasesTypeInfo<#bases_count> = #krate::MultipleBasesTypeInfo { 232 | // vtable: #vtable, 233 | vtable: unsafe { &#krate::vmi_class_type_info.vtable }, 234 | name: #ti_name.as_ptr(), 235 | flags: 0, 236 | bases_count: #bases_count_u32, 237 | bases: [#(#bases),*], 238 | }; 239 | 240 | #[allow(non_upper_case_globals)] 241 | static #global_vtable: #ti_vtable = #ti_vtable { 242 | #(#vtable_impl)* 243 | }; 244 | } 245 | } 246 | 247 | fn crate_name(krate: &str) -> syn::Ident { 248 | use proc_macro_crate::FoundCrate; 249 | 250 | match proc_macro_crate::crate_name(krate) { 251 | Ok(FoundCrate::Itself) => { 252 | let name = krate.to_string().replace("-", "_"); 253 | syn::Ident::new(&name, proc_macro2::Span::call_site()) 254 | } 255 | 256 | Ok(FoundCrate::Name(name)) => syn::Ident::new(&name, proc_macro2::Span::call_site()), 257 | 258 | Err(err) => { 259 | proc_macro_error::abort!(proc_macro2::Span::call_site(), err.to_string()); 260 | } 261 | } 262 | } 263 | 264 | fn byte_str>(name: T, span: proc_macro2::Span) -> syn::LitByteStr { 265 | let mut bytes = Vec::from(name.as_ref().as_bytes()); 266 | bytes.push(0); 267 | 268 | syn::LitByteStr::new(&bytes, span) 269 | } 270 | 271 | fn make_utils(vtable: &VTableDefinition) -> TokenStream { 272 | let name = &vtable.child.ident; 273 | let ref_name = quote::format_ident!("Ref{}", name); 274 | 275 | quote! { 276 | pub fn make_boxed(object: #name) -> *mut #ref_name { 277 | #ref_name::new_boxed(object) 278 | } 279 | 280 | pub fn from_boxed(ptr: *mut #ref_name) -> #name { 281 | let wrapper = unsafe { ::std::boxed::Box::from_raw(ptr) }; 282 | 283 | wrapper.body 284 | } 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /macro/src/vtable.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use proc_macro2::Span; 3 | use proc_macro_error::abort; 4 | use quote::ToTokens; 5 | use syn::{ 6 | parse::{Parse, ParseStream}, 7 | parse_macro_input, 8 | punctuated::Punctuated, 9 | spanned::Spanned, 10 | Attribute, Ident, Item, ItemMod, LitStr, Signature, Token, 11 | }; 12 | 13 | pub fn make_vtable(attr: TokenStream, input: TokenStream) -> TokenStream { 14 | let attr = parse_macro_input!(attr as Attrs); 15 | let module = parse_macro_input!(input as ItemMod); 16 | 17 | match VTableDefinition::try_from(module) { 18 | Ok(vtable) => crate::expand::expand(attr, vtable).into(), 19 | Err(err) => err.into_compile_error().into(), 20 | } 21 | } 22 | 23 | mod keyword { 24 | syn::custom_keyword!(vtable); 25 | syn::custom_keyword!(derive); 26 | syn::custom_keyword!(virtual_class); 27 | 28 | syn::custom_keyword!(abi); 29 | syn::custom_keyword!(type_name); 30 | } 31 | 32 | pub struct Attrs { 33 | pub known_tables: Vec, 34 | } 35 | 36 | impl Parse for Attrs { 37 | fn parse(_input: ParseStream) -> syn::Result { 38 | // let ident = input.parse::()?; 39 | 40 | // // TODO: libstdc++ 41 | // if ident != "known_tables" { 42 | // abort!(ident, "Expect known_tables"); 43 | // } 44 | 45 | // let content; 46 | // syn::parenthesized!(content in input); 47 | 48 | // let tables: Punctuated<_, Token![,]> = content.parse_terminated(LitInt::parse)?; 49 | // let known_tables = tables 50 | // .iter() 51 | // .filter_map(|val| val.base10_parse::().ok()) 52 | // .collect(); 53 | 54 | Ok(Attrs { 55 | known_tables: Vec::new(), 56 | }) 57 | } 58 | } 59 | 60 | struct Derive { 61 | parents: Punctuated, 62 | } 63 | 64 | impl Parse for Derive { 65 | fn parse(input: ParseStream) -> syn::Result { 66 | input.parse::()?; 67 | 68 | let content; 69 | syn::parenthesized!(content in input); 70 | 71 | let parents = content.parse_terminated(Ident::parse)?; 72 | 73 | Ok(Derive { parents }) 74 | } 75 | } 76 | 77 | // TODO: ABI, cpp mangler 78 | struct VirtualClass { 79 | abi: Ident, 80 | type_name: LitStr, 81 | } 82 | 83 | impl Parse for VirtualClass { 84 | fn parse(input: ParseStream) -> syn::Result { 85 | input.parse::()?; 86 | 87 | let content; 88 | syn::parenthesized!(content in input); 89 | 90 | let span = content.span(); 91 | 92 | let mut abi = None; 93 | let mut type_name = None; 94 | 95 | while !content.is_empty() { 96 | let lookahead = content.lookahead1(); 97 | 98 | if lookahead.peek(keyword::abi) { 99 | content.parse::()?; 100 | content.parse::()?; 101 | 102 | abi = Some(content.parse::()?); 103 | } else if lookahead.peek(keyword::type_name) { 104 | content.parse::()?; 105 | content.parse::()?; 106 | 107 | type_name = Some(content.parse::()?); 108 | } else { 109 | return Err(lookahead.error()); 110 | } 111 | 112 | let _ = content.parse::(); 113 | } 114 | 115 | let abi = abi.unwrap_or_else(|| Ident::new("fastcall", span)); 116 | let type_name = type_name.ok_or_else(|| syn::Error::new(span, "Expect type_name"))?; 117 | 118 | Ok(VirtualClass { abi, type_name }) 119 | } 120 | } 121 | 122 | enum VTableAttr { 123 | Derive(Span, Derive), 124 | VirtualClass(Span, VirtualClass), 125 | } 126 | 127 | impl Parse for VTableAttr { 128 | fn parse(input: ParseStream) -> syn::Result { 129 | input.parse::()?; 130 | 131 | let content; 132 | syn::bracketed!(content in input); 133 | 134 | content.parse::()?; 135 | content.parse::()?; 136 | 137 | let lookahead = content.lookahead1(); 138 | 139 | if lookahead.peek(keyword::derive) { 140 | Ok(VTableAttr::Derive(input.span(), content.parse()?)) 141 | } else if lookahead.peek(keyword::virtual_class) { 142 | Ok(VTableAttr::VirtualClass(input.span(), content.parse()?)) 143 | } else { 144 | Err(lookahead.error()) 145 | } 146 | } 147 | } 148 | 149 | impl Spanned for VTableAttr { 150 | fn span(&self) -> Span { 151 | match self { 152 | Self::Derive(span, _) => *span, 153 | Self::VirtualClass(span, _) => *span, 154 | } 155 | } 156 | } 157 | 158 | pub struct Child { 159 | pub ident: Ident, 160 | pub parents: Punctuated, 161 | } 162 | 163 | pub struct Base { 164 | pub ident: Ident, 165 | pub abi: LitStr, 166 | pub type_name: LitStr, 167 | pub funcs: Vec, 168 | } 169 | 170 | pub struct VTableDefinition { 171 | pub module: ItemMod, 172 | pub items: Vec, 173 | pub child: Child, 174 | pub bases: Vec, 175 | } 176 | 177 | impl VTableDefinition { 178 | fn try_from(mut item: ItemMod) -> syn::Result { 179 | let item_span = item.span(); 180 | 181 | let content = &mut item 182 | .content 183 | .as_mut() 184 | .ok_or_else(|| syn::Error::new(item_span, "Module should be inlined"))? 185 | .1; 186 | 187 | let mut items = Vec::new(); 188 | let mut child = None; 189 | let mut bases = Vec::new(); 190 | 191 | for item in content.iter_mut() { 192 | match item { 193 | Item::Const(const_item) => { 194 | let attrs = take_attrs::(&mut const_item.attrs)?; 195 | 196 | if attrs.len() > 0 { 197 | let first = &attrs[0]; 198 | abort!( 199 | first.span(), 200 | "vtable attrubites can be only at struct or trait" 201 | ); 202 | } 203 | } 204 | 205 | Item::Struct(struct_item) => { 206 | let attrs = take_attrs::(&mut struct_item.attrs)?; 207 | 208 | for attr in attrs { 209 | match attr { 210 | VTableAttr::Derive(span, derive) => { 211 | if child.is_some() { 212 | abort!(span, "Only one #[vtable::derive] in the module ..."); 213 | } 214 | 215 | child = Some(Child { 216 | ident: struct_item.ident.clone(), 217 | parents: derive.parents, 218 | }); 219 | } 220 | 221 | VTableAttr::VirtualClass(span, _) => { 222 | abort!(span, "#[vtable::virtual_class] should be on trait."); 223 | } 224 | } 225 | } 226 | } 227 | 228 | Item::Trait(trait_item) => { 229 | let attrs = take_attrs::(&mut trait_item.attrs)?; 230 | 231 | for attr in attrs { 232 | match attr { 233 | VTableAttr::Derive(span, _) => { 234 | abort!(span, "#[vtable::derive] should be on struct."); 235 | } 236 | 237 | VTableAttr::VirtualClass(_, vc) => { 238 | let mut funcs = Vec::new(); 239 | 240 | for item in trait_item.items.iter() { 241 | match item { 242 | syn::TraitItem::Method(method) => { 243 | if method.default.is_some() { 244 | return Err(syn::Error::new(method.span(), "Trait describing virtual class cannot define defaults")); 245 | } 246 | 247 | if method.sig.receiver().is_none() { 248 | return Err(syn::Error::new(method.span(), "Trait describing virtual class cannot have static functions")); 249 | } 250 | 251 | funcs.push(method.sig.clone()); 252 | } 253 | 254 | _ => (), 255 | } 256 | } 257 | 258 | bases.push(Base { 259 | ident: trait_item.ident.clone(), 260 | abi: LitStr::new(&vc.abi.to_string(), vc.abi.span()), 261 | type_name: vc.type_name, 262 | funcs, 263 | }); 264 | } 265 | } 266 | } 267 | } 268 | 269 | _ => (), 270 | } 271 | 272 | items.push(item.clone()); 273 | } 274 | 275 | let child = child 276 | .ok_or_else(|| syn::Error::new(item_span, "Module cannot exist without a child"))?; 277 | 278 | Ok(VTableDefinition { 279 | module: item, 280 | items, 281 | child, 282 | bases, 283 | }) 284 | } 285 | } 286 | 287 | fn take_first_attr(attrs: &mut Vec) -> syn::Result> { 288 | if let Some(idx) = attrs.iter().position(|attr| { 289 | attr.path 290 | .segments 291 | .first() 292 | .map_or(false, |seg| seg.ident == "vtable") 293 | }) { 294 | let attr = attrs.remove(idx); 295 | let parsed = syn::parse2(attr.into_token_stream())?; 296 | 297 | Ok(Some(parsed)) 298 | } else { 299 | Ok(None) 300 | } 301 | } 302 | 303 | fn take_attrs(attrs: &mut Vec) -> syn::Result> { 304 | let mut vtable_attrs = Vec::new(); 305 | 306 | while let Some(attr) = take_first_attr(attrs)? { 307 | vtable_attrs.push(attr); 308 | } 309 | 310 | Ok(vtable_attrs) 311 | } 312 | --------------------------------------------------------------------------------