Merge pull request #2031 from cwize1/char16Macro

uefi: add convenient char16!() macro
This commit is contained in:
Philipp Schuster 2026-08-18 14:32:13 +00:00 committed by GitHub
commit a34b22476b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 88 additions and 77 deletions

View file

@ -4,6 +4,7 @@
## Changed
- Made memory map types `#[repr(C)]`
- Added `char16!` const-compatible macro as convenient replacement for `Char16::try_from().unwrap()`
## Removed

View file

@ -7,8 +7,10 @@
use core::fmt::{self, Display, Formatter};
use crate::char16;
/// Character conversion error
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct CharConversionError;
impl Display for CharConversionError {
@ -80,6 +82,18 @@ pub const NUL_8: Char8 = Char8(0);
pub struct Char16(u16);
impl Char16 {
/// Creates a UCS-2 character from a Rust character.
///
/// Same as `<Char16 as TryFrom<char>>::try_from` but usable in a const context.
pub const fn try_from_char(value: char) -> Result<Self, CharConversionError> {
let code_point = value as u32;
if code_point > (u16::MAX as u32) {
Err(CharConversionError)
} else {
Ok(Self(code_point as u16))
}
}
/// Creates a UCS-2 character from a Rust character without checks.
///
/// # Safety
@ -160,8 +174,7 @@ impl PartialEq<char> for Char16 {
}
/// UCS-2 version of the NUL character
// SAFETY: The character is valid.
pub const NUL_16: Char16 = unsafe { Char16::from_u16_unchecked(0) };
pub const NUL_16: Char16 = char16!('\0');
#[cfg(test)]
mod tests {
@ -169,13 +182,22 @@ mod tests {
#[test]
fn test_char8_from_char() {
assert_eq!(Char8::try_from('A').unwrap(), Char8(0x41));
assert_eq!(Char8::try_from('A'), Ok(Char8(0x41)));
assert_eq!(Char8::try_from('ꋃ'), Err(CharConversionError {}));
}
#[test]
fn test_char16_from_char() {
assert_eq!(Char16::try_from('A').unwrap(), Char16(0x41));
assert_eq!(Char16::try_from('ꋃ').unwrap(), Char16(0xa2c3));
assert_eq!(Char16::try_from('A'), Ok(Char16(0x41)));
assert_eq!(Char16::try_from('ꋃ'), Ok(Char16(0xa2c3)));
assert_eq!(Char16::try_from('😀'), Err(CharConversionError {}));
}
#[test]
fn test_char16_try_from_char() {
assert_eq!(Char16::try_from_char('A'), Ok(Char16(0x41)));
assert_eq!(Char16::try_from_char('ꋃ'), Ok(Char16(0xa2c3)));
assert_eq!(Char16::try_from_char('😀'), Err(CharConversionError {}));
}
/// Test that `Char8` and `Char16` can be directly compared with `char`.

View file

@ -253,7 +253,7 @@ impl<StrType: AsRef<str> + ?Sized> EqStrUntilNul<StrType> for CString16 {
#[cfg(test)]
mod tests {
use super::*;
use crate::cstr16;
use crate::{char16, cstr16};
use alloc::string::String;
use alloc::vec;
@ -261,7 +261,7 @@ mod tests {
fn test_cstring16_from_str() {
assert_eq!(
CString16::try_from("x").unwrap(),
CString16(vec![Char16::try_from('x').unwrap(), NUL_16])
CString16(vec![char16!('x'), NUL_16])
);
assert_eq!(CString16::try_from("😀"), Err(FromStrError::InvalidChar));
@ -331,14 +331,7 @@ mod tests {
let owned: CString16 = s1.to_owned();
let s2: &CStr16 = owned.borrow();
assert_eq!(s1, s2);
assert_eq!(
owned.0,
[
Char16::try_from('a').unwrap(),
Char16::try_from('b').unwrap(),
NUL_16
]
);
assert_eq!(owned.0, [char16!('a'), char16!('b'), NUL_16]);
}
/// This tests the following UCS-2 string functions:
@ -351,12 +344,12 @@ mod tests {
let mut str1 = CString16::new();
assert_eq!(str1.num_bytes(), 2, "Should have null character");
assert_eq!(str1.num_chars(), 0);
str1.push(Char16::try_from('h').unwrap());
str1.push(Char16::try_from('i').unwrap());
str1.push(char16!('h'));
str1.push(char16!('i'));
assert_eq!(str1.num_chars(), 2);
let mut str2 = CString16::new();
str2.push(Char16::try_from('!').unwrap());
str2.push(char16!('!'));
str2.push_str(str1.as_ref());
assert_eq!(str2.num_chars(), 3);
@ -374,8 +367,8 @@ mod tests {
#[test]
fn test_char_replace_all_in_place() {
let mut input = CString16::try_from("foo/bar/foobar//").unwrap();
let search = Char16::try_from('/').unwrap();
let replace = Char16::try_from('\\').unwrap();
let search = char16!('/');
let replace = char16!('\\');
input.replace_char(search, replace);
let input = String::from(&input);

View file

@ -877,7 +877,7 @@ where
#[cfg(test)]
mod tests {
use super::*;
use crate::{cstr8, cstr16};
use crate::{char16, cstr8, cstr16};
use alloc::format;
use alloc::string::String;
@ -958,31 +958,19 @@ mod tests {
// Invalid: no nul character.
assert_eq!(
CStr16::from_char16_until_nul(&[
Char16::try_from('a').unwrap(),
Char16::try_from('b').unwrap(),
]),
CStr16::from_char16_until_nul(&[char16!('a'), char16!('b'),]),
Err(FromSliceUntilNulError::NoNul)
);
// Valid: trailing nul.
assert_eq!(
CStr16::from_char16_until_nul(&[
Char16::try_from('a').unwrap(),
Char16::try_from('b').unwrap(),
NUL_16,
]),
CStr16::from_char16_until_nul(&[char16!('a'), char16!('b'), NUL_16,]),
Ok(cstr16!("ab"))
);
// Valid: interior nul.
assert_eq!(
CStr16::from_char16_until_nul(&[
Char16::try_from('a').unwrap(),
NUL_16,
Char16::try_from('b').unwrap(),
NUL_16
]),
CStr16::from_char16_until_nul(&[char16!('a'), NUL_16, char16!('b'), NUL_16]),
Ok(cstr16!("a"))
);
}
@ -997,31 +985,19 @@ mod tests {
// Invalid: interior null.
assert_eq!(
CStr16::from_char16_with_nul(&[
Char16::try_from('a').unwrap(),
NUL_16,
Char16::try_from('b').unwrap(),
NUL_16
]),
CStr16::from_char16_with_nul(&[char16!('a'), NUL_16, char16!('b'), NUL_16]),
Err(FromSliceWithNulError::InteriorNul(1))
);
// Invalid: no trailing null.
assert_eq!(
CStr16::from_char16_with_nul(&[
Char16::try_from('a').unwrap(),
Char16::try_from('b').unwrap(),
]),
CStr16::from_char16_with_nul(&[char16!('a'), char16!('b'),]),
Err(FromSliceWithNulError::NotNulTerminated)
);
// Valid.
assert_eq!(
CStr16::from_char16_with_nul(&[
Char16::try_from('a').unwrap(),
Char16::try_from('b').unwrap(),
NUL_16,
]),
CStr16::from_char16_with_nul(&[char16!('a'), char16!('b'), NUL_16,]),
Ok(cstr16!("ab"))
);
}
@ -1109,11 +1085,8 @@ mod tests {
#[test]
fn test_cstr16_as_slice() {
let string: &CStr16 = cstr16!("a");
assert_eq!(string.as_slice(), &[Char16::try_from('a').unwrap()]);
assert_eq!(
string.as_slice_with_nul(),
&[Char16::try_from('a').unwrap(), NUL_16]
);
assert_eq!(string.as_slice(), &[char16!('a')]);
assert_eq!(string.as_slice_with_nul(), &[char16!('a'), NUL_16]);
}
#[test]

View file

@ -24,13 +24,12 @@ pub use path::{Components, Path};
pub use pathbuf::PathBuf;
use crate::data_types::chars::NUL_16;
use crate::{CStr16, Char16, cstr16};
use crate::{CStr16, Char16, char16, cstr16};
pub use validation::PathError;
pub(super) use validation::validate_path;
/// The default separator for paths.
// SAFETY: The memory is valid.
pub const SEPARATOR: Char16 = unsafe { Char16::from_u16_unchecked('\\' as u16) };
pub const SEPARATOR: Char16 = char16!('\\');
/// Stringified version of [`SEPARATOR`].
pub const SEPARATOR_STR: &CStr16 = cstr16!("\\");
@ -38,18 +37,17 @@ pub const SEPARATOR_STR: &CStr16 = cstr16!("\\");
/// Deny list of characters for path components. UEFI supports FAT-like file
/// systems. According to <https://en.wikipedia.org/wiki/Comparison_of_file_systems>,
/// paths should not contain these symbols.
// SAFETY: The memory is valid.
pub const CHARACTER_DENY_LIST: [Char16; 10] = unsafe {
pub const CHARACTER_DENY_LIST: [Char16; 10] = {
[
NUL_16,
Char16::from_u16_unchecked('"' as u16),
Char16::from_u16_unchecked('*' as u16),
Char16::from_u16_unchecked('/' as u16),
Char16::from_u16_unchecked(':' as u16),
Char16::from_u16_unchecked('<' as u16),
Char16::from_u16_unchecked('>' as u16),
Char16::from_u16_unchecked('?' as u16),
char16!('"'),
char16!('*'),
char16!('/'),
char16!(':'),
char16!('<'),
char16!('>'),
char16!('?'),
SEPARATOR,
Char16::from_u16_unchecked('|' as u16),
char16!('|'),
]
};

View file

@ -2,7 +2,7 @@
use crate::fs::SEPARATOR;
use crate::fs::path::Path;
use crate::{CStr16, CString16, Char16};
use crate::{CStr16, CString16, Char16, char16};
use core::fmt::{Display, Formatter};
/// A path buffer similar to the `PathBuf` of the standard library, but based on
@ -21,8 +21,7 @@ impl PathBuf {
/// Constructor that replaces all occurrences of `/` with `\`.
fn new_from_cstring16(mut string: CString16) -> Self {
// SAFETY: The memory is valid.
const SEARCH: Char16 = unsafe { Char16::from_u16_unchecked('/' as u16) };
const SEARCH: Char16 = char16!('/');
string.replace_char(SEARCH, SEPARATOR);
Self(string)
}
@ -31,8 +30,7 @@ impl PathBuf {
///
/// UNIX separators (`/`) will be replaced by [`SEPARATOR`] on the fly.
pub fn push<P: AsRef<Path>>(&mut self, path: P) {
// SAFETY: The memory is valid.
const SEARCH: Char16 = unsafe { Char16::from_u16_unchecked('/' as u16) };
const SEARCH: Char16 = char16!('/');
// do nothing on empty path
if path.as_ref().is_empty() {

View file

@ -44,6 +44,32 @@ macro_rules! cstr8 {
}};
}
/// Encodes a char literal as a [`Char16`].
///
/// The encoding is done at compile time, so the result can be used in a
/// `const` item.
///
/// # Example
///
/// ```
/// use uefi::{CStr8, cstr8};
///
/// const S: &CStr8 = cstr8!("ÿ");
/// assert_eq!(S.as_bytes(), [255, 0]);
/// ```
///
/// [`Char16`]: crate::Char16
#[macro_export]
macro_rules! char16 {
($s:literal) => {{
const C: $crate::Char16 = match $crate::Char16::try_from_char($s) {
Ok(c) => c,
Err(_) => panic!("input contains a character which cannot be represented in UCS-2"),
};
C
}};
}
/// Encode a string literal as a [`&CStr16`].
///
/// The encoding is done at compile time, so the result can be used in a

View file

@ -47,7 +47,7 @@ impl Input {
/// ```
/// use log::info;
/// use uefi::proto::console::text::{Input, Key, ScanCode};
/// use uefi::{boot, Char16, Result, ResultExt};
/// use uefi::{boot, char16, Result, ResultExt};
///
/// fn read_keyboard_events(input: &mut Input) -> Result {
/// loop {
@ -55,7 +55,7 @@ impl Input {
/// let mut events = [input.wait_for_key_event().unwrap()];
/// boot::wait_for_event(&mut events).discard_errdata()?;
///
/// let u_key = Char16::try_from('u').unwrap();
/// let u_key = char16!('u');
/// match input.read_key()? {
/// // Example of handling a printable key: print a message when
/// // the 'u' key is pressed.