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