From 309ab5b6e3ca57cc661c3417ec5e650780d02b2c 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 d66f6bc..809f795 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ *.lock # Ignore target directory. -/target +**/target # Ignore crappy mac files. .DS_Store diff --git a/Cargo.toml b/Cargo.toml index 739885b..2e46201 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,9 +24,11 @@ features = ["unstable_docrs"] [features] with_ahash = ["ahash"] unstable_docrs = ["with_ahash"] +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 c00789c..989387f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -226,6 +226,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 7082647ec9e7812d7573dfe5a3ea7710c899b958 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 b0a6f5f6b68b129007335fc40e5b7caa0b280eee 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 | 44 ++----------------- hashconsing/Cargo.toml | 41 +++++++++++++++++ {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 {tests => hashconsing/tests}/try_build.rs | 0 .../tests}/try_build/issue_1.rs | 0 .../tests}/try_build/issue_1.stderr | 0 .../Cargo.toml | 0 .../src/lib.rs | 0 15 files changed, 45 insertions(+), 40 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 {tests => hashconsing/tests}/try_build.rs (100%) rename {tests => hashconsing/tests}/try_build/issue_1.rs (100%) rename {tests => hashconsing/tests}/try_build/issue_1.stderr (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 2e46201..0229b75 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,41 +1,5 @@ -[package] -name = "hashconsing" -version = "1.6.0" -authors = ["Adrien Champion "] -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", +[workspace] +members = [ + "hashconsing", + "hashconsing_derive", ] -keywords = ["hashconsing", "hash", "consing", "sharing", "caching"] -license = "MIT/Apache-2.0" -edition = "2021" - -[package.metadata.docs.rs] -features = ["unstable_docrs"] - -[features] -with_ahash = ["ahash"] -unstable_docrs = ["with_ahash"] -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 - -[dev-dependencies] -crossbeam-utils = "^0.8" -trybuild = "^1.0" -rayon = "^1.5" -rand = "0.8" diff --git a/hashconsing/Cargo.toml b/hashconsing/Cargo.toml new file mode 100644 index 0000000..0c77436 --- /dev/null +++ b/hashconsing/Cargo.toml @@ -0,0 +1,41 @@ +[package] +name = "hashconsing" +version = "1.6.0" +authors = ["Adrien Champion "] +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" + +[package.metadata.docs.rs] +features = ["unstable_docrs"] + +[features] +with_ahash = ["ahash"] +unstable_docrs = ["with_ahash"] +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 + +[dev-dependencies] +crossbeam-utils = "^0.8" +trybuild = "^1.0" +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/tests/try_build.rs b/hashconsing/tests/try_build.rs similarity index 100% rename from tests/try_build.rs rename to hashconsing/tests/try_build.rs diff --git a/tests/try_build/issue_1.rs b/hashconsing/tests/try_build/issue_1.rs similarity index 100% rename from tests/try_build/issue_1.rs rename to hashconsing/tests/try_build/issue_1.rs diff --git a/tests/try_build/issue_1.stderr b/hashconsing/tests/try_build/issue_1.stderr similarity index 100% rename from tests/try_build/issue_1.stderr rename to hashconsing/tests/try_build/issue_1.stderr 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 b18912b7e3318b26fcd089dd4dd5774ee1ab13cb 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 62a784a1c62258e4192e7043f25761916f0be094 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 dd6d01e1fe8888a867a9785c1b2d23fb70396a0c 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 d8ead10ed80d772f53a2a19667d382edbe51e18d 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 6466304752a7a93600e9dfc41b5961507416ca64 Mon Sep 17 00:00:00 2001 From: Patrick LaFontaine <32135464+Pat-Lafon@users.noreply.github.com> Date: Wed, 22 Mar 2023 17:05:06 -0400 Subject: [PATCH 08/23] fix test version number and update error message --- hashconsing/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hashconsing/Cargo.toml b/hashconsing/Cargo.toml index 0c77436..a5ebd2a 100644 --- a/hashconsing/Cargo.toml +++ b/hashconsing/Cargo.toml @@ -35,7 +35,7 @@ version = "^0.8.3" optional = true [dev-dependencies] -crossbeam-utils = "^0.8" +crossbeam-utils = "=0.8.15" trybuild = "^1.0" rayon = "^1.5" rand = "0.8" \ No newline at end of file From 107a4ede85e1356f072b96dce2a4d9f6fbd7eb74 Mon Sep 17 00:00:00 2001 From: Patrick LaFontaine <32135464+Pat-Lafon@users.noreply.github.com> Date: Wed, 13 Sep 2023 15:02:30 -0400 Subject: [PATCH 09/23] Resolve: warning: some crates are on edition 2021 which defaults to `resolver = "2"`, but virtual workspaces default to `resolver = "1"` --- Cargo.toml | 2 ++ hashconsing/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 0229b75..3bc2daf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,3 +3,5 @@ members = [ "hashconsing", "hashconsing_derive", ] + +resolver = "2" \ No newline at end of file diff --git a/hashconsing/Cargo.toml b/hashconsing/Cargo.toml index a5ebd2a..394ad64 100644 --- a/hashconsing/Cargo.toml +++ b/hashconsing/Cargo.toml @@ -28,7 +28,7 @@ derive = ["hashconsing-derive"] [dependencies] lazy_static = "1.*" -hashconsing-derive = { version = "0.1.0", optional = true, path = "hashconsing-derive" } +hashconsing-derive = { version = "0.1.0", optional = true, path = "../hashconsing_derive" } [dependencies.ahash] version = "^0.8.3" From a03f60472d18512b05b4ddab02d8871c81be1c46 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 10/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 6f85b529384445c04722655ddcaf96c7b34a3be2 Mon Sep 17 00:00:00 2001 From: Patrick LaFontaine <32135464+Pat-Lafon@users.noreply.github.com> Date: Wed, 27 Sep 2023 17:18:53 -0400 Subject: [PATCH 11/23] Update error output with some spacing apparently --- hashconsing/tests/try_build/issue_1.stderr | 72 +++++++++++----------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/hashconsing/tests/try_build/issue_1.stderr b/hashconsing/tests/try_build/issue_1.stderr index 379a339..7e75327 100644 --- a/hashconsing/tests/try_build/issue_1.stderr +++ b/hashconsing/tests/try_build/issue_1.stderr @@ -1,42 +1,42 @@ error[E0277]: `Cell>` cannot be shared between threads safely - --> tests/try_build/issue_1.rs:37:17 - | -37 | s.spawn(move |_| { - | ___________-----_^ - | | | - | | required by a bound introduced by this call -38 | | let smuggled_cell = &hcons_cell_ref.get().cell; -39 | | -40 | | loop { -... | -44 | | } -45 | | }); - | |_________^ `Cell>` cannot be shared between threads safely - | - = help: within `&HashableCell>`, the trait `Sync` is not implemented for `Cell>` - = note: if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock` + --> tests/try_build/issue_1.rs:37:17 + | +37 | s.spawn(move |_| { + | ___________-----_^ + | | | + | | required by a bound introduced by this call +38 | | let smuggled_cell = &hcons_cell_ref.get().cell; +39 | | +40 | | loop { +... | +44 | | } +45 | | }); + | |_________^ `Cell>` cannot be shared between threads safely + | + = help: within `&HashableCell>`, the trait `Sync` is not implemented for `Cell>` + = note: if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock` note: required because it appears within the type `HashableCell>` - --> tests/try_build/issue_1.rs:18:8 - | -18 | struct HashableCell { - | ^^^^^^^^^^^^ - = note: required because it appears within the type `&HashableCell>` - = note: required for `Arc<&HashableCell>>` to implement `Send` + --> tests/try_build/issue_1.rs:18:8 + | +18 | struct HashableCell { + | ^^^^^^^^^^^^ + = note: required because it appears within the type `&HashableCell>` + = note: required for `Arc<&HashableCell>>` to implement `Send` note: required because it appears within the type `HConsed<&HashableCell>>` - --> src/lib.rs - | - | pub struct HConsed { - | ^^^^^^^ + --> src/lib.rs + | + | pub struct HConsed { + | ^^^^^^^ note: required because it's used within this closure - --> tests/try_build/issue_1.rs:37:17 - | -37 | s.spawn(move |_| { - | ^^^^^^^^ + --> tests/try_build/issue_1.rs:37:17 + | +37 | s.spawn(move |_| { + | ^^^^^^^^ note: required by a bound in `crossbeam_utils::thread::Scope::<'env>::spawn` - --> $CARGO/crossbeam-utils-0.8.16/src/thread.rs - | - | pub fn spawn<'scope, F, T>(&'scope self, f: F) -> ScopedJoinHandle<'scope, T> - | ----- required by a bound in this associated function + --> /Users/patricklafontaine/.cargo/registry/src/index.crates.io-6f17d22bba15001f/crossbeam-utils-0.8.15/src/thread.rs:251:12 + | +248 | pub fn spawn<'scope, F, T>(&'scope self, f: F) -> ScopedJoinHandle<'scope, T> + | ----- required by a bound in this associated function ... - | F: Send + 'env, - | ^^^^ required by this bound in `Scope::<'env>::spawn` +251 | F: Send + 'env, + | ^^^^ required by this bound in `Scope::<'env>::spawn` From 127fa7bde87733f76950477dd266e52ffe604fc4 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 12/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 4ec8fc154753aee6df666b45bf49c08bd75a2be3 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 13/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 6425000bb8c018b1989a9d6ac87e5ad422bd8a58 Mon Sep 17 00:00:00 2001 From: Patrick LaFontaine <32135464+Pat-Lafon@users.noreply.github.com> Date: Fri, 29 Sep 2023 14:27:35 -0400 Subject: [PATCH 14/23] trim whitespace on issue_1 err --- hashconsing/tests/try_build/issue_1.stderr | 72 +++++++++++----------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/hashconsing/tests/try_build/issue_1.stderr b/hashconsing/tests/try_build/issue_1.stderr index 7e75327..42119a9 100644 --- a/hashconsing/tests/try_build/issue_1.stderr +++ b/hashconsing/tests/try_build/issue_1.stderr @@ -1,42 +1,42 @@ error[E0277]: `Cell>` cannot be shared between threads safely - --> tests/try_build/issue_1.rs:37:17 - | -37 | s.spawn(move |_| { - | ___________-----_^ - | | | - | | required by a bound introduced by this call -38 | | let smuggled_cell = &hcons_cell_ref.get().cell; -39 | | -40 | | loop { -... | -44 | | } -45 | | }); - | |_________^ `Cell>` cannot be shared between threads safely - | - = help: within `&HashableCell>`, the trait `Sync` is not implemented for `Cell>` - = note: if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock` + --> tests/try_build/issue_1.rs:37:17 + | +37 | s.spawn(move |_| { + | ___________-----_^ + | | | + | | required by a bound introduced by this call +38 | | let smuggled_cell = &hcons_cell_ref.get().cell; +39 | | +40 | | loop { +... | +44 | | } +45 | | }); + | |_________^ `Cell>` cannot be shared between threads safely + | + = help: within `&HashableCell>`, the trait `Sync` is not implemented for `Cell>` + = note: if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock` note: required because it appears within the type `HashableCell>` - --> tests/try_build/issue_1.rs:18:8 - | -18 | struct HashableCell { - | ^^^^^^^^^^^^ - = note: required because it appears within the type `&HashableCell>` - = note: required for `Arc<&HashableCell>>` to implement `Send` + --> tests/try_build/issue_1.rs:18:8 + | +18 | struct HashableCell { + | ^^^^^^^^^^^^ + = note: required because it appears within the type `&HashableCell>` + = note: required for `Arc<&HashableCell>>` to implement `Send` note: required because it appears within the type `HConsed<&HashableCell>>` - --> src/lib.rs - | - | pub struct HConsed { - | ^^^^^^^ + --> src/lib.rs + | + | pub struct HConsed { + | ^^^^^^^ note: required because it's used within this closure - --> tests/try_build/issue_1.rs:37:17 - | -37 | s.spawn(move |_| { - | ^^^^^^^^ + --> tests/try_build/issue_1.rs:37:17 + | +37 | s.spawn(move |_| { + | ^^^^^^^^ note: required by a bound in `crossbeam_utils::thread::Scope::<'env>::spawn` - --> /Users/patricklafontaine/.cargo/registry/src/index.crates.io-6f17d22bba15001f/crossbeam-utils-0.8.15/src/thread.rs:251:12 - | -248 | pub fn spawn<'scope, F, T>(&'scope self, f: F) -> ScopedJoinHandle<'scope, T> - | ----- required by a bound in this associated function + --> $CARGO/crossbeam-utils-0.8.15/src/thread.rs + | + | pub fn spawn<'scope, F, T>(&'scope self, f: F) -> ScopedJoinHandle<'scope, T> + | ----- required by a bound in this associated function ... -251 | F: Send + 'env, - | ^^^^ required by this bound in `Scope::<'env>::spawn` + | F: Send + 'env, + | ^^^^ required by this bound in `Scope::<'env>::spawn` From 0b200dba862306dde414278fa3478800e2eea75e 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 15/23] '_ lifetime to iter() return types --- hashconsing/src/coll.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hashconsing/src/coll.rs b/hashconsing/src/coll.rs index 85692ef..d32debc 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(&self) -> ::std::collections::hash_set::Iter> { + pub fn iter(&self) -> ::std::collections::hash_set::Iter<'_, HConsed> { self.set.iter() } } @@ -322,12 +322,12 @@ where } /// An iterator visiting all elements. #[inline] - pub fn iter(&self) -> ::std::collections::hash_map::Iter, 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(&mut self) -> ::std::collections::hash_map::IterMut, V> { + pub fn iter_mut(&mut self) -> ::std::collections::hash_map::IterMut<'_, HConsed, V> { self.map.iter_mut() } } From f8939773b89c86bb95578ed998f85c39e8cf5754 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 16/23] part 2 --- hashconsing/src/hash_coll.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hashconsing/src/hash_coll.rs b/hashconsing/src/hash_coll.rs index 37d2623..c344dc9 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(&self) -> ::std::collections::hash_set::Iter> { + pub fn iter(&self) -> ::std::collections::hash_set::Iter<'_, HConsed> { self.set.iter() } } @@ -520,12 +520,12 @@ where { /// An iterator visiting all elements. #[inline] - pub fn iter(&self) -> ::std::collections::hash_map::Iter, 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(&mut self) -> ::std::collections::hash_map::IterMut, V> { + pub fn iter_mut(&mut self) -> ::std::collections::hash_map::IterMut<'_, HConsed, V> { self.map.iter_mut() } } From d1aa8f2b4bc978660bcbdb544137677ed965cfe9 Mon Sep 17 00:00:00 2001 From: Patrick LaFontaine <32135464+Pat-Lafon@users.noreply.github.com> Date: Mon, 12 Jan 2026 11:40:48 -0500 Subject: [PATCH 17/23] debug_assertions instead of debug --- hashconsing/src/coll.rs | 4 ++-- hashconsing/src/hash_coll.rs | 4 ++-- hashconsing/src/hash_coll/hashers.rs | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/hashconsing/src/coll.rs b/hashconsing/src/coll.rs index d32debc..c88c0ce 100644 --- a/hashconsing/src/coll.rs +++ b/hashconsing/src/coll.rs @@ -437,7 +437,7 @@ mod hash { impl HashU64 { /// Checks that a slice of bytes has the length of a `usize`. Only active /// in debug. - #[cfg(debug)] + #[cfg(debug_assertions)] #[inline(always)] fn test_bytes(bytes: &[u8]) { if bytes.len() != 8 { @@ -451,7 +451,7 @@ mod hash { } /// Checks that a slice of bytes has the length of a `usize`. Only active /// in debug. - #[cfg(not(debug))] + #[cfg(not(debug_assertions))] #[inline(always)] fn test_bytes(_: &[u8]) {} } diff --git a/hashconsing/src/hash_coll.rs b/hashconsing/src/hash_coll.rs index c344dc9..1e29c0b 100644 --- a/hashconsing/src/hash_coll.rs +++ b/hashconsing/src/hash_coll.rs @@ -643,7 +643,7 @@ mod hash { impl HashU64 { /// Checks that a slice of bytes has the length of a `usize`. Only active /// in debug. - #[cfg(debug)] + #[cfg(debug_assertions)] #[inline(always)] fn test_bytes(bytes: &[u8]) { if bytes.len() != 8 { @@ -657,7 +657,7 @@ mod hash { } /// Checks that a slice of bytes has the length of a `usize`. Only active /// in debug. - #[cfg(not(debug))] + #[cfg(not(debug_assertions))] #[inline(always)] fn test_bytes(_: &[u8]) {} } diff --git a/hashconsing/src/hash_coll/hashers.rs b/hashconsing/src/hash_coll/hashers.rs index c7f5e3d..bc10f49 100644 --- a/hashconsing/src/hash_coll/hashers.rs +++ b/hashconsing/src/hash_coll/hashers.rs @@ -50,7 +50,7 @@ pub mod p_hash { impl Hasher { /// Checks that a slice of bytes has the length of a `usize`. Only active /// in debug. - #[cfg(debug)] + #[cfg(debug_assertions)] #[inline(always)] fn test_bytes(bytes: &[u8]) { if bytes.len() != 8 { @@ -64,7 +64,7 @@ pub mod p_hash { } /// Checks that a slice of bytes has the length of a `usize`. Only active /// in debug. - #[cfg(not(debug))] + #[cfg(not(debug_assertions))] #[inline(always)] fn test_bytes(_: &[u8]) {} } @@ -112,7 +112,7 @@ pub mod id_hash { impl Hasher { /// Checks that a slice of bytes has the length of a `usize`. Only active /// in debug. - #[cfg(debug)] + #[cfg(debug_assertions)] #[inline(always)] fn test_bytes(bytes: &[u8]) { if bytes.len() != 8 { @@ -126,7 +126,7 @@ pub mod id_hash { } /// Checks that a slice of bytes has the length of a `usize`. Only active /// in debug. - #[cfg(not(debug))] + #[cfg(not(debug_assertions))] #[inline(always)] fn test_bytes(_: &[u8]) {} } From a0035b0cf318cbfb7345cfa23fb12b6b1ef35148 Mon Sep 17 00:00:00 2001 From: Patrick LaFontaine <32135464+Pat-Lafon@users.noreply.github.com> Date: Mon, 12 Jan 2026 11:41:23 -0500 Subject: [PATCH 18/23] from_ne_bytes instead of transmute --- hashconsing/src/coll.rs | 2 +- hashconsing/src/hash_coll.rs | 2 +- hashconsing/src/hash_coll/hashers.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/hashconsing/src/coll.rs b/hashconsing/src/coll.rs index c88c0ce..d656a6d 100644 --- a/hashconsing/src/coll.rs +++ b/hashconsing/src/coll.rs @@ -457,7 +457,7 @@ mod hash { } impl Hasher for HashU64 { fn finish(&self) -> u64 { - unsafe { ::std::mem::transmute(self.buf) } + u64::from_ne_bytes(self.buf) } fn write(&mut self, bytes: &[u8]) { Self::test_bytes(bytes); diff --git a/hashconsing/src/hash_coll.rs b/hashconsing/src/hash_coll.rs index 1e29c0b..e457b3e 100644 --- a/hashconsing/src/hash_coll.rs +++ b/hashconsing/src/hash_coll.rs @@ -663,7 +663,7 @@ mod hash { } impl Hasher for HashU64 { fn finish(&self) -> u64 { - let block: u64 = unsafe { ::std::mem::transmute(self.buf) }; + let block: u64 = u64::from_ne_bytes(self.buf); // Multiply by random 64-bit prime to distribute block.wrapping_mul(0xDA5DF7A7BD02F2C7u64) } diff --git a/hashconsing/src/hash_coll/hashers.rs b/hashconsing/src/hash_coll/hashers.rs index bc10f49..dc77af3 100644 --- a/hashconsing/src/hash_coll/hashers.rs +++ b/hashconsing/src/hash_coll/hashers.rs @@ -70,7 +70,7 @@ pub mod p_hash { } impl StdHasherExt for Hasher { fn finish(&self) -> u64 { - let block: u64 = unsafe { ::std::mem::transmute(self.buf) }; + let block: u64 = u64::from_ne_bytes(self.buf); // Multiply by random 64-bit prime to distribute block.wrapping_mul(0xDA5DF7A7BD02F2C7u64) } @@ -132,7 +132,7 @@ pub mod id_hash { } impl StdHasherExt for Hasher { fn finish(&self) -> u64 { - unsafe { ::std::mem::transmute(self.buf) } + u64::from_ne_bytes(self.buf) } fn write(&mut self, bytes: &[u8]) { Self::test_bytes(bytes); From 29858022e14e8be9ed15fde63d9b0de68da086e5 Mon Sep 17 00:00:00 2001 From: Patrick LaFontaine <32135464+Pat-Lafon@users.noreply.github.com> Date: Mon, 12 Jan 2026 11:49:40 -0500 Subject: [PATCH 19/23] whitespace issues --- hashconsing/tests/try_build/issue_1.stderr | 71 +++++++++++----------- 1 file changed, 35 insertions(+), 36 deletions(-) diff --git a/hashconsing/tests/try_build/issue_1.stderr b/hashconsing/tests/try_build/issue_1.stderr index 42119a9..79de934 100644 --- a/hashconsing/tests/try_build/issue_1.stderr +++ b/hashconsing/tests/try_build/issue_1.stderr @@ -1,42 +1,41 @@ error[E0277]: `Cell>` cannot be shared between threads safely - --> tests/try_build/issue_1.rs:37:17 - | -37 | s.spawn(move |_| { - | ___________-----_^ - | | | - | | required by a bound introduced by this call -38 | | let smuggled_cell = &hcons_cell_ref.get().cell; -39 | | -40 | | loop { -... | -44 | | } -45 | | }); - | |_________^ `Cell>` cannot be shared between threads safely - | - = help: within `&HashableCell>`, the trait `Sync` is not implemented for `Cell>` - = note: if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock` + --> tests/try_build/issue_1.rs:37:17 + | + 37 | s.spawn(move |_| { + | ___________-----_^ + | | | + | | required by a bound introduced by this call + 38 | | let smuggled_cell = &hcons_cell_ref.get().cell; + 39 | | + 40 | | loop { +... | + 45 | | }); + | |_________^ `Cell>` cannot be shared between threads safely + | + = help: within `&HashableCell>`, the trait `Sync` is not implemented for `Cell>` + = note: if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock` note: required because it appears within the type `HashableCell>` - --> tests/try_build/issue_1.rs:18:8 - | -18 | struct HashableCell { - | ^^^^^^^^^^^^ - = note: required because it appears within the type `&HashableCell>` - = note: required for `Arc<&HashableCell>>` to implement `Send` + --> tests/try_build/issue_1.rs:18:8 + | + 18 | struct HashableCell { + | ^^^^^^^^^^^^ + = note: required because it appears within the type `&HashableCell>` + = note: required for `Arc<&HashableCell>>` to implement `Send` note: required because it appears within the type `HConsed<&HashableCell>>` - --> src/lib.rs - | - | pub struct HConsed { - | ^^^^^^^ + --> src/lib.rs + | + | pub struct HConsed { + | ^^^^^^^ note: required because it's used within this closure - --> tests/try_build/issue_1.rs:37:17 - | -37 | s.spawn(move |_| { - | ^^^^^^^^ + --> tests/try_build/issue_1.rs:37:17 + | + 37 | s.spawn(move |_| { + | ^^^^^^^^ note: required by a bound in `crossbeam_utils::thread::Scope::<'env>::spawn` - --> $CARGO/crossbeam-utils-0.8.15/src/thread.rs - | - | pub fn spawn<'scope, F, T>(&'scope self, f: F) -> ScopedJoinHandle<'scope, T> - | ----- required by a bound in this associated function + --> $CARGO/crossbeam-utils-0.8.15/src/thread.rs + | + | pub fn spawn<'scope, F, T>(&'scope self, f: F) -> ScopedJoinHandle<'scope, T> + | ----- required by a bound in this associated function ... - | F: Send + 'env, - | ^^^^ required by this bound in `Scope::<'env>::spawn` + | F: Send + 'env, + | ^^^^ required by this bound in `Scope::<'env>::spawn` From 6a7d55eb30fabf9150ab300f89c161b1184afac9 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 20/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 3b6ad50ceed7382e6642f123c64b3d6deb4d2da6 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 21/23] clippy --- hashconsing/src/lib.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/hashconsing/src/lib.rs b/hashconsing/src/lib.rs index 989387f..96f8211 100644 --- a/hashconsing/src/lib.rs +++ b/hashconsing/src/lib.rs @@ -247,8 +247,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 { ( @@ -362,7 +361,7 @@ impl Eq for HConsed {} impl PartialOrd for HConsed { #[inline] fn partial_cmp(&self, other: &Self) -> Option { - self.uid.partial_cmp(&other.uid) + Some(self.cmp(other)) } } impl Ord for HConsed { @@ -449,7 +448,7 @@ impl Eq for WHConsed {} impl PartialOrd for WHConsed { #[inline] fn partial_cmp(&self, other: &Self) -> Option { - self.uid.partial_cmp(&other.uid) + Some(self.cmp(other)) } } impl Ord for WHConsed { @@ -628,7 +627,7 @@ pub trait HashConsign: Sized { /// Reserves capacity for at least `additional` more elements. fn reserve(self, additional: usize); } -impl<'a, T: Hash + Eq + Clone, S: BuildHasher> HashConsign for &'a mut HConsign { +impl HashConsign for &mut HConsign { fn mk_is_new(self, elm: T) -> (HConsed, bool) { // If the element is known and upgradable return it. if let Some(hconsed) = self.get(&elm) { @@ -701,7 +700,7 @@ macro_rules! get { }; } -impl<'a, T: Hash + Eq + Clone> HashConsign for &'a RwLock> { +impl HashConsign for &RwLock> { /// If the element is already in the consign, only read access will be /// requested. fn mk_is_new(self, elm: T) -> (HConsed, bool) { From bac0146cd3c7c0879bddd89bc6c1d94289d57aea Mon Sep 17 00:00:00 2001 From: Patrick LaFontaine <32135464+Pat-Lafon@users.noreply.github.com> Date: Mon, 12 Jan 2026 12:02:25 -0500 Subject: [PATCH 22/23] Fix test with non hardcoded version --- hashconsing/tests/try_build/issue_1.stderr | 70 +++++++++++----------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/hashconsing/tests/try_build/issue_1.stderr b/hashconsing/tests/try_build/issue_1.stderr index 79de934..30b1f93 100644 --- a/hashconsing/tests/try_build/issue_1.stderr +++ b/hashconsing/tests/try_build/issue_1.stderr @@ -1,41 +1,41 @@ error[E0277]: `Cell>` cannot be shared between threads safely - --> tests/try_build/issue_1.rs:37:17 - | - 37 | s.spawn(move |_| { - | ___________-----_^ - | | | - | | required by a bound introduced by this call - 38 | | let smuggled_cell = &hcons_cell_ref.get().cell; - 39 | | - 40 | | loop { -... | - 45 | | }); - | |_________^ `Cell>` cannot be shared between threads safely - | - = help: within `&HashableCell>`, the trait `Sync` is not implemented for `Cell>` - = note: if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock` + --> tests/try_build/issue_1.rs:37:17 + | +37 | s.spawn(move |_| { + | ___________-----_^ + | | | + | | required by a bound introduced by this call +38 | | let smuggled_cell = &hcons_cell_ref.get().cell; +39 | | +40 | | loop { +... | +45 | | }); + | |_________^ `Cell>` cannot be shared between threads safely + | + = help: within `&HashableCell>`, the trait `Sync` is not implemented for `Cell>` + = note: if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock` note: required because it appears within the type `HashableCell>` - --> tests/try_build/issue_1.rs:18:8 - | - 18 | struct HashableCell { - | ^^^^^^^^^^^^ - = note: required because it appears within the type `&HashableCell>` - = note: required for `Arc<&HashableCell>>` to implement `Send` + --> tests/try_build/issue_1.rs:18:8 + | +18 | struct HashableCell { + | ^^^^^^^^^^^^ + = note: required because it appears within the type `&HashableCell>` + = note: required for `Arc<&HashableCell>>` to implement `Send` note: required because it appears within the type `HConsed<&HashableCell>>` - --> src/lib.rs - | - | pub struct HConsed { - | ^^^^^^^ + --> src/lib.rs + | + | pub struct HConsed { + | ^^^^^^^ note: required because it's used within this closure - --> tests/try_build/issue_1.rs:37:17 - | - 37 | s.spawn(move |_| { - | ^^^^^^^^ + --> tests/try_build/issue_1.rs:37:17 + | +37 | s.spawn(move |_| { + | ^^^^^^^^ note: required by a bound in `crossbeam_utils::thread::Scope::<'env>::spawn` - --> $CARGO/crossbeam-utils-0.8.15/src/thread.rs - | - | pub fn spawn<'scope, F, T>(&'scope self, f: F) -> ScopedJoinHandle<'scope, T> - | ----- required by a bound in this associated function + --> $CARGO/crossbeam-utils-$VERSION/src/thread.rs + | + | pub fn spawn<'scope, F, T>(&'scope self, f: F) -> ScopedJoinHandle<'scope, T> + | ----- required by a bound in this associated function ... - | F: Send + 'env, - | ^^^^ required by this bound in `Scope::<'env>::spawn` + | F: Send + 'env, + | ^^^^ required by this bound in `Scope::<'env>::spawn` From 61f5462ff62704a978bc900d8a64cddceac5e45b Mon Sep 17 00:00:00 2001 From: Patrick LaFontaine <32135464+Pat-Lafon@users.noreply.github.com> Date: Mon, 12 Jan 2026 12:07:41 -0500 Subject: [PATCH 23/23] Cargo doc fixes --- hashconsing/src/hash_coll.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hashconsing/src/hash_coll.rs b/hashconsing/src/hash_coll.rs index e457b3e..a9b5fad 100644 --- a/hashconsing/src/hash_coll.rs +++ b/hashconsing/src/hash_coll.rs @@ -42,9 +42,9 @@ //! be the natural one, *e.g.* `HConSet`. //! //! However, since `Term` is really an alias for `HConsed`, then if we wanted to declare -//! `HConSet` as an alias for `HashSet` we would get `type HConSet = HashSet< HConsed -//! >` (omitting the custom hasher). That is, our sets would have type `HConSet`, which is -//! not very pretty. We could just define an alias though: `type TermSet = HConSet`, but it +//! `HConSet` as an alias for `HashSet` we would get `type ``HConSet`` = HashSet< ``HConsed`` +//! >` (omitting the custom hasher). That is, our sets would have type ``HConSet``, which is +//! not very pretty. We could just define an alias though: `type TermSet = `HConSet``, but it //! turns out it's better to wrap the actual set in a `struct` anyway. Mostly to be able to define //! `new` and `with_capacity` without relying on a trait (users would need to import) to do that. //!