1use serde_json::Value;
2use std::collections::HashMap;
3use tower_lsp::lsp_types::{
4 CompletionItem, CompletionItemKind, CompletionList, CompletionResponse, Position,
5};
6
7use crate::goto::CHILD_KEYS;
8use crate::types::{FileId, NodeId, SourceLoc};
9
10#[derive(Debug, Clone)]
12pub struct ScopedDeclaration {
13 pub name: String,
15 pub type_id: String,
17}
18
19#[derive(Debug, Clone)]
21pub struct ScopeRange {
22 pub node_id: NodeId,
24 pub start: usize,
26 pub end: usize,
28 pub file_id: FileId,
30}
31
32pub struct CompletionCache {
34 pub names: Vec<CompletionItem>,
36
37 pub name_to_type: HashMap<String, String>,
39
40 pub node_members: HashMap<NodeId, Vec<CompletionItem>>,
42
43 pub type_to_node: HashMap<String, NodeId>,
45
46 pub name_to_node_id: HashMap<String, NodeId>,
48
49 pub method_identifiers: HashMap<NodeId, Vec<CompletionItem>>,
52
53 pub function_return_types: HashMap<(NodeId, String), String>,
56
57 pub using_for: HashMap<String, Vec<CompletionItem>>,
60
61 pub using_for_wildcard: Vec<CompletionItem>,
63
64 pub general_completions: Vec<CompletionItem>,
67
68 pub scope_declarations: HashMap<NodeId, Vec<ScopedDeclaration>>,
72
73 pub scope_parent: HashMap<NodeId, NodeId>,
76
77 pub scope_ranges: Vec<ScopeRange>,
80
81 pub path_to_file_id: HashMap<String, FileId>,
84
85 pub linearized_base_contracts: HashMap<NodeId, Vec<NodeId>>,
89
90 pub contract_kinds: HashMap<NodeId, String>,
94}
95
96fn push_if_node_or_array<'a>(tree: &'a Value, key: &str, stack: &mut Vec<&'a Value>) {
97 if let Some(value) = tree.get(key) {
98 match value {
99 Value::Array(arr) => stack.extend(arr),
100 Value::Object(_) => stack.push(value),
101 _ => {}
102 }
103 }
104}
105
106fn node_type_to_completion_kind(node_type: &str) -> CompletionItemKind {
108 match node_type {
109 "FunctionDefinition" => CompletionItemKind::FUNCTION,
110 "VariableDeclaration" => CompletionItemKind::VARIABLE,
111 "ContractDefinition" => CompletionItemKind::CLASS,
112 "StructDefinition" => CompletionItemKind::STRUCT,
113 "EnumDefinition" => CompletionItemKind::ENUM,
114 "EnumValue" => CompletionItemKind::ENUM_MEMBER,
115 "EventDefinition" => CompletionItemKind::EVENT,
116 "ErrorDefinition" => CompletionItemKind::EVENT,
117 "ModifierDefinition" => CompletionItemKind::METHOD,
118 "ImportDirective" => CompletionItemKind::MODULE,
119 _ => CompletionItemKind::TEXT,
120 }
121}
122
123fn parse_src(node: &Value) -> Option<SourceLoc> {
126 let src = node.get("src").and_then(|v| v.as_str())?;
127 SourceLoc::parse(src)
128}
129
130pub fn extract_node_id_from_type(type_id: &str) -> Option<NodeId> {
135 let mut last_id = None;
138 let mut i = 0;
139 let bytes = type_id.as_bytes();
140 while i < bytes.len() {
141 if i + 1 < bytes.len() && bytes[i] == b'_' && bytes[i + 1] == b'$' {
142 i += 2;
143 let start = i;
144 while i < bytes.len() && bytes[i].is_ascii_digit() {
145 i += 1;
146 }
147 if i > start
148 && let Ok(id) = type_id[start..i].parse::<u64>()
149 {
150 last_id = Some(NodeId(id));
151 }
152 } else {
153 i += 1;
154 }
155 }
156 last_id
157}
158
159fn build_function_signature(node: &Value) -> Option<String> {
162 let name = node.get("name").and_then(|v| v.as_str()).unwrap_or("");
163 if name.is_empty() {
164 return None;
165 }
166
167 let params = node
168 .get("parameters")
169 .and_then(|p| p.get("parameters"))
170 .and_then(|v| v.as_array());
171
172 let mut sig = String::new();
173 sig.push_str(name);
174 sig.push('(');
175
176 if let Some(params) = params {
177 for (i, param) in params.iter().enumerate() {
178 if i > 0 {
179 sig.push_str(", ");
180 }
181 let type_str = param
182 .get("typeDescriptions")
183 .and_then(|td| td.get("typeString"))
184 .and_then(|v| v.as_str())
185 .unwrap_or("?");
186 let clean_type = type_str
188 .strip_prefix("struct ")
189 .or_else(|| type_str.strip_prefix("contract "))
190 .or_else(|| type_str.strip_prefix("enum "))
191 .unwrap_or(type_str);
192 let param_name = param.get("name").and_then(|v| v.as_str()).unwrap_or("");
193 sig.push_str(clean_type);
194 if !param_name.is_empty() {
195 sig.push(' ');
196 sig.push_str(param_name);
197 }
198 }
199 }
200 sig.push(')');
201
202 let returns = node
204 .get("returnParameters")
205 .and_then(|p| p.get("parameters"))
206 .and_then(|v| v.as_array());
207
208 if let Some(returns) = returns
209 && !returns.is_empty()
210 {
211 sig.push_str(" returns (");
212 for (i, ret) in returns.iter().enumerate() {
213 if i > 0 {
214 sig.push_str(", ");
215 }
216 let type_str = ret
217 .get("typeDescriptions")
218 .and_then(|td| td.get("typeString"))
219 .and_then(|v| v.as_str())
220 .unwrap_or("?");
221 let clean_type = type_str
222 .strip_prefix("struct ")
223 .or_else(|| type_str.strip_prefix("contract "))
224 .or_else(|| type_str.strip_prefix("enum "))
225 .unwrap_or(type_str);
226 let ret_name = ret.get("name").and_then(|v| v.as_str()).unwrap_or("");
227 sig.push_str(clean_type);
228 if !ret_name.is_empty() {
229 sig.push(' ');
230 sig.push_str(ret_name);
231 }
232 }
233 sig.push(')');
234 }
235
236 Some(sig)
237}
238
239pub fn extract_mapping_value_type(type_id: &str) -> Option<String> {
246 let mut current = type_id;
247
248 loop {
249 if !current.starts_with("t_mapping$_") {
250 let result = current.trim_end_matches("_$");
253 return if result.is_empty() {
254 None
255 } else {
256 Some(result.to_string())
257 };
258 }
259
260 let inner = ¤t["t_mapping$_".len()..];
262
263 let mut depth = 0i32;
267 let bytes = inner.as_bytes();
268 let mut split_pos = None;
269
270 let mut i = 0;
271 while i < bytes.len() {
272 if i + 1 < bytes.len() && bytes[i] == b'$' && bytes[i + 1] == b'_' {
273 depth += 1;
274 i += 2;
275 } else if i + 2 < bytes.len()
276 && bytes[i] == b'_'
277 && bytes[i + 1] == b'$'
278 && bytes[i + 2] == b'_'
279 && depth == 0
280 {
281 split_pos = Some(i);
283 break;
284 } else if i + 1 < bytes.len() && bytes[i] == b'_' && bytes[i + 1] == b'$' {
285 depth -= 1;
286 i += 2;
287 } else {
288 i += 1;
289 }
290 }
291
292 if let Some(pos) = split_pos {
293 current = &inner[pos + 3..];
295 } else {
296 return None;
297 }
298 }
299}
300
301fn count_abi_params(signature: &str) -> usize {
304 let start = match signature.find('(') {
306 Some(i) => i + 1,
307 None => return 0,
308 };
309 let bytes = signature.as_bytes();
310 if start >= bytes.len() {
311 return 0;
312 }
313 if bytes[start] == b')' {
315 return 0;
316 }
317 let mut count = 1; let mut depth = 0;
319 for &b in &bytes[start..] {
320 match b {
321 b'(' => depth += 1,
322 b')' => {
323 if depth == 0 {
324 break;
325 }
326 depth -= 1;
327 }
328 b',' if depth == 0 => count += 1,
329 _ => {}
330 }
331 }
332 count
333}
334
335fn count_signature_params(sig: &str) -> usize {
337 count_abi_params(sig)
338}
339
340pub fn build_completion_cache(sources: &Value, contracts: Option<&Value>) -> CompletionCache {
343 let mut names: Vec<CompletionItem> = Vec::new();
344 let mut seen_names: HashMap<String, usize> = HashMap::new(); let mut name_to_type: HashMap<String, String> = HashMap::new();
346 let mut node_members: HashMap<NodeId, Vec<CompletionItem>> = HashMap::new();
347 let mut type_to_node: HashMap<String, NodeId> = HashMap::new();
348 let mut method_identifiers: HashMap<NodeId, Vec<CompletionItem>> = HashMap::new();
349 let mut name_to_node_id: HashMap<String, NodeId> = HashMap::new();
350 let mut contract_kinds: HashMap<NodeId, String> = HashMap::new();
351
352 let mut contract_locations: Vec<(String, String, NodeId)> = Vec::new();
354
355 let mut function_signatures: HashMap<NodeId, HashMap<String, Vec<String>>> = HashMap::new();
357
358 let mut function_return_types: HashMap<(NodeId, String), String> = HashMap::new();
360
361 let mut using_for: HashMap<String, Vec<CompletionItem>> = HashMap::new();
363 let mut using_for_wildcard: Vec<CompletionItem> = Vec::new();
364
365 let mut using_for_directives: Vec<(NodeId, Option<String>)> = Vec::new();
367
368 let mut scope_declarations: HashMap<NodeId, Vec<ScopedDeclaration>> = HashMap::new();
370 let mut scope_parent: HashMap<NodeId, NodeId> = HashMap::new();
371 let mut scope_ranges: Vec<ScopeRange> = Vec::new();
372 let mut path_to_file_id: HashMap<String, FileId> = HashMap::new();
373 let mut linearized_base_contracts: HashMap<NodeId, Vec<NodeId>> = HashMap::new();
374
375 if let Some(sources_obj) = sources.as_object() {
376 for (path, source_data) in sources_obj {
377 if let Some(ast) = source_data.get("ast") {
378 if let Some(fid) = source_data.get("id").and_then(|v| v.as_u64()) {
380 path_to_file_id.insert(path.clone(), FileId(fid));
381 }
382 let mut stack: Vec<&Value> = vec![ast];
383
384 while let Some(tree) = stack.pop() {
385 let node_type = tree.get("nodeType").and_then(|v| v.as_str()).unwrap_or("");
386 let name = tree.get("name").and_then(|v| v.as_str()).unwrap_or("");
387 let node_id = tree.get("id").and_then(|v| v.as_u64()).map(NodeId);
388
389 let is_scope_node = matches!(
394 node_type,
395 "SourceUnit"
396 | "ContractDefinition"
397 | "FunctionDefinition"
398 | "ModifierDefinition"
399 | "Block"
400 | "UncheckedBlock"
401 );
402 if is_scope_node && let Some(nid) = node_id {
403 if let Some(src_loc) = parse_src(tree) {
404 scope_ranges.push(ScopeRange {
405 node_id: nid,
406 start: src_loc.offset,
407 end: src_loc.end(),
408 file_id: src_loc.file_id,
409 });
410 }
411 if let Some(parent_id) = tree.get("scope").and_then(|v| v.as_u64()) {
413 scope_parent.insert(nid, NodeId(parent_id));
414 }
415 }
416
417 if node_type == "ContractDefinition"
419 && let Some(nid) = node_id
420 && let Some(bases) = tree
421 .get("linearizedBaseContracts")
422 .and_then(|v| v.as_array())
423 {
424 let base_ids: Vec<NodeId> = bases
425 .iter()
426 .filter_map(|b| b.as_u64())
427 .map(NodeId)
428 .collect();
429 if !base_ids.is_empty() {
430 linearized_base_contracts.insert(nid, base_ids);
431 }
432 }
433
434 if node_type == "VariableDeclaration"
436 && !name.is_empty()
437 && let Some(scope_raw) = tree.get("scope").and_then(|v| v.as_u64())
438 && let Some(tid) = tree
439 .get("typeDescriptions")
440 .and_then(|td| td.get("typeIdentifier"))
441 .and_then(|v| v.as_str())
442 {
443 scope_declarations
444 .entry(NodeId(scope_raw))
445 .or_default()
446 .push(ScopedDeclaration {
447 name: name.to_string(),
448 type_id: tid.to_string(),
449 });
450 }
451
452 if node_type == "FunctionDefinition"
454 && !name.is_empty()
455 && let Some(scope_raw) = tree.get("scope").and_then(|v| v.as_u64())
456 && let Some(tid) = tree
457 .get("typeDescriptions")
458 .and_then(|td| td.get("typeIdentifier"))
459 .and_then(|v| v.as_str())
460 {
461 scope_declarations
462 .entry(NodeId(scope_raw))
463 .or_default()
464 .push(ScopedDeclaration {
465 name: name.to_string(),
466 type_id: tid.to_string(),
467 });
468 }
469
470 if !name.is_empty() && !seen_names.contains_key(name) {
472 let type_string = tree
473 .get("typeDescriptions")
474 .and_then(|td| td.get("typeString"))
475 .and_then(|v| v.as_str())
476 .map(|s| s.to_string());
477
478 let type_id = tree
479 .get("typeDescriptions")
480 .and_then(|td| td.get("typeIdentifier"))
481 .and_then(|v| v.as_str());
482
483 let kind = node_type_to_completion_kind(node_type);
484
485 let item = CompletionItem {
486 label: name.to_string(),
487 kind: Some(kind),
488 detail: type_string,
489 ..Default::default()
490 };
491
492 let idx = names.len();
493 names.push(item);
494 seen_names.insert(name.to_string(), idx);
495
496 if let Some(tid) = type_id {
498 name_to_type.insert(name.to_string(), tid.to_string());
499 }
500 }
501
502 if node_type == "StructDefinition"
504 && let Some(id) = node_id
505 {
506 let mut members = Vec::new();
507 if let Some(member_array) = tree.get("members").and_then(|v| v.as_array()) {
508 for member in member_array {
509 let member_name =
510 member.get("name").and_then(|v| v.as_str()).unwrap_or("");
511 if member_name.is_empty() {
512 continue;
513 }
514 let member_type = member
515 .get("typeDescriptions")
516 .and_then(|td| td.get("typeString"))
517 .and_then(|v| v.as_str())
518 .map(|s| s.to_string());
519
520 members.push(CompletionItem {
521 label: member_name.to_string(),
522 kind: Some(CompletionItemKind::FIELD),
523 detail: member_type,
524 ..Default::default()
525 });
526 }
527 }
528 if !members.is_empty() {
529 node_members.insert(id, members);
530 }
531
532 if let Some(tid) = tree
534 .get("typeDescriptions")
535 .and_then(|td| td.get("typeIdentifier"))
536 .and_then(|v| v.as_str())
537 {
538 type_to_node.insert(tid.to_string(), id);
539 }
540 }
541
542 if node_type == "ContractDefinition"
544 && let Some(id) = node_id
545 {
546 let mut members = Vec::new();
547 let mut fn_sigs: HashMap<String, Vec<String>> = HashMap::new();
548 if let Some(nodes_array) = tree.get("nodes").and_then(|v| v.as_array()) {
549 for member in nodes_array {
550 let member_type = member
551 .get("nodeType")
552 .and_then(|v| v.as_str())
553 .unwrap_or("");
554 let member_name =
555 member.get("name").and_then(|v| v.as_str()).unwrap_or("");
556 if member_name.is_empty() {
557 continue;
558 }
559
560 let (member_detail, label_details) =
562 if member_type == "FunctionDefinition" {
563 if let Some(ret_params) = member
567 .get("returnParameters")
568 .and_then(|rp| rp.get("parameters"))
569 .and_then(|v| v.as_array())
570 && ret_params.len() == 1
571 && let Some(ret_tid) = ret_params[0]
572 .get("typeDescriptions")
573 .and_then(|td| td.get("typeIdentifier"))
574 .and_then(|v| v.as_str())
575 {
576 function_return_types.insert(
577 (id, member_name.to_string()),
578 ret_tid.to_string(),
579 );
580 }
581
582 if let Some(sig) = build_function_signature(member) {
583 fn_sigs
584 .entry(member_name.to_string())
585 .or_default()
586 .push(sig.clone());
587 (Some(sig), None)
588 } else {
589 (
590 member
591 .get("typeDescriptions")
592 .and_then(|td| td.get("typeString"))
593 .and_then(|v| v.as_str())
594 .map(|s| s.to_string()),
595 None,
596 )
597 }
598 } else {
599 (
600 member
601 .get("typeDescriptions")
602 .and_then(|td| td.get("typeString"))
603 .and_then(|v| v.as_str())
604 .map(|s| s.to_string()),
605 None,
606 )
607 };
608
609 let kind = node_type_to_completion_kind(member_type);
610 members.push(CompletionItem {
611 label: member_name.to_string(),
612 kind: Some(kind),
613 detail: member_detail,
614 label_details,
615 ..Default::default()
616 });
617 }
618 }
619 if !members.is_empty() {
620 node_members.insert(id, members);
621 }
622 if !fn_sigs.is_empty() {
623 function_signatures.insert(id, fn_sigs);
624 }
625
626 if let Some(tid) = tree
627 .get("typeDescriptions")
628 .and_then(|td| td.get("typeIdentifier"))
629 .and_then(|v| v.as_str())
630 {
631 type_to_node.insert(tid.to_string(), id);
632 }
633
634 if !name.is_empty() {
636 contract_locations.push((path.clone(), name.to_string(), id));
637 name_to_node_id.insert(name.to_string(), id);
638 }
639
640 if let Some(ck) = tree.get("contractKind").and_then(|v| v.as_str()) {
642 contract_kinds.insert(id, ck.to_string());
643 }
644 }
645
646 if node_type == "EnumDefinition"
648 && let Some(id) = node_id
649 {
650 let mut members = Vec::new();
651 if let Some(member_array) = tree.get("members").and_then(|v| v.as_array()) {
652 for member in member_array {
653 let member_name =
654 member.get("name").and_then(|v| v.as_str()).unwrap_or("");
655 if member_name.is_empty() {
656 continue;
657 }
658 members.push(CompletionItem {
659 label: member_name.to_string(),
660 kind: Some(CompletionItemKind::ENUM_MEMBER),
661 detail: None,
662 ..Default::default()
663 });
664 }
665 }
666 if !members.is_empty() {
667 node_members.insert(id, members);
668 }
669
670 if let Some(tid) = tree
671 .get("typeDescriptions")
672 .and_then(|td| td.get("typeIdentifier"))
673 .and_then(|v| v.as_str())
674 {
675 type_to_node.insert(tid.to_string(), id);
676 }
677 }
678
679 if node_type == "UsingForDirective" {
681 let target_type = tree.get("typeName").and_then(|tn| {
683 tn.get("typeDescriptions")
684 .and_then(|td| td.get("typeIdentifier"))
685 .and_then(|v| v.as_str())
686 .map(|s| s.to_string())
687 });
688
689 if let Some(lib) = tree.get("libraryName") {
691 if let Some(lib_id) =
692 lib.get("referencedDeclaration").and_then(|v| v.as_u64())
693 {
694 using_for_directives.push((NodeId(lib_id), target_type));
695 }
696 }
697 else if let Some(func_list) =
701 tree.get("functionList").and_then(|v| v.as_array())
702 {
703 for entry in func_list {
704 if entry.get("operator").is_some() {
706 continue;
707 }
708 if let Some(def) = entry.get("definition") {
709 let fn_name =
710 def.get("name").and_then(|v| v.as_str()).unwrap_or("");
711 if !fn_name.is_empty() {
712 let items = if let Some(ref tid) = target_type {
713 using_for.entry(tid.clone()).or_default()
714 } else {
715 &mut using_for_wildcard
716 };
717 items.push(CompletionItem {
718 label: fn_name.to_string(),
719 kind: Some(CompletionItemKind::FUNCTION),
720 detail: None,
721 ..Default::default()
722 });
723 }
724 }
725 }
726 }
727 }
728
729 for key in CHILD_KEYS {
731 push_if_node_or_array(tree, key, &mut stack);
732 }
733 }
734 }
735 }
736 }
737
738 for (lib_id, target_type) in &using_for_directives {
741 if let Some(lib_members) = node_members.get(lib_id) {
742 let items: Vec<CompletionItem> = lib_members
743 .iter()
744 .filter(|item| item.kind == Some(CompletionItemKind::FUNCTION))
745 .cloned()
746 .collect();
747 if !items.is_empty() {
748 if let Some(tid) = target_type {
749 using_for.entry(tid.clone()).or_default().extend(items);
750 } else {
751 using_for_wildcard.extend(items);
752 }
753 }
754 }
755 }
756
757 if let Some(contracts_val) = contracts
759 && let Some(contracts_obj) = contracts_val.as_object()
760 {
761 for (path, contract_name, node_id) in &contract_locations {
762 let fn_sigs = function_signatures.get(node_id);
764
765 if let Some(path_entry) = contracts_obj.get(path)
766 && let Some(contract_entry) = path_entry.get(contract_name)
767 && let Some(evm) = contract_entry.get("evm")
768 && let Some(methods) = evm.get("methodIdentifiers")
769 && let Some(methods_obj) = methods.as_object()
770 {
771 let mut items: Vec<CompletionItem> = Vec::new();
772 for (signature, selector_val) in methods_obj {
773 let fn_name = signature.split('(').next().unwrap_or(signature).to_string();
776 let selector_str = selector_val
777 .as_str()
778 .map(|s| crate::types::FuncSelector::new(s).to_prefixed())
779 .unwrap_or_default();
780
781 let description =
783 fn_sigs
784 .and_then(|sigs| sigs.get(&fn_name))
785 .and_then(|sig_list| {
786 if sig_list.len() == 1 {
787 Some(sig_list[0].clone())
789 } else {
790 let abi_param_count = count_abi_params(signature);
792 sig_list
793 .iter()
794 .find(|s| count_signature_params(s) == abi_param_count)
795 .cloned()
796 }
797 });
798
799 items.push(CompletionItem {
800 label: fn_name,
801 kind: Some(CompletionItemKind::FUNCTION),
802 detail: Some(signature.clone()),
803 label_details: Some(tower_lsp::lsp_types::CompletionItemLabelDetails {
804 detail: Some(selector_str),
805 description,
806 }),
807 ..Default::default()
808 });
809 }
810 if !items.is_empty() {
811 method_identifiers.insert(*node_id, items);
812 }
813 }
814 }
815 }
816
817 let mut general_completions = names.clone();
819 general_completions.extend(get_static_completions());
820
821 scope_ranges.sort_by_key(|r| r.end - r.start);
823
824 let orphan_ids: Vec<NodeId> = scope_ranges
830 .iter()
831 .filter(|r| !scope_parent.contains_key(&r.node_id))
832 .map(|r| r.node_id)
833 .collect();
834 let range_by_id: HashMap<NodeId, (usize, usize, FileId)> = scope_ranges
836 .iter()
837 .map(|r| (r.node_id, (r.start, r.end, r.file_id)))
838 .collect();
839 for orphan_id in &orphan_ids {
840 if let Some(&(start, end, file_id)) = range_by_id.get(orphan_id) {
841 let parent = scope_ranges
844 .iter()
845 .find(|r| {
846 r.node_id != *orphan_id
847 && r.file_id == file_id
848 && r.start <= start
849 && r.end >= end
850 && (r.end - r.start) > (end - start)
851 })
852 .map(|r| r.node_id);
853 if let Some(parent_id) = parent {
854 scope_parent.insert(*orphan_id, parent_id);
855 }
856 }
857 }
858
859 CompletionCache {
860 names,
861 name_to_type,
862 node_members,
863 type_to_node,
864 name_to_node_id,
865 method_identifiers,
866 function_return_types,
867 using_for,
868 using_for_wildcard,
869 general_completions,
870 scope_declarations,
871 scope_parent,
872 scope_ranges,
873 path_to_file_id,
874 linearized_base_contracts,
875 contract_kinds,
876 }
877}
878
879fn magic_members(name: &str) -> Option<Vec<CompletionItem>> {
881 let items = match name {
882 "msg" => vec![
883 ("data", "bytes calldata"),
884 ("sender", "address"),
885 ("sig", "bytes4"),
886 ("value", "uint256"),
887 ],
888 "block" => vec![
889 ("basefee", "uint256"),
890 ("blobbasefee", "uint256"),
891 ("chainid", "uint256"),
892 ("coinbase", "address payable"),
893 ("difficulty", "uint256"),
894 ("gaslimit", "uint256"),
895 ("number", "uint256"),
896 ("prevrandao", "uint256"),
897 ("timestamp", "uint256"),
898 ],
899 "tx" => vec![("gasprice", "uint256"), ("origin", "address")],
900 "abi" => vec![
901 ("decode(bytes memory, (...))", "..."),
902 ("encode(...)", "bytes memory"),
903 ("encodePacked(...)", "bytes memory"),
904 ("encodeWithSelector(bytes4, ...)", "bytes memory"),
905 ("encodeWithSignature(string memory, ...)", "bytes memory"),
906 ("encodeCall(function, (...))", "bytes memory"),
907 ],
908 "type" => vec![
911 ("name", "string"),
912 ("creationCode", "bytes memory"),
913 ("runtimeCode", "bytes memory"),
914 ("interfaceId", "bytes4"),
915 ("min", "T"),
916 ("max", "T"),
917 ],
918 "bytes" => vec![("concat(...)", "bytes memory")],
920 "string" => vec![("concat(...)", "string memory")],
921 _ => return None,
922 };
923
924 Some(
925 items
926 .into_iter()
927 .map(|(label, detail)| CompletionItem {
928 label: label.to_string(),
929 kind: Some(CompletionItemKind::PROPERTY),
930 detail: Some(detail.to_string()),
931 ..Default::default()
932 })
933 .collect(),
934 )
935}
936
937#[derive(Debug, Clone, Copy, PartialEq, Eq)]
940enum TypeMetaKind {
941 Contract,
943 Interface,
945 IntegerType,
947 Unknown,
949}
950
951fn classify_type_arg(arg: &str, cache: Option<&CompletionCache>) -> TypeMetaKind {
953 if arg == "int" || arg == "uint" {
955 return TypeMetaKind::IntegerType;
956 }
957 if let Some(suffix) = arg.strip_prefix("uint").or_else(|| arg.strip_prefix("int"))
958 && let Ok(n) = suffix.parse::<u16>()
959 && (8..=256).contains(&n)
960 && n % 8 == 0
961 {
962 return TypeMetaKind::IntegerType;
963 }
964
965 if let Some(c) = cache
967 && let Some(&node_id) = c.name_to_node_id.get(arg)
968 {
969 return match c.contract_kinds.get(&node_id).map(|s| s.as_str()) {
970 Some("interface") => TypeMetaKind::Interface,
971 Some("library") => TypeMetaKind::Contract, _ => TypeMetaKind::Contract,
973 };
974 }
975
976 TypeMetaKind::Unknown
977}
978
979fn type_meta_members(arg: Option<&str>, cache: Option<&CompletionCache>) -> Vec<CompletionItem> {
981 let kind = match arg {
982 Some(a) => classify_type_arg(a, cache),
983 None => TypeMetaKind::Unknown,
984 };
985
986 let items: Vec<(&str, &str)> = match kind {
987 TypeMetaKind::Contract => vec![
988 ("name", "string"),
989 ("creationCode", "bytes memory"),
990 ("runtimeCode", "bytes memory"),
991 ],
992 TypeMetaKind::Interface => vec![("name", "string"), ("interfaceId", "bytes4")],
993 TypeMetaKind::IntegerType => vec![("min", "T"), ("max", "T")],
994 TypeMetaKind::Unknown => vec![
995 ("name", "string"),
996 ("creationCode", "bytes memory"),
997 ("runtimeCode", "bytes memory"),
998 ("interfaceId", "bytes4"),
999 ("min", "T"),
1000 ("max", "T"),
1001 ],
1002 };
1003
1004 items
1005 .into_iter()
1006 .map(|(label, detail)| CompletionItem {
1007 label: label.to_string(),
1008 kind: Some(CompletionItemKind::PROPERTY),
1009 detail: Some(detail.to_string()),
1010 ..Default::default()
1011 })
1012 .collect()
1013}
1014
1015fn address_members() -> Vec<CompletionItem> {
1017 [
1018 ("balance", "uint256", CompletionItemKind::PROPERTY),
1019 ("code", "bytes memory", CompletionItemKind::PROPERTY),
1020 ("codehash", "bytes32", CompletionItemKind::PROPERTY),
1021 ("transfer(uint256)", "", CompletionItemKind::FUNCTION),
1022 ("send(uint256)", "bool", CompletionItemKind::FUNCTION),
1023 (
1024 "call(bytes memory)",
1025 "(bool, bytes memory)",
1026 CompletionItemKind::FUNCTION,
1027 ),
1028 (
1029 "delegatecall(bytes memory)",
1030 "(bool, bytes memory)",
1031 CompletionItemKind::FUNCTION,
1032 ),
1033 (
1034 "staticcall(bytes memory)",
1035 "(bool, bytes memory)",
1036 CompletionItemKind::FUNCTION,
1037 ),
1038 ]
1039 .iter()
1040 .map(|(label, detail, kind)| CompletionItem {
1041 label: label.to_string(),
1042 kind: Some(*kind),
1043 detail: if detail.is_empty() {
1044 None
1045 } else {
1046 Some(detail.to_string())
1047 },
1048 ..Default::default()
1049 })
1050 .collect()
1051}
1052
1053#[derive(Debug, Clone, PartialEq)]
1055pub enum AccessKind {
1056 Plain,
1058 Call,
1060 Index,
1062}
1063
1064#[derive(Debug, Clone, PartialEq)]
1066pub struct DotSegment {
1067 pub name: String,
1068 pub kind: AccessKind,
1069 pub call_args: Option<String>,
1072}
1073
1074fn skip_brackets_backwards(bytes: &[u8], pos: usize) -> usize {
1078 let close = bytes[pos];
1079 let open = match close {
1080 b')' => b'(',
1081 b']' => b'[',
1082 _ => return pos,
1083 };
1084 let mut depth = 1u32;
1085 let mut i = pos;
1086 while i > 0 && depth > 0 {
1087 i -= 1;
1088 if bytes[i] == close {
1089 depth += 1;
1090 } else if bytes[i] == open {
1091 depth -= 1;
1092 }
1093 }
1094 i
1095}
1096
1097pub fn parse_dot_chain(line: &str, character: u32) -> Vec<DotSegment> {
1102 let col = character as usize;
1103 if col == 0 {
1104 return vec![];
1105 }
1106
1107 let bytes = line.as_bytes();
1108 let mut segments: Vec<DotSegment> = Vec::new();
1109
1110 let mut pos = col;
1112 if pos > 0 && pos <= bytes.len() && bytes[pos - 1] == b'.' {
1113 pos -= 1;
1114 }
1115
1116 loop {
1117 if pos == 0 {
1118 break;
1119 }
1120
1121 let (kind, call_args) = if bytes[pos - 1] == b')' {
1123 let close = pos - 1; pos = skip_brackets_backwards(bytes, close);
1125 let args_text = String::from_utf8_lossy(&bytes[pos + 1..close])
1127 .trim()
1128 .to_string();
1129 let args = if args_text.is_empty() {
1130 None
1131 } else {
1132 Some(args_text)
1133 };
1134 (AccessKind::Call, args)
1135 } else if bytes[pos - 1] == b']' {
1136 pos -= 1; pos = skip_brackets_backwards(bytes, pos);
1138 (AccessKind::Index, None)
1139 } else {
1140 (AccessKind::Plain, None)
1141 };
1142
1143 let end = pos;
1145 while pos > 0 && (bytes[pos - 1].is_ascii_alphanumeric() || bytes[pos - 1] == b'_') {
1146 pos -= 1;
1147 }
1148
1149 if pos == end {
1150 break;
1152 }
1153
1154 let name = String::from_utf8_lossy(&bytes[pos..end]).to_string();
1155 segments.push(DotSegment {
1156 name,
1157 kind,
1158 call_args,
1159 });
1160
1161 if pos > 0 && bytes[pos - 1] == b'.' {
1163 pos -= 1; } else {
1165 break;
1166 }
1167 }
1168
1169 segments.reverse(); segments
1171}
1172
1173pub fn extract_identifier_before_dot(line: &str, character: u32) -> Option<String> {
1176 let segments = parse_dot_chain(line, character);
1177 segments.last().map(|s| s.name.clone())
1178}
1179
1180#[doc = r"Strip all storage/memory location suffixes from a typeIdentifier to get the base type.
1181Solidity AST uses different suffixes in different contexts:
1182 - `t_struct$_State_$4809_storage_ptr` (UsingForDirective typeName)
1183 - `t_struct$_State_$4809_storage` (mapping value type after extraction)
1184 - `t_struct$_PoolKey_$8887_memory_ptr` (function parameter)
1185All refer to the same logical type. This strips `_ptr` and `_storage`/`_memory`/`_calldata`."]
1186fn strip_type_suffix(type_id: &str) -> &str {
1187 let s = type_id.strip_suffix("_ptr").unwrap_or(type_id);
1188 s.strip_suffix("_storage")
1189 .or_else(|| s.strip_suffix("_memory"))
1190 .or_else(|| s.strip_suffix("_calldata"))
1191 .unwrap_or(s)
1192}
1193
1194fn lookup_using_for(cache: &CompletionCache, type_id: &str) -> Vec<CompletionItem> {
1198 if let Some(items) = cache.using_for.get(type_id) {
1200 return items.clone();
1201 }
1202
1203 let base = strip_type_suffix(type_id);
1205 let variants = [
1206 base.to_string(),
1207 format!("{}_storage", base),
1208 format!("{}_storage_ptr", base),
1209 format!("{}_memory", base),
1210 format!("{}_memory_ptr", base),
1211 format!("{}_calldata", base),
1212 ];
1213 for variant in &variants {
1214 if variant.as_str() != type_id
1215 && let Some(items) = cache.using_for.get(variant.as_str())
1216 {
1217 return items.clone();
1218 }
1219 }
1220
1221 vec![]
1222}
1223
1224fn completions_for_type(cache: &CompletionCache, type_id: &str) -> Vec<CompletionItem> {
1227 if type_id == "t_address" || type_id == "t_address_payable" {
1229 let mut items = address_members();
1230 if let Some(uf) = cache.using_for.get(type_id) {
1232 items.extend(uf.iter().cloned());
1233 }
1234 items.extend(cache.using_for_wildcard.iter().cloned());
1235 return items;
1236 }
1237
1238 let resolved_node_id = extract_node_id_from_type(type_id)
1239 .or_else(|| cache.type_to_node.get(type_id).copied())
1240 .or_else(|| {
1241 type_id
1243 .strip_prefix("__node_id_")
1244 .and_then(|s| s.parse::<u64>().ok())
1245 .map(NodeId)
1246 });
1247
1248 let mut items = Vec::new();
1249 let mut seen_labels: std::collections::HashSet<String> = std::collections::HashSet::new();
1250
1251 if let Some(node_id) = resolved_node_id {
1252 if let Some(method_items) = cache.method_identifiers.get(&node_id) {
1254 for item in method_items {
1255 seen_labels.insert(item.label.clone());
1256 items.push(item.clone());
1257 }
1258 }
1259
1260 if let Some(members) = cache.node_members.get(&node_id) {
1262 for item in members {
1263 if !seen_labels.contains(&item.label) {
1264 seen_labels.insert(item.label.clone());
1265 items.push(item.clone());
1266 }
1267 }
1268 }
1269 }
1270
1271 let is_contract_name = resolved_node_id
1275 .map(|nid| cache.contract_kinds.contains_key(&nid))
1276 .unwrap_or(false);
1277
1278 if !is_contract_name {
1279 let uf_items = lookup_using_for(cache, type_id);
1281 for item in &uf_items {
1282 if !seen_labels.contains(&item.label) {
1283 seen_labels.insert(item.label.clone());
1284 items.push(item.clone());
1285 }
1286 }
1287
1288 for item in &cache.using_for_wildcard {
1290 if !seen_labels.contains(&item.label) {
1291 seen_labels.insert(item.label.clone());
1292 items.push(item.clone());
1293 }
1294 }
1295 }
1296
1297 items
1298}
1299
1300fn resolve_name_to_type_id(cache: &CompletionCache, name: &str) -> Option<String> {
1302 if let Some(tid) = cache.name_to_type.get(name) {
1304 return Some(tid.clone());
1305 }
1306 if let Some(node_id) = cache.name_to_node_id.get(name) {
1308 for (tid, nid) in &cache.type_to_node {
1310 if nid == node_id {
1311 return Some(tid.clone());
1312 }
1313 }
1314 return Some(format!("__node_id_{}", node_id));
1316 }
1317 None
1318}
1319
1320pub fn find_innermost_scope(
1324 cache: &CompletionCache,
1325 byte_pos: usize,
1326 file_id: FileId,
1327) -> Option<NodeId> {
1328 cache
1330 .scope_ranges
1331 .iter()
1332 .find(|r| r.file_id == file_id && r.start <= byte_pos && byte_pos < r.end)
1333 .map(|r| r.node_id)
1334}
1335
1336pub fn resolve_name_in_scope(
1345 cache: &CompletionCache,
1346 name: &str,
1347 byte_pos: usize,
1348 file_id: FileId,
1349) -> Option<String> {
1350 let mut current_scope = find_innermost_scope(cache, byte_pos, file_id)?;
1351
1352 loop {
1354 if let Some(decls) = cache.scope_declarations.get(¤t_scope) {
1356 for decl in decls {
1357 if decl.name == name {
1358 return Some(decl.type_id.clone());
1359 }
1360 }
1361 }
1362
1363 if let Some(bases) = cache.linearized_base_contracts.get(¤t_scope) {
1367 for &base_id in bases.iter().skip(1) {
1368 if let Some(decls) = cache.scope_declarations.get(&base_id) {
1369 for decl in decls {
1370 if decl.name == name {
1371 return Some(decl.type_id.clone());
1372 }
1373 }
1374 }
1375 }
1376 }
1377
1378 match cache.scope_parent.get(¤t_scope) {
1380 Some(&parent_id) => current_scope = parent_id,
1381 None => break, }
1383 }
1384
1385 resolve_name_to_type_id(cache, name)
1388}
1389
1390fn resolve_member_type(
1395 cache: &CompletionCache,
1396 context_type_id: &str,
1397 member_name: &str,
1398 kind: &AccessKind,
1399) -> Option<String> {
1400 let resolved_node_id = extract_node_id_from_type(context_type_id)
1401 .or_else(|| cache.type_to_node.get(context_type_id).copied())
1402 .or_else(|| {
1403 context_type_id
1405 .strip_prefix("__node_id_")
1406 .and_then(|s| s.parse::<u64>().ok())
1407 .map(NodeId)
1408 });
1409
1410 let node_id = resolved_node_id?;
1411
1412 match kind {
1413 AccessKind::Call => {
1414 cache
1416 .function_return_types
1417 .get(&(node_id, member_name.to_string()))
1418 .cloned()
1419 }
1420 AccessKind::Index => {
1421 if let Some(members) = cache.node_members.get(&node_id) {
1423 for member in members {
1424 if member.label == member_name {
1425 if let Some(tid) = cache.name_to_type.get(member_name) {
1427 if tid.starts_with("t_mapping") {
1428 return extract_mapping_value_type(tid);
1429 }
1430 return Some(tid.clone());
1431 }
1432 }
1433 }
1434 }
1435 if let Some(tid) = cache.name_to_type.get(member_name)
1437 && tid.starts_with("t_mapping")
1438 {
1439 return extract_mapping_value_type(tid);
1440 }
1441 None
1442 }
1443 AccessKind::Plain => {
1444 cache.name_to_type.get(member_name).cloned()
1446 }
1447 }
1448}
1449
1450pub struct ScopeContext {
1454 pub byte_pos: usize,
1456 pub file_id: FileId,
1458}
1459
1460fn resolve_name(
1464 cache: &CompletionCache,
1465 name: &str,
1466 scope_ctx: Option<&ScopeContext>,
1467) -> Option<String> {
1468 if let Some(ctx) = scope_ctx {
1469 resolve_name_in_scope(cache, name, ctx.byte_pos, ctx.file_id)
1470 } else {
1471 resolve_name_to_type_id(cache, name)
1472 }
1473}
1474
1475pub fn get_dot_completions(
1477 cache: &CompletionCache,
1478 identifier: &str,
1479 scope_ctx: Option<&ScopeContext>,
1480) -> Vec<CompletionItem> {
1481 if let Some(items) = magic_members(identifier) {
1483 return items;
1484 }
1485
1486 let type_id = resolve_name(cache, identifier, scope_ctx);
1488
1489 if let Some(tid) = type_id {
1490 return completions_for_type(cache, &tid);
1491 }
1492
1493 vec![]
1494}
1495
1496pub fn get_chain_completions(
1499 cache: &CompletionCache,
1500 chain: &[DotSegment],
1501 scope_ctx: Option<&ScopeContext>,
1502) -> Vec<CompletionItem> {
1503 if chain.is_empty() {
1504 return vec![];
1505 }
1506
1507 if chain.len() == 1 {
1509 let seg = &chain[0];
1510
1511 match seg.kind {
1513 AccessKind::Plain => {
1514 return get_dot_completions(cache, &seg.name, scope_ctx);
1515 }
1516 AccessKind::Call => {
1517 if seg.name == "type" {
1519 return type_meta_members(seg.call_args.as_deref(), Some(cache));
1520 }
1521 if let Some(type_id) = resolve_name(cache, &seg.name, scope_ctx) {
1524 return completions_for_type(cache, &type_id);
1525 }
1526 for ((_, fn_name), ret_type) in &cache.function_return_types {
1528 if fn_name == &seg.name {
1529 return completions_for_type(cache, ret_type);
1530 }
1531 }
1532 return vec![];
1533 }
1534 AccessKind::Index => {
1535 if let Some(tid) = resolve_name(cache, &seg.name, scope_ctx)
1537 && tid.starts_with("t_mapping")
1538 && let Some(val_type) = extract_mapping_value_type(&tid)
1539 {
1540 return completions_for_type(cache, &val_type);
1541 }
1542 return vec![];
1543 }
1544 }
1545 }
1546
1547 let first = &chain[0];
1550 let mut current_type = match first.kind {
1551 AccessKind::Plain => resolve_name(cache, &first.name, scope_ctx),
1552 AccessKind::Call => {
1553 resolve_name(cache, &first.name, scope_ctx).or_else(|| {
1555 cache
1556 .function_return_types
1557 .iter()
1558 .find(|((_, fn_name), _)| fn_name == &first.name)
1559 .map(|(_, ret_type)| ret_type.clone())
1560 })
1561 }
1562 AccessKind::Index => {
1563 resolve_name(cache, &first.name, scope_ctx).and_then(|tid| {
1565 if tid.starts_with("t_mapping") {
1566 extract_mapping_value_type(&tid)
1567 } else {
1568 Some(tid)
1569 }
1570 })
1571 }
1572 };
1573
1574 for seg in &chain[1..] {
1576 let ctx_type = match ¤t_type {
1577 Some(t) => t.clone(),
1578 None => return vec![],
1579 };
1580
1581 current_type = resolve_member_type(cache, &ctx_type, &seg.name, &seg.kind);
1582 }
1583
1584 match current_type {
1586 Some(tid) => completions_for_type(cache, &tid),
1587 None => vec![],
1588 }
1589}
1590
1591pub fn get_static_completions() -> Vec<CompletionItem> {
1594 let mut items = Vec::new();
1595
1596 for kw in SOLIDITY_KEYWORDS {
1598 items.push(CompletionItem {
1599 label: kw.to_string(),
1600 kind: Some(CompletionItemKind::KEYWORD),
1601 ..Default::default()
1602 });
1603 }
1604
1605 for (name, detail) in MAGIC_GLOBALS {
1607 items.push(CompletionItem {
1608 label: name.to_string(),
1609 kind: Some(CompletionItemKind::VARIABLE),
1610 detail: Some(detail.to_string()),
1611 ..Default::default()
1612 });
1613 }
1614
1615 for (name, detail) in GLOBAL_FUNCTIONS {
1617 items.push(CompletionItem {
1618 label: name.to_string(),
1619 kind: Some(CompletionItemKind::FUNCTION),
1620 detail: Some(detail.to_string()),
1621 ..Default::default()
1622 });
1623 }
1624
1625 for (name, detail) in ETHER_UNITS {
1627 items.push(CompletionItem {
1628 label: name.to_string(),
1629 kind: Some(CompletionItemKind::UNIT),
1630 detail: Some(detail.to_string()),
1631 ..Default::default()
1632 });
1633 }
1634
1635 for (name, detail) in TIME_UNITS {
1637 items.push(CompletionItem {
1638 label: name.to_string(),
1639 kind: Some(CompletionItemKind::UNIT),
1640 detail: Some(detail.to_string()),
1641 ..Default::default()
1642 });
1643 }
1644
1645 items
1646}
1647
1648pub fn get_general_completions(cache: &CompletionCache) -> Vec<CompletionItem> {
1650 let mut items = cache.names.clone();
1651 items.extend(get_static_completions());
1652 items
1653}
1654
1655pub fn handle_completion(
1665 cache: Option<&CompletionCache>,
1666 source_text: &str,
1667 position: Position,
1668 trigger_char: Option<&str>,
1669 file_id: Option<FileId>,
1670) -> Option<CompletionResponse> {
1671 let lines: Vec<&str> = source_text.lines().collect();
1672 let line = lines.get(position.line as usize)?;
1673
1674 let abs_byte = crate::utils::position_to_byte_offset(source_text, position);
1676 let line_start_byte: usize = source_text[..abs_byte]
1677 .rfind('\n')
1678 .map(|i| i + 1)
1679 .unwrap_or(0);
1680 let col_byte = (abs_byte - line_start_byte) as u32;
1681
1682 let scope_ctx = file_id.map(|fid| ScopeContext {
1684 byte_pos: abs_byte,
1685 file_id: fid,
1686 });
1687
1688 let items = if trigger_char == Some(".") {
1689 let chain = parse_dot_chain(line, col_byte);
1690 if chain.is_empty() {
1691 return None;
1692 }
1693 match cache {
1694 Some(c) => get_chain_completions(c, &chain, scope_ctx.as_ref()),
1695 None => {
1696 if chain.len() == 1 {
1698 let seg = &chain[0];
1699 if seg.name == "type" && seg.kind == AccessKind::Call {
1700 type_meta_members(seg.call_args.as_deref(), None)
1702 } else if seg.kind == AccessKind::Plain {
1703 magic_members(&seg.name).unwrap_or_default()
1704 } else {
1705 vec![]
1706 }
1707 } else {
1708 vec![]
1709 }
1710 }
1711 }
1712 } else {
1713 match cache {
1714 Some(c) => c.general_completions.clone(),
1715 None => get_static_completions(),
1716 }
1717 };
1718
1719 Some(CompletionResponse::List(CompletionList {
1720 is_incomplete: cache.is_none(),
1721 items,
1722 }))
1723}
1724
1725const SOLIDITY_KEYWORDS: &[&str] = &[
1726 "abstract",
1727 "address",
1728 "assembly",
1729 "bool",
1730 "break",
1731 "bytes",
1732 "bytes1",
1733 "bytes4",
1734 "bytes32",
1735 "calldata",
1736 "constant",
1737 "constructor",
1738 "continue",
1739 "contract",
1740 "delete",
1741 "do",
1742 "else",
1743 "emit",
1744 "enum",
1745 "error",
1746 "event",
1747 "external",
1748 "fallback",
1749 "false",
1750 "for",
1751 "function",
1752 "if",
1753 "immutable",
1754 "import",
1755 "indexed",
1756 "int8",
1757 "int24",
1758 "int128",
1759 "int256",
1760 "interface",
1761 "internal",
1762 "library",
1763 "mapping",
1764 "memory",
1765 "modifier",
1766 "new",
1767 "override",
1768 "payable",
1769 "pragma",
1770 "private",
1771 "public",
1772 "pure",
1773 "receive",
1774 "return",
1775 "returns",
1776 "revert",
1777 "storage",
1778 "string",
1779 "struct",
1780 "true",
1781 "type",
1782 "uint8",
1783 "uint24",
1784 "uint128",
1785 "uint160",
1786 "uint256",
1787 "unchecked",
1788 "using",
1789 "view",
1790 "virtual",
1791 "while",
1792];
1793
1794const ETHER_UNITS: &[(&str, &str)] = &[("wei", "1"), ("gwei", "1e9"), ("ether", "1e18")];
1796
1797const TIME_UNITS: &[(&str, &str)] = &[
1799 ("seconds", "1"),
1800 ("minutes", "60 seconds"),
1801 ("hours", "3600 seconds"),
1802 ("days", "86400 seconds"),
1803 ("weeks", "604800 seconds"),
1804];
1805
1806const MAGIC_GLOBALS: &[(&str, &str)] = &[
1807 ("msg", "msg"),
1808 ("block", "block"),
1809 ("tx", "tx"),
1810 ("abi", "abi"),
1811 ("this", "address"),
1812 ("super", "contract"),
1813 ("type", "type information"),
1814];
1815
1816const GLOBAL_FUNCTIONS: &[(&str, &str)] = &[
1817 ("addmod(uint256, uint256, uint256)", "uint256"),
1819 ("mulmod(uint256, uint256, uint256)", "uint256"),
1820 ("keccak256(bytes memory)", "bytes32"),
1821 ("sha256(bytes memory)", "bytes32"),
1822 ("ripemd160(bytes memory)", "bytes20"),
1823 (
1824 "ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s)",
1825 "address",
1826 ),
1827 ("blockhash(uint256 blockNumber)", "bytes32"),
1829 ("blobhash(uint256 index)", "bytes32"),
1830 ("gasleft()", "uint256"),
1831 ("assert(bool condition)", ""),
1833 ("require(bool condition)", ""),
1834 ("require(bool condition, string memory message)", ""),
1835 ("revert()", ""),
1836 ("revert(string memory reason)", ""),
1837 ("selfdestruct(address payable recipient)", ""),
1839];