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