Skip to content

Commit 312ddbf

Browse files
committed
highlighter: Represent Highlight as a non-max u32
`u32::MAX` is reserved for the null pointer space optimization: size_of::<Option<Highlight>>() == size_of::<Highlight>() Unfortunately this kind of type is not available in std so we must roll our own with bit-wise xor, which is an involution when used with a constant like `u32::MAX`. We could expose an interface like `NonZeroU32` and have `new` return an `Option<Self>`. It is unlikely that a caller would ever pass `u32::MAX` though so an assertion seems appropriate instead.
1 parent f4ffa7c commit 312ddbf

File tree

4 files changed

+29
-15
lines changed

4 files changed

+29
-15
lines changed

highlighter/src/config.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ impl LanguageConfig {
5151
})
5252
}
5353

54-
pub fn configure(&self, mut f: impl FnMut(&str) -> Highlight) {
54+
pub fn configure(&self, mut f: impl FnMut(&str) -> Option<Highlight>) {
5555
self.highlight_query.configure(&mut f);
5656
self.injection_query.configure(&mut f);
5757
}

highlighter/src/highlighter.rs

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::borrow::Cow;
22
use std::mem::replace;
3+
use std::num::NonZeroU32;
34
use std::ops::RangeBounds;
45
use std::path::Path;
56
use std::slice;
@@ -24,7 +25,7 @@ use tree_sitter::{Pattern, QueryMatch};
2425
#[derive(Debug)]
2526
pub struct HighlightQuery {
2627
pub query: Query,
27-
highlight_indices: ArcSwap<Vec<Highlight>>,
28+
highlight_indices: ArcSwap<Vec<Option<Highlight>>>,
2829
#[allow(dead_code)]
2930
/// Patterns that do not match when the node is a local.
3031
non_local_patterns: HashSet<Pattern>,
@@ -86,10 +87,7 @@ impl HighlightQuery {
8687
}
8788

8889
Ok(Self {
89-
highlight_indices: ArcSwap::from_pointee(vec![
90-
Highlight::NONE;
91-
query.num_captures() as usize
92-
]),
90+
highlight_indices: ArcSwap::from_pointee(vec![None; query.num_captures() as usize]),
9391
non_local_patterns,
9492
local_reference_capture: query.get_capture("local.reference"),
9593
query,
@@ -112,7 +110,7 @@ impl HighlightQuery {
112110
/// When highlighting, results are returned as `Highlight` values, configured by this function.
113111
/// The meaning of these indices is up to the user of the implementation. The highlighter
114112
/// treats the indices as entirely opaque.
115-
pub(crate) fn configure(&self, f: &mut impl FnMut(&str) -> Highlight) {
113+
pub(crate) fn configure(&self, f: &mut impl FnMut(&str) -> Option<Highlight>) {
116114
let highlight_indices = self
117115
.query
118116
.captures()
@@ -123,11 +121,26 @@ impl HighlightQuery {
123121
}
124122

125123
/// Indicates which highlight should be applied to a region of source code.
124+
///
125+
/// This type is represented as a non-max u32 - a u32 which cannot be `u32::MAX`. This is checked
126+
/// at runtime with assertions in `Highlight::new`.
126127
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
127-
pub struct Highlight(pub u32);
128+
pub struct Highlight(NonZeroU32);
128129

129130
impl Highlight {
130-
pub const NONE: Highlight = Highlight(u32::MAX);
131+
pub const fn new(inner: u32) -> Self {
132+
assert!(inner != u32::MAX);
133+
// SAFETY: must be non-zero because `inner` is not `u32::MAX`.
134+
Self(unsafe { NonZeroU32::new_unchecked(inner ^ u32::MAX) })
135+
}
136+
137+
const fn get(&self) -> u32 {
138+
self.0.get() ^ u32::MAX
139+
}
140+
141+
pub const fn idx(&self) -> usize {
142+
self.get() as usize
143+
}
131144
}
132145

133146
#[derive(Debug)]
@@ -340,7 +353,6 @@ impl<'a, 'tree: 'a, Loader: LanguageLoader> Highlighter<'a, 'tree, Loader> {
340353
.load()
341354
.get(&capture)
342355
.copied()
343-
.unwrap_or(Highlight::NONE)
344356
} else {
345357
config.highlight_query.highlight_indices.load()[node.capture.idx()]
346358
};
@@ -355,7 +367,7 @@ impl<'a, 'tree: 'a, Loader: LanguageLoader> Highlighter<'a, 'tree, Loader> {
355367
{
356368
self.active_highlights.pop();
357369
}
358-
if highlight != Highlight::NONE {
370+
if let Some(highlight) = highlight {
359371
self.active_highlights.push(HighlightedNode {
360372
end: range.end,
361373
highlight,

highlighter/src/injections_query.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,13 +196,13 @@ impl InjectionsQuery {
196196
})
197197
}
198198

199-
pub(crate) fn configure(&self, f: &mut impl FnMut(&str) -> Highlight) {
199+
pub(crate) fn configure(&self, f: &mut impl FnMut(&str) -> Option<Highlight>) {
200200
let local_definition_captures = self
201201
.local_query
202202
.captures()
203203
.filter_map(|(capture, name)| {
204204
let suffix = name.strip_prefix("local.definition.")?;
205-
Some((capture, f(suffix)))
205+
Some((capture, f(suffix)?))
206206
})
207207
.collect();
208208
self.local_definition_captures

highlighter/src/tests.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,9 @@ impl LanguageLoader for TestLanguageLoader {
179179
&self.overwrites[lang.idx()],
180180
);
181181
let mut theme = self.test_theme.borrow_mut();
182-
config.configure(|scope| Highlight(theme.insert_full(scope.to_owned()).0 as u32));
182+
config.configure(|scope| {
183+
Some(Highlight::new(theme.insert_full(scope.to_owned()).0 as u32))
184+
});
183185
config
184186
});
185187
Some(config)
@@ -206,7 +208,7 @@ fn highlight_fixture(loader: &TestLanguageLoader, fixture: impl AsRef<Path>) {
206208
"// ",
207209
lang,
208210
loader,
209-
|highlight| loader.test_theme.borrow()[highlight.0 as usize].clone(),
211+
|highlight| loader.test_theme.borrow()[highlight.idx()].clone(),
210212
|_| ..,
211213
)
212214
}

0 commit comments

Comments
 (0)