Algebraic Effects System for Rust

Macro for Ability families. (#17)

authored by oeiuwq.com and committed by

GitHub 3cc93e77 9e87770a

+216 -5
+58 -4
Cargo.lock
··· 3 3 version = 4 4 4 5 5 [[package]] 6 + name = "abilities_macro" 7 + version = "0.0.0-git" 8 + dependencies = [ 9 + "dyn-clone", 10 + "fx", 11 + "proc-macro-error", 12 + "proc-macro2", 13 + "quote", 14 + "syn 2.0.104", 15 + ] 16 + 17 + [[package]] 6 18 name = "do_traits" 7 19 version = "0.0.0-git" 8 20 dependencies = [ ··· 21 33 dependencies = [ 22 34 "proc-macro2", 23 35 "quote", 24 - "syn", 36 + "syn 2.0.104", 25 37 ] 26 38 27 39 [[package]] ··· 39 51 "fx", 40 52 "proc-macro2", 41 53 "quote", 42 - "syn", 54 + "syn 2.0.104", 43 55 ] 44 56 45 57 [[package]] ··· 49 61 "fx", 50 62 "proc-macro2", 51 63 "quote", 52 - "syn", 64 + "syn 2.0.104", 53 65 ] 54 66 55 67 [[package]] ··· 58 70 dependencies = [ 59 71 "proc-macro2", 60 72 "quote", 61 - "syn", 73 + "syn 2.0.104", 62 74 ] 63 75 64 76 [[package]] 65 77 name = "integration" 66 78 version = "0.0.0-git" 67 79 dependencies = [ 80 + "abilities_macro", 81 + "dyn-clone", 68 82 "fx", 69 83 "fx-do", 70 84 "fx-field", ··· 72 86 ] 73 87 74 88 [[package]] 89 + name = "proc-macro-error" 90 + version = "1.0.4" 91 + source = "registry+https://github.com/rust-lang/crates.io-index" 92 + checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 93 + dependencies = [ 94 + "proc-macro-error-attr", 95 + "proc-macro2", 96 + "quote", 97 + "syn 1.0.109", 98 + "version_check", 99 + ] 100 + 101 + [[package]] 102 + name = "proc-macro-error-attr" 103 + version = "1.0.4" 104 + source = "registry+https://github.com/rust-lang/crates.io-index" 105 + checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 106 + dependencies = [ 107 + "proc-macro2", 108 + "quote", 109 + "version_check", 110 + ] 111 + 112 + [[package]] 75 113 name = "proc-macro2" 76 114 version = "1.0.95" 77 115 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 91 129 92 130 [[package]] 93 131 name = "syn" 132 + version = "1.0.109" 133 + source = "registry+https://github.com/rust-lang/crates.io-index" 134 + checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 135 + dependencies = [ 136 + "proc-macro2", 137 + "unicode-ident", 138 + ] 139 + 140 + [[package]] 141 + name = "syn" 94 142 version = "2.0.104" 95 143 source = "registry+https://github.com/rust-lang/crates.io-index" 96 144 checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" ··· 105 153 version = "1.0.18" 106 154 source = "registry+https://github.com/rust-lang/crates.io-index" 107 155 checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 156 + 157 + [[package]] 158 + name = "version_check" 159 + version = "0.9.5" 160 + source = "registry+https://github.com/rust-lang/crates.io-index" 161 + checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+2 -1
Cargo.toml
··· 7 7 "crates/lens_macro", 8 8 "crates/do_macro", 9 9 "crates/field_macro", 10 - "crates/forall_macro" 10 + "crates/forall_macro", 11 + "crates/abilities_macro" 11 12 ] 12 13 13 14 [workspace.package]
+21
crates/abilities_macro/Cargo.toml
··· 1 + [package] 2 + name = "abilities_macro" 3 + description = "Procedural macro to derive abilities traits and enums for fx." 4 + keywords.workspace = true 5 + license.workspace = true 6 + authors.workspace = true 7 + repository.workspace = true 8 + homepage.workspace = true 9 + version.workspace = true 10 + edition.workspace = true 11 + 12 + [lib] 13 + proc-macro = true 14 + 15 + [dependencies] 16 + quote = "1" 17 + syn = { version = "2", features = ["full"] } 18 + proc-macro2 = "1" 19 + proc-macro-error = "1" 20 + fx = { path = "../fx" } 21 + dyn-clone = "1.0.19"
+71
crates/abilities_macro/src/lib.rs
··· 1 + // Remove duplicate imports and fix macro import 2 + extern crate proc_macro; 3 + use proc_macro::TokenStream; 4 + use quote::quote; 5 + use syn::{FnArg, Item, ReturnType, TraitItem, parse_macro_input}; 6 + 7 + #[proc_macro] 8 + pub fn abilities(input: TokenStream) -> TokenStream { 9 + let input = parse_macro_input!(input as syn::File); 10 + let mut output = quote! {}; 11 + for item in input.items { 12 + if let Item::Trait(trait_item) = item { 13 + let trait_ident = &trait_item.ident; 14 + output.extend(quote! { 15 + #[derive(Clone)] 16 + pub struct #trait_ident; 17 + }); 18 + for trait_item in &trait_item.items { 19 + if let TraitItem::Fn(method) = trait_item { 20 + let method_ident = &method.sig.ident; 21 + let ability_fn_ident = 22 + syn::Ident::new(&format!("{}_ability", method_ident), method_ident.span()); 23 + let (ctx_ty, input_ty, ret_ty) = match extract_types(&method.sig) { 24 + Some(t) => t, 25 + None => continue, 26 + }; 27 + // Associated function for ability construction 28 + output.extend(quote! { 29 + impl #trait_ident { 30 + pub fn #ability_fn_ident<'f, F>(f: F) -> Box<dyn Ability<'f, #input_ty, #ctx_ty, #ret_ty> + 'f> 31 + where F: FnOnce(#input_ty) -> Fx<'f, #ctx_ty, #ret_ty> + Clone + 'f { 32 + Box::new(f) 33 + } 34 + pub fn #method_ident<'f, P>(arg: #input_ty) -> Fx<'f, P, #ret_ty> 35 + where P: Pair<Box<dyn Ability<'f, #input_ty, #ctx_ty, #ret_ty> + 'f>, #ctx_ty> { 36 + Abilities::request::<P, Box<dyn Ability<'f, #input_ty, #ctx_ty, #ret_ty> + 'f>>(arg) 37 + } 38 + } 39 + }); 40 + } 41 + } 42 + } 43 + } 44 + output.into() 45 + } 46 + 47 + fn extract_types( 48 + sig: &syn::Signature, 49 + ) -> Option<( 50 + proc_macro2::TokenStream, 51 + proc_macro2::TokenStream, 52 + proc_macro2::TokenStream, 53 + )> { 54 + let mut args = sig.inputs.iter(); 55 + let ret_ty = match &sig.output { 56 + ReturnType::Type(_, ty) => quote! { #ty }, 57 + ReturnType::Default => quote! { () }, 58 + }; 59 + match (args.next(), args.next()) { 60 + (Some(FnArg::Typed(arg1)), Some(FnArg::Typed(arg2))) => { 61 + let ctx = &arg1.ty; 62 + let input = &arg2.ty; 63 + Some((quote! { #ctx }, quote! { #input }, ret_ty)) 64 + } 65 + (Some(FnArg::Typed(arg)), None) => { 66 + let input = &arg.ty; 67 + Some((quote! { () }, quote! { #input }, ret_ty)) 68 + } 69 + _ => None, 70 + } 71 + }
+2
crates/integration/Cargo.toml
··· 13 13 fx-lens = { path = "../lens_macro" } 14 14 fx-do = { path = "../do_macro" } 15 15 fx-field = { path = "../field_macro" } 16 + abilities_macro = { path = "../abilities_macro" } 17 + dyn-clone = "1.0.19"
+62
crates/integration/tests/abilities_macro_test.rs
··· 1 + use abilities_macro::abilities; 2 + use fx::{Abilities, Ability, Fx, Has, Pair}; 3 + 4 + abilities! { 5 + pub trait Foo { 6 + fn length(line: String) -> usize; 7 + } 8 + 9 + trait Bar { 10 + fn increment(n: usize) -> usize; 11 + fn decrement(n: usize) -> usize; 12 + } 13 + } 14 + 15 + abilities! { 16 + trait Baz { 17 + fn len_add(ctx: String, n: u8) -> usize; 18 + } 19 + } 20 + 21 + #[test] 22 + fn check_types() { 23 + let _: Foo = Foo; 24 + let _: Bar = Bar; 25 + } 26 + 27 + #[test] 28 + fn ability_from_closure() { 29 + let _: Box<dyn Ability<'_, String, (), usize>> = 30 + Foo::length_ability(|line: String| Fx::value(line.len())); 31 + let _: Box<dyn Ability<'_, String, (), usize>> = Box::new(|line: String| Fx::value(line.len())); 32 + } 33 + 34 + #[test] 35 + fn check_type_of_ability_requests() { 36 + fn check_type<'f, P>(_: Fx<'f, P, usize>) 37 + where 38 + P: Pair<Box<dyn Ability<'f, String, (), usize> + 'f>, ()>, 39 + { 40 + } 41 + check_type(Abilities::request::<(_, _), _>("hello".to_owned())); 42 + check_type(Foo::length::<(_, _)>("Hello".to_owned())); 43 + } 44 + 45 + #[test] 46 + fn ctx_ability_from_closure() { 47 + let _: Box<dyn Ability<'_, u8, String, usize>> = 48 + Baz::len_add_ability(|n: u8| Fx::func(move |s: String| s.len() + (n as usize))); 49 + let _: Box<dyn Ability<'_, u8, String, usize>> = 50 + Box::new(|n: u8| Fx::func(move |s: String| s.len() + (n as usize))); 51 + } 52 + 53 + #[test] 54 + fn ctx_check_type_of_ability_requests() { 55 + fn check_type<'f, P>(_: Fx<'f, P, usize>) 56 + where 57 + P: Pair<Box<dyn Ability<'f, u8, String, usize> + 'f>, String>, 58 + { 59 + } 60 + check_type(Abilities::request::<(_, _), _>(10u8)); 61 + check_type(Baz::len_add::<(_, _)>(10u8)); 62 + }