feat(rust)!: use ops::ControlFlow as parse and query progress return value

Instead of returning an undocumented boolean flag, use a
core::ops::ControlFlow object. At the expense of being a bit more
verbose, this is a type that should be self-explanatory in the context
of a callback, as an indication of whether to continue processing or
stop.
This commit is contained in:
Daniel Müller 2025-06-16 10:31:25 -07:00 committed by Amaan Qureshi
parent 57e2f41f42
commit 937dcf5fd1
6 changed files with 120 additions and 47 deletions

View file

@ -1,6 +1,7 @@
use std::{
fmt, fs,
io::{self, Write},
ops::ControlFlow,
path::{Path, PathBuf},
sync::atomic::{AtomicUsize, Ordering},
time::{Duration, Instant},
@ -357,15 +358,15 @@ pub fn parse_file_at_path(
let progress_callback = &mut |_: &ParseState| {
if let Some(cancellation_flag) = opts.cancellation_flag {
if cancellation_flag.load(Ordering::SeqCst) != 0 {
return true;
return ControlFlow::Break(());
}
}
if opts.timeout > 0 && start_time.elapsed().as_micros() > opts.timeout as u128 {
return true;
return ControlFlow::Break(());
}
false
ControlFlow::Continue(())
};
let parse_opts = ParseOptions::new().progress_callback(progress_callback);

View file

@ -1,4 +1,5 @@
use std::{
ops::ControlFlow,
sync::atomic::{AtomicUsize, Ordering},
thread, time,
};
@ -699,7 +700,13 @@ fn test_parsing_on_multiple_threads() {
fn test_parsing_cancelled_by_another_thread() {
let cancellation_flag = std::sync::Arc::new(AtomicUsize::new(0));
let flag = cancellation_flag.clone();
let callback = &mut |_: &ParseState| cancellation_flag.load(Ordering::SeqCst) != 0;
let callback = &mut |_: &ParseState| {
if cancellation_flag.load(Ordering::SeqCst) != 0 {
ControlFlow::Break(())
} else {
ControlFlow::Continue(())
}
};
let mut parser = Parser::new();
parser.set_language(&get_language("javascript")).unwrap();
@ -764,9 +771,13 @@ fn test_parsing_with_a_timeout() {
}
},
None,
Some(
ParseOptions::new().progress_callback(&mut |_| start_time.elapsed().as_micros() > 1000),
),
Some(ParseOptions::new().progress_callback(&mut |_| {
if start_time.elapsed().as_micros() > 1000 {
ControlFlow::Break(())
} else {
ControlFlow::Continue(())
}
})),
);
assert!(tree.is_none());
assert!(start_time.elapsed().as_micros() < 2000);
@ -782,9 +793,13 @@ fn test_parsing_with_a_timeout() {
}
},
None,
Some(
ParseOptions::new().progress_callback(&mut |_| start_time.elapsed().as_micros() > 5000),
),
Some(ParseOptions::new().progress_callback(&mut |_| {
if start_time.elapsed().as_micros() > 5000 {
ControlFlow::Break(())
} else {
ControlFlow::Continue(())
}
})),
);
assert!(tree.is_none());
assert!(start_time.elapsed().as_micros() > 100);
@ -822,7 +837,13 @@ fn test_parsing_with_a_timeout_and_a_reset() {
}
},
None,
Some(ParseOptions::new().progress_callback(&mut |_| start_time.elapsed().as_micros() > 5)),
Some(ParseOptions::new().progress_callback(&mut |_| {
if start_time.elapsed().as_micros() > 5 {
ControlFlow::Break(())
} else {
ControlFlow::Continue(())
}
})),
);
assert!(tree.is_none());
@ -853,7 +874,13 @@ fn test_parsing_with_a_timeout_and_a_reset() {
}
},
None,
Some(ParseOptions::new().progress_callback(&mut |_| start_time.elapsed().as_micros() > 5)),
Some(ParseOptions::new().progress_callback(&mut |_| {
if start_time.elapsed().as_micros() > 5 {
ControlFlow::Break(())
} else {
ControlFlow::Continue(())
}
})),
);
assert!(tree.is_none());
@ -893,10 +920,13 @@ fn test_parsing_with_a_timeout_and_implicit_reset() {
}
},
None,
Some(
ParseOptions::new()
.progress_callback(&mut |_| start_time.elapsed().as_micros() > 5),
),
Some(ParseOptions::new().progress_callback(&mut |_| {
if start_time.elapsed().as_micros() > 5 {
ControlFlow::Break(())
} else {
ControlFlow::Continue(())
}
})),
);
assert!(tree.is_none());
@ -937,10 +967,13 @@ fn test_parsing_with_timeout_and_no_completion() {
}
},
None,
Some(
ParseOptions::new()
.progress_callback(&mut |_| start_time.elapsed().as_micros() > 5),
),
Some(ParseOptions::new().progress_callback(&mut |_| {
if start_time.elapsed().as_micros() > 5 {
ControlFlow::Break(())
} else {
ControlFlow::Continue(())
}
})),
);
assert!(tree.is_none());
@ -979,10 +1012,10 @@ fn test_parsing_with_timeout_during_balancing() {
// are in the balancing phase.
if state.current_byte_offset() != current_byte_offset {
current_byte_offset = state.current_byte_offset();
false
ControlFlow::Continue(())
} else {
in_balancing = true;
true
ControlFlow::Break(())
}
})),
);
@ -1004,10 +1037,10 @@ fn test_parsing_with_timeout_during_balancing() {
Some(ParseOptions::new().progress_callback(&mut |state| {
if state.current_byte_offset() != current_byte_offset {
current_byte_offset = state.current_byte_offset();
false
ControlFlow::Continue(())
} else {
in_balancing = true;
true
ControlFlow::Break(())
}
})),
);
@ -1031,7 +1064,7 @@ fn test_parsing_with_timeout_during_balancing() {
// Because we've already finished parsing, we should only be resuming the
// balancing phase.
assert!(state.current_byte_offset() == current_byte_offset);
false
ControlFlow::Continue(())
})),
)
.unwrap();
@ -1057,7 +1090,11 @@ fn test_parsing_with_timeout_when_error_detected() {
None,
Some(ParseOptions::new().progress_callback(&mut |state| {
offset = state.current_byte_offset();
state.has_error()
if state.has_error() {
ControlFlow::Break(())
} else {
ControlFlow::Continue(())
}
})),
);
@ -1737,7 +1774,7 @@ fn test_parsing_by_halting_at_offset() {
None,
Some(ParseOptions::new().progress_callback(&mut |p| {
seen_byte_offsets.push(p.current_byte_offset());
false
ControlFlow::Continue(())
})),
)
.unwrap();

