From ae44556dd7e092873b718f057927db4b7ebc211f Mon Sep 17 00:00:00 2001 From: Luca Fulchir Date: Tue, 3 Dec 2024 22:40:16 +0100 Subject: [PATCH] better ergonomics: hide generics in packages Signed-off-by: Luca Fulchir --- bok-macro/src/lib.rs | 29 ++++++++-- bok-macro/src/pkgs.rs | 88 +++++++++++++++++++++++++++++-- bok-macro/src/repos.rs | 29 +++++++++- bok-utils/src/repos/mod.rs | 4 +- bok-utils/src/repos/pkgs/one.rs | 8 +-- bok-utils/src/repos/pkgs/three.rs | 4 +- bok-utils/src/repos/pkgs/two.rs | 6 +-- 7 files changed, 143 insertions(+), 25 deletions(-) diff --git a/bok-macro/src/lib.rs b/bok-macro/src/lib.rs index da7c1b4..15272e3 100644 --- a/bok-macro/src/lib.rs +++ b/bok-macro/src/lib.rs @@ -150,10 +150,26 @@ pub fn pkg_fn_to_code(attrs: TokenStream, input: TokenStream) -> TokenStream { /// #[::bok::package(::bok::PkgEmpty)] /// pub struct MyPkg {} /// ``` -#[::macro_magic::import_tokens_attr] #[proc_macro_attribute] pub fn package(attrs: TokenStream, input: TokenStream) -> TokenStream { - crate::pkgs::package(attrs, input, __source_path) + crate::pkgs::package(attrs, input) +} + +/// **Internal**. try not to use +/// Use as #[::bok::package(MyBasePackage)] +/// Will setup a `base` field that is the given base package +/// and add `#[derive(::bok::Package)]` +/// needs generics +/// +/// e.g.: +/// ``` +/// #[::bok::package_path(::base::pkg)] +/// pub struct MyPkg {} +/// ``` +#[::macro_magic::import_tokens_attr] +#[proc_macro_attribute] +pub fn package_path(attrs: TokenStream, input: TokenStream) -> TokenStream { + crate::pkgs::package_path(attrs, input, __source_path) } /// Specify one or more build-time dependencies @@ -170,12 +186,17 @@ pub fn deps_build(attrs: TokenStream, input: TokenStream) -> TokenStream { } /// Use as #[::bok::package_impl] -/// will add `#[::bok::package_impl_base(..)]` with proper arguments -/// and export the resulting symbols +/// adds the right generics to the package. +/// +/// extra calls and changes are added to `::bok::Pkg` and ::std::Default traits /// /// e.g.: /// ``` /// #[::bok::package_impl] +/// impl Default for Mypackage { +/// fn default() -> Self { ... } +/// } +/// #[::bok::package_impl] /// impl ::bok::Pkg for MyPackage { /// fn build(&self) -> Result<(),()> { /// ... diff --git a/bok-macro/src/pkgs.rs b/bok-macro/src/pkgs.rs index c250d40..617b510 100644 --- a/bok-macro/src/pkgs.rs +++ b/bok-macro/src/pkgs.rs @@ -43,7 +43,37 @@ pub(crate) fn pkg_fn_to_code( asdf.into() } -pub(crate) fn package( +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, @@ -183,9 +213,34 @@ pub(crate) fn deps_build( attrs: TokenStream, input: TokenStream, ) -> TokenStream { - let packages = parse_macro_input!(attrs as crate::PathList); - + 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_build(my::pkg)]" -->> "#[deps_build(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_build(#(#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( @@ -240,7 +295,32 @@ pub(crate) fn package_impl( _attrs: TokenStream, input: TokenStream, ) -> TokenStream { - let ast = parse_macro_input!(input as ::syn::ItemImpl); + let mut ast = parse_macro_input!(input as ::syn::ItemImpl); + + if let Some((_, trait_name, _)) = &ast.trait_ { + let s = &trait_name.segments; + if s.len() != 2 + || s[0].ident.to_string() != "bok" + || s[1].ident.to_string() != "Pkg" + { + // + // only add the generic parameter and nothing else + // + let t_id = &ast.self_ty; + let g: ::syn::ItemImpl = ::syn::parse_quote! { + impl trait_name for #t_id where R: ::bok::Repository {} + }; + 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 ast = ast; // remove mut let name_pkg = match &*ast.self_ty { ::syn::Type::Path(tp) => match tp.path.get_ident() { diff --git a/bok-macro/src/repos.rs b/bok-macro/src/repos.rs index 4585f1e..d20307a 100644 --- a/bok-macro/src/repos.rs +++ b/bok-macro/src/repos.rs @@ -236,6 +236,33 @@ pub(crate) fn repo_packages( input: TokenStream, ) -> TokenStream { let local = parse_macro_input!(input as ItemStruct); + let mut packages = parse_macro_input!(attrs as crate::PathList); + + // make sure all the packages have generics, and if not add the repo as + // generic aka: rewrite + // "#[repo_packages(my::pkg)]" -->> "#[repo_packages(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::repo_packages(#(#p_list,)*)] + #local + } + .into(); + } + let packages = packages; // remove mut + let local_attrs = local.attrs.iter(); let (_, generics, where_clause) = local.generics.split_for_impl(); let ident = local.ident; @@ -292,8 +319,6 @@ pub(crate) fn repo_packages( }) .collect::>(); - 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 // extending the repository diff --git a/bok-utils/src/repos/mod.rs b/bok-utils/src/repos/mod.rs index 8ea62ef..6b2268d 100644 --- a/bok-utils/src/repos/mod.rs +++ b/bok-utils/src/repos/mod.rs @@ -30,7 +30,7 @@ use ::bok::repository; /// /// Base repository with some packages #[::bok::repository(::bok::RepositoryEmpty)] -#[::bok::repo_packages(pkgs::one::One)] +#[::bok::repo_packages(pkgs::one::One)] #[derive(::std::default::Default)] pub struct Pkgs1 { r1: i32, @@ -42,7 +42,7 @@ impl Pkgs1 {} /// /// This repository extends and changes Pkgs1 #[::bok::repository(Pkgs1)] -#[::bok::repo_packages(pkgs::two::Two)] +#[::bok::repo_packages(pkgs::two::Two)] #[derive(::std::default::Default)] pub struct Pkgs2 { r2: i32, diff --git a/bok-utils/src/repos/pkgs/one.rs b/bok-utils/src/repos/pkgs/one.rs index 4cb95a5..defe0f0 100644 --- a/bok-utils/src/repos/pkgs/one.rs +++ b/bok-utils/src/repos/pkgs/one.rs @@ -15,8 +15,6 @@ * limitations under the License. */ -use ::bok::package; - /// Example package /// Automatically implements `.builder().my_attr(42).build()` pattern #[::bok::package(::bok::PkgEmpty)] @@ -25,10 +23,8 @@ pub struct One { pub my_attr: u32, } -impl ::std::default::Default for One -where - R: ::bok::Repository, -{ +#[::bok::package_impl] +impl ::std::default::Default for One { fn default() -> Self { One { _bok_base: ::std::marker::PhantomData::default(), diff --git a/bok-utils/src/repos/pkgs/three.rs b/bok-utils/src/repos/pkgs/three.rs index fcba6af..75eb459 100644 --- a/bok-utils/src/repos/pkgs/three.rs +++ b/bok-utils/src/repos/pkgs/three.rs @@ -15,11 +15,9 @@ * limitations under the License. */ -use ::bok::package; - /// Example package #[::bok::package(::bok::PkgEmpty)] -#[::bok::deps_build(super::One)] +#[::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 8454aba..a2a15a7 100644 --- a/bok-utils/src/repos/pkgs/two.rs +++ b/bok-utils/src/repos/pkgs/two.rs @@ -15,11 +15,9 @@ * limitations under the License. */ -use ::bok::package; - /// Example package -#[::bok::package(super::one::One)] -#[::bok::deps_build(super::one::One)] +#[::bok::package(super::one::One)] +#[::bok::deps_build(super::one::One)] pub struct Two { pub my_attr2: u32, }