1use std::fmt::{self, Display, Formatter};
4use std::hash::{Hash, Hasher};
5use std::num::NonZeroU64;
6use std::rc::Rc;
7
8use crate::html::ImplicitClone;
9
10fn hash_value<H: Hash + ?Sized>(value: &H) -> NonZeroU64 {
11 use std::hash::DefaultHasher;
12
13 let mut hasher = DefaultHasher::new();
14 value.hash(&mut hasher);
15 NonZeroU64::new(hasher.finish()).unwrap_or(NonZeroU64::MIN)
16}
17
18#[derive(Clone, ImplicitClone)]
34pub struct Key {
35 hash: NonZeroU64,
36 #[cfg(debug_assertions)]
37 original: Rc<str>,
38}
39
40impl Display for Key {
41 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
42 #[cfg(debug_assertions)]
43 {
44 self.original.fmt(f)
45 }
46 #[cfg(not(debug_assertions))]
47 {
48 write!(f, "#{}", self.hash)
49 }
50 }
51}
52
53impl fmt::Debug for Key {
54 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
55 #[cfg(debug_assertions)]
56 {
57 write!(f, "Key({:?})", self.original)
58 }
59 #[cfg(not(debug_assertions))]
60 {
61 write!(f, "Key(#{})", self.hash)
62 }
63 }
64}
65
66impl PartialEq for Key {
67 fn eq(&self, other: &Self) -> bool {
68 self.hash == other.hash
69 }
70}
71
72impl Eq for Key {}
73
74impl PartialOrd for Key {
75 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
76 Some(self.cmp(other))
77 }
78}
79
80impl Ord for Key {
81 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
82 self.hash.cmp(&other.hash)
83 }
84}
85
86impl Hash for Key {
87 fn hash<H: Hasher>(&self, state: &mut H) {
88 self.hash.hash(state);
89 }
90}
91
92impl From<Rc<str>> for Key {
93 fn from(key: Rc<str>) -> Self {
94 Self {
95 hash: hash_value(&*key),
96 #[cfg(debug_assertions)]
97 original: key,
98 }
99 }
100}
101
102impl From<&'_ str> for Key {
103 fn from(key: &'_ str) -> Self {
104 Self {
105 hash: hash_value(key),
106 #[cfg(debug_assertions)]
107 original: Rc::from(key),
108 }
109 }
110}
111
112impl From<String> for Key {
113 fn from(key: String) -> Self {
114 Self::from(key.as_str())
115 }
116}
117
118macro_rules! key_impl_from_numeric {
119 ($type:ty) => {
120 impl From<$type> for Key {
121 fn from(key: $type) -> Self {
122 Self {
123 hash: hash_value(&key),
124 #[cfg(debug_assertions)]
125 original: Rc::from(key.to_string().as_str()),
126 }
127 }
128 }
129 };
130}
131
132key_impl_from_numeric!(char);
133key_impl_from_numeric!(u8);
134key_impl_from_numeric!(u16);
135key_impl_from_numeric!(u32);
136key_impl_from_numeric!(u64);
137key_impl_from_numeric!(u128);
138key_impl_from_numeric!(usize);
139key_impl_from_numeric!(i8);
140key_impl_from_numeric!(i16);
141key_impl_from_numeric!(i32);
142key_impl_from_numeric!(i64);
143key_impl_from_numeric!(i128);
144key_impl_from_numeric!(isize);
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149
150 #[test]
151 fn same_str_equal() {
152 assert_eq!(Key::from("hello"), Key::from("hello"));
153 }
154
155 #[test]
156 fn different_str_not_equal() {
157 assert_ne!(Key::from("hello"), Key::from("world"));
158 }
159
160 #[test]
161 fn same_integer_equal() {
162 assert_eq!(Key::from(42u64), Key::from(42u64));
163 }
164
165 #[test]
166 fn different_integer_not_equal() {
167 assert_ne!(Key::from(1u64), Key::from(2u64));
168 }
169
170 #[test]
171 fn str_and_integer_not_equal() {
172 assert_ne!(Key::from("0"), Key::from(0u64));
173 }
174
175 #[test]
176 fn string_and_str_equal() {
177 assert_eq!(Key::from("abc"), Key::from(String::from("abc")));
178 }
179
180 #[test]
181 fn rc_str_and_str_equal() {
182 assert_eq!(Key::from("abc"), Key::from(Rc::<str>::from("abc")));
183 }
184
185 #[test]
186 fn option_key_niche_optimised() {
187 assert_eq!(
188 std::mem::size_of::<Option<Key>>(),
189 std::mem::size_of::<Key>()
190 );
191 }
192}
193
194#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
195#[cfg(test)]
196mod wasm_tests {
197 use std::rc::Rc;
198
199 use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
200
201 use crate::html;
202
203 wasm_bindgen_test_configure!(run_in_browser);
204
205 #[test]
206 fn all_key_conversions() {
207 let _ = html! {
208 <key="string literal">
209 <img key={"String".to_owned()} />
210 <p key={Rc::<str>::from("rc")}></p>
211 <key='a'>
212 <p key=11_usize></p>
213 <p key=12_u8></p>
214 <p key=13_u16></p>
215 <p key=14_u32></p>
216 <p key=15_u64></p>
217 <p key=16_u128></p>
218 <p key=21_isize></p>
219 <p key=22_i8></p>
220 <p key=23_i16></p>
221 <p key=24_i32></p>
222 <p key=25_i128></p>
223 </>
224 </>
225 };
226 }
227}