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