Skip to main content

luaur_analysis/functions/
autocomplete_string_params.rs

1use crate::functions::convert_require_suggestions_to_autocomplete_entry_map::convert_require_suggestions_to_autocomplete_entry_map;
2use crate::functions::follow_type::follow_type_id;
3use crate::functions::get_method_containing_extern_type::get_method_containing_extern_type;
4use crate::functions::get_string_contents::get_string_contents;
5use crate::functions::get_type_alt_j::get_type_id;
6use crate::functions::process_require_suggestions::process_require_suggestions;
7use crate::records::file_resolver::FileResolver;
8use crate::records::function_type::FunctionType;
9use crate::records::intersection_type::IntersectionType;
10use crate::type_aliases::autocomplete_entry_map::AutocompleteEntryMap;
11use crate::type_aliases::module_ptr_module::ModulePtr;
12use crate::type_aliases::string_completion_callback::StringCompletionCallback;
13use luaur_ast::records::ast_expr::AstExpr;
14use luaur_ast::records::ast_expr_call::AstExprCall;
15use luaur_ast::records::ast_expr_constant_string::AstExprConstantString;
16use luaur_ast::records::ast_expr_error::AstExprError;
17use luaur_ast::records::ast_expr_interp_string::AstExprInterpString;
18use luaur_ast::records::ast_node::AstNode;
19use luaur_ast::records::position::Position;
20use luaur_ast::rtti::{ast_node_as, ast_node_as_const, ast_node_is};
21
22const K_REQUIRE_TAG_NAME: &str = "require";
23
24pub fn autocomplete_string_params(
25    module: &ModulePtr,
26    nodes: &alloc::vec::Vec<*mut AstNode>,
27    position: Position,
28    file_resolver: *mut FileResolver,
29    callback: StringCompletionCallback,
30) -> Option<AutocompleteEntryMap> {
31    if nodes.len() < 2 {
32        return None;
33    }
34
35    let last = *nodes.last()?;
36    if !unsafe { ast_node_is::<AstExprConstantString>(&*last) }
37        && !is_simple_interpolated_string(last)
38        && !unsafe { ast_node_is::<AstExprError>(&*last) }
39    {
40        return None;
41    }
42
43    if !unsafe { ast_node_is::<AstExprError>(&*last) } {
44        let last_location = unsafe { (*last).location };
45        if last_location.end == position || last_location.begin == position {
46            return None;
47        }
48    }
49
50    let candidate_node = nodes[nodes.len() - 2];
51    let candidate = unsafe { ast_node_as::<AstExprCall>(candidate_node) };
52    if candidate.is_null() {
53        return None;
54    }
55
56    let candidate_ref = unsafe { &*candidate };
57
58    if candidate_ref.args.size > 1 {
59        let first_arg = unsafe { *candidate_ref.args.data };
60        let first_arg_location = unsafe { (*first_arg).base.location };
61        if !first_arg_location.contains(position) {
62            return None;
63        }
64    }
65
66    let it = module
67        .ast_types
68        .find(&(candidate_ref.func as *const AstExpr))?;
69    let candidate_string = get_string_contents(last as *const AstNode);
70
71    let mut perform_callback = |func_type: &FunctionType| -> Option<AutocompleteEntryMap> {
72        for tag in &func_type.tags {
73            if tag == K_REQUIRE_TAG_NAME && !file_resolver.is_null() {
74                let suggestions = unsafe {
75                    (*file_resolver)
76                        .require_suggester
77                        .as_ref()
78                        .and_then(|suggester| {
79                            suggester.get_require_suggestions_impl(&module.name, &candidate_string)
80                        })
81                };
82                return convert_require_suggestions_to_autocomplete_entry_map(
83                    process_require_suggestions(suggestions),
84                );
85            }
86
87            if let Some(ret) = callback(
88                tag.clone(),
89                get_method_containing_extern_type(module, candidate_ref.func),
90                candidate_string.clone(),
91            ) {
92                return Some(ret);
93            }
94        }
95
96        None
97    };
98
99    let followed_id = unsafe { follow_type_id(*it) };
100    let function_type = unsafe { get_type_id::<FunctionType>(followed_id) };
101    if !function_type.is_null() {
102        return perform_callback(unsafe { &*function_type });
103    }
104
105    let intersect = unsafe { get_type_id::<IntersectionType>(followed_id) };
106    if !intersect.is_null() {
107        for part in unsafe { &(*intersect).parts } {
108            let part = unsafe { follow_type_id(*part) };
109            let candidate_function_type = unsafe { get_type_id::<FunctionType>(part) };
110            if !candidate_function_type.is_null() {
111                if let Some(ret) = perform_callback(unsafe { &*candidate_function_type }) {
112                    return Some(ret);
113                }
114            }
115        }
116    }
117
118    None
119}
120
121fn is_simple_interpolated_string(node: *const AstNode) -> bool {
122    let interp_string = unsafe { ast_node_as_const::<AstExprInterpString>(node) };
123    !interp_string.is_null() && unsafe { (*interp_string).expressions.size == 0 }
124}