naga/proc/
keyword_set.rs

1use core::{fmt, hash};
2
3use crate::racy_lock::RacyLock;
4use crate::FastHashSet;
5
6/// A case-sensitive set of strings,
7/// for use with [`Namer`][crate::proc::Namer] to avoid collisions with keywords and other reserved
8/// identifiers.
9///
10/// This is currently implemented as a hash table.
11/// Future versions of Naga may change the implementation based on speed and code size
12/// considerations.
13#[derive(Clone, Debug, Default, Eq, PartialEq)]
14pub struct KeywordSet(FastHashSet<&'static str>);
15
16impl KeywordSet {
17    /// Returns a new mutable empty set.
18    pub fn new() -> Self {
19        Self::default()
20    }
21
22    /// Returns a reference to the empty set.
23    pub fn empty() -> &'static Self {
24        static EMPTY: RacyLock<KeywordSet> = RacyLock::new(Default::default);
25        &EMPTY
26    }
27
28    /// Returns whether the set contains the given string.
29    #[inline]
30    pub fn contains(&self, identifier: &str) -> bool {
31        self.0.contains(identifier)
32    }
33}
34
35impl Default for &'static KeywordSet {
36    fn default() -> Self {
37        KeywordSet::empty()
38    }
39}
40
41impl FromIterator<&'static str> for KeywordSet {
42    fn from_iter<T: IntoIterator<Item = &'static str>>(iter: T) -> Self {
43        Self(iter.into_iter().collect())
44    }
45}
46
47/// Accepts double references so that `KeywordSet::from_iter(&["foo"])` works.
48impl<'a> FromIterator<&'a &'static str> for KeywordSet {
49    fn from_iter<T: IntoIterator<Item = &'a &'static str>>(iter: T) -> Self {
50        Self::from_iter(iter.into_iter().copied())
51    }
52}
53
54impl Extend<&'static str> for KeywordSet {
55    #[expect(
56        clippy::useless_conversion,
57        reason = "doing .into_iter() sooner reduces distinct monomorphizations"
58    )]
59    fn extend<T: IntoIterator<Item = &'static str>>(&mut self, iter: T) {
60        self.0.extend(iter.into_iter())
61    }
62}
63
64/// Accepts double references so that `.extend(&["foo"])` works.
65impl<'a> Extend<&'a &'static str> for KeywordSet {
66    fn extend<T: IntoIterator<Item = &'a &'static str>>(&mut self, iter: T) {
67        self.extend(iter.into_iter().copied())
68    }
69}
70
71/// A case-insensitive, ASCII-only set of strings,
72/// for use with [`Namer`][crate::proc::Namer] to avoid collisions with keywords and other reserved
73/// identifiers.
74///
75/// This is currently implemented as a hash table.
76/// Future versions of Naga may change the implementation based on speed and code size
77/// considerations.
78#[derive(Clone, Debug, Default, Eq, PartialEq)]
79pub struct CaseInsensitiveKeywordSet(FastHashSet<AsciiUniCase<&'static str>>);
80
81impl CaseInsensitiveKeywordSet {
82    /// Returns a new mutable empty set.
83    pub fn new() -> Self {
84        Self::default()
85    }
86
87    /// Returns a reference to the empty set.
88    pub fn empty() -> &'static Self {
89        static EMPTY: RacyLock<CaseInsensitiveKeywordSet> = RacyLock::new(Default::default);
90        &EMPTY
91    }
92
93    /// Returns whether the set contains the given string, with comparison
94    /// by [`str::eq_ignore_ascii_case()`].
95    #[inline]
96    pub fn contains(&self, identifier: &str) -> bool {
97        self.0.contains(&AsciiUniCase(identifier))
98    }
99}
100
101impl Default for &'static CaseInsensitiveKeywordSet {
102    fn default() -> Self {
103        CaseInsensitiveKeywordSet::empty()
104    }
105}
106
107impl FromIterator<&'static str> for CaseInsensitiveKeywordSet {
108    fn from_iter<T: IntoIterator<Item = &'static str>>(iter: T) -> Self {
109        Self(
110            iter.into_iter()
111                .inspect(debug_assert_ascii)
112                .map(AsciiUniCase)
113                .collect(),
114        )
115    }
116}
117
118/// Accepts double references so that `CaseInsensitiveKeywordSet::from_iter(&["foo"])` works.
119impl<'a> FromIterator<&'a &'static str> for CaseInsensitiveKeywordSet {
120    fn from_iter<T: IntoIterator<Item = &'a &'static str>>(iter: T) -> Self {
121        Self::from_iter(iter.into_iter().copied())
122    }
123}
124
125impl Extend<&'static str> for CaseInsensitiveKeywordSet {
126    fn extend<T: IntoIterator<Item = &'static str>>(&mut self, iter: T) {
127        self.0.extend(
128            iter.into_iter()
129                .inspect(debug_assert_ascii)
130                .map(AsciiUniCase),
131        )
132    }
133}
134
135/// Accepts double references so that `.extend(&["foo"])` works.
136impl<'a> Extend<&'a &'static str> for CaseInsensitiveKeywordSet {
137    fn extend<T: IntoIterator<Item = &'a &'static str>>(&mut self, iter: T) {
138        self.extend(iter.into_iter().copied())
139    }
140}
141
142/// A string wrapper type with an ascii case insensitive Eq and Hash impl
143#[derive(Clone, Copy)]
144struct AsciiUniCase<S: AsRef<str> + ?Sized>(S);
145
146impl<S: ?Sized + AsRef<str>> fmt::Debug for AsciiUniCase<S> {
147    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148        self.0.as_ref().fmt(f)
149    }
150}
151
152impl<S: AsRef<str>> PartialEq<Self> for AsciiUniCase<S> {
153    #[inline]
154    fn eq(&self, other: &Self) -> bool {
155        self.0.as_ref().eq_ignore_ascii_case(other.0.as_ref())
156    }
157}
158
159impl<S: AsRef<str>> Eq for AsciiUniCase<S> {}
160
161impl<S: AsRef<str>> hash::Hash for AsciiUniCase<S> {
162    #[inline]
163    fn hash<H: hash::Hasher>(&self, hasher: &mut H) {
164        for byte in self
165            .0
166            .as_ref()
167            .as_bytes()
168            .iter()
169            .map(|b| b.to_ascii_lowercase())
170        {
171            hasher.write_u8(byte);
172        }
173    }
174}
175
176fn debug_assert_ascii(s: &&'static str) {
177    debug_assert!(s.is_ascii(), "{s:?} not ASCII")
178}