Skip to main content

solidity_language_server/
completion.rs

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