use core::{fmt, hash}; use crate::racy_lock::RacyLock; use crate::FastHashSet; /// A case-sensitive set of strings, /// for use with [`Namer`][crate::proc::Namer] to avoid collisions with keywords and other reserved /// identifiers. /// /// This is currently implemented as a hash table. /// Future versions of Naga may change the implementation based on speed and code size /// considerations. #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct KeywordSet(FastHashSet<&'static str>); impl KeywordSet { /// Returns a new mutable empty set. pub fn new() -> Self { Self::default() } /// Returns a reference to the empty set. pub fn empty() -> &'static Self { static EMPTY: RacyLock = RacyLock::new(Default::default); &EMPTY } /// Returns whether the set contains the given string. #[inline] pub fn contains(&self, identifier: &str) -> bool { self.0.contains(identifier) } } impl Default for &'static KeywordSet { fn default() -> Self { KeywordSet::empty() } } impl FromIterator<&'static str> for KeywordSet { fn from_iter>(iter: T) -> Self { Self(iter.into_iter().collect()) } } /// Accepts double references so that `KeywordSet::from_iter(&["foo"])` works. impl<'a> FromIterator<&'a &'static str> for KeywordSet { fn from_iter>(iter: T) -> Self { Self::from_iter(iter.into_iter().copied()) } } impl Extend<&'static str> for KeywordSet { #[expect( clippy::useless_conversion, reason = "doing .into_iter() sooner reduces distinct monomorphizations" )] fn extend>(&mut self, iter: T) { self.0.extend(iter.into_iter()) } } /// Accepts double references so that `.extend(&["foo"])` works. impl<'a> Extend<&'a &'static str> for KeywordSet { fn extend>(&mut self, iter: T) { self.extend(iter.into_iter().copied()) } } /// A case-insensitive, ASCII-only set of strings, /// for use with [`Namer`][crate::proc::Namer] to avoid collisions with keywords and other reserved /// identifiers. /// /// This is currently implemented as a hash table. /// Future versions of Naga may change the implementation based on speed and code size /// considerations. #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct CaseInsensitiveKeywordSet(FastHashSet>); impl CaseInsensitiveKeywordSet { /// Returns a new mutable empty set. pub fn new() -> Self { Self::default() } /// Returns a reference to the empty set. pub fn empty() -> &'static Self { static EMPTY: RacyLock = RacyLock::new(Default::default); &EMPTY } /// Returns whether the set contains the given string, with comparison /// by [`str::eq_ignore_ascii_case()`]. #[inline] pub fn contains(&self, identifier: &str) -> bool { self.0.contains(&AsciiUniCase(identifier)) } } impl Default for &'static CaseInsensitiveKeywordSet { fn default() -> Self { CaseInsensitiveKeywordSet::empty() } } impl FromIterator<&'static str> for CaseInsensitiveKeywordSet { fn from_iter>(iter: T) -> Self { Self( iter.into_iter() .inspect(debug_assert_ascii) .map(AsciiUniCase) .collect(), ) } } /// Accepts double references so that `CaseInsensitiveKeywordSet::from_iter(&["foo"])` works. impl<'a> FromIterator<&'a &'static str> for CaseInsensitiveKeywordSet { fn from_iter>(iter: T) -> Self { Self::from_iter(iter.into_iter().copied()) } } impl Extend<&'static str> for CaseInsensitiveKeywordSet { fn extend>(&mut self, iter: T) { self.0.extend( iter.into_iter() .inspect(debug_assert_ascii) .map(AsciiUniCase), ) } } /// Accepts double references so that `.extend(&["foo"])` works. impl<'a> Extend<&'a &'static str> for CaseInsensitiveKeywordSet { fn extend>(&mut self, iter: T) { self.extend(iter.into_iter().copied()) } } /// A string wrapper type with an ascii case insensitive Eq and Hash impl #[derive(Clone, Copy)] struct AsciiUniCase + ?Sized>(S); impl> fmt::Debug for AsciiUniCase { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.as_ref().fmt(f) } } impl> PartialEq for AsciiUniCase { #[inline] fn eq(&self, other: &Self) -> bool { self.0.as_ref().eq_ignore_ascii_case(other.0.as_ref()) } } impl> Eq for AsciiUniCase {} impl> hash::Hash for AsciiUniCase { #[inline] fn hash(&self, hasher: &mut H) { for byte in self .0 .as_ref() .as_bytes() .iter() .map(|b| b.to_ascii_lowercase()) { hasher.write_u8(byte); } } } fn debug_assert_ascii(s: &&'static str) { debug_assert!(s.is_ascii(), "{s:?} not ASCII") }