diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b980088c..d211e6cfd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,8 @@ any effect. - The `unsafe_protocol` macro now accepts the path of a `Guid` constant in addition to a string literal. +- The `cstr8` and the `cstr16` macros now both accept `(nothing)` and `""` + (empty inputs) to create valid empty strings. They include the null-byte. ## uefi-services - [Unreleased] diff --git a/uefi-macros/src/lib.rs b/uefi-macros/src/lib.rs index c63429612..00ffe9bbb 100644 --- a/uefi-macros/src/lib.rs +++ b/uefi-macros/src/lib.rs @@ -258,12 +258,26 @@ pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream { /// # Example /// ``` /// # use uefi_macros::cstr8; +/// // Empty string +/// assert_eq!(cstr8!().to_u16_slice_with_nul(), [0]); +/// assert_eq!(cstr8!("").to_u16_slice_with_nul(), [0]); +/// // Non-empty string /// assert_eq!(cstr8!("test").to_bytes_with_nul(), [116, 101, 115, 116, 0]); /// ``` #[proc_macro] pub fn cstr8(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + // Accept empty input. + if input.is_empty() { + return quote!(unsafe { ::uefi::CStr16::from_u16_with_nul_unchecked(&[0]) }).into(); + } let input: LitStr = parse_macro_input!(input); let input = input.value(); + // Accept "" input. + if input.is_empty() { + return quote!(unsafe { ::uefi::CStr16::from_u16_with_nul_unchecked(&[0]) }).into(); + } + + // Accept any non-empty string input. match input .chars() .map(u8::try_from) @@ -283,14 +297,28 @@ pub fn cstr8(input: proc_macro::TokenStream) -> proc_macro::TokenStream { /// This will throw a compile error if an invalid character is in the passed string. /// /// # Example -/// ``` +/// ```rust /// # use uefi_macros::cstr16; +/// // Empty string +/// assert_eq!(cstr16!().to_u16_slice_with_nul(), [0]); +/// assert_eq!(cstr16!("").to_u16_slice_with_nul(), [0]); +/// // Non-empty string /// assert_eq!(cstr16!("test €").to_u16_slice_with_nul(), [116, 101, 115, 116, 32, 8364, 0]); /// ``` #[proc_macro] pub fn cstr16(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + // Accept empty input. + if input.is_empty() { + return quote!(unsafe { ::uefi::CStr16::from_u16_with_nul_unchecked(&[0]) }).into(); + } let input: LitStr = parse_macro_input!(input); let input = input.value(); + // Accept "" input. + if input.is_empty() { + return quote!(unsafe { ::uefi::CStr16::from_u16_with_nul_unchecked(&[0]) }).into(); + } + + // Accept any non-empty string input. match input .chars() .map(|c| u16::try_from(c as u32)) @@ -299,8 +327,11 @@ pub fn cstr16(input: proc_macro::TokenStream) -> proc_macro::TokenStream { Ok(c) => { quote!(unsafe { ::uefi::CStr16::from_u16_with_nul_unchecked(&[ #(#c),* , 0 ]) }).into() } - Err(_) => syn::Error::new_spanned(input, "invalid character in string") - .into_compile_error() - .into(), + Err(_) => syn::Error::new_spanned( + input, + "There are UTF-8 characters that can't be transformed to UCS-2 character", + ) + .into_compile_error() + .into(), } } diff --git a/uefi/src/lib.rs b/uefi/src/lib.rs index 1c5499658..f8fa31f62 100644 --- a/uefi/src/lib.rs +++ b/uefi/src/lib.rs @@ -127,3 +127,24 @@ pub(crate) mod mem; pub(crate) mod polyfill; mod util; + +#[cfg(test)] +// Crates that create procedural macros can't unit test the macros they export. +// Therefore, we do some tests here. +mod macro_tests { + use uefi_macros::{cstr16, cstr8}; + + #[test] + fn cstr8_macro_literal() { + let _empty1 = cstr8!(); + let _empty2 = cstr8!(""); + let _regular = cstr8!("foobar"); + } + + #[test] + fn cstr16_macro_literal() { + let _empty1 = cstr16!(); + let _empty2 = cstr16!(""); + let _regular = cstr16!("foobar"); + } +}