diff --git a/Cargo.toml b/Cargo.toml index c04ff819..bf7a6a5b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ ruma-state-res = { version = "0.11.0", path = "crates/ruma-state-res" } serde = { version = "1.0.164", features = ["derive"] } serde_html_form = "0.2.0" serde_json = "1.0.87" +smallvec = { version = "1.13.2", features = ["const_generics", "const_new", "serde", "union", "write"] } thiserror = "2.0.0" tracing = { version = "0.1.37", default-features = false, features = ["std"] } url = { version = "2.5.0" } diff --git a/crates/ruma-common/Cargo.toml b/crates/ruma-common/Cargo.toml index c0637102..c851b822 100644 --- a/crates/ruma-common/Cargo.toml +++ b/crates/ruma-common/Cargo.toml @@ -79,6 +79,7 @@ ruma-macros = { workspace = true } serde = { workspace = true } serde_html_form = { workspace = true } serde_json = { workspace = true, features = ["raw_value"] } +smallvec = { workspace = true } thiserror = { workspace = true } time = "0.3.34" tracing = { workspace = true, features = ["attributes"] } diff --git a/crates/ruma-common/src/identifiers/event_id.rs b/crates/ruma-common/src/identifiers/event_id.rs index b9835225..ae69ef48 100644 --- a/crates/ruma-common/src/identifiers/event_id.rs +++ b/crates/ruma-common/src/identifiers/event_id.rs @@ -37,7 +37,10 @@ use super::ServerName; /// [room versions]: https://spec.matrix.org/latest/rooms/#complete-list-of-room-versions #[repr(transparent)] #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, IdZst)] -#[ruma_id(validate = ruma_identifiers_validation::event_id::validate)] +#[ruma_id( + validate = ruma_identifiers_validation::event_id::validate, + inline_bytes = 48 +)] pub struct EventId(str); impl EventId { diff --git a/crates/ruma-events/Cargo.toml b/crates/ruma-events/Cargo.toml index 781e5781..13f760d4 100644 --- a/crates/ruma-events/Cargo.toml +++ b/crates/ruma-events/Cargo.toml @@ -75,6 +75,7 @@ ruma-identifiers-validation = { workspace = true } ruma-macros = { workspace = true } serde = { workspace = true } serde_json = { workspace = true, features = ["raw_value"] } +smallvec = { workspace = true } thiserror = { workspace = true } tracing = { workspace = true, features = ["attributes"] } url = { workspace = true } diff --git a/crates/ruma-macros/src/identifiers.rs b/crates/ruma-macros/src/identifiers.rs index 89afd8e3..19f019d7 100644 --- a/crates/ruma-macros/src/identifiers.rs +++ b/crates/ruma-macros/src/identifiers.rs @@ -24,11 +24,6 @@ impl Parse for IdentifierInput { } pub fn expand_id_zst(input: ItemStruct) -> syn::Result { - let id = &input.ident; - let owned = format_ident!("Owned{id}"); - - let owned_decl = expand_owned_id(&input); - let meta = input.attrs.iter().filter(|attr| attr.path().is_ident("ruma_id")).try_fold( IdZstMeta::default(), |meta, attr| { @@ -53,9 +48,18 @@ pub fn expand_id_zst(input: ItemStruct) -> syn::Result { // So we don't have to insert #where_clause everywhere when it is always None in practice assert_eq!(where_clause, None, "where clauses on identifier types are not currently supported"); - let as_str_docs = format!("Creates a string slice from this `{id}`."); - let as_bytes_docs = format!("Creates a byte slice from this `{id}`."); - let max_bytes_docs = format!("Maximum byte length for any `{id}`."); + let id = &input.ident; + let owned = format_ident!("Owned{id}"); + let id_ty = quote! { #id #ty_generics }; + let owned_ty = quote! { #owned #ty_generics }; + + const INLINE_BYTES: usize = 32; + let inline_bytes = meta.inline_bytes.unwrap_or(INLINE_BYTES); + let inline_array = quote! { [u8; #inline_bytes] }; + let sv_decl = quote! { smallvec::SmallVec<#inline_array> }; + let sv = quote! { smallvec::SmallVec::<#inline_array> }; + + let owned_decl = expand_owned_id(&input, inline_bytes); let as_str_impl = match &input.fields { Fields::Named(_) | Fields::Unit => { @@ -68,13 +72,14 @@ pub fn expand_id_zst(input: ItemStruct) -> syn::Result { } }; - let id_ty = quote! { #id #ty_generics }; - let owned_ty = quote! { #owned #ty_generics }; - let as_str_impls = expand_as_str_impls(id_ty.clone(), &impl_generics); // FIXME: Remove? let box_partial_eq_string = expand_partial_eq_string(quote! { Box<#id_ty> }, &impl_generics); + let as_str_docs = format!("Creates a string slice from this `{id}`."); + let as_bytes_docs = format!("Creates a byte slice from this `{id}`."); + let max_bytes_docs = format!("Maximum byte length for any `{id}`."); + Ok(quote! { #owned_decl @@ -100,8 +105,15 @@ pub fn expand_id_zst(input: ItemStruct) -> syn::Result { unsafe { std::sync::Arc::from_raw(std::sync::Arc::into_raw(s) as _) } } - pub(super) fn into_owned(self: Box) -> Box { - unsafe { Box::from_raw(Box::into_raw(self) as _) } + pub(super) fn into_owned(self: Box) -> #sv_decl { + let len = self.as_bytes().len(); + let p: *mut u8 = Box::into_raw(self).cast(); + let v = unsafe { Vec::::from_raw_parts(p, len, len) }; + #sv::from_vec(v) + } + + pub(super) fn into_box(s: Box) -> Box { + unsafe { Box::from_raw(Box::into_raw(s) as _) } } #[doc = #as_str_docs] @@ -129,7 +141,7 @@ pub fn expand_id_zst(input: ItemStruct) -> syn::Result { type Owned = #owned_ty; fn to_owned(&self) -> Self::Owned { - #owned::from_ref(self) + Self::Owned::new(self.as_bytes().into()) } } @@ -178,7 +190,7 @@ pub fn expand_id_zst(input: ItemStruct) -> syn::Result { #[automatically_derived] impl #impl_generics From> for String { fn from(id: Box<#id_ty>) -> Self { - id.into_owned().into() + String::from(<#id_ty>::into_box(id)) } } @@ -239,41 +251,49 @@ pub fn expand_id_zst(input: ItemStruct) -> syn::Result { }) } -fn expand_owned_id(input: &ItemStruct) -> TokenStream { +fn expand_owned_id(input: &ItemStruct, inline_bytes: usize) -> TokenStream { + let (impl_generics, ty_generics, _where_clause) = input.generics.split_for_impl(); + let listed_generics: Punctuated<_, Token![,]> = + input.generics.type_params().map(|param| ¶m.ident).collect(); + let id = &input.ident; let owned = format_ident!("Owned{id}"); - - let doc_header = format!("Owned variant of {id}"); - let (impl_generics, ty_generics, _where_clause) = input.generics.split_for_impl(); - let id_ty = quote! { #id #ty_generics }; let owned_ty = quote! { #owned #ty_generics }; + let inline_array = quote! { [u8; #inline_bytes] }; + let sv_decl = quote! { smallvec::SmallVec<#inline_array> }; + + let has_generics = !listed_generics.is_empty(); + let phantom_decl = if has_generics { + quote! { _p: std::marker::PhantomData<(#listed_generics)>, } + } else { + quote! {} + }; + + let phantom_impl = if has_generics { + quote! { _p: std::marker::PhantomData::<(#listed_generics)>, } + } else { + quote! {} + }; + let as_str_impls = expand_as_str_impls(owned_ty.clone(), &impl_generics); + let doc_header = format!("Owned variant of {id}"); + quote! { #[doc = #doc_header] - /// - /// The wrapper type for this type is variable, by default it'll use [`Box`], - /// but you can change that by setting "`--cfg=ruma_identifiers_storage=...`" using - /// `RUSTFLAGS` or `.cargo/config.toml` (under `[build]` -> `rustflags = ["..."]`) - /// to the following; - /// - `ruma_identifiers_storage="Arc"` to use [`Arc`](std::sync::Arc) as a wrapper type. pub struct #owned #impl_generics { - #[cfg(not(any(ruma_identifiers_storage = "Arc")))] - inner: Box<#id_ty>, - #[cfg(ruma_identifiers_storage = "Arc")] - inner: std::sync::Arc<#id_ty>, + inner: #sv_decl, + #phantom_decl } #[automatically_derived] impl #impl_generics #owned_ty { - fn from_ref(v: &#id_ty) -> Self { + fn new(inner: #sv_decl) -> Self { Self { - #[cfg(not(any(ruma_identifiers_storage = "Arc")))] - inner: #id::from_box(v.as_str().into()), - #[cfg(ruma_identifiers_storage = "Arc")] - inner: #id::from_arc(v.as_str().into()), + inner, + #phantom_impl } } } @@ -281,38 +301,37 @@ fn expand_owned_id(input: &ItemStruct) -> TokenStream { #[automatically_derived] impl #impl_generics AsRef<#id_ty> for #owned_ty { fn as_ref(&self) -> &#id_ty { - &*self.inner + let s: &str = self.as_ref(); + <#id_ty>::from_borrowed(s) } } #[automatically_derived] impl #impl_generics AsRef for #owned_ty { fn as_ref(&self) -> &str { - self.inner.as_str() + let s: &[u8] = self.as_ref(); + unsafe { std::str::from_utf8_unchecked(s) } } } #[automatically_derived] impl #impl_generics AsRef<[u8]> for #owned_ty { fn as_ref(&self) -> &[u8] { - self.inner.as_bytes() + self.inner.as_slice() } } #[automatically_derived] impl #impl_generics From<#owned_ty> for String { fn from(id: #owned_ty) -> String { - #[cfg(not(any(ruma_identifiers_storage = "Arc")))] - { id.inner.into() } - #[cfg(ruma_identifiers_storage = "Arc")] - { id.inner.as_ref().into() } + unsafe { String::from_utf8_unchecked(id.inner.into_vec()) } } } #[automatically_derived] impl #impl_generics std::clone::Clone for #owned_ty { fn clone(&self) -> Self { - (&*self.inner).into() + Self::new(self.inner.clone()) } } @@ -321,7 +340,7 @@ fn expand_owned_id(input: &ItemStruct) -> TokenStream { type Target = #id_ty; fn deref(&self) -> &Self::Target { - &self.inner + self.as_ref() } } @@ -335,46 +354,35 @@ fn expand_owned_id(input: &ItemStruct) -> TokenStream { #[automatically_derived] impl #impl_generics From<&'_ #id_ty> for #owned_ty { fn from(id: &#id_ty) -> #owned_ty { - #owned { inner: id.into() } + Self::new(id.as_bytes().into()) } } #[automatically_derived] impl #impl_generics From> for #owned_ty { fn from(b: Box<#id_ty>) -> #owned_ty { - Self { inner: b.into() } + Self::new(<#id_ty>::into_owned(b)) } } #[automatically_derived] impl #impl_generics From> for #owned_ty { fn from(a: std::sync::Arc<#id_ty>) -> #owned_ty { - Self { - #[cfg(not(any(ruma_identifiers_storage = "Arc")))] - inner: a.as_ref().into(), - #[cfg(ruma_identifiers_storage = "Arc")] - inner: a, - } + Self::new(a.as_bytes().into()) //TODO } } #[automatically_derived] impl #impl_generics From<#owned_ty> for Box<#id_ty> { fn from(a: #owned_ty) -> Box<#id_ty> { - #[cfg(not(any(ruma_identifiers_storage = "Arc")))] - { a.inner } - #[cfg(ruma_identifiers_storage = "Arc")] - { a.inner.as_ref().into() } + { Box::from(<#id_ty>::from_borrowed(a.as_str())) } } } #[automatically_derived] impl #impl_generics From<#owned_ty> for std::sync::Arc<#id_ty> { fn from(a: #owned_ty) -> std::sync::Arc<#id_ty> { - #[cfg(not(any(ruma_identifiers_storage = "Arc")))] - { a.inner.into() } - #[cfg(ruma_identifiers_storage = "Arc")] - { a.inner } + { std::sync::Arc::from(<#id_ty>::from_borrowed(a.as_str())) } } } @@ -473,20 +481,19 @@ fn expand_owned_id(input: &ItemStruct) -> TokenStream { } fn expand_checked_impls(input: &ItemStruct, validate: Path) -> TokenStream { - let id = &input.ident; - let owned = format_ident!("Owned{id}"); - let (impl_generics, ty_generics, _where_clause) = input.generics.split_for_impl(); let generic_params = &input.generics.params; + let id = &input.ident; + let owned = format_ident!("Owned{id}"); + let id_ty = quote! { #id #ty_generics }; + let owned_ty = quote! { #owned #ty_generics }; + let parse_doc_header = format!("Try parsing a `&str` into an `Owned{id}`."); let parse_box_doc_header = format!("Try parsing a `&str` into a `Box<{id}>`."); let parse_rc_docs = format!("Try parsing a `&str` into an `Rc<{id}>`."); let parse_arc_docs = format!("Try parsing a `&str` into an `Arc<{id}>`."); - let id_ty = quote! { #id #ty_generics }; - let owned_ty = quote! { #owned #ty_generics }; - quote! { #[automatically_derived] impl #impl_generics #id_ty { @@ -498,9 +505,8 @@ fn expand_checked_impls(input: &ItemStruct, validate: Path) -> TokenStream { pub fn parse( s: impl AsRef, ) -> Result<#owned_ty, crate::IdParseError> { - let s = s.as_ref(); - #validate(s)?; - Ok(#id::from_borrowed(s).to_owned()) + #validate(s.as_ref())?; + Ok((<#id_ty>::from_borrowed(s.as_ref())).to_owned()) } #[inline] @@ -512,7 +518,7 @@ fn expand_checked_impls(input: &ItemStruct, validate: Path) -> TokenStream { s: impl AsRef + Into>, ) -> Result, crate::IdParseError> { #validate(s.as_ref())?; - Ok(#id::from_box(s.into())) + Ok(<#id_ty>::from_box(s.into())) } #[inline] @@ -521,7 +527,7 @@ fn expand_checked_impls(input: &ItemStruct, validate: Path) -> TokenStream { s: impl AsRef + Into>, ) -> Result, crate::IdParseError> { #validate(s.as_ref())?; - Ok(#id::from_rc(s.into())) + Ok(<#id_ty>::from_rc(s.into())) } #[inline] @@ -530,7 +536,7 @@ fn expand_checked_impls(input: &ItemStruct, validate: Path) -> TokenStream { s: impl AsRef + Into>, ) -> Result, crate::IdParseError> { #validate(s.as_ref())?; - Ok(#id::from_arc(s.into())) + Ok(<#id_ty>::from_arc(s.into())) } } @@ -544,7 +550,7 @@ fn expand_checked_impls(input: &ItemStruct, validate: Path) -> TokenStream { let s = String::deserialize(deserializer)?; - match #id::parse_box(s) { + match <#id_ty>::parse_box(s) { Ok(o) => Ok(o), Err(e) => Err(D::Error::custom(e)), } @@ -561,7 +567,7 @@ fn expand_checked_impls(input: &ItemStruct, validate: Path) -> TokenStream { let s = String::deserialize(deserializer)?; - match #id::parse(s) { + match <#id_ty>::parse(s) { Ok(o) => Ok(o), Err(e) => Err(D::Error::custom(e)), } @@ -656,7 +662,7 @@ fn expand_checked_impls(input: &ItemStruct, validate: Path) -> TokenStream { type Error = crate::IdParseError; fn try_from(s: String) -> Result { - <#id_ty>::parse_box(s) + <#id_ty>::parse_box(s.into_boxed_str()) } } @@ -690,97 +696,103 @@ fn expand_checked_impls(input: &ItemStruct, validate: Path) -> TokenStream { } fn expand_unchecked_impls(input: &ItemStruct) -> TokenStream { + let (impl_generics, ty_generics, _where_clause) = input.generics.split_for_impl(); + let generic_params = &input.generics.params; + let id = &input.ident; let owned = format_ident!("Owned{id}"); + let id_ty = quote! { #id #ty_generics }; + let owned_ty = quote! { #owned #ty_generics }; quote! { #[automatically_derived] - impl<'a> From<&'a str> for &'a #id { + impl<'a, #generic_params> From<&'a str> for &'a #id_ty { fn from(s: &'a str) -> Self { - #id::from_borrowed(s) + <#id_ty>::from_borrowed(s) } } #[automatically_derived] - impl From<&str> for #owned { + impl #impl_generics From<&str> for #owned_ty { fn from(s: &str) -> Self { - <&#id>::from(s).into() + <&#id_ty>::from(s).into() } } #[automatically_derived] - impl From> for #owned { + impl #impl_generics From> for #owned_ty { fn from(s: Box) -> Self { - <&#id>::from(&*s).into() + let s: String = s.into(); + Self::from(s) } } #[automatically_derived] - impl From for #owned { + impl #impl_generics From for #owned_ty { fn from(s: String) -> Self { - <&#id>::from(s.as_str()).into() + Self::new(s.into_bytes().into()) } } #[automatically_derived] - impl From<&str> for Box<#id> { + impl #impl_generics From<&str> for Box<#id_ty> { fn from(s: &str) -> Self { - #id::from_box(s.into()) + Self::from(s.to_owned().into_boxed_str()) } } #[automatically_derived] - impl From> for Box<#id> { + impl #impl_generics From> for Box<#id_ty> { fn from(s: Box) -> Self { - #id::from_box(s) + <#id_ty>::from_box(s) } } #[automatically_derived] - impl From for Box<#id> { + impl #impl_generics From for Box<#id_ty> { fn from(s: String) -> Self { - #id::from_box(s.into()) + let s = s.into_boxed_str(); + Self::from(s) } } #[automatically_derived] - impl From> for Box { - fn from(id: Box<#id>) -> Self { - id.into_owned() + impl #impl_generics From> for Box { + fn from(id: Box<#id_ty>) -> Self { + <#id_ty>::into_box(id) } } #[automatically_derived] - impl<'de> serde::Deserialize<'de> for Box<#id> { + impl<'de, #generic_params> serde::Deserialize<'de> for Box<#id_ty> { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { - Box::::deserialize(deserializer).map(#id::from_box) + Box::::deserialize(deserializer).map(<#id_ty>::from_box) } } #[automatically_derived] - impl<'de> serde::Deserialize<'de> for #owned { + impl<'de, #generic_params> serde::Deserialize<'de> for #owned_ty { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { // FIXME: Deserialize inner, convert that - Box::::deserialize(deserializer).map(#id::from_box).map(Into::into) + Box::::deserialize(deserializer).map(<#id_ty>::from_box).map(Into::into) } } #[automatically_derived] - impl<'de> serde::Deserialize<'de> for &'de #id { + impl<'de, #generic_params> serde::Deserialize<'de> for &'de #id_ty { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { - <&'de str>::deserialize(deserializer).map(<#id>::from_borrowed).map(Into::into) + <&'de str>::deserialize(deserializer).map(<#id_ty>::from_borrowed).map(Into::into) } } - } } @@ -830,8 +842,7 @@ fn expand_partial_eq_string(ty: TokenStream, impl_generics: &ImplGenerics<'_>) - #[automatically_derived] impl #impl_generics PartialEq<#rhs> for #lhs { fn eq(&self, other: &#rhs) -> bool { - AsRef::::as_ref(self) - == AsRef::::as_ref(other) + AsRef::::as_ref(self) == AsRef::::as_ref(other) } } } @@ -841,11 +852,13 @@ fn expand_partial_eq_string(ty: TokenStream, impl_generics: &ImplGenerics<'_>) - mod kw { syn::custom_keyword!(validate); + syn::custom_keyword!(inline_bytes); } #[derive(Default)] struct IdZstMeta { validate: Option, + inline_bytes: Option, } impl IdZstMeta { @@ -860,7 +873,17 @@ impl IdZstMeta { } }; - Ok(Self { validate }) + let inline_bytes = match (self.inline_bytes, other.inline_bytes) { + (None, None) => None, + (Some(val), None) | (None, Some(val)) => Some(val), + (Some(a), Some(b)) => { + let mut error = syn::Error::new_spanned(b, "duplicate attribute argument"); + error.combine(syn::Error::new_spanned(a, "note: first one here")); + return Err(error); + } + }; + + Ok(Self { validate, inline_bytes }) } } @@ -869,6 +892,15 @@ impl Parse for IdZstMeta { let _: kw::validate = input.parse()?; let _: Token![=] = input.parse()?; let validate = Some(input.parse()?); - Ok(Self { validate }) + + let _: Option = input.parse()?; + + let _: Option = input.parse()?; + let _: Option = input.parse()?; + let inline_bytes: Option = input.parse()?; + let inline_bytes = + inline_bytes.map(|ib| ib.base10_digits().parse().expect("inline_bytes is an integer")); + + Ok(Self { validate, inline_bytes }) } }