type_sitter_lib/query/
matches.rs

1use crate::query::match_captures::QueryMatchCaptures;
2use crate::{raw, Query};
3use std::fmt::Debug;
4use streaming_iterator::StreamingIterator;
5#[cfg(not(feature = "yak-sitter"))]
6use tree_sitter::Point;
7#[cfg(feature = "yak-sitter")]
8use yak_sitter::PointRange;
9
10/// Iterate a typed query's matches (see [tree-sitter's `QueryMatches`](raw::QueryMatches)).
11///
12/// `QueryMatches` (in both this crate and tree-sitter) is NOT a real iterator, it's a
13///     [`StreamingIterator`] (see <https://github.com/tree-sitter/tree-sitter/issues/608>).
14///     Therefore this doesn't implement [`Iterator`].
15#[cfg(feature = "yak-sitter")]
16pub struct QueryMatches<'query, 'tree: 'query, Query: crate::Query> {
17    typed_query: &'query Query,
18    untyped_matches: raw::QueryMatches<'query, 'tree>,
19    current_match: Option<*const Query::Match<'query, 'tree>>,
20}
21
22/// Iterate a typed query's matches (see [tree-sitter's `QueryMatches`](raw::QueryMatches)).
23///
24/// `QueryMatches` (in both this crate and tree-sitter) is NOT a real iterator, it's a
25///     [`StreamingIterator`] (see <https://github.com/tree-sitter/tree-sitter/issues/608>).
26///     Therefore this doesn't implement [`Iterator`].
27#[cfg(not(feature = "yak-sitter"))]
28pub struct QueryMatches<
29    'query,
30    'tree: 'query,
31    Query: crate::Query + 'tree,
32    Text: raw::TextProvider<I>,
33    I: AsRef<[u8]>,
34> {
35    typed_query: &'query Query,
36    untyped_matches: raw::QueryMatches<'query, 'tree, Text, I>,
37    current_match: Option<*const Query::Match<'query, 'tree>>,
38}
39
40/// A match from a [`Query`] with [typed nodes](Node)
41pub trait QueryMatch<'query, 'tree: 'query>: Debug {
42    /// The type of query this match came from
43    type Query: Query<Match<'query, 'tree> = Self>;
44
45    /// The query this match came from
46    fn query(&self) -> &'query Self::Query;
47
48    /// The tree this match came from
49    #[cfg(feature = "yak-sitter")]
50    fn tree(&self) -> &'tree raw::Tree;
51
52    /// The underlying tree-sitter [`QueryMatch`]
53    fn raw(&self) -> &raw::QueryMatch<'query, 'tree>;
54
55    /// Destruct into the underlying tree-sitter [`QueryMatch`]
56    fn into_raw(self) -> raw::QueryMatch<'query, 'tree>;
57
58    /// See [tree-sitter's `QueryMatch::captures`](raw::QueryMatch::captures)
59    #[cfg(feature = "yak-sitter")]
60    #[inline]
61    fn captures(&self) -> QueryMatchCaptures<'query, 'tree, Self::Query> {
62        // SAFETY: Captures come from the same query
63        unsafe {
64            QueryMatchCaptures::new(self.query(), self.raw().as_inner().captures, self.tree())
65        }
66    }
67
68    /// See [tree-sitter's `QueryMatch::captures`](raw::QueryMatch::captures)
69    #[cfg(not(feature = "yak-sitter"))]
70    #[inline]
71    fn captures(&self) -> QueryMatchCaptures<'query, 'tree, Self::Query> {
72        // SAFETY: Captures come from the same query
73        unsafe { QueryMatchCaptures::new(self.query(), self.raw().captures) }
74    }
75
76    /// Remove the match (honestly I don't know what this does because it's not documented)
77    // I don't know why `tree: 'query` is required, since it's not in any bounds from anything in
78    // the function body.
79    #[inline]
80    fn remove(self)
81    where
82        Self: Sized,
83        'tree: 'query,
84    {
85        self.into_raw().remove()
86    }
87}
88
89#[cfg(feature = "yak-sitter")]
90impl<'query, 'tree: 'query, Query: crate::Query + 'tree> QueryMatches<'query, 'tree, Query> {
91    /// Wrap untyped matches along with the query.
92    ///
93    /// # Safety
94    /// The matches must have come from the same query.
95    #[inline]
96    pub(super) unsafe fn from_raw(
97        typed_query: &'query Query,
98        untyped_matches: raw::QueryMatches<'query, 'tree>,
99    ) -> Self {
100        Self {
101            typed_query,
102            untyped_matches,
103            current_match: None,
104        }
105    }
106
107    /// Limit matches to a byte range
108    #[inline]
109    pub fn set_byte_range(&mut self, range: std::ops::Range<usize>) {
110        self.untyped_matches.set_byte_range(range)
111    }
112
113    /// Limit matches to a point range
114    #[inline]
115    #[cfg(feature = "yak-sitter")]
116    pub fn set_point_range(&mut self, range: PointRange) {
117        self.untyped_matches.set_point_range(range)
118    }
119}
120
121#[cfg(not(feature = "yak-sitter"))]
122impl<
123        'query,
124        'tree: 'query,
125        Query: crate::Query + 'tree,
126        Text: raw::TextProvider<I>,
127        I: AsRef<[u8]>,
128    > QueryMatches<'query, 'tree, Query, Text, I>
129{
130    /// Wrap untyped matches along with the query.
131    ///
132    /// # Safety
133    /// The matches must have come from the same query.
134    #[inline]
135    pub(super) unsafe fn from_raw(
136        typed_query: &'query Query,
137        untyped_matches: raw::QueryMatches<'query, 'tree, Text, I>,
138    ) -> Self {
139        Self {
140            typed_query,
141            untyped_matches,
142            current_match: None,
143        }
144    }
145
146    /// Limit matches to a byte range
147    #[inline]
148    pub fn set_byte_range(&mut self, range: std::ops::Range<usize>) {
149        self.untyped_matches.set_byte_range(range)
150    }
151
152    /// Limit matches to a point range
153    #[inline]
154    pub fn set_point_range(&mut self, range: std::ops::Range<Point>) {
155        self.untyped_matches.set_point_range(range)
156    }
157}
158
159//noinspection DuplicatedCode
160#[cfg(feature = "yak-sitter")]
161impl<'query, 'tree: 'query, Query: crate::Query + 'tree> StreamingIterator
162    for QueryMatches<'query, 'tree, Query>
163{
164    type Item = Query::Match<'query, 'tree>;
165
166    #[inline]
167    fn advance(&mut self) {
168        self.untyped_matches.advance();
169        // SAFETY: Matches come from the same query and tree.
170        self.current_match = unsafe {
171            self.untyped_matches
172                .get()
173                .map(|m| self.typed_query.wrap_match_ref(m) as *const _)
174        }
175    }
176
177    #[inline]
178    fn get(&self) -> Option<&Self::Item> {
179        // SAFETY: `m` is still live, because it's only invalidated when this `untyped_matches` is
180        // dropped (which only happens when `self` is dropped, which can't have happened here) or
181        // `untyped_matches.advance` is called (which can only happen when `self.advance` is called,
182        // which replaces `current_match` with another value where, if `Some`, `m` is live).
183        self.current_match.map(|m| unsafe { &*m })
184    }
185
186    #[inline]
187    fn size_hint(&self) -> (usize, Option<usize>) {
188        self.untyped_matches.size_hint()
189    }
190}
191
192//noinspection DuplicatedCode
193#[cfg(not(feature = "yak-sitter"))]
194impl<
195        'query,
196        'tree: 'query,
197        Query: crate::Query + 'tree,
198        Text: raw::TextProvider<I>,
199        I: AsRef<[u8]>,
200    > StreamingIterator for QueryMatches<'query, 'tree, Query, Text, I>
201{
202    type Item = Query::Match<'query, 'tree>;
203
204    #[inline]
205    fn advance(&mut self) {
206        self.untyped_matches.advance();
207        // SAFETY: Matches come from the same query and tree.
208        self.current_match = unsafe {
209            self.untyped_matches
210                .get()
211                .map(|m| self.typed_query.wrap_match_ref(m) as *const _)
212        }
213    }
214
215    #[inline]
216    fn get(&self) -> Option<&Self::Item> {
217        // SAFETY: `m` is still live, because it's only invalidated when this `untyped_matches` is
218        // dropped (which only happens when `self` is dropped, which can't have happened here) or
219        // `untyped_matches.advance` is called (which can only happen when `self.advance` is called,
220        // which replaces `current_match` with another value where, if `Some`, `m` is live).
221        self.current_match.map(|m| unsafe { &*m })
222    }
223
224    #[inline]
225    fn size_hint(&self) -> (usize, Option<usize>) {
226        self.untyped_matches.size_hint()
227    }
228}