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