Skip to content

Commit

Permalink
Metadata support (#11)
Browse files Browse the repository at this point in the history
* Add metadata support

* Avoid dynamic allocation in Builder::interfaces()

* Simplify signature

* Review comments
  • Loading branch information
sergiimk authored Jul 29, 2024
1 parent d95d3c1 commit 9efe795
Show file tree
Hide file tree
Showing 18 changed files with 506 additions and 49 deletions.
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,21 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## Unreleased
### Added
- It's now possible to associate custom static metadata with builders:
```rust
#[component]
#[interface(dyn EventHandler)]
#[meta(EventHandlerDesc { event_type: "A"})]
#[meta(EventHandlerDesc { event_type: "B"})]
struct EventHandlerAB;
```
- New `BuilderExt` trait was added to provide convenient access to metadata and interfaces
- New `Catalog::builders_for_with_meta()` allows to filter builders by metadata with a custom predicate
### Changed
- `Builder::interfaces` method was changed to iteration via callback to avoid allocating a `Vec`

## [0.8.1] - 2024-05-27
### Fixed
- Fixed pedantic linter warnings
Expand Down
32 changes: 16 additions & 16 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ wildcard_imports = "allow"


# clippy::cargo
cargo = { level = "warn", priority = 0 }
cargo = { level = "warn", priority = -1 }
multiple_crate_versions = { level = "allow", priority = 1 }


Expand Down
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,15 @@ assert_eq!(inst.test(), "aimpl::bimpl");
- `#[component]` macro can derive `Builder`:
- When used directly for a `struct` or on `impl` block with `Impl::new()` function
- Can inject as `Arc<T>`, `T: Clone`, `&T`
- `Option<T>` is interpreted as `OneOf<T>` spec
- `Option<T>` is interpreted as `Maybe<OneOf<T>>` spec
- `Vec<T>` is interpreted as `AllOf<T>` spec
- Supports custom argument bindings in `Builder`
- Supports default interface bindings via `#[interface]` attribute
- Supports metadata association via `#[meta(...)]` attribute
- Prebuilt / add by value support
- By value injection of `Clone` types
- `Catalog` can be self-injected
- Chaining of `Catalog`s allows adding values dynamically (e.g. in middleware chains like `tower`)


# Design Principles
Expand Down Expand Up @@ -115,7 +118,7 @@ assert_eq!(inst.test(), "aimpl::bimpl");
- Add `trybuild` tests (see https://youtu.be/geovSK3wMB8?t=956)
- Support generic types
- Replace `add_*` with generic `add<B: Into<Builder>>`
- value by reference in new()
- value by reference in `new()`
- + Send + Sync plague https://www.reddit.com/r/rust/comments/6dz0xh/abstracting_over_reference_counted_types_rc_and/
- Add low-overhead resolution stack to errors (e.g. populated on unwind)
- Extra scopes
Expand All @@ -124,13 +127,11 @@ assert_eq!(inst.test(), "aimpl::bimpl");
- task
- catalog?
- thread safety
- adding values to catalog dynamically
- lazy values
- externally defined types
- custom builders
- error handling
- doctests
- Advanced queries (based on metadata + custom filters)
- improve catalog fluent interface (or macro?)
- proc macro error handling
- build a type without registering
Expand Down
95 changes: 90 additions & 5 deletions dill-impl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ use proc_macro::TokenStream;
use quote::{format_ident, quote};
use types::InjectionType;

/////////////////////////////////////////////////////////////////////////////////////////

#[proc_macro_attribute]
pub fn component(attr: TokenStream, item: TokenStream) -> TokenStream {
let ast: syn::Item = syn::parse(item).unwrap();
Expand All @@ -19,16 +21,29 @@ pub fn component(attr: TokenStream, item: TokenStream) -> TokenStream {
}
}

/////////////////////////////////////////////////////////////////////////////////////////

#[proc_macro_attribute]
pub fn scope(_args: TokenStream, item: TokenStream) -> TokenStream {
item
}

/////////////////////////////////////////////////////////////////////////////////////////

#[proc_macro_attribute]
pub fn interface(_args: TokenStream, item: TokenStream) -> TokenStream {
item
}

/////////////////////////////////////////////////////////////////////////////////////////

#[proc_macro_attribute]
pub fn meta(_args: TokenStream, item: TokenStream) -> TokenStream {
item
}

/////////////////////////////////////////////////////////////////////////////////////////

fn component_from_struct(ast: syn::ItemStruct) -> TokenStream {
let impl_name = &ast.ident;
let impl_type = syn::parse2(quote! { #impl_name }).unwrap();
Expand All @@ -44,6 +59,7 @@ fn component_from_struct(ast: syn::ItemStruct) -> TokenStream {
get_scope(&ast.attrs).unwrap_or_else(|| syn::parse_str("::dill::Transient").unwrap());

let interfaces = get_interfaces(&ast.attrs);
let meta = get_meta(&ast.attrs);

let mut gen: TokenStream = quote! { #ast }.into();
let builder: TokenStream = implement_builder(
Expand All @@ -52,6 +68,7 @@ fn component_from_struct(ast: syn::ItemStruct) -> TokenStream {
&impl_generics,
scope_type,
interfaces,
meta,
args,
false,
);
Expand All @@ -60,6 +77,8 @@ fn component_from_struct(ast: syn::ItemStruct) -> TokenStream {
gen
}

/////////////////////////////////////////////////////////////////////////////////////////

fn component_from_impl(vis: syn::Visibility, ast: syn::ItemImpl) -> TokenStream {
let impl_generics = &ast.generics;
let impl_type = &ast.self_ty;
Expand Down Expand Up @@ -91,6 +110,7 @@ fn component_from_impl(vis: syn::Visibility, ast: syn::ItemImpl) -> TokenStream
get_scope(&ast.attrs).unwrap_or_else(|| syn::parse_str("::dill::Transient").unwrap());

let interfaces = get_interfaces(&ast.attrs);
let meta = get_meta(&ast.attrs);

let mut gen: TokenStream = quote! { #ast }.into();
let builder: TokenStream = implement_builder(
Expand All @@ -99,6 +119,7 @@ fn component_from_impl(vis: syn::Visibility, ast: syn::ItemImpl) -> TokenStream
impl_generics,
scope_type,
interfaces,
meta,
args,
true,
);
Expand All @@ -107,19 +128,34 @@ fn component_from_impl(vis: syn::Visibility, ast: syn::ItemImpl) -> TokenStream
gen
}

/////////////////////////////////////////////////////////////////////////////////////////

#[allow(clippy::too_many_arguments)]
fn implement_builder(
impl_vis: &syn::Visibility,
impl_type: &syn::Type,
_impl_generics: &syn::Generics,
scope_type: syn::Path,
interfaces: Vec<syn::Type>,
meta: Vec<syn::ExprStruct>,
args: Vec<(syn::Ident, syn::Type)>,
has_new: bool,
) -> TokenStream {
let builder_name = format_ident!("{}Builder", quote! { #impl_type }.to_string());

let arg_name: Vec<_> = args.iter().map(|(name, _)| name).collect();

let meta_provide: Vec<_> = meta
.iter()
.enumerate()
.map(|(i, e)| implement_meta_provide(i, e))
.collect();
let meta_vars: Vec<_> = meta
.iter()
.enumerate()
.map(|(i, e)| implement_meta_var(i, e))
.collect();

let mut arg_override_fn_field = Vec::new();
let mut arg_override_fn_field_ctor = Vec::new();
let mut arg_override_setters = Vec::new();
Expand Down Expand Up @@ -182,6 +218,8 @@ fn implement_builder(
}

impl #builder_name {
#( #meta_vars )*

pub fn new() -> Self {
Self {
scope: #scope_type::new(),
Expand Down Expand Up @@ -209,13 +247,17 @@ fn implement_builder(
::std::any::type_name::<#impl_type>()
}

fn interfaces(&self) -> Vec<::dill::InterfaceDesc> {
vec![#(
::dill::InterfaceDesc {
fn interfaces(&self, clb: &mut dyn FnMut(&::dill::InterfaceDesc) -> bool) {
#(
if !clb(&::dill::InterfaceDesc {
type_id: ::std::any::TypeId::of::<#interfaces>(),
type_name: ::std::any::type_name::<#interfaces>(),
},
)*]
}) { return }
)*
}

fn metadata<'a>(&'a self, clb: & mut dyn FnMut(&'a dyn std::any::Any) -> bool) {
#( #meta_provide )*
}

fn get(&self, cat: &::dill::Catalog) -> Result<::std::sync::Arc<dyn ::std::any::Any + Send + Sync>, ::dill::InjectionError> {
Expand Down Expand Up @@ -258,6 +300,8 @@ fn implement_builder(
gen.into()
}

/////////////////////////////////////////////////////////////////////////////////////////

fn implement_arg(
name: &syn::Ident,
typ: &syn::Type,
Expand Down Expand Up @@ -377,6 +421,25 @@ fn implement_arg(
)
}

/////////////////////////////////////////////////////////////////////////////////////////

fn implement_meta_var(index: usize, expr: &syn::ExprStruct) -> proc_macro2::TokenStream {
let ident = format_ident!("_meta_{index}");
let typ = &expr.path;
quote! {
const #ident: #typ = #expr;
}
}

fn implement_meta_provide(index: usize, _expr: &syn::ExprStruct) -> proc_macro2::TokenStream {
let ident = format_ident!("_meta_{index}");
quote! {
if !clb(&Self::#ident) { return }
}
}

/////////////////////////////////////////////////////////////////////////////////////////

/// Searches for `#[scope(X)]` attribute and returns `X`
fn get_scope(attrs: &Vec<syn::Attribute>) -> Option<syn::Path> {
let mut scope = None;
Expand All @@ -394,6 +457,8 @@ fn get_scope(attrs: &Vec<syn::Attribute>) -> Option<syn::Path> {
scope
}

/////////////////////////////////////////////////////////////////////////////////////////

/// Searches for all `#[interface(X)]` attributes and returns all types
fn get_interfaces(attrs: &Vec<syn::Attribute>) -> Vec<syn::Type> {
let mut interfaces = Vec::new();
Expand All @@ -408,6 +473,24 @@ fn get_interfaces(attrs: &Vec<syn::Attribute>) -> Vec<syn::Type> {
interfaces
}

/////////////////////////////////////////////////////////////////////////////////////////

/// Searches for all `#[meta(X)]` attributes and returns all expressions
fn get_meta(attrs: &Vec<syn::Attribute>) -> Vec<syn::ExprStruct> {
let mut meta = Vec::new();

for attr in attrs {
if is_dill_attr(attr, "meta") {
let expr = attr.parse_args().unwrap();
meta.push(expr);
}
}

meta
}

/////////////////////////////////////////////////////////////////////////////////////////

fn is_dill_attr<I: ?Sized>(attr: &syn::Attribute, ident: &I) -> bool
where
syn::Ident: PartialEq<I>,
Expand All @@ -421,6 +504,8 @@ where
}
}

/////////////////////////////////////////////////////////////////////////////////////////

/// Searches `impl` block for `new()` method
fn get_new(impl_items: &[syn::ImplItem]) -> Option<&syn::ImplItemFn> {
impl_items
Expand Down
Loading

0 comments on commit 9efe795

Please sign in to comment.