View file

@ -1,4 +1,4 @@
use std::{env, fmt::Write, sync::LazyLock};
use std::{env, fmt::Write, ops::ControlFlow, sync::LazyLock};
use indoc::indoc;
use rand::{prelude::StdRng, SeedableRng};
@ -5446,8 +5446,13 @@ fn test_query_execution_with_timeout() {
&query,
tree.root_node(),
source_code.as_bytes(),
QueryCursorOptions::new()
.progress_callback(&mut |_| start_time.elapsed().as_micros() > 1000),
QueryCursorOptions::new().progress_callback(&mut |_| {
if start_time.elapsed().as_micros() > 1000 {
ControlFlow::Break(())
} else {
ControlFlow::Continue(())
}
}),
)
.count();
assert!(matches < 1000);

View file

@ -7,7 +7,8 @@ use std::{
iter,
marker::PhantomData,
mem::{self, MaybeUninit},
ops, str,
ops::{self, ControlFlow},
str,
sync::{
atomic::{AtomicUsize, Ordering},
LazyLock,
@ -538,9 +539,13 @@ impl<'a> HighlightIterLayer<'a> {
None,
Some(ParseOptions::new().progress_callback(&mut |_| {
if let Some(cancellation_flag) = cancellation_flag {
cancellation_flag.load(Ordering::SeqCst) != 0
if cancellation_flag.load(Ordering::SeqCst) != 0 {
ControlFlow::Break(())
} else {
ControlFlow::Continue(())
}
} else {
false
ControlFlow::Continue(())
}
})),
)

View file

@ -7,7 +7,7 @@ use std::{
collections::HashMap,
ffi::{CStr, CString},
mem,
ops::Range,
ops::{ControlFlow, Range},
os::raw::c_char,
str,
sync::atomic::{AtomicUsize, Ordering},
@ -301,9 +301,13 @@ impl TagsContext {
None,
Some(ParseOptions::new().progress_callback(&mut |_| {
if let Some(cancellation_flag) = cancellation_flag {
cancellation_flag.load(Ordering::SeqCst) != 0
if cancellation_flag.load(Ordering::SeqCst) != 0 {
ControlFlow::Break(())
} else {
ControlFlow::Continue(())
}
} else {
false
ControlFlow::Continue(())
}
})),
)

View file

@ -16,7 +16,7 @@ use core::{
marker::PhantomData,
mem::MaybeUninit,
num::NonZeroU16,
ops::{self, Deref},
ops::{self, ControlFlow, Deref},
ptr::{self, NonNull},
slice, str,
sync::atomic::AtomicUsize,
@ -177,7 +177,10 @@ impl<'a> ParseOptions<'a> {
}
#[must_use]
pub fn progress_callback<F: FnMut(&ParseState) -> bool>(mut self, callback: &'a mut F) -> Self {
pub fn progress_callback<F: FnMut(&ParseState) -> ControlFlow<()>>(
mut self,
callback: &'a mut F,
) -> Self {
self.progress_callback = Some(callback);
self
}
@ -195,7 +198,7 @@ impl<'a> QueryCursorOptions<'a> {
}
#[must_use]
pub fn progress_callback<F: FnMut(&QueryCursorState) -> bool>(
pub fn progress_callback<F: FnMut(&QueryCursorState) -> ControlFlow<()>>(
mut self,
callback: &'a mut F,
) -> Self {
@ -232,10 +235,10 @@ type FieldId = NonZeroU16;
type Logger<'a> = Box<dyn FnMut(LogType, &str) + 'a>;
/// A callback that receives the parse state during parsing.
type ParseProgressCallback<'a> = &'a mut dyn FnMut(&ParseState) -> bool;
type ParseProgressCallback<'a> = &'a mut dyn FnMut(&ParseState) -> ControlFlow<()>;
/// A callback that receives the query state during query execution.
type QueryProgressCallback<'a> = &'a mut dyn FnMut(&QueryCursorState) -> bool;
type QueryProgressCallback<'a> = &'a mut dyn FnMut(&QueryCursorState) -> ControlFlow<()>;
pub trait Decode {
/// A callback that decodes the next code point from the input slice. It should return the code
@ -869,7 +872,10 @@ impl Parser {
.cast::<ParseProgressCallback>()
.as_mut()
.unwrap();
callback(&ParseState::from_raw(state))
match callback(&ParseState::from_raw(state)) {
ControlFlow::Continue(()) => false,
ControlFlow::Break(()) => true,
}
}
// This C function is passed to Tree-sitter as the input callback.
@ -1001,7 +1007,10 @@ impl Parser {
.cast::<ParseProgressCallback>()
.as_mut()
.unwrap();
callback(&ParseState::from_raw(state))
match callback(&ParseState::from_raw(state)) {
ControlFlow::Continue(()) => false,
ControlFlow::Break(()) => true,
}
}
// This C function is passed to Tree-sitter as the input callback.
@ -1118,7 +1127,10 @@ impl Parser {
.cast::<ParseProgressCallback>()
.as_mut()
.unwrap();
callback(&ParseState::from_raw(state))
match callback(&ParseState::from_raw(state)) {
ControlFlow::Continue(()) => false,
ControlFlow::Break(()) => true,
}
}
// This C function is passed to Tree-sitter as the input callback.
@ -1218,7 +1230,10 @@ impl Parser {
.cast::<ParseProgressCallback>()
.as_mut()
.unwrap();
callback(&ParseState::from_raw(state))
match callback(&ParseState::from_raw(state)) {
ControlFlow::Continue(()) => false,
ControlFlow::Break(()) => true,
}
}
// At compile time, create a C-compatible callback that calls the custom `decode` method.
@ -3103,7 +3118,10 @@ impl QueryCursor {
.cast::<QueryProgressCallback>()
.as_mut()
.unwrap();
(callback)(&QueryCursorState::from_raw(state))
match callback(&QueryCursorState::from_raw(state)) {
ControlFlow::Continue(()) => false,
ControlFlow::Break(()) => true,
}
}
let query_options = options.progress_callback.map(|cb| {
@ -3189,7 +3207,10 @@ impl QueryCursor {
.cast::<QueryProgressCallback>()
.as_mut()
.unwrap();
(callback)(&QueryCursorState::from_raw(state))
match callback(&QueryCursorState::from_raw(state)) {
ControlFlow::Continue(()) => false,
ControlFlow::Break(()) => true,
}
}
let query_options = options.progress_callback.map(|cb| {