Algebraic Effects System for Rust

Vic/jj change lmqsyzqlxyvv (#19)

* feat(builder-macro): conditional Has<T> impl for builder types

- Macro now generates impl Has<T> for builder only when the corresponding field is Present
- Added integration test to verify Has<T> is only implemented when field is present
- Refactored builder macro to use UpperCamelCase marker types
- All builder macro tests pass and code is formatted

Unlocks type-driven context provision and safer builder usage. Future: extend to more traits and ergonomic context composition. ��

* refactor(builder-macro): move Absent/Present marker types to builder_types crate

- Created builder_types crate to host Absent and Present marker types
- Macro now generates code referencing builder_types::Absent and builder_types::Present
- Updated integration and test code to import marker types from builder_types
- Ensures proc-macro crate does not export non-macro items, following Rust best practices

This enables clean separation of marker types and macro logic, and future extensibility for builder state markers. 🧩

* refactor: migrate Put trait to type-parameter output form 🦀

- Refactored all Put trait usages to use type parameters (Put<T, U>) instead of associated type.
- Updated builder and lens macros to generate new Put impls.
- Updated all manual Put impls and trait bounds in core and tests.
- Fixed logic in manual Put impls to properly update struct fields.
- Ensured all tests are green and code is formatted.
- This unlocks more flexible type-changing builder and lens operations, and simplifies trait bounds throughout the system.

authored by oeiuwq.com and committed by

GitHub e00e3335 80f311bd

+515 -64
+16
Cargo.lock
··· 15 15 ] 16 16 17 17 [[package]] 18 + name = "builder_macro" 19 + version = "0.1.0" 20 + dependencies = [ 21 + "builder_types", 22 + "proc-macro2", 23 + "quote", 24 + "syn 2.0.104", 25 + ] 26 + 27 + [[package]] 28 + name = "builder_types" 29 + version = "0.1.0" 30 + 31 + [[package]] 18 32 name = "do_traits" 19 33 version = "0.0.0-git" 20 34 dependencies = [ ··· 78 92 version = "0.0.0-git" 79 93 dependencies = [ 80 94 "abilities_macro", 95 + "builder_macro", 96 + "builder_types", 81 97 "dyn-clone", 82 98 "fx", 83 99 "fx-do",
+2 -1
Cargo.toml
··· 8 8 "crates/do_macro", 9 9 "crates/field_macro", 10 10 "crates/forall_macro", 11 - "crates/abilities_macro" 11 + "crates/abilities_macro", 12 + "crates/builder_macro" 12 13 ] 13 14 14 15 [workspace.package]
+47
book/src/macros.md
··· 57 57 58 58 The `forall_macro` crate provides macros for working with generic fields and quantified access, enabling advanced patterns for generic and reusable effectful code. 59 59 60 + ## ContextBuilder Macro: Effectful Context Provision 61 + 62 + The `builder_macro` crate provides a procedural macro for generating type-safe, incremental context builders for struct contexts. This enables ergonomic and effectful context provision, especially when working with fx-rs's effect system and trait-based dependency injection. 63 + 64 + ### Example: Using ContextBuilder for Effectful Contexts 65 + 66 + ```rust 67 + use builder_macro::ContextBuilder; 68 + use fx::{Has, Put}; 69 + 70 + #[derive(ContextBuilder, Debug, Clone)] 71 + struct MyContext { 72 + a: i32, 73 + b: String, 74 + c: bool, 75 + } 76 + 77 + // Incrementally build the context 78 + let ctx = MyContextBuilder::empty() 79 + .a(42) 80 + .b("hello".to_string()) 81 + .c(true) 82 + .build(); 83 + 84 + // Use trait-based access 85 + assert_eq!(Has::<i32>::get(ctx.clone()), 42); 86 + assert_eq!(Has::<String>::get(ctx.clone()), "hello"); 87 + assert_eq!(Has::<bool>::get(ctx.clone()), true); 88 + 89 + // Use macro-generated accessors 90 + assert_eq!(ctx.a(), Some(42)); 91 + assert!(ctx.has_a()); 92 + 93 + // Compose with fx-rs effect system 94 + // (see fx.rs book for more advanced examples) 95 + ``` 96 + 97 + The macro generates: 98 + 99 + - A builder type with `put_*` methods for each field 100 + - Marker types for tracking field presence 101 + - `build()` method (requires all fields set) 102 + - Trait impls for `Has` and `Put` 103 + - Field accessors and presence checks 104 + 105 + This pattern enables ergonomic, type-safe context provision and mutation for effectful code. 106 + 60 107 ______________________________________________________________________ 61 108 62 109 These macros are designed to work seamlessly with the fx-rs core, making advanced effect patterns accessible and ergonomic for Rust developers.
+13
crates/builder_macro/Cargo.toml
··· 1 + [package] 2 + name = "builder_macro" 3 + version = "0.1.0" 4 + edition = "2021" 5 + 6 + [lib] 7 + proc-macro = true 8 + 9 + [dependencies] 10 + syn = "2" 11 + quote = "1" 12 + proc-macro2 = "1" 13 + builder_types = { path = "../builder_types" }
+190
crates/builder_macro/src/lib.rs
··· 1 + extern crate proc_macro; 2 + use proc_macro::TokenStream; 3 + use quote::{format_ident, quote}; 4 + use syn::{Data, DeriveInput, Fields, parse_macro_input}; 5 + 6 + #[proc_macro_derive(ContextBuilder)] 7 + pub fn context_builder(input: TokenStream) -> TokenStream { 8 + let input = parse_macro_input!(input as DeriveInput); 9 + let struct_name = &input.ident; 10 + let builder_name = format_ident!("{}Builder", struct_name); 11 + let mut field_idents = Vec::new(); 12 + let mut field_types = Vec::new(); 13 + if let Data::Struct(data_struct) = &input.data { 14 + if let Fields::Named(fields_named) = &data_struct.fields { 15 + for field in &fields_named.named { 16 + field_idents.push(field.ident.as_ref().unwrap()); 17 + field_types.push(&field.ty); 18 + } 19 + } 20 + } 21 + // Marker types for each field (UpperCamelCase) 22 + let marker_types: Vec<_> = field_idents 23 + .iter() 24 + .map(|id| { 25 + let name = id.to_string(); 26 + let mut chars = name.chars(); 27 + let first = chars.next().unwrap().to_uppercase().collect::<String>(); 28 + let rest = chars.collect::<String>(); 29 + format_ident!("{}State", format!("{}{}", first, rest)) 30 + }) 31 + .collect(); 32 + // Derive Clone for marker types 33 + let marker_structs = marker_types 34 + .iter() 35 + .map(|ty| quote! { #[derive(Clone)] pub struct #ty; }); 36 + let absent_generics = vec![quote! { builder_types::Absent }; marker_types.len()]; 37 + let present_generics = vec![quote! { builder_types::Present }; marker_types.len()]; 38 + 39 + // Generate unique builder field names 40 + let builder_field_idents: Vec<_> = field_idents 41 + .iter() 42 + .map(|id| format_ident!("maybe_{}", id)) 43 + .collect(); 44 + 45 + // Derive Clone for all builder types 46 + let builder_struct = quote! { 47 + #[derive(Clone)] 48 + pub struct #builder_name<#(#marker_types),*> { 49 + #(#builder_field_idents: Option<#field_types>,)* 50 + _marker: std::marker::PhantomData<(#(#marker_types),*)>, 51 + } 52 + }; 53 + 54 + // empty() for all Absent 55 + let empty_impl = quote! { 56 + impl #builder_name<#(#absent_generics),*> { 57 + pub fn empty() -> Self { 58 + Self { 59 + #(#builder_field_idents: None,)* 60 + _marker: std::marker::PhantomData, 61 + } 62 + } 63 + } 64 + }; 65 + 66 + // put_x for each field 67 + let mut put_methods = Vec::new(); 68 + for (i, (id, ty)) in field_idents.iter().zip(field_types.iter()).enumerate() { 69 + let generics = marker_types.clone(); 70 + let mut next_generics = generics.clone(); 71 + next_generics[i] = format_ident!("Present"); 72 + let assignments = builder_field_idents.iter().enumerate().map(|(j, f)| { 73 + if i == j { 74 + quote! { #f: Some(value) } 75 + } else { 76 + quote! { #f: self.#f } 77 + } 78 + }); 79 + let put_method = quote! { 80 + impl<#(#generics),*> #builder_name<#(#generics),*> { 81 + pub fn #id(self, value: #ty) -> #builder_name<#(#next_generics),*> { 82 + #builder_name { 83 + #(#assignments,)* 84 + _marker: std::marker::PhantomData, 85 + } 86 + } 87 + } 88 + }; 89 + put_methods.push(put_method); 90 + } 91 + 92 + // build() for all Present 93 + let build_impl = quote! { 94 + impl #builder_name<#(#present_generics),*> { 95 + pub fn build(self) -> #struct_name { 96 + #struct_name { 97 + #( 98 + #field_idents: self.#builder_field_idents.unwrap(), 99 + )* 100 + } 101 + } 102 + } 103 + }; 104 + 105 + // Accessor methods 106 + let mut accessor_methods = Vec::new(); 107 + for idx in 0..field_idents.len() { 108 + let id = &field_idents[idx]; 109 + let ty = &field_types[idx]; 110 + let builder_id = &builder_field_idents[idx]; 111 + let get_method_name = format_ident!("maybe_{}", id); 112 + let get_method = quote! { 113 + pub fn #get_method_name(&self) -> Option<#ty> { 114 + self.#builder_id.as_ref().map(|v| v.clone()) 115 + } 116 + }; 117 + let has_method_name = format_ident!("has_builder_{}", id); 118 + let has_method = quote! { 119 + pub fn #has_method_name(&self) -> bool { 120 + self.#builder_id.is_some() 121 + } 122 + }; 123 + accessor_methods.push(get_method); 124 + accessor_methods.push(has_method); 125 + } 126 + 127 + // Conditional Has impls for builder 128 + let mut has_impls = Vec::new(); 129 + for (i, ty) in field_types.iter().enumerate() { 130 + let mut generics = marker_types 131 + .iter() 132 + .map(|_| quote! { builder_types::Absent }) 133 + .collect::<Vec<_>>(); 134 + generics[i] = quote! { builder_types::Present }; 135 + let builder_ty = quote! { #builder_name<#(#generics),*> }; 136 + let builder_field = &builder_field_idents[i]; 137 + has_impls.push(quote! { 138 + impl Has<#ty> for #builder_ty { 139 + fn get(self) -> #ty { 140 + self.#builder_field.expect("Field must be present") 141 + } 142 + } 143 + }); 144 + } 145 + 146 + // Conditional Put impls for builder (using new trait signature) 147 + let mut put_impls = Vec::new(); 148 + for (i, (ty, _)) in field_types.iter().zip(field_idents.iter()).enumerate() { 149 + let generics = marker_types 150 + .iter() 151 + .map(|_| quote! { builder_types::Absent }) 152 + .collect::<Vec<_>>(); 153 + let mut next_generics = generics.clone(); 154 + next_generics[i] = quote! { builder_types::Present }; 155 + let builder_ty = quote! { #builder_name<#(#generics),*> }; 156 + let next_builder_ty = quote! { #builder_name<#(#next_generics),*> }; 157 + let builder_field = &builder_field_idents[i]; 158 + let assignments = builder_field_idents.iter().enumerate().map(|(j, f)| { 159 + if i == j { 160 + quote! { #f: Some(value) } 161 + } else { 162 + quote! { #f: self.#f } 163 + } 164 + }); 165 + put_impls.push(quote! { 166 + impl Put<#ty, #next_builder_ty> for #builder_ty { 167 + fn put(self, value: #ty) -> #next_builder_ty { 168 + #builder_name { 169 + #(#assignments,)* 170 + _marker: std::marker::PhantomData, 171 + } 172 + } 173 + } 174 + }); 175 + } 176 + 177 + let expanded = quote! { 178 + #(#marker_structs)* 179 + #builder_struct 180 + impl<#(#marker_types),*> #builder_name<#(#marker_types),*> { 181 + #(#accessor_methods)* 182 + } 183 + #empty_impl 184 + #(#put_methods)* 185 + #build_impl 186 + #(#has_impls)* 187 + #(#put_impls)* 188 + }; 189 + TokenStream::from(expanded) 190 + }
+6
crates/builder_types/Cargo.toml
··· 1 + [package] 2 + name = "builder_types" 3 + version = "0.1.0" 4 + edition = "2021" 5 + 6 + [dependencies]
+4
crates/builder_types/src/lib.rs
··· 1 + #[derive(Clone)] 2 + pub struct Absent; 3 + #[derive(Clone)] 4 + pub struct Present;
+7 -2
crates/fx/src/core/ability.rs
··· 1 1 use std::marker::PhantomData; 2 2 3 3 use crate::{ 4 - core::{handler::Handler, has_put::HasPut, pair::Pair, state::State}, 4 + core::{ 5 + handler::Handler, 6 + has_put::{HasPut, Put}, 7 + pair::Pair, 8 + state::State, 9 + }, 5 10 kernel::{ability::Ability, fx::Fx}, 6 11 }; 7 12 ··· 40 45 pub fn lift_req<P, A>(i: I) -> Fx<'f, P, O> 41 46 where 42 47 A: Ability<'f, I, S, O> + Clone, 43 - P: HasPut<A> + HasPut<S> + Clone, 48 + P: HasPut<A, P> + HasPut<S, P> + Put<A, P> + Put<S, P> + Clone, 44 49 { 45 50 State::<A>::get().lift_map(|a: A| a.apply(i)) 46 51 }
+3 -3
crates/fx/src/core/fx.rs
··· 1 - use crate::core::has_put::{Has, HasPut}; 1 + use crate::core::has_put::{Has, HasPut, Put}; 2 2 use crate::kernel::fx::Fx; 3 3 4 4 use super::pair::Pair; ··· 94 94 where 95 95 U: Clone + 'f, 96 96 R: Clone + 'f, 97 - P: HasPut<S> + HasPut<R> + Clone + 'f, 97 + P: HasPut<S, P> + HasPut<R, P> + Put<S, P> + Put<R, P> + Clone + 'f, 98 98 F: FnOnce(V) -> Fx<'f, R, U> + Clone + 'f, 99 99 { 100 100 self.lift().map_m(|v| f(v).lift()) ··· 102 102 103 103 pub fn lift<T>(self) -> Fx<'f, T, V> 104 104 where 105 - T: HasPut<S> + Clone + 'f, 105 + T: HasPut<S, T> + Put<S, T> + Clone + 'f, 106 106 S: Clone + 'f, 107 107 V: Clone + 'f, 108 108 {
+7 -7
crates/fx/src/core/has_put.rs
··· 13 13 } 14 14 } 15 15 16 - pub trait Put<T> { 17 - fn put(self, value: T) -> Self; 16 + pub trait Put<T, U> { 17 + fn put(self, value: T) -> U; 18 18 } 19 19 20 - impl<T: Clone> Put<T> for T { 21 - fn put(self, value: T) -> Self { 20 + impl<T: Clone> Put<T, T> for T { 21 + fn put(self, value: T) -> T { 22 22 value 23 23 } 24 24 } 25 25 26 - pub trait HasPut<T> 26 + pub trait HasPut<T, U> 27 27 where 28 - Self: Has<T> + Put<T>, 28 + Self: Has<T> + Put<T, U>, 29 29 { 30 30 } 31 31 32 - impl<I, O> HasPut<I> for O where O: Has<I> + Put<I> {} 32 + impl<I, O, U> HasPut<I, U> for O where O: Has<I> + Put<I, U> {} 33 33 clone_trait_object!(<T> Has<T>);
+6 -2
crates/fx/src/core/lens.rs
··· 1 1 use dyn_clone::{DynClone, clone_trait_object}; 2 2 3 3 use crate::{ 4 - core::{handler::Handler, has_put::HasPut, pair::Pair}, 4 + core::{ 5 + handler::Handler, 6 + has_put::{HasPut, Put}, 7 + pair::Pair, 8 + }, 5 9 kernel::fx::Fx, 6 10 }; 7 11 ··· 16 20 pub fn new() -> Self 17 21 where 18 22 Inner: Clone, 19 - Outer: HasPut<Inner> + Clone, 23 + Outer: HasPut<Inner, Outer> + Put<Inner, Outer> + Clone, 20 24 { 21 25 Self( 22 26 Box::new(|outer: Outer| outer.get().clone()),
+1 -1
crates/fx/src/core/provide.rs
··· 18 18 19 19 pub fn update_context<T>(self, t: T) -> Fx<'a, S, V> 20 20 where 21 - S: Put<T>, 21 + S: Put<T, S>, 22 22 T: Clone + 'a, 23 23 { 24 24 self.contra_map(|p: S| p.put(t), |_, p| p)
+4 -6
crates/fx/src/core/tests/ability_test.rs
··· 107 107 self.ab 108 108 } 109 109 } 110 - impl Put<MyAbility> for Ctx { 111 - fn put(mut self, ab: MyAbility) -> Self { 112 - self.ab = ab; 110 + impl Put<MyAbility, Ctx> for Ctx { 111 + fn put(self, _: MyAbility) -> Ctx { 113 112 self 114 113 } 115 114 } ··· 118 117 self.n 119 118 } 120 119 } 121 - impl Put<i32> for Ctx { 122 - fn put(mut self, n: i32) -> Self { 123 - self.n = n; 120 + impl Put<i32, Ctx> for Ctx { 121 + fn put(self, _: i32) -> Ctx { 124 122 self 125 123 } 126 124 }
+6 -6
crates/fx/src/core/tests/forall_test.rs
··· 49 49 self.y 50 50 } 51 51 } 52 - impl Put<i32> for S2 { 53 - fn put(mut self, value: i32) -> Self { 54 - self.x = value; 52 + impl Put<i32, S2> for S2 { 53 + fn put(mut self, x: i32) -> S2 { 54 + self.x = x; 55 55 self 56 56 } 57 57 } 58 - impl Put<bool> for S2 { 59 - fn put(mut self, value: bool) -> Self { 60 - self.y = value; 58 + impl Put<bool, S2> for S2 { 59 + fn put(mut self, y: bool) -> S2 { 60 + self.y = y; 61 61 self 62 62 } 63 63 }
+4 -6
crates/fx/src/core/tests/fx_test.rs
··· 19 19 self.a 20 20 } 21 21 } 22 - impl Put<A> for S { 23 - fn put(mut self, value: A) -> Self { 24 - self.a = value; 22 + impl Put<A, S> for S { 23 + fn put(self, _: A) -> S { 25 24 self 26 25 } 27 26 } ··· 30 29 self.b 31 30 } 32 31 } 33 - impl Put<B> for S { 34 - fn put(mut self, value: B) -> Self { 35 - self.b = value; 32 + impl Put<B, S> for S { 33 + fn put(self, _: B) -> S { 36 34 self 37 35 } 38 36 }
+1 -1
crates/fx/src/core/tests/has_put_test.rs
··· 18 18 19 19 #[test] 20 20 fn has_put_trait_bound_for_u32() { 21 - fn assert_has_put<T: HasPut<u32>>() {} 21 + fn assert_has_put<T: HasPut<u32, T>>() {} 22 22 assert_has_put::<u32>(); 23 23 }
+15 -15
crates/fx/src/core/tests/lens_test.rs
··· 14 14 self.a 15 15 } 16 16 } 17 - impl Put<u32> for Ctx { 18 - fn put(mut self, value: u32) -> Self { 19 - self.a = value; 17 + impl Put<u32, Ctx> for Ctx { 18 + fn put(mut self, a: u32) -> Ctx { 19 + self.a = a; 20 20 self 21 21 } 22 22 } ··· 32 32 self.a 33 33 } 34 34 } 35 - impl Put<i32> for ST { 36 - fn put(mut self, value: i32) -> Self { 37 - self.a = value; 35 + impl Put<i32, ST> for ST { 36 + fn put(mut self, a: i32) -> ST { 37 + self.a = a; 38 38 self 39 39 } 40 40 } ··· 43 43 self.b 44 44 } 45 45 } 46 - impl Put<String> for ST { 47 - fn put(mut self, value: String) -> Self { 48 - self.b = value; 46 + impl Put<String, ST> for ST { 47 + fn put(mut self, b: String) -> ST { 48 + self.b = b; 49 49 self 50 50 } 51 51 } ··· 150 150 self.st 151 151 } 152 152 } 153 - impl Put<ST> for Outer { 154 - fn put(mut self, value: ST) -> Self { 155 - self.st = value; 153 + impl Put<ST, Outer> for Outer { 154 + fn put(mut self, st: ST) -> Outer { 155 + self.st = st; 156 156 self 157 157 } 158 158 } ··· 183 183 self.st 184 184 } 185 185 } 186 - impl Put<ST> for Outer { 187 - fn put(mut self, value: ST) -> Self { 188 - self.st = value; 186 + impl Put<ST, Outer> for Outer { 187 + fn put(mut self, st: ST) -> Outer { 188 + self.st = st; 189 189 self 190 190 } 191 191 }
+6 -6
crates/fx/src/core/tests/provide_test.rs
··· 25 25 self.a 26 26 } 27 27 } 28 - impl Put<i32> for Ctx { 29 - fn put(mut self, value: i32) -> Self { 30 - self.a = value; 28 + impl Put<i32, Ctx> for Ctx { 29 + fn put(mut self, a: i32) -> Ctx { 30 + self.a = a; 31 31 self 32 32 } 33 33 } 34 34 35 - impl Put<String> for Ctx { 36 - fn put(mut self, value: String) -> Self { 37 - self.b = value; 35 + impl Put<String, Ctx> for Ctx { 36 + fn put(mut self, b: String) -> Ctx { 37 + self.b = b; 38 38 self 39 39 } 40 40 }
+3 -3
crates/fx/src/core/tests/put_test.rs
··· 14 14 b: &'static str, 15 15 } 16 16 17 - impl Put<u32> for Ctx { 18 - fn put(mut self, value: u32) -> Self { 19 - self.a = value; 17 + impl Put<u32, Ctx> for Ctx { 18 + fn put(mut self, a: u32) -> Ctx { 19 + self.a = a; 20 20 self 21 21 } 22 22 }
+2
crates/integration/Cargo.toml
··· 15 15 fx-field = { path = "../field_macro" } 16 16 abilities_macro = { path = "../abilities_macro" } 17 17 dyn-clone = "1.0.19" 18 + builder_macro = { path = "../builder_macro" } 19 + builder_types = { path = "../builder_types" }
+141
crates/integration/tests/builder_macro_test.rs
··· 1 + use builder_macro::ContextBuilder; 2 + use builder_types::{Absent, Present}; 3 + use fx::Has; 4 + use fx::Put; 5 + use fx_field::HasFields; 6 + 7 + #[derive(HasFields, ContextBuilder, Debug, PartialEq, Clone)] 8 + struct Foo { 9 + a: usize, 10 + b: String, 11 + c: bool, 12 + } 13 + 14 + #[test] 15 + fn all_fields() { 16 + let builder = FooBuilder::empty().a(42).b("hello".to_owned()).c(true); 17 + let foo = builder.build(); 18 + assert_eq!( 19 + foo, 20 + Foo { 21 + a: 42, 22 + b: "hello".to_owned(), 23 + c: true 24 + } 25 + ); 26 + } 27 + 28 + #[test] 29 + fn partial_fields() { 30 + let builder = FooBuilder::empty().a(1); 31 + // Should not be able to call build() yet (type error if uncommented) 32 + // let foo = builder.build(); 33 + assert_eq!(builder.maybe_a(), Some(1)); 34 + assert_eq!(builder.maybe_b(), None); 35 + assert_eq!(builder.maybe_c(), None); 36 + } 37 + 38 + #[test] 39 + fn fields_any_order() { 40 + let builder = FooBuilder::<Absent, Absent, Absent>::empty() 41 + .c(true) 42 + .b("world".to_owned()) 43 + .a(99); 44 + let foo = builder.build(); 45 + assert_eq!( 46 + foo, 47 + Foo { 48 + a: 99, 49 + b: "world".to_owned(), 50 + c: true 51 + } 52 + ); 53 + } 54 + 55 + #[test] 56 + fn double_put_overwrites() { 57 + let builder = FooBuilder::<Absent, Absent, Absent>::empty() 58 + .a(1) 59 + .a(2) 60 + .b("x".to_owned()) 61 + .c(false); 62 + let foo = builder.build(); 63 + assert_eq!( 64 + foo, 65 + Foo { 66 + a: 2, 67 + b: "x".to_owned(), 68 + c: false 69 + } 70 + ); 71 + } 72 + 73 + #[test] 74 + fn double_put_all_fields() { 75 + let builder = FooBuilder::<Absent, Absent, Absent>::empty() 76 + .a(1) 77 + .a(2) 78 + .b("x".to_owned()) 79 + .b("y".to_owned()) 80 + .c(false) 81 + .c(true); 82 + let foo = builder.build(); 83 + assert_eq!( 84 + foo, 85 + Foo { 86 + a: 2, 87 + b: "y".to_owned(), 88 + c: true 89 + } 90 + ); 91 + } 92 + 93 + #[test] 94 + fn all_fields_none() { 95 + let builder = FooBuilder::<Absent, Absent, Absent>::empty(); 96 + assert_eq!(builder.maybe_a(), None); 97 + assert_eq!(builder.maybe_b(), None); 98 + assert_eq!(builder.maybe_c(), None); 99 + } 100 + 101 + #[test] 102 + fn all_fields_default() { 103 + let builder = FooBuilder::<Absent, Absent, Absent>::empty() 104 + .a(Default::default()) 105 + .b(Default::default()) 106 + .c(Default::default()); 107 + let foo = builder.build(); 108 + assert_eq!( 109 + foo, 110 + Foo { 111 + a: 0, 112 + b: String::new(), 113 + c: false 114 + } 115 + ); 116 + } 117 + 118 + #[test] 119 + fn has_trait_impl_only_when_field_present() { 120 + // Builder with field 'a' present 121 + let builder_start = FooBuilder::<Absent, Absent, Absent>::empty(); 122 + let builder = builder_start.a(1); // Now FooBuilder<Present, Absent, Absent> 123 + fn assert_has_a<T: Has<usize>>(_t: &T) {} 124 + assert_has_a(&builder); // Should compile 125 + 126 + // Builder with field 'a' absent 127 + let builder_absent = FooBuilder::<Absent, Absent, Absent>::empty(); 128 + // The following line should fail to compile if uncommented: 129 + // assert_has_a(&builder_absent); 130 + } 131 + 132 + #[test] 133 + fn put_trait_enables_has_trait() { 134 + // Builder with field 'a' absent 135 + let builder = FooBuilder::<Absent, Absent, Absent>::empty(); 136 + // Use .put() method to set 'a' and enable Has 137 + let builder_with_a = builder.put(123); // Now FooBuilder<Present, Absent, Absent> 138 + fn assert_has_a<T: Has<usize>>(_t: &T) {} 139 + assert_has_a(&builder_with_a); // Should compile 140 + assert_eq!(builder_with_a.maybe_a(), Some(123)); 141 + }
+29 -3
crates/integration/tests/lens_macro_test.rs
··· 1 - use fx::{Fx, State}; 2 - use fx_lens::{HasPut, Lens}; 1 + use fx::{Fx, Has, Put, State}; 2 + use fx_lens::Lens; 3 3 4 - #[derive(Clone, Debug, PartialEq, Lens, HasPut)] 4 + #[derive(Clone, Debug, PartialEq, Lens)] 5 5 struct ST { 6 6 a: i32, 7 7 b: String, 8 + } 9 + 10 + impl Has<i32> for ST { 11 + fn get(self) -> i32 { 12 + self.a 13 + } 14 + } 15 + 16 + impl Has<String> for ST { 17 + fn get(self) -> String { 18 + self.b 19 + } 20 + } 21 + 22 + impl Put<i32, ST> for ST { 23 + fn put(mut self, a: i32) -> ST { 24 + self.a = a; 25 + self 26 + } 27 + } 28 + 29 + impl Put<String, ST> for ST { 30 + fn put(mut self, b: String) -> ST { 31 + self.b = b; 32 + self 33 + } 8 34 } 9 35 10 36 #[test]
+2 -2
crates/lens_macro/src/lib.rs
··· 67 67 let fname = f.ident.as_ref().unwrap(); 68 68 let fty = &f.ty; 69 69 quote! { 70 - impl #impl_generics ::fx::Put<#fty> for #name #ty_generics #where_clause { 71 - fn put(mut self, value: #fty) -> Self { 70 + impl #impl_generics ::fx::Put<#fty, #name #ty_generics> for #name #ty_generics #where_clause { 71 + fn put(mut self, value: #fty) -> #name #ty_generics { 72 72 self.#fname = value; 73 73 self 74 74 }