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