better ergonomics: hide generics in packages

Signed-off-by: Luca Fulchir <luca.fulchir@runesauth.com>
This commit is contained in:
Luca Fulchir 2024-12-03 22:40:16 +01:00
parent 34a1771695
commit ae44556dd7
Signed by: luca.fulchir
GPG Key ID: 8F6440603D13A78E
7 changed files with 143 additions and 25 deletions

View File

@ -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<R>)]
/// 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<R>)]
/// 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<(),()> {
/// ...

View File

@ -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<R>)])"
// 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<R>);
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<R>)])"
let mut rewrite = false;
let repo_argument = {
let generic_id: ::syn::Path = ::syn::parse_quote!(id<R>);
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<R> trait_name for #t_id<R> 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() {

View File

@ -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<Self>)])"
let mut rewrite = false;
let repo_argument = {
let generic_id: ::syn::Path = ::syn::parse_quote!(id<Self>);
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::<Vec<&::syn::Field>>();
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

View File

@ -30,7 +30,7 @@ use ::bok::repository;
///
/// Base repository with some packages
#[::bok::repository(::bok::RepositoryEmpty)]
#[::bok::repo_packages(pkgs::one::One<Self>)]
#[::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<Self>)]
#[::bok::repo_packages(pkgs::two::Two)]
#[derive(::std::default::Default)]
pub struct Pkgs2 {
r2: i32,

View File

@ -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<R> ::std::default::Default for One<R>
where
R: ::bok::Repository,
{
#[::bok::package_impl]
impl ::std::default::Default for One {
fn default() -> Self {
One {
_bok_base: ::std::marker::PhantomData::default(),

View File

@ -15,11 +15,9 @@
* limitations under the License.
*/
use ::bok::package;
/// Example package
#[::bok::package(::bok::PkgEmpty)]
#[::bok::deps_build(super::One<R>)]
#[::bok::deps_build(super::One)]
pub struct Three {
pub my_attr: u32,
}

View File

@ -15,11 +15,9 @@
* limitations under the License.
*/
use ::bok::package;
/// Example package
#[::bok::package(super::one::One<R>)]
#[::bok::deps_build(super::one::One<R>)]
#[::bok::package(super::one::One)]
#[::bok::deps_build(super::one::One)]
pub struct Two {
pub my_attr2: u32,
}