From b6be8fa26ee6971bad7f08ff39a92f80073b43d8 Mon Sep 17 00:00:00 2001 From: Patrick LaFontaine <32135464+Pat-Lafon@users.noreply.github.com> Date: Mon, 13 Feb 2023 11:38:19 -0500 Subject: [PATCH 01/23] First stab at macro support --- .gitignore | 2 +- Cargo.toml | 2 + hashconsing-derive/Cargo.toml | 15 ++++ hashconsing-derive/src/lib.rs | 129 ++++++++++++++++++++++++++++++++++ src/lib.rs | 8 +++ 5 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 hashconsing-derive/Cargo.toml create mode 100644 hashconsing-derive/src/lib.rs diff --git a/.gitignore b/.gitignore index 63a000b..f0aafa3 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,7 @@ Cargo.lock # Ignore target directory. -/target +**/target # Ignore crappy mac files. # Ignore files ending in ".nogit". diff --git a/Cargo.toml b/Cargo.toml index d406671..4320a66 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,9 +29,11 @@ features = ["unstable_docrs"] with_ahash = ["ahash"] unstable_docrs = ["with_ahash"] weak-table = ["dep:weak-table"] +derive = ["hashconsing-derive"] [dependencies] lazy_static = "1.*" +hashconsing-derive = { version = "0.1.0", optional = true, path = "hashconsing-derive" } [dependencies.ahash] version = "^0.8.3" diff --git a/hashconsing-derive/Cargo.toml b/hashconsing-derive/Cargo.toml new file mode 100644 index 0000000..dd2ac3e --- /dev/null +++ b/hashconsing-derive/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "hashconsing-derive" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +proc-macro = true + +[dependencies] +darling = "0.14.2" +proc-macro2 = "1.0" +quote = "1.0" +syn = { version = "1.0", features = ["full"] } diff --git a/hashconsing-derive/src/lib.rs b/hashconsing-derive/src/lib.rs new file mode 100644 index 0000000..f961629 --- /dev/null +++ b/hashconsing-derive/src/lib.rs @@ -0,0 +1,129 @@ +use darling::FromMeta; +use proc_macro::{self, TokenStream}; +use proc_macro2::{Ident, Span}; +use quote::{format_ident, quote}; +use syn::{ + parse_macro_input, punctuated::Punctuated, AttributeArgs, Data, DataEnum, DeriveInput, FnArg, + Pat, PatIdent, PatType, Token, +}; + +#[derive(Debug, Default, FromMeta)] +struct MacroArgs { + name: String, + #[darling(default)] + impls: bool, +} + +#[proc_macro_attribute] +pub fn hcons(args: TokenStream, mut input: TokenStream) -> TokenStream { + let parsed_input = input.clone(); + let attr_args = parse_macro_input!(args as AttributeArgs); + let DeriveInput { + ident, + vis, + attrs, + generics, + data, + } = parse_macro_input!(parsed_input); + + let args = match MacroArgs::from_list(&attr_args) { + Ok(v) => v, + Err(e) => { + return TokenStream::from(e.write_errors()); + } + }; + + let struct_name = format_ident!("{}", args.name); + let factory_name = format_ident!("{}_FACTORY", args.name); + + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + let hash_struct = quote! { + #(#attrs)* + #vis struct #struct_name(HConsed<#ident>); + + impl std::ops::Deref for #struct_name { + type Target = HConsed<#ident>; + fn deref(&self) -> &Self::Target { + &self.0 + } + } + }; + + let hash_impl = match data { + Data::Enum(DataEnum { variants, .. }) => { + let variant_names = variants.iter().map(|v| &v.ident); + let (variant_field_function_args, variant_field_calling_args): ( + Vec>, + Vec>, + ) = variants + .iter() + .map(|v| { + let (arg_names, arg_types): (Vec<_>, Vec<_>) = v + .fields + .iter() + .enumerate() + .map(|(i, f)| (format_ident!("args{}", i), &f.ty)) + .unzip(); + + let calling_args = Punctuated::from_iter(arg_names.clone()); + + let function_args = arg_names + .into_iter() + .zip(arg_types.into_iter()) + .map( + |(n, t)| { + FnArg::Typed(PatType { + attrs: Vec::new(), + pat: Box::new(Pat::Ident(PatIdent { + attrs: Vec::new(), + by_ref: None, + mutability: None, + ident: n, + subpat: None, + })), + colon_token: Token![:](Span::call_site()), + ty: Box::new(t.clone()), + }) + }, /* format!("{} : {}", n, t.into_token_stream()) */ + ) + .collect::>(); + + (Punctuated::from_iter(function_args), calling_args) + }) + .unzip(); + + quote! { + consign! { + /// Factory for terms. + let #factory_name = consign(50) for #ident ; + } + + impl #struct_name { + #(pub fn #variant_names(#variant_field_function_args) -> Self { + Self(#factory_name.mk(#ident :: #variant_names(#variant_field_calling_args))) + })* + } + } + } + _ => todo!("Doesn't yet support things that aren't enums for impl"), + }; + + let output = if args.impls { + quote! { + #hash_struct + + #hash_impl + } + } else { + quote! { + #hash_struct + } + }; + + println!("{output}"); + + input.extend::(output.into()); + + input +} diff --git a/src/lib.rs b/src/lib.rs index ecb0876..b125b95 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -227,6 +227,14 @@ use std::{ pub extern crate lazy_static; +#[cfg(feature = "derive")] +#[allow(unused_imports)] +#[macro_use] +extern crate hashconsing_derive; +#[cfg(feature = "derive")] +#[doc(hidden)] +pub use hashconsing_derive::*; + #[cfg(test)] mod test; From c3f52a1c52a0dc4db45eed2d03011fe7d6d6564e Mon Sep 17 00:00:00 2001 From: Patrick LaFontaine <32135464+Pat-Lafon@users.noreply.github.com> Date: Mon, 13 Feb 2023 15:00:41 -0500 Subject: [PATCH 02/23] Make more structured and fix for empty enum variants --- hashconsing-derive/src/lib.rs | 91 +++++++++++++++++++++++++---------- 1 file changed, 65 insertions(+), 26 deletions(-) diff --git a/hashconsing-derive/src/lib.rs b/hashconsing-derive/src/lib.rs index f961629..005c3cc 100644 --- a/hashconsing-derive/src/lib.rs +++ b/hashconsing-derive/src/lib.rs @@ -1,10 +1,11 @@ use darling::FromMeta; use proc_macro::{self, TokenStream}; -use proc_macro2::{Ident, Span}; +use proc_macro2::Span; use quote::{format_ident, quote}; use syn::{ - parse_macro_input, punctuated::Punctuated, AttributeArgs, Data, DataEnum, DeriveInput, FnArg, - Pat, PatIdent, PatType, Token, + parse_macro_input, punctuated::Punctuated, token::Paren, AttributeArgs, Data, DataEnum, + DeriveInput, Expr, ExprCall, ExprPath, FnArg, Pat, PatIdent, PatType, Path, PathArguments, + PathSegment, Token, }; #[derive(Debug, Default, FromMeta)] @@ -22,7 +23,7 @@ pub fn hcons(args: TokenStream, mut input: TokenStream) -> TokenStream { ident, vis, attrs, - generics, + generics: _, data, } = parse_macro_input!(parsed_input); @@ -36,7 +37,7 @@ pub fn hcons(args: TokenStream, mut input: TokenStream) -> TokenStream { let struct_name = format_ident!("{}", args.name); let factory_name = format_ident!("{}_FACTORY", args.name); - let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + /* let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); */ let hash_struct = quote! { #(#attrs)* @@ -55,53 +56,91 @@ pub fn hcons(args: TokenStream, mut input: TokenStream) -> TokenStream { let variant_names = variants.iter().map(|v| &v.ident); let (variant_field_function_args, variant_field_calling_args): ( Vec>, - Vec>, + Vec, ) = variants .iter() .map(|v| { - let (arg_names, arg_types): (Vec<_>, Vec<_>) = v + let (arg_names, arg_types): (Vec, Vec) = v .fields .iter() .enumerate() - .map(|(i, f)| (format_ident!("args{}", i), &f.ty)) - .unzip(); - - let calling_args = Punctuated::from_iter(arg_names.clone()); - - let function_args = arg_names - .into_iter() - .zip(arg_types.into_iter()) - .map( - |(n, t)| { + .map(|(i, f)| { + let id = format_ident!("args{}", i); + ( + { + ExprPath { + attrs: Vec::new(), + qself: None, + path: Path { + leading_colon: None, + segments: Punctuated::from_iter(vec![PathSegment { + ident: id.clone(), + arguments: PathArguments::None, + }]), + }, + } + .into() + }, FnArg::Typed(PatType { attrs: Vec::new(), pat: Box::new(Pat::Ident(PatIdent { attrs: Vec::new(), by_ref: None, mutability: None, - ident: n, + ident: id, subpat: None, })), colon_token: Token![:](Span::call_site()), - ty: Box::new(t.clone()), - }) - }, /* format!("{} : {}", n, t.into_token_stream()) */ - ) - .collect::>(); + ty: Box::new(f.ty.clone()), + }), + ) + }) + .unzip(); - (Punctuated::from_iter(function_args), calling_args) + let calling_args = Punctuated::from_iter(arg_names); + + let variant_name = Expr::Path(ExprPath { + attrs: Vec::new(), + qself: None, + path: Path { + leading_colon: None, + segments: Punctuated::from_iter(vec![ + PathSegment { + ident: ident.clone(), + arguments: PathArguments::None, + }, + PathSegment { + ident: v.ident.clone(), + arguments: PathArguments::None, + }, + ]), + }, + }); + + let variant_expr = if calling_args.is_empty() { + variant_name + } else { + ExprCall { + attrs: Vec::new(), + func: Box::new(variant_name), + paren_token: Paren(Span::call_site()), + args: calling_args, + } + .into() + }; + + (Punctuated::from_iter(arg_types), variant_expr) }) .unzip(); quote! { consign! { - /// Factory for terms. let #factory_name = consign(50) for #ident ; } impl #struct_name { #(pub fn #variant_names(#variant_field_function_args) -> Self { - Self(#factory_name.mk(#ident :: #variant_names(#variant_field_calling_args))) + Self(#factory_name.mk(#variant_field_calling_args)) })* } } From 9626831e2860aa28c88b9e5bf1ad9113c687230b Mon Sep 17 00:00:00 2001 From: Patrick LaFontaine <32135464+Pat-Lafon@users.noreply.github.com> Date: Sat, 18 Feb 2023 10:46:15 -0500 Subject: [PATCH 03/23] Move hashconsing/derive into a workspace --- Cargo.toml | 53 ++----------------- hashconsing/Cargo.toml | 49 +++++++++++++++++ {src => hashconsing/src}/coll.rs | 0 {src => hashconsing/src}/hash_coll.rs | 0 {src => hashconsing/src}/hash_coll/hashers.rs | 0 {src => hashconsing/src}/lib.rs | 0 {src => hashconsing/src}/test.rs | 0 {src => hashconsing/src}/test/basic.rs | 0 {src => hashconsing/src}/test/collect.rs | 0 {tests => hashconsing/tests}/send_sync.rs | 0 .../Cargo.toml | 0 .../src/lib.rs | 0 12 files changed, 54 insertions(+), 48 deletions(-) create mode 100644 hashconsing/Cargo.toml rename {src => hashconsing/src}/coll.rs (100%) rename {src => hashconsing/src}/hash_coll.rs (100%) rename {src => hashconsing/src}/hash_coll/hashers.rs (100%) rename {src => hashconsing/src}/lib.rs (100%) rename {src => hashconsing/src}/test.rs (100%) rename {src => hashconsing/src}/test/basic.rs (100%) rename {src => hashconsing/src}/test/collect.rs (100%) rename {tests => hashconsing/tests}/send_sync.rs (100%) rename {hashconsing-derive => hashconsing_derive}/Cargo.toml (100%) rename {hashconsing-derive => hashconsing_derive}/src/lib.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 4320a66..c56bdc6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,50 +1,7 @@ -[package] -name = "hashconsing" -version = "1.7.0" -authors = [ - "Adrien Champion ", - "Leni Aniva ", +[workspace] +members = [ + "hashconsing", + "hashconsing_derive", ] -description = "A hash consing library." -documentation = "https://docs.rs/hashconsing" -homepage = "https://github.com/AdrienChampion/hashconsing" -repository = "https://github.com/AdrienChampion/hashconsing" -readme = "README.md" -categories = [ - "caching", - "compression", - "concurrency", - "data-structures", - "memory-management", -] -keywords = ["hashconsing", "hash", "consing", "sharing", "caching"] -license = "MIT/Apache-2.0" -edition = "2021" -rust-version = "1.60" - -[package.metadata.docs.rs] -features = ["unstable_docrs"] - -[features] -with_ahash = ["ahash"] -unstable_docrs = ["with_ahash"] -weak-table = ["dep:weak-table"] -derive = ["hashconsing-derive"] - -[dependencies] -lazy_static = "1.*" -hashconsing-derive = { version = "0.1.0", optional = true, path = "hashconsing-derive" } - -[dependencies.ahash] -version = "^0.8.3" -optional = true - -[dependencies.weak-table] -version = "^0.3.0" -optional = true -[dev-dependencies] -crossbeam-utils = "^0.8" -trybuild = "^1.0" -rayon = "^1.5" -rand = "0.8" +resolver = "2" diff --git a/hashconsing/Cargo.toml b/hashconsing/Cargo.toml new file mode 100644 index 0000000..b22bdee --- /dev/null +++ b/hashconsing/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "hashconsing" +version = "1.7.0" +authors = [ + "Adrien Champion ", + "Leni Aniva ", +] +description = "A hash consing library." +documentation = "https://docs.rs/hashconsing" +homepage = "https://github.com/AdrienChampion/hashconsing" +repository = "https://github.com/AdrienChampion/hashconsing" +readme = "README.md" +categories = [ + "caching", + "compression", + "concurrency", + "data-structures", + "memory-management", +] +keywords = ["hashconsing", "hash", "consing", "sharing", "caching"] +license = "MIT/Apache-2.0" +edition = "2021" +rust-version = "1.60" + +[package.metadata.docs.rs] +features = ["unstable_docrs"] + +[features] +with_ahash = ["ahash"] +unstable_docrs = ["with_ahash"] +weak-table = ["dep:weak-table"] +derive = ["hashconsing-derive"] + +[dependencies] +lazy_static = "1.*" +hashconsing-derive = { version = "0.1.0", optional = true, path = "../hashconsing_derive" } + +[dependencies.ahash] +version = "^0.8.3" +optional = true + +[dependencies.weak-table] +version = "^0.3.0" +optional = true + +[dev-dependencies] +crossbeam-utils = "^0.8" +rayon = "^1.5" +rand = "0.8" \ No newline at end of file diff --git a/src/coll.rs b/hashconsing/src/coll.rs similarity index 100% rename from src/coll.rs rename to hashconsing/src/coll.rs diff --git a/src/hash_coll.rs b/hashconsing/src/hash_coll.rs similarity index 100% rename from src/hash_coll.rs rename to hashconsing/src/hash_coll.rs diff --git a/src/hash_coll/hashers.rs b/hashconsing/src/hash_coll/hashers.rs similarity index 100% rename from src/hash_coll/hashers.rs rename to hashconsing/src/hash_coll/hashers.rs diff --git a/src/lib.rs b/hashconsing/src/lib.rs similarity index 100% rename from src/lib.rs rename to hashconsing/src/lib.rs diff --git a/src/test.rs b/hashconsing/src/test.rs similarity index 100% rename from src/test.rs rename to hashconsing/src/test.rs diff --git a/src/test/basic.rs b/hashconsing/src/test/basic.rs similarity index 100% rename from src/test/basic.rs rename to hashconsing/src/test/basic.rs diff --git a/src/test/collect.rs b/hashconsing/src/test/collect.rs similarity index 100% rename from src/test/collect.rs rename to hashconsing/src/test/collect.rs diff --git a/tests/send_sync.rs b/hashconsing/tests/send_sync.rs similarity index 100% rename from tests/send_sync.rs rename to hashconsing/tests/send_sync.rs diff --git a/hashconsing-derive/Cargo.toml b/hashconsing_derive/Cargo.toml similarity index 100% rename from hashconsing-derive/Cargo.toml rename to hashconsing_derive/Cargo.toml diff --git a/hashconsing-derive/src/lib.rs b/hashconsing_derive/src/lib.rs similarity index 100% rename from hashconsing-derive/src/lib.rs rename to hashconsing_derive/src/lib.rs From f3df5a1453ff7e373dd0bfcdcdcb0e87773a5a46 Mon Sep 17 00:00:00 2001 From: Patrick LaFontaine <32135464+Pat-Lafon@users.noreply.github.com> Date: Fri, 3 Mar 2023 11:10:36 -0500 Subject: [PATCH 04/23] update error handling with proc_macro_error --- hashconsing_derive/Cargo.toml | 1 + hashconsing_derive/src/lib.rs | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/hashconsing_derive/Cargo.toml b/hashconsing_derive/Cargo.toml index dd2ac3e..a671b35 100644 --- a/hashconsing_derive/Cargo.toml +++ b/hashconsing_derive/Cargo.toml @@ -10,6 +10,7 @@ proc-macro = true [dependencies] darling = "0.14.2" +proc-macro-error = "1.0.4" proc-macro2 = "1.0" quote = "1.0" syn = { version = "1.0", features = ["full"] } diff --git a/hashconsing_derive/src/lib.rs b/hashconsing_derive/src/lib.rs index 005c3cc..747603c 100644 --- a/hashconsing_derive/src/lib.rs +++ b/hashconsing_derive/src/lib.rs @@ -1,6 +1,7 @@ use darling::FromMeta; use proc_macro::{self, TokenStream}; use proc_macro2::Span; +use proc_macro_error::{abort_call_site, proc_macro_error}; use quote::{format_ident, quote}; use syn::{ parse_macro_input, punctuated::Punctuated, token::Paren, AttributeArgs, Data, DataEnum, @@ -15,6 +16,7 @@ struct MacroArgs { impls: bool, } +#[proc_macro_error] #[proc_macro_attribute] pub fn hcons(args: TokenStream, mut input: TokenStream) -> TokenStream { let parsed_input = input.clone(); @@ -145,7 +147,7 @@ pub fn hcons(args: TokenStream, mut input: TokenStream) -> TokenStream { } } } - _ => todo!("Doesn't yet support things that aren't enums for impl"), + _ => abort_call_site!("unsupported syntax: hashconsing expected an enum definition"), }; let output = if args.impls { @@ -160,7 +162,7 @@ pub fn hcons(args: TokenStream, mut input: TokenStream) -> TokenStream { } }; - println!("{output}"); + /* println!("{output}"); */ input.extend::(output.into()); From 064c273917ad54d72f75b4f0b1bc5602145b82ac Mon Sep 17 00:00:00 2001 From: Patrick LaFontaine <32135464+Pat-Lafon@users.noreply.github.com> Date: Fri, 3 Mar 2023 14:38:52 -0500 Subject: [PATCH 05/23] Use darling flags for factory x impls --- hashconsing_derive/src/lib.rs | 38 ++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/hashconsing_derive/src/lib.rs b/hashconsing_derive/src/lib.rs index 747603c..d37bbec 100644 --- a/hashconsing_derive/src/lib.rs +++ b/hashconsing_derive/src/lib.rs @@ -1,4 +1,4 @@ -use darling::FromMeta; +use darling::{util::Flag, FromMeta, Result}; use proc_macro::{self, TokenStream}; use proc_macro2::Span; use proc_macro_error::{abort_call_site, proc_macro_error}; @@ -10,10 +10,20 @@ use syn::{ }; #[derive(Debug, Default, FromMeta)] +#[darling(and_then = "Self::not_constructors_without_factory")] struct MacroArgs { name: String, - #[darling(default)] - impls: bool, + no_factory: Flag, + no_constructors: Flag, +} + +impl MacroArgs { + fn not_constructors_without_factory(self) -> Result { + if self.no_factory.is_present() && !self.no_constructors.is_present() { + abort_call_site!("unsupported flag usage: Can't implement constructors without a static factory") + }; + Ok(self) + } } #[proc_macro_error] @@ -53,6 +63,12 @@ pub fn hcons(args: TokenStream, mut input: TokenStream) -> TokenStream { } }; + let hash_factory = quote! { + consign! { + let #factory_name = consign(50) for #ident ; + } + }; + let hash_impl = match data { Data::Enum(DataEnum { variants, .. }) => { let variant_names = variants.iter().map(|v| &v.ident); @@ -136,10 +152,6 @@ pub fn hcons(args: TokenStream, mut input: TokenStream) -> TokenStream { .unzip(); quote! { - consign! { - let #factory_name = consign(50) for #ident ; - } - impl #struct_name { #(pub fn #variant_names(#variant_field_function_args) -> Self { Self(#factory_name.mk(#variant_field_calling_args)) @@ -150,15 +162,23 @@ pub fn hcons(args: TokenStream, mut input: TokenStream) -> TokenStream { _ => abort_call_site!("unsupported syntax: hashconsing expected an enum definition"), }; - let output = if args.impls { + let output = if args.no_constructors.is_present() && args.no_factory.is_present() { + quote! { + #hash_struct + } + } else if args.no_constructors.is_present() { quote! { #hash_struct - #hash_impl + #hash_factory } } else { quote! { #hash_struct + + #hash_factory + + #hash_impl } }; From 6571f3e4012bc254cda8b6b115c8be54e700193a Mon Sep 17 00:00:00 2001 From: Patrick LaFontaine <32135464+Pat-Lafon@users.noreply.github.com> Date: Fri, 3 Mar 2023 14:41:18 -0500 Subject: [PATCH 06/23] cargo fmt --- hashconsing_derive/src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/hashconsing_derive/src/lib.rs b/hashconsing_derive/src/lib.rs index d37bbec..753fd4f 100644 --- a/hashconsing_derive/src/lib.rs +++ b/hashconsing_derive/src/lib.rs @@ -20,7 +20,9 @@ struct MacroArgs { impl MacroArgs { fn not_constructors_without_factory(self) -> Result { if self.no_factory.is_present() && !self.no_constructors.is_present() { - abort_call_site!("unsupported flag usage: Can't implement constructors without a static factory") + abort_call_site!( + "unsupported flag usage: Can't implement constructors without a static factory" + ) }; Ok(self) } From 94f2c86acff6bd050929c45b1f907f25b629bb94 Mon Sep 17 00:00:00 2001 From: Patrick LaFontaine <32135464+Pat-Lafon@users.noreply.github.com> Date: Wed, 22 Mar 2023 16:47:50 -0400 Subject: [PATCH 07/23] Hide uninteresting clippy warnings from generated code --- hashconsing_derive/src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/hashconsing_derive/src/lib.rs b/hashconsing_derive/src/lib.rs index 753fd4f..fe3a8b4 100644 --- a/hashconsing_derive/src/lib.rs +++ b/hashconsing_derive/src/lib.rs @@ -55,8 +55,10 @@ pub fn hcons(args: TokenStream, mut input: TokenStream) -> TokenStream { let hash_struct = quote! { #(#attrs)* + #[automatically_derived] #vis struct #struct_name(HConsed<#ident>); + #[automatically_derived] impl std::ops::Deref for #struct_name { type Target = HConsed<#ident>; fn deref(&self) -> &Self::Target { @@ -154,6 +156,8 @@ pub fn hcons(args: TokenStream, mut input: TokenStream) -> TokenStream { .unzip(); quote! { + #[automatically_derived] + #[allow(non_snake_case)] impl #struct_name { #(pub fn #variant_names(#variant_field_function_args) -> Self { Self(#factory_name.mk(#variant_field_calling_args)) From a164e02dc750b6c26c3fb33bbe544edb8bee9cb0 Mon Sep 17 00:00:00 2001 From: Patrick LaFontaine <32135464+Pat-Lafon@users.noreply.github.com> Date: Wed, 27 Sep 2023 17:09:00 -0400 Subject: [PATCH 08/23] Small version updates --- hashconsing_derive/Cargo.toml | 6 +++--- hashconsing_derive/src/lib.rs | 17 ++++++++++++----- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/hashconsing_derive/Cargo.toml b/hashconsing_derive/Cargo.toml index a671b35..b2cc1e0 100644 --- a/hashconsing_derive/Cargo.toml +++ b/hashconsing_derive/Cargo.toml @@ -9,8 +9,8 @@ edition = "2021" proc-macro = true [dependencies] -darling = "0.14.2" -proc-macro-error = "1.0.4" +darling = "0.20" +proc-macro-error = "1.0" proc-macro2 = "1.0" quote = "1.0" -syn = { version = "1.0", features = ["full"] } +syn = { version = "2.0", features = ["full"] } diff --git a/hashconsing_derive/src/lib.rs b/hashconsing_derive/src/lib.rs index fe3a8b4..962e6bc 100644 --- a/hashconsing_derive/src/lib.rs +++ b/hashconsing_derive/src/lib.rs @@ -1,12 +1,12 @@ -use darling::{util::Flag, FromMeta, Result}; +use darling::{ast::NestedMeta, util::Flag, Error, FromMeta, Result}; use proc_macro::{self, TokenStream}; use proc_macro2::Span; use proc_macro_error::{abort_call_site, proc_macro_error}; use quote::{format_ident, quote}; use syn::{ - parse_macro_input, punctuated::Punctuated, token::Paren, AttributeArgs, Data, DataEnum, - DeriveInput, Expr, ExprCall, ExprPath, FnArg, Pat, PatIdent, PatType, Path, PathArguments, - PathSegment, Token, + parse_macro_input, punctuated::Punctuated, token::Paren, Data, DataEnum, DeriveInput, Expr, + ExprCall, ExprPath, FnArg, Pat, PatIdent, PatType, Path, PathArguments, PathSegment, + Token, }; #[derive(Debug, Default, FromMeta)] @@ -32,7 +32,14 @@ impl MacroArgs { #[proc_macro_attribute] pub fn hcons(args: TokenStream, mut input: TokenStream) -> TokenStream { let parsed_input = input.clone(); - let attr_args = parse_macro_input!(args as AttributeArgs); + + let attr_args = match NestedMeta::parse_meta_list(args.into()) { + Ok(v) => v, + Err(e) => { + return TokenStream::from(Error::from(e).write_errors()); + } + }; + let DeriveInput { ident, vis, From 596d268323c0e710127b7f2a36501ed2d2e77331 Mon Sep 17 00:00:00 2001 From: Patrick LaFontaine <32135464+Pat-Lafon@users.noreply.github.com> Date: Wed, 27 Sep 2023 17:59:15 -0400 Subject: [PATCH 09/23] Add doc test --- hashconsing_derive/Cargo.toml | 4 +++ hashconsing_derive/src/lib.rs | 52 +++++++++++++++++++++++++++++++---- 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/hashconsing_derive/Cargo.toml b/hashconsing_derive/Cargo.toml index b2cc1e0..2ca579b 100644 --- a/hashconsing_derive/Cargo.toml +++ b/hashconsing_derive/Cargo.toml @@ -14,3 +14,7 @@ proc-macro-error = "1.0" proc-macro2 = "1.0" quote = "1.0" syn = { version = "2.0", features = ["full"] } + +# For doc tests +[dev-dependencies] +hashconsing = {path = "../hashconsing", features = ["derive"]} diff --git a/hashconsing_derive/src/lib.rs b/hashconsing_derive/src/lib.rs index 962e6bc..5ae2a1e 100644 --- a/hashconsing_derive/src/lib.rs +++ b/hashconsing_derive/src/lib.rs @@ -1,3 +1,45 @@ +//! The derive attribute implementation for the hashconsing crate +//! +//! This automatically generates some of the boilerplate that is needed in the standard use case of hashconsing. +//! +//! There are two parts to this: +//! - the static factory which can be referenced as `_FACTORY` +//! - A series of constructor functions for creating each of the variants +//! +//! +//! +//! Example: +//! ```rust +//! use hashconsing::hcons; +//! use std::ops::Deref; +//! +//! // Can optionally turn off the factory or the constructor generation +//! // #[hcons(name = "Type", no_factory, no_constructors)] +//! #[hcons(name = "Type")] +//! #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)] +//! pub enum ActualType { +//! Named(String), +//! Arrow(Type, Type), +//! Tuple(Vec), +//! Mu(String, Type), +//! Variant(Vec<(String, Type)>), +//! } +//! +//! impl ActualType { +//! pub fn is_named(&self) -> bool { +//! matches!(self, Self::Named(_)) +//! } +//! } +//! +//! fn main() { +//! let named_type = Type::Named("int".to_string()); +//! // Dereferences to the underlying type with access to methods +//! assert!(named_type.is_named()); +//! let tuple = Type::Tuple(vec![named_type]); +//! assert!(!tuple.is_named()); +//! } +//! ``` + use darling::{ast::NestedMeta, util::Flag, Error, FromMeta, Result}; use proc_macro::{self, TokenStream}; use proc_macro2::Span; @@ -5,8 +47,7 @@ use proc_macro_error::{abort_call_site, proc_macro_error}; use quote::{format_ident, quote}; use syn::{ parse_macro_input, punctuated::Punctuated, token::Paren, Data, DataEnum, DeriveInput, Expr, - ExprCall, ExprPath, FnArg, Pat, PatIdent, PatType, Path, PathArguments, PathSegment, - Token, + ExprCall, ExprPath, FnArg, Pat, PatIdent, PatType, Path, PathArguments, PathSegment, Token, }; #[derive(Debug, Default, FromMeta)] @@ -63,11 +104,11 @@ pub fn hcons(args: TokenStream, mut input: TokenStream) -> TokenStream { let hash_struct = quote! { #(#attrs)* #[automatically_derived] - #vis struct #struct_name(HConsed<#ident>); + #vis struct #struct_name(hashconsing::HConsed<#ident>); #[automatically_derived] impl std::ops::Deref for #struct_name { - type Target = HConsed<#ident>; + type Target = hashconsing::HConsed<#ident>; fn deref(&self) -> &Self::Target { &self.0 } @@ -75,7 +116,7 @@ pub fn hcons(args: TokenStream, mut input: TokenStream) -> TokenStream { }; let hash_factory = quote! { - consign! { + hashconsing::consign! { let #factory_name = consign(50) for #ident ; } }; @@ -167,6 +208,7 @@ pub fn hcons(args: TokenStream, mut input: TokenStream) -> TokenStream { #[allow(non_snake_case)] impl #struct_name { #(pub fn #variant_names(#variant_field_function_args) -> Self { + use hashconsing::HashConsign; Self(#factory_name.mk(#variant_field_calling_args)) })* } From d6a511f217825ae52e08cd2715b9a3921417e683 Mon Sep 17 00:00:00 2001 From: Patrick LaFontaine <32135464+Pat-Lafon@users.noreply.github.com> Date: Fri, 29 Sep 2023 14:19:16 -0400 Subject: [PATCH 10/23] Remove needless main --- hashconsing_derive/src/lib.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/hashconsing_derive/src/lib.rs b/hashconsing_derive/src/lib.rs index 5ae2a1e..513bc1e 100644 --- a/hashconsing_derive/src/lib.rs +++ b/hashconsing_derive/src/lib.rs @@ -6,7 +6,7 @@ //! - the static factory which can be referenced as `_FACTORY` //! - A series of constructor functions for creating each of the variants //! -//! +//! //! //! Example: //! ```rust @@ -31,13 +31,11 @@ //! } //! } //! -//! fn main() { -//! let named_type = Type::Named("int".to_string()); -//! // Dereferences to the underlying type with access to methods -//! assert!(named_type.is_named()); -//! let tuple = Type::Tuple(vec![named_type]); -//! assert!(!tuple.is_named()); -//! } +//! let named_type = Type::Named("int".to_string()); +//! // Dereferences to the underlying type with access to methods +//! assert!(named_type.is_named()); +//! let tuple = Type::Tuple(vec![named_type]); +//! assert!(!tuple.is_named()); //! ``` use darling::{ast::NestedMeta, util::Flag, Error, FromMeta, Result}; From 3c0b9872e8895699c89db30a8b32f26d24591fa2 Mon Sep 17 00:00:00 2001 From: Patrick LaFontaine <32135464+Pat-Lafon@users.noreply.github.com> Date: Mon, 12 Jan 2026 11:38:41 -0500 Subject: [PATCH 11/23] '_ lifetime to iter() return types --- hashconsing/src/coll.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/hashconsing/src/coll.rs b/hashconsing/src/coll.rs index 2855128..d656a6d 100644 --- a/hashconsing/src/coll.rs +++ b/hashconsing/src/coll.rs @@ -178,7 +178,7 @@ where } /// An iterator visiting all elements. #[inline] - pub fn iter<'a>(&'a self) -> ::std::collections::hash_set::Iter<'a, HConsed> { + pub fn iter(&self) -> ::std::collections::hash_set::Iter<'_, HConsed> { self.set.iter() } } @@ -322,14 +322,12 @@ where } /// An iterator visiting all elements. #[inline] - pub fn iter<'a>(&'a self) -> ::std::collections::hash_map::Iter<'a, HConsed, V> { + pub fn iter(&self) -> ::std::collections::hash_map::Iter<'_, HConsed, V> { self.map.iter() } /// An iterator visiting all elements. #[inline] - pub fn iter_mut<'a>( - &'a mut self, - ) -> ::std::collections::hash_map::IterMut<'a, HConsed, V> { + pub fn iter_mut(&mut self) -> ::std::collections::hash_map::IterMut<'_, HConsed, V> { self.map.iter_mut() } } From 2a7fffc987b8bfec2daa2d04300484b07f5f5cad Mon Sep 17 00:00:00 2001 From: Patrick LaFontaine <32135464+Pat-Lafon@users.noreply.github.com> Date: Mon, 12 Jan 2026 11:39:53 -0500 Subject: [PATCH 12/23] part 2 --- hashconsing/src/hash_coll.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/hashconsing/src/hash_coll.rs b/hashconsing/src/hash_coll.rs index a79d87c..65943e5 100644 --- a/hashconsing/src/hash_coll.rs +++ b/hashconsing/src/hash_coll.rs @@ -322,7 +322,7 @@ where { /// An iterator visiting all elements. #[inline] - pub fn iter<'a>(&'a self) -> ::std::collections::hash_set::Iter<'a, HConsed> { + pub fn iter(&self) -> ::std::collections::hash_set::Iter<'_, HConsed> { self.set.iter() } } @@ -520,14 +520,12 @@ where { /// An iterator visiting all elements. #[inline] - pub fn iter<'a>(&'a self) -> ::std::collections::hash_map::Iter<'a, HConsed, V> { + pub fn iter(&self) -> ::std::collections::hash_map::Iter<'_, HConsed, V> { self.map.iter() } /// An iterator visiting all elements. #[inline] - pub fn iter_mut<'a>( - &'a mut self, - ) -> ::std::collections::hash_map::IterMut<'a, HConsed, V> { + pub fn iter_mut(&mut self) -> ::std::collections::hash_map::IterMut<'_, HConsed, V> { self.map.iter_mut() } } From 68a31dc80555aa88f621b6d24ac90342a64d637f Mon Sep 17 00:00:00 2001 From: Patrick LaFontaine <32135464+Pat-Lafon@users.noreply.github.com> Date: Mon, 12 Jan 2026 11:51:13 -0500 Subject: [PATCH 13/23] Add named fields --- hashconsing_derive/src/lib.rs | 235 +++++++++++++++++++++++----------- 1 file changed, 160 insertions(+), 75 deletions(-) diff --git a/hashconsing_derive/src/lib.rs b/hashconsing_derive/src/lib.rs index 513bc1e..8d4ca61 100644 --- a/hashconsing_derive/src/lib.rs +++ b/hashconsing_derive/src/lib.rs @@ -18,24 +18,40 @@ //! #[hcons(name = "Type")] //! #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)] //! pub enum ActualType { -//! Named(String), -//! Arrow(Type, Type), -//! Tuple(Vec), -//! Mu(String, Type), -//! Variant(Vec<(String, Type)>), +//! // Tuple-style variants +//! Named(String), +//! Arrow(Type, Type), +//! Tuple(Vec), +//! Mu(String, Type), +//! Variant(Vec<(String, Type)>), +//! // Struct-style variant +//! Record { fields: Vec<(String, Type)>, is_open: bool }, +//! // Unit variant +//! Unit, //! } //! //! impl ActualType { //! pub fn is_named(&self) -> bool { //! matches!(self, Self::Named(_)) //! } +//! pub fn is_record(&self) -> bool { +//! matches!(self, Self::Record { .. }) +//! } //! } //! //! let named_type = Type::Named("int".to_string()); //! // Dereferences to the underlying type with access to methods //! assert!(named_type.is_named()); -//! let tuple = Type::Tuple(vec![named_type]); +//! let tuple = Type::Tuple(vec![named_type.clone()]); //! assert!(!tuple.is_named()); +//! +//! // Struct-style variant with named fields +//! let record = Type::Record(vec![("x".to_string(), named_type)], false); +//! assert!(record.is_record()); +//! +//! // Unit variant +//! let unit = Type::Unit(); +//! assert!(!unit.is_named()); //! ``` use darling::{ast::NestedMeta, util::Flag, Error, FromMeta, Result}; @@ -44,8 +60,9 @@ use proc_macro2::Span; use proc_macro_error::{abort_call_site, proc_macro_error}; use quote::{format_ident, quote}; use syn::{ - parse_macro_input, punctuated::Punctuated, token::Paren, Data, DataEnum, DeriveInput, Expr, - ExprCall, ExprPath, FnArg, Pat, PatIdent, PatType, Path, PathArguments, PathSegment, Token, + parse_macro_input, punctuated::Punctuated, token::Brace, token::Paren, Data, DataEnum, + DeriveInput, Expr, ExprCall, ExprPath, ExprStruct, FieldValue, Fields, FnArg, Member, Pat, + PatIdent, PatType, Path, PathArguments, PathSegment, Token, }; #[derive(Debug, Default, FromMeta)] @@ -128,76 +145,144 @@ pub fn hcons(args: TokenStream, mut input: TokenStream) -> TokenStream { ) = variants .iter() .map(|v| { - let (arg_names, arg_types): (Vec, Vec) = v - .fields - .iter() - .enumerate() - .map(|(i, f)| { - let id = format_ident!("args{}", i); - ( - { - ExprPath { - attrs: Vec::new(), - qself: None, - path: Path { - leading_colon: None, - segments: Punctuated::from_iter(vec![PathSegment { - ident: id.clone(), - arguments: PathArguments::None, - }]), + let variant_path = Path { + leading_colon: None, + segments: Punctuated::from_iter(vec![ + PathSegment { + ident: ident.clone(), + arguments: PathArguments::None, + }, + PathSegment { + ident: v.ident.clone(), + arguments: PathArguments::None, + }, + ]), + }; + + match &v.fields { + Fields::Named(named_fields) => { + // Struct-like variant: MyVariant { field_1: Type, field_2: Type } + let (field_values, fn_args): (Vec, Vec) = + named_fields + .named + .iter() + .map(|f| { + let field_ident = f.ident.clone().unwrap(); + ( + FieldValue { + attrs: Vec::new(), + member: Member::Named(field_ident.clone()), + colon_token: None, // shorthand syntax + expr: Expr::Path(ExprPath { + attrs: Vec::new(), + qself: None, + path: Path { + leading_colon: None, + segments: Punctuated::from_iter(vec![ + PathSegment { + ident: field_ident.clone(), + arguments: PathArguments::None, + }, + ]), + }, + }), + }, + FnArg::Typed(PatType { + attrs: Vec::new(), + pat: Box::new(Pat::Ident(PatIdent { + attrs: Vec::new(), + by_ref: None, + mutability: None, + ident: field_ident, + subpat: None, + })), + colon_token: Token![:](Span::call_site()), + ty: Box::new(f.ty.clone()), + }), + ) + }) + .unzip(); + + let variant_expr = ExprStruct { + attrs: Vec::new(), + qself: None, + path: variant_path, + brace_token: Brace(Span::call_site()), + fields: Punctuated::from_iter(field_values), + dot2_token: None, + rest: None, + }; + + (Punctuated::from_iter(fn_args), variant_expr.into()) + } + Fields::Unnamed(_) => { + // Tuple-like variant: MyVariant(Type1, Type2) + let (arg_names, arg_types): (Vec, Vec) = v + .fields + .iter() + .enumerate() + .map(|(i, f)| { + let id = format_ident!("args{}", i); + ( + { + ExprPath { + attrs: Vec::new(), + qself: None, + path: Path { + leading_colon: None, + segments: Punctuated::from_iter(vec![ + PathSegment { + ident: id.clone(), + arguments: PathArguments::None, + }, + ]), + }, + } + .into() }, - } - .into() - }, - FnArg::Typed(PatType { - attrs: Vec::new(), - pat: Box::new(Pat::Ident(PatIdent { - attrs: Vec::new(), - by_ref: None, - mutability: None, - ident: id, - subpat: None, - })), - colon_token: Token![:](Span::call_site()), - ty: Box::new(f.ty.clone()), - }), - ) - }) - .unzip(); - - let calling_args = Punctuated::from_iter(arg_names); - - let variant_name = Expr::Path(ExprPath { - attrs: Vec::new(), - qself: None, - path: Path { - leading_colon: None, - segments: Punctuated::from_iter(vec![ - PathSegment { - ident: ident.clone(), - arguments: PathArguments::None, - }, - PathSegment { - ident: v.ident.clone(), - arguments: PathArguments::None, - }, - ]), - }, - }); - - let variant_expr = if calling_args.is_empty() { - variant_name - } else { - ExprCall { - attrs: Vec::new(), - func: Box::new(variant_name), - paren_token: Paren(Span::call_site()), - args: calling_args, + FnArg::Typed(PatType { + attrs: Vec::new(), + pat: Box::new(Pat::Ident(PatIdent { + attrs: Vec::new(), + by_ref: None, + mutability: None, + ident: id, + subpat: None, + })), + colon_token: Token![:](Span::call_site()), + ty: Box::new(f.ty.clone()), + }), + ) + }) + .unzip(); + + let calling_args = Punctuated::from_iter(arg_names); + let variant_name = Expr::Path(ExprPath { + attrs: Vec::new(), + qself: None, + path: variant_path, + }); + + let variant_expr = ExprCall { + attrs: Vec::new(), + func: Box::new(variant_name), + paren_token: Paren(Span::call_site()), + args: calling_args, + }; + + (Punctuated::from_iter(arg_types), variant_expr.into()) } - .into() - }; + Fields::Unit => { + // Unit variant: MyVariant + let variant_expr = Expr::Path(ExprPath { + attrs: Vec::new(), + qself: None, + path: variant_path, + }); - (Punctuated::from_iter(arg_types), variant_expr) + (Punctuated::new(), variant_expr) + } + } }) .unzip(); From 3be65754a61666ce3c24b957a42b5b38fc9bbfb4 Mon Sep 17 00:00:00 2001 From: Patrick LaFontaine <32135464+Pat-Lafon@users.noreply.github.com> Date: Mon, 12 Jan 2026 11:53:23 -0500 Subject: [PATCH 14/23] clippy --- hashconsing/src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/hashconsing/src/lib.rs b/hashconsing/src/lib.rs index b125b95..e07ef09 100644 --- a/hashconsing/src/lib.rs +++ b/hashconsing/src/lib.rs @@ -248,8 +248,7 @@ mod test; /// - `$capa:expr` initial capacity when creating the consign ; /// - `$hash_builder:expr` optional hash builder, an /// implementation of [`std::hash::BuildHasher`] ; -/// - `$typ:typ,` type being hashconsed (the underlying type, not the -/// hashconsed one) ; +/// - `$typ:typ,` type being hashconsed (the underlying type, not the hashconsed one) ; #[macro_export] macro_rules! consign { ( From 5358127c1e37522936847f3bb9086490acd23602 Mon Sep 17 00:00:00 2001 From: Patrick LaFontaine <32135464+Pat-Lafon@users.noreply.github.com> Date: Fri, 3 Apr 2026 12:39:51 -0400 Subject: [PATCH 15/23] Restructure back a little bit --- Cargo.toml | 51 ++++++++++++++++++- hashconsing/Cargo.toml | 49 ------------------ hashconsing_derive/Cargo.toml | 2 +- {hashconsing/src => src}/coll.rs | 0 {hashconsing/src => src}/hash_coll.rs | 0 {hashconsing/src => src}/hash_coll/hashers.rs | 0 {hashconsing/src => src}/lib.rs | 0 {hashconsing/src => src}/test.rs | 0 {hashconsing/src => src}/test/basic.rs | 0 {hashconsing/src => src}/test/collect.rs | 0 {hashconsing/tests => tests}/send_sync.rs | 0 11 files changed, 51 insertions(+), 51 deletions(-) delete mode 100644 hashconsing/Cargo.toml rename {hashconsing/src => src}/coll.rs (100%) rename {hashconsing/src => src}/hash_coll.rs (100%) rename {hashconsing/src => src}/hash_coll/hashers.rs (100%) rename {hashconsing/src => src}/lib.rs (100%) rename {hashconsing/src => src}/test.rs (100%) rename {hashconsing/src => src}/test/basic.rs (100%) rename {hashconsing/src => src}/test/collect.rs (100%) rename {hashconsing/tests => tests}/send_sync.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index c56bdc6..46974c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,56 @@ [workspace] members = [ - "hashconsing", "hashconsing_derive", ] resolver = "2" + +[package] +name = "hashconsing" +version = "1.7.0" +authors = [ + "Adrien Champion ", + "Leni Aniva ", +] +description = "A hash consing library." +documentation = "https://docs.rs/hashconsing" +homepage = "https://github.com/AdrienChampion/hashconsing" +repository = "https://github.com/AdrienChampion/hashconsing" +readme = "README.md" +categories = [ + "caching", + "compression", + "concurrency", + "data-structures", + "memory-management", +] +keywords = ["hashconsing", "hash", "consing", "sharing", "caching"] +license = "MIT/Apache-2.0" +edition = "2021" +rust-version = "1.60" + +[package.metadata.docs.rs] +features = ["unstable_docrs"] + +[features] +with_ahash = ["ahash"] +unstable_docrs = ["with_ahash"] +weak-table = ["dep:weak-table"] +derive = ["hashconsing-derive"] + +[dependencies] +lazy_static = "1.*" +hashconsing-derive = { version = "0.1.0", optional = true, path = "hashconsing_derive" } + +[dependencies.ahash] +version = "^0.8.3" +optional = true + +[dependencies.weak-table] +version = "^0.3.0" +optional = true + +[dev-dependencies] +crossbeam-utils = "^0.8" +rayon = "^1.5" +rand = "0.8" diff --git a/hashconsing/Cargo.toml b/hashconsing/Cargo.toml deleted file mode 100644 index b22bdee..0000000 --- a/hashconsing/Cargo.toml +++ /dev/null @@ -1,49 +0,0 @@ -[package] -name = "hashconsing" -version = "1.7.0" -authors = [ - "Adrien Champion ", - "Leni Aniva ", -] -description = "A hash consing library." -documentation = "https://docs.rs/hashconsing" -homepage = "https://github.com/AdrienChampion/hashconsing" -repository = "https://github.com/AdrienChampion/hashconsing" -readme = "README.md" -categories = [ - "caching", - "compression", - "concurrency", - "data-structures", - "memory-management", -] -keywords = ["hashconsing", "hash", "consing", "sharing", "caching"] -license = "MIT/Apache-2.0" -edition = "2021" -rust-version = "1.60" - -[package.metadata.docs.rs] -features = ["unstable_docrs"] - -[features] -with_ahash = ["ahash"] -unstable_docrs = ["with_ahash"] -weak-table = ["dep:weak-table"] -derive = ["hashconsing-derive"] - -[dependencies] -lazy_static = "1.*" -hashconsing-derive = { version = "0.1.0", optional = true, path = "../hashconsing_derive" } - -[dependencies.ahash] -version = "^0.8.3" -optional = true - -[dependencies.weak-table] -version = "^0.3.0" -optional = true - -[dev-dependencies] -crossbeam-utils = "^0.8" -rayon = "^1.5" -rand = "0.8" \ No newline at end of file diff --git a/hashconsing_derive/Cargo.toml b/hashconsing_derive/Cargo.toml index 2ca579b..2835e3b 100644 --- a/hashconsing_derive/Cargo.toml +++ b/hashconsing_derive/Cargo.toml @@ -17,4 +17,4 @@ syn = { version = "2.0", features = ["full"] } # For doc tests [dev-dependencies] -hashconsing = {path = "../hashconsing", features = ["derive"]} +hashconsing = {path = "..", features = ["derive"]} diff --git a/hashconsing/src/coll.rs b/src/coll.rs similarity index 100% rename from hashconsing/src/coll.rs rename to src/coll.rs diff --git a/hashconsing/src/hash_coll.rs b/src/hash_coll.rs similarity index 100% rename from hashconsing/src/hash_coll.rs rename to src/hash_coll.rs diff --git a/hashconsing/src/hash_coll/hashers.rs b/src/hash_coll/hashers.rs similarity index 100% rename from hashconsing/src/hash_coll/hashers.rs rename to src/hash_coll/hashers.rs diff --git a/hashconsing/src/lib.rs b/src/lib.rs similarity index 100% rename from hashconsing/src/lib.rs rename to src/lib.rs diff --git a/hashconsing/src/test.rs b/src/test.rs similarity index 100% rename from hashconsing/src/test.rs rename to src/test.rs diff --git a/hashconsing/src/test/basic.rs b/src/test/basic.rs similarity index 100% rename from hashconsing/src/test/basic.rs rename to src/test/basic.rs diff --git a/hashconsing/src/test/collect.rs b/src/test/collect.rs similarity index 100% rename from hashconsing/src/test/collect.rs rename to src/test/collect.rs diff --git a/hashconsing/tests/send_sync.rs b/tests/send_sync.rs similarity index 100% rename from hashconsing/tests/send_sync.rs rename to tests/send_sync.rs From 314a1f9bf1319456295dc3ee2105032684d3304f Mon Sep 17 00:00:00 2001 From: Patrick LaFontaine <32135464+Pat-Lafon@users.noreply.github.com> Date: Fri, 3 Apr 2026 12:41:39 -0400 Subject: [PATCH 16/23] Add workspace to ci --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aec0024..3c1c173 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,13 +23,13 @@ jobs: with: rustflags: "" - name: Build - run: cargo build --verbose + run: cargo build --workspace --verbose - name: Run tests - run: cargo test --verbose + run: cargo test --workspace --verbose - name: Build doc - run: cargo doc --verbose + run: cargo doc --workspace --verbose - name: Run clippy - run: cargo clippy --verbose + run: cargo clippy --workspace --verbose - name: Run rustfmt run: cargo fmt --verbose --check - name: Build release From 551e52f4d653cdbb9dec066e6d3afc9e8e35f595 Mon Sep 17 00:00:00 2001 From: Patrick LaFontaine <32135464+Pat-Lafon@users.noreply.github.com> Date: Fri, 3 Apr 2026 12:55:27 -0400 Subject: [PATCH 17/23] Share some fields between cargo.toml's --- Cargo.toml | 10 +++++++--- hashconsing_derive/Cargo.toml | 4 ++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 46974c1..cd7a9d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,9 +5,13 @@ members = [ resolver = "2" +[workspace.package] +version = "1.7.0" +edition = "2021" + [package] name = "hashconsing" -version = "1.7.0" +version.workspace = true authors = [ "Adrien Champion ", "Leni Aniva ", @@ -26,7 +30,7 @@ categories = [ ] keywords = ["hashconsing", "hash", "consing", "sharing", "caching"] license = "MIT/Apache-2.0" -edition = "2021" +edition.workspace = true rust-version = "1.60" [package.metadata.docs.rs] @@ -40,7 +44,7 @@ derive = ["hashconsing-derive"] [dependencies] lazy_static = "1.*" -hashconsing-derive = { version = "0.1.0", optional = true, path = "hashconsing_derive" } +hashconsing-derive = { version = "1.7.0", optional = true, path = "hashconsing_derive" } [dependencies.ahash] version = "^0.8.3" diff --git a/hashconsing_derive/Cargo.toml b/hashconsing_derive/Cargo.toml index 2835e3b..472e7ac 100644 --- a/hashconsing_derive/Cargo.toml +++ b/hashconsing_derive/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "hashconsing-derive" -version = "0.1.0" -edition = "2021" +version.workspace = true +edition.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From adf5f5c26efb59bcde1368f9b51e8b30b1dd39ae Mon Sep 17 00:00:00 2001 From: Patrick LaFontaine <32135464+Pat-Lafon@users.noreply.github.com> Date: Fri, 3 Apr 2026 12:57:04 -0400 Subject: [PATCH 18/23] Use all-features now that their is the derive feature --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3c1c173..b857e7c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,13 +23,13 @@ jobs: with: rustflags: "" - name: Build - run: cargo build --workspace --verbose + run: cargo build --workspace --all-features --verbose - name: Run tests - run: cargo test --workspace --verbose + run: cargo test --workspace --all-features --verbose - name: Build doc - run: cargo doc --workspace --verbose + run: cargo doc --workspace --all-features --verbose - name: Run clippy - run: cargo clippy --workspace --verbose + run: cargo clippy --workspace --all-features --verbose - name: Run rustfmt run: cargo fmt --verbose --check - name: Build release From a46ab8e274191a1efddf7ead85cdf2d718ddcdb4 Mon Sep 17 00:00:00 2001 From: Patrick LaFontaine <32135464+Pat-Lafon@users.noreply.github.com> Date: Fri, 3 Apr 2026 14:09:39 -0400 Subject: [PATCH 19/23] Add repr transparent --- hashconsing_derive/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/hashconsing_derive/src/lib.rs b/hashconsing_derive/src/lib.rs index 8d4ca61..3d90c2d 100644 --- a/hashconsing_derive/src/lib.rs +++ b/hashconsing_derive/src/lib.rs @@ -119,6 +119,7 @@ pub fn hcons(args: TokenStream, mut input: TokenStream) -> TokenStream { let hash_struct = quote! { #(#attrs)* #[automatically_derived] + #[repr(transparent)] #vis struct #struct_name(hashconsing::HConsed<#ident>); #[automatically_derived] From 2926b32600c91642b41fcdb28db1e4e134959369 Mon Sep 17 00:00:00 2001 From: Patrick LaFontaine <32135464+Pat-Lafon@users.noreply.github.com> Date: Fri, 3 Apr 2026 14:16:25 -0400 Subject: [PATCH 20/23] Allow factory capacity to be adjustable --- hashconsing_derive/src/lib.rs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/hashconsing_derive/src/lib.rs b/hashconsing_derive/src/lib.rs index 3d90c2d..5f47b23 100644 --- a/hashconsing_derive/src/lib.rs +++ b/hashconsing_derive/src/lib.rs @@ -53,6 +53,24 @@ //! let unit = Type::Unit(); //! assert!(!unit.is_named()); //! ``` +//! +//! Custom factory capacity: +//! ```rust +//! use hashconsing::hcons; +//! +//! #[hcons(name = "Expr", capacity = 1000)] +//! #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)] +//! pub enum ActualExpr { +//! Lit(i64), +//! Add(Expr, Expr), +//! } +//! +//! let lit = Expr::Lit(42); +//! let sum = Expr::Add(lit.clone(), lit); +//! +//! // Note capacity is imprecise and allowed to allocate more space than expected +//! assert!(Expr_FACTORY.read().unwrap().capacity() >= 1000); +//! ``` use darling::{ast::NestedMeta, util::Flag, Error, FromMeta, Result}; use proc_macro::{self, TokenStream}; @@ -71,6 +89,8 @@ struct MacroArgs { name: String, no_factory: Flag, no_constructors: Flag, + #[darling(default)] + capacity: Option, } impl MacroArgs { @@ -131,9 +151,10 @@ pub fn hcons(args: TokenStream, mut input: TokenStream) -> TokenStream { } }; + let capacity = args.capacity.unwrap_or(50); let hash_factory = quote! { hashconsing::consign! { - let #factory_name = consign(50) for #ident ; + let #factory_name = consign(#capacity) for #ident ; } }; From e79c587527f848898c8e7f88256ab4776d9a4fde Mon Sep 17 00:00:00 2001 From: Patrick LaFontaine <32135464+Pat-Lafon@users.noreply.github.com> Date: Fri, 3 Apr 2026 14:25:52 -0400 Subject: [PATCH 21/23] Hide the assertion check from the documentation --- hashconsing_derive/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hashconsing_derive/src/lib.rs b/hashconsing_derive/src/lib.rs index 5f47b23..9076d42 100644 --- a/hashconsing_derive/src/lib.rs +++ b/hashconsing_derive/src/lib.rs @@ -68,8 +68,8 @@ //! let lit = Expr::Lit(42); //! let sum = Expr::Add(lit.clone(), lit); //! -//! // Note capacity is imprecise and allowed to allocate more space than expected -//! assert!(Expr_FACTORY.read().unwrap().capacity() >= 1000); +//! # // Note capacity is imprecise and allowed to allocate more space than expected +//! # assert!(Expr_FACTORY.read().unwrap().capacity() >= 1000); //! ``` use darling::{ast::NestedMeta, util::Flag, Error, FromMeta, Result}; From ee7b307897d3f53dcdebbd2f3cbc18bda79030d6 Mon Sep 17 00:00:00 2001 From: Patrick LaFontaine <32135464+Pat-Lafon@users.noreply.github.com> Date: Fri, 3 Apr 2026 14:26:07 -0400 Subject: [PATCH 22/23] compile time assert the traits are there --- hashconsing_derive/src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/hashconsing_derive/src/lib.rs b/hashconsing_derive/src/lib.rs index 9076d42..144eb0a 100644 --- a/hashconsing_derive/src/lib.rs +++ b/hashconsing_derive/src/lib.rs @@ -52,6 +52,10 @@ //! // Unit variant //! let unit = Type::Unit(); //! assert!(!unit.is_named()); +//! +//! # // Verify derived traits are inherited by the generated wrapper type +//! # fn assert_traits() {} +//! # assert_traits::(); //! ``` //! //! Custom factory capacity: From cc3c138579809eecbcee8c1f11947a66a6f04f37 Mon Sep 17 00:00:00 2001 From: Patrick LaFontaine <32135464+Pat-Lafon@users.noreply.github.com> Date: Sat, 4 Apr 2026 09:57:00 -0400 Subject: [PATCH 23/23] Enforce snack casing on function names --- hashconsing_derive/Cargo.toml | 1 + hashconsing_derive/src/lib.rs | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/hashconsing_derive/Cargo.toml b/hashconsing_derive/Cargo.toml index 472e7ac..d49d3bd 100644 --- a/hashconsing_derive/Cargo.toml +++ b/hashconsing_derive/Cargo.toml @@ -9,6 +9,7 @@ edition.workspace = true proc-macro = true [dependencies] +convert_case = "0.11" darling = "0.20" proc-macro-error = "1.0" proc-macro2 = "1.0" diff --git a/hashconsing_derive/src/lib.rs b/hashconsing_derive/src/lib.rs index 144eb0a..70e1f79 100644 --- a/hashconsing_derive/src/lib.rs +++ b/hashconsing_derive/src/lib.rs @@ -39,18 +39,18 @@ //! } //! } //! -//! let named_type = Type::Named("int".to_string()); +//! let named_type = Type::named("int".to_string()); //! // Dereferences to the underlying type with access to methods //! assert!(named_type.is_named()); -//! let tuple = Type::Tuple(vec![named_type.clone()]); +//! let tuple = Type::tuple(vec![named_type.clone()]); //! assert!(!tuple.is_named()); //! //! // Struct-style variant with named fields -//! let record = Type::Record(vec![("x".to_string(), named_type)], false); +//! let record = Type::record(vec![("x".to_string(), named_type)], false); //! assert!(record.is_record()); //! //! // Unit variant -//! let unit = Type::Unit(); +//! let unit = Type::unit(); //! assert!(!unit.is_named()); //! //! # // Verify derived traits are inherited by the generated wrapper type @@ -69,13 +69,14 @@ //! Add(Expr, Expr), //! } //! -//! let lit = Expr::Lit(42); -//! let sum = Expr::Add(lit.clone(), lit); +//! let lit = Expr::lit(42); +//! let sum = Expr::add(lit.clone(), lit); //! //! # // Note capacity is imprecise and allowed to allocate more space than expected //! # assert!(Expr_FACTORY.read().unwrap().capacity() >= 1000); //! ``` +use convert_case::{Case, Casing}; use darling::{ast::NestedMeta, util::Flag, Error, FromMeta, Result}; use proc_macro::{self, TokenStream}; use proc_macro2::Span; @@ -164,7 +165,9 @@ pub fn hcons(args: TokenStream, mut input: TokenStream) -> TokenStream { let hash_impl = match data { Data::Enum(DataEnum { variants, .. }) => { - let variant_names = variants.iter().map(|v| &v.ident); + let variant_names = variants + .iter() + .map(|v| format_ident!("{}", v.ident.to_string().to_case(Case::Snake))); let (variant_field_function_args, variant_field_calling_args): ( Vec>, Vec, @@ -314,7 +317,6 @@ pub fn hcons(args: TokenStream, mut input: TokenStream) -> TokenStream { quote! { #[automatically_derived] - #[allow(non_snake_case)] impl #struct_name { #(pub fn #variant_names(#variant_field_function_args) -> Self { use hashconsing::HashConsign;