1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
// SPDX-FileCopyrightText: 2022 Herrington Darkholme <2883231+HerringtonDarkholme@users.noreply.github.com>
// SPDX-FileCopyrightText: 2025 Knitli Inc. <knitli@knit.li>
// SPDX-FileContributor: Adam Poulemanos <adam@knit.li>
//
// SPDX-License-Identifier: AGPL-3.0-or-later AND MIT
//! # Lifetime Extension for AST Nodes Across Thread and FFI Boundaries
//!
//! Enables safe passing of AST nodes across threads and FFI boundaries by extending
//! their lifetimes beyond the normal borrow checker constraints.
//!
//! ## The Problem
//!
//! Normally, AST nodes have lifetimes tied to their root document:
//! ```rust,ignore
//! let root = parse_code("let x = 42;");
//! let node = root.find("$VAR").unwrap(); // node lifetime tied to root
//! // Can't send node to another thread without root
//! ```
//!
//! ## The Solution
//!
//! [`PinnedNodeData`] keeps the root alive while allowing nodes to have `'static` lifetimes:
//! ```rust,ignore
//! let pinned = PinnedNodeData::new(root, |static_root| {
//! static_root.find("$VAR").unwrap() // Now has 'static lifetime
//! });
//! // Can safely send `pinned` across threads
//! ```
//!
//! ## Safety
//!
//! This module uses unsafe code to extend lifetimes, but maintains safety by:
//! - Keeping the root document alive as long as nodes exist
//! - Re-adopting nodes when accessing them to ensure validity
//! - Using tree-sitter's heap-allocated node pointers which remain stable
//!
//! ## Use Cases
//!
//! - **Threading**: Send AST analysis results between threads
//! - **FFI**: Pass nodes to JavaScript (NAPI) or Python (PyO3)
//! - **Async**: Store nodes across await points
//! - **Caching**: Keep processed nodes in long-lived data structures
use crateDoc;
use crateNodeMatch;
use crate;
// ast-grep Node contains a reference to Root. It implies that
// node can be used only when the Root is valid and not dropped.
// By default, tree-sitter Node<'r> is scoped by ast Root's lifetime
// That is, Node can be only used when root is on the call stack (RAII)
// It is usually sufficient but for following scenario the brwchck is too conservative:
// 1. passing Root and Node across threads
// 2. passing Root and Node across FFI boundary (from Rust to napi/pyo3)
//
// This resembles self-referencing pattern and we can use solution similar to PinBox.
// Actually, tree-sitter's Node reference is already pointing to a heap address.
// N.B. it is not documented but can be inferred from the source code and concurrency doc paragraph.
// https://github.com/tree-sitter/tree-sitter/blob/20924fa4cdeb10d82ac308481e39bf8519334e55/lib/src/tree.c#L9-L20
// https://github.com/tree-sitter/tree-sitter/blob/20924fa4cdeb10d82ac308481e39bf8519334e55/lib/src/tree.c#L37-L39
// https://tree-sitter.github.io/tree-sitter/using-parsers#concurrency
//
/// Container that extends AST node lifetimes by keeping their root document alive.
///
/// `PinnedNodeData` solves the problem of passing AST nodes across thread boundaries
/// or FFI interfaces where normal lifetime constraints are too restrictive. It combines
/// a root document with data containing nodes, ensuring the nodes remain valid.
///
/// # Type Parameters
///
/// - `D: Doc` - The document type (e.g., `StrDoc<Language>`)
/// - `T` - Data containing nodes with `'static` lifetimes
///
/// # Safety Model
///
/// The container uses unsafe code to extend node lifetimes, but maintains safety by:
/// - Keeping the root document alive to prevent tree deallocation
/// - Re-adopting nodes when accessed to ensure they point to valid memory
/// - Leveraging tree-sitter's stable heap-allocated node pointers
///
/// # Usage Patterns
///
/// ## 1. Borrowing Pattern (Recommended)
/// Use through references to guarantee safety:
/// ```rust,ignore
/// let pinned = PinnedNodeData::new(root, |static_root| {
/// static_root.find("pattern").unwrap()
/// });
/// let node = pinned.get_data(); // Safe access
/// ```
///
/// ## 2. Ownership Pattern (Advanced)
/// Take ownership but ensure root stays alive:
/// ```rust,ignore
/// let (root, node_data) = pinned.into_raw();
/// // You must keep `root` alive while using `node_data`
/// ```
///
/// # Thread Safety
///
/// Safe to send across threads as long as the contained data is `Send`:
/// ```rust,ignore
/// std::thread::spawn(move || {
/// let node = pinned.get_data();
/// // Process node in background thread
/// });
/// ```
/// # Safety
/// This trait is unsafe because implementors must ensure that `visit_nodes` calls
/// the provided function on all nodes contained within the data structure.
/// Failure to do so will result in stale node pointers that may reference freed memory.
pub unsafe