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
9pub struct CompletionCache {
11 pub names: Vec<CompletionItem>,
13
14 pub name_to_type: HashMap<String, String>,
16
17 pub node_members: HashMap<u64, Vec<CompletionItem>>,
19
20 pub type_to_node: HashMap<String, u64>,
22
23 pub name_to_node_id: HashMap<String, u64>,
25
26 pub method_identifiers: HashMap<u64, Vec<CompletionItem>>,
29
30 pub function_return_types: HashMap<(u64, String), String>,
33
34 pub using_for: HashMap<String, Vec<CompletionItem>>,
37
38 pub using_for_wildcard: Vec<CompletionItem>,
40
41 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
56fn 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
73pub fn extract_node_id_from_type(type_id: &str) -> Option<u64> {
78 let mut last_id = None;
81 let mut i = 0;
82 let bytes = type_id.as_bytes();
83 while i < bytes.len() {
84 if i + 1 < bytes.len() && bytes[i] == b'_' && bytes[i + 1] == b'$' {
85 i += 2;
86 let start = i;
87 while i < bytes.len() && bytes[i].is_ascii_digit() {
88 i += 1;
89 }
90 if i > start
91 && let Ok(id) = type_id[start..i].parse::<u64>() {
92 last_id = Some(id);
93 }
94 } else {
95 i += 1;
96 }
97 }
98 last_id
99}
100
101fn build_function_signature(node: &Value) -> Option<String> {
104 let name = node.get("name").and_then(|v| v.as_str()).unwrap_or("");
105 if name.is_empty() {
106 return None;
107 }
108
109 let params = node
110 .get("parameters")
111 .and_then(|p| p.get("parameters"))
112 .and_then(|v| v.as_array());
113
114 let mut sig = String::new();
115 sig.push_str(name);
116 sig.push('(');
117
118 if let Some(params) = params {
119 for (i, param) in params.iter().enumerate() {
120 if i > 0 {
121 sig.push_str(", ");
122 }
123 let type_str = param
124 .get("typeDescriptions")
125 .and_then(|td| td.get("typeString"))
126 .and_then(|v| v.as_str())
127 .unwrap_or("?");
128 let clean_type = type_str
130 .strip_prefix("struct ")
131 .or_else(|| type_str.strip_prefix("contract "))
132 .or_else(|| type_str.strip_prefix("enum "))
133 .unwrap_or(type_str);
134 let param_name = param.get("name").and_then(|v| v.as_str()).unwrap_or("");
135 sig.push_str(clean_type);
136 if !param_name.is_empty() {
137 sig.push(' ');
138 sig.push_str(param_name);
139 }
140 }
141 }
142 sig.push(')');
143
144 let returns = node
146 .get("returnParameters")
147 .and_then(|p| p.get("parameters"))
148 .and_then(|v| v.as_array());
149
150 if let Some(returns) = returns
151 && !returns.is_empty() {
152 sig.push_str(" returns (");
153 for (i, ret) in returns.iter().enumerate() {
154 if i > 0 {
155 sig.push_str(", ");
156 }
157 let type_str = ret
158 .get("typeDescriptions")
159 .and_then(|td| td.get("typeString"))
160 .and_then(|v| v.as_str())
161 .unwrap_or("?");
162 let clean_type = type_str
163 .strip_prefix("struct ")
164 .or_else(|| type_str.strip_prefix("contract "))
165 .or_else(|| type_str.strip_prefix("enum "))
166 .unwrap_or(type_str);
167 let ret_name = ret.get("name").and_then(|v| v.as_str()).unwrap_or("");
168 sig.push_str(clean_type);
169 if !ret_name.is_empty() {
170 sig.push(' ');
171 sig.push_str(ret_name);
172 }
173 }
174 sig.push(')');
175 }
176
177 Some(sig)
178}
179
180pub fn extract_mapping_value_type(type_id: &str) -> Option<String> {
187 let mut current = type_id;
188
189 loop {
190 if !current.starts_with("t_mapping$_") {
191 let result = current.trim_end_matches("_$");
194 return if result.is_empty() {
195 None
196 } else {
197 Some(result.to_string())
198 };
199 }
200
201 let inner = ¤t["t_mapping$_".len()..];
203
204 let mut depth = 0i32;
208 let bytes = inner.as_bytes();
209 let mut split_pos = None;
210
211 let mut i = 0;
212 while i < bytes.len() {
213 if i + 1 < bytes.len() && bytes[i] == b'$' && bytes[i + 1] == b'_' {
214 depth += 1;
215 i += 2;
216 } else if i + 2 < bytes.len()
217 && bytes[i] == b'_'
218 && bytes[i + 1] == b'$'
219 && bytes[i + 2] == b'_'
220 && depth == 0
221 {
222 split_pos = Some(i);
224 break;
225 } else if i + 1 < bytes.len() && bytes[i] == b'_' && bytes[i + 1] == b'$' {
226 depth -= 1;
227 i += 2;
228 } else {
229 i += 1;
230 }
231 }
232
233 if let Some(pos) = split_pos {
234 current = &inner[pos + 3..];
236 } else {
237 return None;
238 }
239 }
240}
241
242fn count_abi_params(signature: &str) -> usize {
245 let start = match signature.find('(') {
247 Some(i) => i + 1,
248 None => return 0,
249 };
250 let bytes = signature.as_bytes();
251 if start >= bytes.len() {
252 return 0;
253 }
254 if bytes[start] == b')' {
256 return 0;
257 }
258 let mut count = 1; let mut depth = 0;
260 for &b in &bytes[start..] {
261 match b {
262 b'(' => depth += 1,
263 b')' => {
264 if depth == 0 {
265 break;
266 }
267 depth -= 1;
268 }
269 b',' if depth == 0 => count += 1,
270 _ => {}
271 }
272 }
273 count
274}
275
276fn count_signature_params(sig: &str) -> usize {
278 count_abi_params(sig)
279}
280
281pub fn build_completion_cache(sources: &Value, contracts: Option<&Value>) -> CompletionCache {
284 let mut names: Vec<CompletionItem> = Vec::new();
285 let mut seen_names: HashMap<String, usize> = HashMap::new(); let mut name_to_type: HashMap<String, String> = HashMap::new();
287 let mut node_members: HashMap<u64, Vec<CompletionItem>> = HashMap::new();
288 let mut type_to_node: HashMap<String, u64> = HashMap::new();
289 let mut method_identifiers: HashMap<u64, Vec<CompletionItem>> = HashMap::new();
290 let mut name_to_node_id: HashMap<String, u64> = HashMap::new();
291
292 let mut contract_locations: Vec<(String, String, u64)> = Vec::new();
294
295 let mut function_signatures: HashMap<u64, HashMap<String, Vec<String>>> = HashMap::new();
297
298 let mut function_return_types: HashMap<(u64, String), String> = HashMap::new();
300
301 let mut using_for: HashMap<String, Vec<CompletionItem>> = HashMap::new();
303 let mut using_for_wildcard: Vec<CompletionItem> = Vec::new();
304
305 let mut using_for_directives: Vec<(u64, Option<String>)> = Vec::new();
307
308 if let Some(sources_obj) = sources.as_object() {
309 for (path, contents) in sources_obj {
310 if let Some(contents_array) = contents.as_array()
311 && let Some(first_content) = contents_array.first()
312 && let Some(source_file) = first_content.get("source_file")
313 && let Some(ast) = source_file.get("ast")
314 {
315 let mut stack: Vec<&Value> = vec![ast];
316
317 while let Some(tree) = stack.pop() {
318 let node_type = tree
319 .get("nodeType")
320 .and_then(|v| v.as_str())
321 .unwrap_or("");
322 let name = tree.get("name").and_then(|v| v.as_str()).unwrap_or("");
323 let node_id = tree.get("id").and_then(|v| v.as_u64());
324
325 if !name.is_empty() && !seen_names.contains_key(name) {
327 let type_string = tree
328 .get("typeDescriptions")
329 .and_then(|td| td.get("typeString"))
330 .and_then(|v| v.as_str())
331 .map(|s| s.to_string());
332
333 let type_id = tree
334 .get("typeDescriptions")
335 .and_then(|td| td.get("typeIdentifier"))
336 .and_then(|v| v.as_str());
337
338 let kind = node_type_to_completion_kind(node_type);
339
340 let item = CompletionItem {
341 label: name.to_string(),
342 kind: Some(kind),
343 detail: type_string,
344 ..Default::default()
345 };
346
347 let idx = names.len();
348 names.push(item);
349 seen_names.insert(name.to_string(), idx);
350
351 if let Some(tid) = type_id {
353 name_to_type.insert(name.to_string(), tid.to_string());
354 }
355 }
356
357 if node_type == "StructDefinition"
359 && let Some(id) = node_id {
360 let mut members = Vec::new();
361 if let Some(member_array) =
362 tree.get("members").and_then(|v| v.as_array())
363 {
364 for member in member_array {
365 let member_name =
366 member.get("name").and_then(|v| v.as_str()).unwrap_or("");
367 if member_name.is_empty() {
368 continue;
369 }
370 let member_type = member
371 .get("typeDescriptions")
372 .and_then(|td| td.get("typeString"))
373 .and_then(|v| v.as_str())
374 .map(|s| s.to_string());
375
376 members.push(CompletionItem {
377 label: member_name.to_string(),
378 kind: Some(CompletionItemKind::FIELD),
379 detail: member_type,
380 ..Default::default()
381 });
382 }
383 }
384 if !members.is_empty() {
385 node_members.insert(id, members);
386 }
387
388 if let Some(tid) = tree
390 .get("typeDescriptions")
391 .and_then(|td| td.get("typeIdentifier"))
392 .and_then(|v| v.as_str())
393 {
394 type_to_node.insert(tid.to_string(), id);
395 }
396 }
397
398 if node_type == "ContractDefinition"
400 && let Some(id) = node_id {
401 let mut members = Vec::new();
402 let mut fn_sigs: HashMap<String, Vec<String>> = HashMap::new();
403 if let Some(nodes_array) =
404 tree.get("nodes").and_then(|v| v.as_array())
405 {
406 for member in nodes_array {
407 let member_type = member
408 .get("nodeType")
409 .and_then(|v| v.as_str())
410 .unwrap_or("");
411 let member_name =
412 member.get("name").and_then(|v| v.as_str()).unwrap_or("");
413 if member_name.is_empty() {
414 continue;
415 }
416
417 let (member_detail, label_details) =
419 if member_type == "FunctionDefinition" {
420 if let Some(ret_params) = member
424 .get("returnParameters")
425 .and_then(|rp| rp.get("parameters"))
426 .and_then(|v| v.as_array())
427 && ret_params.len() == 1
428 && let Some(ret_tid) = ret_params[0]
429 .get("typeDescriptions")
430 .and_then(|td| td.get("typeIdentifier"))
431 .and_then(|v| v.as_str())
432 {
433 function_return_types.insert(
434 (id, member_name.to_string()),
435 ret_tid.to_string(),
436 );
437 }
438
439 if let Some(sig) = build_function_signature(member) {
440 fn_sigs
441 .entry(member_name.to_string())
442 .or_default()
443 .push(sig.clone());
444 (
445 Some(sig),
446 None,
447 )
448 } else {
449 (
450 member
451 .get("typeDescriptions")
452 .and_then(|td| td.get("typeString"))
453 .and_then(|v| v.as_str())
454 .map(|s| s.to_string()),
455 None,
456 )
457 }
458 } else {
459 (
460 member
461 .get("typeDescriptions")
462 .and_then(|td| td.get("typeString"))
463 .and_then(|v| v.as_str())
464 .map(|s| s.to_string()),
465 None,
466 )
467 };
468
469 let kind = node_type_to_completion_kind(member_type);
470 members.push(CompletionItem {
471 label: member_name.to_string(),
472 kind: Some(kind),
473 detail: member_detail,
474 label_details,
475 ..Default::default()
476 });
477 }
478 }
479 if !members.is_empty() {
480 node_members.insert(id, members);
481 }
482 if !fn_sigs.is_empty() {
483 function_signatures.insert(id, fn_sigs);
484 }
485
486 if let Some(tid) = tree
487 .get("typeDescriptions")
488 .and_then(|td| td.get("typeIdentifier"))
489 .and_then(|v| v.as_str())
490 {
491 type_to_node.insert(tid.to_string(), id);
492 }
493
494 if !name.is_empty() {
496 contract_locations.push((
497 path.clone(),
498 name.to_string(),
499 id,
500 ));
501 name_to_node_id.insert(name.to_string(), id);
502 }
503 }
504
505 if node_type == "EnumDefinition"
507 && let Some(id) = node_id {
508 let mut members = Vec::new();
509 if let Some(member_array) =
510 tree.get("members").and_then(|v| v.as_array())
511 {
512 for member in member_array {
513 let member_name =
514 member.get("name").and_then(|v| v.as_str()).unwrap_or("");
515 if member_name.is_empty() {
516 continue;
517 }
518 members.push(CompletionItem {
519 label: member_name.to_string(),
520 kind: Some(CompletionItemKind::ENUM_MEMBER),
521 detail: None,
522 ..Default::default()
523 });
524 }
525 }
526 if !members.is_empty() {
527 node_members.insert(id, members);
528 }
529
530 if let Some(tid) = tree
531 .get("typeDescriptions")
532 .and_then(|td| td.get("typeIdentifier"))
533 .and_then(|v| v.as_str())
534 {
535 type_to_node.insert(tid.to_string(), id);
536 }
537 }
538
539 if node_type == "UsingForDirective" {
541 let target_type = tree
543 .get("typeName")
544 .and_then(|tn| {
545 tn.get("typeDescriptions")
546 .and_then(|td| td.get("typeIdentifier"))
547 .and_then(|v| v.as_str())
548 .map(|s| s.to_string())
549 });
550
551 if let Some(lib) = tree.get("libraryName") {
553 if let Some(lib_id) = lib
554 .get("referencedDeclaration")
555 .and_then(|v| v.as_u64())
556 {
557 using_for_directives.push((lib_id, target_type));
558 }
559 }
560 else if let Some(func_list) =
564 tree.get("functionList").and_then(|v| v.as_array())
565 {
566 for entry in func_list {
567 if entry.get("operator").is_some() {
569 continue;
570 }
571 if let Some(def) = entry.get("definition") {
572 let fn_name = def
573 .get("name")
574 .and_then(|v| v.as_str())
575 .unwrap_or("");
576 if !fn_name.is_empty() {
577 let items = if let Some(ref tid) = target_type {
578 using_for.entry(tid.clone()).or_default()
579 } else {
580 &mut using_for_wildcard
581 };
582 items.push(CompletionItem {
583 label: fn_name.to_string(),
584 kind: Some(CompletionItemKind::FUNCTION),
585 detail: None,
586 ..Default::default()
587 });
588 }
589 }
590 }
591 }
592 }
593
594 for key in CHILD_KEYS {
596 push_if_node_or_array(tree, key, &mut stack);
597 }
598 }
599 }
600 }
601 }
602
603 for (lib_id, target_type) in &using_for_directives {
606 if let Some(lib_members) = node_members.get(lib_id) {
607 let items: Vec<CompletionItem> = lib_members
608 .iter()
609 .filter(|item| item.kind == Some(CompletionItemKind::FUNCTION))
610 .cloned()
611 .collect();
612 if !items.is_empty() {
613 if let Some(tid) = target_type {
614 using_for.entry(tid.clone()).or_default().extend(items);
615 } else {
616 using_for_wildcard.extend(items);
617 }
618 }
619 }
620 }
621
622 if let Some(contracts_val) = contracts
624 && let Some(contracts_obj) = contracts_val.as_object() {
625 for (path, contract_name, node_id) in &contract_locations {
626 let fn_sigs = function_signatures.get(node_id);
628
629 if let Some(path_entry) = contracts_obj.get(path)
630 && let Some(contract_entry) = path_entry.get(contract_name)
631 && let Some(first) = contract_entry.get(0)
632 && let Some(evm) = first
633 .get("contract")
634 .and_then(|c| c.get("evm"))
635 && let Some(methods) = evm.get("methodIdentifiers")
636 && let Some(methods_obj) = methods.as_object()
637 {
638 let mut items: Vec<CompletionItem> = Vec::new();
639 for (signature, selector) in methods_obj {
640 let fn_name = signature
643 .split('(')
644 .next()
645 .unwrap_or(signature)
646 .to_string();
647 let selector_str = selector
648 .as_str()
649 .map(|s| format!("0x{}", s))
650 .unwrap_or_default();
651
652 let description = fn_sigs
654 .and_then(|sigs| sigs.get(&fn_name))
655 .and_then(|sig_list| {
656 if sig_list.len() == 1 {
657 Some(sig_list[0].clone())
659 } else {
660 let abi_param_count =
662 count_abi_params(signature);
663 sig_list.iter().find(|s| {
664 count_signature_params(s) == abi_param_count
665 }).cloned()
666 }
667 });
668
669 items.push(CompletionItem {
670 label: fn_name,
671 kind: Some(CompletionItemKind::FUNCTION),
672 detail: Some(signature.clone()),
673 label_details: Some(tower_lsp::lsp_types::CompletionItemLabelDetails {
674 detail: Some(selector_str),
675 description,
676 }),
677 ..Default::default()
678 });
679 }
680 if !items.is_empty() {
681 method_identifiers.insert(*node_id, items);
682 }
683 }
684 }
685 }
686
687 let mut general_completions = names.clone();
689 general_completions.extend(get_static_completions());
690
691 CompletionCache {
692 names,
693 name_to_type,
694 node_members,
695 type_to_node,
696 name_to_node_id,
697 method_identifiers,
698 function_return_types,
699 using_for,
700 using_for_wildcard,
701 general_completions,
702 }
703}
704
705fn magic_members(name: &str) -> Option<Vec<CompletionItem>> {
707 let items = match name {
708 "msg" => vec![
709 ("data", "bytes calldata"),
710 ("sender", "address"),
711 ("sig", "bytes4"),
712 ("value", "uint256"),
713 ],
714 "block" => vec![
715 ("basefee", "uint256"),
716 ("blobbasefee", "uint256"),
717 ("chainid", "uint256"),
718 ("coinbase", "address payable"),
719 ("difficulty", "uint256"),
720 ("gaslimit", "uint256"),
721 ("number", "uint256"),
722 ("prevrandao", "uint256"),
723 ("timestamp", "uint256"),
724 ],
725 "tx" => vec![("gasprice", "uint256"), ("origin", "address")],
726 "abi" => vec![
727 ("decode(bytes memory, (...))", "..."),
728 ("encode(...)", "bytes memory"),
729 ("encodePacked(...)", "bytes memory"),
730 ("encodeWithSelector(bytes4, ...)", "bytes memory"),
731 ("encodeWithSignature(string memory, ...)", "bytes memory"),
732 ("encodeCall(function, (...))", "bytes memory"),
733 ],
734 "type" => vec![
737 ("name", "string"),
738 ("creationCode", "bytes memory"),
739 ("runtimeCode", "bytes memory"),
740 ("interfaceId", "bytes4"),
741 ("min", "T"),
742 ("max", "T"),
743 ],
744 "bytes" => vec![("concat(...)", "bytes memory")],
746 "string" => vec![("concat(...)", "string memory")],
747 _ => return None,
748 };
749
750 Some(
751 items
752 .into_iter()
753 .map(|(label, detail)| CompletionItem {
754 label: label.to_string(),
755 kind: Some(CompletionItemKind::PROPERTY),
756 detail: Some(detail.to_string()),
757 ..Default::default()
758 })
759 .collect(),
760 )
761}
762
763fn address_members() -> Vec<CompletionItem> {
765 [
766 ("balance", "uint256", CompletionItemKind::PROPERTY),
767 ("code", "bytes memory", CompletionItemKind::PROPERTY),
768 ("codehash", "bytes32", CompletionItemKind::PROPERTY),
769 ("transfer(uint256)", "", CompletionItemKind::FUNCTION),
770 ("send(uint256)", "bool", CompletionItemKind::FUNCTION),
771 ("call(bytes memory)", "(bool, bytes memory)", CompletionItemKind::FUNCTION),
772 ("delegatecall(bytes memory)", "(bool, bytes memory)", CompletionItemKind::FUNCTION),
773 ("staticcall(bytes memory)", "(bool, bytes memory)", CompletionItemKind::FUNCTION),
774 ]
775 .iter()
776 .map(|(label, detail, kind)| CompletionItem {
777 label: label.to_string(),
778 kind: Some(*kind),
779 detail: if detail.is_empty() {
780 None
781 } else {
782 Some(detail.to_string())
783 },
784 ..Default::default()
785 })
786 .collect()
787}
788
789#[derive(Debug, Clone, PartialEq)]
791pub enum AccessKind {
792 Plain,
794 Call,
796 Index,
798}
799
800#[derive(Debug, Clone, PartialEq)]
802pub struct DotSegment {
803 pub name: String,
804 pub kind: AccessKind,
805}
806
807fn skip_brackets_backwards(bytes: &[u8], pos: usize) -> usize {
811 let close = bytes[pos];
812 let open = match close {
813 b')' => b'(',
814 b']' => b'[',
815 _ => return pos,
816 };
817 let mut depth = 1u32;
818 let mut i = pos;
819 while i > 0 && depth > 0 {
820 i -= 1;
821 if bytes[i] == close {
822 depth += 1;
823 } else if bytes[i] == open {
824 depth -= 1;
825 }
826 }
827 i
828}
829
830pub fn parse_dot_chain(line: &str, character: u32) -> Vec<DotSegment> {
835 let col = character as usize;
836 if col == 0 {
837 return vec![];
838 }
839
840 let bytes = line.as_bytes();
841 let mut segments: Vec<DotSegment> = Vec::new();
842
843 let mut pos = col;
845 if pos > 0 && pos <= bytes.len() && bytes[pos - 1] == b'.' {
846 pos -= 1;
847 }
848
849 loop {
850 if pos == 0 {
851 break;
852 }
853
854 let kind = if bytes[pos - 1] == b')' {
856 pos -= 1; pos = skip_brackets_backwards(bytes, pos);
858 AccessKind::Call
859 } else if bytes[pos - 1] == b']' {
860 pos -= 1; pos = skip_brackets_backwards(bytes, pos);
862 AccessKind::Index
863 } else {
864 AccessKind::Plain
865 };
866
867 let end = pos;
869 while pos > 0 && (bytes[pos - 1].is_ascii_alphanumeric() || bytes[pos - 1] == b'_') {
870 pos -= 1;
871 }
872
873 if pos == end {
874 break;
876 }
877
878 let name = String::from_utf8_lossy(&bytes[pos..end]).to_string();
879 segments.push(DotSegment { name, kind });
880
881 if pos > 0 && bytes[pos - 1] == b'.' {
883 pos -= 1; } else {
885 break;
886 }
887 }
888
889 segments.reverse(); segments
891}
892
893pub fn extract_identifier_before_dot(line: &str, character: u32) -> Option<String> {
896 let segments = parse_dot_chain(line, character);
897 segments.last().map(|s| s.name.clone())
898}
899
900#[doc = r"Strip all storage/memory location suffixes from a typeIdentifier to get the base type.
901Solidity AST uses different suffixes in different contexts:
902 - `t_struct$_State_$4809_storage_ptr` (UsingForDirective typeName)
903 - `t_struct$_State_$4809_storage` (mapping value type after extraction)
904 - `t_struct$_PoolKey_$8887_memory_ptr` (function parameter)
905All refer to the same logical type. This strips `_ptr` and `_storage`/`_memory`/`_calldata`."]
906fn strip_type_suffix(type_id: &str) -> &str {
907 let s = type_id.strip_suffix("_ptr").unwrap_or(type_id);
908 s.strip_suffix("_storage")
909 .or_else(|| s.strip_suffix("_memory"))
910 .or_else(|| s.strip_suffix("_calldata"))
911 .unwrap_or(s)
912}
913
914fn lookup_using_for(cache: &CompletionCache, type_id: &str) -> Vec<CompletionItem> {
918 if let Some(items) = cache.using_for.get(type_id) {
920 return items.clone();
921 }
922
923 let base = strip_type_suffix(type_id);
925 let variants = [
926 base.to_string(),
927 format!("{}_storage", base),
928 format!("{}_storage_ptr", base),
929 format!("{}_memory", base),
930 format!("{}_memory_ptr", base),
931 format!("{}_calldata", base),
932 ];
933 for variant in &variants {
934 if variant.as_str() != type_id
935 && let Some(items) = cache.using_for.get(variant.as_str()) {
936 return items.clone();
937 }
938 }
939
940 vec![]
941}
942
943fn completions_for_type(cache: &CompletionCache, type_id: &str) -> Vec<CompletionItem> {
946 if type_id == "t_address" || type_id == "t_address_payable" {
948 let mut items = address_members();
949 if let Some(uf) = cache.using_for.get(type_id) {
951 items.extend(uf.iter().cloned());
952 }
953 items.extend(cache.using_for_wildcard.iter().cloned());
954 return items;
955 }
956
957 let resolved_node_id = extract_node_id_from_type(type_id)
958 .or_else(|| cache.type_to_node.get(type_id).copied())
959 .or_else(|| {
960 type_id
962 .strip_prefix("__node_id_")
963 .and_then(|s| s.parse::<u64>().ok())
964 });
965
966 let mut items = Vec::new();
967 let mut seen_labels: std::collections::HashSet<String> = std::collections::HashSet::new();
968
969 if let Some(node_id) = resolved_node_id {
970 if let Some(method_items) = cache.method_identifiers.get(&node_id) {
972 for item in method_items {
973 seen_labels.insert(item.label.clone());
974 items.push(item.clone());
975 }
976 }
977
978 if let Some(members) = cache.node_members.get(&node_id) {
980 for item in members {
981 if !seen_labels.contains(&item.label) {
982 seen_labels.insert(item.label.clone());
983 items.push(item.clone());
984 }
985 }
986 }
987 }
988
989 let uf_items = lookup_using_for(cache, type_id);
992 for item in &uf_items {
993 if !seen_labels.contains(&item.label) {
994 seen_labels.insert(item.label.clone());
995 items.push(item.clone());
996 }
997 }
998
999 for item in &cache.using_for_wildcard {
1001 if !seen_labels.contains(&item.label) {
1002 seen_labels.insert(item.label.clone());
1003 items.push(item.clone());
1004 }
1005 }
1006
1007 items
1008}
1009
1010fn resolve_name_to_type_id(cache: &CompletionCache, name: &str) -> Option<String> {
1012 if let Some(tid) = cache.name_to_type.get(name) {
1014 return Some(tid.clone());
1015 }
1016 if let Some(node_id) = cache.name_to_node_id.get(name) {
1018 for (tid, nid) in &cache.type_to_node {
1020 if nid == node_id {
1021 return Some(tid.clone());
1022 }
1023 }
1024 return Some(format!("__node_id_{}", node_id));
1026 }
1027 None
1028}
1029
1030fn resolve_member_type(
1035 cache: &CompletionCache,
1036 context_type_id: &str,
1037 member_name: &str,
1038 kind: &AccessKind,
1039) -> Option<String> {
1040 let resolved_node_id = extract_node_id_from_type(context_type_id)
1041 .or_else(|| cache.type_to_node.get(context_type_id).copied())
1042 .or_else(|| {
1043 context_type_id
1045 .strip_prefix("__node_id_")
1046 .and_then(|s| s.parse::<u64>().ok())
1047 });
1048
1049 let node_id = resolved_node_id?;
1050
1051 match kind {
1052 AccessKind::Call => {
1053 cache
1055 .function_return_types
1056 .get(&(node_id, member_name.to_string()))
1057 .cloned()
1058 }
1059 AccessKind::Index => {
1060 if let Some(members) = cache.node_members.get(&node_id) {
1062 for member in members {
1063 if member.label == member_name {
1064 if let Some(tid) = cache.name_to_type.get(member_name) {
1066 if tid.starts_with("t_mapping") {
1067 return extract_mapping_value_type(tid);
1068 }
1069 return Some(tid.clone());
1070 }
1071 }
1072 }
1073 }
1074 if let Some(tid) = cache.name_to_type.get(member_name)
1076 && tid.starts_with("t_mapping") {
1077 return extract_mapping_value_type(tid);
1078 }
1079 None
1080 }
1081 AccessKind::Plain => {
1082 cache.name_to_type.get(member_name).cloned()
1084 }
1085 }
1086}
1087
1088pub fn get_dot_completions(cache: &CompletionCache, identifier: &str) -> Vec<CompletionItem> {
1090 if let Some(items) = magic_members(identifier) {
1092 return items;
1093 }
1094
1095 let type_id = resolve_name_to_type_id(cache, identifier);
1097
1098 if let Some(tid) = type_id {
1099 return completions_for_type(cache, &tid);
1100 }
1101
1102 vec![]
1103}
1104
1105pub fn get_chain_completions(cache: &CompletionCache, chain: &[DotSegment]) -> Vec<CompletionItem> {
1108 if chain.is_empty() {
1109 return vec![];
1110 }
1111
1112 if chain.len() == 1 {
1114 let seg = &chain[0];
1115
1116 match seg.kind {
1118 AccessKind::Plain => {
1119 return get_dot_completions(cache, &seg.name);
1120 }
1121 AccessKind::Call => {
1122 if let Some(type_id) = resolve_name_to_type_id(cache, &seg.name) {
1125 return completions_for_type(cache, &type_id);
1126 }
1127 for ((_, fn_name), ret_type) in &cache.function_return_types {
1129 if fn_name == &seg.name {
1130 return completions_for_type(cache, ret_type);
1131 }
1132 }
1133 return vec![];
1134 }
1135 AccessKind::Index => {
1136 if let Some(tid) = cache.name_to_type.get(&seg.name)
1138 && tid.starts_with("t_mapping")
1139 && let Some(val_type) = extract_mapping_value_type(tid) {
1140 return completions_for_type(cache, &val_type);
1141 }
1142 return vec![];
1143 }
1144 }
1145 }
1146
1147 let first = &chain[0];
1150 let mut current_type = match first.kind {
1151 AccessKind::Plain => resolve_name_to_type_id(cache, &first.name),
1152 AccessKind::Call => {
1153 resolve_name_to_type_id(cache, &first.name).or_else(|| {
1155 cache
1156 .function_return_types
1157 .iter()
1158 .find(|((_, fn_name), _)| fn_name == &first.name)
1159 .map(|(_, ret_type)| ret_type.clone())
1160 })
1161 }
1162 AccessKind::Index => {
1163 cache.name_to_type.get(&first.name).and_then(|tid| {
1165 if tid.starts_with("t_mapping") {
1166 extract_mapping_value_type(tid)
1167 } else {
1168 Some(tid.clone())
1169 }
1170 })
1171 }
1172 };
1173
1174 for seg in &chain[1..] {
1176 let ctx_type = match ¤t_type {
1177 Some(t) => t.clone(),
1178 None => return vec![],
1179 };
1180
1181 current_type = resolve_member_type(cache, &ctx_type, &seg.name, &seg.kind);
1182 }
1183
1184 match current_type {
1186 Some(tid) => completions_for_type(cache, &tid),
1187 None => vec![],
1188 }
1189}
1190
1191pub fn get_static_completions() -> Vec<CompletionItem> {
1194 let mut items = Vec::new();
1195
1196 for kw in SOLIDITY_KEYWORDS {
1198 items.push(CompletionItem {
1199 label: kw.to_string(),
1200 kind: Some(CompletionItemKind::KEYWORD),
1201 ..Default::default()
1202 });
1203 }
1204
1205 for (name, detail) in MAGIC_GLOBALS {
1207 items.push(CompletionItem {
1208 label: name.to_string(),
1209 kind: Some(CompletionItemKind::VARIABLE),
1210 detail: Some(detail.to_string()),
1211 ..Default::default()
1212 });
1213 }
1214
1215 for (name, detail) in GLOBAL_FUNCTIONS {
1217 items.push(CompletionItem {
1218 label: name.to_string(),
1219 kind: Some(CompletionItemKind::FUNCTION),
1220 detail: Some(detail.to_string()),
1221 ..Default::default()
1222 });
1223 }
1224
1225 for (name, detail) in ETHER_UNITS {
1227 items.push(CompletionItem {
1228 label: name.to_string(),
1229 kind: Some(CompletionItemKind::UNIT),
1230 detail: Some(detail.to_string()),
1231 ..Default::default()
1232 });
1233 }
1234
1235 for (name, detail) in TIME_UNITS {
1237 items.push(CompletionItem {
1238 label: name.to_string(),
1239 kind: Some(CompletionItemKind::UNIT),
1240 detail: Some(detail.to_string()),
1241 ..Default::default()
1242 });
1243 }
1244
1245 items
1246}
1247
1248pub fn get_general_completions(cache: &CompletionCache) -> Vec<CompletionItem> {
1250 let mut items = cache.names.clone();
1251 items.extend(get_static_completions());
1252 items
1253}
1254
1255pub fn handle_completion(
1266 cache: Option<&CompletionCache>,
1267 source_text: &str,
1268 position: Position,
1269 trigger_char: Option<&str>,
1270 fast: bool,
1271) -> Option<CompletionResponse> {
1272 let lines: Vec<&str> = source_text.lines().collect();
1273 let line = lines.get(position.line as usize)?;
1274
1275 let items = if trigger_char == Some(".") {
1276 let chain = parse_dot_chain(line, position.character);
1277 if chain.is_empty() {
1278 return None;
1279 }
1280 match cache {
1281 Some(c) => get_chain_completions(c, &chain),
1282 None => {
1283 if chain.len() == 1 && chain[0].kind == AccessKind::Plain {
1285 magic_members(&chain[0].name).unwrap_or_default()
1286 } else {
1287 vec![]
1288 }
1289 }
1290 }
1291 } else {
1292 match cache {
1293 Some(c) if fast => c.general_completions.clone(),
1294 Some(c) => get_general_completions(c),
1295 None => get_static_completions(),
1296 }
1297 };
1298
1299 Some(CompletionResponse::List(CompletionList {
1300 is_incomplete: cache.is_none(),
1301 items,
1302 }))
1303}
1304
1305const SOLIDITY_KEYWORDS: &[&str] = &[
1306 "abstract",
1307 "address",
1308 "assembly",
1309 "bool",
1310 "break",
1311 "bytes",
1312 "bytes1",
1313 "bytes4",
1314 "bytes32",
1315 "calldata",
1316 "constant",
1317 "constructor",
1318 "continue",
1319 "contract",
1320 "delete",
1321 "do",
1322 "else",
1323 "emit",
1324 "enum",
1325 "error",
1326 "event",
1327 "external",
1328 "fallback",
1329 "false",
1330 "for",
1331 "function",
1332 "if",
1333 "immutable",
1334 "import",
1335 "indexed",
1336 "int8",
1337 "int24",
1338 "int128",
1339 "int256",
1340 "interface",
1341 "internal",
1342 "library",
1343 "mapping",
1344 "memory",
1345 "modifier",
1346 "new",
1347 "override",
1348 "payable",
1349 "pragma",
1350 "private",
1351 "public",
1352 "pure",
1353 "receive",
1354 "return",
1355 "returns",
1356 "revert",
1357 "storage",
1358 "string",
1359 "struct",
1360 "true",
1361 "type",
1362 "uint8",
1363 "uint24",
1364 "uint128",
1365 "uint160",
1366 "uint256",
1367 "unchecked",
1368 "using",
1369 "view",
1370 "virtual",
1371 "while",
1372];
1373
1374const ETHER_UNITS: &[(&str, &str)] = &[
1376 ("wei", "1"),
1377 ("gwei", "1e9"),
1378 ("ether", "1e18"),
1379];
1380
1381const TIME_UNITS: &[(&str, &str)] = &[
1383 ("seconds", "1"),
1384 ("minutes", "60 seconds"),
1385 ("hours", "3600 seconds"),
1386 ("days", "86400 seconds"),
1387 ("weeks", "604800 seconds"),
1388];
1389
1390const MAGIC_GLOBALS: &[(&str, &str)] = &[
1391 ("msg", "msg"),
1392 ("block", "block"),
1393 ("tx", "tx"),
1394 ("abi", "abi"),
1395 ("this", "address"),
1396 ("super", "contract"),
1397 ("type", "type information"),
1398];
1399
1400const GLOBAL_FUNCTIONS: &[(&str, &str)] = &[
1401 ("addmod(uint256, uint256, uint256)", "uint256"),
1403 ("mulmod(uint256, uint256, uint256)", "uint256"),
1404 ("keccak256(bytes memory)", "bytes32"),
1405 ("sha256(bytes memory)", "bytes32"),
1406 ("ripemd160(bytes memory)", "bytes20"),
1407 (
1408 "ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s)",
1409 "address",
1410 ),
1411 ("blockhash(uint256 blockNumber)", "bytes32"),
1413 ("blobhash(uint256 index)", "bytes32"),
1414 ("gasleft()", "uint256"),
1415 ("assert(bool condition)", ""),
1417 ("require(bool condition)", ""),
1418 ("require(bool condition, string memory message)", ""),
1419 ("revert()", ""),
1420 ("revert(string memory reason)", ""),
1421 ("selfdestruct(address payable recipient)", ""),
1423];