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/// A declaration found within a specific scope.
10#[derive(Debug, Clone)]
11pub struct ScopedDeclaration {
12    /// Variable/function/type name.
13    pub name: String,
14    /// typeIdentifier from typeDescriptions (e.g. "t_struct$_PoolKey_$8887_memory_ptr").
15    pub type_id: String,
16}
17
18/// A byte range identifying a scope-creating AST node.
19#[derive(Debug, Clone)]
20pub struct ScopeRange {
21    /// AST node id of this scope.
22    pub node_id: u64,
23    /// Byte offset where this scope starts (from `src` field).
24    pub start: usize,
25    /// Byte offset where this scope ends (start + length).
26    pub end: usize,
27    /// Source file id (from `src` field).
28    pub file_id: u64,
29}
30
31/// Completion cache built from the AST.
32pub struct CompletionCache {
33    /// All named identifiers as completion items (flat, unscoped).
34    pub names: Vec<CompletionItem>,
35
36    /// name → typeIdentifier (for dot-completion: look up what type a variable is).
37    pub name_to_type: HashMap<String, String>,
38
39    /// node id → Vec<CompletionItem> (members of structs, contracts, enums, libraries).
40    pub node_members: HashMap<u64, Vec<CompletionItem>>,
41
42    /// typeIdentifier → node id (resolve a type string to its definition).
43    pub type_to_node: HashMap<String, u64>,
44
45    /// contract/library/interface name → node id (for direct name dot-completion like `FullMath.`).
46    pub name_to_node_id: HashMap<String, u64>,
47
48    /// node id → Vec<CompletionItem> from methodIdentifiers in .contracts section.
49    /// Full function signatures with 4-byte selectors for contracts/interfaces.
50    pub method_identifiers: HashMap<u64, Vec<CompletionItem>>,
51
52    /// (contract_node_id, fn_name) → return typeIdentifier.
53    /// For resolving `foo().` — look up what `foo` returns.
54    pub function_return_types: HashMap<(u64, String), String>,
55
56    /// typeIdentifier → Vec<CompletionItem> from UsingForDirective.
57    /// Library functions available on a type via `using X for Y`.
58    pub using_for: HashMap<String, Vec<CompletionItem>>,
59
60    /// Wildcard using-for: `using X for *` — available on all types.
61    pub using_for_wildcard: Vec<CompletionItem>,
62
63    /// Pre-built general completions (AST names + keywords + globals + units).
64    /// Built once, returned by reference on every non-dot completion request.
65    pub general_completions: Vec<CompletionItem>,
66
67    /// scope node_id → declarations in that scope.
68    /// Each scope (Block, FunctionDefinition, ContractDefinition, SourceUnit)
69    /// has the variables/functions/types declared directly within it.
70    pub scope_declarations: HashMap<u64, Vec<ScopedDeclaration>>,
71
72    /// node_id → parent scope node_id.
73    /// Walk this chain upward to widen the search scope.
74    pub scope_parent: HashMap<u64, u64>,
75
76    /// All scope ranges, for finding which scope a byte position falls in.
77    /// Sorted by span size ascending (smallest first) for efficient innermost-scope lookup.
78    pub scope_ranges: Vec<ScopeRange>,
79
80    /// absolute file path → AST source file id.
81    /// Used to map a URI to the file_id needed for scope resolution.
82    pub path_to_file_id: HashMap<String, u64>,
83
84    /// contract node_id → linearized base contracts (C3 linearization order).
85    /// First element is the contract itself, followed by parents in resolution order.
86    /// Used to search inherited state variables and functions during scope resolution.
87    pub linearized_base_contracts: HashMap<u64, Vec<u64>>,
88
89    /// contract/interface/library node_id → contractKind string.
90    /// Values are `"contract"`, `"interface"`, or `"library"`.
91    /// Used to determine which `type(X).` members to offer.
92    pub contract_kinds: HashMap<u64, String>,
93}
94
95fn push_if_node_or_array<'a>(tree: &'a Value, key: &str, stack: &mut Vec<&'a Value>) {
96    if let Some(value) = tree.get(key) {
97        match value {
98            Value::Array(arr) => stack.extend(arr),
99            Value::Object(_) => stack.push(value),
100            _ => {}
101        }
102    }
103}
104
105/// Map AST nodeType to LSP CompletionItemKind.
106fn node_type_to_completion_kind(node_type: &str) -> CompletionItemKind {
107    match node_type {
108        "FunctionDefinition" => CompletionItemKind::FUNCTION,
109        "VariableDeclaration" => CompletionItemKind::VARIABLE,
110        "ContractDefinition" => CompletionItemKind::CLASS,
111        "StructDefinition" => CompletionItemKind::STRUCT,
112        "EnumDefinition" => CompletionItemKind::ENUM,
113        "EnumValue" => CompletionItemKind::ENUM_MEMBER,
114        "EventDefinition" => CompletionItemKind::EVENT,
115        "ErrorDefinition" => CompletionItemKind::EVENT,
116        "ModifierDefinition" => CompletionItemKind::METHOD,
117        "ImportDirective" => CompletionItemKind::MODULE,
118        _ => CompletionItemKind::TEXT,
119    }
120}
121
122/// Parse the `src` field of an AST node: "offset:length:fileId".
123/// Returns (offset, length, file_id) or None if the format is invalid.
124fn parse_src(node: &Value) -> Option<(usize, usize, u64)> {
125    let src = node.get("src").and_then(|v| v.as_str())?;
126    let mut parts = src.split(':');
127    let offset = parts.next()?.parse::<usize>().ok()?;
128    let length = parts.next()?.parse::<usize>().ok()?;
129    let file_id = parts.next()?.parse::<u64>().ok()?;
130    Some((offset, length, file_id))
131}
132
133/// Extract the trailing node id from a typeIdentifier string.
134/// e.g. `t_struct$_PoolKey_$8887_storage_ptr` → Some(8887)
135///      `t_contract$_IHooks_$2248` → Some(2248)
136///      `t_uint256` → None
137pub fn extract_node_id_from_type(type_id: &str) -> Option<u64> {
138    // Pattern: ..._$<digits>... where digits follow the last _$
139    // We find all _$<digits> groups and take the last one that's part of the type name
140    let mut last_id = None;
141    let mut i = 0;
142    let bytes = type_id.as_bytes();
143    while i < bytes.len() {
144        if i + 1 < bytes.len() && bytes[i] == b'_' && bytes[i + 1] == b'$' {
145            i += 2;
146            let start = i;
147            while i < bytes.len() && bytes[i].is_ascii_digit() {
148                i += 1;
149            }
150            if i > start
151                && let Ok(id) = type_id[start..i].parse::<u64>()
152            {
153                last_id = Some(id);
154            }
155        } else {
156            i += 1;
157        }
158    }
159    last_id
160}
161
162/// Build a human-readable function signature from a FunctionDefinition AST node.
163/// e.g. `swap(PoolKey key, SwapParams params, bytes hookData) returns (BalanceDelta swapDelta)`
164fn build_function_signature(node: &Value) -> Option<String> {
165    let name = node.get("name").and_then(|v| v.as_str()).unwrap_or("");
166    if name.is_empty() {
167        return None;
168    }
169
170    let params = node
171        .get("parameters")
172        .and_then(|p| p.get("parameters"))
173        .and_then(|v| v.as_array());
174
175    let mut sig = String::new();
176    sig.push_str(name);
177    sig.push('(');
178
179    if let Some(params) = params {
180        for (i, param) in params.iter().enumerate() {
181            if i > 0 {
182                sig.push_str(", ");
183            }
184            let type_str = param
185                .get("typeDescriptions")
186                .and_then(|td| td.get("typeString"))
187                .and_then(|v| v.as_str())
188                .unwrap_or("?");
189            // Clean up the type string: "struct PoolKey" → "PoolKey", "contract IHooks" → "IHooks"
190            let clean_type = type_str
191                .strip_prefix("struct ")
192                .or_else(|| type_str.strip_prefix("contract "))
193                .or_else(|| type_str.strip_prefix("enum "))
194                .unwrap_or(type_str);
195            let param_name = param.get("name").and_then(|v| v.as_str()).unwrap_or("");
196            sig.push_str(clean_type);
197            if !param_name.is_empty() {
198                sig.push(' ');
199                sig.push_str(param_name);
200            }
201        }
202    }
203    sig.push(')');
204
205    // Add return parameters if present
206    let returns = node
207        .get("returnParameters")
208        .and_then(|p| p.get("parameters"))
209        .and_then(|v| v.as_array());
210
211    if let Some(returns) = returns
212        && !returns.is_empty()
213    {
214        sig.push_str(" returns (");
215        for (i, ret) in returns.iter().enumerate() {
216            if i > 0 {
217                sig.push_str(", ");
218            }
219            let type_str = ret
220                .get("typeDescriptions")
221                .and_then(|td| td.get("typeString"))
222                .and_then(|v| v.as_str())
223                .unwrap_or("?");
224            let clean_type = type_str
225                .strip_prefix("struct ")
226                .or_else(|| type_str.strip_prefix("contract "))
227                .or_else(|| type_str.strip_prefix("enum "))
228                .unwrap_or(type_str);
229            let ret_name = ret.get("name").and_then(|v| v.as_str()).unwrap_or("");
230            sig.push_str(clean_type);
231            if !ret_name.is_empty() {
232                sig.push(' ');
233                sig.push_str(ret_name);
234            }
235        }
236        sig.push(')');
237    }
238
239    Some(sig)
240}
241
242/// Extract the deepest value type from a mapping typeIdentifier.
243/// Peels off all `t_mapping$_<key>_$_<value>` layers and returns the innermost value type.
244///
245/// e.g. `t_mapping$_t_address_$_t_uint256_$` → `t_uint256`
246///      `t_mapping$_t_address_$_t_mapping$_t_uint256_$_t_uint256_$_$` → `t_uint256`
247///      `t_mapping$_t_userDefinedValueType$_PoolId_$8841_$_t_struct$_State_$4809_storage_$` → `t_struct$_State_$4809_storage`
248pub fn extract_mapping_value_type(type_id: &str) -> Option<String> {
249    let mut current = type_id;
250
251    loop {
252        if !current.starts_with("t_mapping$_") {
253            // Not a mapping — this is the value type
254            // Strip trailing _$ suffixes (mapping closers)
255            let result = current.trim_end_matches("_$");
256            return if result.is_empty() {
257                None
258            } else {
259                Some(result.to_string())
260            };
261        }
262
263        // Strip "t_mapping$_" prefix to get "<key>_$_<value>_$"
264        let inner = &current["t_mapping$_".len()..];
265
266        // Find the boundary between key and value.
267        // We need to find the _$_ that separates key from value at depth 0.
268        // Each $_ opens a nesting level, each _$ closes one.
269        let mut depth = 0i32;
270        let bytes = inner.as_bytes();
271        let mut split_pos = None;
272
273        let mut i = 0;
274        while i < bytes.len() {
275            if i + 1 < bytes.len() && bytes[i] == b'$' && bytes[i + 1] == b'_' {
276                depth += 1;
277                i += 2;
278            } else if i + 2 < bytes.len()
279                && bytes[i] == b'_'
280                && bytes[i + 1] == b'$'
281                && bytes[i + 2] == b'_'
282                && depth == 0
283            {
284                // This is the _$_ separator at depth 0
285                split_pos = Some(i);
286                break;
287            } else if i + 1 < bytes.len() && bytes[i] == b'_' && bytes[i + 1] == b'$' {
288                depth -= 1;
289                i += 2;
290            } else {
291                i += 1;
292            }
293        }
294
295        if let Some(pos) = split_pos {
296            // Value type starts after "_$_"
297            current = &inner[pos + 3..];
298        } else {
299            return None;
300        }
301    }
302}
303
304/// Count parameters in an ABI method signature like "swap((address,address),uint256,bytes)".
305/// Counts commas at depth 0 (inside the outer parens), handling nested tuples.
306fn count_abi_params(signature: &str) -> usize {
307    // Find the first '(' and work from there
308    let start = match signature.find('(') {
309        Some(i) => i + 1,
310        None => return 0,
311    };
312    let bytes = signature.as_bytes();
313    if start >= bytes.len() {
314        return 0;
315    }
316    // Check for empty params "()"
317    if bytes[start] == b')' {
318        return 0;
319    }
320    let mut count = 1; // at least one param if not empty
321    let mut depth = 0;
322    for &b in &bytes[start..] {
323        match b {
324            b'(' => depth += 1,
325            b')' => {
326                if depth == 0 {
327                    break;
328                }
329                depth -= 1;
330            }
331            b',' if depth == 0 => count += 1,
332            _ => {}
333        }
334    }
335    count
336}
337
338/// Count parameters in an AST-derived signature like "swap(PoolKey key, SwapParams params, bytes hookData)".
339fn count_signature_params(sig: &str) -> usize {
340    count_abi_params(sig)
341}
342
343/// Build a CompletionCache from AST sources and contracts.
344/// `contracts` is the `.contracts` section of the compiler output (optional).
345pub fn build_completion_cache(sources: &Value, contracts: Option<&Value>) -> CompletionCache {
346    let mut names: Vec<CompletionItem> = Vec::new();
347    let mut seen_names: HashMap<String, usize> = HashMap::new(); // name → index in names vec
348    let mut name_to_type: HashMap<String, String> = HashMap::new();
349    let mut node_members: HashMap<u64, Vec<CompletionItem>> = HashMap::new();
350    let mut type_to_node: HashMap<String, u64> = HashMap::new();
351    let mut method_identifiers: HashMap<u64, Vec<CompletionItem>> = HashMap::new();
352    let mut name_to_node_id: HashMap<String, u64> = HashMap::new();
353    let mut contract_kinds: HashMap<u64, String> = HashMap::new();
354
355    // Collect (path, contract_name, node_id) during AST walk for methodIdentifiers lookup after.
356    let mut contract_locations: Vec<(String, String, u64)> = Vec::new();
357
358    // contract_node_id → fn_name → Vec<signature> (for matching method_identifiers to AST signatures)
359    let mut function_signatures: HashMap<u64, HashMap<String, Vec<String>>> = HashMap::new();
360
361    // (contract_node_id, fn_name) → return typeIdentifier
362    let mut function_return_types: HashMap<(u64, String), String> = HashMap::new();
363
364    // typeIdentifier → Vec<CompletionItem> from UsingForDirective
365    let mut using_for: HashMap<String, Vec<CompletionItem>> = HashMap::new();
366    let mut using_for_wildcard: Vec<CompletionItem> = Vec::new();
367
368    // Temp: (library_node_id, target_type_id_or_none) for resolving after walk
369    let mut using_for_directives: Vec<(u64, Option<String>)> = Vec::new();
370
371    // Scope-aware completion data
372    let mut scope_declarations: HashMap<u64, Vec<ScopedDeclaration>> = HashMap::new();
373    let mut scope_parent: HashMap<u64, u64> = HashMap::new();
374    let mut scope_ranges: Vec<ScopeRange> = Vec::new();
375    let mut path_to_file_id: HashMap<String, u64> = HashMap::new();
376    let mut linearized_base_contracts: HashMap<u64, Vec<u64>> = HashMap::new();
377
378    if let Some(sources_obj) = sources.as_object() {
379        for (path, contents) in sources_obj {
380            if let Some(contents_array) = contents.as_array()
381                && let Some(first_content) = contents_array.first()
382                && let Some(source_file) = first_content.get("source_file")
383                && let Some(ast) = source_file.get("ast")
384            {
385                // Map file path → source file id for scope resolution
386                if let Some(fid) = source_file.get("id").and_then(|v| v.as_u64()) {
387                    path_to_file_id.insert(path.clone(), fid);
388                }
389                let mut stack: Vec<&Value> = vec![ast];
390
391                while let Some(tree) = stack.pop() {
392                    let node_type = tree.get("nodeType").and_then(|v| v.as_str()).unwrap_or("");
393                    let name = tree.get("name").and_then(|v| v.as_str()).unwrap_or("");
394                    let node_id = tree.get("id").and_then(|v| v.as_u64());
395
396                    // --- Scope-aware data collection ---
397
398                    // Record scope-creating nodes (SourceUnit, ContractDefinition,
399                    // FunctionDefinition, ModifierDefinition, Block) and their byte ranges.
400                    let is_scope_node = matches!(
401                        node_type,
402                        "SourceUnit"
403                            | "ContractDefinition"
404                            | "FunctionDefinition"
405                            | "ModifierDefinition"
406                            | "Block"
407                            | "UncheckedBlock"
408                    );
409                    if is_scope_node && let Some(nid) = node_id {
410                        if let Some((start, len, file_id)) = parse_src(tree) {
411                            scope_ranges.push(ScopeRange {
412                                node_id: nid,
413                                start,
414                                end: start + len,
415                                file_id,
416                            });
417                        }
418                        // Record parent link: this node's scope → its parent
419                        if let Some(parent_id) = tree.get("scope").and_then(|v| v.as_u64()) {
420                            scope_parent.insert(nid, parent_id);
421                        }
422                    }
423
424                    // For ContractDefinitions, record linearizedBaseContracts
425                    if node_type == "ContractDefinition"
426                        && let Some(nid) = node_id
427                        && let Some(bases) = tree
428                            .get("linearizedBaseContracts")
429                            .and_then(|v| v.as_array())
430                    {
431                        let base_ids: Vec<u64> = bases.iter().filter_map(|b| b.as_u64()).collect();
432                        if !base_ids.is_empty() {
433                            linearized_base_contracts.insert(nid, base_ids);
434                        }
435                    }
436
437                    // For VariableDeclarations, record the declaration in its scope
438                    if node_type == "VariableDeclaration"
439                        && !name.is_empty()
440                        && let Some(scope_id) = tree.get("scope").and_then(|v| v.as_u64())
441                        && let Some(tid) = tree
442                            .get("typeDescriptions")
443                            .and_then(|td| td.get("typeIdentifier"))
444                            .and_then(|v| v.as_str())
445                    {
446                        scope_declarations
447                            .entry(scope_id)
448                            .or_default()
449                            .push(ScopedDeclaration {
450                                name: name.to_string(),
451                                type_id: tid.to_string(),
452                            });
453                    }
454
455                    // For FunctionDefinitions, record them in their parent scope (the contract)
456                    if node_type == "FunctionDefinition"
457                        && !name.is_empty()
458                        && let Some(scope_id) = tree.get("scope").and_then(|v| v.as_u64())
459                        && let Some(tid) = tree
460                            .get("typeDescriptions")
461                            .and_then(|td| td.get("typeIdentifier"))
462                            .and_then(|v| v.as_str())
463                    {
464                        scope_declarations
465                            .entry(scope_id)
466                            .or_default()
467                            .push(ScopedDeclaration {
468                                name: name.to_string(),
469                                type_id: tid.to_string(),
470                            });
471                    }
472
473                    // Collect named nodes as completion items
474                    if !name.is_empty() && !seen_names.contains_key(name) {
475                        let type_string = tree
476                            .get("typeDescriptions")
477                            .and_then(|td| td.get("typeString"))
478                            .and_then(|v| v.as_str())
479                            .map(|s| s.to_string());
480
481                        let type_id = tree
482                            .get("typeDescriptions")
483                            .and_then(|td| td.get("typeIdentifier"))
484                            .and_then(|v| v.as_str());
485
486                        let kind = node_type_to_completion_kind(node_type);
487
488                        let item = CompletionItem {
489                            label: name.to_string(),
490                            kind: Some(kind),
491                            detail: type_string,
492                            ..Default::default()
493                        };
494
495                        let idx = names.len();
496                        names.push(item);
497                        seen_names.insert(name.to_string(), idx);
498
499                        // Store name → typeIdentifier mapping
500                        if let Some(tid) = type_id {
501                            name_to_type.insert(name.to_string(), tid.to_string());
502                        }
503                    }
504
505                    // Collect struct members
506                    if node_type == "StructDefinition"
507                        && let Some(id) = node_id
508                    {
509                        let mut members = Vec::new();
510                        if let Some(member_array) = tree.get("members").and_then(|v| v.as_array()) {
511                            for member in member_array {
512                                let member_name =
513                                    member.get("name").and_then(|v| v.as_str()).unwrap_or("");
514                                if member_name.is_empty() {
515                                    continue;
516                                }
517                                let member_type = member
518                                    .get("typeDescriptions")
519                                    .and_then(|td| td.get("typeString"))
520                                    .and_then(|v| v.as_str())
521                                    .map(|s| s.to_string());
522
523                                members.push(CompletionItem {
524                                    label: member_name.to_string(),
525                                    kind: Some(CompletionItemKind::FIELD),
526                                    detail: member_type,
527                                    ..Default::default()
528                                });
529                            }
530                        }
531                        if !members.is_empty() {
532                            node_members.insert(id, members);
533                        }
534
535                        // Map typeIdentifier → node id
536                        if let Some(tid) = tree
537                            .get("typeDescriptions")
538                            .and_then(|td| td.get("typeIdentifier"))
539                            .and_then(|v| v.as_str())
540                        {
541                            type_to_node.insert(tid.to_string(), id);
542                        }
543                    }
544
545                    // Collect contract/library members (functions, state variables, events, etc.)
546                    if node_type == "ContractDefinition"
547                        && let Some(id) = node_id
548                    {
549                        let mut members = Vec::new();
550                        let mut fn_sigs: HashMap<String, Vec<String>> = HashMap::new();
551                        if let Some(nodes_array) = tree.get("nodes").and_then(|v| v.as_array()) {
552                            for member in nodes_array {
553                                let member_type = member
554                                    .get("nodeType")
555                                    .and_then(|v| v.as_str())
556                                    .unwrap_or("");
557                                let member_name =
558                                    member.get("name").and_then(|v| v.as_str()).unwrap_or("");
559                                if member_name.is_empty() {
560                                    continue;
561                                }
562
563                                // Build function signature and collect return types for FunctionDefinitions
564                                let (member_detail, label_details) =
565                                    if member_type == "FunctionDefinition" {
566                                        // Collect return type for chain resolution.
567                                        // Only single-return functions can be dot-chained
568                                        // (tuples require destructuring).
569                                        if let Some(ret_params) = member
570                                            .get("returnParameters")
571                                            .and_then(|rp| rp.get("parameters"))
572                                            .and_then(|v| v.as_array())
573                                            && ret_params.len() == 1
574                                            && let Some(ret_tid) = ret_params[0]
575                                                .get("typeDescriptions")
576                                                .and_then(|td| td.get("typeIdentifier"))
577                                                .and_then(|v| v.as_str())
578                                        {
579                                            function_return_types.insert(
580                                                (id, member_name.to_string()),
581                                                ret_tid.to_string(),
582                                            );
583                                        }
584
585                                        if let Some(sig) = build_function_signature(member) {
586                                            fn_sigs
587                                                .entry(member_name.to_string())
588                                                .or_default()
589                                                .push(sig.clone());
590                                            (Some(sig), None)
591                                        } else {
592                                            (
593                                                member
594                                                    .get("typeDescriptions")
595                                                    .and_then(|td| td.get("typeString"))
596                                                    .and_then(|v| v.as_str())
597                                                    .map(|s| s.to_string()),
598                                                None,
599                                            )
600                                        }
601                                    } else {
602                                        (
603                                            member
604                                                .get("typeDescriptions")
605                                                .and_then(|td| td.get("typeString"))
606                                                .and_then(|v| v.as_str())
607                                                .map(|s| s.to_string()),
608                                            None,
609                                        )
610                                    };
611
612                                let kind = node_type_to_completion_kind(member_type);
613                                members.push(CompletionItem {
614                                    label: member_name.to_string(),
615                                    kind: Some(kind),
616                                    detail: member_detail,
617                                    label_details,
618                                    ..Default::default()
619                                });
620                            }
621                        }
622                        if !members.is_empty() {
623                            node_members.insert(id, members);
624                        }
625                        if !fn_sigs.is_empty() {
626                            function_signatures.insert(id, fn_sigs);
627                        }
628
629                        if let Some(tid) = tree
630                            .get("typeDescriptions")
631                            .and_then(|td| td.get("typeIdentifier"))
632                            .and_then(|v| v.as_str())
633                        {
634                            type_to_node.insert(tid.to_string(), id);
635                        }
636
637                        // Record for methodIdentifiers lookup after traversal
638                        if !name.is_empty() {
639                            contract_locations.push((path.clone(), name.to_string(), id));
640                            name_to_node_id.insert(name.to_string(), id);
641                        }
642
643                        // Record contractKind (contract, interface, library) for type(X). completions
644                        if let Some(ck) = tree.get("contractKind").and_then(|v| v.as_str()) {
645                            contract_kinds.insert(id, ck.to_string());
646                        }
647                    }
648
649                    // Collect enum members
650                    if node_type == "EnumDefinition"
651                        && let Some(id) = node_id
652                    {
653                        let mut members = Vec::new();
654                        if let Some(member_array) = tree.get("members").and_then(|v| v.as_array()) {
655                            for member in member_array {
656                                let member_name =
657                                    member.get("name").and_then(|v| v.as_str()).unwrap_or("");
658                                if member_name.is_empty() {
659                                    continue;
660                                }
661                                members.push(CompletionItem {
662                                    label: member_name.to_string(),
663                                    kind: Some(CompletionItemKind::ENUM_MEMBER),
664                                    detail: None,
665                                    ..Default::default()
666                                });
667                            }
668                        }
669                        if !members.is_empty() {
670                            node_members.insert(id, members);
671                        }
672
673                        if let Some(tid) = tree
674                            .get("typeDescriptions")
675                            .and_then(|td| td.get("typeIdentifier"))
676                            .and_then(|v| v.as_str())
677                        {
678                            type_to_node.insert(tid.to_string(), id);
679                        }
680                    }
681
682                    // Collect UsingForDirective: using Library for Type
683                    if node_type == "UsingForDirective" {
684                        // Get target type (None = wildcard `for *`)
685                        let target_type = tree.get("typeName").and_then(|tn| {
686                            tn.get("typeDescriptions")
687                                .and_then(|td| td.get("typeIdentifier"))
688                                .and_then(|v| v.as_str())
689                                .map(|s| s.to_string())
690                        });
691
692                        // Form 1: library name object with referencedDeclaration
693                        if let Some(lib) = tree.get("libraryName") {
694                            if let Some(lib_id) =
695                                lib.get("referencedDeclaration").and_then(|v| v.as_u64())
696                            {
697                                using_for_directives.push((lib_id, target_type));
698                            }
699                        }
700                        // Form 2: functionList array — individual function references
701                        // These are typically operator overloads (not dot-callable),
702                        // but collect non-operator ones just in case
703                        else if let Some(func_list) =
704                            tree.get("functionList").and_then(|v| v.as_array())
705                        {
706                            for entry in func_list {
707                                // Skip operator overloads
708                                if entry.get("operator").is_some() {
709                                    continue;
710                                }
711                                if let Some(def) = entry.get("definition") {
712                                    let fn_name =
713                                        def.get("name").and_then(|v| v.as_str()).unwrap_or("");
714                                    if !fn_name.is_empty() {
715                                        let items = if let Some(ref tid) = target_type {
716                                            using_for.entry(tid.clone()).or_default()
717                                        } else {
718                                            &mut using_for_wildcard
719                                        };
720                                        items.push(CompletionItem {
721                                            label: fn_name.to_string(),
722                                            kind: Some(CompletionItemKind::FUNCTION),
723                                            detail: None,
724                                            ..Default::default()
725                                        });
726                                    }
727                                }
728                            }
729                        }
730                    }
731
732                    // Traverse children
733                    for key in CHILD_KEYS {
734                        push_if_node_or_array(tree, key, &mut stack);
735                    }
736                }
737            }
738        }
739    }
740
741    // Resolve UsingForDirective library references (Form 1)
742    // Now that node_members is populated, look up each library's functions
743    for (lib_id, target_type) in &using_for_directives {
744        if let Some(lib_members) = node_members.get(lib_id) {
745            let items: Vec<CompletionItem> = lib_members
746                .iter()
747                .filter(|item| item.kind == Some(CompletionItemKind::FUNCTION))
748                .cloned()
749                .collect();
750            if !items.is_empty() {
751                if let Some(tid) = target_type {
752                    using_for.entry(tid.clone()).or_default().extend(items);
753                } else {
754                    using_for_wildcard.extend(items);
755                }
756            }
757        }
758    }
759
760    // Build method_identifiers from .contracts section
761    if let Some(contracts_val) = contracts
762        && let Some(contracts_obj) = contracts_val.as_object()
763    {
764        for (path, contract_name, node_id) in &contract_locations {
765            // Get AST function signatures for this contract (if available)
766            let fn_sigs = function_signatures.get(node_id);
767
768            if let Some(path_entry) = contracts_obj.get(path)
769                && let Some(contract_entry) = path_entry.get(contract_name)
770                && let Some(first) = contract_entry.get(0)
771                && let Some(evm) = first.get("contract").and_then(|c| c.get("evm"))
772                && let Some(methods) = evm.get("methodIdentifiers")
773                && let Some(methods_obj) = methods.as_object()
774            {
775                let mut items: Vec<CompletionItem> = Vec::new();
776                for (signature, selector) in methods_obj {
777                    // signature is e.g. "swap((address,address,uint24,int24,address),(bool,int256,uint160),bytes)"
778                    // selector is e.g. "f3cd914c"
779                    let fn_name = signature.split('(').next().unwrap_or(signature).to_string();
780                    let selector_str = selector
781                        .as_str()
782                        .map(|s| format!("0x{}", s))
783                        .unwrap_or_default();
784
785                    // Look up the AST signature with parameter names
786                    let description =
787                        fn_sigs
788                            .and_then(|sigs| sigs.get(&fn_name))
789                            .and_then(|sig_list| {
790                                if sig_list.len() == 1 {
791                                    // Only one overload — use it directly
792                                    Some(sig_list[0].clone())
793                                } else {
794                                    // Multiple overloads — match by parameter count
795                                    let abi_param_count = count_abi_params(signature);
796                                    sig_list
797                                        .iter()
798                                        .find(|s| count_signature_params(s) == abi_param_count)
799                                        .cloned()
800                                }
801                            });
802
803                    items.push(CompletionItem {
804                        label: fn_name,
805                        kind: Some(CompletionItemKind::FUNCTION),
806                        detail: Some(signature.clone()),
807                        label_details: Some(tower_lsp::lsp_types::CompletionItemLabelDetails {
808                            detail: Some(selector_str),
809                            description,
810                        }),
811                        ..Default::default()
812                    });
813                }
814                if !items.is_empty() {
815                    method_identifiers.insert(*node_id, items);
816                }
817            }
818        }
819    }
820
821    // Pre-build the general completions list (names + statics) once
822    let mut general_completions = names.clone();
823    general_completions.extend(get_static_completions());
824
825    // Sort scope_ranges by span size ascending (smallest first) for innermost-scope lookup
826    scope_ranges.sort_by_key(|r| r.end - r.start);
827
828    // Infer parent links for Block/UncheckedBlock/ModifierDefinition nodes.
829    // These AST nodes have no `scope` field, so scope_parent has no entry for them.
830    // For each orphan, find the next-larger enclosing scope range in the same file.
831    // scope_ranges is sorted smallest-first, so we scan forward from each orphan's
832    // position to find the first range that strictly contains it.
833    let orphan_ids: Vec<u64> = scope_ranges
834        .iter()
835        .filter(|r| !scope_parent.contains_key(&r.node_id))
836        .map(|r| r.node_id)
837        .collect();
838    // Build a lookup from node_id → (start, end, file_id) for quick access
839    let range_by_id: HashMap<u64, (usize, usize, u64)> = scope_ranges
840        .iter()
841        .map(|r| (r.node_id, (r.start, r.end, r.file_id)))
842        .collect();
843    for orphan_id in &orphan_ids {
844        if let Some(&(start, end, file_id)) = range_by_id.get(orphan_id) {
845            // Find the smallest range that strictly contains this orphan's range
846            // (same file, starts at or before, ends at or after, and is strictly larger)
847            let parent = scope_ranges
848                .iter()
849                .find(|r| {
850                    r.node_id != *orphan_id
851                        && r.file_id == file_id
852                        && r.start <= start
853                        && r.end >= end
854                        && (r.end - r.start) > (end - start)
855                })
856                .map(|r| r.node_id);
857            if let Some(parent_id) = parent {
858                scope_parent.insert(*orphan_id, parent_id);
859            }
860        }
861    }
862
863    CompletionCache {
864        names,
865        name_to_type,
866        node_members,
867        type_to_node,
868        name_to_node_id,
869        method_identifiers,
870        function_return_types,
871        using_for,
872        using_for_wildcard,
873        general_completions,
874        scope_declarations,
875        scope_parent,
876        scope_ranges,
877        path_to_file_id,
878        linearized_base_contracts,
879        contract_kinds,
880    }
881}
882
883/// Magic type member definitions (msg, block, tx, abi, address).
884fn magic_members(name: &str) -> Option<Vec<CompletionItem>> {
885    let items = match name {
886        "msg" => vec![
887            ("data", "bytes calldata"),
888            ("sender", "address"),
889            ("sig", "bytes4"),
890            ("value", "uint256"),
891        ],
892        "block" => vec![
893            ("basefee", "uint256"),
894            ("blobbasefee", "uint256"),
895            ("chainid", "uint256"),
896            ("coinbase", "address payable"),
897            ("difficulty", "uint256"),
898            ("gaslimit", "uint256"),
899            ("number", "uint256"),
900            ("prevrandao", "uint256"),
901            ("timestamp", "uint256"),
902        ],
903        "tx" => vec![("gasprice", "uint256"), ("origin", "address")],
904        "abi" => vec![
905            ("decode(bytes memory, (...))", "..."),
906            ("encode(...)", "bytes memory"),
907            ("encodePacked(...)", "bytes memory"),
908            ("encodeWithSelector(bytes4, ...)", "bytes memory"),
909            ("encodeWithSignature(string memory, ...)", "bytes memory"),
910            ("encodeCall(function, (...))", "bytes memory"),
911        ],
912        // type(X) — contract type properties
913        // Also includes interface (interfaceId) and integer (min, max) properties
914        "type" => vec![
915            ("name", "string"),
916            ("creationCode", "bytes memory"),
917            ("runtimeCode", "bytes memory"),
918            ("interfaceId", "bytes4"),
919            ("min", "T"),
920            ("max", "T"),
921        ],
922        // bytes and string type-level members
923        "bytes" => vec![("concat(...)", "bytes memory")],
924        "string" => vec![("concat(...)", "string memory")],
925        _ => return None,
926    };
927
928    Some(
929        items
930            .into_iter()
931            .map(|(label, detail)| CompletionItem {
932                label: label.to_string(),
933                kind: Some(CompletionItemKind::PROPERTY),
934                detail: Some(detail.to_string()),
935                ..Default::default()
936            })
937            .collect(),
938    )
939}
940
941/// The kind of argument passed to `type(X)`, which determines which
942/// meta-type members are available.
943#[derive(Debug, Clone, Copy, PartialEq, Eq)]
944enum TypeMetaKind {
945    /// `type(SomeContract)` — has `name`, `creationCode`, `runtimeCode`
946    Contract,
947    /// `type(SomeInterface)` — has `name`, `interfaceId`
948    Interface,
949    /// `type(uint256)` / `type(int8)` — has `min`, `max`
950    IntegerType,
951    /// Unknown argument — return all possible members as a fallback
952    Unknown,
953}
954
955/// Classify the argument of `type(X)` based on the cache.
956fn classify_type_arg(arg: &str, cache: Option<&CompletionCache>) -> TypeMetaKind {
957    // Check if it's an integer type: int, uint, int8..int256, uint8..uint256
958    if arg == "int" || arg == "uint" {
959        return TypeMetaKind::IntegerType;
960    }
961    if let Some(suffix) = arg.strip_prefix("uint").or_else(|| arg.strip_prefix("int"))
962        && let Ok(n) = suffix.parse::<u16>()
963        && (8..=256).contains(&n)
964        && n % 8 == 0
965    {
966        return TypeMetaKind::IntegerType;
967    }
968
969    // With a cache, look up the name to determine contract vs interface
970    if let Some(c) = cache
971        && let Some(&node_id) = c.name_to_node_id.get(arg)
972    {
973        return match c.contract_kinds.get(&node_id).map(|s| s.as_str()) {
974            Some("interface") => TypeMetaKind::Interface,
975            Some("library") => TypeMetaKind::Contract, // libraries have name/creationCode/runtimeCode
976            _ => TypeMetaKind::Contract,
977        };
978    }
979
980    TypeMetaKind::Unknown
981}
982
983/// Return context-sensitive `type(X).` completions based on what `X` is.
984fn type_meta_members(arg: Option<&str>, cache: Option<&CompletionCache>) -> Vec<CompletionItem> {
985    let kind = match arg {
986        Some(a) => classify_type_arg(a, cache),
987        None => TypeMetaKind::Unknown,
988    };
989
990    let items: Vec<(&str, &str)> = match kind {
991        TypeMetaKind::Contract => vec![
992            ("name", "string"),
993            ("creationCode", "bytes memory"),
994            ("runtimeCode", "bytes memory"),
995        ],
996        TypeMetaKind::Interface => vec![("name", "string"), ("interfaceId", "bytes4")],
997        TypeMetaKind::IntegerType => vec![("min", "T"), ("max", "T")],
998        TypeMetaKind::Unknown => vec![
999            ("name", "string"),
1000            ("creationCode", "bytes memory"),
1001            ("runtimeCode", "bytes memory"),
1002            ("interfaceId", "bytes4"),
1003            ("min", "T"),
1004            ("max", "T"),
1005        ],
1006    };
1007
1008    items
1009        .into_iter()
1010        .map(|(label, detail)| CompletionItem {
1011            label: label.to_string(),
1012            kind: Some(CompletionItemKind::PROPERTY),
1013            detail: Some(detail.to_string()),
1014            ..Default::default()
1015        })
1016        .collect()
1017}
1018
1019/// Address type members (available on any address value).
1020fn address_members() -> Vec<CompletionItem> {
1021    [
1022        ("balance", "uint256", CompletionItemKind::PROPERTY),
1023        ("code", "bytes memory", CompletionItemKind::PROPERTY),
1024        ("codehash", "bytes32", CompletionItemKind::PROPERTY),
1025        ("transfer(uint256)", "", CompletionItemKind::FUNCTION),
1026        ("send(uint256)", "bool", CompletionItemKind::FUNCTION),
1027        (
1028            "call(bytes memory)",
1029            "(bool, bytes memory)",
1030            CompletionItemKind::FUNCTION,
1031        ),
1032        (
1033            "delegatecall(bytes memory)",
1034            "(bool, bytes memory)",
1035            CompletionItemKind::FUNCTION,
1036        ),
1037        (
1038            "staticcall(bytes memory)",
1039            "(bool, bytes memory)",
1040            CompletionItemKind::FUNCTION,
1041        ),
1042    ]
1043    .iter()
1044    .map(|(label, detail, kind)| CompletionItem {
1045        label: label.to_string(),
1046        kind: Some(*kind),
1047        detail: if detail.is_empty() {
1048            None
1049        } else {
1050            Some(detail.to_string())
1051        },
1052        ..Default::default()
1053    })
1054    .collect()
1055}
1056
1057/// What kind of access precedes the dot.
1058#[derive(Debug, Clone, PartialEq)]
1059pub enum AccessKind {
1060    /// Plain identifier: `foo.`
1061    Plain,
1062    /// Function call: `foo().` or `foo(x, bar()).`
1063    Call,
1064    /// Index/storage access: `foo[key].` or `foo[func()].`
1065    Index,
1066}
1067
1068/// A segment of a dot-expression chain.
1069#[derive(Debug, Clone, PartialEq)]
1070pub struct DotSegment {
1071    pub name: String,
1072    pub kind: AccessKind,
1073    /// For `Call` segments, the raw text inside the parentheses.
1074    /// e.g. `type(uint256).` → `call_args = Some("uint256")`
1075    pub call_args: Option<String>,
1076}
1077
1078/// Skip backwards over a matched bracket pair (parens or square brackets).
1079/// `pos` should point to the closing bracket. Returns the position of the matching
1080/// opening bracket, or 0 if not found.
1081fn skip_brackets_backwards(bytes: &[u8], pos: usize) -> usize {
1082    let close = bytes[pos];
1083    let open = match close {
1084        b')' => b'(',
1085        b']' => b'[',
1086        _ => return pos,
1087    };
1088    let mut depth = 1u32;
1089    let mut i = pos;
1090    while i > 0 && depth > 0 {
1091        i -= 1;
1092        if bytes[i] == close {
1093            depth += 1;
1094        } else if bytes[i] == open {
1095            depth -= 1;
1096        }
1097    }
1098    i
1099}
1100
1101/// Parse the expression chain before the dot into segments.
1102/// e.g. `poolManager.swap(key, params).` → [("poolManager", Plain), ("swap", Call)]
1103///      `_pools[poolId].fee.` → [("_pools", Index), ("fee", Plain)]
1104///      `msg.` → [("msg", Plain)]
1105pub fn parse_dot_chain(line: &str, character: u32) -> Vec<DotSegment> {
1106    let col = character as usize;
1107    if col == 0 {
1108        return vec![];
1109    }
1110
1111    let bytes = line.as_bytes();
1112    let mut segments: Vec<DotSegment> = Vec::new();
1113
1114    // Start from the cursor position, skip trailing dot
1115    let mut pos = col;
1116    if pos > 0 && pos <= bytes.len() && bytes[pos - 1] == b'.' {
1117        pos -= 1;
1118    }
1119
1120    loop {
1121        if pos == 0 {
1122            break;
1123        }
1124
1125        // Determine access kind by what's immediately before: ')' = Call, ']' = Index, else Plain
1126        let (kind, call_args) = if bytes[pos - 1] == b')' {
1127            let close = pos - 1; // position of ')'
1128            pos = skip_brackets_backwards(bytes, close);
1129            // Extract the text between '(' and ')'
1130            let args_text = String::from_utf8_lossy(&bytes[pos + 1..close])
1131                .trim()
1132                .to_string();
1133            let args = if args_text.is_empty() {
1134                None
1135            } else {
1136                Some(args_text)
1137            };
1138            (AccessKind::Call, args)
1139        } else if bytes[pos - 1] == b']' {
1140            pos -= 1; // point to ']'
1141            pos = skip_brackets_backwards(bytes, pos);
1142            (AccessKind::Index, None)
1143        } else {
1144            (AccessKind::Plain, None)
1145        };
1146
1147        // Now extract the identifier name (walk backwards over alphanumeric + underscore)
1148        let end = pos;
1149        while pos > 0 && (bytes[pos - 1].is_ascii_alphanumeric() || bytes[pos - 1] == b'_') {
1150            pos -= 1;
1151        }
1152
1153        if pos == end {
1154            // No identifier found (could be something like `().`)
1155            break;
1156        }
1157
1158        let name = String::from_utf8_lossy(&bytes[pos..end]).to_string();
1159        segments.push(DotSegment {
1160            name,
1161            kind,
1162            call_args,
1163        });
1164
1165        // Check if there's a dot before this segment (meaning more chain)
1166        if pos > 0 && bytes[pos - 1] == b'.' {
1167            pos -= 1; // skip the dot, continue parsing next segment
1168        } else {
1169            break;
1170        }
1171    }
1172
1173    segments.reverse(); // We parsed right-to-left, flip to left-to-right
1174    segments
1175}
1176
1177/// Extract the identifier before the cursor (the word before the dot).
1178/// Returns just the last identifier name for backward compatibility.
1179pub fn extract_identifier_before_dot(line: &str, character: u32) -> Option<String> {
1180    let segments = parse_dot_chain(line, character);
1181    segments.last().map(|s| s.name.clone())
1182}
1183
1184#[doc = r"Strip all storage/memory location suffixes from a typeIdentifier to get the base type.
1185Solidity AST uses different suffixes in different contexts:
1186  - `t_struct$_State_$4809_storage_ptr` (UsingForDirective typeName)
1187  - `t_struct$_State_$4809_storage` (mapping value type after extraction)
1188  - `t_struct$_PoolKey_$8887_memory_ptr` (function parameter)
1189All refer to the same logical type. This strips `_ptr` and `_storage`/`_memory`/`_calldata`."]
1190fn strip_type_suffix(type_id: &str) -> &str {
1191    let s = type_id.strip_suffix("_ptr").unwrap_or(type_id);
1192    s.strip_suffix("_storage")
1193        .or_else(|| s.strip_suffix("_memory"))
1194        .or_else(|| s.strip_suffix("_calldata"))
1195        .unwrap_or(s)
1196}
1197
1198/// Look up using-for completions for a type, trying suffix variants.
1199/// The AST stores types with different suffixes (_storage_ptr, _storage, _memory_ptr, etc.)
1200/// across different contexts, so we try multiple forms.
1201fn lookup_using_for(cache: &CompletionCache, type_id: &str) -> Vec<CompletionItem> {
1202    // Exact match first
1203    if let Some(items) = cache.using_for.get(type_id) {
1204        return items.clone();
1205    }
1206
1207    // Strip to base form, then try all common suffix variants
1208    let base = strip_type_suffix(type_id);
1209    let variants = [
1210        base.to_string(),
1211        format!("{}_storage", base),
1212        format!("{}_storage_ptr", base),
1213        format!("{}_memory", base),
1214        format!("{}_memory_ptr", base),
1215        format!("{}_calldata", base),
1216    ];
1217    for variant in &variants {
1218        if variant.as_str() != type_id
1219            && let Some(items) = cache.using_for.get(variant.as_str())
1220        {
1221            return items.clone();
1222        }
1223    }
1224
1225    vec![]
1226}
1227
1228/// Collect completions available for a given typeIdentifier.
1229/// Includes node_members, method_identifiers, using_for, and using_for_wildcard.
1230fn completions_for_type(cache: &CompletionCache, type_id: &str) -> Vec<CompletionItem> {
1231    // Address type
1232    if type_id == "t_address" || type_id == "t_address_payable" {
1233        let mut items = address_members();
1234        // Also add using-for on address
1235        if let Some(uf) = cache.using_for.get(type_id) {
1236            items.extend(uf.iter().cloned());
1237        }
1238        items.extend(cache.using_for_wildcard.iter().cloned());
1239        return items;
1240    }
1241
1242    let resolved_node_id = extract_node_id_from_type(type_id)
1243        .or_else(|| cache.type_to_node.get(type_id).copied())
1244        .or_else(|| {
1245            // Handle synthetic __node_id_ markers from name_to_node_id fallback
1246            type_id
1247                .strip_prefix("__node_id_")
1248                .and_then(|s| s.parse::<u64>().ok())
1249        });
1250
1251    let mut items = Vec::new();
1252    let mut seen_labels: std::collections::HashSet<String> = std::collections::HashSet::new();
1253
1254    if let Some(node_id) = resolved_node_id {
1255        // Method identifiers first — they have full signatures with selectors
1256        if let Some(method_items) = cache.method_identifiers.get(&node_id) {
1257            for item in method_items {
1258                seen_labels.insert(item.label.clone());
1259                items.push(item.clone());
1260            }
1261        }
1262
1263        // Supplement with node_members (state variables, events, errors, modifiers, etc.)
1264        if let Some(members) = cache.node_members.get(&node_id) {
1265            for item in members {
1266                if !seen_labels.contains(&item.label) {
1267                    seen_labels.insert(item.label.clone());
1268                    items.push(item.clone());
1269                }
1270            }
1271        }
1272    }
1273
1274    // Add using-for library functions, but only for value types — not for
1275    // contract/library/interface names. When you type `Lock.`, you want Lock's
1276    // own members, not functions from `using Pool for *` or `using SafeCast for *`.
1277    let is_contract_name = resolved_node_id
1278        .map(|nid| cache.contract_kinds.contains_key(&nid))
1279        .unwrap_or(false);
1280
1281    if !is_contract_name {
1282        // Try exact match first, then try normalized variants (storage_ptr vs storage vs memory_ptr etc.)
1283        let uf_items = lookup_using_for(cache, type_id);
1284        for item in &uf_items {
1285            if !seen_labels.contains(&item.label) {
1286                seen_labels.insert(item.label.clone());
1287                items.push(item.clone());
1288            }
1289        }
1290
1291        // Add wildcard using-for (using X for *)
1292        for item in &cache.using_for_wildcard {
1293            if !seen_labels.contains(&item.label) {
1294                seen_labels.insert(item.label.clone());
1295                items.push(item.clone());
1296            }
1297        }
1298    }
1299
1300    items
1301}
1302
1303/// Resolve a type identifier for a name, considering name_to_type and name_to_node_id.
1304fn resolve_name_to_type_id(cache: &CompletionCache, name: &str) -> Option<String> {
1305    // Direct type lookup
1306    if let Some(tid) = cache.name_to_type.get(name) {
1307        return Some(tid.clone());
1308    }
1309    // Contract/library/interface name → synthesize a type id from node id
1310    if let Some(node_id) = cache.name_to_node_id.get(name) {
1311        // Find a matching typeIdentifier in type_to_node (reverse lookup)
1312        for (tid, nid) in &cache.type_to_node {
1313            if nid == node_id {
1314                return Some(tid.clone());
1315            }
1316        }
1317        // Fallback: use a synthetic marker so completions_for_type can resolve via node_id
1318        return Some(format!("__node_id_{}", node_id));
1319    }
1320    None
1321}
1322
1323/// Find the innermost scope node that contains the given byte position and file.
1324/// `scope_ranges` must be sorted by span size ascending (smallest first).
1325/// Returns the node_id of the smallest scope enclosing the position.
1326pub fn find_innermost_scope(cache: &CompletionCache, byte_pos: usize, file_id: u64) -> Option<u64> {
1327    // scope_ranges is sorted smallest-first, so the first match is the innermost scope
1328    cache
1329        .scope_ranges
1330        .iter()
1331        .find(|r| r.file_id == file_id && r.start <= byte_pos && byte_pos < r.end)
1332        .map(|r| r.node_id)
1333}
1334
1335/// Resolve a variable name to its type by walking up the scope chain.
1336///
1337/// Starting from the innermost scope at the cursor position, check each scope's
1338/// declarations for a matching name. If not found, follow `scope_parent` to the
1339/// next enclosing scope and check again. Stop at the first match.
1340///
1341/// Falls back to `resolve_name_to_type_id` (flat lookup) if scope resolution
1342/// finds nothing, or if the scope data is unavailable.
1343pub fn resolve_name_in_scope(
1344    cache: &CompletionCache,
1345    name: &str,
1346    byte_pos: usize,
1347    file_id: u64,
1348) -> Option<String> {
1349    let mut current_scope = find_innermost_scope(cache, byte_pos, file_id)?;
1350
1351    // Walk up the scope chain
1352    loop {
1353        // Check declarations in this scope
1354        if let Some(decls) = cache.scope_declarations.get(&current_scope) {
1355            for decl in decls {
1356                if decl.name == name {
1357                    return Some(decl.type_id.clone());
1358                }
1359            }
1360        }
1361
1362        // If this scope is a contract, also search inherited contracts
1363        // in C3 linearization order (skipping index 0 which is the contract itself,
1364        // since we already checked its declarations above).
1365        if let Some(bases) = cache.linearized_base_contracts.get(&current_scope) {
1366            for &base_id in bases.iter().skip(1) {
1367                if let Some(decls) = cache.scope_declarations.get(&base_id) {
1368                    for decl in decls {
1369                        if decl.name == name {
1370                            return Some(decl.type_id.clone());
1371                        }
1372                    }
1373                }
1374            }
1375        }
1376
1377        // Move up to parent scope
1378        match cache.scope_parent.get(&current_scope) {
1379            Some(&parent_id) => current_scope = parent_id,
1380            None => break, // reached the top (SourceUnit has no parent)
1381        }
1382    }
1383
1384    // Scope walk found nothing — fall back to flat lookup
1385    // (handles contract/library names which aren't in scope_declarations)
1386    resolve_name_to_type_id(cache, name)
1387}
1388
1389/// Resolve a name within a type context to get the member's type.
1390/// `context_type_id` is the type of the object before the dot.
1391/// `member_name` is the name after the dot.
1392/// `kind` determines how to interpret the result (Call = return type, Index = mapping value, Plain = member type).
1393fn resolve_member_type(
1394    cache: &CompletionCache,
1395    context_type_id: &str,
1396    member_name: &str,
1397    kind: &AccessKind,
1398) -> Option<String> {
1399    let resolved_node_id = extract_node_id_from_type(context_type_id)
1400        .or_else(|| cache.type_to_node.get(context_type_id).copied())
1401        .or_else(|| {
1402            // Handle synthetic __node_id_ markers
1403            context_type_id
1404                .strip_prefix("__node_id_")
1405                .and_then(|s| s.parse::<u64>().ok())
1406        });
1407
1408    let node_id = resolved_node_id?;
1409
1410    match kind {
1411        AccessKind::Call => {
1412            // Look up the function's return type
1413            cache
1414                .function_return_types
1415                .get(&(node_id, member_name.to_string()))
1416                .cloned()
1417        }
1418        AccessKind::Index => {
1419            // Look up the member's type, then extract mapping value type
1420            if let Some(members) = cache.node_members.get(&node_id) {
1421                for member in members {
1422                    if member.label == member_name {
1423                        // Get the typeIdentifier from name_to_type
1424                        if let Some(tid) = cache.name_to_type.get(member_name) {
1425                            if tid.starts_with("t_mapping") {
1426                                return extract_mapping_value_type(tid);
1427                            }
1428                            return Some(tid.clone());
1429                        }
1430                    }
1431                }
1432            }
1433            // Also check: the identifier itself might be a mapping variable
1434            if let Some(tid) = cache.name_to_type.get(member_name)
1435                && tid.starts_with("t_mapping")
1436            {
1437                return extract_mapping_value_type(tid);
1438            }
1439            None
1440        }
1441        AccessKind::Plain => {
1442            // Look up member's own type from name_to_type
1443            cache.name_to_type.get(member_name).cloned()
1444        }
1445    }
1446}
1447
1448/// Scope context for scope-aware completion resolution.
1449/// When present, type resolution uses the scope chain at the cursor position
1450/// instead of the flat first-wins `name_to_type` map.
1451pub struct ScopeContext {
1452    /// Byte offset of the cursor in the source file.
1453    pub byte_pos: usize,
1454    /// Source file id (from the AST `src` field).
1455    pub file_id: u64,
1456}
1457
1458/// Resolve a name to a type, using scope context if available.
1459/// With scope context: walks up the scope chain from the cursor position.
1460/// Without: falls back to flat `name_to_type` lookup.
1461fn resolve_name(
1462    cache: &CompletionCache,
1463    name: &str,
1464    scope_ctx: Option<&ScopeContext>,
1465) -> Option<String> {
1466    if let Some(ctx) = scope_ctx {
1467        resolve_name_in_scope(cache, name, ctx.byte_pos, ctx.file_id)
1468    } else {
1469        resolve_name_to_type_id(cache, name)
1470    }
1471}
1472
1473/// Get completions for a dot-completion request by resolving the full expression chain.
1474pub fn get_dot_completions(
1475    cache: &CompletionCache,
1476    identifier: &str,
1477    scope_ctx: Option<&ScopeContext>,
1478) -> Vec<CompletionItem> {
1479    // Simple single-segment case (backward compat) — just use the identifier directly
1480    if let Some(items) = magic_members(identifier) {
1481        return items;
1482    }
1483
1484    // Try to resolve the identifier's type
1485    let type_id = resolve_name(cache, identifier, scope_ctx);
1486
1487    if let Some(tid) = type_id {
1488        return completions_for_type(cache, &tid);
1489    }
1490
1491    vec![]
1492}
1493
1494/// Get completions by resolving a full dot-expression chain.
1495/// This is the main entry point for dot-completions with chaining support.
1496pub fn get_chain_completions(
1497    cache: &CompletionCache,
1498    chain: &[DotSegment],
1499    scope_ctx: Option<&ScopeContext>,
1500) -> Vec<CompletionItem> {
1501    if chain.is_empty() {
1502        return vec![];
1503    }
1504
1505    // Single segment: simple dot-completion
1506    if chain.len() == 1 {
1507        let seg = &chain[0];
1508
1509        // For Call/Index on the single segment, we need to resolve the return/value type
1510        match seg.kind {
1511            AccessKind::Plain => {
1512                return get_dot_completions(cache, &seg.name, scope_ctx);
1513            }
1514            AccessKind::Call => {
1515                // type(X). — Solidity metatype expression
1516                if seg.name == "type" {
1517                    return type_meta_members(seg.call_args.as_deref(), Some(cache));
1518                }
1519                // foo(). — could be a function call or a type cast like IFoo(addr).
1520                // First check if it's a type cast: name matches a contract/interface/library
1521                if let Some(type_id) = resolve_name(cache, &seg.name, scope_ctx) {
1522                    return completions_for_type(cache, &type_id);
1523                }
1524                // Otherwise look up as a function call — check all function_return_types
1525                for ((_, fn_name), ret_type) in &cache.function_return_types {
1526                    if fn_name == &seg.name {
1527                        return completions_for_type(cache, ret_type);
1528                    }
1529                }
1530                return vec![];
1531            }
1532            AccessKind::Index => {
1533                // foo[key]. — look up foo's type and extract mapping value type
1534                if let Some(tid) = resolve_name(cache, &seg.name, scope_ctx)
1535                    && tid.starts_with("t_mapping")
1536                    && let Some(val_type) = extract_mapping_value_type(&tid)
1537                {
1538                    return completions_for_type(cache, &val_type);
1539                }
1540                return vec![];
1541            }
1542        }
1543    }
1544
1545    // Multi-segment chain: resolve step by step
1546    // First segment: resolve to a type (scope-aware when available)
1547    let first = &chain[0];
1548    let mut current_type = match first.kind {
1549        AccessKind::Plain => resolve_name(cache, &first.name, scope_ctx),
1550        AccessKind::Call => {
1551            // Type cast (e.g. IFoo(addr).) or free function call at the start
1552            resolve_name(cache, &first.name, scope_ctx).or_else(|| {
1553                cache
1554                    .function_return_types
1555                    .iter()
1556                    .find(|((_, fn_name), _)| fn_name == &first.name)
1557                    .map(|(_, ret_type)| ret_type.clone())
1558            })
1559        }
1560        AccessKind::Index => {
1561            // Mapping access at the start
1562            resolve_name(cache, &first.name, scope_ctx).and_then(|tid| {
1563                if tid.starts_with("t_mapping") {
1564                    extract_mapping_value_type(&tid)
1565                } else {
1566                    Some(tid)
1567                }
1568            })
1569        }
1570    };
1571
1572    // Middle segments: resolve each to advance the type
1573    for seg in &chain[1..] {
1574        let ctx_type = match &current_type {
1575            Some(t) => t.clone(),
1576            None => return vec![],
1577        };
1578
1579        current_type = resolve_member_type(cache, &ctx_type, &seg.name, &seg.kind);
1580    }
1581
1582    // Return completions for the final resolved type
1583    match current_type {
1584        Some(tid) => completions_for_type(cache, &tid),
1585        None => vec![],
1586    }
1587}
1588
1589/// Get static completions that never change (keywords, magic globals, global functions, units).
1590/// These are available immediately without an AST cache.
1591pub fn get_static_completions() -> Vec<CompletionItem> {
1592    let mut items = Vec::new();
1593
1594    // Add Solidity keywords
1595    for kw in SOLIDITY_KEYWORDS {
1596        items.push(CompletionItem {
1597            label: kw.to_string(),
1598            kind: Some(CompletionItemKind::KEYWORD),
1599            ..Default::default()
1600        });
1601    }
1602
1603    // Add magic globals
1604    for (name, detail) in MAGIC_GLOBALS {
1605        items.push(CompletionItem {
1606            label: name.to_string(),
1607            kind: Some(CompletionItemKind::VARIABLE),
1608            detail: Some(detail.to_string()),
1609            ..Default::default()
1610        });
1611    }
1612
1613    // Add global functions
1614    for (name, detail) in GLOBAL_FUNCTIONS {
1615        items.push(CompletionItem {
1616            label: name.to_string(),
1617            kind: Some(CompletionItemKind::FUNCTION),
1618            detail: Some(detail.to_string()),
1619            ..Default::default()
1620        });
1621    }
1622
1623    // Add ether denomination units
1624    for (name, detail) in ETHER_UNITS {
1625        items.push(CompletionItem {
1626            label: name.to_string(),
1627            kind: Some(CompletionItemKind::UNIT),
1628            detail: Some(detail.to_string()),
1629            ..Default::default()
1630        });
1631    }
1632
1633    // Add time units
1634    for (name, detail) in TIME_UNITS {
1635        items.push(CompletionItem {
1636            label: name.to_string(),
1637            kind: Some(CompletionItemKind::UNIT),
1638            detail: Some(detail.to_string()),
1639            ..Default::default()
1640        });
1641    }
1642
1643    items
1644}
1645
1646/// Get general completions (all known names + static completions).
1647pub fn get_general_completions(cache: &CompletionCache) -> Vec<CompletionItem> {
1648    let mut items = cache.names.clone();
1649    items.extend(get_static_completions());
1650    items
1651}
1652
1653/// Handle a completion request.
1654///
1655/// When `cache` is `Some`, full AST-aware completions are returned.
1656/// When `cache` is `None`, only static completions (keywords, globals, units)
1657/// and magic dot completions (msg., block., tx., abi., type().) are returned
1658/// immediately — no blocking.
1659///
1660/// `file_id` is the AST source file id, needed for scope-aware resolution.
1661/// When `None`, scope resolution is skipped and flat lookup is used.
1662pub fn handle_completion(
1663    cache: Option<&CompletionCache>,
1664    source_text: &str,
1665    position: Position,
1666    trigger_char: Option<&str>,
1667    file_id: Option<u64>,
1668) -> Option<CompletionResponse> {
1669    let lines: Vec<&str> = source_text.lines().collect();
1670    let line = lines.get(position.line as usize)?;
1671
1672    // Convert encoding-aware column to a byte offset within this line.
1673    let abs_byte = crate::utils::position_to_byte_offset(source_text, position);
1674    let line_start_byte: usize = source_text[..abs_byte]
1675        .rfind('\n')
1676        .map(|i| i + 1)
1677        .unwrap_or(0);
1678    let col_byte = (abs_byte - line_start_byte) as u32;
1679
1680    // Build scope context for scope-aware type resolution
1681    let scope_ctx = file_id.map(|fid| ScopeContext {
1682        byte_pos: abs_byte,
1683        file_id: fid,
1684    });
1685
1686    let items = if trigger_char == Some(".") {
1687        let chain = parse_dot_chain(line, col_byte);
1688        if chain.is_empty() {
1689            return None;
1690        }
1691        match cache {
1692            Some(c) => get_chain_completions(c, &chain, scope_ctx.as_ref()),
1693            None => {
1694                // No cache yet — serve magic dot completions (msg., block., etc.)
1695                if chain.len() == 1 {
1696                    let seg = &chain[0];
1697                    if seg.name == "type" && seg.kind == AccessKind::Call {
1698                        // type(X). without cache — classify based on name alone
1699                        type_meta_members(seg.call_args.as_deref(), None)
1700                    } else if seg.kind == AccessKind::Plain {
1701                        magic_members(&seg.name).unwrap_or_default()
1702                    } else {
1703                        vec![]
1704                    }
1705                } else {
1706                    vec![]
1707                }
1708            }
1709        }
1710    } else {
1711        match cache {
1712            Some(c) => c.general_completions.clone(),
1713            None => get_static_completions(),
1714        }
1715    };
1716
1717    Some(CompletionResponse::List(CompletionList {
1718        is_incomplete: cache.is_none(),
1719        items,
1720    }))
1721}
1722
1723const SOLIDITY_KEYWORDS: &[&str] = &[
1724    "abstract",
1725    "address",
1726    "assembly",
1727    "bool",
1728    "break",
1729    "bytes",
1730    "bytes1",
1731    "bytes4",
1732    "bytes32",
1733    "calldata",
1734    "constant",
1735    "constructor",
1736    "continue",
1737    "contract",
1738    "delete",
1739    "do",
1740    "else",
1741    "emit",
1742    "enum",
1743    "error",
1744    "event",
1745    "external",
1746    "fallback",
1747    "false",
1748    "for",
1749    "function",
1750    "if",
1751    "immutable",
1752    "import",
1753    "indexed",
1754    "int8",
1755    "int24",
1756    "int128",
1757    "int256",
1758    "interface",
1759    "internal",
1760    "library",
1761    "mapping",
1762    "memory",
1763    "modifier",
1764    "new",
1765    "override",
1766    "payable",
1767    "pragma",
1768    "private",
1769    "public",
1770    "pure",
1771    "receive",
1772    "return",
1773    "returns",
1774    "revert",
1775    "storage",
1776    "string",
1777    "struct",
1778    "true",
1779    "type",
1780    "uint8",
1781    "uint24",
1782    "uint128",
1783    "uint160",
1784    "uint256",
1785    "unchecked",
1786    "using",
1787    "view",
1788    "virtual",
1789    "while",
1790];
1791
1792/// Ether denomination units — suffixes for literal numbers.
1793const ETHER_UNITS: &[(&str, &str)] = &[("wei", "1"), ("gwei", "1e9"), ("ether", "1e18")];
1794
1795/// Time units — suffixes for literal numbers.
1796const TIME_UNITS: &[(&str, &str)] = &[
1797    ("seconds", "1"),
1798    ("minutes", "60 seconds"),
1799    ("hours", "3600 seconds"),
1800    ("days", "86400 seconds"),
1801    ("weeks", "604800 seconds"),
1802];
1803
1804const MAGIC_GLOBALS: &[(&str, &str)] = &[
1805    ("msg", "msg"),
1806    ("block", "block"),
1807    ("tx", "tx"),
1808    ("abi", "abi"),
1809    ("this", "address"),
1810    ("super", "contract"),
1811    ("type", "type information"),
1812];
1813
1814const GLOBAL_FUNCTIONS: &[(&str, &str)] = &[
1815    // Mathematical and Cryptographic Functions
1816    ("addmod(uint256, uint256, uint256)", "uint256"),
1817    ("mulmod(uint256, uint256, uint256)", "uint256"),
1818    ("keccak256(bytes memory)", "bytes32"),
1819    ("sha256(bytes memory)", "bytes32"),
1820    ("ripemd160(bytes memory)", "bytes20"),
1821    (
1822        "ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s)",
1823        "address",
1824    ),
1825    // Block and Transaction Properties (functions)
1826    ("blockhash(uint256 blockNumber)", "bytes32"),
1827    ("blobhash(uint256 index)", "bytes32"),
1828    ("gasleft()", "uint256"),
1829    // Error Handling
1830    ("assert(bool condition)", ""),
1831    ("require(bool condition)", ""),
1832    ("require(bool condition, string memory message)", ""),
1833    ("revert()", ""),
1834    ("revert(string memory reason)", ""),
1835    // Contract-related
1836    ("selfdestruct(address payable recipient)", ""),
1837];