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 {
93 last_id = Some(id);
94 }
95 } else {
96 i += 1;
97 }
98 }
99 last_id
100}
101
102fn build_function_signature(node: &Value) -> Option<String> {
105 let name = node.get("name").and_then(|v| v.as_str()).unwrap_or("");
106 if name.is_empty() {
107 return None;
108 }
109
110 let params = node
111 .get("parameters")
112 .and_then(|p| p.get("parameters"))
113 .and_then(|v| v.as_array());
114
115 let mut sig = String::new();
116 sig.push_str(name);
117 sig.push('(');
118
119 if let Some(params) = params {
120 for (i, param) in params.iter().enumerate() {
121 if i > 0 {
122 sig.push_str(", ");
123 }
124 let type_str = param
125 .get("typeDescriptions")
126 .and_then(|td| td.get("typeString"))
127 .and_then(|v| v.as_str())
128 .unwrap_or("?");
129 let clean_type = type_str
131 .strip_prefix("struct ")
132 .or_else(|| type_str.strip_prefix("contract "))
133 .or_else(|| type_str.strip_prefix("enum "))
134 .unwrap_or(type_str);
135 let param_name = param.get("name").and_then(|v| v.as_str()).unwrap_or("");
136 sig.push_str(clean_type);
137 if !param_name.is_empty() {
138 sig.push(' ');
139 sig.push_str(param_name);
140 }
141 }
142 }
143 sig.push(')');
144
145 let returns = node
147 .get("returnParameters")
148 .and_then(|p| p.get("parameters"))
149 .and_then(|v| v.as_array());
150
151 if let Some(returns) = returns
152 && !returns.is_empty()
153 {
154 sig.push_str(" returns (");
155 for (i, ret) in returns.iter().enumerate() {
156 if i > 0 {
157 sig.push_str(", ");
158 }
159 let type_str = ret
160 .get("typeDescriptions")
161 .and_then(|td| td.get("typeString"))
162 .and_then(|v| v.as_str())
163 .unwrap_or("?");
164 let clean_type = type_str
165 .strip_prefix("struct ")
166 .or_else(|| type_str.strip_prefix("contract "))
167 .or_else(|| type_str.strip_prefix("enum "))
168 .unwrap_or(type_str);
169 let ret_name = ret.get("name").and_then(|v| v.as_str()).unwrap_or("");
170 sig.push_str(clean_type);
171 if !ret_name.is_empty() {
172 sig.push(' ');
173 sig.push_str(ret_name);
174 }
175 }
176 sig.push(')');
177 }
178
179 Some(sig)
180}
181
182pub fn extract_mapping_value_type(type_id: &str) -> Option<String> {
189 let mut current = type_id;
190
191 loop {
192 if !current.starts_with("t_mapping$_") {
193 let result = current.trim_end_matches("_$");
196 return if result.is_empty() {
197 None
198 } else {
199 Some(result.to_string())
200 };
201 }
202
203 let inner = ¤t["t_mapping$_".len()..];
205
206 let mut depth = 0i32;
210 let bytes = inner.as_bytes();
211 let mut split_pos = None;
212
213 let mut i = 0;
214 while i < bytes.len() {
215 if i + 1 < bytes.len() && bytes[i] == b'$' && bytes[i + 1] == b'_' {
216 depth += 1;
217 i += 2;
218 } else if i + 2 < bytes.len()
219 && bytes[i] == b'_'
220 && bytes[i + 1] == b'$'
221 && bytes[i + 2] == b'_'
222 && depth == 0
223 {
224 split_pos = Some(i);
226 break;
227 } else if i + 1 < bytes.len() && bytes[i] == b'_' && bytes[i + 1] == b'$' {
228 depth -= 1;
229 i += 2;
230 } else {
231 i += 1;
232 }
233 }
234
235 if let Some(pos) = split_pos {
236 current = &inner[pos + 3..];
238 } else {
239 return None;
240 }
241 }
242}
243
244fn count_abi_params(signature: &str) -> usize {
247 let start = match signature.find('(') {
249 Some(i) => i + 1,
250 None => return 0,
251 };
252 let bytes = signature.as_bytes();
253 if start >= bytes.len() {
254 return 0;
255 }
256 if bytes[start] == b')' {
258 return 0;
259 }
260 let mut count = 1; let mut depth = 0;
262 for &b in &bytes[start..] {
263 match b {
264 b'(' => depth += 1,
265 b')' => {
266 if depth == 0 {
267 break;
268 }
269 depth -= 1;
270 }
271 b',' if depth == 0 => count += 1,
272 _ => {}
273 }
274 }
275 count
276}
277
278fn count_signature_params(sig: &str) -> usize {
280 count_abi_params(sig)
281}
282
283pub fn build_completion_cache(sources: &Value, contracts: Option<&Value>) -> CompletionCache {
286 let mut names: Vec<CompletionItem> = Vec::new();
287 let mut seen_names: HashMap<String, usize> = HashMap::new(); let mut name_to_type: HashMap<String, String> = HashMap::new();
289 let mut node_members: HashMap<u64, Vec<CompletionItem>> = HashMap::new();
290 let mut type_to_node: HashMap<String, u64> = HashMap::new();
291 let mut method_identifiers: HashMap<u64, Vec<CompletionItem>> = HashMap::new();
292 let mut name_to_node_id: HashMap<String, u64> = HashMap::new();
293
294 let mut contract_locations: Vec<(String, String, u64)> = Vec::new();
296
297 let mut function_signatures: HashMap<u64, HashMap<String, Vec<String>>> = HashMap::new();
299
300 let mut function_return_types: HashMap<(u64, String), String> = HashMap::new();
302
303 let mut using_for: HashMap<String, Vec<CompletionItem>> = HashMap::new();
305 let mut using_for_wildcard: Vec<CompletionItem> = Vec::new();
306
307 let mut using_for_directives: Vec<(u64, Option<String>)> = Vec::new();
309
310 if let Some(sources_obj) = sources.as_object() {
311 for (path, contents) in sources_obj {
312 if let Some(contents_array) = contents.as_array()
313 && let Some(first_content) = contents_array.first()
314 && let Some(source_file) = first_content.get("source_file")
315 && let Some(ast) = source_file.get("ast")
316 {
317 let mut stack: Vec<&Value> = vec![ast];
318
319 while let Some(tree) = stack.pop() {
320 let node_type = tree.get("nodeType").and_then(|v| v.as_str()).unwrap_or("");
321 let name = tree.get("name").and_then(|v| v.as_str()).unwrap_or("");
322 let node_id = tree.get("id").and_then(|v| v.as_u64());
323
324 if !name.is_empty() && !seen_names.contains_key(name) {
326 let type_string = tree
327 .get("typeDescriptions")
328 .and_then(|td| td.get("typeString"))
329 .and_then(|v| v.as_str())
330 .map(|s| s.to_string());
331
332 let type_id = tree
333 .get("typeDescriptions")
334 .and_then(|td| td.get("typeIdentifier"))
335 .and_then(|v| v.as_str());
336
337 let kind = node_type_to_completion_kind(node_type);
338
339 let item = CompletionItem {
340 label: name.to_string(),
341 kind: Some(kind),
342 detail: type_string,
343 ..Default::default()
344 };
345
346 let idx = names.len();
347 names.push(item);
348 seen_names.insert(name.to_string(), idx);
349
350 if let Some(tid) = type_id {
352 name_to_type.insert(name.to_string(), tid.to_string());
353 }
354 }
355
356 if node_type == "StructDefinition"
358 && let Some(id) = node_id
359 {
360 let mut members = Vec::new();
361 if let Some(member_array) = tree.get("members").and_then(|v| v.as_array()) {
362 for member in member_array {
363 let member_name =
364 member.get("name").and_then(|v| v.as_str()).unwrap_or("");
365 if member_name.is_empty() {
366 continue;
367 }
368 let member_type = member
369 .get("typeDescriptions")
370 .and_then(|td| td.get("typeString"))
371 .and_then(|v| v.as_str())
372 .map(|s| s.to_string());
373
374 members.push(CompletionItem {
375 label: member_name.to_string(),
376 kind: Some(CompletionItemKind::FIELD),
377 detail: member_type,
378 ..Default::default()
379 });
380 }
381 }
382 if !members.is_empty() {
383 node_members.insert(id, members);
384 }
385
386 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 if node_type == "ContractDefinition"
398 && let Some(id) = node_id
399 {
400 let mut members = Vec::new();
401 let mut fn_sigs: HashMap<String, Vec<String>> = HashMap::new();
402 if let Some(nodes_array) = tree.get("nodes").and_then(|v| v.as_array()) {
403 for member in nodes_array {
404 let member_type = member
405 .get("nodeType")
406 .and_then(|v| v.as_str())
407 .unwrap_or("");
408 let member_name =
409 member.get("name").and_then(|v| v.as_str()).unwrap_or("");
410 if member_name.is_empty() {
411 continue;
412 }
413
414 let (member_detail, label_details) =
416 if member_type == "FunctionDefinition" {
417 if let Some(ret_params) = member
421 .get("returnParameters")
422 .and_then(|rp| rp.get("parameters"))
423 .and_then(|v| v.as_array())
424 && ret_params.len() == 1
425 && let Some(ret_tid) = ret_params[0]
426 .get("typeDescriptions")
427 .and_then(|td| td.get("typeIdentifier"))
428 .and_then(|v| v.as_str())
429 {
430 function_return_types.insert(
431 (id, member_name.to_string()),
432 ret_tid.to_string(),
433 );
434 }
435
436 if let Some(sig) = build_function_signature(member) {
437 fn_sigs
438 .entry(member_name.to_string())
439 .or_default()
440 .push(sig.clone());
441 (Some(sig), None)
442 } else {
443 (
444 member
445 .get("typeDescriptions")
446 .and_then(|td| td.get("typeString"))
447 .and_then(|v| v.as_str())
448 .map(|s| s.to_string()),
449 None,
450 )
451 }
452 } else {
453 (
454 member
455 .get("typeDescriptions")
456 .and_then(|td| td.get("typeString"))
457 .and_then(|v| v.as_str())
458 .map(|s| s.to_string()),
459 None,
460 )
461 };
462
463 let kind = node_type_to_completion_kind(member_type);
464 members.push(CompletionItem {
465 label: member_name.to_string(),
466 kind: Some(kind),
467 detail: member_detail,
468 label_details,
469 ..Default::default()
470 });
471 }
472 }
473 if !members.is_empty() {
474 node_members.insert(id, members);
475 }
476 if !fn_sigs.is_empty() {
477 function_signatures.insert(id, fn_sigs);
478 }
479
480 if let Some(tid) = tree
481 .get("typeDescriptions")
482 .and_then(|td| td.get("typeIdentifier"))
483 .and_then(|v| v.as_str())
484 {
485 type_to_node.insert(tid.to_string(), id);
486 }
487
488 if !name.is_empty() {
490 contract_locations.push((path.clone(), name.to_string(), id));
491 name_to_node_id.insert(name.to_string(), id);
492 }
493 }
494
495 if node_type == "EnumDefinition"
497 && let Some(id) = node_id
498 {
499 let mut members = Vec::new();
500 if let Some(member_array) = tree.get("members").and_then(|v| v.as_array()) {
501 for member in member_array {
502 let member_name =
503 member.get("name").and_then(|v| v.as_str()).unwrap_or("");
504 if member_name.is_empty() {
505 continue;
506 }
507 members.push(CompletionItem {
508 label: member_name.to_string(),
509 kind: Some(CompletionItemKind::ENUM_MEMBER),
510 detail: None,
511 ..Default::default()
512 });
513 }
514 }
515 if !members.is_empty() {
516 node_members.insert(id, members);
517 }
518
519 if let Some(tid) = tree
520 .get("typeDescriptions")
521 .and_then(|td| td.get("typeIdentifier"))
522 .and_then(|v| v.as_str())
523 {
524 type_to_node.insert(tid.to_string(), id);
525 }
526 }
527
528 if node_type == "UsingForDirective" {
530 let target_type = tree.get("typeName").and_then(|tn| {
532 tn.get("typeDescriptions")
533 .and_then(|td| td.get("typeIdentifier"))
534 .and_then(|v| v.as_str())
535 .map(|s| s.to_string())
536 });
537
538 if let Some(lib) = tree.get("libraryName") {
540 if let Some(lib_id) =
541 lib.get("referencedDeclaration").and_then(|v| v.as_u64())
542 {
543 using_for_directives.push((lib_id, target_type));
544 }
545 }
546 else if let Some(func_list) =
550 tree.get("functionList").and_then(|v| v.as_array())
551 {
552 for entry in func_list {
553 if entry.get("operator").is_some() {
555 continue;
556 }
557 if let Some(def) = entry.get("definition") {
558 let fn_name =
559 def.get("name").and_then(|v| v.as_str()).unwrap_or("");
560 if !fn_name.is_empty() {
561 let items = if let Some(ref tid) = target_type {
562 using_for.entry(tid.clone()).or_default()
563 } else {
564 &mut using_for_wildcard
565 };
566 items.push(CompletionItem {
567 label: fn_name.to_string(),
568 kind: Some(CompletionItemKind::FUNCTION),
569 detail: None,
570 ..Default::default()
571 });
572 }
573 }
574 }
575 }
576 }
577
578 for key in CHILD_KEYS {
580 push_if_node_or_array(tree, key, &mut stack);
581 }
582 }
583 }
584 }
585 }
586
587 for (lib_id, target_type) in &using_for_directives {
590 if let Some(lib_members) = node_members.get(lib_id) {
591 let items: Vec<CompletionItem> = lib_members
592 .iter()
593 .filter(|item| item.kind == Some(CompletionItemKind::FUNCTION))
594 .cloned()
595 .collect();
596 if !items.is_empty() {
597 if let Some(tid) = target_type {
598 using_for.entry(tid.clone()).or_default().extend(items);
599 } else {
600 using_for_wildcard.extend(items);
601 }
602 }
603 }
604 }
605
606 if let Some(contracts_val) = contracts
608 && let Some(contracts_obj) = contracts_val.as_object()
609 {
610 for (path, contract_name, node_id) in &contract_locations {
611 let fn_sigs = function_signatures.get(node_id);
613
614 if let Some(path_entry) = contracts_obj.get(path)
615 && let Some(contract_entry) = path_entry.get(contract_name)
616 && let Some(first) = contract_entry.get(0)
617 && let Some(evm) = first.get("contract").and_then(|c| c.get("evm"))
618 && let Some(methods) = evm.get("methodIdentifiers")
619 && let Some(methods_obj) = methods.as_object()
620 {
621 let mut items: Vec<CompletionItem> = Vec::new();
622 for (signature, selector) in methods_obj {
623 let fn_name = signature.split('(').next().unwrap_or(signature).to_string();
626 let selector_str = selector
627 .as_str()
628 .map(|s| format!("0x{}", s))
629 .unwrap_or_default();
630
631 let description =
633 fn_sigs
634 .and_then(|sigs| sigs.get(&fn_name))
635 .and_then(|sig_list| {
636 if sig_list.len() == 1 {
637 Some(sig_list[0].clone())
639 } else {
640 let abi_param_count = count_abi_params(signature);
642 sig_list
643 .iter()
644 .find(|s| count_signature_params(s) == abi_param_count)
645 .cloned()
646 }
647 });
648
649 items.push(CompletionItem {
650 label: fn_name,
651 kind: Some(CompletionItemKind::FUNCTION),
652 detail: Some(signature.clone()),
653 label_details: Some(tower_lsp::lsp_types::CompletionItemLabelDetails {
654 detail: Some(selector_str),
655 description,
656 }),
657 ..Default::default()
658 });
659 }
660 if !items.is_empty() {
661 method_identifiers.insert(*node_id, items);
662 }
663 }
664 }
665 }
666
667 let mut general_completions = names.clone();
669 general_completions.extend(get_static_completions());
670
671 CompletionCache {
672 names,
673 name_to_type,
674 node_members,
675 type_to_node,
676 name_to_node_id,
677 method_identifiers,
678 function_return_types,
679 using_for,
680 using_for_wildcard,
681 general_completions,
682 }
683}
684
685fn magic_members(name: &str) -> Option<Vec<CompletionItem>> {
687 let items = match name {
688 "msg" => vec![
689 ("data", "bytes calldata"),
690 ("sender", "address"),
691 ("sig", "bytes4"),
692 ("value", "uint256"),
693 ],
694 "block" => vec![
695 ("basefee", "uint256"),
696 ("blobbasefee", "uint256"),
697 ("chainid", "uint256"),
698 ("coinbase", "address payable"),
699 ("difficulty", "uint256"),
700 ("gaslimit", "uint256"),
701 ("number", "uint256"),
702 ("prevrandao", "uint256"),
703 ("timestamp", "uint256"),
704 ],
705 "tx" => vec![("gasprice", "uint256"), ("origin", "address")],
706 "abi" => vec![
707 ("decode(bytes memory, (...))", "..."),
708 ("encode(...)", "bytes memory"),
709 ("encodePacked(...)", "bytes memory"),
710 ("encodeWithSelector(bytes4, ...)", "bytes memory"),
711 ("encodeWithSignature(string memory, ...)", "bytes memory"),
712 ("encodeCall(function, (...))", "bytes memory"),
713 ],
714 "type" => vec![
717 ("name", "string"),
718 ("creationCode", "bytes memory"),
719 ("runtimeCode", "bytes memory"),
720 ("interfaceId", "bytes4"),
721 ("min", "T"),
722 ("max", "T"),
723 ],
724 "bytes" => vec![("concat(...)", "bytes memory")],
726 "string" => vec![("concat(...)", "string memory")],
727 _ => return None,
728 };
729
730 Some(
731 items
732 .into_iter()
733 .map(|(label, detail)| CompletionItem {
734 label: label.to_string(),
735 kind: Some(CompletionItemKind::PROPERTY),
736 detail: Some(detail.to_string()),
737 ..Default::default()
738 })
739 .collect(),
740 )
741}
742
743fn address_members() -> Vec<CompletionItem> {
745 [
746 ("balance", "uint256", CompletionItemKind::PROPERTY),
747 ("code", "bytes memory", CompletionItemKind::PROPERTY),
748 ("codehash", "bytes32", CompletionItemKind::PROPERTY),
749 ("transfer(uint256)", "", CompletionItemKind::FUNCTION),
750 ("send(uint256)", "bool", CompletionItemKind::FUNCTION),
751 (
752 "call(bytes memory)",
753 "(bool, bytes memory)",
754 CompletionItemKind::FUNCTION,
755 ),
756 (
757 "delegatecall(bytes memory)",
758 "(bool, bytes memory)",
759 CompletionItemKind::FUNCTION,
760 ),
761 (
762 "staticcall(bytes memory)",
763 "(bool, bytes memory)",
764 CompletionItemKind::FUNCTION,
765 ),
766 ]
767 .iter()
768 .map(|(label, detail, kind)| CompletionItem {
769 label: label.to_string(),
770 kind: Some(*kind),
771 detail: if detail.is_empty() {
772 None
773 } else {
774 Some(detail.to_string())
775 },
776 ..Default::default()
777 })
778 .collect()
779}
780
781#[derive(Debug, Clone, PartialEq)]
783pub enum AccessKind {
784 Plain,
786 Call,
788 Index,
790}
791
792#[derive(Debug, Clone, PartialEq)]
794pub struct DotSegment {
795 pub name: String,
796 pub kind: AccessKind,
797}
798
799fn skip_brackets_backwards(bytes: &[u8], pos: usize) -> usize {
803 let close = bytes[pos];
804 let open = match close {
805 b')' => b'(',
806 b']' => b'[',
807 _ => return pos,
808 };
809 let mut depth = 1u32;
810 let mut i = pos;
811 while i > 0 && depth > 0 {
812 i -= 1;
813 if bytes[i] == close {
814 depth += 1;
815 } else if bytes[i] == open {
816 depth -= 1;
817 }
818 }
819 i
820}
821
822pub fn parse_dot_chain(line: &str, character: u32) -> Vec<DotSegment> {
827 let col = character as usize;
828 if col == 0 {
829 return vec![];
830 }
831
832 let bytes = line.as_bytes();
833 let mut segments: Vec<DotSegment> = Vec::new();
834
835 let mut pos = col;
837 if pos > 0 && pos <= bytes.len() && bytes[pos - 1] == b'.' {
838 pos -= 1;
839 }
840
841 loop {
842 if pos == 0 {
843 break;
844 }
845
846 let kind = if bytes[pos - 1] == b')' {
848 pos -= 1; pos = skip_brackets_backwards(bytes, pos);
850 AccessKind::Call
851 } else if bytes[pos - 1] == b']' {
852 pos -= 1; pos = skip_brackets_backwards(bytes, pos);
854 AccessKind::Index
855 } else {
856 AccessKind::Plain
857 };
858
859 let end = pos;
861 while pos > 0 && (bytes[pos - 1].is_ascii_alphanumeric() || bytes[pos - 1] == b'_') {
862 pos -= 1;
863 }
864
865 if pos == end {
866 break;
868 }
869
870 let name = String::from_utf8_lossy(&bytes[pos..end]).to_string();
871 segments.push(DotSegment { name, kind });
872
873 if pos > 0 && bytes[pos - 1] == b'.' {
875 pos -= 1; } else {
877 break;
878 }
879 }
880
881 segments.reverse(); segments
883}
884
885pub fn extract_identifier_before_dot(line: &str, character: u32) -> Option<String> {
888 let segments = parse_dot_chain(line, character);
889 segments.last().map(|s| s.name.clone())
890}
891
892#[doc = r"Strip all storage/memory location suffixes from a typeIdentifier to get the base type.
893Solidity AST uses different suffixes in different contexts:
894 - `t_struct$_State_$4809_storage_ptr` (UsingForDirective typeName)
895 - `t_struct$_State_$4809_storage` (mapping value type after extraction)
896 - `t_struct$_PoolKey_$8887_memory_ptr` (function parameter)
897All refer to the same logical type. This strips `_ptr` and `_storage`/`_memory`/`_calldata`."]
898fn strip_type_suffix(type_id: &str) -> &str {
899 let s = type_id.strip_suffix("_ptr").unwrap_or(type_id);
900 s.strip_suffix("_storage")
901 .or_else(|| s.strip_suffix("_memory"))
902 .or_else(|| s.strip_suffix("_calldata"))
903 .unwrap_or(s)
904}
905
906fn lookup_using_for(cache: &CompletionCache, type_id: &str) -> Vec<CompletionItem> {
910 if let Some(items) = cache.using_for.get(type_id) {
912 return items.clone();
913 }
914
915 let base = strip_type_suffix(type_id);
917 let variants = [
918 base.to_string(),
919 format!("{}_storage", base),
920 format!("{}_storage_ptr", base),
921 format!("{}_memory", base),
922 format!("{}_memory_ptr", base),
923 format!("{}_calldata", base),
924 ];
925 for variant in &variants {
926 if variant.as_str() != type_id
927 && let Some(items) = cache.using_for.get(variant.as_str())
928 {
929 return items.clone();
930 }
931 }
932
933 vec![]
934}
935
936fn completions_for_type(cache: &CompletionCache, type_id: &str) -> Vec<CompletionItem> {
939 if type_id == "t_address" || type_id == "t_address_payable" {
941 let mut items = address_members();
942 if let Some(uf) = cache.using_for.get(type_id) {
944 items.extend(uf.iter().cloned());
945 }
946 items.extend(cache.using_for_wildcard.iter().cloned());
947 return items;
948 }
949
950 let resolved_node_id = extract_node_id_from_type(type_id)
951 .or_else(|| cache.type_to_node.get(type_id).copied())
952 .or_else(|| {
953 type_id
955 .strip_prefix("__node_id_")
956 .and_then(|s| s.parse::<u64>().ok())
957 });
958
959 let mut items = Vec::new();
960 let mut seen_labels: std::collections::HashSet<String> = std::collections::HashSet::new();
961
962 if let Some(node_id) = resolved_node_id {
963 if let Some(method_items) = cache.method_identifiers.get(&node_id) {
965 for item in method_items {
966 seen_labels.insert(item.label.clone());
967 items.push(item.clone());
968 }
969 }
970
971 if let Some(members) = cache.node_members.get(&node_id) {
973 for item in members {
974 if !seen_labels.contains(&item.label) {
975 seen_labels.insert(item.label.clone());
976 items.push(item.clone());
977 }
978 }
979 }
980 }
981
982 let uf_items = lookup_using_for(cache, type_id);
985 for item in &uf_items {
986 if !seen_labels.contains(&item.label) {
987 seen_labels.insert(item.label.clone());
988 items.push(item.clone());
989 }
990 }
991
992 for item in &cache.using_for_wildcard {
994 if !seen_labels.contains(&item.label) {
995 seen_labels.insert(item.label.clone());
996 items.push(item.clone());
997 }
998 }
999
1000 items
1001}
1002
1003fn resolve_name_to_type_id(cache: &CompletionCache, name: &str) -> Option<String> {
1005 if let Some(tid) = cache.name_to_type.get(name) {
1007 return Some(tid.clone());
1008 }
1009 if let Some(node_id) = cache.name_to_node_id.get(name) {
1011 for (tid, nid) in &cache.type_to_node {
1013 if nid == node_id {
1014 return Some(tid.clone());
1015 }
1016 }
1017 return Some(format!("__node_id_{}", node_id));
1019 }
1020 None
1021}
1022
1023fn resolve_member_type(
1028 cache: &CompletionCache,
1029 context_type_id: &str,
1030 member_name: &str,
1031 kind: &AccessKind,
1032) -> Option<String> {
1033 let resolved_node_id = extract_node_id_from_type(context_type_id)
1034 .or_else(|| cache.type_to_node.get(context_type_id).copied())
1035 .or_else(|| {
1036 context_type_id
1038 .strip_prefix("__node_id_")
1039 .and_then(|s| s.parse::<u64>().ok())
1040 });
1041
1042 let node_id = resolved_node_id?;
1043
1044 match kind {
1045 AccessKind::Call => {
1046 cache
1048 .function_return_types
1049 .get(&(node_id, member_name.to_string()))
1050 .cloned()
1051 }
1052 AccessKind::Index => {
1053 if let Some(members) = cache.node_members.get(&node_id) {
1055 for member in members {
1056 if member.label == member_name {
1057 if let Some(tid) = cache.name_to_type.get(member_name) {
1059 if tid.starts_with("t_mapping") {
1060 return extract_mapping_value_type(tid);
1061 }
1062 return Some(tid.clone());
1063 }
1064 }
1065 }
1066 }
1067 if let Some(tid) = cache.name_to_type.get(member_name)
1069 && tid.starts_with("t_mapping")
1070 {
1071 return extract_mapping_value_type(tid);
1072 }
1073 None
1074 }
1075 AccessKind::Plain => {
1076 cache.name_to_type.get(member_name).cloned()
1078 }
1079 }
1080}
1081
1082pub fn get_dot_completions(cache: &CompletionCache, identifier: &str) -> Vec<CompletionItem> {
1084 if let Some(items) = magic_members(identifier) {
1086 return items;
1087 }
1088
1089 let type_id = resolve_name_to_type_id(cache, identifier);
1091
1092 if let Some(tid) = type_id {
1093 return completions_for_type(cache, &tid);
1094 }
1095
1096 vec![]
1097}
1098
1099pub fn get_chain_completions(cache: &CompletionCache, chain: &[DotSegment]) -> Vec<CompletionItem> {
1102 if chain.is_empty() {
1103 return vec![];
1104 }
1105
1106 if chain.len() == 1 {
1108 let seg = &chain[0];
1109
1110 match seg.kind {
1112 AccessKind::Plain => {
1113 return get_dot_completions(cache, &seg.name);
1114 }
1115 AccessKind::Call => {
1116 if let Some(type_id) = resolve_name_to_type_id(cache, &seg.name) {
1119 return completions_for_type(cache, &type_id);
1120 }
1121 for ((_, fn_name), ret_type) in &cache.function_return_types {
1123 if fn_name == &seg.name {
1124 return completions_for_type(cache, ret_type);
1125 }
1126 }
1127 return vec![];
1128 }
1129 AccessKind::Index => {
1130 if let Some(tid) = cache.name_to_type.get(&seg.name)
1132 && tid.starts_with("t_mapping")
1133 && let Some(val_type) = extract_mapping_value_type(tid)
1134 {
1135 return completions_for_type(cache, &val_type);
1136 }
1137 return vec![];
1138 }
1139 }
1140 }
1141
1142 let first = &chain[0];
1145 let mut current_type = match first.kind {
1146 AccessKind::Plain => resolve_name_to_type_id(cache, &first.name),
1147 AccessKind::Call => {
1148 resolve_name_to_type_id(cache, &first.name).or_else(|| {
1150 cache
1151 .function_return_types
1152 .iter()
1153 .find(|((_, fn_name), _)| fn_name == &first.name)
1154 .map(|(_, ret_type)| ret_type.clone())
1155 })
1156 }
1157 AccessKind::Index => {
1158 cache.name_to_type.get(&first.name).and_then(|tid| {
1160 if tid.starts_with("t_mapping") {
1161 extract_mapping_value_type(tid)
1162 } else {
1163 Some(tid.clone())
1164 }
1165 })
1166 }
1167 };
1168
1169 for seg in &chain[1..] {
1171 let ctx_type = match ¤t_type {
1172 Some(t) => t.clone(),
1173 None => return vec![],
1174 };
1175
1176 current_type = resolve_member_type(cache, &ctx_type, &seg.name, &seg.kind);
1177 }
1178
1179 match current_type {
1181 Some(tid) => completions_for_type(cache, &tid),
1182 None => vec![],
1183 }
1184}
1185
1186pub fn get_static_completions() -> Vec<CompletionItem> {
1189 let mut items = Vec::new();
1190
1191 for kw in SOLIDITY_KEYWORDS {
1193 items.push(CompletionItem {
1194 label: kw.to_string(),
1195 kind: Some(CompletionItemKind::KEYWORD),
1196 ..Default::default()
1197 });
1198 }
1199
1200 for (name, detail) in MAGIC_GLOBALS {
1202 items.push(CompletionItem {
1203 label: name.to_string(),
1204 kind: Some(CompletionItemKind::VARIABLE),
1205 detail: Some(detail.to_string()),
1206 ..Default::default()
1207 });
1208 }
1209
1210 for (name, detail) in GLOBAL_FUNCTIONS {
1212 items.push(CompletionItem {
1213 label: name.to_string(),
1214 kind: Some(CompletionItemKind::FUNCTION),
1215 detail: Some(detail.to_string()),
1216 ..Default::default()
1217 });
1218 }
1219
1220 for (name, detail) in ETHER_UNITS {
1222 items.push(CompletionItem {
1223 label: name.to_string(),
1224 kind: Some(CompletionItemKind::UNIT),
1225 detail: Some(detail.to_string()),
1226 ..Default::default()
1227 });
1228 }
1229
1230 for (name, detail) in TIME_UNITS {
1232 items.push(CompletionItem {
1233 label: name.to_string(),
1234 kind: Some(CompletionItemKind::UNIT),
1235 detail: Some(detail.to_string()),
1236 ..Default::default()
1237 });
1238 }
1239
1240 items
1241}
1242
1243pub fn get_general_completions(cache: &CompletionCache) -> Vec<CompletionItem> {
1245 let mut items = cache.names.clone();
1246 items.extend(get_static_completions());
1247 items
1248}
1249
1250pub fn handle_completion(
1261 cache: Option<&CompletionCache>,
1262 source_text: &str,
1263 position: Position,
1264 trigger_char: Option<&str>,
1265 fast: bool,
1266) -> Option<CompletionResponse> {
1267 let lines: Vec<&str> = source_text.lines().collect();
1268 let line = lines.get(position.line as usize)?;
1269
1270 let abs_byte =
1272 crate::utils::position_to_byte_offset(source_text, position.line, position.character);
1273 let line_start_byte: usize = source_text[..abs_byte]
1274 .rfind('\n')
1275 .map(|i| i + 1)
1276 .unwrap_or(0);
1277 let col_byte = (abs_byte - line_start_byte) as u32;
1278
1279 let items = if trigger_char == Some(".") {
1280 let chain = parse_dot_chain(line, col_byte);
1281 if chain.is_empty() {
1282 return None;
1283 }
1284 match cache {
1285 Some(c) => get_chain_completions(c, &chain),
1286 None => {
1287 if chain.len() == 1 && chain[0].kind == AccessKind::Plain {
1289 magic_members(&chain[0].name).unwrap_or_default()
1290 } else {
1291 vec![]
1292 }
1293 }
1294 }
1295 } else {
1296 match cache {
1297 Some(c) if fast => c.general_completions.clone(),
1298 Some(c) => get_general_completions(c),
1299 None => get_static_completions(),
1300 }
1301 };
1302
1303 Some(CompletionResponse::List(CompletionList {
1304 is_incomplete: cache.is_none(),
1305 items,
1306 }))
1307}
1308
1309const SOLIDITY_KEYWORDS: &[&str] = &[
1310 "abstract",
1311 "address",
1312 "assembly",
1313 "bool",
1314 "break",
1315 "bytes",
1316 "bytes1",
1317 "bytes4",
1318 "bytes32",
1319 "calldata",
1320 "constant",
1321 "constructor",
1322 "continue",
1323 "contract",
1324 "delete",
1325 "do",
1326 "else",
1327 "emit",
1328 "enum",
1329 "error",
1330 "event",
1331 "external",
1332 "fallback",
1333 "false",
1334 "for",
1335 "function",
1336 "if",
1337 "immutable",
1338 "import",
1339 "indexed",
1340 "int8",
1341 "int24",
1342 "int128",
1343 "int256",
1344 "interface",
1345 "internal",
1346 "library",
1347 "mapping",
1348 "memory",
1349 "modifier",
1350 "new",
1351 "override",
1352 "payable",
1353 "pragma",
1354 "private",
1355 "public",
1356 "pure",
1357 "receive",
1358 "return",
1359 "returns",
1360 "revert",
1361 "storage",
1362 "string",
1363 "struct",
1364 "true",
1365 "type",
1366 "uint8",
1367 "uint24",
1368 "uint128",
1369 "uint160",
1370 "uint256",
1371 "unchecked",
1372 "using",
1373 "view",
1374 "virtual",
1375 "while",
1376];
1377
1378const ETHER_UNITS: &[(&str, &str)] = &[("wei", "1"), ("gwei", "1e9"), ("ether", "1e18")];
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];