psibase_macros_lib/
service_tables_macro.rs1mod tables;
2
3use darling::{ast::NestedMeta, Error, FromMeta};
4use proc_macro2::TokenStream;
5use proc_macro_error::{abort, emit_error};
6use quote::quote;
7use std::{collections::HashMap, str::FromStr};
8use syn::{Ident, Item, ItemMod, Type};
9use tables::{is_table_attr, process_service_tables, process_table_schema_root};
10
11#[derive(Debug, FromMeta)]
12#[darling(default)]
13pub struct Options {
14 pub psibase_mod: String,
15}
16
17impl Default for Options {
18 fn default() -> Self {
19 Options {
20 psibase_mod: "psibase".into(),
21 }
22 }
23}
24
25pub fn service_tables_macro_impl(attr: TokenStream, item: TokenStream) -> TokenStream {
26 let attr_args = match NestedMeta::parse_meta_list(attr) {
27 Ok(v) => v,
28 Err(e) => {
29 return TokenStream::from(Error::from(e).write_errors());
30 }
31 };
32 let options: Options = match Options::from_list(&attr_args) {
33 Ok(val) => val,
34 Err(err) => {
35 return err.write_errors().into();
36 }
37 };
38 let psibase_mod = proc_macro2::TokenStream::from_str(&options.psibase_mod).unwrap();
39
40 let item = syn::parse2::<syn::Item>(item).unwrap();
41 match item {
42 Item::Mod(mut impl_mod) => {
43 process_mod(&psibase_mod, &mut impl_mod);
44 quote! { #impl_mod }.into()
45 }
46 _ => {
47 abort!(
48 item,
49 "service_tables attribute may only be used on a module"
50 )
51 }
52 }
53}
54
55pub(crate) fn process_mod(psibase_mod: &proc_macro2::TokenStream, impl_mod: &mut ItemMod) {
56 if let Some((_, items)) = &mut impl_mod.content {
57 let mut table_structs: HashMap<Ident, Vec<usize>> = HashMap::new();
58 let mut table_names = Vec::new();
59
60 for (item_index, item) in items.iter_mut().enumerate() {
62 if let Item::Struct(s) = item {
63 if s.attrs.iter().any(is_table_attr) {
64 table_structs.insert(s.ident.clone(), vec![item_index]);
65 }
66 }
67 }
68
69 for (item_index, item) in items.iter().enumerate() {
71 if let Item::Impl(i) = item {
72 if let Type::Path(type_path) = &*i.self_ty {
73 if let Some(tpps) = type_path.path.segments.first() {
74 table_structs.entry(tpps.ident.clone()).and_modify(|refs| {
75 refs.push(item_index);
76 });
77 }
78 }
79 }
80 }
81
82 let mut processed_tables = Vec::new();
84 for (tb_name, items_idxs) in table_structs.iter() {
85 let table_idx =
86 process_service_tables(psibase_mod, tb_name, items, items_idxs, &mut table_names);
87 if table_idx.is_ok() {
88 processed_tables.push((tb_name, table_idx.unwrap()));
89 }
90 }
91
92 process_table_schema_root(psibase_mod, items, table_names);
93
94 processed_tables.sort_by_key(|t| t.1);
96 for (expected_idx, (table_struct, tb_index)) in processed_tables.iter().enumerate() {
97 if *tb_index as usize != expected_idx {
98 emit_error!(
99 table_struct,
100 format!("Missing expected table index {}; tables may not have gaps and may not be removed or reordered.", expected_idx)
101 );
102 }
103 }
104 } else {
105 emit_error!(
106 impl_mod,
107 "#[psibase::service_tables] module must have inline contents"
108 )
109 }
110}