Repository: z-galaxy/zbus (Rust)

Issue:
Provide a `signature!` macro that constructs a const singature
This was discussed before but at the time, we didn't have the means to do it. Now it should quite easy, with the parsed signature type. In fact, the `Type` macro is already doing it for its `signature` attribute. This would be mostly about moving that logic out into a separate macro. The folks can do things like:

```rust
let singature = signature!("a{sv}");
const SIGNATURE: Signature = signature!("a{sv}");
```

with the string checked at compile-time.


Discussion:

Patch:
diff --git a/zbus/src/message/header.rs b/zbus/src/message/header.rs
index 9ac99cbf7..9f54012e7 100644
--- a/zbus/src/message/header.rs
+++ b/zbus/src/message/header.rs
@@ -321,7 +321,7 @@ mod tests {
     use std::{borrow::Cow, error::Error};
     use test_log::test;
     use zbus_names::{InterfaceName, MemberName};
-    use zvariant::{ObjectPath, Signature};
+    use zvariant::{signature, ObjectPath, Signature};
 
     #[test]
     fn header() -> Result<(), Box<dyn Error>> {
@@ -362,7 +362,7 @@ mod tests {
         assert_eq!(h.destination().unwrap(), ":1.11");
         assert_eq!(h.reply_serial().map(Into::into), Some(88));
         assert_eq!(h.sender(), None);
-        assert_eq!(h.signature(), &Signature::try_from("say").unwrap());
+        assert_eq!(h.signature(), &signature!("say"));
         assert_eq!(h.unix_fds(), Some(12));
 
         Ok(())
diff --git a/zvariant/src/lib.rs b/zvariant/src/lib.rs
index 9760b7a98..46c4aa36d 100644
--- a/zvariant/src/lib.rs
+++ b/zvariant/src/lib.rs
@@ -98,7 +98,7 @@ pub use as_value::Deserialize as DeserializeValue;
 #[deprecated(since = "5.5.0", note = "Use `as_value::Serialize` instead.")]
 pub use as_value::Serialize as SerializeValue;
 
-pub use zvariant_derive::{DeserializeDict, OwnedValue, SerializeDict, Type, Value};
+pub use zvariant_derive::{signature, DeserializeDict, OwnedValue, SerializeDict, Type, Value};
 
 // Required for the macros to function within this crate.
 extern crate self as zvariant;
diff --git a/zvariant/src/value.rs b/zvariant/src/value.rs
index d1e4b9340..3ac07294a 100644
--- a/zvariant/src/value.rs
+++ b/zvariant/src/value.rs
@@ -1058,10 +1058,7 @@ mod tests {
 
         assert_eq!(
             Value::new((
-                vec![
-                    Signature::try_from("").unwrap(),
-                    Signature::try_from("(ysa{sd})").unwrap(),
-                ],
+                vec![crate::signature!(""), crate::signature!("(ysa{sd})"),],
                 vec![
                     ObjectPath::from_static_str("/").unwrap(),
                     ObjectPath::from_static_str("/a/very/looooooooooooooooooooooooo0000o0ng/path")
diff --git a/zvariant_derive/src/lib.rs b/zvariant_derive/src/lib.rs
index 4e5f5245f..91f150bf1 100644
--- a/zvariant_derive/src/lib.rs
+++ b/zvariant_derive/src/lib.rs
@@ -15,6 +15,7 @@ use proc_macro::TokenStream;
 use syn::DeriveInput;
 
 mod dict;
+mod signature;
 mod r#type;
 mod utils;
 mod value;
@@ -489,3 +490,87 @@ pub fn owned_value_macro_derive(input: TokenStream) -> TokenStream {
         .unwrap_or_else(|err| err.to_compile_error())
         .into()
 }
+
+/// Constructs a const [`Signature`] with compile-time validation.
+///
+/// This macro creates a `Signature` from a string literal at compile time, validating
+/// that the signature string is valid D-Bus signature. Invalid signatures will cause
+/// a compilation error.
+///
+/// # Examples
+///
+/// ## Basic usage
+///
+/// ```
+/// use zvariant::signature;
+///
+/// // Create signatures for basic types
+/// let sig = signature!("s");  // String signature
+/// assert_eq!(sig.to_string(), "s");
+///
+/// let sig = signature!("i");  // 32-bit integer signature
+/// assert_eq!(sig.to_string(), "i");
+/// ```
+///
+/// ## Container types
+///
+/// ```
+/// use zvariant::signature;
+///
+/// // Array of strings
+/// let sig = signature!("as");
+/// assert_eq!(sig.to_string(), "as");
+///
+/// // Dictionary mapping strings to variants
+/// let sig = signature!("a{sv}");
+/// assert_eq!(sig.to_string(), "a{sv}");
+///
+/// // Structures
+/// let sig = signature!("(isx)");
+/// assert_eq!(sig.to_string(), "(isx)");
+/// ```
+///
+/// ## Const signatures
+///
+/// The macro can be used to create const signatures, which is especially useful
+/// for defining signatures at compile time:
+///
+/// ```
+/// use zvariant::{signature, Signature};
+///
+/// const MY_SIGNATURE: Signature = signature!("a{sv}");
+///
+/// fn process_data(_data: &str) {
+///     assert_eq!(MY_SIGNATURE.to_string(), "a{sv}");
+/// }
+/// ```
+///
+/// ## Using the `dict` alias
+///
+/// For convenience, `dict` is an alias for `a{sv}` (string-to-variant dictionary):
+///
+/// ```
+/// use zvariant::signature;
+///
+/// let sig = signature!("dict");
+/// assert_eq!(sig.to_string(), "a{sv}");
+/// ```
+///
+/// ## Compile-time validation
+///
+/// Invalid signatures will be caught at compile time:
+///
+/// ```compile_fail
+/// use zvariant::signature;
+///
+/// // This will fail to compile because 'z' is not a valid D-Bus type
+/// let sig = signature!("z");
+/// ```
+///
+/// [`Signature`]: https://docs.rs/zvariant/latest/zvariant/enum.Signature.html
+#[proc_macro]
+pub fn signature(input: TokenStream) -> TokenStream {
+    signature::expand_signature_macro(input.into())
+        .unwrap_or_else(|err| err.to_compile_error())
+        .into()
+}
diff --git a/zvariant_derive/src/signature.rs b/zvariant_derive/src/signature.rs
new file mode 100644
index 000000000..76dfd4877
--- /dev/null
+++ b/zvariant_derive/src/signature.rs
@@ -0,0 +1,107 @@
+use std::str::FromStr;
+
+use proc_macro2::{Literal, TokenStream};
+use quote::quote;
+use syn::{parse::Parse, Error};
+use zvariant_utils::signature::Signature;
+
+/// Expand the `signature!` macro implementation.
+///
+/// Takes a string literal signature and converts it to compile-time tokens
+/// representing a const `Signature`.
+pub fn expand_signature_macro(input: TokenStream) -> Result<TokenStream, Error> {
+    let SignatureInput {
+        literal: signature_str,
+    } = syn::parse2(input)?;
+
+    let signature_string = signature_str.to_string();
+    let signature_string = signature_string.trim_matches('"');
+
+    let signature = match signature_string {
+        "dict" => Signature::dict(Signature::Str, Signature::Variant),
+        s => Signature::from_str(s).map_err(|e| Error::new(signature_str.span(), e))?,
+    };
+
+    let signature_tokens = signature_to_tokens(&signature);
+
+    Ok(signature_tokens)
+}
+
+/// Input type for the signature macro.
+struct SignatureInput {
+    literal: Literal,
+}
+
+impl Parse for SignatureInput {
+    fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
+        Ok(SignatureInput {
+            literal: input.parse()?,
+        })
+    }
+}
+
+/// Converts a parsed `Signature` to compile-time token representation.
+///
+/// This function generates the Rust tokens that will construct the signature
+/// at compile time. Used by both the signature! macro and the Type derive macro.
+pub fn signature_to_tokens(signature: &Signature) -> TokenStream {
+    let zv = quote! { ::zvariant };
+
+    match signature {
+        Signature::Unit => quote! { #zv::Signature::Unit },
+        Signature::Bool => quote! { #zv::Signature::Bool },
+        Signature::U8 => quote! { #zv::Signature::U8 },
+        Signature::I16 => quote! { #zv::Signature::I16 },
+        Signature::U16 => quote! { #zv::Signature::U16 },
+        Signature::I32 => quote! { #zv::Signature::I32 },
+        Signature::U32 => quote! { #zv::Signature::U32 },
+        Signature::I64 => quote! { #zv::Signature::I64 },
+        Signature::U64 => quote! { #zv::Signature::U64 },
+        Signature::F64 => quote! { #zv::Signature::F64 },
+        Signature::Str => quote! { #zv::Signature::Str },
+        Signature::Signature => quote! { #zv::Signature::Signature },
+        Signature::ObjectPath => quote! { #zv::Signature::ObjectPath },
+        Signature::Variant => quote! { #zv::Signature::Variant },
+        #[cfg(unix)]
+        Signature::Fd => quote! { #zv::Signature::Fd },
+        Signature::Array(child) => {
+            let signature = signature_to_tokens(child.signature());
+            quote! {
+                #zv::Signature::Array(#zv::signature::Child::Static {
+                    child: &#signature,
+                })
+            }
+        }
+        Signature::Dict { key, value } => {
+            let key_sig = signature_to_tokens(key.signature());
+            let value_sig = signature_to_tokens(value.signature());
+            quote! {
+                #zv::Signature::Dict {
+                    key: #zv::signature::Child::Static {
+                        child: &#key_sig,
+                    },
+                    value: #zv::signature::Child::Static {
+                        child: &#value_sig,
+                    },
+                }
+            }
+        }
+        Signature::Structure(fields) => {
+            let fields = fields.iter().map(signature_to_tokens);
+            quote! {
+                #zv::Signature::Structure(#zv::signature::Fields::Static {
+                    fields: &[#(&#fields),*],
+                })
+            }
+        }
+        #[cfg(feature = "gvariant")]
+        Signature::Maybe(child) => {
+            let signature = signature_to_tokens(child.signature());
+            quote! {
+                #zv::Signature::Maybe(#zv::signature::Child::Static {
+                    child: &#signature,
+                })
+            }
+        }
+    }
+}
diff --git a/zvariant_derive/src/type.rs b/zvariant_derive/src/type.rs
index b140445e9..e2f536d55 100644
--- a/zvariant_derive/src/type.rs
+++ b/zvariant_derive/src/type.rs
@@ -7,7 +7,7 @@ use syn::{
 };
 use zvariant_utils::signature::Signature;
 
-use crate::utils::*;
+use crate::{signature::signature_to_tokens, utils::*};
 
 pub fn expand_derive(ast: DeriveInput) -> Result<TokenStream, Error> {
     let StructAttributes { signature, .. } = StructAttributes::parse(&ast.attrs)?;
@@ -20,7 +20,7 @@ pub fn expand_derive(ast: DeriveInput) -> Result<TokenStream, Error> {
             "dict" => Signature::dict(Signature::Str, Signature::Variant),
             s => Signature::from_str(s).map_err(|e| Error::new(ast.span(), e))?,
         };
-        let signature_tokens = signature_to_tokens(&signature, &zv);
+        let signature_tokens = signature_to_tokens(&signature);
 
         let name = ast.ident;
         let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
@@ -191,63 +191,3 @@ fn signature_for_variant(
         Fields::Unnamed(_) => Ok(signature_for_struct(&variant.fields, zv, true)),
     }
 }
-
-fn signature_to_tokens(signature: &Signature, zv: &TokenStream) -> TokenStream {
-    match signature {
-        Signature::Unit => quote! { #zv::Signature::Unit },
-        Signature::Bool => quote! { #zv::Signature::Bool },
-        Signature::U8 => quote! { #zv::Signature::U8 },
-        Signature::I16 => quote! { #zv::Signature::I16 },
-        Signature::U16 => quote! { #zv::Signature::U16 },
-        Signature::I32 => quote! { #zv::Signature::I32 },
-        Signature::U32 => quote! { #zv::Signature::U32 },
-        Signature::I64 => quote! { #zv::Signature::I64 },
-        Signature::U64 => quote! { #zv::Signature::U64 },
-        Signature::F64 => quote! { #zv::Signature::F64 },
-        Signature::Str => quote! { #zv::Signature::Str },
-        Signature::Signature => quote! { #zv::Signature::Signature },
-        Signature::ObjectPath => quote! { #zv::Signature::ObjectPath },
-        Signature::Variant => quote! { #zv::Signature::Variant },
-        #[cfg(unix)]
-        Signature::Fd => quote! { #zv::Signature::Fd },
-        Signature::Array(child) => {
-            let signature = signature_to_tokens(child.signature(), zv);
-            quote! {
-                #zv::Signature::Array(#zv::signature::Child::Static {
-                    child: &#signature,
-                })
-            }
-        }
-        Signature::Dict { key, value } => {
-            let key_sig = signature_to_tokens(key.signature(), zv);
-            let value_sig = signature_to_tokens(value.signature(), zv);
-            quote! {
-                #zv::Signature::Dict {
-                    key: #zv::signature::Child::Static {
-                        child: &#key_sig,
-                    },
-                    value: #zv::signature::Child::Static {
-                        child: &#value_sig,
-                    },
-                }
-            }
-        }
-        Signature::Structure(fields) => {
-            let fields = fields.iter().map(|f| signature_to_tokens(f, zv));
-            quote! {
-                #zv::Signature::Structure(#zv::signature::Fields::Static {
-                    fields: &[#(&#fields),*],
-                })
-            }
-        }
-        #[cfg(feature = "gvariant")]
-        Signature::Maybe(child) => {
-            let signature = signature_to_tokens(child.signature(), zv);
-            quote! {
-                #zv::Signature::Maybe(#zv::signature::Child::Static {
-                    child: &#signature,
-                })
-            }
-        }
-    }
-}
diff --git a/zvariant_utils/src/signature/mod.rs b/zvariant_utils/src/signature/mod.rs
index ad8171e83..e4ae0e0c7 100644
--- a/zvariant_utils/src/signature/mod.rs
+++ b/zvariant_utils/src/signature/mod.rs
@@ -24,19 +24,39 @@ use crate::serialized::Format;
 ///
 /// # Examples
 ///
-/// Typically, you'd create a `Signature` from a string:
+/// ## Using the `signature!` macro
+///
+/// The recommended way to create a `Signature` is using the [`signature!`] macro, which provides
+/// compile-time validation and can be used in const contexts:
 ///
 /// ```
-/// use std::str::FromStr;
+/// use zvariant::signature;
 /// use zvariant::Signature;
 ///
-/// let sig = Signature::from_str("a{sv}").unwrap();
+/// // Compile-time validated signatures
+/// let sig = signature!("a{sv}");
 /// assert_eq!(sig.to_string(), "a{sv}");
 ///
-/// let sig = Signature::from_str("(xa{bs}as)").unwrap();
+/// let sig = signature!("(xa{bs}as)");
 /// assert_eq!(sig.to_string(), "(xa{bs}as)");
+///
+/// // Can be used in const contexts
+/// const SIGNATURE: Signature = signature!("a{sv}");
+/// ```
+///
+/// ## Creating from a string at runtime
+///
+/// If you need to create a `Signature` from a runtime string, use `from_str`:
+///
+/// ```
+/// use std::str::FromStr;
+/// use zvariant::Signature;
+///
+/// let sig = Signature::from_str("a{sv}").unwrap();
+/// assert_eq!(sig.to_string(), "a{sv}");
 /// ```
 ///
+/// [`signature!`]: https://docs.rs/zvariant/latest/zvariant/macro.signature.html
 /// [`zvariant::Signature`]: https://docs.rs/zvariant/latest/zvariant/struct.Signature.html
 #[derive(Debug, Default, Clone)]
 pub enum Signature {


Test patch:
diff --git a/zvariant/tests/signature.rs b/zvariant/tests/signature.rs
index 4888cf707..0dfb44b32 100644
--- a/zvariant/tests/signature.rs
+++ b/zvariant/tests/signature.rs
@@ -1,5 +1,4 @@
-use std::str::FromStr;
-use zvariant::{Signature, Value, LE};
+use zvariant::{signature, Signature, Value, LE};
 
 #[macro_use]
 mod common {
@@ -8,7 +7,8 @@ mod common {
 
 #[test]
 fn signature() {
-    let sig = Signature::from_str("yys").unwrap();
+    let sig: Signature = signature!("yys");
+
     // Structure will always add () around the signature if it's a struct.
     basic_type_test!(LE, DBus, sig, 7, Signature, 1);
 
@@ -20,13 +20,13 @@ fn signature() {
     assert_eq!(v.value_signature(), "g");
     let encoded = value_test!(LE, DBus, v, 10);
     let v = encoded.deserialize::<Value<'_>>().unwrap().0;
-    assert_eq!(v, Value::Signature(Signature::try_from("yys").unwrap()));
+    assert_eq!(v, Value::Signature(signature!("yys")));
 
     // GVariant format now
     #[cfg(feature = "gvariant")]
     {
         let encoded = value_test!(LE, GVariant, v, 8);
         let v = encoded.deserialize::<Value<'_>>().unwrap().0;
-        assert_eq!(v, Value::Signature(Signature::try_from("yys").unwrap()));
+        assert_eq!(v, Value::Signature(signature!("yys")));
     }
 }