1use serde_json::Value;
2use std::collections::HashMap;
3use tower_lsp::lsp_types::{
4 CompletionItem, CompletionItemKind, CompletionList, CompletionResponse, Position,
5};
6
7use crate::goto::CHILD_KEYS;
8
9#[derive(Debug, Clone)]
11pub struct ScopedDeclaration {
12 pub name: String,
14 pub type_id: String,
16}
17
18#[derive(Debug, Clone)]
20pub struct ScopeRange {
21 pub node_id: u64,
23 pub start: usize,
25 pub end: usize,
27 pub file_id: u64,
29}
30
31pub struct CompletionCache {
33 pub names: Vec<CompletionItem>,
35
36 pub name_to_type: HashMap<String, String>,
38
39 pub node_members: HashMap<u64, Vec<CompletionItem>>,
41
42 pub type_to_node: HashMap<String, u64>,
44
45 pub name_to_node_id: HashMap<String, u64>,
47
48 pub method_identifiers: HashMap<u64, Vec<CompletionItem>>,
51
52 pub function_return_types: HashMap<(u64, String), String>,
55
56 pub using_for: HashMap<String, Vec<CompletionItem>>,
59
60 pub using_for_wildcard: Vec<CompletionItem>,
62
63 pub general_completions: Vec<CompletionItem>,
66
67 pub scope_declarations: HashMap<u64, Vec<ScopedDeclaration>>,
71
72 pub scope_parent: HashMap<u64, u64>,
75
76 pub scope_ranges: Vec<ScopeRange>,
79
80 pub path_to_file_id: HashMap<String, u64>,
83
84 pub linearized_base_contracts: HashMap<u64, Vec<u64>>,
88}
89
90fn push_if_node_or_array<'a>(tree: &'a Value, key: &str, stack: &mut Vec<&'a Value>) {
91 if let Some(value) = tree.get(key) {
92 match value {
93 Value::Array(arr) => stack.extend(arr),
94 Value::Object(_) => stack.push(value),
95 _ => {}
96 }
97 }
98}
99
100fn node_type_to_completion_kind(node_type: &str) -> CompletionItemKind {
102 match node_type {
103 "FunctionDefinition" => CompletionItemKind::FUNCTION,
104 "VariableDeclaration" => CompletionItemKind::VARIABLE,
105 "ContractDefinition" => CompletionItemKind::CLASS,
106 "StructDefinition" => CompletionItemKind::STRUCT,
107 "EnumDefinition" => CompletionItemKind::ENUM,
108 "EnumValue" => CompletionItemKind::ENUM_MEMBER,
109 "EventDefinition" => CompletionItemKind::EVENT,
110 "ErrorDefinition" => CompletionItemKind::EVENT,
111 "ModifierDefinition" => CompletionItemKind::METHOD,
112 "ImportDirective" => CompletionItemKind::MODULE,
113 _ => CompletionItemKind::TEXT,
114 }
115}
116
117fn parse_src(node: &Value) -> Option<(usize, usize, u64)> {
120 let src = node.get("src").and_then(|v| v.as_str())?;
121 let mut parts = src.split(':');
122 let offset = parts.next()?.parse::<usize>().ok()?;
123 let length = parts.next()?.parse::<usize>().ok()?;
124 let file_id = parts.next()?.parse::<u64>().ok()?;
125 Some((offset, length, file_id))
126}
127
128pub fn extract_node_id_from_type(type_id: &str) -> Option<u64> {
133 let mut last_id = None;
136 let mut i = 0;
137 let bytes = type_id.as_bytes();
138 while i < bytes.len() {
139 if i + 1 < bytes.len() && bytes[i] == b'_' && bytes[i + 1] == b'$' {
140 i += 2;
141 let start = i;
142 while i < bytes.len() && bytes[i].is_ascii_digit() {
143 i += 1;
144 }
145 if i > start
146 && let Ok(id) = type_id[start..i].parse::<u64>()
147 {
148 last_id = Some(id);
149 }
150 } else {
151 i += 1;
152 }
153 }
154 last_id
155}
156
157fn build_function_signature(node: &Value) -> Option<String> {
160 let name = node.get("name").and_then(|v| v.as_str()).unwrap_or("");
161 if name.is_empty() {
162 return None;
163 }
164
165 let params = node
166 .get("parameters")
167 .and_then(|p| p.get("parameters"))
168 .and_then(|v| v.as_array());
169
170 let mut sig = String::new();
171 sig.push_str(name);
172 sig.push('(');
173
174 if let Some(params) = params {
175 for (i, param) in params.iter().enumerate() {
176 if i > 0 {
177 sig.push_str(", ");
178 }
179 let type_str = param
180 .get("typeDescriptions")
181 .and_then(|td| td.get("typeString"))
182 .and_then(|v| v.as_str())
183 .unwrap_or("?");
184 let clean_type = type_str
186 .strip_prefix("struct ")
187 .or_else(|| type_str.strip_prefix("contract "))
188 .or_else(|| type_str.strip_prefix("enum "))
189 .unwrap_or(type_str);
190 let param_name = param.get("name").and_then(|v| v.as_str()).unwrap_or("");
191 sig.push_str(clean_type);
192 if !param_name.is_empty() {
193 sig.push(' ');
194 sig.push_str(param_name);
195 }
196 }
197 }
198 sig.push(')');
199
200 let returns = node
202 .get("returnParameters")
203 .and_then(|p| p.get("parameters"))
204 .and_then(|v| v.as_array());
205
206 if let Some(returns) = returns
207 && !returns.is_empty()
208 {
209 sig.push_str(" returns (");
210 for (i, ret) in returns.iter().enumerate() {
211 if i > 0 {
212 sig.push_str(", ");
213 }
214 let type_str = ret
215 .get("typeDescriptions")
216 .and_then(|td| td.get("typeString"))
217 .and_then(|v| v.as_str())
218 .unwrap_or("?");
219 let clean_type = type_str
220 .strip_prefix("struct ")
221 .or_else(|| type_str.strip_prefix("contract "))
222 .or_else(|| type_str.strip_prefix("enum "))
223 .unwrap_or(type_str);
224 let ret_name = ret.get("name").and_then(|v| v.as_str()).unwrap_or("");
225 sig.push_str(clean_type);
226 if !ret_name.is_empty() {
227 sig.push(' ');
228 sig.push_str(ret_name);
229 }
230 }
231 sig.push(')');
232 }
233
234 Some(sig)
235}
236
237pub fn extract_mapping_value_type(type_id: &str) -> Option<String> {
244 let mut current = type_id;
245
246 loop {
247 if !current.starts_with("t_mapping$_") {
248 let result = current.trim_end_matches("_$");
251 return if result.is_empty() {
252 None
253 } else {
254 Some(result.to_string())
255 };
256 }
257
258 let inner = ¤t["t_mapping$_".len()..];
260
261 let mut depth = 0i32;
265 let bytes = inner.as_bytes();
266 let mut split_pos = None;
267
268 let mut i = 0;
269 while i < bytes.len() {
270 if i + 1 < bytes.len() && bytes[i] == b'$' && bytes[i + 1] == b'_' {
271 depth += 1;
272 i += 2;
273 } else if i + 2 < bytes.len()
274 && bytes[i] == b'_'
275 && bytes[i + 1] == b'$'
276 && bytes[i + 2] == b'_'
277 && depth == 0
278 {
279 split_pos = Some(i);
281 break;
282 } else if i + 1 < bytes.len() && bytes[i] == b'_' && bytes[i + 1] == b'$' {
283 depth -= 1;
284 i += 2;
285 } else {
286 i += 1;
287 }
288 }
289
290 if let Some(pos) = split_pos {
291 current = &inner[pos + 3..];
293 } else {
294 return None;
295 }
296 }
297}
298
299fn count_abi_params(signature: &str) -> usize {
302 let start = match signature.find('(') {
304 Some(i) => i + 1,
305 None => return 0,
306 };
307 let bytes = signature.as_bytes();
308 if start >= bytes.len() {
309 return 0;
310 }
311 if bytes[start] == b')' {
313 return 0;
314 }
315 let mut count = 1; let mut depth = 0;
317 for &b in &bytes[start..] {
318 match b {
319 b'(' => depth += 1,
320 b')' => {
321 if depth == 0 {
322 break;
323 }
324 depth -= 1;
325 }
326 b',' if depth == 0 => count += 1,
327 _ => {}
328 }
329 }
330 count
331}
332
333fn count_signature_params(sig: &str) -> usize {
335 count_abi_params(sig)
336}
337
338pub fn build_completion_cache(sources: &Value, contracts: Option<&Value>) -> CompletionCache {
341 let mut names: Vec<CompletionItem> = Vec::new();
342 let mut seen_names: HashMap<String, usize> = HashMap::new(); let mut name_to_type: HashMap<String, String> = HashMap::new();
344 let mut node_members: HashMap<u64, Vec<CompletionItem>> = HashMap::new();
345 let mut type_to_node: HashMap<String, u64> = HashMap::new();
346 let mut method_identifiers: HashMap<u64, Vec<CompletionItem>> = HashMap::new();
347 let mut name_to_node_id: HashMap<String, u64> = HashMap::new();
348
349 let mut contract_locations: Vec<(String, String, u64)> = Vec::new();
351
352 let mut function_signatures: HashMap<u64, HashMap<String, Vec<String>>> = HashMap::new();
354
355 let mut function_return_types: HashMap<(u64, String), String> = HashMap::new();
357
358 let mut using_for: HashMap<String, Vec<CompletionItem>> = HashMap::new();
360 let mut using_for_wildcard: Vec<CompletionItem> = Vec::new();
361
362 let mut using_for_directives: Vec<(u64, Option<String>)> = Vec::new();
364
365 let mut scope_declarations: HashMap<u64, Vec<ScopedDeclaration>> = HashMap::new();
367 let mut scope_parent: HashMap<u64, u64> = HashMap::new();
368 let mut scope_ranges: Vec<ScopeRange> = Vec::new();
369 let mut path_to_file_id: HashMap<String, u64> = HashMap::new();
370 let mut linearized_base_contracts: HashMap<u64, Vec<u64>> = HashMap::new();
371
372 if let Some(sources_obj) = sources.as_object() {
373 for (path, contents) in sources_obj {
374 if let Some(contents_array) = contents.as_array()
375 && let Some(first_content) = contents_array.first()
376 && let Some(source_file) = first_content.get("source_file")
377 && let Some(ast) = source_file.get("ast")
378 {
379 if let Some(fid) = source_file.get("id").and_then(|v| v.as_u64()) {
381 path_to_file_id.insert(path.clone(), fid);
382 }
383 let mut stack: Vec<&Value> = vec![ast];
384
385 while let Some(tree) = stack.pop() {
386 let node_type = tree.get("nodeType").and_then(|v| v.as_str()).unwrap_or("");
387 let name = tree.get("name").and_then(|v| v.as_str()).unwrap_or("");
388 let node_id = tree.get("id").and_then(|v| v.as_u64());
389
390 let is_scope_node = matches!(
395 node_type,
396 "SourceUnit"
397 | "ContractDefinition"
398 | "FunctionDefinition"
399 | "ModifierDefinition"
400 | "Block"
401 | "UncheckedBlock"
402 );
403 if is_scope_node {
404 if let Some(nid) = node_id {
405 if let Some((start, len, file_id)) = parse_src(tree) {
406 scope_ranges.push(ScopeRange {
407 node_id: nid,
408 start,
409 end: start + len,
410 file_id,
411 });
412 }
413 if let Some(parent_id) = tree.get("scope").and_then(|v| v.as_u64()) {
415 scope_parent.insert(nid, parent_id);
416 }
417 }
418 }
419
420 if node_type == "ContractDefinition" {
422 if let Some(nid) = node_id {
423 if let Some(bases) = tree
424 .get("linearizedBaseContracts")
425 .and_then(|v| v.as_array())
426 {
427 let base_ids: Vec<u64> =
428 bases.iter().filter_map(|b| b.as_u64()).collect();
429 if !base_ids.is_empty() {
430 linearized_base_contracts.insert(nid, base_ids);
431 }
432 }
433 }
434 }
435
436 if node_type == "VariableDeclaration" && !name.is_empty() {
438 if let Some(scope_id) = tree.get("scope").and_then(|v| v.as_u64()) {
439 if let Some(tid) = tree
440 .get("typeDescriptions")
441 .and_then(|td| td.get("typeIdentifier"))
442 .and_then(|v| v.as_str())
443 {
444 scope_declarations.entry(scope_id).or_default().push(
445 ScopedDeclaration {
446 name: name.to_string(),
447 type_id: tid.to_string(),
448 },
449 );
450 }
451 }
452 }
453
454 if node_type == "FunctionDefinition" && !name.is_empty() {
456 if let Some(scope_id) = tree.get("scope").and_then(|v| v.as_u64()) {
457 if let Some(tid) = tree
458 .get("typeDescriptions")
459 .and_then(|td| td.get("typeIdentifier"))
460 .and_then(|v| v.as_str())
461 {
462 scope_declarations.entry(scope_id).or_default().push(
463 ScopedDeclaration {
464 name: name.to_string(),
465 type_id: tid.to_string(),
466 },
467 );
468 }
469 }
470 }
471
472 if !name.is_empty() && !seen_names.contains_key(name) {
474 let type_string = tree
475 .get("typeDescriptions")
476 .and_then(|td| td.get("typeString"))
477 .and_then(|v| v.as_str())
478 .map(|s| s.to_string());
479
480 let type_id = tree
481 .get("typeDescriptions")
482 .and_then(|td| td.get("typeIdentifier"))
483 .and_then(|v| v.as_str());
484
485 let kind = node_type_to_completion_kind(node_type);
486
487 let item = CompletionItem {
488 label: name.to_string(),
489 kind: Some(kind),
490 detail: type_string,
491 ..Default::default()
492 };
493
494 let idx = names.len();
495 names.push(item);
496 seen_names.insert(name.to_string(), idx);
497
498 if let Some(tid) = type_id {
500 name_to_type.insert(name.to_string(), tid.to_string());
501 }
502 }
503
504 if node_type == "StructDefinition"
506 && let Some(id) = node_id
507 {
508 let mut members = Vec::new();
509 if let Some(member_array) = tree.get("members").and_then(|v| v.as_array()) {
510 for member in member_array {
511 let member_name =
512 member.get("name").and_then(|v| v.as_str()).unwrap_or("");
513 if member_name.is_empty() {
514 continue;
515 }
516 let member_type = member
517 .get("typeDescriptions")
518 .and_then(|td| td.get("typeString"))
519 .and_then(|v| v.as_str())
520 .map(|s| s.to_string());
521
522 members.push(CompletionItem {
523 label: member_name.to_string(),
524 kind: Some(CompletionItemKind::FIELD),
525 detail: member_type,
526 ..Default::default()
527 });
528 }
529 }
530 if !members.is_empty() {
531 node_members.insert(id, members);
532 }
533
534 if let Some(tid) = tree
536 .get("typeDescriptions")
537 .and_then(|td| td.get("typeIdentifier"))
538 .and_then(|v| v.as_str())
539 {
540 type_to_node.insert(tid.to_string(), id);
541 }
542 }
543
544 if node_type == "ContractDefinition"
546 && let Some(id) = node_id
547 {
548 let mut members = Vec::new();
549 let mut fn_sigs: HashMap<String, Vec<String>> = HashMap::new();
550 if let Some(nodes_array) = tree.get("nodes").and_then(|v| v.as_array()) {
551 for member in nodes_array {
552 let member_type = member
553 .get("nodeType")
554 .and_then(|v| v.as_str())
555 .unwrap_or("");
556 let member_name =
557 member.get("name").and_then(|v| v.as_str()).unwrap_or("");
558 if member_name.is_empty() {
559 continue;
560 }
561
562 let (member_detail, label_details) =
564 if member_type == "FunctionDefinition" {
565 if let Some(ret_params) = member
569 .get("returnParameters")
570 .and_then(|rp| rp.get("parameters"))
571 .and_then(|v| v.as_array())
572 && ret_params.len() == 1
573 && let Some(ret_tid) = ret_params[0]
574 .get("typeDescriptions")
575 .and_then(|td| td.get("typeIdentifier"))
576 .and_then(|v| v.as_str())
577 {
578 function_return_types.insert(
579 (id, member_name.to_string()),
580 ret_tid.to_string(),
581 );
582 }
583
584 if let Some(sig) = build_function_signature(member) {
585 fn_sigs
586 .entry(member_name.to_string())
587 .or_default()
588 .push(sig.clone());
589 (Some(sig), None)
590 } else {
591 (
592 member
593 .get("typeDescriptions")
594 .and_then(|td| td.get("typeString"))
595 .and_then(|v| v.as_str())
596 .map(|s| s.to_string()),
597 None,
598 )
599 }
600 } else {
601 (
602 member
603 .get("typeDescriptions")
604 .and_then(|td| td.get("typeString"))
605 .and_then(|v| v.as_str())
606 .map(|s| s.to_string()),
607 None,
608 )
609 };
610
611 let kind = node_type_to_completion_kind(member_type);
612 members.push(CompletionItem {
613 label: member_name.to_string(),
614 kind: Some(kind),
615 detail: member_detail,
616 label_details,
617 ..Default::default()
618 });
619 }
620 }
621 if !members.is_empty() {
622 node_members.insert(id, members);
623 }
624 if !fn_sigs.is_empty() {
625 function_signatures.insert(id, fn_sigs);
626 }
627
628 if let Some(tid) = tree
629 .get("typeDescriptions")
630 .and_then(|td| td.get("typeIdentifier"))
631 .and_then(|v| v.as_str())
632 {
633 type_to_node.insert(tid.to_string(), id);
634 }
635
636 if !name.is_empty() {
638 contract_locations.push((path.clone(), name.to_string(), id));
639 name_to_node_id.insert(name.to_string(), id);
640 }
641 }
642
643 if node_type == "EnumDefinition"
645 && let Some(id) = node_id
646 {
647 let mut members = Vec::new();
648 if let Some(member_array) = tree.get("members").and_then(|v| v.as_array()) {
649 for member in member_array {
650 let member_name =
651 member.get("name").and_then(|v| v.as_str()).unwrap_or("");
652 if member_name.is_empty() {
653 continue;
654 }
655 members.push(CompletionItem {
656 label: member_name.to_string(),
657 kind: Some(CompletionItemKind::ENUM_MEMBER),
658 detail: None,
659 ..Default::default()
660 });
661 }
662 }
663 if !members.is_empty() {
664 node_members.insert(id, members);
665 }
666
667 if let Some(tid) = tree
668 .get("typeDescriptions")
669 .and_then(|td| td.get("typeIdentifier"))
670 .and_then(|v| v.as_str())
671 {
672 type_to_node.insert(tid.to_string(), id);
673 }
674 }
675
676 if node_type == "UsingForDirective" {
678 let target_type = tree.get("typeName").and_then(|tn| {
680 tn.get("typeDescriptions")
681 .and_then(|td| td.get("typeIdentifier"))
682 .and_then(|v| v.as_str())
683 .map(|s| s.to_string())
684 });
685
686 if let Some(lib) = tree.get("libraryName") {
688 if let Some(lib_id) =
689 lib.get("referencedDeclaration").and_then(|v| v.as_u64())
690 {
691 using_for_directives.push((lib_id, target_type));
692 }
693 }
694 else if let Some(func_list) =
698 tree.get("functionList").and_then(|v| v.as_array())
699 {
700 for entry in func_list {
701 if entry.get("operator").is_some() {
703 continue;
704 }
705 if let Some(def) = entry.get("definition") {
706 let fn_name =
707 def.get("name").and_then(|v| v.as_str()).unwrap_or("");
708 if !fn_name.is_empty() {
709 let items = if let Some(ref tid) = target_type {
710 using_for.entry(tid.clone()).or_default()
711 } else {
712 &mut using_for_wildcard
713 };
714 items.push(CompletionItem {
715 label: fn_name.to_string(),
716 kind: Some(CompletionItemKind::FUNCTION),
717 detail: None,
718 ..Default::default()
719 });
720 }
721 }
722 }
723 }
724 }
725
726 for key in CHILD_KEYS {
728 push_if_node_or_array(tree, key, &mut stack);
729 }
730 }
731 }
732 }
733 }
734
735 for (lib_id, target_type) in &using_for_directives {
738 if let Some(lib_members) = node_members.get(lib_id) {
739 let items: Vec<CompletionItem> = lib_members
740 .iter()
741 .filter(|item| item.kind == Some(CompletionItemKind::FUNCTION))
742 .cloned()
743 .collect();
744 if !items.is_empty() {
745 if let Some(tid) = target_type {
746 using_for.entry(tid.clone()).or_default().extend(items);
747 } else {
748 using_for_wildcard.extend(items);
749 }
750 }
751 }
752 }
753
754 if let Some(contracts_val) = contracts
756 && let Some(contracts_obj) = contracts_val.as_object()
757 {
758 for (path, contract_name, node_id) in &contract_locations {
759 let fn_sigs = function_signatures.get(node_id);
761
762 if let Some(path_entry) = contracts_obj.get(path)
763 && let Some(contract_entry) = path_entry.get(contract_name)
764 && let Some(first) = contract_entry.get(0)
765 && let Some(evm) = first.get("contract").and_then(|c| c.get("evm"))
766 && let Some(methods) = evm.get("methodIdentifiers")
767 && let Some(methods_obj) = methods.as_object()
768 {
769 let mut items: Vec<CompletionItem> = Vec::new();
770 for (signature, selector) in methods_obj {
771 let fn_name = signature.split('(').next().unwrap_or(signature).to_string();
774 let selector_str = selector
775 .as_str()
776 .map(|s| format!("0x{}", s))
777 .unwrap_or_default();
778
779 let description =
781 fn_sigs
782 .and_then(|sigs| sigs.get(&fn_name))
783 .and_then(|sig_list| {
784 if sig_list.len() == 1 {
785 Some(sig_list[0].clone())
787 } else {
788 let abi_param_count = count_abi_params(signature);
790 sig_list
791 .iter()
792 .find(|s| count_signature_params(s) == abi_param_count)
793 .cloned()
794 }
795 });
796
797 items.push(CompletionItem {
798 label: fn_name,
799 kind: Some(CompletionItemKind::FUNCTION),
800 detail: Some(signature.clone()),
801 label_details: Some(tower_lsp::lsp_types::CompletionItemLabelDetails {
802 detail: Some(selector_str),
803 description,
804 }),
805 ..Default::default()
806 });
807 }
808 if !items.is_empty() {
809 method_identifiers.insert(*node_id, items);
810 }
811 }
812 }
813 }
814
815 let mut general_completions = names.clone();
817 general_completions.extend(get_static_completions());
818
819 scope_ranges.sort_by_key(|r| r.end - r.start);
821
822 let orphan_ids: Vec<u64> = scope_ranges
828 .iter()
829 .filter(|r| !scope_parent.contains_key(&r.node_id))
830 .map(|r| r.node_id)
831 .collect();
832 let range_by_id: HashMap<u64, (usize, usize, u64)> = scope_ranges
834 .iter()
835 .map(|r| (r.node_id, (r.start, r.end, r.file_id)))
836 .collect();
837 for orphan_id in &orphan_ids {
838 if let Some(&(start, end, file_id)) = range_by_id.get(orphan_id) {
839 let parent = scope_ranges
842 .iter()
843 .find(|r| {
844 r.node_id != *orphan_id
845 && r.file_id == file_id
846 && r.start <= start
847 && r.end >= end
848 && (r.end - r.start) > (end - start)
849 })
850 .map(|r| r.node_id);
851 if let Some(parent_id) = parent {
852 scope_parent.insert(*orphan_id, parent_id);
853 }
854 }
855 }
856
857 CompletionCache {
858 names,
859 name_to_type,
860 node_members,
861 type_to_node,
862 name_to_node_id,
863 method_identifiers,
864 function_return_types,
865 using_for,
866 using_for_wildcard,
867 general_completions,
868 scope_declarations,
869 scope_parent,
870 scope_ranges,
871 path_to_file_id,
872 linearized_base_contracts,
873 }
874}
875
876fn magic_members(name: &str) -> Option<Vec<CompletionItem>> {
878 let items = match name {
879 "msg" => vec![
880 ("data", "bytes calldata"),
881 ("sender", "address"),
882 ("sig", "bytes4"),
883 ("value", "uint256"),
884 ],
885 "block" => vec![
886 ("basefee", "uint256"),
887 ("blobbasefee", "uint256"),
888 ("chainid", "uint256"),
889 ("coinbase", "address payable"),
890 ("difficulty", "uint256"),
891 ("gaslimit", "uint256"),
892 ("number", "uint256"),
893 ("prevrandao", "uint256"),
894 ("timestamp", "uint256"),
895 ],
896 "tx" => vec![("gasprice", "uint256"), ("origin", "address")],
897 "abi" => vec![
898 ("decode(bytes memory, (...))", "..."),
899 ("encode(...)", "bytes memory"),
900 ("encodePacked(...)", "bytes memory"),
901 ("encodeWithSelector(bytes4, ...)", "bytes memory"),
902 ("encodeWithSignature(string memory, ...)", "bytes memory"),
903 ("encodeCall(function, (...))", "bytes memory"),
904 ],
905 "type" => vec![
908 ("name", "string"),
909 ("creationCode", "bytes memory"),
910 ("runtimeCode", "bytes memory"),
911 ("interfaceId", "bytes4"),
912 ("min", "T"),
913 ("max", "T"),
914 ],
915 "bytes" => vec![("concat(...)", "bytes memory")],
917 "string" => vec![("concat(...)", "string memory")],
918 _ => return None,
919 };
920
921 Some(
922 items
923 .into_iter()
924 .map(|(label, detail)| CompletionItem {
925 label: label.to_string(),
926 kind: Some(CompletionItemKind::PROPERTY),
927 detail: Some(detail.to_string()),
928 ..Default::default()
929 })
930 .collect(),
931 )
932}
933
934fn address_members() -> Vec<CompletionItem> {
936 [
937 ("balance", "uint256", CompletionItemKind::PROPERTY),
938 ("code", "bytes memory", CompletionItemKind::PROPERTY),
939 ("codehash", "bytes32", CompletionItemKind::PROPERTY),
940 ("transfer(uint256)", "", CompletionItemKind::FUNCTION),
941 ("send(uint256)", "bool", CompletionItemKind::FUNCTION),
942 (
943 "call(bytes memory)",
944 "(bool, bytes memory)",
945 CompletionItemKind::FUNCTION,
946 ),
947 (
948 "delegatecall(bytes memory)",
949 "(bool, bytes memory)",
950 CompletionItemKind::FUNCTION,
951 ),
952 (
953 "staticcall(bytes memory)",
954 "(bool, bytes memory)",
955 CompletionItemKind::FUNCTION,
956 ),
957 ]
958 .iter()
959 .map(|(label, detail, kind)| CompletionItem {
960 label: label.to_string(),
961 kind: Some(*kind),
962 detail: if detail.is_empty() {
963 None
964 } else {
965 Some(detail.to_string())
966 },
967 ..Default::default()
968 })
969 .collect()
970}
971
972#[derive(Debug, Clone, PartialEq)]
974pub enum AccessKind {
975 Plain,
977 Call,
979 Index,
981}
982
983#[derive(Debug, Clone, PartialEq)]
985pub struct DotSegment {
986 pub name: String,
987 pub kind: AccessKind,
988}
989
990fn skip_brackets_backwards(bytes: &[u8], pos: usize) -> usize {
994 let close = bytes[pos];
995 let open = match close {
996 b')' => b'(',
997 b']' => b'[',
998 _ => return pos,
999 };
1000 let mut depth = 1u32;
1001 let mut i = pos;
1002 while i > 0 && depth > 0 {
1003 i -= 1;
1004 if bytes[i] == close {
1005 depth += 1;
1006 } else if bytes[i] == open {
1007 depth -= 1;
1008 }
1009 }
1010 i
1011}
1012
1013pub fn parse_dot_chain(line: &str, character: u32) -> Vec<DotSegment> {
1018 let col = character as usize;
1019 if col == 0 {
1020 return vec![];
1021 }
1022
1023 let bytes = line.as_bytes();
1024 let mut segments: Vec<DotSegment> = Vec::new();
1025
1026 let mut pos = col;
1028 if pos > 0 && pos <= bytes.len() && bytes[pos - 1] == b'.' {
1029 pos -= 1;
1030 }
1031
1032 loop {
1033 if pos == 0 {
1034 break;
1035 }
1036
1037 let kind = if bytes[pos - 1] == b')' {
1039 pos -= 1; pos = skip_brackets_backwards(bytes, pos);
1041 AccessKind::Call
1042 } else if bytes[pos - 1] == b']' {
1043 pos -= 1; pos = skip_brackets_backwards(bytes, pos);
1045 AccessKind::Index
1046 } else {
1047 AccessKind::Plain
1048 };
1049
1050 let end = pos;
1052 while pos > 0 && (bytes[pos - 1].is_ascii_alphanumeric() || bytes[pos - 1] == b'_') {
1053 pos -= 1;
1054 }
1055
1056 if pos == end {
1057 break;
1059 }
1060
1061 let name = String::from_utf8_lossy(&bytes[pos..end]).to_string();
1062 segments.push(DotSegment { name, kind });
1063
1064 if pos > 0 && bytes[pos - 1] == b'.' {
1066 pos -= 1; } else {
1068 break;
1069 }
1070 }
1071
1072 segments.reverse(); segments
1074}
1075
1076pub fn extract_identifier_before_dot(line: &str, character: u32) -> Option<String> {
1079 let segments = parse_dot_chain(line, character);
1080 segments.last().map(|s| s.name.clone())
1081}
1082
1083#[doc = r"Strip all storage/memory location suffixes from a typeIdentifier to get the base type.
1084Solidity AST uses different suffixes in different contexts:
1085 - `t_struct$_State_$4809_storage_ptr` (UsingForDirective typeName)
1086 - `t_struct$_State_$4809_storage` (mapping value type after extraction)
1087 - `t_struct$_PoolKey_$8887_memory_ptr` (function parameter)
1088All refer to the same logical type. This strips `_ptr` and `_storage`/`_memory`/`_calldata`."]
1089fn strip_type_suffix(type_id: &str) -> &str {
1090 let s = type_id.strip_suffix("_ptr").unwrap_or(type_id);
1091 s.strip_suffix("_storage")
1092 .or_else(|| s.strip_suffix("_memory"))
1093 .or_else(|| s.strip_suffix("_calldata"))
1094 .unwrap_or(s)
1095}
1096
1097fn lookup_using_for(cache: &CompletionCache, type_id: &str) -> Vec<CompletionItem> {
1101 if let Some(items) = cache.using_for.get(type_id) {
1103 return items.clone();
1104 }
1105
1106 let base = strip_type_suffix(type_id);
1108 let variants = [
1109 base.to_string(),
1110 format!("{}_storage", base),
1111 format!("{}_storage_ptr", base),
1112 format!("{}_memory", base),
1113 format!("{}_memory_ptr", base),
1114 format!("{}_calldata", base),
1115 ];
1116 for variant in &variants {
1117 if variant.as_str() != type_id
1118 && let Some(items) = cache.using_for.get(variant.as_str())
1119 {
1120 return items.clone();
1121 }
1122 }
1123
1124 vec![]
1125}
1126
1127fn completions_for_type(cache: &CompletionCache, type_id: &str) -> Vec<CompletionItem> {
1130 if type_id == "t_address" || type_id == "t_address_payable" {
1132 let mut items = address_members();
1133 if let Some(uf) = cache.using_for.get(type_id) {
1135 items.extend(uf.iter().cloned());
1136 }
1137 items.extend(cache.using_for_wildcard.iter().cloned());
1138 return items;
1139 }
1140
1141 let resolved_node_id = extract_node_id_from_type(type_id)
1142 .or_else(|| cache.type_to_node.get(type_id).copied())
1143 .or_else(|| {
1144 type_id
1146 .strip_prefix("__node_id_")
1147 .and_then(|s| s.parse::<u64>().ok())
1148 });
1149
1150 let mut items = Vec::new();
1151 let mut seen_labels: std::collections::HashSet<String> = std::collections::HashSet::new();
1152
1153 if let Some(node_id) = resolved_node_id {
1154 if let Some(method_items) = cache.method_identifiers.get(&node_id) {
1156 for item in method_items {
1157 seen_labels.insert(item.label.clone());
1158 items.push(item.clone());
1159 }
1160 }
1161
1162 if let Some(members) = cache.node_members.get(&node_id) {
1164 for item in members {
1165 if !seen_labels.contains(&item.label) {
1166 seen_labels.insert(item.label.clone());
1167 items.push(item.clone());
1168 }
1169 }
1170 }
1171 }
1172
1173 let uf_items = lookup_using_for(cache, type_id);
1176 for item in &uf_items {
1177 if !seen_labels.contains(&item.label) {
1178 seen_labels.insert(item.label.clone());
1179 items.push(item.clone());
1180 }
1181 }
1182
1183 for item in &cache.using_for_wildcard {
1185 if !seen_labels.contains(&item.label) {
1186 seen_labels.insert(item.label.clone());
1187 items.push(item.clone());
1188 }
1189 }
1190
1191 items
1192}
1193
1194fn resolve_name_to_type_id(cache: &CompletionCache, name: &str) -> Option<String> {
1196 if let Some(tid) = cache.name_to_type.get(name) {
1198 return Some(tid.clone());
1199 }
1200 if let Some(node_id) = cache.name_to_node_id.get(name) {
1202 for (tid, nid) in &cache.type_to_node {
1204 if nid == node_id {
1205 return Some(tid.clone());
1206 }
1207 }
1208 return Some(format!("__node_id_{}", node_id));
1210 }
1211 None
1212}
1213
1214pub fn find_innermost_scope(cache: &CompletionCache, byte_pos: usize, file_id: u64) -> Option<u64> {
1218 cache
1220 .scope_ranges
1221 .iter()
1222 .find(|r| r.file_id == file_id && r.start <= byte_pos && byte_pos < r.end)
1223 .map(|r| r.node_id)
1224}
1225
1226pub fn resolve_name_in_scope(
1235 cache: &CompletionCache,
1236 name: &str,
1237 byte_pos: usize,
1238 file_id: u64,
1239) -> Option<String> {
1240 let mut current_scope = find_innermost_scope(cache, byte_pos, file_id)?;
1241
1242 loop {
1244 if let Some(decls) = cache.scope_declarations.get(¤t_scope) {
1246 for decl in decls {
1247 if decl.name == name {
1248 return Some(decl.type_id.clone());
1249 }
1250 }
1251 }
1252
1253 if let Some(bases) = cache.linearized_base_contracts.get(¤t_scope) {
1257 for &base_id in bases.iter().skip(1) {
1258 if let Some(decls) = cache.scope_declarations.get(&base_id) {
1259 for decl in decls {
1260 if decl.name == name {
1261 return Some(decl.type_id.clone());
1262 }
1263 }
1264 }
1265 }
1266 }
1267
1268 match cache.scope_parent.get(¤t_scope) {
1270 Some(&parent_id) => current_scope = parent_id,
1271 None => break, }
1273 }
1274
1275 resolve_name_to_type_id(cache, name)
1278}
1279
1280fn resolve_member_type(
1285 cache: &CompletionCache,
1286 context_type_id: &str,
1287 member_name: &str,
1288 kind: &AccessKind,
1289) -> Option<String> {
1290 let resolved_node_id = extract_node_id_from_type(context_type_id)
1291 .or_else(|| cache.type_to_node.get(context_type_id).copied())
1292 .or_else(|| {
1293 context_type_id
1295 .strip_prefix("__node_id_")
1296 .and_then(|s| s.parse::<u64>().ok())
1297 });
1298
1299 let node_id = resolved_node_id?;
1300
1301 match kind {
1302 AccessKind::Call => {
1303 cache
1305 .function_return_types
1306 .get(&(node_id, member_name.to_string()))
1307 .cloned()
1308 }
1309 AccessKind::Index => {
1310 if let Some(members) = cache.node_members.get(&node_id) {
1312 for member in members {
1313 if member.label == member_name {
1314 if let Some(tid) = cache.name_to_type.get(member_name) {
1316 if tid.starts_with("t_mapping") {
1317 return extract_mapping_value_type(tid);
1318 }
1319 return Some(tid.clone());
1320 }
1321 }
1322 }
1323 }
1324 if let Some(tid) = cache.name_to_type.get(member_name)
1326 && tid.starts_with("t_mapping")
1327 {
1328 return extract_mapping_value_type(tid);
1329 }
1330 None
1331 }
1332 AccessKind::Plain => {
1333 cache.name_to_type.get(member_name).cloned()
1335 }
1336 }
1337}
1338
1339pub struct ScopeContext {
1343 pub byte_pos: usize,
1345 pub file_id: u64,
1347}
1348
1349fn resolve_name(
1353 cache: &CompletionCache,
1354 name: &str,
1355 scope_ctx: Option<&ScopeContext>,
1356) -> Option<String> {
1357 if let Some(ctx) = scope_ctx {
1358 resolve_name_in_scope(cache, name, ctx.byte_pos, ctx.file_id)
1359 } else {
1360 resolve_name_to_type_id(cache, name)
1361 }
1362}
1363
1364pub fn get_dot_completions(
1366 cache: &CompletionCache,
1367 identifier: &str,
1368 scope_ctx: Option<&ScopeContext>,
1369) -> Vec<CompletionItem> {
1370 if let Some(items) = magic_members(identifier) {
1372 return items;
1373 }
1374
1375 let type_id = resolve_name(cache, identifier, scope_ctx);
1377
1378 if let Some(tid) = type_id {
1379 return completions_for_type(cache, &tid);
1380 }
1381
1382 vec![]
1383}
1384
1385pub fn get_chain_completions(
1388 cache: &CompletionCache,
1389 chain: &[DotSegment],
1390 scope_ctx: Option<&ScopeContext>,
1391) -> Vec<CompletionItem> {
1392 if chain.is_empty() {
1393 return vec![];
1394 }
1395
1396 if chain.len() == 1 {
1398 let seg = &chain[0];
1399
1400 match seg.kind {
1402 AccessKind::Plain => {
1403 return get_dot_completions(cache, &seg.name, scope_ctx);
1404 }
1405 AccessKind::Call => {
1406 if let Some(type_id) = resolve_name(cache, &seg.name, scope_ctx) {
1409 return completions_for_type(cache, &type_id);
1410 }
1411 for ((_, fn_name), ret_type) in &cache.function_return_types {
1413 if fn_name == &seg.name {
1414 return completions_for_type(cache, ret_type);
1415 }
1416 }
1417 return vec![];
1418 }
1419 AccessKind::Index => {
1420 if let Some(tid) = resolve_name(cache, &seg.name, scope_ctx)
1422 && tid.starts_with("t_mapping")
1423 && let Some(val_type) = extract_mapping_value_type(&tid)
1424 {
1425 return completions_for_type(cache, &val_type);
1426 }
1427 return vec![];
1428 }
1429 }
1430 }
1431
1432 let first = &chain[0];
1435 let mut current_type = match first.kind {
1436 AccessKind::Plain => resolve_name(cache, &first.name, scope_ctx),
1437 AccessKind::Call => {
1438 resolve_name(cache, &first.name, scope_ctx).or_else(|| {
1440 cache
1441 .function_return_types
1442 .iter()
1443 .find(|((_, fn_name), _)| fn_name == &first.name)
1444 .map(|(_, ret_type)| ret_type.clone())
1445 })
1446 }
1447 AccessKind::Index => {
1448 resolve_name(cache, &first.name, scope_ctx).and_then(|tid| {
1450 if tid.starts_with("t_mapping") {
1451 extract_mapping_value_type(&tid)
1452 } else {
1453 Some(tid)
1454 }
1455 })
1456 }
1457 };
1458
1459 for seg in &chain[1..] {
1461 let ctx_type = match ¤t_type {
1462 Some(t) => t.clone(),
1463 None => return vec![],
1464 };
1465
1466 current_type = resolve_member_type(cache, &ctx_type, &seg.name, &seg.kind);
1467 }
1468
1469 match current_type {
1471 Some(tid) => completions_for_type(cache, &tid),
1472 None => vec![],
1473 }
1474}
1475
1476pub fn get_static_completions() -> Vec<CompletionItem> {
1479 let mut items = Vec::new();
1480
1481 for kw in SOLIDITY_KEYWORDS {
1483 items.push(CompletionItem {
1484 label: kw.to_string(),
1485 kind: Some(CompletionItemKind::KEYWORD),
1486 ..Default::default()
1487 });
1488 }
1489
1490 for (name, detail) in MAGIC_GLOBALS {
1492 items.push(CompletionItem {
1493 label: name.to_string(),
1494 kind: Some(CompletionItemKind::VARIABLE),
1495 detail: Some(detail.to_string()),
1496 ..Default::default()
1497 });
1498 }
1499
1500 for (name, detail) in GLOBAL_FUNCTIONS {
1502 items.push(CompletionItem {
1503 label: name.to_string(),
1504 kind: Some(CompletionItemKind::FUNCTION),
1505 detail: Some(detail.to_string()),
1506 ..Default::default()
1507 });
1508 }
1509
1510 for (name, detail) in ETHER_UNITS {
1512 items.push(CompletionItem {
1513 label: name.to_string(),
1514 kind: Some(CompletionItemKind::UNIT),
1515 detail: Some(detail.to_string()),
1516 ..Default::default()
1517 });
1518 }
1519
1520 for (name, detail) in TIME_UNITS {
1522 items.push(CompletionItem {
1523 label: name.to_string(),
1524 kind: Some(CompletionItemKind::UNIT),
1525 detail: Some(detail.to_string()),
1526 ..Default::default()
1527 });
1528 }
1529
1530 items
1531}
1532
1533pub fn get_general_completions(cache: &CompletionCache) -> Vec<CompletionItem> {
1535 let mut items = cache.names.clone();
1536 items.extend(get_static_completions());
1537 items
1538}
1539
1540pub fn handle_completion(
1550 cache: Option<&CompletionCache>,
1551 source_text: &str,
1552 position: Position,
1553 trigger_char: Option<&str>,
1554 file_id: Option<u64>,
1555) -> Option<CompletionResponse> {
1556 let lines: Vec<&str> = source_text.lines().collect();
1557 let line = lines.get(position.line as usize)?;
1558
1559 let abs_byte =
1561 crate::utils::position_to_byte_offset(source_text, position.line, position.character);
1562 let line_start_byte: usize = source_text[..abs_byte]
1563 .rfind('\n')
1564 .map(|i| i + 1)
1565 .unwrap_or(0);
1566 let col_byte = (abs_byte - line_start_byte) as u32;
1567
1568 let scope_ctx = file_id.map(|fid| ScopeContext {
1570 byte_pos: abs_byte,
1571 file_id: fid,
1572 });
1573
1574 let items = if trigger_char == Some(".") {
1575 let chain = parse_dot_chain(line, col_byte);
1576 if chain.is_empty() {
1577 return None;
1578 }
1579 match cache {
1580 Some(c) => get_chain_completions(c, &chain, scope_ctx.as_ref()),
1581 None => {
1582 if chain.len() == 1 && chain[0].kind == AccessKind::Plain {
1584 magic_members(&chain[0].name).unwrap_or_default()
1585 } else {
1586 vec![]
1587 }
1588 }
1589 }
1590 } else {
1591 match cache {
1592 Some(c) => c.general_completions.clone(),
1593 None => get_static_completions(),
1594 }
1595 };
1596
1597 Some(CompletionResponse::List(CompletionList {
1598 is_incomplete: cache.is_none(),
1599 items,
1600 }))
1601}
1602
1603const SOLIDITY_KEYWORDS: &[&str] = &[
1604 "abstract",
1605 "address",
1606 "assembly",
1607 "bool",
1608 "break",
1609 "bytes",
1610 "bytes1",
1611 "bytes4",
1612 "bytes32",
1613 "calldata",
1614 "constant",
1615 "constructor",
1616 "continue",
1617 "contract",
1618 "delete",
1619 "do",
1620 "else",
1621 "emit",
1622 "enum",
1623 "error",
1624 "event",
1625 "external",
1626 "fallback",
1627 "false",
1628 "for",
1629 "function",
1630 "if",
1631 "immutable",
1632 "import",
1633 "indexed",
1634 "int8",
1635 "int24",
1636 "int128",
1637 "int256",
1638 "interface",
1639 "internal",
1640 "library",
1641 "mapping",
1642 "memory",
1643 "modifier",
1644 "new",
1645 "override",
1646 "payable",
1647 "pragma",
1648 "private",
1649 "public",
1650 "pure",
1651 "receive",
1652 "return",
1653 "returns",
1654 "revert",
1655 "storage",
1656 "string",
1657 "struct",
1658 "true",
1659 "type",
1660 "uint8",
1661 "uint24",
1662 "uint128",
1663 "uint160",
1664 "uint256",
1665 "unchecked",
1666 "using",
1667 "view",
1668 "virtual",
1669 "while",
1670];
1671
1672const ETHER_UNITS: &[(&str, &str)] = &[("wei", "1"), ("gwei", "1e9"), ("ether", "1e18")];
1674
1675const TIME_UNITS: &[(&str, &str)] = &[
1677 ("seconds", "1"),
1678 ("minutes", "60 seconds"),
1679 ("hours", "3600 seconds"),
1680 ("days", "86400 seconds"),
1681 ("weeks", "604800 seconds"),
1682];
1683
1684const MAGIC_GLOBALS: &[(&str, &str)] = &[
1685 ("msg", "msg"),
1686 ("block", "block"),
1687 ("tx", "tx"),
1688 ("abi", "abi"),
1689 ("this", "address"),
1690 ("super", "contract"),
1691 ("type", "type information"),
1692];
1693
1694const GLOBAL_FUNCTIONS: &[(&str, &str)] = &[
1695 ("addmod(uint256, uint256, uint256)", "uint256"),
1697 ("mulmod(uint256, uint256, uint256)", "uint256"),
1698 ("keccak256(bytes memory)", "bytes32"),
1699 ("sha256(bytes memory)", "bytes32"),
1700 ("ripemd160(bytes memory)", "bytes20"),
1701 (
1702 "ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s)",
1703 "address",
1704 ),
1705 ("blockhash(uint256 blockNumber)", "bytes32"),
1707 ("blobhash(uint256 index)", "bytes32"),
1708 ("gasleft()", "uint256"),
1709 ("assert(bool condition)", ""),
1711 ("require(bool condition)", ""),
1712 ("require(bool condition, string memory message)", ""),
1713 ("revert()", ""),
1714 ("revert(string memory reason)", ""),
1715 ("selfdestruct(address payable recipient)", ""),
1717];