use std::{
cell::UnsafeCell,
ffi::{OsStr, OsString},
fmt::{self, Write},
hash::Hasher,
marker::PhantomData,
mem::MaybeUninit,
panic::{RefUnwindSafe, UnwindSafe},
path::Path,
sync::Once,
};
#[cfg(feature = "parallel")]
use std::cell::Cell;
use crate::{Error, ErrorKind};
pub(super) struct JoinOsStrs<'a, T> {
pub(super) slice: &'a [T],
pub(super) delimiter: char,
}
impl<T> fmt::Display for JoinOsStrs<'_, T>
where
T: AsRef<OsStr>,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let len = self.slice.len();
for (index, os_str) in self.slice.iter().enumerate() {
write!(f, "{}", Path::new(os_str).display())?;
if index + 1 < len {
f.write_char(self.delimiter)?;
}
}
Ok(())
}
}
pub(super) struct OptionOsStrDisplay<T>(pub(super) Option<T>);
impl<T> fmt::Display for OptionOsStrDisplay<T>
where
T: AsRef<OsStr>,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(os_str) = self.0.as_ref() {
write!(f, "Some({})", Path::new(os_str).display())
} else {
f.write_str("None")
}
}
}
pub(crate) struct OnceLock<T> {
once: Once,
value: UnsafeCell<MaybeUninit<T>>,
_marker: PhantomData<T>,
}
impl<T> Default for OnceLock<T> {
fn default() -> Self {
Self::new()
}
}
impl<T> OnceLock<T> {
pub(crate) const fn new() -> Self {
Self {
once: Once::new(),
value: UnsafeCell::new(MaybeUninit::uninit()),
_marker: PhantomData,
}
}
#[inline]
fn is_initialized(&self) -> bool {
self.once.is_completed()
}
unsafe fn get_unchecked(&self) -> &T {
debug_assert!(self.is_initialized());
#[allow(clippy::needless_borrow)]
#[allow(unused_unsafe)]
unsafe {
(&*self.value.get()).assume_init_ref()
}
}
pub(crate) fn get_or_init(&self, f: impl FnOnce() -> T) -> &T {
self.once.call_once(|| {
unsafe { &mut *self.value.get() }.write(f());
});
unsafe { self.get_unchecked() }
}
pub(crate) fn get(&self) -> Option<&T> {
self.is_initialized().then(|| {
unsafe { self.get_unchecked() }
})
}
}
impl<T> From<T> for OnceLock<T> {
fn from(value: T) -> Self {
let cell = Self::new();
cell.get_or_init(|| value);
cell
}
}
impl<T: Clone> Clone for OnceLock<T> {
fn clone(&self) -> Self {
let cell = Self::new();
if let Some(value) = self.get() {
cell.get_or_init(|| value.clone());
}
cell
}
}
impl<T: fmt::Debug> fmt::Debug for OnceLock<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut d = f.debug_tuple("OnceLock");
match self.get() {
Some(v) => d.field(v),
None => d.field(&format_args!("<uninit>")),
};
d.finish()
}
}
unsafe impl<T: Sync + Send> Sync for OnceLock<T> {}
unsafe impl<T: Send> Send for OnceLock<T> {}
impl<T: RefUnwindSafe + UnwindSafe> RefUnwindSafe for OnceLock<T> {}
impl<T: UnwindSafe> UnwindSafe for OnceLock<T> {}
impl<T> Drop for OnceLock<T> {
#[inline]
fn drop(&mut self) {
if self.once.is_completed() {
unsafe { self.value.get_mut().assume_init_drop() };
}
}
}
#[allow(clippy::disallowed_methods)] pub(crate) fn cargo_env_var_os(key: &str) -> Option<OsString> {
std::env::var_os(key)
}
pub(crate) fn cargo_env_var(key: &str) -> Result<String, Error> {
if let Some(value) = cargo_env_var_os(key) {
match value.into_string() {
Ok(value) => Ok(value),
Err(value) => Err(Error::new(
ErrorKind::EnvVarNotFound,
format!("environment variable {key} is not valid utf-8: {value:?}"),
)),
}
} else {
Err(Error::new(
ErrorKind::EnvVarNotFound,
format!("environment variable {key} not defined"),
))
}
}
pub(crate) trait IgnoreAsciiCase {
fn contains_ignore_ascii_case(&self, needle: &str) -> bool;
fn starts_with_ignore_ascii_case(&self, prefix: &str) -> bool;
fn ends_with_ignore_ascii_case(&self, suffix: &str) -> bool;
}
impl IgnoreAsciiCase for str {
fn contains_ignore_ascii_case(&self, needle: &str) -> bool {
needle.is_empty()
|| self
.as_bytes()
.windows(needle.len())
.any(|window| window.eq_ignore_ascii_case(needle.as_bytes()))
}
fn starts_with_ignore_ascii_case(&self, prefix: &str) -> bool {
self.as_bytes()
.get(..prefix.len())
.map_or(false, |start| start.eq_ignore_ascii_case(prefix.as_bytes()))
}
fn ends_with_ignore_ascii_case(&self, suffix: &str) -> bool {
self.len().checked_sub(suffix.len()).map_or(false, |start| {
self.as_bytes()[start..].eq_ignore_ascii_case(suffix.as_bytes())
})
}
}
#[derive(Default)]
pub(crate) struct HashRecorder(Vec<u8>);
impl HashRecorder {
pub(crate) fn into_bytes(self) -> Box<[u8]> {
self.0.into_boxed_slice()
}
}
impl Hasher for HashRecorder {
fn write(&mut self, bytes: &[u8]) {
self.0.extend_from_slice(bytes);
}
fn finish(&self) -> u64 {
unreachable!("only records what is written")
}
}
#[cfg(feature = "parallel")]
pub(crate) fn cell_modify<T, F, R>(cell: &Cell<T>, f: F) -> R
where
T: Default,
F: FnOnce(&mut T) -> R,
{
let mut value = cell.take();
let r = f(&mut value);
cell.set(value);
r
}
#[cfg(feature = "parallel")]
pub(crate) fn cell_push<T>(cell: &Cell<Vec<T>>, element: T) {
cell_modify(cell, |vec| vec.push(element));
}
#[cfg(test)]
mod tests {
use super::IgnoreAsciiCase;
#[test]
fn ignore_ascii_case() {
assert!("x86_64-w64-mingw32-CLANG".contains_ignore_ascii_case("mingw32-clang"));
assert!("Clang-CL.exe".contains_ignore_ascii_case("clang-cl"));
assert!("zig".contains_ignore_ascii_case(""));
assert!(!"zi".contains_ignore_ascii_case("zig"));
assert!(!"clang".contains_ignore_ascii_case("clang-cl"));
assert!("LLVM-ML64".starts_with_ignore_ascii_case("llvm-ml"));
assert!(!"llvm-m".starts_with_ignore_ascii_case("llvm-ml"));
assert!(!"my-llvm-ml".starts_with_ignore_ascii_case("llvm-ml"));
assert!("x86_64-w64-mingw32-Clang++".ends_with_ignore_ascii_case("-mingw32-clang++"));
assert!("CL".ends_with_ignore_ascii_case("cl"));
assert!(!"l".ends_with_ignore_ascii_case("cl"));
assert!(!"cl.exe".ends_with_ignore_ascii_case("cl"));
assert!("Ünïcode-CLANG".contains_ignore_ascii_case("Ünïcode-clang"));
assert!(!"ÜNÏCODE-clang".contains_ignore_ascii_case("ünïcode-clang"));
assert!(!"\u{212A}ache".starts_with_ignore_ascii_case("kache"));
}
}