Skip to main content

luaur_analysis/functions/
autocomplete_statement.rs

1use crate::enums::autocomplete_entry_kind::AutocompleteEntryKind;
2use crate::enums::type_correct_kind::TypeCorrectKind;
3use crate::functions::autocomplete_keywords::autocomplete_keywords;
4use crate::functions::get_paren_recommendation::get_paren_recommendation;
5use crate::functions::is_binding_legal_at_current_position::is_binding_legal_at_current_position;
6use crate::functions::is_identifier::is_identifier;
7use crate::functions::is_in_local_names::is_in_local_names;
8use crate::functions::is_valid_break_continue_context::is_valid_break_continue_context;
9use crate::functions::to_string_symbol::to_string_symbol;
10use crate::records::autocomplete_entry::AutocompleteEntry;
11use crate::records::binding::Binding;
12use crate::records::module::Module;
13use crate::type_aliases::autocomplete_entry_map::AutocompleteEntryMap;
14use crate::type_aliases::scope_ptr_type::ScopePtr;
15
16use alloc::collections::BTreeMap;
17use alloc::string::String;
18
19use luaur_ast::records::ast_expr_function::AstExprFunction;
20use luaur_ast::records::ast_node::AstNode;
21use luaur_ast::records::ast_stat_block::AstStatBlock;
22use luaur_ast::records::ast_stat_error::AstStatError;
23use luaur_ast::records::ast_stat_for::AstStatFor;
24use luaur_ast::records::ast_stat_for_in::AstStatForIn;
25use luaur_ast::records::ast_stat_if::AstStatIf;
26use luaur_ast::records::ast_stat_repeat::AstStatRepeat;
27use luaur_ast::records::ast_stat_while::AstStatWhile;
28
29use luaur_ast::records::position::Position;
30
31use luaur_ast::rtti::ast_node_as;
32use luaur_ast::rtti::ast_node_is;
33use luaur_common::FFlag;
34
35#[allow(dead_code)]
36const kStatementStartingKeywords_DEPRECATED: [&str; 12] = [
37    "while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue",
38    "type", "export",
39];
40const K_STATEMENT_STARTING_KEYWORDS_DEPRECATED: [&str; 12] = [
41    "while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue",
42    "type", "export",
43];
44
45#[allow(dead_code)]
46const kStatementStartingKeywords_CONST: [&str; 13] = [
47    "while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue",
48    "type", "export", "const",
49];
50const K_STATEMENT_STARTING_KEYWORDS_CONST: [&str; 13] = [
51    "while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue",
52    "type", "export", "const",
53];
54
55#[allow(dead_code)]
56const kStatementStartingKeywords_EXPORT: [&str; 14] = [
57    "while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue",
58    "type", "export", "const", "export",
59];
60const K_STATEMENT_STARTING_KEYWORDS_EXPORT: [&str; 14] = [
61    "while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue",
62    "type", "export", "const", "export",
63];
64
65pub fn autocomplete_statement(
66    module: &Module,
67    ancestry: &alloc::vec::Vec<*mut AstNode>,
68    scope_at_position: &ScopePtr,
69    position: &mut Position,
70) -> AutocompleteEntryMap {
71    let mut result: AutocompleteEntryMap = BTreeMap::new();
72
73    if is_in_local_names(ancestry, *position) {
74        autocomplete_keywords(ancestry, *position, &mut result);
75        return result;
76    }
77
78    let mut scope = Some(scope_at_position.clone());
79    while let Some(scope_ref) = scope {
80        for (name, binding) in &scope_ref.bindings {
81            if !is_binding_legal_at_current_position(name, binding, *position) {
82                continue;
83            }
84
85            let n = unsafe { to_string_symbol(name) };
86            if !result.contains_key(&n) {
87                result.insert(
88                    n.clone(),
89                    AutocompleteEntry {
90                        kind: AutocompleteEntryKind::Binding,
91                        r#type: Some(binding.type_id),
92                        deprecated: binding.deprecated,
93                        wrong_index_type: false,
94                        type_correct: TypeCorrectKind::None,
95                        containing_extern_type: None,
96                        prop: None,
97                        documentation_symbol: binding.documentation_symbol.clone(),
98                        tags: Default::default(),
99                        parens: get_paren_recommendation(
100                            binding.type_id,
101                            ancestry,
102                            TypeCorrectKind::None,
103                        ),
104                        insert_text: None,
105                        indexed_with_self: false,
106                    },
107                );
108            }
109        }
110
111        scope = scope_ref.parent.clone();
112    }
113
114    let should_include_break_and_continue = is_valid_break_continue_context(ancestry, *position);
115
116    if FFlag::LuauExportValueSyntax.get() && FFlag::LuauAutocompleteExport.get() {
117        for &kw in &K_STATEMENT_STARTING_KEYWORDS_EXPORT {
118            if (kw != "break" && kw != "continue") || should_include_break_and_continue {
119                result.insert(
120                    kw.to_string(),
121                    AutocompleteEntry {
122                        kind: AutocompleteEntryKind::Keyword,
123                        r#type: None,
124                        deprecated: false,
125                        wrong_index_type: false,
126                        type_correct: TypeCorrectKind::None,
127                        containing_extern_type: None,
128                        prop: None,
129                        documentation_symbol: None,
130                        tags: Default::default(),
131                        parens: Default::default(),
132                        insert_text: None,
133                        indexed_with_self: false,
134                    },
135                );
136            }
137        }
138    } else if FFlag::LuauAutocompleteConst.get() {
139        for &kw in &K_STATEMENT_STARTING_KEYWORDS_CONST {
140            if (kw != "break" && kw != "continue") || should_include_break_and_continue {
141                result.insert(
142                    kw.to_string(),
143                    AutocompleteEntry {
144                        kind: AutocompleteEntryKind::Keyword,
145                        r#type: None,
146                        deprecated: false,
147                        wrong_index_type: false,
148                        type_correct: TypeCorrectKind::None,
149                        containing_extern_type: None,
150                        prop: None,
151                        documentation_symbol: None,
152                        tags: Default::default(),
153                        parens: Default::default(),
154                        insert_text: None,
155                        indexed_with_self: false,
156                    },
157                );
158            }
159        }
160    } else {
161        for &kw in &K_STATEMENT_STARTING_KEYWORDS_DEPRECATED {
162            if (kw != "break" && kw != "continue") || should_include_break_and_continue {
163                result.insert(
164                    kw.to_string(),
165                    AutocompleteEntry {
166                        kind: AutocompleteEntryKind::Keyword,
167                        r#type: None,
168                        deprecated: false,
169                        wrong_index_type: false,
170                        type_correct: TypeCorrectKind::None,
171                        containing_extern_type: None,
172                        prop: None,
173                        documentation_symbol: None,
174                        tags: Default::default(),
175                        parens: Default::default(),
176                        insert_text: None,
177                        indexed_with_self: false,
178                    },
179                );
180            }
181        }
182    }
183
184    for it_idx in (0..ancestry.len()).rev() {
185        let node_ptr = ancestry[it_idx];
186        let ast_node = node_ptr as *mut AstNode;
187
188        if unsafe { ast_node_is::<AstStatForIn>(&*ast_node) }
189            && !unsafe { (*ast_node_as::<AstStatForIn>(ast_node)).body }.is_null()
190        {
191            let stat_for_in = unsafe { ast_node_as::<AstStatForIn>(ast_node) };
192            if !unsafe { (*(*stat_for_in).body).has_end } {
193                result.insert(
194                    "end".to_string(),
195                    AutocompleteEntry {
196                        kind: AutocompleteEntryKind::Keyword,
197                        r#type: None,
198                        deprecated: false,
199                        wrong_index_type: false,
200                        type_correct: TypeCorrectKind::None,
201                        containing_extern_type: None,
202                        prop: None,
203                        documentation_symbol: None,
204                        tags: Default::default(),
205                        parens: Default::default(),
206                        insert_text: None,
207                        indexed_with_self: false,
208                    },
209                );
210            }
211        } else if unsafe { ast_node_is::<AstStatFor>(&*ast_node) } {
212            let stat_for = unsafe { ast_node_as::<AstStatFor>(ast_node) };
213            if !unsafe { (*(*stat_for).body).has_end } {
214                result.insert(
215                    "end".to_string(),
216                    AutocompleteEntry {
217                        kind: AutocompleteEntryKind::Keyword,
218                        r#type: None,
219                        deprecated: false,
220                        wrong_index_type: false,
221                        type_correct: TypeCorrectKind::None,
222                        containing_extern_type: None,
223                        prop: None,
224                        documentation_symbol: None,
225                        tags: Default::default(),
226                        parens: Default::default(),
227                        insert_text: None,
228                        indexed_with_self: false,
229                    },
230                );
231            }
232        } else if unsafe { ast_node_is::<AstStatIf>(&*ast_node) } {
233            let stat_if = unsafe { ast_node_as::<AstStatIf>(ast_node) };
234            let mut has_end = unsafe { (*stat_if).thenbody }.is_null()
235                || unsafe { (*(*stat_if).thenbody).has_end };
236            if !unsafe { (*stat_if).elsebody }.is_null() {
237                let elsebody = unsafe { (*stat_if).elsebody };
238                let elsebody_node = elsebody as *mut AstNode;
239                if unsafe { ast_node_is::<AstStatBlock>(&*elsebody_node) } {
240                    let else_block = unsafe { ast_node_as::<AstStatBlock>(elsebody_node) };
241                    has_end = unsafe { (*else_block).has_end };
242                }
243            }
244            if !has_end {
245                result.insert(
246                    "end".to_string(),
247                    AutocompleteEntry {
248                        kind: AutocompleteEntryKind::Keyword,
249                        r#type: None,
250                        deprecated: false,
251                        wrong_index_type: false,
252                        type_correct: TypeCorrectKind::None,
253                        containing_extern_type: None,
254                        prop: None,
255                        documentation_symbol: None,
256                        tags: Default::default(),
257                        parens: Default::default(),
258                        insert_text: None,
259                        indexed_with_self: false,
260                    },
261                );
262            }
263        } else if unsafe { ast_node_is::<AstStatWhile>(&*ast_node) } {
264            let stat_while = unsafe { ast_node_as::<AstStatWhile>(ast_node) };
265            if !unsafe { (*(*stat_while).body).has_end } {
266                result.insert(
267                    "end".to_string(),
268                    AutocompleteEntry {
269                        kind: AutocompleteEntryKind::Keyword,
270                        r#type: None,
271                        deprecated: false,
272                        wrong_index_type: false,
273                        type_correct: TypeCorrectKind::None,
274                        containing_extern_type: None,
275                        prop: None,
276                        documentation_symbol: None,
277                        tags: Default::default(),
278                        parens: Default::default(),
279                        insert_text: None,
280                        indexed_with_self: false,
281                    },
282                );
283            }
284        } else if unsafe { ast_node_is::<AstExprFunction>(&*ast_node) } {
285            let expr_function = unsafe { ast_node_as::<AstExprFunction>(ast_node) };
286            if !unsafe { (*expr_function).body }.is_null()
287                && !unsafe { (*(*expr_function).body).has_end }
288            {
289                result.insert(
290                    "end".to_string(),
291                    AutocompleteEntry {
292                        kind: AutocompleteEntryKind::Keyword,
293                        r#type: None,
294                        deprecated: false,
295                        wrong_index_type: false,
296                        type_correct: TypeCorrectKind::None,
297                        containing_extern_type: None,
298                        prop: None,
299                        documentation_symbol: None,
300                        tags: Default::default(),
301                        parens: Default::default(),
302                        insert_text: None,
303                        indexed_with_self: false,
304                    },
305                );
306            }
307        }
308
309        if unsafe { ast_node_is::<AstStatBlock>(&*ast_node) } {
310            let expr_block = unsafe { ast_node_as::<AstStatBlock>(ast_node) };
311            if !unsafe { (*expr_block).has_end } {
312                result.insert(
313                    "end".to_string(),
314                    AutocompleteEntry {
315                        kind: AutocompleteEntryKind::Keyword,
316                        r#type: None,
317                        deprecated: false,
318                        wrong_index_type: false,
319                        type_correct: TypeCorrectKind::None,
320                        containing_extern_type: None,
321                        prop: None,
322                        documentation_symbol: None,
323                        tags: Default::default(),
324                        parens: Default::default(),
325                        insert_text: None,
326                        indexed_with_self: false,
327                    },
328                );
329            }
330        }
331    }
332
333    if ancestry.len() >= 2 {
334        let parent = ancestry[ancestry.len() - 2];
335        if unsafe { ast_node_is::<AstStatIf>(&*parent) } {
336            let stat_if = unsafe { ast_node_as::<AstStatIf>(parent) };
337            let elsebody = unsafe { (*stat_if).elsebody };
338            let else_location = unsafe { (*stat_if).else_location };
339            if elsebody.is_null()
340                || (else_location.is_some() && else_location.unwrap().containsClosed(*position))
341            {
342                result.insert(
343                    "else".to_string(),
344                    AutocompleteEntry {
345                        kind: AutocompleteEntryKind::Keyword,
346                        r#type: None,
347                        deprecated: false,
348                        wrong_index_type: false,
349                        type_correct: TypeCorrectKind::None,
350                        containing_extern_type: None,
351                        prop: None,
352                        documentation_symbol: None,
353                        tags: Default::default(),
354                        parens: Default::default(),
355                        insert_text: None,
356                        indexed_with_self: false,
357                    },
358                );
359                result.insert(
360                    "elseif".to_string(),
361                    AutocompleteEntry {
362                        kind: AutocompleteEntryKind::Keyword,
363                        r#type: None,
364                        deprecated: false,
365                        wrong_index_type: false,
366                        type_correct: TypeCorrectKind::None,
367                        containing_extern_type: None,
368                        prop: None,
369                        documentation_symbol: None,
370                        tags: Default::default(),
371                        parens: Default::default(),
372                        insert_text: None,
373                        indexed_with_self: false,
374                    },
375                );
376            }
377        }
378
379        if unsafe { ast_node_is::<AstStatRepeat>(&*parent) } {
380            let stat_repeat = unsafe { ast_node_as::<AstStatRepeat>(parent) };
381            if !unsafe { (*(*stat_repeat).body).has_end } {
382                result.insert(
383                    "until".to_string(),
384                    AutocompleteEntry {
385                        kind: AutocompleteEntryKind::Keyword,
386                        r#type: None,
387                        deprecated: false,
388                        wrong_index_type: false,
389                        type_correct: TypeCorrectKind::None,
390                        containing_extern_type: None,
391                        prop: None,
392                        documentation_symbol: None,
393                        tags: Default::default(),
394                        parens: Default::default(),
395                        insert_text: None,
396                        indexed_with_self: false,
397                    },
398                );
399            }
400        }
401    }
402
403    if ancestry.len() >= 4 {
404        let iter3 = ancestry[ancestry.len() - 4];
405        let stat_if_ptr = unsafe {
406            if ast_node_is::<AstStatIf>(&*iter3.cast::<AstNode>()) {
407                ast_node_as::<AstStatIf>(iter3)
408            } else {
409                core::ptr::null_mut()
410            }
411        };
412        if !stat_if_ptr.is_null()
413            && unsafe { (*stat_if_ptr).elsebody }.is_null()
414            && ancestry[ancestry.len() - 3].is_null() == false
415            && unsafe { ast_node_is::<AstStatBlock>(&*ancestry[ancestry.len() - 3]) }
416            && unsafe { ast_node_is::<AstStatError>(&*ancestry[ancestry.len() - 2]) }
417            && is_identifier(ancestry[ancestry.len() - 1])
418        {
419            result.insert(
420                "else".to_string(),
421                AutocompleteEntry {
422                    kind: AutocompleteEntryKind::Keyword,
423                    r#type: None,
424                    deprecated: false,
425                    wrong_index_type: false,
426                    type_correct: TypeCorrectKind::None,
427                    containing_extern_type: None,
428                    prop: None,
429                    documentation_symbol: None,
430                    tags: Default::default(),
431                    parens: Default::default(),
432                    insert_text: None,
433                    indexed_with_self: false,
434                },
435            );
436            result.insert(
437                "elseif".to_string(),
438                AutocompleteEntry {
439                    kind: AutocompleteEntryKind::Keyword,
440                    r#type: None,
441                    deprecated: false,
442                    wrong_index_type: false,
443                    type_correct: TypeCorrectKind::None,
444                    containing_extern_type: None,
445                    prop: None,
446                    documentation_symbol: None,
447                    tags: Default::default(),
448                    parens: Default::default(),
449                    insert_text: None,
450                    indexed_with_self: false,
451                },
452            );
453        }
454    }
455
456    // extractStat<AstStatRepeat>(ancestry) isn't available here via required context,
457    // so mimic by scanning for the first AstStatRepeat from end.
458    let mut found_repeat: *mut AstStatRepeat = core::ptr::null_mut();
459    for &node in ancestry.iter().rev() {
460        if unsafe { ast_node_is::<AstStatRepeat>(&*node) } {
461            found_repeat = unsafe { ast_node_as::<AstStatRepeat>(node) };
462            break;
463        }
464    }
465    if !found_repeat.is_null() && !unsafe { (*(*found_repeat).body).has_end } {
466        result.insert(
467            "until".to_string(),
468            AutocompleteEntry {
469                kind: AutocompleteEntryKind::Keyword,
470                r#type: None,
471                deprecated: false,
472                wrong_index_type: false,
473                type_correct: TypeCorrectKind::None,
474                containing_extern_type: None,
475                prop: None,
476                documentation_symbol: None,
477                tags: Default::default(),
478                parens: Default::default(),
479                insert_text: None,
480                indexed_with_self: false,
481            },
482        );
483    }
484
485    result
486}