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
42fn push_if_node_or_array<'a>(tree: &'a Value, key: &str, stack: &mut Vec<&'a Value>) {
43 if let Some(value) = tree.get(key) {
44 match value {
45 Value::Array(arr) => stack.extend(arr),
46 Value::Object(_) => stack.push(value),
47 _ => {}
48 }
49 }
50}
51
52fn node_type_to_completion_kind(node_type: &str) -> CompletionItemKind {
54 match node_type {
55 "FunctionDefinition" => CompletionItemKind::FUNCTION,
56 "VariableDeclaration" => CompletionItemKind::VARIABLE,
57 "ContractDefinition" => CompletionItemKind::CLASS,
58 "StructDefinition" => CompletionItemKind::STRUCT,
59 "EnumDefinition" => CompletionItemKind::ENUM,
60 "EnumValue" => CompletionItemKind::ENUM_MEMBER,
61 "EventDefinition" => CompletionItemKind::EVENT,
62 "ErrorDefinition" => CompletionItemKind::EVENT,
63 "ModifierDefinition" => CompletionItemKind::METHOD,
64 "ImportDirective" => CompletionItemKind::MODULE,
65 _ => CompletionItemKind::TEXT,
66 }
67}
68
69pub fn extract_node_id_from_type(type_id: &str) -> Option<u64> {
74 let mut last_id = None;
77 let mut i = 0;
78 let bytes = type_id.as_bytes();
79 while i < bytes.len() {
80 if i + 1 < bytes.len() && bytes[i] == b'_' && bytes[i + 1] == b'$' {
81 i += 2;
82 let start = i;
83 while i < bytes.len() && bytes[i].is_ascii_digit() {
84 i += 1;
85 }
86 if i > start {
87 if let Ok(id) = type_id[start..i].parse::<u64>() {
88 last_id = Some(id);
89 }
90 }
91 } else {
92 i += 1;
93 }
94 }
95 last_id
96}
97
98fn build_function_signature(node: &Value) -> Option<String> {
101 let name = node.get("name").and_then(|v| v.as_str()).unwrap_or("");
102 if name.is_empty() {
103 return None;
104 }
105
106 let params = node
107 .get("parameters")
108 .and_then(|p| p.get("parameters"))
109 .and_then(|v| v.as_array());
110
111 let mut sig = String::new();
112 sig.push_str(name);
113 sig.push('(');
114
115 if let Some(params) = params {
116 for (i, param) in params.iter().enumerate() {
117 if i > 0 {
118 sig.push_str(", ");
119 }
120 let type_str = param
121 .get("typeDescriptions")
122 .and_then(|td| td.get("typeString"))
123 .and_then(|v| v.as_str())
124 .unwrap_or("?");
125 let clean_type = type_str
127 .strip_prefix("struct ")
128 .or_else(|| type_str.strip_prefix("contract "))
129 .or_else(|| type_str.strip_prefix("enum "))
130 .unwrap_or(type_str);
131 let param_name = param.get("name").and_then(|v| v.as_str()).unwrap_or("");
132 sig.push_str(clean_type);
133 if !param_name.is_empty() {
134 sig.push(' ');
135 sig.push_str(param_name);
136 }
137 }
138 }
139 sig.push(')');
140
141 let returns = node
143 .get("returnParameters")
144 .and_then(|p| p.get("parameters"))
145 .and_then(|v| v.as_array());
146
147 if let Some(returns) = returns {
148 if !returns.is_empty() {
149 sig.push_str(" returns (");
150 for (i, ret) in returns.iter().enumerate() {
151 if i > 0 {
152 sig.push_str(", ");
153 }
154 let type_str = ret
155 .get("typeDescriptions")
156 .and_then(|td| td.get("typeString"))
157 .and_then(|v| v.as_str())
158 .unwrap_or("?");
159 let clean_type = type_str
160 .strip_prefix("struct ")
161 .or_else(|| type_str.strip_prefix("contract "))
162 .or_else(|| type_str.strip_prefix("enum "))
163 .unwrap_or(type_str);
164 let ret_name = ret.get("name").and_then(|v| v.as_str()).unwrap_or("");
165 sig.push_str(clean_type);
166 if !ret_name.is_empty() {
167 sig.push(' ');
168 sig.push_str(ret_name);
169 }
170 }
171 sig.push(')');
172 }
173 }
174
175 Some(sig)
176}
177
178pub fn extract_mapping_value_type(type_id: &str) -> Option<String> {
185 let mut current = type_id;
186
187 loop {
188 if !current.starts_with("t_mapping$_") {
189 let result = current.trim_end_matches("_$");
192 return if result.is_empty() {
193 None
194 } else {
195 Some(result.to_string())
196 };
197 }
198
199 let inner = ¤t["t_mapping$_".len()..];
201
202 let mut depth = 0i32;
206 let bytes = inner.as_bytes();
207 let mut split_pos = None;
208
209 let mut i = 0;
210 while i < bytes.len() {
211 if i + 1 < bytes.len() && bytes[i] == b'$' && bytes[i + 1] == b'_' {
212 depth += 1;
213 i += 2;
214 } else if i + 2 < bytes.len()
215 && bytes[i] == b'_'
216 && bytes[i + 1] == b'$'
217 && bytes[i + 2] == b'_'
218 && depth == 0
219 {
220 split_pos = Some(i);
222 break;
223 } else if i + 1 < bytes.len() && bytes[i] == b'_' && bytes[i + 1] == b'$' {
224 depth -= 1;
225 i += 2;
226 } else {
227 i += 1;
228 }
229 }
230
231 if let Some(pos) = split_pos {
232 current = &inner[pos + 3..];
234 } else {
235 return None;
236 }
237 }
238}
239
240fn count_abi_params(signature: &str) -> usize {
243 let start = match signature.find('(') {
245 Some(i) => i + 1,
246 None => return 0,
247 };
248 let bytes = signature.as_bytes();
249 if start >= bytes.len() {
250 return 0;
251 }
252 if bytes[start] == b')' {
254 return 0;
255 }
256 let mut count = 1; let mut depth = 0;
258 for &b in &bytes[start..] {
259 match b {
260 b'(' => depth += 1,
261 b')' => {
262 if depth == 0 {
263 break;
264 }
265 depth -= 1;
266 }
267 b',' if depth == 0 => count += 1,
268 _ => {}
269 }
270 }
271 count
272}
273
274fn count_signature_params(sig: &str) -> usize {
276 count_abi_params(sig)
277}
278
279pub fn build_completion_cache(sources: &Value, contracts: Option<&Value>) -> CompletionCache {
282 let mut names: Vec<CompletionItem> = Vec::new();
283 let mut seen_names: HashMap<String, usize> = HashMap::new(); let mut name_to_type: HashMap<String, String> = HashMap::new();
285 let mut node_members: HashMap<u64, Vec<CompletionItem>> = HashMap::new();
286 let mut type_to_node: HashMap<String, u64> = HashMap::new();
287 let mut method_identifiers: HashMap<u64, Vec<CompletionItem>> = HashMap::new();
288 let mut name_to_node_id: HashMap<String, u64> = HashMap::new();
289
290 let mut contract_locations: Vec<(String, String, u64)> = Vec::new();
292
293 let mut function_signatures: HashMap<u64, HashMap<String, Vec<String>>> = HashMap::new();
295
296 let mut function_return_types: HashMap<(u64, String), String> = HashMap::new();
298
299 let mut using_for: HashMap<String, Vec<CompletionItem>> = HashMap::new();
301 let mut using_for_wildcard: Vec<CompletionItem> = Vec::new();
302
303 let mut using_for_directives: Vec<(u64, Option<String>)> = Vec::new();
305
306 if let Some(sources_obj) = sources.as_object() {
307 for (path, contents) in sources_obj {
308 if let Some(contents_array) = contents.as_array()
309 && let Some(first_content) = contents_array.first()
310 && let Some(source_file) = first_content.get("source_file")
311 && let Some(ast) = source_file.get("ast")
312 {
313 let mut stack: Vec<&Value> = vec![ast];
314
315 while let Some(tree) = stack.pop() {
316 let node_type = tree
317 .get("nodeType")
318 .and_then(|v| v.as_str())
319 .unwrap_or("");
320 let name = tree.get("name").and_then(|v| v.as_str()).unwrap_or("");
321 let node_id = tree.get("id").and_then(|v| v.as_u64());
322
323 if !name.is_empty() && !seen_names.contains_key(name) {
325 let type_string = tree
326 .get("typeDescriptions")
327 .and_then(|td| td.get("typeString"))
328 .and_then(|v| v.as_str())
329 .map(|s| s.to_string());
330
331 let type_id = tree
332 .get("typeDescriptions")
333 .and_then(|td| td.get("typeIdentifier"))
334 .and_then(|v| v.as_str());
335
336 let kind = node_type_to_completion_kind(node_type);
337
338 let item = CompletionItem {
339 label: name.to_string(),
340 kind: Some(kind),
341 detail: type_string,
342 ..Default::default()
343 };
344
345 let idx = names.len();
346 names.push(item);
347 seen_names.insert(name.to_string(), idx);
348
349 if let Some(tid) = type_id {
351 name_to_type.insert(name.to_string(), tid.to_string());
352 }
353 }
354
355 if node_type == "StructDefinition" {
357 if let Some(id) = node_id {
358 let mut members = Vec::new();
359 if let Some(member_array) =
360 tree.get("members").and_then(|v| v.as_array())
361 {
362 for member in member_array {
363 let member_name =
364 member.get("name").and_then(|v| v.as_str()).unwrap_or("");
365 if member_name.is_empty() {
366 continue;
367 }
368 let member_type = member
369 .get("typeDescriptions")
370 .and_then(|td| td.get("typeString"))
371 .and_then(|v| v.as_str())
372 .map(|s| s.to_string());
373
374 members.push(CompletionItem {
375 label: member_name.to_string(),
376 kind: Some(CompletionItemKind::FIELD),
377 detail: member_type,
378 ..Default::default()
379 });
380 }
381 }
382 if !members.is_empty() {
383 node_members.insert(id, members);
384 }
385
386 if let Some(tid) = tree
388 .get("typeDescriptions")
389 .and_then(|td| td.get("typeIdentifier"))
390 .and_then(|v| v.as_str())
391 {
392 type_to_node.insert(tid.to_string(), id);
393 }
394 }
395 }
396
397 if node_type == "ContractDefinition" {
399 if let Some(id) = node_id {
400 let mut members = Vec::new();
401 let mut fn_sigs: HashMap<String, Vec<String>> = HashMap::new();
402 if let Some(nodes_array) =
403 tree.get("nodes").and_then(|v| v.as_array())
404 {
405 for member in nodes_array {
406 let member_type = member
407 .get("nodeType")
408 .and_then(|v| v.as_str())
409 .unwrap_or("");
410 let member_name =
411 member.get("name").and_then(|v| v.as_str()).unwrap_or("");
412 if member_name.is_empty() {
413 continue;
414 }
415
416 let (member_detail, label_details) =
418 if member_type == "FunctionDefinition" {
419 if let Some(ret_params) = member
423 .get("returnParameters")
424 .and_then(|rp| rp.get("parameters"))
425 .and_then(|v| v.as_array())
426 {
427 if ret_params.len() == 1 {
428 if 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 }
440
441 if let Some(sig) = build_function_signature(member) {
442 fn_sigs
443 .entry(member_name.to_string())
444 .or_default()
445 .push(sig.clone());
446 (
447 Some(sig),
448 None,
449 )
450 } else {
451 (
452 member
453 .get("typeDescriptions")
454 .and_then(|td| td.get("typeString"))
455 .and_then(|v| v.as_str())
456 .map(|s| s.to_string()),
457 None,
458 )
459 }
460 } else {
461 (
462 member
463 .get("typeDescriptions")
464 .and_then(|td| td.get("typeString"))
465 .and_then(|v| v.as_str())
466 .map(|s| s.to_string()),
467 None,
468 )
469 };
470
471 let kind = node_type_to_completion_kind(member_type);
472 members.push(CompletionItem {
473 label: member_name.to_string(),
474 kind: Some(kind),
475 detail: member_detail,
476 label_details,
477 ..Default::default()
478 });
479 }
480 }
481 if !members.is_empty() {
482 node_members.insert(id, members);
483 }
484 if !fn_sigs.is_empty() {
485 function_signatures.insert(id, fn_sigs);
486 }
487
488 if let Some(tid) = tree
489 .get("typeDescriptions")
490 .and_then(|td| td.get("typeIdentifier"))
491 .and_then(|v| v.as_str())
492 {
493 type_to_node.insert(tid.to_string(), id);
494 }
495
496 if !name.is_empty() {
498 contract_locations.push((
499 path.clone(),
500 name.to_string(),
501 id,
502 ));
503 name_to_node_id.insert(name.to_string(), id);
504 }
505 }
506 }
507
508 if node_type == "EnumDefinition" {
510 if let Some(id) = node_id {
511 let mut members = Vec::new();
512 if let Some(member_array) =
513 tree.get("members").and_then(|v| v.as_array())
514 {
515 for member in member_array {
516 let member_name =
517 member.get("name").and_then(|v| v.as_str()).unwrap_or("");
518 if member_name.is_empty() {
519 continue;
520 }
521 members.push(CompletionItem {
522 label: member_name.to_string(),
523 kind: Some(CompletionItemKind::ENUM_MEMBER),
524 detail: None,
525 ..Default::default()
526 });
527 }
528 }
529 if !members.is_empty() {
530 node_members.insert(id, members);
531 }
532
533 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
543 if node_type == "UsingForDirective" {
545 let target_type = tree
547 .get("typeName")
548 .and_then(|tn| {
549 tn.get("typeDescriptions")
550 .and_then(|td| td.get("typeIdentifier"))
551 .and_then(|v| v.as_str())
552 .map(|s| s.to_string())
553 });
554
555 if let Some(lib) = tree.get("libraryName") {
557 if let Some(lib_id) = lib
558 .get("referencedDeclaration")
559 .and_then(|v| v.as_u64())
560 {
561 using_for_directives.push((lib_id, target_type));
562 }
563 }
564 else if let Some(func_list) =
568 tree.get("functionList").and_then(|v| v.as_array())
569 {
570 for entry in func_list {
571 if entry.get("operator").is_some() {
573 continue;
574 }
575 if let Some(def) = entry.get("definition") {
576 let fn_name = def
577 .get("name")
578 .and_then(|v| v.as_str())
579 .unwrap_or("");
580 if !fn_name.is_empty() {
581 let items = if let Some(ref tid) = target_type {
582 using_for.entry(tid.clone()).or_default()
583 } else {
584 &mut using_for_wildcard
585 };
586 items.push(CompletionItem {
587 label: fn_name.to_string(),
588 kind: Some(CompletionItemKind::FUNCTION),
589 detail: None,
590 ..Default::default()
591 });
592 }
593 }
594 }
595 }
596 }
597
598 for key in CHILD_KEYS {
600 push_if_node_or_array(tree, key, &mut stack);
601 }
602 }
603 }
604 }
605 }
606
607 for (lib_id, target_type) in &using_for_directives {
610 if let Some(lib_members) = node_members.get(lib_id) {
611 let items: Vec<CompletionItem> = lib_members
612 .iter()
613 .filter(|item| item.kind == Some(CompletionItemKind::FUNCTION))
614 .cloned()
615 .collect();
616 if !items.is_empty() {
617 if let Some(tid) = target_type {
618 using_for.entry(tid.clone()).or_default().extend(items);
619 } else {
620 using_for_wildcard.extend(items);
621 }
622 }
623 }
624 }
625
626 if let Some(contracts_val) = contracts {
628 if let Some(contracts_obj) = contracts_val.as_object() {
629 for (path, contract_name, node_id) in &contract_locations {
630 let fn_sigs = function_signatures.get(node_id);
632
633 if let Some(path_entry) = contracts_obj.get(path)
634 && let Some(contract_entry) = path_entry.get(contract_name)
635 && let Some(first) = contract_entry.get(0)
636 && let Some(evm) = first
637 .get("contract")
638 .and_then(|c| c.get("evm"))
639 && let Some(methods) = evm.get("methodIdentifiers")
640 && let Some(methods_obj) = methods.as_object()
641 {
642 let mut items: Vec<CompletionItem> = Vec::new();
643 for (signature, selector) in methods_obj {
644 let fn_name = signature
647 .split('(')
648 .next()
649 .unwrap_or(signature)
650 .to_string();
651 let selector_str = selector
652 .as_str()
653 .map(|s| format!("0x{}", s))
654 .unwrap_or_default();
655
656 let description = fn_sigs
658 .and_then(|sigs| sigs.get(&fn_name))
659 .and_then(|sig_list| {
660 if sig_list.len() == 1 {
661 Some(sig_list[0].clone())
663 } else {
664 let abi_param_count =
666 count_abi_params(signature);
667 sig_list.iter().find(|s| {
668 count_signature_params(s) == abi_param_count
669 }).cloned()
670 }
671 });
672
673 items.push(CompletionItem {
674 label: fn_name,
675 kind: Some(CompletionItemKind::FUNCTION),
676 detail: Some(signature.clone()),
677 label_details: Some(tower_lsp::lsp_types::CompletionItemLabelDetails {
678 detail: Some(selector_str),
679 description,
680 }),
681 ..Default::default()
682 });
683 }
684 if !items.is_empty() {
685 method_identifiers.insert(*node_id, items);
686 }
687 }
688 }
689 }
690 }
691
692 CompletionCache {
693 names,
694 name_to_type,
695 node_members,
696 type_to_node,
697 name_to_node_id,
698 method_identifiers,
699 function_return_types,
700 using_for,
701 using_for_wildcard,
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
900fn 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 if let Some(items) = cache.using_for.get(variant.as_str()) {
936 return items.clone();
937 }
938 }
939 }
940
941 vec![]
942}
943
944fn completions_for_type(cache: &CompletionCache, type_id: &str) -> Vec<CompletionItem> {
947 if type_id == "t_address" || type_id == "t_address_payable" {
949 let mut items = address_members();
950 if let Some(uf) = cache.using_for.get(type_id) {
952 items.extend(uf.iter().cloned());
953 }
954 items.extend(cache.using_for_wildcard.iter().cloned());
955 return items;
956 }
957
958 let resolved_node_id = extract_node_id_from_type(type_id)
959 .or_else(|| cache.type_to_node.get(type_id).copied())
960 .or_else(|| {
961 type_id
963 .strip_prefix("__node_id_")
964 .and_then(|s| s.parse::<u64>().ok())
965 });
966
967 let mut items = Vec::new();
968 let mut seen_labels: std::collections::HashSet<String> = std::collections::HashSet::new();
969
970 if let Some(node_id) = resolved_node_id {
971 if let Some(method_items) = cache.method_identifiers.get(&node_id) {
973 for item in method_items {
974 seen_labels.insert(item.label.clone());
975 items.push(item.clone());
976 }
977 }
978
979 if let Some(members) = cache.node_members.get(&node_id) {
981 for item in members {
982 if !seen_labels.contains(&item.label) {
983 seen_labels.insert(item.label.clone());
984 items.push(item.clone());
985 }
986 }
987 }
988 }
989
990 let uf_items = lookup_using_for(cache, type_id);
993 for item in &uf_items {
994 if !seen_labels.contains(&item.label) {
995 seen_labels.insert(item.label.clone());
996 items.push(item.clone());
997 }
998 }
999
1000 for item in &cache.using_for_wildcard {
1002 if !seen_labels.contains(&item.label) {
1003 seen_labels.insert(item.label.clone());
1004 items.push(item.clone());
1005 }
1006 }
1007
1008 items
1009}
1010
1011fn resolve_name_to_type_id(cache: &CompletionCache, name: &str) -> Option<String> {
1013 if let Some(tid) = cache.name_to_type.get(name) {
1015 return Some(tid.clone());
1016 }
1017 if let Some(node_id) = cache.name_to_node_id.get(name) {
1019 for (tid, nid) in &cache.type_to_node {
1021 if nid == node_id {
1022 return Some(tid.clone());
1023 }
1024 }
1025 return Some(format!("__node_id_{}", node_id));
1027 }
1028 None
1029}
1030
1031fn resolve_member_type(
1036 cache: &CompletionCache,
1037 context_type_id: &str,
1038 member_name: &str,
1039 kind: &AccessKind,
1040) -> Option<String> {
1041 let resolved_node_id = extract_node_id_from_type(context_type_id)
1042 .or_else(|| cache.type_to_node.get(context_type_id).copied())
1043 .or_else(|| {
1044 context_type_id
1046 .strip_prefix("__node_id_")
1047 .and_then(|s| s.parse::<u64>().ok())
1048 });
1049
1050 let node_id = resolved_node_id?;
1051
1052 match kind {
1053 AccessKind::Call => {
1054 cache
1056 .function_return_types
1057 .get(&(node_id, member_name.to_string()))
1058 .cloned()
1059 }
1060 AccessKind::Index => {
1061 if let Some(members) = cache.node_members.get(&node_id) {
1063 for member in members {
1064 if member.label == member_name {
1065 if let Some(tid) = cache.name_to_type.get(member_name) {
1067 if tid.starts_with("t_mapping") {
1068 return extract_mapping_value_type(tid);
1069 }
1070 return Some(tid.clone());
1071 }
1072 }
1073 }
1074 }
1075 if let Some(tid) = cache.name_to_type.get(member_name) {
1077 if tid.starts_with("t_mapping") {
1078 return extract_mapping_value_type(tid);
1079 }
1080 }
1081 None
1082 }
1083 AccessKind::Plain => {
1084 cache.name_to_type.get(member_name).cloned()
1086 }
1087 }
1088}
1089
1090pub fn get_dot_completions(cache: &CompletionCache, identifier: &str) -> Vec<CompletionItem> {
1092 if let Some(items) = magic_members(identifier) {
1094 return items;
1095 }
1096
1097 let type_id = resolve_name_to_type_id(cache, identifier);
1099
1100 if let Some(tid) = type_id {
1101 return completions_for_type(cache, &tid);
1102 }
1103
1104 vec![]
1105}
1106
1107pub fn get_chain_completions(cache: &CompletionCache, chain: &[DotSegment]) -> Vec<CompletionItem> {
1110 if chain.is_empty() {
1111 return vec![];
1112 }
1113
1114 if chain.len() == 1 {
1116 let seg = &chain[0];
1117
1118 match seg.kind {
1120 AccessKind::Plain => {
1121 return get_dot_completions(cache, &seg.name);
1122 }
1123 AccessKind::Call => {
1124 if let Some(type_id) = resolve_name_to_type_id(cache, &seg.name) {
1127 return completions_for_type(cache, &type_id);
1128 }
1129 for ((_, fn_name), ret_type) in &cache.function_return_types {
1131 if fn_name == &seg.name {
1132 return completions_for_type(cache, ret_type);
1133 }
1134 }
1135 return vec![];
1136 }
1137 AccessKind::Index => {
1138 if let Some(tid) = cache.name_to_type.get(&seg.name) {
1140 if tid.starts_with("t_mapping") {
1141 if let Some(val_type) = extract_mapping_value_type(tid) {
1142 return completions_for_type(cache, &val_type);
1143 }
1144 }
1145 }
1146 return vec![];
1147 }
1148 }
1149 }
1150
1151 let first = &chain[0];
1154 let mut current_type = match first.kind {
1155 AccessKind::Plain => resolve_name_to_type_id(cache, &first.name),
1156 AccessKind::Call => {
1157 resolve_name_to_type_id(cache, &first.name).or_else(|| {
1159 cache
1160 .function_return_types
1161 .iter()
1162 .find(|((_, fn_name), _)| fn_name == &first.name)
1163 .map(|(_, ret_type)| ret_type.clone())
1164 })
1165 }
1166 AccessKind::Index => {
1167 cache.name_to_type.get(&first.name).and_then(|tid| {
1169 if tid.starts_with("t_mapping") {
1170 extract_mapping_value_type(tid)
1171 } else {
1172 Some(tid.clone())
1173 }
1174 })
1175 }
1176 };
1177
1178 for seg in &chain[1..] {
1180 let ctx_type = match ¤t_type {
1181 Some(t) => t.clone(),
1182 None => return vec![],
1183 };
1184
1185 current_type = resolve_member_type(cache, &ctx_type, &seg.name, &seg.kind);
1186 }
1187
1188 match current_type {
1190 Some(tid) => completions_for_type(cache, &tid),
1191 None => vec![],
1192 }
1193}
1194
1195pub fn get_general_completions(cache: &CompletionCache) -> Vec<CompletionItem> {
1197 let mut items = cache.names.clone();
1198
1199 for kw in SOLIDITY_KEYWORDS {
1201 items.push(CompletionItem {
1202 label: kw.to_string(),
1203 kind: Some(CompletionItemKind::KEYWORD),
1204 ..Default::default()
1205 });
1206 }
1207
1208 for (name, detail) in MAGIC_GLOBALS {
1210 items.push(CompletionItem {
1211 label: name.to_string(),
1212 kind: Some(CompletionItemKind::VARIABLE),
1213 detail: Some(detail.to_string()),
1214 ..Default::default()
1215 });
1216 }
1217
1218 for (name, detail) in GLOBAL_FUNCTIONS {
1220 items.push(CompletionItem {
1221 label: name.to_string(),
1222 kind: Some(CompletionItemKind::FUNCTION),
1223 detail: Some(detail.to_string()),
1224 ..Default::default()
1225 });
1226 }
1227
1228 for (name, detail) in ETHER_UNITS {
1230 items.push(CompletionItem {
1231 label: name.to_string(),
1232 kind: Some(CompletionItemKind::UNIT),
1233 detail: Some(detail.to_string()),
1234 ..Default::default()
1235 });
1236 }
1237
1238 for (name, detail) in TIME_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 items
1249}
1250
1251pub fn handle_completion(
1253 ast_data: &Value,
1254 source_text: &str,
1255 position: Position,
1256 trigger_char: Option<&str>,
1257) -> Option<CompletionResponse> {
1258 let sources = ast_data.get("sources")?;
1259 let contracts = ast_data.get("contracts");
1260 let cache = build_completion_cache(sources, contracts);
1261
1262 let lines: Vec<&str> = source_text.lines().collect();
1263 let line = lines.get(position.line as usize)?;
1264
1265 let items = if trigger_char == Some(".") {
1266 let chain = parse_dot_chain(line, position.character);
1267 if chain.is_empty() {
1268 return None;
1269 }
1270 get_chain_completions(&cache, &chain)
1271 } else {
1272 get_general_completions(&cache)
1273 };
1274
1275 Some(CompletionResponse::List(CompletionList {
1276 is_incomplete: false,
1277 items,
1278 }))
1279}
1280
1281const SOLIDITY_KEYWORDS: &[&str] = &[
1282 "abstract",
1283 "address",
1284 "assembly",
1285 "bool",
1286 "break",
1287 "bytes",
1288 "bytes1",
1289 "bytes4",
1290 "bytes32",
1291 "calldata",
1292 "constant",
1293 "constructor",
1294 "continue",
1295 "contract",
1296 "delete",
1297 "do",
1298 "else",
1299 "emit",
1300 "enum",
1301 "error",
1302 "event",
1303 "external",
1304 "fallback",
1305 "false",
1306 "for",
1307 "function",
1308 "if",
1309 "immutable",
1310 "import",
1311 "indexed",
1312 "int8",
1313 "int24",
1314 "int128",
1315 "int256",
1316 "interface",
1317 "internal",
1318 "library",
1319 "mapping",
1320 "memory",
1321 "modifier",
1322 "new",
1323 "override",
1324 "payable",
1325 "pragma",
1326 "private",
1327 "public",
1328 "pure",
1329 "receive",
1330 "return",
1331 "returns",
1332 "revert",
1333 "storage",
1334 "string",
1335 "struct",
1336 "true",
1337 "type",
1338 "uint8",
1339 "uint24",
1340 "uint128",
1341 "uint160",
1342 "uint256",
1343 "unchecked",
1344 "using",
1345 "view",
1346 "virtual",
1347 "while",
1348];
1349
1350const ETHER_UNITS: &[(&str, &str)] = &[
1352 ("wei", "1"),
1353 ("gwei", "1e9"),
1354 ("ether", "1e18"),
1355];
1356
1357const TIME_UNITS: &[(&str, &str)] = &[
1359 ("seconds", "1"),
1360 ("minutes", "60 seconds"),
1361 ("hours", "3600 seconds"),
1362 ("days", "86400 seconds"),
1363 ("weeks", "604800 seconds"),
1364];
1365
1366const MAGIC_GLOBALS: &[(&str, &str)] = &[
1367 ("msg", "msg"),
1368 ("block", "block"),
1369 ("tx", "tx"),
1370 ("abi", "abi"),
1371 ("this", "address"),
1372 ("super", "contract"),
1373 ("type", "type information"),
1374];
1375
1376const GLOBAL_FUNCTIONS: &[(&str, &str)] = &[
1377 ("addmod(uint256, uint256, uint256)", "uint256"),
1379 ("mulmod(uint256, uint256, uint256)", "uint256"),
1380 ("keccak256(bytes memory)", "bytes32"),
1381 ("sha256(bytes memory)", "bytes32"),
1382 ("ripemd160(bytes memory)", "bytes20"),
1383 (
1384 "ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s)",
1385 "address",
1386 ),
1387 ("blockhash(uint256 blockNumber)", "bytes32"),
1389 ("blobhash(uint256 index)", "bytes32"),
1390 ("gasleft()", "uint256"),
1391 ("assert(bool condition)", ""),
1393 ("require(bool condition)", ""),
1394 ("require(bool condition, string memory message)", ""),
1395 ("revert()", ""),
1396 ("revert(string memory reason)", ""),
1397 ("selfdestruct(address payable recipient)", ""),
1399];