mirror of
https://github.com/tree-sitter/tree-sitter.git
synced 2026-09-10 07:26:23 -04:00
fix(rust)!: make lookahead accessors fallible and iteration fused
`current_symbol` and `current_symbol_name` return an `Option` (`None` when the iterator is not positioned on a symbol). Change `iter_names`'s item from `&'static str` to `&str`.
This commit is contained in:
parent
7bf4a10995
commit
081929092f
|
|
@ -31,11 +31,12 @@ fn test_lookahead_iterator() {
|
|||
let mut lookahead = language.lookahead_iterator(next_state).unwrap();
|
||||
assert_eq!(*lookahead.language(), language);
|
||||
assert!(lookahead.iter_names().eq(expected_symbols));
|
||||
assert_eq!(lookahead.iter_names().count(), 0);
|
||||
|
||||
lookahead.reset_state(next_state);
|
||||
assert!(lookahead.reset_state(next_state));
|
||||
assert!(lookahead.iter_names().eq(expected_symbols));
|
||||
|
||||
lookahead.reset(&language, next_state);
|
||||
assert!(lookahead.reset(&language, next_state));
|
||||
assert!(
|
||||
lookahead
|
||||
.map(|s| language.node_kind_for_id(s).unwrap())
|
||||
|
|
@ -66,6 +67,33 @@ fn test_lookahead_iterator_modifiable_only_by_mut() {
|
|||
let _ = names.next();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lookahead_iterator_exhaustion() {
|
||||
let language = get_language("json");
|
||||
|
||||
for state in 0..language.parse_state_count() {
|
||||
let state = u16::try_from(state).unwrap();
|
||||
let mut lookahead = language.lookahead_iterator(state).unwrap();
|
||||
|
||||
// A fresh iterator is not positioned on a symbol.
|
||||
assert_eq!(lookahead.current_symbol(), None);
|
||||
assert_eq!(lookahead.current_symbol_name(), None);
|
||||
|
||||
let count = lookahead.by_ref().count();
|
||||
|
||||
// An exhausted iterator is not positioned on a symbol, and stays exhausted.
|
||||
assert_eq!(lookahead.current_symbol(), None);
|
||||
assert_eq!(lookahead.current_symbol_name(), None);
|
||||
assert_eq!(lookahead.by_ref().count(), 0);
|
||||
assert_eq!(lookahead.iter_names().count(), 0);
|
||||
|
||||
// Resetting restores it exactly.
|
||||
assert!(lookahead.reset_state(state));
|
||||
assert_eq!(lookahead.current_symbol(), None);
|
||||
assert_eq!(lookahead.by_ref().count(), count);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_symbol_metadata_checks() {
|
||||
let language = get_language("rust");
|
||||
|
|
|
|||
|
|
@ -387,3 +387,18 @@ fn test_wasm_oom() {
|
|||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lookahead_iterator_outlives_wasm_language() {
|
||||
allocations::record(|| {
|
||||
let mut store = WasmStore::new(&ENGINE).unwrap();
|
||||
let wasm = fs::read(WASM_DIR.join("tree-sitter-ruby.wasm")).unwrap();
|
||||
let language = store.load_language("ruby", &wasm).unwrap();
|
||||
|
||||
let mut lookahead = language.lookahead_iterator(0).unwrap();
|
||||
drop(language);
|
||||
|
||||
// The iterator retains the language, so the names are still live.
|
||||
assert!(lookahead.iter_names().count() > 0);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -670,8 +670,12 @@ impl Language {
|
|||
/// This returns `None` if state is invalid for this language.
|
||||
///
|
||||
/// Iterating [`LookaheadIterator`] will yield valid symbols in the given
|
||||
/// parse state. Newly created lookahead iterators will return the `ERROR`
|
||||
/// symbol from [`LookaheadIterator::current_symbol`].
|
||||
/// parse state. A newly created iterator is not positioned on a symbol, so
|
||||
/// [`LookaheadIterator::current_symbol`] returns `None` until the first
|
||||
/// [`Iterator::next`] call.
|
||||
///
|
||||
/// The iterator retains the language, so the language may be dropped while
|
||||
/// the iterator is still in use.
|
||||
///
|
||||
/// Lookahead iterators can be useful to generate suggestions and improve
|
||||
/// syntax error diagnostics. To get symbols valid in an `ERROR` node, use the
|
||||
|
|
@ -2337,22 +2341,34 @@ impl LookaheadIterator {
|
|||
}
|
||||
|
||||
/// Get the current symbol of the lookahead iterator.
|
||||
///
|
||||
/// Returns `None` if the iterator is not positioned on a symbol:
|
||||
///
|
||||
/// - Before the first [`Iterator::next`] call
|
||||
/// - After the iterator is exhausted
|
||||
/// - After a [`Self::reset`] or [`Self::reset_state`] call
|
||||
#[doc(alias = "ts_lookahead_iterator_current_symbol")]
|
||||
#[must_use]
|
||||
pub fn current_symbol(&self) -> u16 {
|
||||
unsafe { ffi::ts_lookahead_iterator_current_symbol(self.0.as_ptr()) }
|
||||
pub fn current_symbol(&self) -> Option<u16> {
|
||||
// C signals "not positioned" through a null symbol name.
|
||||
let name = unsafe { ffi::ts_lookahead_iterator_current_symbol_name(self.0.as_ptr()) };
|
||||
(!name.is_null())
|
||||
.then(|| unsafe { ffi::ts_lookahead_iterator_current_symbol(self.0.as_ptr()) })
|
||||
}
|
||||
|
||||
/// Get the current symbol name of the lookahead iterator.
|
||||
///
|
||||
/// Returns `None` if the iterator is not positioned on a symbol.
|
||||
#[doc(alias = "ts_lookahead_iterator_current_symbol_name")]
|
||||
#[must_use]
|
||||
pub fn current_symbol_name(&self) -> &'static str {
|
||||
pub fn current_symbol_name(&self) -> Option<&str> {
|
||||
unsafe {
|
||||
CStr::from_ptr(ffi::ts_lookahead_iterator_current_symbol_name(
|
||||
self.0.as_ptr(),
|
||||
))
|
||||
.to_str()
|
||||
.unwrap()
|
||||
let name = ffi::ts_lookahead_iterator_current_symbol_name(self.0.as_ptr());
|
||||
if name.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(CStr::from_ptr(name).to_str().unwrap())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2375,31 +2391,44 @@ impl LookaheadIterator {
|
|||
}
|
||||
|
||||
/// Iterate symbol names.
|
||||
pub fn iter_names(&mut self) -> impl Iterator<Item = &'static str> + '_ {
|
||||
pub fn iter_names(&mut self) -> impl iter::FusedIterator<Item = &str> + '_ {
|
||||
LookaheadNamesIterator(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for LookaheadNamesIterator<'_> {
|
||||
type Item = &'static str;
|
||||
impl<'a> Iterator for LookaheadNamesIterator<'a> {
|
||||
type Item = &'a str;
|
||||
|
||||
#[doc(alias = "ts_lookahead_iterator_next")]
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
unsafe { ffi::ts_lookahead_iterator_next(self.0.0.as_ptr()) }
|
||||
.then(|| self.0.current_symbol_name())
|
||||
let ptr = self.0.0.as_ptr();
|
||||
// SAFETY: The borrow keeps the iterator (and the language refcount it holds)
|
||||
// alive for `'a`. The name is non-null because the iterator is positioned
|
||||
// whenever `next` returns `true`.
|
||||
unsafe {
|
||||
ffi::ts_lookahead_iterator_next(ptr).then(|| {
|
||||
let name = ffi::ts_lookahead_iterator_current_symbol_name(ptr);
|
||||
debug_assert!(!name.is_null());
|
||||
CStr::from_ptr(name).to_str().unwrap()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl iter::FusedIterator for LookaheadNamesIterator<'_> {}
|
||||
|
||||
impl Iterator for LookaheadIterator {
|
||||
type Item = u16;
|
||||
|
||||
#[doc(alias = "ts_lookahead_iterator_next")]
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
// the first symbol is always `0` so we can safely skip it
|
||||
unsafe { ffi::ts_lookahead_iterator_next(self.0.as_ptr()) }.then(|| self.current_symbol())
|
||||
unsafe { ffi::ts_lookahead_iterator_next(self.0.as_ptr()) }
|
||||
.then(|| unsafe { ffi::ts_lookahead_iterator_current_symbol(self.0.as_ptr()) })
|
||||
}
|
||||
}
|
||||
|
||||
impl iter::FusedIterator for LookaheadIterator {}
|
||||
|
||||
impl Drop for LookaheadIterator {
|
||||
#[doc(alias = "ts_lookahead_iterator_delete")]
|
||||
fn drop(&mut self) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue