/* * Copyright 2024 Luca Fulchir * * Licensed under the Apache License, Version 2.0 with LLVM exception (the * "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License and of the exception at * * http://www.apache.org/licenses/LICENSE-2.0 * https://spdx.org/licenses/LLVM-exception.html * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ use ::proc_macro::TokenStream; use ::quote::quote; use ::syn::{parse::Parser, parse_macro_input, DeriveInput}; use syn::{parse_quote, spanned::Spanned}; pub(crate) fn pkg_fn_to_code( _attrs: TokenStream, input: TokenStream, ) -> TokenStream { let ast_orig: ::syn::ItemFn = parse_macro_input!(input as ::syn::ItemFn); let mut ast_new = ast_orig.clone(); let new_fn_name = quote::format_ident!("{}_code", ast_orig.sig.ident); ast_new.sig.ident = quote::format_ident!("{}_code", new_fn_name); use ::quote::ToTokens; let fn_block = ast_orig.block.to_token_stream(); let asdf = quote! { #ast_orig fn #new_fn_name (&self) -> ::proc_macro2::TokenStream { ::quote::quote! #fn_block } }; asdf.into() } pub(crate) fn package(attrs: TokenStream, input: TokenStream) -> TokenStream { let local = parse_macro_input!(input as ::syn::ItemStruct); let mut path = parse_macro_input!(attrs as ::syn::Path); let last = path.segments.last().unwrap(); if last.arguments.is_empty() { // make sure all the packages have generics, and if not add the // repo as generic aka: rewrite // "#[package(my::pkg)]" -->> "#[package(my::pkg)])" // unless it's '::bok::PkgEmpty' if path.segments.len() != 2 || path.segments[0].ident.to_string() != "bok" || path.segments[1].ident.to_string() != "PkgEmpty" { let repo_argument = { let generic_id: ::syn::Path = ::syn::parse_quote!(id); generic_id.segments.last().unwrap().arguments.clone() }; path.segments.last_mut().unwrap().arguments = repo_argument; } } return quote! { use ::bok_macro::package_path; #[::bok_macro::package_path(#path)] #local } .into(); } pub(crate) fn package_path( attrs: TokenStream, input: TokenStream, __source_path: TokenStream, ) -> TokenStream { use ::syn::{spanned::Spanned, Fields, ItemStruct}; let local = parse_macro_input!(input as ItemStruct); let local_span = local.span(); let Fields::Named(local_fields) = local.fields else { use ::syn::spanned::Spanned; return ::syn::Error::new( local.fields.span(), "#[::bok::package()]: unnamed fields are not supported", ) .to_compile_error() .into(); }; let local_attrs = local.attrs.iter(); let ident = local.ident; let vis = local.vis; let local_generics = local.generics; if local_generics.params.len() != 0 { return ::syn::Error::new( local_span, "#[::bok::package()]: a package can not have generics", ) .to_compile_error() .into(); }; let generic_struct: ::syn::ItemStruct = ::syn::parse_quote! { pub struct test where R: ::bok::Repository + 'static {} }; let (_, generics, where_clause) = generic_struct.generics.split_for_impl(); let source_path = parse_macro_input!(__source_path as ::syn::Path); let base = parse_macro_input!(attrs as ItemStruct); let Fields::Named(base_fields) = &base.fields else { use ::syn::spanned::Spanned; return ::syn::Error::new( base.fields.span(), "#[::bok::package(..)]: base: unnamed fields are not supported", ) .to_compile_error() .into(); }; // make sure the 'version' field is the first after the user's fields // we will use as a separator from the other imported fields let mut base_fields_extra = Vec::with_capacity(base.fields.len()); match base_fields.named.iter().find(|&x| match &x.ident { Some(id_local) => id_local == "version", _ => false, }) { Some(_) => { let pkg_version = ::syn::Field::parse_named .parse2(quote! { version: ::bok::Version }) .unwrap(); let base_marker = ::syn::Field::parse_named .parse2(quote! { _bok_base: ::std::marker::PhantomData<#source_path> }) .unwrap(); let repo_marker = ::syn::Field::parse_named .parse2(quote! { _bok_repo: ::std::marker::PhantomData }) .unwrap(); base_fields_extra.push(pkg_version); base_fields_extra.push(base_marker); base_fields_extra.push(repo_marker); } None => { return ::syn::Error::new( local_span, "pkg: \"".to_owned() + &ident.to_string() + "\" base package \"" + &base.ident.to_string() + "\" does not have 'version' field", ) .to_compile_error() .into(); } } for f in base_fields.named.iter() { let Some(id) = &f.ident else { continue }; // don't add 'version' again if id.to_string() == "version" || id.to_string() == "_bok_base" || id.to_string() == "_bok_repo" { continue; } // compiler error if you try to extend a package while // reusing its fields names if let Some(repeated) = local_fields.named.iter().find(|&x| { let Some(id_local) = &x.ident else { return false; }; id_local.to_string() == id.to_string() }) { return ::syn::Error::new( local_span, "pkg: \"".to_owned() + &ident.to_string() + "\" field \"" + &repeated.ident.as_ref().unwrap().to_string() + "\" is already in package " + &base.ident.to_string() + "\" or its base", ) .to_compile_error() .into(); } base_fields_extra.push(f.clone()); } let mut all_fields = Vec::new(); all_fields.extend(local_fields.named.iter()); all_fields.extend(base_fields_extra.iter()); quote! { #(#local_attrs) * #[::macro_magic::export_tokens] #[derive(::bok::Package, ::std::fmt::Debug, Clone)] #vis struct #ident #generics #where_clause{ #(#all_fields), * } } .into() } pub(crate) fn deps(attrs: TokenStream, input: TokenStream) -> TokenStream { let mut packages = parse_macro_input!(attrs as crate::PathList); let local = parse_macro_input!(input as ::syn::ItemStruct); // make sure all the packages have generics, and if not add the repo as // generic aka: rewrite // "#[deps(my::pkg)]" -->> "#[deps(my::pkg)])" let mut rewrite = false; let repo_argument = { let generic_id: ::syn::Path = ::syn::parse_quote!(id); generic_id.segments.last().unwrap().arguments.clone() }; for p in packages.0.iter_mut() { let last = p.segments.last_mut().unwrap(); if last.arguments.is_empty() { rewrite = true; last.arguments = repo_argument.clone(); } } if rewrite { let p_list = packages.0.into_iter(); return quote! { #[::bok::deps(#(#p_list,)*)] #local } .into(); } let packages = packages; // remove mut; let ::syn::Fields::Named(local_fields) = local.fields else { use ::syn::spanned::Spanned; return ::syn::Error::new( local.fields.span(), "unnamed fields are not supported", ) .to_compile_error() .into(); }; let local_attrs = local.attrs.iter(); let local_fields = local_fields.named.iter(); let (_, generics, where_clause) = local.generics.split_for_impl(); let ident = local.ident; let vis = local.vis; let pkg_trait = quote::format_ident!("BokDeps{}", ident); let pkg_builder_trait = quote::format_ident!("BokBuilderDeps{}", ident); use ::convert_case::{Case, Casing}; let deps = packages .0 .iter() .map(|x| { let ident = x.segments.last().unwrap().ident.clone(); quote::format_ident!("{}", ident.to_string().to_case(Case::Snake)) }) .collect::>(); let deps_builders = packages .0 .iter() .map(|x| { let ident = x.segments.last().unwrap().ident.clone(); quote::format_ident!( "{}_builder", ident.to_string().to_case(Case::Snake) ) }) .collect::>(); let deps_builders2 = deps_builders.clone(); let deps_options = packages .0 .iter() .map(|x| { let mut trait_option_path = x.clone(); let last_segment = trait_option_path.segments.last_mut().unwrap(); last_segment.ident = quote::format_ident!("BokOptions{}", last_segment.ident); last_segment.arguments = ::syn::PathArguments::None; trait_option_path }) .collect::>(); let deps_options2 = deps_options.clone(); let deps_options3 = deps_options.clone(); quote! { #(#local_attrs) * #vis struct #ident #generics #where_clause{ #(#local_fields), * } #[::macro_magic::export_tokens(#pkg_trait)] pub trait #pkg_trait: ::bok::Repository { #(fn #deps(&self) -> ::std::boxed::Box;) * #(fn #deps_builders(&self) -> ::std::boxed::Box;) * } #[::macro_magic::export_tokens(#pkg_builder_trait)] pub trait #pkg_builder_trait { #(fn #deps_builders2(&self) -> ::std::boxed::Box;) * } } .into() } // package_impl has 3 stages, and uses macro_magic import for the last two: // * called without parameters => add default ::bok::Pkg functions, then add the // same macro with the package type as argument // * called with the package type as argument => get the base type, then add the // same macro with the base type IMPL as argument // * called with the a package impl as argument, copy the non-implemented // functions from the base pub(crate) fn package_impl( _attrs: TokenStream, input: TokenStream, ) -> TokenStream { let mut ast = parse_macro_input!(input as ::syn::ItemImpl); let Some((_, trait_name, _)) = &ast.trait_ else { return ::syn::Error::new( ast.span(), "package_impl: trait name not foun", ) .to_compile_error() .into(); }; let s = &trait_name.segments; if s.len() != 2 || s[0].ident.to_string() != "bok" || (s[1].ident.to_string() != "Pkg" && s[1].ident.to_string() != "PkgBuilder") { // // only add the generic parameter and nothing else // let ::syn::Type::Path(t_id) = &ast.self_ty.as_ref() else { return ::syn::Error::new( ast.span(), "package_impl: Type is not Path", ) .to_compile_error() .into(); }; let trait_deps = ::quote::format_ident!( "BokDeps{}", t_id.path.segments.last().unwrap().ident ); let g: ::syn::ItemImpl = ::syn::parse_quote! { impl trait_name for #t_id where R: ::bok::Repository + #trait_deps {} }; ast.generics = g.generics; ast.self_ty = g.self_ty; // TODO: if the trait is `::std::default::Default` // then add the `_bok_base` and `_bok_repo` defaults automatically return quote! { #ast } .into(); } let name_pkg = match &*ast.self_ty { ::syn::Type::Path(tp) => match tp.path.get_ident() { Some(id) => id.clone(), _ => { return ::syn::Error::new( ast.span(), "package_impl: type::Path but no ident", ) .to_compile_error() .into() } }, _ => { return ::syn::Error::new( ast.span(), "package_impl: not a type::Path", ) .to_compile_error() .into() } }; if trait_name.segments[1].ident.to_string() == "PkgBuilder" { return quote! { use ::bok_macro::package_impl_builder; #[::bok_macro::package_impl_builder(#name_pkg)] #ast } .into(); } let full_path_name = proc_macro2::Ident::new( &("_bok_pkg_".to_owned() + &name_pkg.to_string()), proc_macro2::Span::call_site(), ); quote! { use ::bok_macro::package_impl_base; #[::bok_macro::package_impl_base(#name_pkg)] #[::macro_magic::export_tokens(#full_path_name)] #ast } .into() } // get the actual base type and re-call the macro with the impl symbols pub(crate) fn package_impl_base( attrs: TokenStream, input: TokenStream, ) -> TokenStream { // This is a two-phase macro // * The first time this is called the argument will be the same path as the // struct that implements ::bok::Pkg // * The macro will be rewritten with the base package name that is extended // by the package // And now that we have the proper base name we can add the missing // methods from the base let mut ast = parse_macro_input!(input as ::syn::ItemImpl); let name_pkg = match &*ast.self_ty { ::syn::Type::Path(tp) => match tp.path.get_ident() { Some(id) => id.clone(), _ => panic!("::bok::package_impl expected path ident"), }, _ => panic!("::bok::package_impl expected path"), }; let Ok(syntax_tree) = syn::parse::<::syn::ItemStruct>(attrs.clone()) else { return package_impl_base_add(attrs, &mut ast); }; // we were passed an exported struct. first phase. if syntax_tree.ident.to_string() != name_pkg.to_string() { return ::syn::Error::new( ast.span(), "package_impl: called on \"".to_owned() + &name_pkg.to_string() + "\" with wrong initial name" + &syntax_tree.ident.to_string() + "\" ", ) .to_compile_error() .into(); } // go through the struct definition, extract the base type // from the `_bok_base` phantom data marker let base_path = match syntax_tree.fields.iter().find(|&x| { let Some(id) = &x.ident else { return false; }; return id.to_string() == "_bok_base"; }) { Some(field) => match &field.ty { ::syn::Type::Path(tp) => { if tp.path.segments.len() != 3 || tp.path.segments[0].ident.to_string() != "std" || tp.path.segments[1].ident.to_string() != "marker" || tp.path.segments[2].ident.to_string() != "PhantomData" { return ::syn::Error::new( ast.span(), "package_impl: called on \"".to_owned() + &name_pkg.to_string() + "\" base is not PhantomData, " + "what are you doing?", ) .to_compile_error() .into(); } let ::syn::PathArguments::AngleBracketed(psa) = &tp.path.segments[2].arguments else { return ::syn::Error::new( ast.span(), "package_impl: called on \"".to_owned() + &name_pkg.to_string() + "\" base PhantomData has no arguments " + "what are you doing?", ) .to_compile_error() .into(); }; if psa.args.len() != 1 { return ::syn::Error::new( ast.span(), "package_impl: called on \"".to_owned() + &name_pkg.to_string() + "\" base PhantomData has too many arguments " + "what are you doing?", ) .to_compile_error() .into(); } let ::syn::GenericArgument::Type(::syn::Type::Path( base_pkg_path, )) = &psa.args[0] else { return ::syn::Error::new( ast.span(), "package_impl: called on \"".to_owned() + &name_pkg.to_string() + "\" base PhantomData has wrong argument type " + "what are you doing?", ) .to_compile_error() .into(); }; base_pkg_path.path.clone() } _ => { return ::syn::Error::new( ast.span(), "package_impl: called on \"".to_owned() + &name_pkg.to_string() + "\" base of unknown type", ) .to_compile_error() .into(); } }, None => { return ::syn::Error::new( ast.span(), "package_impl: called on \"".to_owned() + &name_pkg.to_string() + "\" can't find base", ) .to_compile_error() .into(); } }; // now, `base_path` is an absolute path. // but it does not mean that it is in our crate // we need to maintain the whole path and change only the last ident, // to the one generated by us instead of by macro_magic // now `base_type` is the base struct. but what we actually want is // to import the `impl ::bok::Pkg` for `base_type` let mut exported_impl_name = base_path.clone(); let last_segment_n = exported_impl_name.segments.len() - 1; let last_segment = &mut exported_impl_name.segments[last_segment_n]; last_segment.ident = ::quote::format_ident!("_bok_pkg_{}", last_segment.ident); return quote! { #[::bok_macro::package_impl_base(#exported_impl_name)] #ast } .into(); } // implement PkgBuilder pub(crate) fn package_impl_builder( attrs: TokenStream, input: TokenStream, __source_path: TokenStream, ) -> TokenStream { let local = parse_macro_input!(input as ::syn::ItemImpl); let pkg = parse_macro_input!(attrs as ::syn::ItemStruct); let local_path = &local.trait_.as_ref().unwrap().1; if local_path.segments.len() != 2 || local_path.segments[0].ident.to_string() != "bok" || local_path.segments[1].ident.to_string() != "PkgBuilder" { return ::syn::Error::new( local.span(), "package_impl_builder: can only be called on `impl \ ::bok::PkgBuilder`", ) .to_compile_error() .into(); } let local_funcs = local.items.iter().filter(|&it| match it { ::syn::ImplItem::Fn(_) => true, _ => false, }); let ::syn::Fields::Named(elements) = pkg.fields else { return ::syn::Error::new( local.span(), "package_impl_builder: only named fields supported in base pkg", ) .to_compile_error() .into(); }; let pkg_name = pkg.ident; let name_builder = ::quote::format_ident!("{}Builder", pkg_name); let trait_deps = ::quote::format_ident!("BokDeps{}", pkg_name); let trait_options = ::quote::format_ident!("BokOptions{}", pkg_name); let non_opt_fields = elements.named.iter().filter_map(|field| { if let Some(id) = field.ident.clone() { if id.to_string() == "version" || id.to_string() == "_bok_base" || id.to_string() == "_bok_repo" { return None; } } if field .attrs .iter() .find(|&a| a.path().is_ident("option")) .is_none() { return None; } match &field.ty { syn::Type::Path(pth) => { let first_path = pth.path.segments.first().unwrap(); if first_path.ident == "Option" { None } else { let id = &field.ident; Some(quote! {#id}) } } t => Some(quote! {#t}), } }); let opt_fields = elements.named.iter().filter_map(|field| { if let Some(id) = field.ident.clone() { if id.to_string() == "version" || id.to_string() == "_bok_base" || id.to_string() == "_bok_repo" { return None; } } if field .attrs .iter() .find(|&a| a.path().is_ident("option")) .is_none() { return None; } match &field.ty { syn::Type::Path(pth) => { let first_path = pth.path.segments.first().unwrap(); if first_path.ident == "Option" { let id = &field.ident; Some(quote! {#id}) } else { None } } _ => None, } }); let non_opt_types = elements.named.iter().filter_map(|field| { if let Some(id) = field.ident.clone() { if id.to_string() == "version" || id.to_string() == "_bok_base" || id.to_string() == "_bok_repo" { return None; } } if field .attrs .iter() .find(|&a| a.path().is_ident("option")) .is_none() { return None; } match &field.ty { syn::Type::Path(pth) => { let first_path = pth.path.segments.first().unwrap(); if first_path.ident == "Option" { None } else { let t = &field.ty; Some(quote! {#t}) } } _ => None, } }); let opt_types = elements.named.iter().filter_map(|field| { if let Some(id) = field.ident.clone() { if id.to_string() == "version" || id.to_string() == "_bok_base" || id.to_string() == "_bok_repo" { return None; } } if field .attrs .iter() .find(|&a| a.path().is_ident("option")) .is_none() { return None; } if let ::syn::Type::Path(pth) = &field.ty { let first_path = pth.path.segments.first().unwrap(); if first_path.ident == "Option" { if let syn::PathArguments::AngleBracketed( syn::AngleBracketedGenericArguments { args, .. }, ) = &first_path.arguments { if let Some(syn::GenericArgument::Type(syn::Type::Path( p, ))) = args.first() { let id = &p.path.segments.first().unwrap().ident; return Some(quote! {#id}); } } } } None }); let non_opt_types2 = non_opt_types.clone(); let non_opt_types3 = non_opt_types.clone(); let non_opt_types4 = non_opt_types.clone(); let non_opt_fields2 = non_opt_fields.clone(); let non_opt_fields3 = non_opt_fields.clone(); let non_opt_fields4 = non_opt_fields.clone(); let non_opt_fields5 = non_opt_fields.clone(); let non_opt_fields6 = non_opt_fields.clone(); let non_opt_fields7 = non_opt_fields.clone(); let non_opt_fields8 = non_opt_fields.clone(); let opt_fields2 = opt_fields.clone(); let opt_fields3 = opt_fields.clone(); let opt_fields4 = opt_fields.clone(); let opt_fields5 = opt_fields.clone(); let opt_fields6 = opt_fields.clone(); let opt_types2 = opt_types.clone(); let opt_types3 = opt_types.clone(); let opt_types4 = opt_types.clone(); let full_export_path_name = proc_macro2::Ident::new( &("_bok_pkgbuilder_".to_owned() + &pkg_name.to_string()), proc_macro2::Span::call_site(), ); quote! { #[derive(::std::default::Default, ::std::fmt::Debug)] pub struct #name_builder where R: ::bok::Repository + 'static + #trait_deps, { _bok_repo: ::std::marker::PhantomData, #(#non_opt_fields: ::std::option::Option<#non_opt_types>,)* #(#opt_fields: ::std::option::Option<#opt_types>,)* } #[::macro_magic::export_tokens(#full_export_path_name)] impl ::bok::PkgBuilder for #name_builder where R: ::bok::Repository + 'static + #trait_deps, { fn name(&self) -> ::bok::PkgName { use ::bok::Pkg; #pkg_name::::default().name() } fn path(&self) -> ::bok::Path<::bok::PkgName> { use ::bok::Pkg; #pkg_name::::default().path() } fn version(&self) -> ::bok::Version { use ::bok::Pkg; #pkg_name::::default().version() } fn as_any(&self) -> &dyn ::std::any::Any { self } fn as_any_mut(&mut self) -> &mut dyn ::std::any::Any { self } fn default_unused(&mut self) -> &mut dyn ::bok::PkgBuilder { let def = #pkg_name::::default(); #(if self.#non_opt_fields5.is_none() { self.#non_opt_fields5 = Some(def.#non_opt_fields5); })* self } fn build(&mut self) -> Result< Box, ::std::boxed::Box > { #(if self.#non_opt_fields2.is_none() { return ::std::result::Result::Err("unset field".into()); })* let mut pkg = ::std::boxed::Box::new(#pkg_name::::default()); #(pkg.#non_opt_fields3 = self.#non_opt_fields3.clone().unwrap();) * #(pkg.#opt_fields2 = self.#opt_fields2.clone();) * Ok(pkg) } #(#local_funcs) * } impl #name_builder where R: ::bok::Repository +'static + #trait_deps, { pub fn new() -> Self { Self { _bok_repo: ::std::marker::PhantomData::default(), #(#non_opt_fields6: None,)* #(#opt_fields4: None,)* } } pub fn as_any(&self) -> &dyn ::std::any::Any { self } pub fn as_any_mut(&mut self) -> &mut dyn ::std::any::Any { self } pub fn as_builder(&self) -> &dyn ::bok::PkgBuilder { self } pub fn as_builder_mut(&mut self) -> &mut dyn ::bok::PkgBuilder { self } #(pub fn #non_opt_fields4 (&mut self, val : #non_opt_types2) -> ::std::result::Result<&mut Self, ()> { if self.#non_opt_fields4 != None && self.#non_opt_fields4 != Some(val) { panic!("Package \"{}\": mandatory attribute set multiple times: \"{}\"", ::std::stringify!(#pkg_name), ::std::stringify!(#non_opt_fields4)); } // FIXME: 'val' validation self.#non_opt_fields4 = Some(val); Ok(self) }) * #(pub fn #opt_fields3 (&mut self, val : #opt_types2) -> ::std::result::Result<&mut Self, ()> { if self.#opt_fields3 != None && self.#opt_fields3 != Some(val) { panic!("Package \"{}\": optional attribute set multiple times: \"{}\"", ::std::stringify!(#pkg_name), ::std::stringify!(#opt_fields3)); } // FIXME: 'val' validation self.#opt_fields3 = Some(val); Ok(self) }) * } pub trait #trait_options: ::bok::PkgBuilder { #(fn #non_opt_fields7 (&mut self, val : #non_opt_types3) -> ::std::result::Result<(),()>;) * #(fn #opt_fields5 (&mut self, val : #opt_types3) -> ::std::result::Result<(),()>;) * } impl #trait_options for #name_builder where R: ::bok::Repository +'static + #trait_deps, { #(fn #non_opt_fields8 (&mut self, val : #non_opt_types4) -> ::std::result::Result<(),()> { self.#non_opt_fields8(val)?; Ok(()) }) * #(fn #opt_fields6 (&mut self, val : #opt_types4) -> ::std::result::Result<(),()>{ self.#opt_fields6(val)? Ok(()) }) * } } .into() } // implements ::bok::Pkg: // * add base methods // * copy the non-implemented methods from the base pub(crate) fn package_impl_base_add( attrs: TokenStream, ast: &mut ::syn::ItemImpl, ) -> TokenStream { let name_pkg = match &*ast.self_ty { ::syn::Type::Path(tp) => match tp.path.get_ident() { Some(id) => id.clone(), _ => panic!("::bok::package_impl expected path ident"), }, _ => panic!("::bok::package_impl expected path"), }; let base_ast = parse_macro_input!(attrs as ::syn::ItemImpl); let name: ::syn::ImplItem = ::syn::parse_quote! { fn name(&self) -> ::bok::PkgName { (module_path!().to_owned() + "::" + ::std::stringify!(#name_pkg)).as_str().into() } }; let path: ::syn::ImplItem = ::syn::parse_quote! { fn path(&self) -> ::bok::Path<::bok::PkgName> { (module_path!().to_owned() + "::" + ::std::stringify!(#name_pkg)).as_str().into() } }; let version: ::syn::ImplItem = ::syn::parse_quote! { fn version(&self) -> ::bok::Version { self.version.clone() } }; let any: ::syn::ImplItem = ::syn::parse_quote! { fn as_any(&self) -> &dyn ::std::any::Any { self } }; let any_mut: ::syn::ImplItem = ::syn::parse_quote! { fn as_any_mut(&mut self) -> &mut dyn ::std::any::Any { self } }; let hash: ::syn::ImplItem = ::syn::parse_quote! { fn hash(&self) -> ::bok::Hash { use ::sha3::{Digest}; let mut hasher = ::sha3::Sha3_256::new(); let mut pkg_vars = String::new(); use ::std::fmt::Write; write!(&mut pkg_vars, "{:?}", self, ).ok(); hasher.update(&pkg_vars); hasher.update(self.to_string()); let hash = hasher.finalize(); ::bok::Hash::Sha3(hash) } }; let hash_code: ::syn::ImplItem = ::syn::parse_quote! { fn hash_code(&self) -> ::bok::Hash { use ::sha3::{Digest}; let mut hasher = ::sha3::Sha3_256::new(); hasher.update(self.to_string()); let hash = hasher.finalize(); ::bok::Hash::Sha3(hash) } }; for mut impl_item in ast.items.iter_mut() { if let ::syn::ImplItem::Fn(ref mut fn_impl) = &mut impl_item { if ["prepare", "configure", "build", "test", "install"] .iter() .find(|&f| *f == fn_impl.sig.ident.to_string().as_str()) .is_none() { continue; } let fn_with_code: ::syn::ImplItemFn = ::syn::parse_quote! { #[::bok::pkg_fn_to_code] #fn_impl }; *fn_impl = fn_with_code; } else { panic!("wtf"); } } let trait_deps = ::quote::format_ident!("BokDeps{}", name_pkg); let generic_struct: ::syn::ItemImpl = ::syn::parse_quote! { impl test for #name_pkg where R: ::bok::Repository + 'static + #trait_deps, {} }; ast.generics = generic_struct.generics; ast.self_ty = generic_struct.self_ty; ast.items.push(name); ast.items.push(path); ast.items.push(version); ast.items.push(any); ast.items.push(any_mut); ast.items.push(hash); ast.items.push(hash_code); fn maybe_add_fn( name_pkg: &str, ast: &mut ::syn::ItemImpl, base_ast: &::syn::ItemImpl, name: &str, ) -> Result<(), TokenStream> { let Some(base_fn) = base_ast.items.iter().find(|&f| { let ::syn::ImplItem::Fn(func) = f else { return false; }; return func.sig.ident.to_string() == name; }) else { return Err(::syn::Error::new( ast.span(), "package_impl: called on \"".to_owned() + name_pkg + "\" maybe_add_fn can't find " + name + " on base", ) .to_compile_error() .into()); }; let ::syn::ImplItem::Fn(mut base_fn_no_code) = base_fn.clone() else { return Err(::syn::Error::new( ast.span(), "package_impl: called on \"".to_owned() + name_pkg + "\" what was Fn is not anymore", ) .to_compile_error() .into()); }; base_fn_no_code.attrs.retain_mut(|a| { let ::syn::Meta::Path(mp) = &a.meta else { return true; }; if mp.segments.len() == 2 && mp.segments[0].ident.to_string() == "bok" && mp.segments[1].ident.to_string() == "pkg_fn_to_code" { return false; } return true; }); if ast .items .iter() .find(|&f| { let ::syn::ImplItem::Fn(func) = f else { return false; }; return func.sig.ident.to_string() == name; }) .is_none() { let new_fn: ::syn::ImplItem = parse_quote!( #[::bok::pkg_fn_to_code] #base_fn_no_code ); ast.items.push(new_fn); } Ok(()) } for name in ["prepare", "configure", "build", "test", "install"].into_iter() { let Err(e) = maybe_add_fn(&name_pkg.to_string(), ast, &base_ast, name) else { continue; }; return e; } quote! { #ast } .into() } pub(crate) fn derive_package(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let name = input.ident.clone(); let pkg_struct = quote! { #input }; let mut pkg_struct_str = String::new(); { use ::core::fmt::Write; write!(&mut pkg_struct_str, "{}", pkg_struct) .expect("can't write package struct into string"); } let trait_deps = ::quote::format_ident!("BokDeps{}", name); let expanded = quote! { impl ::core::fmt::Display for #name where R: ::bok::Repository + #trait_deps, { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) ->::core::fmt::Result { <#name as ::bok::PkgCode>::fmt(self, f) } } impl ::bok::PkgCode for #name where R: ::bok::Repository + 'static + #trait_deps, { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) ->::core::fmt::Result { // FIXME: this sounds convoluted. // first we turn everything into a string, then we // parse the string into tokenstream again to format it // reason: the `prettyplease` works on `::syn::File` // and I can't find an easy translation // `TokenStream2 -> TokenStream` use ::bok::Pkg; // don't add a method if it is just the base Trait implementation fn maybe_add_fn(out: &mut String, name: &str, fn_str: &str) -> ::core::fmt::Result { //FIXME: check with the code exported from macro_magic base let str_base = "self . base () . unwrap () . ".to_owned() + name + " ()"; if fn_str.trim() != str_base { use ::core::fmt::Write; write!(out, "\ fn {}(&self) -> Result<(), ()> {{\n\ {}\n\ }}\n", name, fn_str, ) } else { ::core::fmt::Result::Ok(()) } }; use ::core::fmt::Write; let pkg_empty = ::bok::PkgEmpty::default(); let mut pkg_string = String::new(); // FIXME: filter out the things added from base pkg write!(&mut pkg_string, "#[::bok::package(::bok::PkgEmpty)]\n\ {}", #pkg_struct_str)?; write!(&mut pkg_string, "#[::bok::package_impl]\n\ impl ::bok::Pkg for {} {{\n", ::std::stringify!(#name) )?; maybe_add_fn(&mut pkg_string, "prepare", &self.prepare_code().to_string())?; maybe_add_fn(&mut pkg_string, "configure", &self.configure_code().to_string())?; maybe_add_fn(&mut pkg_string, "build", &self.build_code().to_string())?; maybe_add_fn(&mut pkg_string, "test", &self.test_code().to_string())?; maybe_add_fn(&mut pkg_string, "install", &self.install_code().to_string())?; write!(&mut pkg_string, "}}\n")?; let re_parsed = ::syn::parse_file(&pkg_string).unwrap(); let formatted = prettyplease::unparse(&re_parsed); f.write_str(&formatted) } } impl #name where R: ::bok::Repository + 'static + #trait_deps + ::core::default::Default, { pub fn as_any(&self) -> &dyn ::std::any::Any { self } pub fn as_any_mut(&mut self) -> &mut dyn ::std::any::Any { self } pub fn as_pkg(&self) -> &dyn ::bok::Pkg { self } pub fn as_pkg_mut(&mut self) -> &mut dyn ::bok::Pkg { self } } }; TokenStream::from(expanded) }