1#![deny(warnings)]
2
3extern crate proc_macro;
4
5use anycase::{to_pascal, to_snake, to_screaming_snake};
6use indoc::{formatdoc, indoc};
7use macro_magic::import_tokens_attr;
8use proc_macro2::{TokenStream, Span};
9use proc_macro2_diagnostics::{Diagnostic, SpanDiagnosticExt};
10use quote::{quote, TokenStreamExt, ToTokens};
11use syn::{parse_macro_input, parse_quote, ItemStruct, Path, Type, Fields, Meta, Attribute, Visibility, Ident};
12use syn::{Field, FieldMutability, TypePath, Token, PathSegment, PathArguments, LitInt, TypeBareFn, Expr};
13use syn::{BareFnArg, Generics, Signature, Pat, PatType, PatIdent, FnArg, ImplItemFn, Stmt, ExprPath};
14use syn::{Receiver, TypeReference, AttrStyle};
15use syn::punctuated::Punctuated;
16use syn::spanned::Spanned;
17use syn::token::Bracket;
18
19fn parse_base_field_meta(meta: &Meta) -> Option<bool> {
20 match meta {
21 Meta::Path(path) if
22 path.leading_colon.is_none()
23 && path.segments.len() == 1
24 && path.segments[0].arguments.is_none()
25 => match path.segments[0].ident.to_string().as_ref() {
26 "non_virt" => Some(false),
27 "virt" => Some(true),
28 _ => None
29 },
30 _ => None
31 }
32}
33
34fn parse_base_field_attrs(attrs: &[Attribute]) -> Option<bool> {
35 for attr in attrs {
36 if let Some(virt) = parse_base_field_meta(&attr.meta) {
37 return Some(virt);
38 }
39 }
40 None
41}
42
43fn parse_base_meta(meta: &Meta) -> Option<bool> {
44 match meta {
45 Meta::Path(path) if
46 path.leading_colon.is_none()
47 && path.segments.len() == 1
48 && path.segments[0].arguments.is_none()
49 => match path.segments[0].ident.to_string().as_ref() {
50 "non_sync" => Some(false),
51 "sync" => Some(true),
52 _ => None
53 },
54 _ => None
55 }
56}
57
58fn parse_base_sync(inherits: &ItemStruct) -> Option<bool> {
59 for attr in &inherits.attrs {
60 if let Some(is_sync) = parse_base_meta(&attr.meta) {
61 return Some(is_sync);
62 }
63 }
64 None
65}
66
67struct Base {
68 ty: Path,
69 non_virt_methods: Vec<(Ident, TypeBareFn, Vec<Attribute>)>,
70 virt_methods: Vec<(Ident, TypeBareFn, Vec<Attribute>)>,
71}
72
73fn parse_base_types(inherits: ItemStruct) -> Result<Vec<Base>, Diagnostic> {
74 let Fields::Named(fields) = inherits.fields else {
75 return Err(inherits.fields.span().error("invalid base class"));
76 };
77 let mut res = Vec::new();
78 let mut base = None;
79 for field in fields.named {
80 if *field.ident.as_ref().unwrap() == "__class__" {
81 if let Some(base) = base.take() {
82 res.push(base);
83 }
84 let Type::Path(type_path) = field.ty else {
85 return Err(field.ty.span().error("invalid base class"));
86 };
87 if type_path.path.segments.len() != 1 {
88 return Err(type_path.span().error("invalid base class"));
89 }
90 base = Some(Base {
91 ty: type_path.path,
92 non_virt_methods: Vec::new(),
93 virt_methods: Vec::new()
94 });
95 } else {
96 let name = field.ident.as_ref().unwrap().clone();
97 let Type::BareFn(type_fn) = &field.ty else {
98 return Err(field.ty.span().error("invalid base class"));
99 };
100 let Some(base) = base.as_mut() else {
101 return Err(type_fn.span().error("invalid base class"));
102 };
103 let Some(virt) = parse_base_field_attrs(&field.attrs) else {
104 return Err(field.span().error("invalid base class"))
105 };
106 let attrs = field.attrs.into_iter().filter(|x| parse_base_field_meta(&x.meta).is_none()).collect();
107 if virt {
108 base.virt_methods.push((name, type_fn.clone(), attrs));
109 } else {
110 base.non_virt_methods.push((name, type_fn.clone(), attrs));
111 }
112 }
113 }
114 if let Some(base) = base.take() {
115 res.push(base);
116 }
117 Ok(res)
118}
119
120#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
121enum FieldKind {
122 NonVirtMethod,
123 VirtMethod,
124 Override,
125 Data,
126}
127
128fn parse_field_meta(meta: &Meta) -> FieldKind {
129 match meta {
130 Meta::Path(path) if
131 path.leading_colon.is_none()
132 && path.segments.len() == 1
133 && path.segments[0].arguments.is_none()
134 => match path.segments[0].ident.to_string().as_ref() {
135 "non_virt" => FieldKind::NonVirtMethod,
136 "virt" => FieldKind::VirtMethod,
137 "over" => FieldKind::Override,
138 _ => FieldKind::Data,
139 },
140 _ => FieldKind::Data,
141 }
142}
143
144fn parse_field_attrs(attrs: &[Attribute]) -> Result<FieldKind, Diagnostic> {
145 let mut kind = FieldKind::Data;
146 for attr in attrs {
147 match parse_field_meta(&attr.meta) {
148 FieldKind::Data => { },
149 FieldKind::NonVirtMethod => {
150 if kind != FieldKind::Data {
151 return Err(
152 attr.span()
153 .error("only one of 'non_virt'/'virt'/'over' attributes can be specified for a field")
154 );
155 }
156 kind = FieldKind::NonVirtMethod;
157 },
158 FieldKind::VirtMethod => {
159 if kind != FieldKind::Data {
160 return Err(
161 attr.span()
162 .error("only one of 'non_virt'/'virt'/'over' attributes can be specified for a field")
163 );
164 }
165 kind = FieldKind::VirtMethod;
166 },
167 FieldKind::Override => {
168 if kind != FieldKind::Data {
169 return Err(
170 attr.span()
171 .error("only one of 'non_virt'/'virt'/'over' attributes can be specified for a field")
172 );
173 }
174 kind = FieldKind::Override;
175 },
176 }
177 }
178 Ok(kind)
179}
180
181struct Class {
182 attrs: Vec<Attribute>,
183 vis: Visibility,
184 name: Ident,
185 fields: Vec<Field>,
186 non_virt_methods: Vec<(Ident, TypeBareFn, Vec<Attribute>)>,
187 virt_methods: Vec<(Ident, TypeBareFn, Vec<Attribute>)>,
188 overrides: Vec<Ident>,
189}
190
191impl Class {
192 fn parse(descr: ItemStruct) -> Result<Class, Diagnostic> {
193 if descr.generics.lt_token.is_some() || descr.generics.where_clause.is_some() {
194 return Err(descr.generics.span().error("basic-oop does not support generics"));
195 }
196 let mut fields = Vec::new();
197 let mut non_virt_methods = Vec::new();
198 let mut virt_methods = Vec::new();
199 let mut overrides = Vec::new();
200 let Fields::Named(descr_fields) = descr.fields else {
201 return Err(descr.fields.span().error("class should be described as struct with named fields"));
202 };
203 for field in descr_fields.named.iter() {
204 match parse_field_attrs(&field.attrs)? {
205 FieldKind::Data => fields.push(field.clone()),
206 FieldKind::NonVirtMethod => {
207 let name = field.ident.clone().unwrap();
208 if name == "__class__" {
209 return Err(name.span().error("this name is reserved"));
210 }
211 let Type::BareFn(type_fn) = &field.ty else {
212 return Err(field.ty.span().error("invalid non-virtual method type"));
213 };
214 if
215 type_fn.unsafety.is_some()
216 || type_fn.abi.is_some()
217 || type_fn.variadic.is_some()
218 || type_fn.inputs.iter().any(|x| !x.attrs.is_empty())
219 {
220 return Err(field.ty.span().error("invalid non-virtual method type"));
221 }
222 if let Some(arg) = type_fn.inputs.iter().find(|x| x.name.is_none()) {
223 return Err(arg.span().error("argument name required"));
224 }
225 let attrs = field.attrs.iter()
226 .filter(|x| parse_field_meta(&x.meta) == FieldKind::Data)
227 .cloned().collect();
228 non_virt_methods.push((name, type_fn.clone(), attrs));
229 },
230 FieldKind::VirtMethod => {
231 let name = field.ident.clone().unwrap();
232 if name == "__class__" {
233 return Err(name.span().error("this name is reserved"));
234 }
235 let Type::BareFn(type_fn) = &field.ty else {
236 return Err(field.ty.span().error("invalid virtual method type"));
237 };
238 if
239 type_fn.unsafety.is_some()
240 || type_fn.abi.is_some()
241 || type_fn.variadic.is_some()
242 || type_fn.inputs.iter().any(|x| !x.attrs.is_empty())
243 {
244 return Err(field.ty.span().error("invalid virtual method type"));
245 }
246 if let Some(arg) = type_fn.inputs.iter().find(|x| x.name.is_none()) {
247 return Err(arg.span().error("argument name required"));
248 }
249 let attrs = field.attrs.iter()
250 .filter(|x| parse_field_meta(&x.meta) == FieldKind::Data)
251 .cloned().collect();
252 virt_methods.push((name, type_fn.clone(), attrs));
253 },
254 FieldKind::Override => {
255 let name = field.ident.clone().unwrap();
256 let Type::Tuple(type_tuple) = &field.ty else {
257 return Err(field.ty.span().error("invalid override method type"));
258 };
259 if !type_tuple.elems.is_empty() {
260 return Err(field.ty.span().error("invalid override method type"));
261 }
262 overrides.push(name);
263 },
264 }
265 }
266 Ok(Class {
267 attrs: descr.attrs,
268 vis: descr.vis,
269 name: descr.ident,
270 fields,
271 non_virt_methods,
272 virt_methods,
273 overrides,
274 })
275 }
276}
277
278fn build_inherits(
279 base_types: &[Base],
280 class_name: &Ident,
281 sync: bool,
282 non_virt_methods: &[(Ident, TypeBareFn, Vec<Attribute>)],
283 virt_methods: &[(Ident, TypeBareFn, Vec<Attribute>)]
284) -> ItemStruct {
285 let name = Ident::new(&("inherits_".to_string() + &class_name.to_string()), Span::call_site());
286 let sync_name = Ident::new(if sync { "sync" } else { "non_sync" }, Span::call_site());
287 let mut struct_: ItemStruct = parse_quote! {
288 #[::basic_oop::macro_magic::export_tokens_no_emit]
289 #[#sync_name]
290 struct #name {
291 __class__: #class_name
292 }
293 };
294 let Fields::Named(fields) = &mut struct_.fields else { panic!() };
295 for (method_name, method_ty, method_attrs) in non_virt_methods {
296 let mut attrs = method_attrs.clone();
297 let mut segments = Punctuated::new();
298 segments.push(PathSegment {
299 ident: Ident::new("non_virt", Span::call_site()),
300 arguments: PathArguments::None
301 });
302 attrs.push(Attribute {
303 pound_token: <Token![#]>::default(),
304 style: AttrStyle::Outer,
305 bracket_token: Bracket::default(),
306 meta: Meta::Path(Path { leading_colon: None, segments }),
307 });
308 fields.named.push(Field {
309 attrs,
310 vis: Visibility::Inherited,
311 mutability: FieldMutability::None,
312 ident: Some(method_name.clone()),
313 colon_token: Some(<Token![:]>::default()),
314 ty: Type::BareFn(method_ty.clone()),
315 });
316 }
317 for (method_name, method_ty, method_attrs) in virt_methods {
318 let mut attrs = method_attrs.clone();
319 let mut segments = Punctuated::new();
320 segments.push(PathSegment {
321 ident: Ident::new("virt", Span::call_site()),
322 arguments: PathArguments::None
323 });
324 attrs.push(Attribute {
325 pound_token: <Token![#]>::default(),
326 style: AttrStyle::Outer,
327 bracket_token: Bracket::default(),
328 meta: Meta::Path(Path { leading_colon: None, segments }),
329 });
330 fields.named.push(Field {
331 attrs,
332 vis: Visibility::Inherited,
333 mutability: FieldMutability::None,
334 ident: Some(method_name.clone()),
335 colon_token: Some(<Token![:]>::default()),
336 ty: Type::BareFn(method_ty.clone()),
337 });
338 }
339 for base_type in base_types {
340 fields.named.push(Field {
341 attrs: Vec::new(),
342 vis: Visibility::Inherited,
343 mutability: FieldMutability::None,
344 ident: Some(Ident::new("__class__", Span::call_site())),
345 colon_token: Some(<Token![:]>::default()),
346 ty: Type::Path(TypePath {
347 qself: None,
348 path: base_type.ty.clone()
349 }),
350 });
351 for (method_name, method_ty, method_attrs) in &base_type.non_virt_methods {
352 let mut attrs = method_attrs.clone();
353 let mut segments = Punctuated::new();
354 segments.push(PathSegment {
355 ident: Ident::new("non_virt", Span::call_site()),
356 arguments: PathArguments::None
357 });
358 attrs.push(Attribute {
359 pound_token: <Token![#]>::default(),
360 style: AttrStyle::Outer,
361 bracket_token: Bracket::default(),
362 meta: Meta::Path(Path { leading_colon: None, segments }),
363 });
364 fields.named.push(Field {
365 attrs,
366 vis: Visibility::Inherited,
367 mutability: FieldMutability::None,
368 ident: Some(method_name.clone()),
369 colon_token: Some(<Token![:]>::default()),
370 ty: Type::BareFn(method_ty.clone()),
371 });
372 }
373 for (method_name, method_ty, method_attrs) in &base_type.virt_methods {
374 let mut attrs = method_attrs.clone();
375 let mut segments = Punctuated::new();
376 segments.push(PathSegment {
377 ident: Ident::new("virt", Span::call_site()),
378 arguments: PathArguments::None
379 });
380 attrs.push(Attribute {
381 pound_token: <Token![#]>::default(),
382 style: AttrStyle::Outer,
383 bracket_token: Bracket::default(),
384 meta: Meta::Path(Path { leading_colon: None, segments }),
385 });
386 fields.named.push(Field {
387 attrs,
388 vis: Visibility::Inherited,
389 mutability: FieldMutability::None,
390 ident: Some(method_name.clone()),
391 colon_token: Some(<Token![:]>::default()),
392 ty: Type::BareFn(method_ty.clone()),
393 });
394 }
395 }
396 struct_
397}
398
399fn patch_path(path: &mut Path, f: impl FnOnce(String) -> String) {
400 let ident = f(path.segments.last().unwrap().ident.to_string());
401 path.segments.last_mut().unwrap().ident = Ident::new(&ident, Span::call_site());
402}
403
404fn build_attrs(attrs: &[Attribute]) -> TokenStream {
405 let mut tokens = TokenStream::new();
406 tokens.append_all(attrs);
407 tokens
408}
409
410fn build_struct(
411 base_types: &[Base],
412 attrs: &[Attribute],
413 vis: &Visibility,
414 name: &Ident,
415 fields: &[Field]
416) -> ItemStruct {
417 let base_type = base_types[0].ty.clone();
418 let base_field = Ident::new(&to_snake(base_type.segments.last().unwrap().ident.to_string()), Span::call_site());
419 let attrs = build_attrs(attrs);
420 let mut struct_: ItemStruct = parse_quote! {
421 #attrs
422 #vis struct #name {
423 #base_field: #base_type
424 }
425 };
426 let Fields::Named(struct_fields) = &mut struct_.fields else { panic!() };
427 for field in fields {
428 struct_fields.named.push(field.clone());
429 }
430 struct_
431}
432
433fn build_trait(base_types: &[Base], vis: &Visibility, class_name: &Ident, sync: bool) -> TokenStream {
434 let doc_name = class_name.to_string();
435 let rc = if sync { "arc" } else { "rc" };
436 let doc = formatdoc!("
437 Represents [`{doc_name}`] or any of its inheritors.
438
439 Use `dynamic_cast_{rc}` from `dynamic_cast` crate to convert to or from this trait.
440 ");
441 let method_doc = indoc!("
442 Returns reference to inner data.
443 ");
444 let base_type = base_types[0].ty.clone();
445 let base_field = Ident::new(
446 &to_snake(base_type.segments.last().unwrap().ident.to_string()),
447 Span::call_site()
448 );
449 let mut base_trait = base_types[0].ty.clone();
450 patch_path(&mut base_trait, |x| "Is".to_string() + &x);
451 let trait_name = Ident::new(&("Is".to_string() + &class_name.to_string()), Span::call_site());
452 let method_name = Ident::new(&to_snake(class_name.to_string()), Span::call_site());
453 let mut trait_ = quote! {
454 #[doc=#doc]
455 #vis trait #trait_name: #base_trait {
456 #[doc=#method_doc]
457 fn #method_name(&self) -> &#class_name;
458 }
459
460 impl #trait_name for #class_name {
461 fn #method_name(&self) -> &#class_name { self }
462 }
463
464 impl #base_trait for #class_name {
465 fn #base_field(&self) -> &#base_type { &self.#base_field }
466 }
467 };
468 for base_base_type in base_types.iter().skip(1) {
469 let method_name = Ident::new(
470 &to_snake(base_base_type.ty.segments.last().unwrap().ident.to_string()),
471 Span::call_site()
472 );
473 let mut base_base_trait = base_base_type.ty.clone();
474 patch_path(&mut base_base_trait, |x| "Is".to_string() + &x);
475 let base_base_type_ty = &base_base_type.ty;
476 trait_.extend(quote! {
477 impl #base_base_trait for #class_name {
478 fn #method_name(&self) -> &#base_base_type_ty {
479 #base_base_trait::#method_name(&self.#base_field)
480 }
481 }
482 });
483 }
484 let mut traits_list: Punctuated<Path, Token![,]> = Punctuated::new();
485 let mut trait_path = Path {
486 leading_colon: None,
487 segments: Punctuated::new()
488 };
489 trait_path.segments.push(PathSegment {
490 ident: trait_name,
491 arguments: PathArguments::None
492 });
493 traits_list.push(trait_path);
494 for base_type in base_types {
495 let mut base_trait = base_type.ty.clone();
496 patch_path(&mut base_trait, |x| "Is".to_string() + &x);
497 traits_list.push(base_trait);
498 }
499 trait_.extend(quote! {
500 ::basic_oop::dynamic_cast_impl_supports_interfaces!(#class_name: #traits_list);
501 });
502 trait_
503}
504
505fn build_virt_methods_enum(
506 base_type: &Path,
507 vis: &Visibility,
508 class_name: &Ident,
509 virt_methods: &[(Ident, TypeBareFn, Vec<Attribute>)]
510) -> TokenStream {
511 let doc_name = class_name.to_string();
512 let doc = formatdoc!("
513 [`{doc_name}`] virtual methods list.
514
515 Used by the [`class_unsafe`](::basic_oop::class_unsafe) macro, not intended for direct use in code.
516 ");
517 let mut base_methods_enum = base_type.clone();
518 patch_path(&mut base_methods_enum, |x| x + "VirtMethods");
519 let methods_enum = Ident::new(&(class_name.to_string() + "VirtMethods"), Span::call_site());
520 let mut values = TokenStream::new();
521 values.append_terminated(
522 virt_methods.iter().enumerate().map(|(i, (method_name, _method_ty, _))| {
523 let name = Ident::new(&to_pascal(method_name.to_string()), Span::call_site());
524 let index = LitInt::new(&(i.to_string() + "usize"), Span::call_site());
525 quote! {
526 #name = (#base_methods_enum::VirtMethodsCount as usize) + #index
527 }
528 }),
529 <Token![,]>::default()
530 );
531 let count = virt_methods.len();
532 quote! {
533 #[doc=#doc]
534 #[derive(Debug, Eq, PartialEq, Clone, Copy, Ord, PartialOrd, Hash)]
535 #[repr(usize)]
536 #vis enum #methods_enum {
537 #values
538 VirtMethodsCount = (#base_methods_enum::VirtMethodsCount as usize) + #count
539 }
540 }
541}
542
543fn build_consts_for_vtable(class_name: &Ident, base_types: &[Base]) -> TokenStream {
544 let enum_name = Ident::new(&(class_name.to_string() + "VirtMethods"), Span::call_site());
545 let mut tokens = TokenStream::new();
546 for base_type in base_types {
547 let mut base_methods_enum = base_type.ty.clone();
548 patch_path(&mut base_methods_enum, |x| x + "VirtMethods");
549 let base_const_name = Ident::new(
550 &(
551 to_screaming_snake(
552 class_name.to_string() + &base_type.ty.segments.last().unwrap().ident.to_string()
553 ) + "_VIRT_METHODS_COUNT"
554 ),
555 Span::call_site()
556 );
557 let complement_const_name = Ident::new(&(base_const_name.to_string() + "_COMPL"), Span::call_site());
558 tokens.extend(quote! {
559 const #base_const_name: usize = #base_methods_enum::VirtMethodsCount as usize;
560 const #complement_const_name: usize = (#enum_name::VirtMethodsCount as usize) - #base_const_name;
561 });
562 }
563 tokens
564}
565
566fn actual_base_method_ty(mut ty: TypeBareFn, base_type: &Path, sync: bool) -> TypeBareFn {
567 let rc = if sync {
568 quote! { ::basic_oop::alloc_sync_Arc }
569 } else {
570 quote! { ::basic_oop::alloc_rc_Rc }
571 };
572 let mut base_trait = base_type.clone();
573 patch_path(&mut base_trait, |x| "Is".to_string() + &x);
574 let this_arg = BareFnArg {
575 attrs: Vec::new(),
576 name: Some((Ident::new("this", Span::call_site()), <Token![:]>::default())),
577 ty: parse_quote! { &#rc<dyn #base_trait> },
578 };
579 ty.inputs.insert(0, this_arg);
580 ty
581}
582
583fn actual_method_ty(mut ty: TypeBareFn, class_name: &Ident, sync: bool) -> TypeBareFn {
584 let rc = if sync {
585 quote! { ::basic_oop::alloc_sync_Arc }
586 } else {
587 quote! { ::basic_oop::alloc_rc_Rc }
588 };
589 let trait_name = Ident::new(&("Is".to_string() + &class_name.to_string()), Span::call_site());
590 let this_arg = BareFnArg {
591 attrs: Vec::new(),
592 name: Some((Ident::new("this", Span::call_site()), <Token![:]>::default())),
593 ty: parse_quote! { &#rc<dyn #trait_name> },
594 };
595 ty.inputs.insert(0, this_arg);
596 ty
597}
598
599fn fn_ty_without_idents(mut ty: TypeBareFn) -> TypeBareFn {
600 for arg in &mut ty.inputs {
601 arg.name = None;
602 }
603 ty
604}
605
606fn bare_fn_arg_to_fn_arg(a: &BareFnArg) -> FnArg {
607 let Some((name, colon_token)) = &a.name else { panic!() };
608 FnArg::Typed(PatType {
609 attrs: Vec::new(),
610 pat: Box::new(Pat::Ident(PatIdent {
611 attrs: Vec::new(),
612 by_ref: None,
613 mutability: None,
614 ident: name.clone(),
615 subpat: None
616 })),
617 colon_token: *colon_token,
618 ty: Box::new(a.ty.clone()),
619 })
620}
621
622fn method_signature(ty: &TypeBareFn, name: Ident) -> Signature {
623 let generics = if let Some(lifetimes) = &ty.lifetimes {
624 Generics {
625 lt_token: Some(lifetimes.lt_token),
626 params: lifetimes.lifetimes.clone(),
627 gt_token: Some(lifetimes.gt_token),
628 where_clause: None,
629 }
630 } else {
631 Generics {
632 lt_token: None,
633 params: Punctuated::new(),
634 gt_token: None,
635 where_clause: None,
636 }
637 };
638 let mut s = Signature {
639 constness: None,
640 asyncness: None,
641 unsafety: None,
642 abi: None,
643 fn_token: ty.fn_token,
644 ident: name,
645 generics,
646 paren_token: ty.paren_token,
647 inputs: Punctuated::new(),
648 variadic: None,
649 output: ty.output.clone(),
650 };
651 let mut segments = Punctuated::new();
652 segments.push(PathSegment {
653 ident: Ident::new("Self", Span::call_site()),
654 arguments: PathArguments::None
655 });
656 s.inputs.push(FnArg::Receiver(Receiver {
657 attrs: Vec::new(),
658 reference: Some((<Token![&]>::default(), None)),
659 mutability: None,
660 self_token: <Token![self]>::default(),
661 colon_token: None,
662 ty: Box::new(Type::Reference(TypeReference {
663 and_token: <Token![&]>::default(),
664 lifetime: None,
665 mutability: None,
666 elem: Box::new(Type::Path(TypePath {
667 qself: None,
668 path: Path {
669 leading_colon: None,
670 segments
671 }
672 }))
673 }))
674 }));
675 for arg in ty.inputs.iter().skip(1) {
676 s.inputs.push(bare_fn_arg_to_fn_arg(arg));
677 }
678 s
679}
680
681fn build_vtable(
682 base_types: &[Base],
683 class_name: &Ident,
684 sync: bool,
685 vis: &Visibility,
686 virt_methods: &[(Ident, TypeBareFn, Vec<Attribute>)],
687 overrides: &[Ident],
688) -> TokenStream {
689 let doc_name = class_name.to_string();
690 let doc = formatdoc!("
691 [`{doc_name}`] virtual methods table.
692
693 Used by the [`class_unsafe`](::basic_oop::class_unsafe) macro, not intended for direct use in code.
694 ");
695 let vtable_name = Ident::new(&(class_name.to_string() + "Vtable"), Span::call_site());
696 let methods_enum_name = Ident::new(&(class_name.to_string() + "VirtMethods"), Span::call_site());
697 let struct_ = quote! {
698 #[doc=#doc]
699 #vis struct #vtable_name(pub [*const (); #methods_enum_name::VirtMethodsCount as usize]);
700 };
701 let mut methods_impl = TokenStream::new();
702 methods_impl.append_separated(virt_methods.iter().map(|(m, ty, _)| {
703 let ty = actual_method_ty(ty.clone(), class_name, sync);
704 let ty_without_idents = fn_ty_without_idents(ty);
705 let impl_name = Ident::new(&(m.to_string() + "_impl"), Span::call_site());
706 quote! { { let f: #ty_without_idents = #class_name::#impl_name; f as *const () } }
707 }), <Token![,]>::default());
708 let mut base_vtable = base_types[0].ty.clone();
709 patch_path(&mut base_vtable, |x| x + "Vtable");
710 let base_vtable_new: Expr = parse_quote! { #base_vtable::new() };
711 let mut base_vtable_with_overrides = base_vtable_new;
712 for override_name in overrides {
713 let impl_name = Ident::new(&(override_name.to_string() + "_impl"), Span::call_site());
714 base_vtable_with_overrides = parse_quote! {
715 #base_vtable_with_overrides.#override_name(#class_name::#impl_name)
716 };
717 }
718 let base_const_name = Ident::new(
719 &(
720 to_screaming_snake(
721 class_name.to_string() + &base_types[0].ty.segments.last().unwrap().ident.to_string()
722 ) + "_VIRT_METHODS_COUNT"
723 ),
724 Span::call_site()
725 );
726 let complement_const_name = Ident::new(&(base_const_name.to_string() + "_COMPL"), Span::call_site());
727 let mut base_methods = TokenStream::new();
728 for base_type in base_types {
729 let mut base_vtable = base_type.ty.clone();
730 patch_path(&mut base_vtable, |x| x + "Vtable");
731 let base_const_name = Ident::new(
732 &(
733 to_screaming_snake(
734 class_name.to_string() + &base_type.ty.segments.last().unwrap().ident.to_string()
735 ) + "_VIRT_METHODS_COUNT"
736 ),
737 Span::call_site()
738 );
739 let complement_const_name = Ident::new(&(base_const_name.to_string() + "_COMPL"), Span::call_site());
740 for (base_method, base_method_ty, _) in &base_type.virt_methods {
741 let ty = actual_base_method_ty(base_method_ty.clone(), &base_type.ty, sync);
742 let ty_without_idents = fn_ty_without_idents(ty);
743 base_methods.extend(quote! {
744 pub const fn #base_method(
745 self,
746 f: #ty_without_idents
747 ) -> Self {
748 let vtable = unsafe { ::basic_oop::core_mem_transmute::<
749 [*const (); #methods_enum_name::VirtMethodsCount as usize],
750 ::basic_oop::VtableJoin<#base_const_name, #complement_const_name>
751 >(self.0) };
752 let vtable: ::basic_oop::VtableJoin<
753 #base_const_name,
754 #complement_const_name
755 > = ::basic_oop::VtableJoin {
756 a: #base_vtable(vtable.a).#base_method(f).0,
757 b: vtable.b
758 };
759 #vtable_name(unsafe { ::basic_oop::core_mem_transmute::<
760 ::basic_oop::VtableJoin<#base_const_name, #complement_const_name>,
761 [*const (); #methods_enum_name::VirtMethodsCount as usize]
762 >(vtable) })
763 }
764 });
765 }
766 }
767 let mut methods_tokens = TokenStream::new();
768 for (method_index, (method_name, method_ty, _)) in virt_methods.iter().enumerate() {
769 let ty = actual_method_ty(method_ty.clone(), class_name, sync);
770 let ty_without_idents = fn_ty_without_idents(ty);
771 let mut list: Punctuated<Expr, Token![,]> = Punctuated::new();
772 for i in 0 .. method_index {
773 let index = LitInt::new(&(i.to_string() + "usize"), Span::call_site());
774 list.push(parse_quote! { vtable.b[#index] });
775 }
776 list.push(parse_quote! { f as *const () });
777 for i in method_index + 1 .. virt_methods.len() {
778 let index = LitInt::new(&(i.to_string() + "usize"), Span::call_site());
779 list.push(parse_quote! { vtable.b[#index] });
780 }
781 methods_tokens.extend(quote! {
782 pub const fn #method_name(
783 self,
784 f: #ty_without_idents
785 ) -> Self {
786 let vtable = unsafe { ::basic_oop::core_mem_transmute::<
787 [*const (); #methods_enum_name::VirtMethodsCount as usize],
788 ::basic_oop::VtableJoin<#base_const_name, #complement_const_name>
789 >(self.0) };
790 let vtable: ::basic_oop::VtableJoin<
791 #base_const_name,
792 #complement_const_name
793 > = ::basic_oop::VtableJoin {
794 a: vtable.a,
795 b: [#list]
796 };
797 #vtable_name(unsafe { ::basic_oop::core_mem_transmute::<
798 ::basic_oop::VtableJoin<#base_const_name, #complement_const_name>,
799 [*const (); #methods_enum_name::VirtMethodsCount as usize]
800 >(vtable) })
801 }
802 });
803 }
804 quote! {
805 #struct_
806
807 impl #vtable_name {
808 pub const fn new() -> Self {
809 let vtable: ::basic_oop::VtableJoin<
810 #base_const_name,
811 #complement_const_name
812 > = ::basic_oop::VtableJoin {
813 a: #base_vtable_with_overrides.0,
814 b: [#methods_impl]
815 };
816 #vtable_name(unsafe { ::basic_oop::core_mem_transmute::<
817 ::basic_oop::VtableJoin<#base_const_name, #complement_const_name>,
818 [*const (); #methods_enum_name::VirtMethodsCount as usize]
819 >(vtable) })
820 }
821
822 #base_methods
823 #methods_tokens
824 }
825 }
826}
827
828fn build_methods(
829 base_types: &[Base],
830 class_name: &Ident,
831 sync: bool,
832 non_virt_methods: &[(Ident, TypeBareFn, Vec<Attribute>)],
833 virt_methods: &[(Ident, TypeBareFn, Vec<Attribute>)]
834) -> TokenStream {
835 if
836 !base_types.iter().any(|x| x.non_virt_methods.iter().chain(x.virt_methods.iter()).next().is_some())
837 && non_virt_methods.is_empty()
838 && virt_methods.is_empty()
839 {
840 return TokenStream::new();
841 }
842 let (rc, cast) = if sync {
843 (quote! { ::basic_oop::alloc_sync_Arc }, quote! { ::basic_oop::dynamic_cast_dyn_cast_arc })
844 } else {
845 (quote! { ::basic_oop::alloc_rc_Rc }, quote! { ::basic_oop::dynamic_cast_dyn_cast_rc })
846 };
847 let methods_enum_name = Ident::new(&(class_name.to_string() + "VirtMethods"), Span::call_site());
848 let mut methods_tokens = TokenStream::new();
849 for base_type in base_types {
850 let base_type_ty = base_type.ty.clone();
851 let mut base_trait = base_type_ty.clone();
852 patch_path(&mut base_trait, |x| "Is".to_string() + &x);
853 let mut base_trait_ext = base_type_ty.clone();
854 patch_path(&mut base_trait_ext, |x| x + "Ext");
855 for (method_name, method_ty, _) in base_type.non_virt_methods.iter().chain(base_type.virt_methods.iter()) {
856 let ty = actual_method_ty(method_ty.clone(), class_name, sync);
857 let signature = method_signature(&ty, method_name.clone());
858 let mut item: ImplItemFn = parse_quote! {
859 #signature {
860 let this: #rc<dyn #base_trait> = #cast(self.clone()).unwrap();
861 #base_trait_ext::#method_name(&this)
862 }
863 };
864 let Stmt::Expr(Expr::Call(call), _) = item.block.stmts.last_mut().unwrap() else { panic!() };
865 for arg in ty.inputs.iter().skip(1) {
866 let mut segments = Punctuated::new();
867 segments.push(PathSegment {
868 ident: arg.name.clone().unwrap().0,
869 arguments: PathArguments::None,
870 });
871 call.args.push(Expr::Path(ExprPath {
872 attrs: Vec::new(),
873 qself: None,
874 path: Path { leading_colon: None, segments },
875 }));
876 }
877 item.to_tokens(&mut methods_tokens);
878 }
879 }
880 for (method_name, method_ty, _) in non_virt_methods {
881 let ty = actual_method_ty(method_ty.clone(), class_name, sync);
882 let signature = method_signature(&ty, method_name.clone());
883 let name = Ident::new(&(method_name.to_string() + "_impl"), Span::call_site());
884 let mut item: ImplItemFn = parse_quote! {
885 #signature {
886 #class_name::#name(self)
887 }
888 };
889 let Stmt::Expr(Expr::Call(call), _) = item.block.stmts.last_mut().unwrap() else { panic!() };
890 for arg in ty.inputs.into_iter().skip(1) {
891 let mut segments = Punctuated::new();
892 segments.push(PathSegment {
893 ident: arg.name.unwrap().0,
894 arguments: PathArguments::None,
895 });
896 call.args.push(Expr::Path(ExprPath {
897 attrs: Vec::new(),
898 qself: None,
899 path: Path { leading_colon: None, segments },
900 }));
901 }
902 item.to_tokens(&mut methods_tokens);
903 }
904 for (method_name, method_ty, _) in virt_methods {
905 let ty = actual_method_ty(method_ty.clone(), class_name, sync);
906 let signature = method_signature(&ty, method_name.clone());
907 let ty_without_idents = fn_ty_without_idents(ty.clone());
908 let name = Ident::new(&to_pascal(method_name.to_string()), Span::call_site());
909 let mut item: ImplItemFn = parse_quote! {
910 #signature {
911 let vtable = ::basic_oop::obj::IsObj::obj(self.as_ref()).vtable();
912 let method = unsafe { ::basic_oop::core_mem_transmute::<*const (), #ty_without_idents>(
913 *vtable.add(#methods_enum_name::#name as usize)
914 ) };
915 method(self)
916 }
917 };
918 let Stmt::Expr(Expr::Call(call), _) = item.block.stmts.last_mut().unwrap() else { panic!() };
919 for arg in ty.inputs.into_iter().skip(1) {
920 let mut segments = Punctuated::new();
921 segments.push(PathSegment {
922 ident: arg.name.unwrap().0,
923 arguments: PathArguments::None,
924 });
925 call.args.push(Expr::Path(ExprPath {
926 attrs: Vec::new(),
927 qself: None,
928 path: Path { leading_colon: None, segments },
929 }));
930 }
931 item.to_tokens(&mut methods_tokens);
932 }
933 let trait_name = Ident::new(&(class_name.to_string() + "Ext"), Span::call_site());
934 let t = Ident::new(&("Is".to_string() + &class_name.to_string()), Span::call_site());
935 quote! {
936 impl #trait_name for #rc<dyn #t> {
937 #methods_tokens
938 }
939 }
940}
941
942fn build_vtable_const(class_name: &Ident) -> TokenStream {
943 let methods_enum_name = Ident::new(&(class_name.to_string() + "VirtMethods"), Span::call_site());
944 let vtable_name = Ident::new(&(class_name.to_string() + "Vtable"), Span::call_site());
945 let const_name = Ident::new(&to_screaming_snake(vtable_name.to_string()), Span::call_site());
946 quote! {
947 const #const_name: [*const (); #methods_enum_name::VirtMethodsCount as usize] = #vtable_name::new().0;
948 }
949}
950
951fn build_call_trait(
952 base_types: &[Base],
953 vis: &Visibility,
954 class_name: &Ident,
955 sync: bool,
956 non_virt_methods: &[(Ident, TypeBareFn, Vec<Attribute>)],
957 virt_methods: &[(Ident, TypeBareFn, Vec<Attribute>)]
958) -> TokenStream {
959 if
960 !base_types.iter().any(|x| x.non_virt_methods.iter().chain(x.virt_methods.iter()).next().is_some())
961 && non_virt_methods.is_empty()
962 && virt_methods.is_empty()
963 {
964 return TokenStream::new();
965 }
966 let doc_name = class_name.to_string();
967 let rc = if sync { "Arc" } else { "Rc" };
968 let doc = formatdoc!("
969 [`{doc_name}`] methods extension trait.
970
971 Implemented by the `{rc}<{doc_name}>` type for convenient method calling syntax.
972 ");
973 let mut methods_tokens = TokenStream::new();
974 for base_type in base_types {
975 for
976 (method_name, method_ty, method_attrs)
977 in
978 base_type.non_virt_methods.iter().chain(base_type.virt_methods.iter())
979 {
980 for attr in method_attrs {
981 attr.to_tokens(&mut methods_tokens);
982 }
983 let ty = actual_method_ty(method_ty.clone(), class_name, sync);
984 let signature = method_signature(&ty, method_name.clone());
985 signature.to_tokens(&mut methods_tokens);
986 <Token![;]>::default().to_tokens(&mut methods_tokens);
987 }
988 }
989 for (method_name, method_ty, method_attrs) in non_virt_methods.iter().chain(virt_methods.iter()) {
990 for attr in method_attrs {
991 attr.to_tokens(&mut methods_tokens);
992 }
993 let ty = actual_method_ty(method_ty.clone(), class_name, sync);
994 let signature = method_signature(&ty, method_name.clone());
995 signature.to_tokens(&mut methods_tokens);
996 <Token![;]>::default().to_tokens(&mut methods_tokens);
997 }
998 let trait_name = Ident::new(&(class_name.to_string() + "Ext"), Span::call_site());
999 quote! {
1000 #[doc=#doc]
1001 #vis trait #trait_name {
1002 #methods_tokens
1003 }
1004 }
1005}
1006
1007fn build(inherits: ItemStruct, class: ItemStruct) -> Result<TokenStream, Diagnostic> {
1008 let Some(sync) = parse_base_sync(&inherits) else {
1009 return Err(inherits.span().error("Invalid base class"));
1010 };
1011 let base_types = parse_base_types(inherits)?;
1012 let class = Class::parse(class)?;
1013 let new_inherits = build_inherits(
1014 &base_types, &class.name, sync, &class.non_virt_methods, &class.virt_methods
1015 );
1016 let struct_ = build_struct(&base_types, &class.attrs, &class.vis, &class.name, &class.fields);
1017 let trait_ = build_trait(&base_types, &class.vis, &class.name, sync);
1018 let methods_enum = build_virt_methods_enum(
1019 &base_types[0].ty, &class.vis, &class.name, &class.virt_methods
1020 );
1021 let consts_for_vtable = build_consts_for_vtable(&class.name, &base_types);
1022 let vtable = build_vtable(
1023 &base_types, &class.name, sync, &class.vis, &class.virt_methods, &class.overrides
1024 );
1025 let vtable_const = build_vtable_const(&class.name);
1026 let call_trait = build_call_trait(
1027 &base_types, &class.vis, &class.name, sync, &class.non_virt_methods, &class.virt_methods
1028 );
1029 let methods = build_methods(
1030 &base_types, &class.name, sync, &class.non_virt_methods, &class.virt_methods
1031 );
1032 Ok(quote! {
1033 #new_inherits
1034 #struct_
1035 #trait_
1036 #methods_enum
1037 #consts_for_vtable
1038 #vtable
1039 #vtable_const
1040 #call_trait
1041 #methods
1042 })
1043}
1044
1045#[import_tokens_attr(::basic_oop::macro_magic)]
1046#[proc_macro_attribute]
1047pub fn class_unsafe(attr: proc_macro::TokenStream, input: proc_macro::TokenStream) -> proc_macro::TokenStream {
1048 let inherits = parse_macro_input!(attr as ItemStruct);
1049 let class = parse_macro_input!(input as ItemStruct);
1050 match build(inherits, class) {
1051 Ok(tokens) => tokens.into(),
1052 Err(diag) => diag.emit_as_expr_tokens().into(),
1053 }
1054}