Skip to main content

solidity_language_server/
completion.rs

1use serde_json::Value;
2use std::collections::HashMap;
3use tower_lsp::lsp_types::{
4    CompletionItem, CompletionItemKind, CompletionList, CompletionResponse, Position,
5};
6
7use crate::goto::CHILD_KEYS;
8
9/// Completion cache built from the AST.
10pub struct CompletionCache {
11    /// All named identifiers as completion items (flat, unscoped).
12    pub names: Vec<CompletionItem>,
13
14    /// name → typeIdentifier (for dot-completion: look up what type a variable is).
15    pub name_to_type: HashMap<String, String>,
16
17    /// node id → Vec<CompletionItem> (members of structs, contracts, enums, libraries).
18    pub node_members: HashMap<u64, Vec<CompletionItem>>,
19
20    /// typeIdentifier → node id (resolve a type string to its definition).
21    pub type_to_node: HashMap<String, u64>,
22
23    /// contract/library/interface name → node id (for direct name dot-completion like `FullMath.`).
24    pub name_to_node_id: HashMap<String, u64>,
25
26    /// node id → Vec<CompletionItem> from methodIdentifiers in .contracts section.
27    /// Full function signatures with 4-byte selectors for contracts/interfaces.
28    pub method_identifiers: HashMap<u64, Vec<CompletionItem>>,
29
30    /// (contract_node_id, fn_name) → return typeIdentifier.
31    /// For resolving `foo().` — look up what `foo` returns.
32    pub function_return_types: HashMap<(u64, String), String>,
33
34    /// typeIdentifier → Vec<CompletionItem> from UsingForDirective.
35    /// Library functions available on a type via `using X for Y`.
36    pub using_for: HashMap<String, Vec<CompletionItem>>,
37
38    /// Wildcard using-for: `using X for *` — available on all types.
39    pub using_for_wildcard: Vec<CompletionItem>,
40}
41
42fn push_if_node_or_array<'a>(tree: &'a Value, key: &str, stack: &mut Vec<&'a Value>) {
43    if let Some(value) = tree.get(key) {
44        match value {
45            Value::Array(arr) => stack.extend(arr),
46            Value::Object(_) => stack.push(value),
47            _ => {}
48        }
49    }
50}
51
52/// Map AST nodeType to LSP CompletionItemKind.
53fn node_type_to_completion_kind(node_type: &str) -> CompletionItemKind {
54    match node_type {
55        "FunctionDefinition" => CompletionItemKind::FUNCTION,
56        "VariableDeclaration" => CompletionItemKind::VARIABLE,
57        "ContractDefinition" => CompletionItemKind::CLASS,
58        "StructDefinition" => CompletionItemKind::STRUCT,
59        "EnumDefinition" => CompletionItemKind::ENUM,
60        "EnumValue" => CompletionItemKind::ENUM_MEMBER,
61        "EventDefinition" => CompletionItemKind::EVENT,
62        "ErrorDefinition" => CompletionItemKind::EVENT,
63        "ModifierDefinition" => CompletionItemKind::METHOD,
64        "ImportDirective" => CompletionItemKind::MODULE,
65        _ => CompletionItemKind::TEXT,
66    }
67}
68
69/// Extract the trailing node id from a typeIdentifier string.
70/// e.g. `t_struct$_PoolKey_$8887_storage_ptr` → Some(8887)
71///      `t_contract$_IHooks_$2248` → Some(2248)
72///      `t_uint256` → None
73pub fn extract_node_id_from_type(type_id: &str) -> Option<u64> {
74    // Pattern: ..._$<digits>... where digits follow the last _$
75    // We find all _$<digits> groups and take the last one that's part of the type name
76    let mut last_id = None;
77    let mut i = 0;
78    let bytes = type_id.as_bytes();
79    while i < bytes.len() {
80        if i + 1 < bytes.len() && bytes[i] == b'_' && bytes[i + 1] == b'$' {
81            i += 2;
82            let start = i;
83            while i < bytes.len() && bytes[i].is_ascii_digit() {
84                i += 1;
85            }
86            if i > start {
87                if let Ok(id) = type_id[start..i].parse::<u64>() {
88                    last_id = Some(id);
89                }
90            }
91        } else {
92            i += 1;
93        }
94    }
95    last_id
96}
97
98/// Build a human-readable function signature from a FunctionDefinition AST node.
99/// e.g. `swap(PoolKey key, SwapParams params, bytes hookData) returns (BalanceDelta swapDelta)`
100fn build_function_signature(node: &Value) -> Option<String> {
101    let name = node.get("name").and_then(|v| v.as_str()).unwrap_or("");
102    if name.is_empty() {
103        return None;
104    }
105
106    let params = node
107        .get("parameters")
108        .and_then(|p| p.get("parameters"))
109        .and_then(|v| v.as_array());
110
111    let mut sig = String::new();
112    sig.push_str(name);
113    sig.push('(');
114
115    if let Some(params) = params {
116        for (i, param) in params.iter().enumerate() {
117            if i > 0 {
118                sig.push_str(", ");
119            }
120            let type_str = param
121                .get("typeDescriptions")
122                .and_then(|td| td.get("typeString"))
123                .and_then(|v| v.as_str())
124                .unwrap_or("?");
125            // Clean up the type string: "struct PoolKey" → "PoolKey", "contract IHooks" → "IHooks"
126            let clean_type = type_str
127                .strip_prefix("struct ")
128                .or_else(|| type_str.strip_prefix("contract "))
129                .or_else(|| type_str.strip_prefix("enum "))
130                .unwrap_or(type_str);
131            let param_name = param.get("name").and_then(|v| v.as_str()).unwrap_or("");
132            sig.push_str(clean_type);
133            if !param_name.is_empty() {
134                sig.push(' ');
135                sig.push_str(param_name);
136            }
137        }
138    }
139    sig.push(')');
140
141    // Add return parameters if present
142    let returns = node
143        .get("returnParameters")
144        .and_then(|p| p.get("parameters"))
145        .and_then(|v| v.as_array());
146
147    if let Some(returns) = returns {
148        if !returns.is_empty() {
149            sig.push_str(" returns (");
150            for (i, ret) in returns.iter().enumerate() {
151                if i > 0 {
152                    sig.push_str(", ");
153                }
154                let type_str = ret
155                    .get("typeDescriptions")
156                    .and_then(|td| td.get("typeString"))
157                    .and_then(|v| v.as_str())
158                    .unwrap_or("?");
159                let clean_type = type_str
160                    .strip_prefix("struct ")
161                    .or_else(|| type_str.strip_prefix("contract "))
162                    .or_else(|| type_str.strip_prefix("enum "))
163                    .unwrap_or(type_str);
164                let ret_name = ret.get("name").and_then(|v| v.as_str()).unwrap_or("");
165                sig.push_str(clean_type);
166                if !ret_name.is_empty() {
167                    sig.push(' ');
168                    sig.push_str(ret_name);
169                }
170            }
171            sig.push(')');
172        }
173    }
174
175    Some(sig)
176}
177
178/// Extract the deepest value type from a mapping typeIdentifier.
179/// Peels off all `t_mapping$_<key>_$_<value>` layers and returns the innermost value type.
180///
181/// e.g. `t_mapping$_t_address_$_t_uint256_$` → `t_uint256`
182///      `t_mapping$_t_address_$_t_mapping$_t_uint256_$_t_uint256_$_$` → `t_uint256`
183///      `t_mapping$_t_userDefinedValueType$_PoolId_$8841_$_t_struct$_State_$4809_storage_$` → `t_struct$_State_$4809_storage`
184pub fn extract_mapping_value_type(type_id: &str) -> Option<String> {
185    let mut current = type_id;
186
187    loop {
188        if !current.starts_with("t_mapping$_") {
189            // Not a mapping — this is the value type
190            // Strip trailing _$ suffixes (mapping closers)
191            let result = current.trim_end_matches("_$");
192            return if result.is_empty() {
193                None
194            } else {
195                Some(result.to_string())
196            };
197        }
198
199        // Strip "t_mapping$_" prefix to get "<key>_$_<value>_$"
200        let inner = &current["t_mapping$_".len()..];
201
202        // Find the boundary between key and value.
203        // We need to find the _$_ that separates key from value at depth 0.
204        // Each $_ opens a nesting level, each _$ closes one.
205        let mut depth = 0i32;
206        let bytes = inner.as_bytes();
207        let mut split_pos = None;
208
209        let mut i = 0;
210        while i < bytes.len() {
211            if i + 1 < bytes.len() && bytes[i] == b'$' && bytes[i + 1] == b'_' {
212                depth += 1;
213                i += 2;
214            } else if i + 2 < bytes.len()
215                && bytes[i] == b'_'
216                && bytes[i + 1] == b'$'
217                && bytes[i + 2] == b'_'
218                && depth == 0
219            {
220                // This is the _$_ separator at depth 0
221                split_pos = Some(i);
222                break;
223            } else if i + 1 < bytes.len() && bytes[i] == b'_' && bytes[i + 1] == b'$' {
224                depth -= 1;
225                i += 2;
226            } else {
227                i += 1;
228            }
229        }
230
231        if let Some(pos) = split_pos {
232            // Value type starts after "_$_"
233            current = &inner[pos + 3..];
234        } else {
235            return None;
236        }
237    }
238}
239
240/// Count parameters in an ABI method signature like "swap((address,address),uint256,bytes)".
241/// Counts commas at depth 0 (inside the outer parens), handling nested tuples.
242fn count_abi_params(signature: &str) -> usize {
243    // Find the first '(' and work from there
244    let start = match signature.find('(') {
245        Some(i) => i + 1,
246        None => return 0,
247    };
248    let bytes = signature.as_bytes();
249    if start >= bytes.len() {
250        return 0;
251    }
252    // Check for empty params "()"
253    if bytes[start] == b')' {
254        return 0;
255    }
256    let mut count = 1; // at least one param if not empty
257    let mut depth = 0;
258    for &b in &bytes[start..] {
259        match b {
260            b'(' => depth += 1,
261            b')' => {
262                if depth == 0 {
263                    break;
264                }
265                depth -= 1;
266            }
267            b',' if depth == 0 => count += 1,
268            _ => {}
269        }
270    }
271    count
272}
273
274/// Count parameters in an AST-derived signature like "swap(PoolKey key, SwapParams params, bytes hookData)".
275fn count_signature_params(sig: &str) -> usize {
276    count_abi_params(sig)
277}
278
279/// Build a CompletionCache from AST sources and contracts.
280/// `contracts` is the `.contracts` section of the compiler output (optional).
281pub fn build_completion_cache(sources: &Value, contracts: Option<&Value>) -> CompletionCache {
282    let mut names: Vec<CompletionItem> = Vec::new();
283    let mut seen_names: HashMap<String, usize> = HashMap::new(); // name → index in names vec
284    let mut name_to_type: HashMap<String, String> = HashMap::new();
285    let mut node_members: HashMap<u64, Vec<CompletionItem>> = HashMap::new();
286    let mut type_to_node: HashMap<String, u64> = HashMap::new();
287    let mut method_identifiers: HashMap<u64, Vec<CompletionItem>> = HashMap::new();
288    let mut name_to_node_id: HashMap<String, u64> = HashMap::new();
289
290    // Collect (path, contract_name, node_id) during AST walk for methodIdentifiers lookup after.
291    let mut contract_locations: Vec<(String, String, u64)> = Vec::new();
292
293    // contract_node_id → fn_name → Vec<signature> (for matching method_identifiers to AST signatures)
294    let mut function_signatures: HashMap<u64, HashMap<String, Vec<String>>> = HashMap::new();
295
296    // (contract_node_id, fn_name) → return typeIdentifier
297    let mut function_return_types: HashMap<(u64, String), String> = HashMap::new();
298
299    // typeIdentifier → Vec<CompletionItem> from UsingForDirective
300    let mut using_for: HashMap<String, Vec<CompletionItem>> = HashMap::new();
301    let mut using_for_wildcard: Vec<CompletionItem> = Vec::new();
302
303    // Temp: (library_node_id, target_type_id_or_none) for resolving after walk
304    let mut using_for_directives: Vec<(u64, Option<String>)> = Vec::new();
305
306    if let Some(sources_obj) = sources.as_object() {
307        for (path, contents) in sources_obj {
308            if let Some(contents_array) = contents.as_array()
309                && let Some(first_content) = contents_array.first()
310                && let Some(source_file) = first_content.get("source_file")
311                && let Some(ast) = source_file.get("ast")
312            {
313                let mut stack: Vec<&Value> = vec![ast];
314
315                while let Some(tree) = stack.pop() {
316                    let node_type = tree
317                        .get("nodeType")
318                        .and_then(|v| v.as_str())
319                        .unwrap_or("");
320                    let name = tree.get("name").and_then(|v| v.as_str()).unwrap_or("");
321                    let node_id = tree.get("id").and_then(|v| v.as_u64());
322
323                    // Collect named nodes as completion items
324                    if !name.is_empty() && !seen_names.contains_key(name) {
325                        let type_string = tree
326                            .get("typeDescriptions")
327                            .and_then(|td| td.get("typeString"))
328                            .and_then(|v| v.as_str())
329                            .map(|s| s.to_string());
330
331                        let type_id = tree
332                            .get("typeDescriptions")
333                            .and_then(|td| td.get("typeIdentifier"))
334                            .and_then(|v| v.as_str());
335
336                        let kind = node_type_to_completion_kind(node_type);
337
338                        let item = CompletionItem {
339                            label: name.to_string(),
340                            kind: Some(kind),
341                            detail: type_string,
342                            ..Default::default()
343                        };
344
345                        let idx = names.len();
346                        names.push(item);
347                        seen_names.insert(name.to_string(), idx);
348
349                        // Store name → typeIdentifier mapping
350                        if let Some(tid) = type_id {
351                            name_to_type.insert(name.to_string(), tid.to_string());
352                        }
353                    }
354
355                    // Collect struct members
356                    if node_type == "StructDefinition" {
357                        if let Some(id) = node_id {
358                            let mut members = Vec::new();
359                            if let Some(member_array) =
360                                tree.get("members").and_then(|v| v.as_array())
361                            {
362                                for member in member_array {
363                                    let member_name =
364                                        member.get("name").and_then(|v| v.as_str()).unwrap_or("");
365                                    if member_name.is_empty() {
366                                        continue;
367                                    }
368                                    let member_type = member
369                                        .get("typeDescriptions")
370                                        .and_then(|td| td.get("typeString"))
371                                        .and_then(|v| v.as_str())
372                                        .map(|s| s.to_string());
373
374                                    members.push(CompletionItem {
375                                        label: member_name.to_string(),
376                                        kind: Some(CompletionItemKind::FIELD),
377                                        detail: member_type,
378                                        ..Default::default()
379                                    });
380                                }
381                            }
382                            if !members.is_empty() {
383                                node_members.insert(id, members);
384                            }
385
386                            // Map typeIdentifier → node id
387                            if let Some(tid) = tree
388                                .get("typeDescriptions")
389                                .and_then(|td| td.get("typeIdentifier"))
390                                .and_then(|v| v.as_str())
391                            {
392                                type_to_node.insert(tid.to_string(), id);
393                            }
394                        }
395                    }
396
397                    // Collect contract/library members (functions, state variables, events, etc.)
398                    if node_type == "ContractDefinition" {
399                        if let Some(id) = node_id {
400                            let mut members = Vec::new();
401                            let mut fn_sigs: HashMap<String, Vec<String>> = HashMap::new();
402                            if let Some(nodes_array) =
403                                tree.get("nodes").and_then(|v| v.as_array())
404                            {
405                                for member in nodes_array {
406                                    let member_type = member
407                                        .get("nodeType")
408                                        .and_then(|v| v.as_str())
409                                        .unwrap_or("");
410                                    let member_name =
411                                        member.get("name").and_then(|v| v.as_str()).unwrap_or("");
412                                    if member_name.is_empty() {
413                                        continue;
414                                    }
415
416                                    // Build function signature and collect return types for FunctionDefinitions
417                                    let (member_detail, label_details) =
418                                        if member_type == "FunctionDefinition" {
419                                            // Collect return type for chain resolution.
420                                            // Only single-return functions can be dot-chained
421                                            // (tuples require destructuring).
422                                            if let Some(ret_params) = member
423                                                .get("returnParameters")
424                                                .and_then(|rp| rp.get("parameters"))
425                                                .and_then(|v| v.as_array())
426                                            {
427                                                if ret_params.len() == 1 {
428                                                    if let Some(ret_tid) = ret_params[0]
429                                                        .get("typeDescriptions")
430                                                        .and_then(|td| td.get("typeIdentifier"))
431                                                        .and_then(|v| v.as_str())
432                                                    {
433                                                        function_return_types.insert(
434                                                            (id, member_name.to_string()),
435                                                            ret_tid.to_string(),
436                                                        );
437                                                    }
438                                                }
439                                            }
440
441                                            if let Some(sig) = build_function_signature(member) {
442                                                fn_sigs
443                                                    .entry(member_name.to_string())
444                                                    .or_default()
445                                                    .push(sig.clone());
446                                                (
447                                                    Some(sig),
448                                                    None,
449                                                )
450                                            } else {
451                                                (
452                                                    member
453                                                        .get("typeDescriptions")
454                                                        .and_then(|td| td.get("typeString"))
455                                                        .and_then(|v| v.as_str())
456                                                        .map(|s| s.to_string()),
457                                                    None,
458                                                )
459                                            }
460                                        } else {
461                                            (
462                                                member
463                                                    .get("typeDescriptions")
464                                                    .and_then(|td| td.get("typeString"))
465                                                    .and_then(|v| v.as_str())
466                                                    .map(|s| s.to_string()),
467                                                None,
468                                            )
469                                        };
470
471                                    let kind = node_type_to_completion_kind(member_type);
472                                    members.push(CompletionItem {
473                                        label: member_name.to_string(),
474                                        kind: Some(kind),
475                                        detail: member_detail,
476                                        label_details,
477                                        ..Default::default()
478                                    });
479                                }
480                            }
481                            if !members.is_empty() {
482                                node_members.insert(id, members);
483                            }
484                            if !fn_sigs.is_empty() {
485                                function_signatures.insert(id, fn_sigs);
486                            }
487
488                            if let Some(tid) = tree
489                                .get("typeDescriptions")
490                                .and_then(|td| td.get("typeIdentifier"))
491                                .and_then(|v| v.as_str())
492                            {
493                                type_to_node.insert(tid.to_string(), id);
494                            }
495
496                            // Record for methodIdentifiers lookup after traversal
497                            if !name.is_empty() {
498                                contract_locations.push((
499                                    path.clone(),
500                                    name.to_string(),
501                                    id,
502                                ));
503                                name_to_node_id.insert(name.to_string(), id);
504                            }
505                        }
506                    }
507
508                    // Collect enum members
509                    if node_type == "EnumDefinition" {
510                        if let Some(id) = node_id {
511                            let mut members = Vec::new();
512                            if let Some(member_array) =
513                                tree.get("members").and_then(|v| v.as_array())
514                            {
515                                for member in member_array {
516                                    let member_name =
517                                        member.get("name").and_then(|v| v.as_str()).unwrap_or("");
518                                    if member_name.is_empty() {
519                                        continue;
520                                    }
521                                    members.push(CompletionItem {
522                                        label: member_name.to_string(),
523                                        kind: Some(CompletionItemKind::ENUM_MEMBER),
524                                        detail: None,
525                                        ..Default::default()
526                                    });
527                                }
528                            }
529                            if !members.is_empty() {
530                                node_members.insert(id, members);
531                            }
532
533                            if let Some(tid) = tree
534                                .get("typeDescriptions")
535                                .and_then(|td| td.get("typeIdentifier"))
536                                .and_then(|v| v.as_str())
537                            {
538                                type_to_node.insert(tid.to_string(), id);
539                            }
540                        }
541                    }
542
543                    // Collect UsingForDirective: using Library for Type
544                    if node_type == "UsingForDirective" {
545                        // Get target type (None = wildcard `for *`)
546                        let target_type = tree
547                            .get("typeName")
548                            .and_then(|tn| {
549                                tn.get("typeDescriptions")
550                                    .and_then(|td| td.get("typeIdentifier"))
551                                    .and_then(|v| v.as_str())
552                                    .map(|s| s.to_string())
553                            });
554
555                        // Form 1: library name object with referencedDeclaration
556                        if let Some(lib) = tree.get("libraryName") {
557                            if let Some(lib_id) = lib
558                                .get("referencedDeclaration")
559                                .and_then(|v| v.as_u64())
560                            {
561                                using_for_directives.push((lib_id, target_type));
562                            }
563                        }
564                        // Form 2: functionList array — individual function references
565                        // These are typically operator overloads (not dot-callable),
566                        // but collect non-operator ones just in case
567                        else if let Some(func_list) =
568                            tree.get("functionList").and_then(|v| v.as_array())
569                        {
570                            for entry in func_list {
571                                // Skip operator overloads
572                                if entry.get("operator").is_some() {
573                                    continue;
574                                }
575                                if let Some(def) = entry.get("definition") {
576                                    let fn_name = def
577                                        .get("name")
578                                        .and_then(|v| v.as_str())
579                                        .unwrap_or("");
580                                    if !fn_name.is_empty() {
581                                        let items = if let Some(ref tid) = target_type {
582                                            using_for.entry(tid.clone()).or_default()
583                                        } else {
584                                            &mut using_for_wildcard
585                                        };
586                                        items.push(CompletionItem {
587                                            label: fn_name.to_string(),
588                                            kind: Some(CompletionItemKind::FUNCTION),
589                                            detail: None,
590                                            ..Default::default()
591                                        });
592                                    }
593                                }
594                            }
595                        }
596                    }
597
598                    // Traverse children
599                    for key in CHILD_KEYS {
600                        push_if_node_or_array(tree, key, &mut stack);
601                    }
602                }
603            }
604        }
605    }
606
607    // Resolve UsingForDirective library references (Form 1)
608    // Now that node_members is populated, look up each library's functions
609    for (lib_id, target_type) in &using_for_directives {
610        if let Some(lib_members) = node_members.get(lib_id) {
611            let items: Vec<CompletionItem> = lib_members
612                .iter()
613                .filter(|item| item.kind == Some(CompletionItemKind::FUNCTION))
614                .cloned()
615                .collect();
616            if !items.is_empty() {
617                if let Some(tid) = target_type {
618                    using_for.entry(tid.clone()).or_default().extend(items);
619                } else {
620                    using_for_wildcard.extend(items);
621                }
622            }
623        }
624    }
625
626    // Build method_identifiers from .contracts section
627    if let Some(contracts_val) = contracts {
628        if let Some(contracts_obj) = contracts_val.as_object() {
629            for (path, contract_name, node_id) in &contract_locations {
630                // Get AST function signatures for this contract (if available)
631                let fn_sigs = function_signatures.get(node_id);
632
633                if let Some(path_entry) = contracts_obj.get(path)
634                    && let Some(contract_entry) = path_entry.get(contract_name)
635                    && let Some(first) = contract_entry.get(0)
636                    && let Some(evm) = first
637                        .get("contract")
638                        .and_then(|c| c.get("evm"))
639                    && let Some(methods) = evm.get("methodIdentifiers")
640                    && let Some(methods_obj) = methods.as_object()
641                {
642                    let mut items: Vec<CompletionItem> = Vec::new();
643                    for (signature, selector) in methods_obj {
644                        // signature is e.g. "swap((address,address,uint24,int24,address),(bool,int256,uint160),bytes)"
645                        // selector is e.g. "f3cd914c"
646                        let fn_name = signature
647                            .split('(')
648                            .next()
649                            .unwrap_or(signature)
650                            .to_string();
651                        let selector_str = selector
652                            .as_str()
653                            .map(|s| format!("0x{}", s))
654                            .unwrap_or_default();
655
656                        // Look up the AST signature with parameter names
657                        let description = fn_sigs
658                            .and_then(|sigs| sigs.get(&fn_name))
659                            .and_then(|sig_list| {
660                                if sig_list.len() == 1 {
661                                    // Only one overload — use it directly
662                                    Some(sig_list[0].clone())
663                                } else {
664                                    // Multiple overloads — match by parameter count
665                                    let abi_param_count =
666                                        count_abi_params(signature);
667                                    sig_list.iter().find(|s| {
668                                        count_signature_params(s) == abi_param_count
669                                    }).cloned()
670                                }
671                            });
672
673                        items.push(CompletionItem {
674                            label: fn_name,
675                            kind: Some(CompletionItemKind::FUNCTION),
676                            detail: Some(signature.clone()),
677                            label_details: Some(tower_lsp::lsp_types::CompletionItemLabelDetails {
678                                detail: Some(selector_str),
679                                description,
680                            }),
681                            ..Default::default()
682                        });
683                    }
684                    if !items.is_empty() {
685                        method_identifiers.insert(*node_id, items);
686                    }
687                }
688            }
689        }
690    }
691
692    CompletionCache {
693        names,
694        name_to_type,
695        node_members,
696        type_to_node,
697        name_to_node_id,
698        method_identifiers,
699        function_return_types,
700        using_for,
701        using_for_wildcard,
702    }
703}
704
705/// Magic type member definitions (msg, block, tx, abi, address).
706fn magic_members(name: &str) -> Option<Vec<CompletionItem>> {
707    let items = match name {
708        "msg" => vec![
709            ("data", "bytes calldata"),
710            ("sender", "address"),
711            ("sig", "bytes4"),
712            ("value", "uint256"),
713        ],
714        "block" => vec![
715            ("basefee", "uint256"),
716            ("blobbasefee", "uint256"),
717            ("chainid", "uint256"),
718            ("coinbase", "address payable"),
719            ("difficulty", "uint256"),
720            ("gaslimit", "uint256"),
721            ("number", "uint256"),
722            ("prevrandao", "uint256"),
723            ("timestamp", "uint256"),
724        ],
725        "tx" => vec![("gasprice", "uint256"), ("origin", "address")],
726        "abi" => vec![
727            ("decode(bytes memory, (...))", "..."),
728            ("encode(...)", "bytes memory"),
729            ("encodePacked(...)", "bytes memory"),
730            ("encodeWithSelector(bytes4, ...)", "bytes memory"),
731            ("encodeWithSignature(string memory, ...)", "bytes memory"),
732            ("encodeCall(function, (...))", "bytes memory"),
733        ],
734        // type(X) — contract type properties
735        // Also includes interface (interfaceId) and integer (min, max) properties
736        "type" => vec![
737            ("name", "string"),
738            ("creationCode", "bytes memory"),
739            ("runtimeCode", "bytes memory"),
740            ("interfaceId", "bytes4"),
741            ("min", "T"),
742            ("max", "T"),
743        ],
744        // bytes and string type-level members
745        "bytes" => vec![("concat(...)", "bytes memory")],
746        "string" => vec![("concat(...)", "string memory")],
747        _ => return None,
748    };
749
750    Some(
751        items
752            .into_iter()
753            .map(|(label, detail)| CompletionItem {
754                label: label.to_string(),
755                kind: Some(CompletionItemKind::PROPERTY),
756                detail: Some(detail.to_string()),
757                ..Default::default()
758            })
759            .collect(),
760    )
761}
762
763/// Address type members (available on any address value).
764fn address_members() -> Vec<CompletionItem> {
765    [
766        ("balance", "uint256", CompletionItemKind::PROPERTY),
767        ("code", "bytes memory", CompletionItemKind::PROPERTY),
768        ("codehash", "bytes32", CompletionItemKind::PROPERTY),
769        ("transfer(uint256)", "", CompletionItemKind::FUNCTION),
770        ("send(uint256)", "bool", CompletionItemKind::FUNCTION),
771        ("call(bytes memory)", "(bool, bytes memory)", CompletionItemKind::FUNCTION),
772        ("delegatecall(bytes memory)", "(bool, bytes memory)", CompletionItemKind::FUNCTION),
773        ("staticcall(bytes memory)", "(bool, bytes memory)", CompletionItemKind::FUNCTION),
774    ]
775    .iter()
776    .map(|(label, detail, kind)| CompletionItem {
777        label: label.to_string(),
778        kind: Some(*kind),
779        detail: if detail.is_empty() {
780            None
781        } else {
782            Some(detail.to_string())
783        },
784        ..Default::default()
785    })
786    .collect()
787}
788
789/// What kind of access precedes the dot.
790#[derive(Debug, Clone, PartialEq)]
791pub enum AccessKind {
792    /// Plain identifier: `foo.`
793    Plain,
794    /// Function call: `foo().` or `foo(x, bar()).`
795    Call,
796    /// Index/storage access: `foo[key].` or `foo[func()].`
797    Index,
798}
799
800/// A segment of a dot-expression chain.
801#[derive(Debug, Clone, PartialEq)]
802pub struct DotSegment {
803    pub name: String,
804    pub kind: AccessKind,
805}
806
807/// Skip backwards over a matched bracket pair (parens or square brackets).
808/// `pos` should point to the closing bracket. Returns the position of the matching
809/// opening bracket, or 0 if not found.
810fn skip_brackets_backwards(bytes: &[u8], pos: usize) -> usize {
811    let close = bytes[pos];
812    let open = match close {
813        b')' => b'(',
814        b']' => b'[',
815        _ => return pos,
816    };
817    let mut depth = 1u32;
818    let mut i = pos;
819    while i > 0 && depth > 0 {
820        i -= 1;
821        if bytes[i] == close {
822            depth += 1;
823        } else if bytes[i] == open {
824            depth -= 1;
825        }
826    }
827    i
828}
829
830/// Parse the expression chain before the dot into segments.
831/// e.g. `poolManager.swap(key, params).` → [("poolManager", Plain), ("swap", Call)]
832///      `_pools[poolId].fee.` → [("_pools", Index), ("fee", Plain)]
833///      `msg.` → [("msg", Plain)]
834pub fn parse_dot_chain(line: &str, character: u32) -> Vec<DotSegment> {
835    let col = character as usize;
836    if col == 0 {
837        return vec![];
838    }
839
840    let bytes = line.as_bytes();
841    let mut segments: Vec<DotSegment> = Vec::new();
842
843    // Start from the cursor position, skip trailing dot
844    let mut pos = col;
845    if pos > 0 && pos <= bytes.len() && bytes[pos - 1] == b'.' {
846        pos -= 1;
847    }
848
849    loop {
850        if pos == 0 {
851            break;
852        }
853
854        // Determine access kind by what's immediately before: ')' = Call, ']' = Index, else Plain
855        let kind = if bytes[pos - 1] == b')' {
856            pos -= 1; // point to ')'
857            pos = skip_brackets_backwards(bytes, pos);
858            AccessKind::Call
859        } else if bytes[pos - 1] == b']' {
860            pos -= 1; // point to ']'
861            pos = skip_brackets_backwards(bytes, pos);
862            AccessKind::Index
863        } else {
864            AccessKind::Plain
865        };
866
867        // Now extract the identifier name (walk backwards over alphanumeric + underscore)
868        let end = pos;
869        while pos > 0 && (bytes[pos - 1].is_ascii_alphanumeric() || bytes[pos - 1] == b'_') {
870            pos -= 1;
871        }
872
873        if pos == end {
874            // No identifier found (could be something like `().`)
875            break;
876        }
877
878        let name = String::from_utf8_lossy(&bytes[pos..end]).to_string();
879        segments.push(DotSegment { name, kind });
880
881        // Check if there's a dot before this segment (meaning more chain)
882        if pos > 0 && bytes[pos - 1] == b'.' {
883            pos -= 1; // skip the dot, continue parsing next segment
884        } else {
885            break;
886        }
887    }
888
889    segments.reverse(); // We parsed right-to-left, flip to left-to-right
890    segments
891}
892
893/// Extract the identifier before the cursor (the word before the dot).
894/// Returns just the last identifier name for backward compatibility.
895pub fn extract_identifier_before_dot(line: &str, character: u32) -> Option<String> {
896    let segments = parse_dot_chain(line, character);
897    segments.last().map(|s| s.name.clone())
898}
899
900/// Strip all storage/memory location suffixes from a typeIdentifier to get the base type.
901/// Solidity AST uses different suffixes in different contexts:
902///   - `t_struct$_State_$4809_storage_ptr` (UsingForDirective typeName)
903///   - `t_struct$_State_$4809_storage` (mapping value type after extraction)
904///   - `t_struct$_PoolKey_$8887_memory_ptr` (function parameter)
905/// All refer to the same logical type. This strips `_ptr` and `_storage`/`_memory`/`_calldata`.
906fn strip_type_suffix(type_id: &str) -> &str {
907    let s = type_id.strip_suffix("_ptr").unwrap_or(type_id);
908    s.strip_suffix("_storage")
909        .or_else(|| s.strip_suffix("_memory"))
910        .or_else(|| s.strip_suffix("_calldata"))
911        .unwrap_or(s)
912}
913
914/// Look up using-for completions for a type, trying suffix variants.
915/// The AST stores types with different suffixes (_storage_ptr, _storage, _memory_ptr, etc.)
916/// across different contexts, so we try multiple forms.
917fn lookup_using_for(cache: &CompletionCache, type_id: &str) -> Vec<CompletionItem> {
918    // Exact match first
919    if let Some(items) = cache.using_for.get(type_id) {
920        return items.clone();
921    }
922
923    // Strip to base form, then try all common suffix variants
924    let base = strip_type_suffix(type_id);
925    let variants = [
926        base.to_string(),
927        format!("{}_storage", base),
928        format!("{}_storage_ptr", base),
929        format!("{}_memory", base),
930        format!("{}_memory_ptr", base),
931        format!("{}_calldata", base),
932    ];
933    for variant in &variants {
934        if variant.as_str() != type_id {
935            if let Some(items) = cache.using_for.get(variant.as_str()) {
936                return items.clone();
937            }
938        }
939    }
940
941    vec![]
942}
943
944/// Collect completions available for a given typeIdentifier.
945/// Includes node_members, method_identifiers, using_for, and using_for_wildcard.
946fn completions_for_type(cache: &CompletionCache, type_id: &str) -> Vec<CompletionItem> {
947    // Address type
948    if type_id == "t_address" || type_id == "t_address_payable" {
949        let mut items = address_members();
950        // Also add using-for on address
951        if let Some(uf) = cache.using_for.get(type_id) {
952            items.extend(uf.iter().cloned());
953        }
954        items.extend(cache.using_for_wildcard.iter().cloned());
955        return items;
956    }
957
958    let resolved_node_id = extract_node_id_from_type(type_id)
959        .or_else(|| cache.type_to_node.get(type_id).copied())
960        .or_else(|| {
961            // Handle synthetic __node_id_ markers from name_to_node_id fallback
962            type_id
963                .strip_prefix("__node_id_")
964                .and_then(|s| s.parse::<u64>().ok())
965        });
966
967    let mut items = Vec::new();
968    let mut seen_labels: std::collections::HashSet<String> = std::collections::HashSet::new();
969
970    if let Some(node_id) = resolved_node_id {
971        // Method identifiers first — they have full signatures with selectors
972        if let Some(method_items) = cache.method_identifiers.get(&node_id) {
973            for item in method_items {
974                seen_labels.insert(item.label.clone());
975                items.push(item.clone());
976            }
977        }
978
979        // Supplement with node_members (state variables, events, errors, modifiers, etc.)
980        if let Some(members) = cache.node_members.get(&node_id) {
981            for item in members {
982                if !seen_labels.contains(&item.label) {
983                    seen_labels.insert(item.label.clone());
984                    items.push(item.clone());
985                }
986            }
987        }
988    }
989
990    // Add using-for library functions for this type
991    // Try exact match first, then try normalized variants (storage_ptr vs storage vs memory_ptr etc.)
992    let uf_items = lookup_using_for(cache, type_id);
993    for item in &uf_items {
994        if !seen_labels.contains(&item.label) {
995            seen_labels.insert(item.label.clone());
996            items.push(item.clone());
997        }
998    }
999
1000    // Add wildcard using-for (using X for *)
1001    for item in &cache.using_for_wildcard {
1002        if !seen_labels.contains(&item.label) {
1003            seen_labels.insert(item.label.clone());
1004            items.push(item.clone());
1005        }
1006    }
1007
1008    items
1009}
1010
1011/// Resolve a type identifier for a name, considering name_to_type and name_to_node_id.
1012fn resolve_name_to_type_id(cache: &CompletionCache, name: &str) -> Option<String> {
1013    // Direct type lookup
1014    if let Some(tid) = cache.name_to_type.get(name) {
1015        return Some(tid.clone());
1016    }
1017    // Contract/library/interface name → synthesize a type id from node id
1018    if let Some(node_id) = cache.name_to_node_id.get(name) {
1019        // Find a matching typeIdentifier in type_to_node (reverse lookup)
1020        for (tid, nid) in &cache.type_to_node {
1021            if nid == node_id {
1022                return Some(tid.clone());
1023            }
1024        }
1025        // Fallback: use a synthetic marker so completions_for_type can resolve via node_id
1026        return Some(format!("__node_id_{}", node_id));
1027    }
1028    None
1029}
1030
1031/// Resolve a name within a type context to get the member's type.
1032/// `context_type_id` is the type of the object before the dot.
1033/// `member_name` is the name after the dot.
1034/// `kind` determines how to interpret the result (Call = return type, Index = mapping value, Plain = member type).
1035fn resolve_member_type(
1036    cache: &CompletionCache,
1037    context_type_id: &str,
1038    member_name: &str,
1039    kind: &AccessKind,
1040) -> Option<String> {
1041    let resolved_node_id = extract_node_id_from_type(context_type_id)
1042        .or_else(|| cache.type_to_node.get(context_type_id).copied())
1043        .or_else(|| {
1044            // Handle synthetic __node_id_ markers
1045            context_type_id
1046                .strip_prefix("__node_id_")
1047                .and_then(|s| s.parse::<u64>().ok())
1048        });
1049
1050    let node_id = resolved_node_id?;
1051
1052    match kind {
1053        AccessKind::Call => {
1054            // Look up the function's return type
1055            cache
1056                .function_return_types
1057                .get(&(node_id, member_name.to_string()))
1058                .cloned()
1059        }
1060        AccessKind::Index => {
1061            // Look up the member's type, then extract mapping value type
1062            if let Some(members) = cache.node_members.get(&node_id) {
1063                for member in members {
1064                    if member.label == member_name {
1065                        // Get the typeIdentifier from name_to_type
1066                        if let Some(tid) = cache.name_to_type.get(member_name) {
1067                            if tid.starts_with("t_mapping") {
1068                                return extract_mapping_value_type(tid);
1069                            }
1070                            return Some(tid.clone());
1071                        }
1072                    }
1073                }
1074            }
1075            // Also check: the identifier itself might be a mapping variable
1076            if let Some(tid) = cache.name_to_type.get(member_name) {
1077                if tid.starts_with("t_mapping") {
1078                    return extract_mapping_value_type(tid);
1079                }
1080            }
1081            None
1082        }
1083        AccessKind::Plain => {
1084            // Look up member's own type from name_to_type
1085            cache.name_to_type.get(member_name).cloned()
1086        }
1087    }
1088}
1089
1090/// Get completions for a dot-completion request by resolving the full expression chain.
1091pub fn get_dot_completions(cache: &CompletionCache, identifier: &str) -> Vec<CompletionItem> {
1092    // Simple single-segment case (backward compat) — just use the identifier directly
1093    if let Some(items) = magic_members(identifier) {
1094        return items;
1095    }
1096
1097    // Try to resolve the identifier's type
1098    let type_id = resolve_name_to_type_id(cache, identifier);
1099
1100    if let Some(tid) = type_id {
1101        return completions_for_type(cache, &tid);
1102    }
1103
1104    vec![]
1105}
1106
1107/// Get completions by resolving a full dot-expression chain.
1108/// This is the main entry point for dot-completions with chaining support.
1109pub fn get_chain_completions(cache: &CompletionCache, chain: &[DotSegment]) -> Vec<CompletionItem> {
1110    if chain.is_empty() {
1111        return vec![];
1112    }
1113
1114    // Single segment: simple dot-completion
1115    if chain.len() == 1 {
1116        let seg = &chain[0];
1117
1118        // For Call/Index on the single segment, we need to resolve the return/value type
1119        match seg.kind {
1120            AccessKind::Plain => {
1121                return get_dot_completions(cache, &seg.name);
1122            }
1123            AccessKind::Call => {
1124                // foo(). — could be a function call or a type cast like IFoo(addr).
1125                // First check if it's a type cast: name matches a contract/interface/library
1126                if let Some(type_id) = resolve_name_to_type_id(cache, &seg.name) {
1127                    return completions_for_type(cache, &type_id);
1128                }
1129                // Otherwise look up as a function call — check all function_return_types
1130                for ((_, fn_name), ret_type) in &cache.function_return_types {
1131                    if fn_name == &seg.name {
1132                        return completions_for_type(cache, ret_type);
1133                    }
1134                }
1135                return vec![];
1136            }
1137            AccessKind::Index => {
1138                // foo[key]. — look up foo's type and extract mapping value type
1139                if let Some(tid) = cache.name_to_type.get(&seg.name) {
1140                    if tid.starts_with("t_mapping") {
1141                        if let Some(val_type) = extract_mapping_value_type(tid) {
1142                            return completions_for_type(cache, &val_type);
1143                        }
1144                    }
1145                }
1146                return vec![];
1147            }
1148        }
1149    }
1150
1151    // Multi-segment chain: resolve step by step
1152    // First segment: resolve to a type
1153    let first = &chain[0];
1154    let mut current_type = match first.kind {
1155        AccessKind::Plain => resolve_name_to_type_id(cache, &first.name),
1156        AccessKind::Call => {
1157            // Type cast (e.g. IFoo(addr).) or free function call at the start
1158            resolve_name_to_type_id(cache, &first.name).or_else(|| {
1159                cache
1160                    .function_return_types
1161                    .iter()
1162                    .find(|((_, fn_name), _)| fn_name == &first.name)
1163                    .map(|(_, ret_type)| ret_type.clone())
1164            })
1165        }
1166        AccessKind::Index => {
1167            // Mapping access at the start
1168            cache.name_to_type.get(&first.name).and_then(|tid| {
1169                if tid.starts_with("t_mapping") {
1170                    extract_mapping_value_type(tid)
1171                } else {
1172                    Some(tid.clone())
1173                }
1174            })
1175        }
1176    };
1177
1178    // Middle segments: resolve each to advance the type
1179    for seg in &chain[1..] {
1180        let ctx_type = match &current_type {
1181            Some(t) => t.clone(),
1182            None => return vec![],
1183        };
1184
1185        current_type = resolve_member_type(cache, &ctx_type, &seg.name, &seg.kind);
1186    }
1187
1188    // Return completions for the final resolved type
1189    match current_type {
1190        Some(tid) => completions_for_type(cache, &tid),
1191        None => vec![],
1192    }
1193}
1194
1195/// Get general completions (all known names).
1196pub fn get_general_completions(cache: &CompletionCache) -> Vec<CompletionItem> {
1197    let mut items = cache.names.clone();
1198
1199    // Add Solidity keywords
1200    for kw in SOLIDITY_KEYWORDS {
1201        items.push(CompletionItem {
1202            label: kw.to_string(),
1203            kind: Some(CompletionItemKind::KEYWORD),
1204            ..Default::default()
1205        });
1206    }
1207
1208    // Add magic globals
1209    for (name, detail) in MAGIC_GLOBALS {
1210        items.push(CompletionItem {
1211            label: name.to_string(),
1212            kind: Some(CompletionItemKind::VARIABLE),
1213            detail: Some(detail.to_string()),
1214            ..Default::default()
1215        });
1216    }
1217
1218    // Add global functions
1219    for (name, detail) in GLOBAL_FUNCTIONS {
1220        items.push(CompletionItem {
1221            label: name.to_string(),
1222            kind: Some(CompletionItemKind::FUNCTION),
1223            detail: Some(detail.to_string()),
1224            ..Default::default()
1225        });
1226    }
1227
1228    // Add ether denomination units
1229    for (name, detail) in ETHER_UNITS {
1230        items.push(CompletionItem {
1231            label: name.to_string(),
1232            kind: Some(CompletionItemKind::UNIT),
1233            detail: Some(detail.to_string()),
1234            ..Default::default()
1235        });
1236    }
1237
1238    // Add time units
1239    for (name, detail) in TIME_UNITS {
1240        items.push(CompletionItem {
1241            label: name.to_string(),
1242            kind: Some(CompletionItemKind::UNIT),
1243            detail: Some(detail.to_string()),
1244            ..Default::default()
1245        });
1246    }
1247
1248    items
1249}
1250
1251/// Handle a completion request.
1252pub fn handle_completion(
1253    ast_data: &Value,
1254    source_text: &str,
1255    position: Position,
1256    trigger_char: Option<&str>,
1257) -> Option<CompletionResponse> {
1258    let sources = ast_data.get("sources")?;
1259    let contracts = ast_data.get("contracts");
1260    let cache = build_completion_cache(sources, contracts);
1261
1262    let lines: Vec<&str> = source_text.lines().collect();
1263    let line = lines.get(position.line as usize)?;
1264
1265    let items = if trigger_char == Some(".") {
1266        let chain = parse_dot_chain(line, position.character);
1267        if chain.is_empty() {
1268            return None;
1269        }
1270        get_chain_completions(&cache, &chain)
1271    } else {
1272        get_general_completions(&cache)
1273    };
1274
1275    Some(CompletionResponse::List(CompletionList {
1276        is_incomplete: false,
1277        items,
1278    }))
1279}
1280
1281const SOLIDITY_KEYWORDS: &[&str] = &[
1282    "abstract",
1283    "address",
1284    "assembly",
1285    "bool",
1286    "break",
1287    "bytes",
1288    "bytes1",
1289    "bytes4",
1290    "bytes32",
1291    "calldata",
1292    "constant",
1293    "constructor",
1294    "continue",
1295    "contract",
1296    "delete",
1297    "do",
1298    "else",
1299    "emit",
1300    "enum",
1301    "error",
1302    "event",
1303    "external",
1304    "fallback",
1305    "false",
1306    "for",
1307    "function",
1308    "if",
1309    "immutable",
1310    "import",
1311    "indexed",
1312    "int8",
1313    "int24",
1314    "int128",
1315    "int256",
1316    "interface",
1317    "internal",
1318    "library",
1319    "mapping",
1320    "memory",
1321    "modifier",
1322    "new",
1323    "override",
1324    "payable",
1325    "pragma",
1326    "private",
1327    "public",
1328    "pure",
1329    "receive",
1330    "return",
1331    "returns",
1332    "revert",
1333    "storage",
1334    "string",
1335    "struct",
1336    "true",
1337    "type",
1338    "uint8",
1339    "uint24",
1340    "uint128",
1341    "uint160",
1342    "uint256",
1343    "unchecked",
1344    "using",
1345    "view",
1346    "virtual",
1347    "while",
1348];
1349
1350/// Ether denomination units — suffixes for literal numbers.
1351const ETHER_UNITS: &[(&str, &str)] = &[
1352    ("wei", "1"),
1353    ("gwei", "1e9"),
1354    ("ether", "1e18"),
1355];
1356
1357/// Time units — suffixes for literal numbers.
1358const TIME_UNITS: &[(&str, &str)] = &[
1359    ("seconds", "1"),
1360    ("minutes", "60 seconds"),
1361    ("hours", "3600 seconds"),
1362    ("days", "86400 seconds"),
1363    ("weeks", "604800 seconds"),
1364];
1365
1366const MAGIC_GLOBALS: &[(&str, &str)] = &[
1367    ("msg", "msg"),
1368    ("block", "block"),
1369    ("tx", "tx"),
1370    ("abi", "abi"),
1371    ("this", "address"),
1372    ("super", "contract"),
1373    ("type", "type information"),
1374];
1375
1376const GLOBAL_FUNCTIONS: &[(&str, &str)] = &[
1377    // Mathematical and Cryptographic Functions
1378    ("addmod(uint256, uint256, uint256)", "uint256"),
1379    ("mulmod(uint256, uint256, uint256)", "uint256"),
1380    ("keccak256(bytes memory)", "bytes32"),
1381    ("sha256(bytes memory)", "bytes32"),
1382    ("ripemd160(bytes memory)", "bytes20"),
1383    (
1384        "ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s)",
1385        "address",
1386    ),
1387    // Block and Transaction Properties (functions)
1388    ("blockhash(uint256 blockNumber)", "bytes32"),
1389    ("blobhash(uint256 index)", "bytes32"),
1390    ("gasleft()", "uint256"),
1391    // Error Handling
1392    ("assert(bool condition)", ""),
1393    ("require(bool condition)", ""),
1394    ("require(bool condition, string memory message)", ""),
1395    ("revert()", ""),
1396    ("revert(string memory reason)", ""),
1397    // Contract-related
1398    ("selfdestruct(address payable recipient)", ""),
1399];