Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion packages/iocraft-macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ proc-macro = true
proc-macro2 = "1.0.86"
quote = "1.0.37"
syn = { version = "2.0.77", features = ["full"] }
uuid = { version = "1.10.0", features = ["v4"] }

[dev-dependencies]
iocraft = { path = "../iocraft" }
Expand Down
16 changes: 12 additions & 4 deletions packages/iocraft-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::{quote, ToTokens};
use std::sync::atomic::{AtomicU64, Ordering};
use syn::{
braced, parenthesized,
parse::{Parse, ParseStream, Parser},
Expand All @@ -15,7 +16,6 @@ use syn::{
DeriveInput, Error, Expr, FieldValue, FnArg, GenericParam, Generics, Ident, ItemFn, ItemStruct,
Lifetime, Lit, Member, Pat, Result, Token, Type, TypePath, WhereClause, WherePredicate,
};
use uuid::Uuid;

enum ParsedElementChild {
Element(ParsedElement),
Expand Down Expand Up @@ -73,16 +73,24 @@ impl ToTokens for ParsedElement {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let ty = &self.ty;

let decl_key = Uuid::new_v4().as_u128();
// Use module_path!() combined with a deterministic counter to ensure
// keys are unique both within a compilation unit and across crates.
// module_path!() resolves to the caller's module path at compile time,
// providing cross-crate uniqueness. The counter provides within-crate
// uniqueness. This also ensures reproducible builds (no random UUIDs).
static COUNTER: AtomicU64 = AtomicU64::new(0);
let counter = COUNTER.fetch_add(1, Ordering::Relaxed);

let key = self
.props
.iter()
.find_map(|FieldValue { member, expr, .. }| match member {
Member::Named(ident) if ident == "key" => Some(quote!((#decl_key, #expr))),
Member::Named(ident) if ident == "key" => {
Some(quote!((module_path!(), #counter, #expr)))
}
_ => None,
})
.unwrap_or_else(|| quote!(#decl_key));
.unwrap_or_else(|| quote!((module_path!(), #counter)));

let prop_assignments = self
.props
Expand Down
20 changes: 20 additions & 0 deletions packages/iocraft-macros/tests/element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,3 +172,23 @@ fn generics() {
};
assert_eq!(vec![0], e.props.items);
}

#[test]
fn deterministic_keys() {
// Verify that element keys are deterministic across invocations.
// This is important for reproducible builds: the element! macro must
// produce the same output on every compilation.
fn make_elements() -> (Element<'static, MyComponent>, Element<'static, MyComponent>) {
let a = element!(MyComponent);
let b = element!(MyComponent);
(a, b)
}
let (a1, b1) = make_elements();
let (a2, b2) = make_elements();
assert_eq!(a1.key, a2.key, "same call site should produce the same key");
assert_eq!(b1.key, b2.key, "same call site should produce the same key");
assert_ne!(
a1.key, b1.key,
"different call sites should produce different keys"
);
}
Loading