Skip to main content

luaur_analysis/methods/
frontend_check_queued_modules.rs

1use crate::enums::solver_mode::SolverMode;
2use crate::functions::make_type_check_limits::make_type_check_limits;
3use crate::records::build_queue_work_state::{BuildQueueWorkState, Task};
4use crate::records::frontend::Frontend;
5use crate::records::frontend_options::FrontendOptions;
6use crate::type_aliases::module_name_file_resolver::ModuleName;
7use alloc::boxed::Box;
8use alloc::sync::Arc;
9use alloc::vec::Vec;
10use core::cell::Cell;
11use luaur_common::macros::luau_assert::LUAU_ASSERT;
12use luaur_common::records::dense_hash_set::DenseHashSet;
13use std::collections::HashMap;
14use std::sync::{Condvar, Mutex};
15
16/// Adapts a `Task` (`FnOnce`) into the `Box<dyn Fn()>` shape expected by the
17/// caller-provided `execute_tasks`. The closure runs the task at most once.
18fn task_to_fn(task: Task) -> Box<dyn Fn()> {
19    let cell = Cell::new(Some(task));
20    Box::new(move || {
21        if let Some(t) = cell.take() {
22            t();
23        }
24    })
25}
26
27/// Wraps the caller-provided (non-`Send`) executor so it can populate the
28/// `Send + Sync` `execute_tasks` slot. The default single-threaded executor runs
29/// every task immediately on the calling thread, so the `Send`/`Sync` assertion is
30/// sound — tasks never actually cross a thread boundary.
31struct ExecutorWrapper {
32    inner: Box<dyn Fn(Vec<Box<dyn Fn()>>)>,
33}
34
35unsafe impl Send for ExecutorWrapper {}
36unsafe impl Sync for ExecutorWrapper {}
37
38impl ExecutorWrapper {
39    fn run(&self, tasks: Vec<Task>) {
40        let adapted: Vec<Box<dyn Fn()>> = tasks.into_iter().map(task_to_fn).collect();
41        (self.inner)(adapted);
42    }
43}
44
45impl Frontend {
46    pub fn check_queued_modules(
47        &mut self,
48        option_override: Option<FrontendOptions>,
49        execute_tasks: Box<dyn Fn(Vec<Box<dyn Fn()>>)>,
50        progress: Box<dyn Fn(usize, usize) -> bool>,
51    ) -> Vec<ModuleName> {
52        let mut frontend_options = option_override.unwrap_or_else(|| self.options.clone());
53        if self.get_luau_solver_mode() == SolverMode::New {
54            frontend_options.for_autocomplete = false;
55        }
56
57        // By taking data into locals, the queue is cleared at the end even on an ICE.
58        let curr_module_queue: Vec<ModuleName> = core::mem::take(&mut self.module_queue);
59
60        let mut seen: DenseHashSet<ModuleName> = DenseHashSet::new(ModuleName::default());
61
62        let state = Arc::new(BuildQueueWorkState {
63            execute_task_deprecated: None,
64            execute_tasks: None,
65            build_queue_items: Vec::new(),
66            mtx: Mutex::new(()),
67            cv: Condvar::new(),
68            ready_queue_items: Vec::new(),
69            processing: 0,
70            remaining: 0,
71        });
72
73        // SAFETY: `state` is uniquely owned here until tasks are dispatched, and the
74        // default executor runs synchronously; the C++ relies on `shared_ptr` + `mtx`
75        // for the same in-place mutation.
76        let state_ptr = Arc::as_ptr(&state) as *mut BuildQueueWorkState;
77
78        for name in &curr_module_queue {
79            if seen.contains(name) {
80                continue;
81            }
82
83            if !self.is_dirty(name, frontend_options.for_autocomplete) {
84                seen.insert(name.clone());
85                continue;
86            }
87
88            let mut queue: Vec<ModuleName> = Vec::new();
89            let cycle_detected = self.parse_graph(
90                &mut queue,
91                name,
92                &make_type_check_limits(&frontend_options),
93                frontend_options.for_autocomplete,
94            );
95
96            {
97                let bqi = unsafe { &mut (*state_ptr).build_queue_items };
98                self.add_build_queue_items(
99                    bqi,
100                    &queue,
101                    cycle_detected,
102                    &mut seen,
103                    &frontend_options,
104                );
105            }
106        }
107
108        {
109            let bqi = unsafe { &(*state_ptr).build_queue_items };
110            if bqi.is_empty() {
111                return Vec::new();
112            }
113        }
114
115        // Mapping from modules to build queue slots.
116        let mut module_name_to_queue: HashMap<ModuleName, usize> = HashMap::new();
117        {
118            let bqi = unsafe { &(*state_ptr).build_queue_items };
119            for i in 0..bqi.len() {
120                module_name_to_queue.insert(bqi[i].name.clone(), i);
121            }
122        }
123
124        // Wire `execute_tasks` into the work state. C++ defaults a null executor to a
125        // single-threaded immediate runner.
126        let executor = ExecutorWrapper {
127            inner: execute_tasks,
128        };
129        unsafe {
130            (*state_ptr).execute_tasks = Some(Box::new(move |tasks: Vec<Task>| {
131                executor.run(tasks);
132            }));
133        }
134        {
135            let len = {
136                let bqi = unsafe { &(*state_ptr).build_queue_items };
137                bqi.len()
138            };
139            unsafe {
140                (*state_ptr).remaining = len;
141            }
142        }
143
144        // Record dependencies between modules.
145        {
146            let count = {
147                let bqi = unsafe { &(*state_ptr).build_queue_items };
148                bqi.len()
149            };
150            for i in 0..count {
151                let deps: Vec<ModuleName> = {
152                    let bqi = unsafe { &(*state_ptr).build_queue_items };
153                    bqi[i].source_node.require_set.iter().cloned().collect()
154                };
155
156                for dep in deps {
157                    if let Some(node) = self.source_nodes.get(&dep) {
158                        if node.has_dirty_module(frontend_options.for_autocomplete) {
159                            let dep_pos = module_name_to_queue[&dep];
160                            let bqi = unsafe { &mut (*state_ptr).build_queue_items };
161                            bqi[i].dirty_dependencies += 1;
162                            bqi[dep_pos].reverse_deps.push(i);
163                        }
164                    }
165                }
166            }
167        }
168
169        let mut next_items: Vec<usize> = Vec::new();
170
171        // First pass: check all modules with no pending dependencies.
172        {
173            let bqi = unsafe { &(*state_ptr).build_queue_items };
174            for i in 0..bqi.len() {
175                if bqi[i].dirty_dependencies == 0 {
176                    next_items.push(i);
177                }
178            }
179        }
180
181        if !next_items.is_empty() {
182            self.send_queue_item_tasks(state.clone(), core::mem::take(&mut next_items));
183        }
184
185        // If not a single item was found, a cycle in the graph was hit.
186        if unsafe { (*state_ptr).processing } == 0 {
187            self.send_queue_cycle_item_task(state.clone());
188        }
189
190        let mut item_with_exception: Option<usize> = None;
191        let mut cancelled = false;
192
193        while unsafe { (*state_ptr).remaining } != 0 {
194            {
195                let mtx = unsafe { &(*state_ptr).mtx };
196                let cv = unsafe { &(*state_ptr).cv };
197                let guard = mtx.lock().unwrap();
198
199                // If nothing is ready yet, wait.
200                let _guard = cv
201                    .wait_while(guard, |_| {
202                        let ready = unsafe { &(*state_ptr).ready_queue_items };
203                        ready.is_empty()
204                    })
205                    .unwrap();
206
207                // Handle checked items.
208                let ready: Vec<usize> = {
209                    let r = unsafe { &(*state_ptr).ready_queue_items };
210                    r.clone()
211                };
212                for i in ready.iter().copied() {
213                    let (has_exception, is_cancelled) = {
214                        let bqi = unsafe { &(*state_ptr).build_queue_items };
215                        (bqi[i].exception.is_some(), bqi[i].module.cancelled)
216                    };
217                    if has_exception {
218                        item_with_exception = Some(i);
219                    }
220                    if is_cancelled {
221                        cancelled = true;
222                    }
223
224                    if item_with_exception.is_some() || cancelled {
225                        break;
226                    }
227
228                    {
229                        let bqi = unsafe { &(*state_ptr).build_queue_items };
230                        let item_ref = &bqi[i];
231                        self.record_item_result(item_ref);
232                    }
233
234                    // Notify items waiting on this dependency.
235                    let reverse_deps: Vec<usize> = {
236                        let bqi = unsafe { &(*state_ptr).build_queue_items };
237                        bqi[i].reverse_deps.clone()
238                    };
239                    for reverse_dep in reverse_deps {
240                        let bqi = unsafe { &mut (*state_ptr).build_queue_items };
241                        LUAU_ASSERT!(bqi[reverse_dep].dirty_dependencies != 0);
242                        bqi[reverse_dep].dirty_dependencies -= 1;
243
244                        if !bqi[reverse_dep].processing && bqi[reverse_dep].dirty_dependencies == 0
245                        {
246                            next_items.push(reverse_dep);
247                        }
248                    }
249                }
250
251                {
252                    let ready_len = {
253                        let r = unsafe { &(*state_ptr).ready_queue_items };
254                        r.len()
255                    };
256                    unsafe {
257                        LUAU_ASSERT!((*state_ptr).processing >= ready_len);
258                        (*state_ptr).processing -= ready_len;
259
260                        LUAU_ASSERT!((*state_ptr).remaining >= ready_len);
261                        (*state_ptr).remaining -= ready_len;
262                    }
263                    let r = unsafe { &mut (*state_ptr).ready_queue_items };
264                    r.clear();
265                }
266            }
267
268            {
269                let total = {
270                    let bqi = unsafe { &(*state_ptr).build_queue_items };
271                    bqi.len()
272                };
273                let done = total - unsafe { (*state_ptr).remaining };
274                if !progress(done, total) {
275                    cancelled = true;
276                }
277            }
278
279            // Items cannot be submitted while holding the lock.
280            if !next_items.is_empty() {
281                self.send_queue_item_tasks(state.clone(), core::mem::take(&mut next_items));
282            }
283
284            if unsafe { (*state_ptr).processing } == 0 {
285                // Typechecking might have been cancelled by user; don't return partial results.
286                if cancelled {
287                    return Vec::new();
288                }
289
290                // We might have stopped because of a pending exception.
291                if let Some(idx) = item_with_exception {
292                    let bqi = unsafe { &(*state_ptr).build_queue_items };
293                    let item_ref = &bqi[idx];
294                    self.record_item_result(item_ref);
295                }
296            }
297
298            // If we aren't done but have nothing processing, we hit a cycle.
299            if unsafe { (*state_ptr).remaining } != 0 && unsafe { (*state_ptr).processing } == 0 {
300                self.send_queue_cycle_item_task(state.clone());
301            }
302        }
303
304        let mut checked_modules: Vec<ModuleName> = Vec::new();
305        {
306            let count = {
307                let bqi = unsafe { &(*state_ptr).build_queue_items };
308                bqi.len()
309            };
310            checked_modules.reserve(count);
311            for i in 0..count {
312                let bqi = unsafe { &mut (*state_ptr).build_queue_items };
313                checked_modules.push(core::mem::take(&mut bqi[i].name));
314            }
315        }
316
317        checked_modules
318    }
319}