From 00e93ce612542df42a92aee9e7aa30b7501366f0 Mon Sep 17 00:00:00 2001 From: Luca Fulchir Date: Mon, 2 Dec 2024 12:21:29 +0100 Subject: [PATCH] Package dependencies Signed-off-by: Luca Fulchir --- bok-macro/src/lib.rs | 64 +++++- bok-macro/src/pkgs.rs | 46 ++++ bok-macro/src/repos.rs | 349 ++++++++++++++++++------------ bok-utils/src/repos/mod.rs | 2 +- bok-utils/src/repos/pkgs/one.rs | 1 + bok-utils/src/repos/pkgs/three.rs | 1 + bok-utils/src/repos/pkgs/two.rs | 1 + bok/src/lib.rs | 4 +- 8 files changed, 320 insertions(+), 148 deletions(-) diff --git a/bok-macro/src/lib.rs b/bok-macro/src/lib.rs index cbe3e77..da7c1b4 100644 --- a/bok-macro/src/lib.rs +++ b/bok-macro/src/lib.rs @@ -84,7 +84,7 @@ pub fn repo_impl(attrs: TokenStream, input: TokenStream) -> TokenStream { /// Create the methods that will return the builders and packages /// Usage: /// ``` -/// #[::bok_macro::repo_impl(MyRepo)] +/// #[::bok_macro::repo_impl_methods(MyRepo)] /// impl MyRepo{} /// ``` #[::macro_magic::import_tokens_attr] @@ -96,6 +96,24 @@ pub fn repo_impl_methods( crate::repos::repo_impl_methods(attrs, input, __source_path) } +/// Internal. **Do not use unless you know what you are doing** +/// given a Repository and a trait "BokDeps...", +/// implement all the fn returning the dependencies needed by the package +/// +/// Usage: +/// ``` +/// #[::bok_macro::repo_impl_(MyRepo)] +/// impl MyRepo{} +/// ``` +#[::macro_magic::import_tokens_attr] +#[proc_macro_attribute] +pub fn repo_impl_pkg_deps( + attrs: TokenStream, + input: TokenStream, +) -> TokenStream { + crate::repos::repo_impl_pkg_deps(attrs, input) +} + // // ####### Package stuff ########## // @@ -138,6 +156,19 @@ pub fn package(attrs: TokenStream, input: TokenStream) -> TokenStream { crate::pkgs::package(attrs, input, __source_path) } +/// Specify one or more build-time dependencies +/// +/// e.g: +/// ``` +/// #[::bok::package(::bok::PkgEmpty)] +/// #[::bok::deps_build(some::Package, other::Package)] +/// pub struct MyPkg {} +/// ``` +#[proc_macro_attribute] +pub fn deps_build(attrs: TokenStream, input: TokenStream) -> TokenStream { + crate::pkgs::deps_build(attrs, input) +} + /// Use as #[::bok::package_impl] /// will add `#[::bok::package_impl_base(..)]` with proper arguments /// and export the resulting symbols @@ -205,3 +236,34 @@ pub fn package_impl_base( pub fn derive_package(input: TokenStream) -> TokenStream { crate::pkgs::derive_package(input) } + +// ==== Common package stuff ==== + +pub(crate) struct PathList(Vec<::syn::Path>); + +impl ::syn::parse::Parse for PathList { + fn parse(input: ::syn::parse::ParseStream) -> syn::Result { + use ::syn::punctuated::Punctuated; + let raw = + Punctuated::<::syn::Path, ::syn::Token![,]>::parse_terminated( + input, + )?; + let mut result = Vec::with_capacity(raw.len()); + for r in raw.into_iter() { + result.push(r) + } + Ok(PathList(result)) + } +} + +pub(crate) fn path_to_magic_export(p: &::syn::Path) -> ::syn::Path { + let mut tmp = p.clone(); + let new_id = { + macro_magic::mm_core::export_tokens_macro_ident( + &tmp.segments.last().unwrap().ident, + ) + }; + tmp.segments.last_mut().unwrap().ident = new_id; + + tmp +} diff --git a/bok-macro/src/pkgs.rs b/bok-macro/src/pkgs.rs index 2213aec..3afebba 100644 --- a/bok-macro/src/pkgs.rs +++ b/bok-macro/src/pkgs.rs @@ -157,6 +157,52 @@ pub(crate) fn package( .into() } +pub(crate) fn deps_build( + attrs: TokenStream, + input: TokenStream, +) -> TokenStream { + let packages = parse_macro_input!(attrs as crate::PathList); + + let local = parse_macro_input!(input as ::syn::ItemStruct); + 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 = local.generics; + let ident = local.ident; + let vis = local.vis; + + let pkg_trait = quote::format_ident!("BokDeps{}", ident); + + let deps = packages + .0 + .iter() + .map(|x| x.segments.last().unwrap().ident.clone()) + .collect::>(); + + quote! { + #(#local_attrs) + * + #vis struct #ident<#generics> { + #(#local_fields), + * + } + #[::macro_magic::export_tokens(#pkg_trait)] + pub trait #pkg_trait { + #(fn #deps(&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 diff --git a/bok-macro/src/repos.rs b/bok-macro/src/repos.rs index d3e5ec0..6afee70 100644 --- a/bok-macro/src/repos.rs +++ b/bok-macro/src/repos.rs @@ -19,122 +19,6 @@ use ::proc_macro::TokenStream; use ::quote::quote; use ::syn::{parse_macro_input, DeriveInput, Fields, ItemImpl, ItemStruct}; -pub(crate) fn repo_impl( - _attrs: TokenStream, - input: TokenStream, -) -> TokenStream { - let local = parse_macro_input!(input as ItemImpl); - - let reponame = &local.self_ty; - - quote! { - #[::bok_macro::repo_impl_methods(#reponame)] - #local - } - .into() -} - -pub(crate) fn repo_impl_methods( - attrs: TokenStream, - input: TokenStream, - __source_name: TokenStream, -) -> TokenStream { - let base = parse_macro_input!(attrs as ItemStruct); - let local = parse_macro_input!(input as ItemImpl); - let source_name = parse_macro_input!(__source_name as ::syn::Path); - - if let ::syn::Type::Path(self_type_path) = local.self_ty.as_ref() { - if self_type_path.path != source_name { - return ::syn::Error::new( - proc_macro2::Span::call_site(), - "#[::bok_macro::repo_impl_methods(..)]: argument and impl \ - type differ", - ) - .to_compile_error() - .into(); - } - } else { - return ::syn::Error::new( - proc_macro2::Span::call_site(), - "#[::bok_macro::repo_impl_methods(..)]: argument and impl type \ - differ", - ) - .to_compile_error() - .into(); - } - - let local_attrs = local.attrs.iter(); - let generics = local.generics; - let ident = &local.self_ty; - let items = local.items.iter(); - - let ::syn::Fields::Named(base_fields) = base.fields else { - return ::syn::Error::new( - proc_macro2::Span::call_site(), - "#[::bok_macro::repo_impl_methods(..)]: type has unsupported \ - unnamed fields", - ) - .to_compile_error() - .into(); - }; - - let mut fn_to_add = Vec::new(); - for f in base_fields.named.iter() { - if !f - .ident - .as_ref() - .is_some_and(|id| id.to_string().starts_with("_p_")) - { - continue; - }; - let ::syn::Type::Path(f_type) = &f.ty else { - continue; - }; - let t_phantom = &f_type.path; - let t_id = { - let args = &t_phantom - .segments - .last() - .expect("t_phantom no last?") - .arguments; - let ::syn::PathArguments::AngleBracketed(bracketed) = &args else { - panic!("phantom without anglebracket?"); - }; - let ::syn::GenericArgument::Type(::syn::Type::Path(t)) = - bracketed.args.first().expect("phantom bracketed, no args") - else { - panic!("phantom bracketed, generic not a type path"); - }; - t.path.clone() - }; - let pkg_name = { - let segment = t_id.segments.iter().last().unwrap(); - - &segment.ident - }; - let pkg_fn: ::syn::ImplItemFn = ::syn::parse_quote! { - pub fn #pkg_name(&self) -> #t_id { - #t_id::default() - } - }; - fn_to_add.push(pkg_fn); - } - - let new_fn = fn_to_add.iter(); - - quote! { - #(#local_attrs) - * - impl #ident<#generics> { - #(#items) - * - #(#new_fn) - * - } - } - .into() -} - pub(crate) fn repository( attrs: TokenStream, input: TokenStream, @@ -368,22 +252,6 @@ pub(crate) fn derive_repository(input: TokenStream) -> TokenStream { } }.into() } -struct PathList(Vec<::syn::Path>); - -impl ::syn::parse::Parse for PathList { - fn parse(input: ::syn::parse::ParseStream) -> syn::Result { - use ::syn::punctuated::Punctuated; - let raw = - Punctuated::<::syn::Path, ::syn::Token![,]>::parse_terminated( - input, - )?; - let mut result = Vec::with_capacity(raw.len()); - for r in raw.into_iter() { - result.push(r) - } - Ok(PathList(result)) - } -} pub(crate) fn repo_packages( attrs: TokenStream, @@ -446,7 +314,7 @@ pub(crate) fn repo_packages( }) .collect::>(); - let packages = parse_macro_input!(attrs as PathList); + let packages = parse_macro_input!(attrs as crate::PathList); // the packages added manually must not be repeated manually. // but they will override any other package added by @@ -530,20 +398,213 @@ pub(crate) fn repo_packages( .into() } -fn path_to_snake_case(path: &::syn::Path) -> String { - let mut s = String::new(); +pub(crate) fn repo_impl( + _attrs: TokenStream, + input: TokenStream, +) -> TokenStream { + let local = parse_macro_input!(input as ItemImpl); - let mut is_first = true; - for segment in path.segments.iter() { - if !is_first { - s += "_"; - } else { - is_first = false + let reponame = &local.self_ty; + + quote! { + #[::bok_macro::repo_impl_methods(#reponame)] + #local + } + .into() +} + +pub(crate) fn repo_impl_methods( + attrs: TokenStream, + input: TokenStream, + __source_name: TokenStream, +) -> TokenStream { + let base = parse_macro_input!(attrs as ItemStruct); + let local = parse_macro_input!(input as ItemImpl); + let source_name = parse_macro_input!(__source_name as ::syn::Path); + + if let ::syn::Type::Path(self_type_path) = local.self_ty.as_ref() { + if self_type_path.path != source_name { + return ::syn::Error::new( + proc_macro2::Span::call_site(), + "#[::bok_macro::repo_impl_methods(..)]: argument and impl \ + type differ", + ) + .to_compile_error() + .into(); } - s += segment.ident.to_string().as_str(); + } else { + return ::syn::Error::new( + proc_macro2::Span::call_site(), + "#[::bok_macro::repo_impl_methods(..)]: argument and impl type \ + differ", + ) + .to_compile_error() + .into(); } - use ::convert_case::{Case, Casing}; + let local_attrs = local.attrs.iter(); + let generics = local.generics; + let ::syn::Type::Path(local_tp) = local.self_ty.as_ref() else { + return ::syn::Error::new( + proc_macro2::Span::call_site(), + "#[::bok_macro::repo_impl_methods(..)]: no ident?", + ) + .to_compile_error() + .into(); + }; + let local_ident = local_tp.path.get_ident().expect("NOT AN IDENT"); + let items = local.items.iter(); + // FIXME: make sure `items` does not have methods that are not packages in + // the impl - s.to_case(Case::Snake) + let ::syn::Fields::Named(base_fields) = base.fields else { + return ::syn::Error::new( + proc_macro2::Span::call_site(), + "#[::bok_macro::repo_impl_methods(..)]: type has unsupported \ + unnamed fields", + ) + .to_compile_error() + .into(); + }; + + let mut all_pkgs_types = Vec::with_capacity(base_fields.named.len()); + + let mut fn_to_add = Vec::new(); + for f in base_fields.named.iter() { + if !f + .ident + .as_ref() + .is_some_and(|id| id.to_string().starts_with("_p_")) + { + continue; + }; + let ::syn::Type::Path(f_type) = &f.ty else { + continue; + }; + let t_phantom = &f_type.path; + let t_id = { + let args = &t_phantom + .segments + .last() + .expect("t_phantom no last?") + .arguments; + let ::syn::PathArguments::AngleBracketed(bracketed) = &args else { + panic!("phantom without anglebracket?"); + }; + let ::syn::GenericArgument::Type(::syn::Type::Path(t)) = + bracketed.args.first().expect("phantom bracketed, no args") + else { + panic!("phantom bracketed, generic not a type path"); + }; + t.path.clone() + }; + let pkg_name = { + let segment = t_id.segments.iter().last().unwrap(); + + &segment.ident + }; + all_pkgs_types.push(t_id.clone()); + if local + .items + .iter() + .find(|&func_it| { + let ::syn::ImplItem::Fn(func) = &func_it else { + return false; + }; + func.sig.ident.to_string() == pkg_name.to_string() + }) + .is_some() + { + // the user overrode the `::default()` package build + // don't try to add it again + continue; + } + let pkg_fn: ::syn::ImplItemFn = ::syn::parse_quote! { + pub fn #pkg_name(&self) -> #t_id { + #t_id::default() + } + }; + fn_to_add.push(pkg_fn); + } + + let new_fn = fn_to_add.iter(); + + let mut all_impl_deps = Vec::with_capacity(all_pkgs_types.len()); + + for p_type in all_pkgs_types.into_iter() { + let bok_dep_trait = { + let mut tmp = p_type.clone(); + tmp.segments.last_mut().unwrap().ident = quote::format_ident!( + "BokDeps{}", + tmp.segments.last().unwrap().ident + ); + tmp + }; + let pkg_trait_impl = &bok_dep_trait; + let dep_view = quote! { + #[::bok_macro::repo_impl_pkg_deps(#pkg_trait_impl)] + impl #bok_dep_trait for #local_ident {} + }; + all_impl_deps.push(dep_view); + break; + } + + quote! { + #(#local_attrs) + * + impl #local_ident<#generics> { + #(#items) + * + #(#new_fn) + * + } + #(#all_impl_deps) + * + } + .into() +} + +pub(crate) fn repo_impl_pkg_deps( + attrs: TokenStream, + input: TokenStream, +) -> TokenStream { + let trait_deps = parse_macro_input!(attrs as ::syn::ItemTrait); + let local = parse_macro_input!(input as ItemImpl); + + let impl_trait = local + .trait_ + .expect("#[::bok_macro::repo_impl_pkg_deps()]: no trait found") + .1; + let local_attrs = local.attrs.iter(); + let generics = local.generics; + let ident = &local.self_ty; + + //let deps = Vec::<::syn::TraitItemFn>::new(); + let deps = trait_deps + .items + .iter() + .filter_map(|x| { + if let ::syn::TraitItem::Fn(func) = &x { + let name = &func.sig.ident; + let dep_impl: ::syn::TraitItemFn = ::syn::parse_quote! { + fn #name(&self) -> ::std::boxed::Box { + ::std::boxed::Box::new(self.#name()) + } + }; + Some(dep_impl) + } else { + None + } + }) + .collect::>(); + + quote! { + #(#local_attrs) + * + impl #impl_trait for #ident<#generics> { + #(#deps) + * + } + } + .into() } diff --git a/bok-utils/src/repos/mod.rs b/bok-utils/src/repos/mod.rs index 17edc32..6b2268d 100644 --- a/bok-utils/src/repos/mod.rs +++ b/bok-utils/src/repos/mod.rs @@ -23,7 +23,7 @@ pub mod pkgs; // FIXME: why? use ::bok_macro::repo_impl_methods; - +use ::bok_macro::repo_impl_pkg_deps; // Export multiple packages in this module use ::bok::repository; diff --git a/bok-utils/src/repos/pkgs/one.rs b/bok-utils/src/repos/pkgs/one.rs index c2eb7e6..bbb444c 100644 --- a/bok-utils/src/repos/pkgs/one.rs +++ b/bok-utils/src/repos/pkgs/one.rs @@ -22,6 +22,7 @@ use ::bok::package; /// Example package /// Automatically implements `.builder().my_attr(42).build()` pattern #[::bok::package(::bok::PkgEmpty)] +#[::bok::deps_build()] pub struct One { pub my_attr: u32, } diff --git a/bok-utils/src/repos/pkgs/three.rs b/bok-utils/src/repos/pkgs/three.rs index 19cc64c..7f801f6 100644 --- a/bok-utils/src/repos/pkgs/three.rs +++ b/bok-utils/src/repos/pkgs/three.rs @@ -19,6 +19,7 @@ use ::bok::package; /// Example package #[::bok::package(::bok::PkgEmpty)] +#[::bok::deps_build(super::One)] pub struct Three { pub my_attr: u32, } diff --git a/bok-utils/src/repos/pkgs/two.rs b/bok-utils/src/repos/pkgs/two.rs index 03310e1..efc073a 100644 --- a/bok-utils/src/repos/pkgs/two.rs +++ b/bok-utils/src/repos/pkgs/two.rs @@ -19,6 +19,7 @@ use ::bok::package; /// Example package #[::bok::package(super::one::One)] +#[::bok::deps_build(super::one::One)] pub struct Two { pub my_attr2: u32, } diff --git a/bok/src/lib.rs b/bok/src/lib.rs index e6c3366..6a686db 100644 --- a/bok/src/lib.rs +++ b/bok/src/lib.rs @@ -27,8 +27,8 @@ pub mod deps { } pub use ::bok_macro::{ - package, package_impl, pkg_fn_to_code, repo_impl, repo_packages, - repository, Package, Repository, + deps_build, package, package_impl, pkg_fn_to_code, repo_impl, + repo_packages, repository, Package, Repository, }; pub use ::semver::{BuildMetadata, Prerelease, Version};