1use serde_json::Value;
2use std::collections::HashMap;
3use tower_lsp::lsp_types::{
4 CompletionItem, CompletionItemKind, CompletionList, CompletionResponse, Position,
5};
6
7use crate::goto::CHILD_KEYS;
8use crate::hover::build_function_signature;
9use crate::types::{FileId, NodeId, SourceLoc};
10use crate::utils::push_if_node_or_array;
11
12#[derive(Debug, Clone)]
14pub struct ScopedDeclaration {
15 pub name: String,
17 pub type_id: String,
19}
20
21#[derive(Debug, Clone)]
23pub struct ScopeRange {
24 pub node_id: NodeId,
26 pub start: usize,
28 pub end: usize,
30 pub file_id: FileId,
32}
33
34#[derive(Debug)]
36pub struct CompletionCache {
37 pub names: Vec<CompletionItem>,
39
40 pub name_to_type: HashMap<String, String>,
42
43 pub node_members: HashMap<NodeId, Vec<CompletionItem>>,
45
46 pub type_to_node: HashMap<String, NodeId>,
48
49 pub name_to_node_id: HashMap<String, NodeId>,
51
52 pub method_identifiers: HashMap<NodeId, Vec<CompletionItem>>,
55
56 pub function_return_types: HashMap<(NodeId, String), String>,
59
60 pub using_for: HashMap<String, Vec<CompletionItem>>,
63
64 pub using_for_wildcard: Vec<CompletionItem>,
66
67 pub general_completions: Vec<CompletionItem>,
70
71 pub scope_declarations: HashMap<NodeId, Vec<ScopedDeclaration>>,
75
76 pub scope_parent: HashMap<NodeId, NodeId>,
79
80 pub scope_ranges: Vec<ScopeRange>,
83
84 pub path_to_file_id: HashMap<String, FileId>,
87
88 pub linearized_base_contracts: HashMap<NodeId, Vec<NodeId>>,
92
93 pub contract_kinds: HashMap<NodeId, String>,
97}
98
99fn node_type_to_completion_kind(node_type: &str) -> CompletionItemKind {
101 match node_type {
102 "FunctionDefinition" => CompletionItemKind::FUNCTION,
103 "VariableDeclaration" => CompletionItemKind::VARIABLE,
104 "ContractDefinition" => CompletionItemKind::CLASS,
105 "StructDefinition" => CompletionItemKind::STRUCT,
106 "EnumDefinition" => CompletionItemKind::ENUM,
107 "EnumValue" => CompletionItemKind::ENUM_MEMBER,
108 "EventDefinition" => CompletionItemKind::EVENT,
109 "ErrorDefinition" => CompletionItemKind::EVENT,
110 "ModifierDefinition" => CompletionItemKind::METHOD,
111 "ImportDirective" => CompletionItemKind::MODULE,
112 _ => CompletionItemKind::TEXT,
113 }
114}
115
116fn parse_src(node: &Value) -> Option<SourceLoc> {
119 let src = node.get("src").and_then(|v| v.as_str())?;
120 SourceLoc::parse(src)
121}
122
123pub fn extract_node_id_from_type(type_id: &str) -> Option<NodeId> {
128 let mut last_id = None;
131 let mut i = 0;
132 let bytes = type_id.as_bytes();
133 while i < bytes.len() {
134 if i + 1 < bytes.len() && bytes[i] == b'_' && bytes[i + 1] == b'$' {
135 i += 2;
136 let start = i;
137 while i < bytes.len() && bytes[i].is_ascii_digit() {
138 i += 1;
139 }
140 if i > start
141 && let Ok(id) = type_id[start..i].parse::<u64>()
142 {
143 last_id = Some(NodeId(id));
144 }
145 } else {
146 i += 1;
147 }
148 }
149 last_id
150}
151
152pub fn extract_mapping_value_type(type_id: &str) -> Option<String> {
159 let mut current = type_id;
160
161 loop {
162 if !current.starts_with("t_mapping$_") {
163 let result = current.trim_end_matches("_$");
166 return if result.is_empty() {
167 None
168 } else {
169 Some(result.to_string())
170 };
171 }
172
173 let inner = ¤t["t_mapping$_".len()..];
175
176 let mut depth = 0i32;
180 let bytes = inner.as_bytes();
181 let mut split_pos = None;
182
183 let mut i = 0;
184 while i < bytes.len() {
185 if i + 1 < bytes.len() && bytes[i] == b'$' && bytes[i + 1] == b'_' {
186 depth += 1;
187 i += 2;
188 } else if i + 2 < bytes.len()
189 && bytes[i] == b'_'
190 && bytes[i + 1] == b'$'
191 && bytes[i + 2] == b'_'
192 && depth == 0
193 {
194 split_pos = Some(i);
196 break;
197 } else if i + 1 < bytes.len() && bytes[i] == b'_' && bytes[i + 1] == b'$' {
198 depth -= 1;
199 i += 2;
200 } else {
201 i += 1;
202 }
203 }
204
205 if let Some(pos) = split_pos {
206 current = &inner[pos + 3..];
208 } else {
209 return None;
210 }
211 }
212}
213
214fn count_abi_params(signature: &str) -> usize {
217 let start = match signature.find('(') {
219 Some(i) => i + 1,
220 None => return 0,
221 };
222 let bytes = signature.as_bytes();
223 if start >= bytes.len() {
224 return 0;
225 }
226 if bytes[start] == b')' {
228 return 0;
229 }
230 let mut count = 1; let mut depth = 0;
232 for &b in &bytes[start..] {
233 match b {
234 b'(' => depth += 1,
235 b')' => {
236 if depth == 0 {
237 break;
238 }
239 depth -= 1;
240 }
241 b',' if depth == 0 => count += 1,
242 _ => {}
243 }
244 }
245 count
246}
247
248fn count_signature_params(sig: &str) -> usize {
250 count_abi_params(sig)
251}
252
253pub fn build_completion_cache(sources: &Value, contracts: Option<&Value>) -> CompletionCache {
256 let source_count = sources.as_object().map_or(0, |obj| obj.len());
257 let est_names = source_count * 20;
260 let est_contracts = source_count * 5;
261
262 let mut names: Vec<CompletionItem> = Vec::with_capacity(est_names);
263 let mut seen_names: HashMap<String, usize> = HashMap::with_capacity(est_names);
264 let mut name_to_type: HashMap<String, String> = HashMap::with_capacity(est_names);
265 let mut node_members: HashMap<NodeId, Vec<CompletionItem>> =
266 HashMap::with_capacity(est_contracts);
267 let mut type_to_node: HashMap<String, NodeId> = HashMap::with_capacity(est_contracts);
268 let mut method_identifiers: HashMap<NodeId, Vec<CompletionItem>> =
269 HashMap::with_capacity(est_contracts);
270 let mut name_to_node_id: HashMap<String, NodeId> = HashMap::with_capacity(est_names);
271 let mut contract_kinds: HashMap<NodeId, String> = HashMap::with_capacity(est_contracts);
272
273 let mut contract_locations: Vec<(String, String, NodeId)> = Vec::with_capacity(est_contracts);
275
276 let mut function_signatures: HashMap<NodeId, HashMap<String, Vec<String>>> =
278 HashMap::with_capacity(est_contracts);
279
280 let mut function_return_types: HashMap<(NodeId, String), String> =
282 HashMap::with_capacity(source_count * 10);
283
284 let mut using_for: HashMap<String, Vec<CompletionItem>> = HashMap::with_capacity(source_count);
286 let mut using_for_wildcard: Vec<CompletionItem> = Vec::new();
287
288 let mut using_for_directives: Vec<(NodeId, Option<String>)> = Vec::new();
290
291 let mut scope_declarations: HashMap<NodeId, Vec<ScopedDeclaration>> =
293 HashMap::with_capacity(est_contracts);
294 let mut scope_parent: HashMap<NodeId, NodeId> = HashMap::with_capacity(est_contracts);
295 let mut scope_ranges: Vec<ScopeRange> = Vec::with_capacity(est_contracts);
296 let mut path_to_file_id: HashMap<String, FileId> = HashMap::with_capacity(source_count);
297 let mut linearized_base_contracts: HashMap<NodeId, Vec<NodeId>> =
298 HashMap::with_capacity(est_contracts);
299
300 if let Some(sources_obj) = sources.as_object() {
301 for (path, source_data) in sources_obj {
302 if let Some(ast) = source_data.get("ast") {
303 if let Some(fid) = source_data.get("id").and_then(|v| v.as_u64()) {
305 path_to_file_id.insert(path.clone(), FileId(fid));
306 }
307 let mut stack: Vec<&Value> = vec![ast];
308
309 while let Some(tree) = stack.pop() {
310 let node_type = tree.get("nodeType").and_then(|v| v.as_str()).unwrap_or("");
311 let name = tree.get("name").and_then(|v| v.as_str()).unwrap_or("");
312 let node_id = tree.get("id").and_then(|v| v.as_u64()).map(NodeId);
313
314 let is_scope_node = matches!(
319 node_type,
320 "SourceUnit"
321 | "ContractDefinition"
322 | "FunctionDefinition"
323 | "ModifierDefinition"
324 | "Block"
325 | "UncheckedBlock"
326 );
327 if is_scope_node && let Some(nid) = node_id {
328 if let Some(src_loc) = parse_src(tree) {
329 scope_ranges.push(ScopeRange {
330 node_id: nid,
331 start: src_loc.offset,
332 end: src_loc.end(),
333 file_id: src_loc.file_id,
334 });
335 }
336 if let Some(parent_id) = tree.get("scope").and_then(|v| v.as_u64()) {
338 scope_parent.insert(nid, NodeId(parent_id));
339 }
340 }
341
342 if node_type == "ContractDefinition"
344 && let Some(nid) = node_id
345 && let Some(bases) = tree
346 .get("linearizedBaseContracts")
347 .and_then(|v| v.as_array())
348 {
349 let base_ids: Vec<NodeId> = bases
350 .iter()
351 .filter_map(|b| b.as_u64())
352 .map(NodeId)
353 .collect();
354 if !base_ids.is_empty() {
355 linearized_base_contracts.insert(nid, base_ids);
356 }
357 }
358
359 if node_type == "VariableDeclaration"
361 && !name.is_empty()
362 && let Some(scope_raw) = tree.get("scope").and_then(|v| v.as_u64())
363 && let Some(tid) = tree
364 .get("typeDescriptions")
365 .and_then(|td| td.get("typeIdentifier"))
366 .and_then(|v| v.as_str())
367 {
368 scope_declarations
369 .entry(NodeId(scope_raw))
370 .or_default()
371 .push(ScopedDeclaration {
372 name: name.to_string(),
373 type_id: tid.to_string(),
374 });
375 }
376
377 if node_type == "FunctionDefinition"
379 && !name.is_empty()
380 && let Some(scope_raw) = tree.get("scope").and_then(|v| v.as_u64())
381 && let Some(tid) = tree
382 .get("typeDescriptions")
383 .and_then(|td| td.get("typeIdentifier"))
384 .and_then(|v| v.as_str())
385 {
386 scope_declarations
387 .entry(NodeId(scope_raw))
388 .or_default()
389 .push(ScopedDeclaration {
390 name: name.to_string(),
391 type_id: tid.to_string(),
392 });
393 }
394
395 if !name.is_empty() && !seen_names.contains_key(name) {
397 let type_string = tree
398 .get("typeDescriptions")
399 .and_then(|td| td.get("typeString"))
400 .and_then(|v| v.as_str())
401 .map(|s| s.to_string());
402
403 let type_id = tree
404 .get("typeDescriptions")
405 .and_then(|td| td.get("typeIdentifier"))
406 .and_then(|v| v.as_str());
407
408 let kind = node_type_to_completion_kind(node_type);
409
410 let item = CompletionItem {
411 label: name.to_string(),
412 kind: Some(kind),
413 detail: type_string,
414 ..Default::default()
415 };
416
417 let idx = names.len();
418 names.push(item);
419 seen_names.insert(name.to_string(), idx);
420
421 if let Some(tid) = type_id {
423 name_to_type.insert(name.to_string(), tid.to_string());
424 }
425 }
426
427 if node_type == "StructDefinition"
429 && let Some(id) = node_id
430 {
431 let mut members = Vec::new();
432 if let Some(member_array) = tree.get("members").and_then(|v| v.as_array()) {
433 for member in member_array {
434 let member_name =
435 member.get("name").and_then(|v| v.as_str()).unwrap_or("");
436 if member_name.is_empty() {
437 continue;
438 }
439 let member_type = member
440 .get("typeDescriptions")
441 .and_then(|td| td.get("typeString"))
442 .and_then(|v| v.as_str())
443 .map(|s| s.to_string());
444
445 members.push(CompletionItem {
446 label: member_name.to_string(),
447 kind: Some(CompletionItemKind::FIELD),
448 detail: member_type,
449 ..Default::default()
450 });
451 }
452 }
453 if !members.is_empty() {
454 node_members.insert(id, members);
455 }
456
457 if let Some(tid) = tree
459 .get("typeDescriptions")
460 .and_then(|td| td.get("typeIdentifier"))
461 .and_then(|v| v.as_str())
462 {
463 type_to_node.insert(tid.to_string(), id);
464 }
465 }
466
467 if node_type == "ContractDefinition"
469 && let Some(id) = node_id
470 {
471 let mut members = Vec::new();
472 let mut fn_sigs: HashMap<String, Vec<String>> = HashMap::new();
473 if let Some(nodes_array) = tree.get("nodes").and_then(|v| v.as_array()) {
474 for member in nodes_array {
475 let member_type = member
476 .get("nodeType")
477 .and_then(|v| v.as_str())
478 .unwrap_or("");
479 let member_name =
480 member.get("name").and_then(|v| v.as_str()).unwrap_or("");
481 if member_name.is_empty() {
482 continue;
483 }
484
485 let (member_detail, label_details) =
487 if member_type == "FunctionDefinition" {
488 if let Some(ret_params) = member
492 .get("returnParameters")
493 .and_then(|rp| rp.get("parameters"))
494 .and_then(|v| v.as_array())
495 && ret_params.len() == 1
496 && let Some(ret_tid) = ret_params[0]
497 .get("typeDescriptions")
498 .and_then(|td| td.get("typeIdentifier"))
499 .and_then(|v| v.as_str())
500 {
501 function_return_types.insert(
502 (id, member_name.to_string()),
503 ret_tid.to_string(),
504 );
505 }
506
507 if let Some(sig) = build_function_signature(member) {
508 fn_sigs
509 .entry(member_name.to_string())
510 .or_default()
511 .push(sig.clone());
512 (Some(sig), None)
513 } else {
514 (
515 member
516 .get("typeDescriptions")
517 .and_then(|td| td.get("typeString"))
518 .and_then(|v| v.as_str())
519 .map(|s| s.to_string()),
520 None,
521 )
522 }
523 } else {
524 (
525 member
526 .get("typeDescriptions")
527 .and_then(|td| td.get("typeString"))
528 .and_then(|v| v.as_str())
529 .map(|s| s.to_string()),
530 None,
531 )
532 };
533
534 let kind = node_type_to_completion_kind(member_type);
535 members.push(CompletionItem {
536 label: member_name.to_string(),
537 kind: Some(kind),
538 detail: member_detail,
539 label_details,
540 ..Default::default()
541 });
542 }
543 }
544 if !members.is_empty() {
545 node_members.insert(id, members);
546 }
547 if !fn_sigs.is_empty() {
548 function_signatures.insert(id, fn_sigs);
549 }
550
551 if let Some(tid) = tree
552 .get("typeDescriptions")
553 .and_then(|td| td.get("typeIdentifier"))
554 .and_then(|v| v.as_str())
555 {
556 type_to_node.insert(tid.to_string(), id);
557 }
558
559 if !name.is_empty() {
561 contract_locations.push((path.clone(), name.to_string(), id));
562 name_to_node_id.insert(name.to_string(), id);
563 }
564
565 if let Some(ck) = tree.get("contractKind").and_then(|v| v.as_str()) {
567 contract_kinds.insert(id, ck.to_string());
568 }
569 }
570
571 if node_type == "EnumDefinition"
573 && let Some(id) = node_id
574 {
575 let mut members = Vec::new();
576 if let Some(member_array) = tree.get("members").and_then(|v| v.as_array()) {
577 for member in member_array {
578 let member_name =
579 member.get("name").and_then(|v| v.as_str()).unwrap_or("");
580 if member_name.is_empty() {
581 continue;
582 }
583 members.push(CompletionItem {
584 label: member_name.to_string(),
585 kind: Some(CompletionItemKind::ENUM_MEMBER),
586 detail: None,
587 ..Default::default()
588 });
589 }
590 }
591 if !members.is_empty() {
592 node_members.insert(id, members);
593 }
594
595 if let Some(tid) = tree
596 .get("typeDescriptions")
597 .and_then(|td| td.get("typeIdentifier"))
598 .and_then(|v| v.as_str())
599 {
600 type_to_node.insert(tid.to_string(), id);
601 }
602 }
603
604 if node_type == "UsingForDirective" {
606 let target_type = tree.get("typeName").and_then(|tn| {
608 tn.get("typeDescriptions")
609 .and_then(|td| td.get("typeIdentifier"))
610 .and_then(|v| v.as_str())
611 .map(|s| s.to_string())
612 });
613
614 if let Some(lib) = tree.get("libraryName") {
616 if let Some(lib_id) =
617 lib.get("referencedDeclaration").and_then(|v| v.as_u64())
618 {
619 using_for_directives.push((NodeId(lib_id), target_type));
620 }
621 }
622 else if let Some(func_list) =
626 tree.get("functionList").and_then(|v| v.as_array())
627 {
628 for entry in func_list {
629 if entry.get("operator").is_some() {
631 continue;
632 }
633 if let Some(def) = entry.get("definition") {
634 let fn_name =
635 def.get("name").and_then(|v| v.as_str()).unwrap_or("");
636 if !fn_name.is_empty() {
637 let items = if let Some(ref tid) = target_type {
638 using_for.entry(tid.clone()).or_default()
639 } else {
640 &mut using_for_wildcard
641 };
642 items.push(CompletionItem {
643 label: fn_name.to_string(),
644 kind: Some(CompletionItemKind::FUNCTION),
645 detail: None,
646 ..Default::default()
647 });
648 }
649 }
650 }
651 }
652 }
653
654 for key in CHILD_KEYS {
656 push_if_node_or_array(tree, key, &mut stack);
657 }
658 }
659 }
660 }
661 }
662
663 for (lib_id, target_type) in &using_for_directives {
666 if let Some(lib_members) = node_members.get(lib_id) {
667 let items: Vec<CompletionItem> = lib_members
668 .iter()
669 .filter(|item| item.kind == Some(CompletionItemKind::FUNCTION))
670 .cloned()
671 .collect();
672 if !items.is_empty() {
673 if let Some(tid) = target_type {
674 using_for.entry(tid.clone()).or_default().extend(items);
675 } else {
676 using_for_wildcard.extend(items);
677 }
678 }
679 }
680 }
681
682 if let Some(contracts_val) = contracts
684 && let Some(contracts_obj) = contracts_val.as_object()
685 {
686 for (path, contract_name, node_id) in &contract_locations {
687 let fn_sigs = function_signatures.get(node_id);
689
690 if let Some(path_entry) = contracts_obj.get(path)
691 && let Some(contract_entry) = path_entry.get(contract_name)
692 && let Some(evm) = contract_entry.get("evm")
693 && let Some(methods) = evm.get("methodIdentifiers")
694 && let Some(methods_obj) = methods.as_object()
695 {
696 let mut items: Vec<CompletionItem> = Vec::new();
697 for (signature, selector_val) in methods_obj {
698 let fn_name = signature.split('(').next().unwrap_or(signature).to_string();
701 let selector_str = selector_val
702 .as_str()
703 .map(|s| crate::types::FuncSelector::new(s).to_prefixed())
704 .unwrap_or_default();
705
706 let description =
708 fn_sigs
709 .and_then(|sigs| sigs.get(&fn_name))
710 .and_then(|sig_list| {
711 if sig_list.len() == 1 {
712 Some(sig_list[0].clone())
714 } else {
715 let abi_param_count = count_abi_params(signature);
717 sig_list
718 .iter()
719 .find(|s| count_signature_params(s) == abi_param_count)
720 .cloned()
721 }
722 });
723
724 items.push(CompletionItem {
725 label: fn_name,
726 kind: Some(CompletionItemKind::FUNCTION),
727 detail: Some(signature.clone()),
728 label_details: Some(tower_lsp::lsp_types::CompletionItemLabelDetails {
729 detail: Some(selector_str),
730 description,
731 }),
732 ..Default::default()
733 });
734 }
735 if !items.is_empty() {
736 method_identifiers.insert(*node_id, items);
737 }
738 }
739 }
740 }
741
742 let mut general_completions = names.clone();
744 general_completions.extend(get_static_completions());
745
746 scope_ranges.sort_by_key(|r| r.end - r.start);
748
749 let orphan_ids: Vec<NodeId> = scope_ranges
755 .iter()
756 .filter(|r| !scope_parent.contains_key(&r.node_id))
757 .map(|r| r.node_id)
758 .collect();
759 let range_by_id: HashMap<NodeId, (usize, usize, FileId)> = scope_ranges
761 .iter()
762 .map(|r| (r.node_id, (r.start, r.end, r.file_id)))
763 .collect();
764 for orphan_id in &orphan_ids {
765 if let Some(&(start, end, file_id)) = range_by_id.get(orphan_id) {
766 let parent = scope_ranges
769 .iter()
770 .find(|r| {
771 r.node_id != *orphan_id
772 && r.file_id == file_id
773 && r.start <= start
774 && r.end >= end
775 && (r.end - r.start) > (end - start)
776 })
777 .map(|r| r.node_id);
778 if let Some(parent_id) = parent {
779 scope_parent.insert(*orphan_id, parent_id);
780 }
781 }
782 }
783
784 CompletionCache {
785 names,
786 name_to_type,
787 node_members,
788 type_to_node,
789 name_to_node_id,
790 method_identifiers,
791 function_return_types,
792 using_for,
793 using_for_wildcard,
794 general_completions,
795 scope_declarations,
796 scope_parent,
797 scope_ranges,
798 path_to_file_id,
799 linearized_base_contracts,
800 contract_kinds,
801 }
802}
803
804fn magic_members(name: &str) -> Option<Vec<CompletionItem>> {
806 let items = match name {
807 "msg" => vec![
808 ("data", "bytes calldata"),
809 ("sender", "address"),
810 ("sig", "bytes4"),
811 ("value", "uint256"),
812 ],
813 "block" => vec![
814 ("basefee", "uint256"),
815 ("blobbasefee", "uint256"),
816 ("chainid", "uint256"),
817 ("coinbase", "address payable"),
818 ("difficulty", "uint256"),
819 ("gaslimit", "uint256"),
820 ("number", "uint256"),
821 ("prevrandao", "uint256"),
822 ("timestamp", "uint256"),
823 ],
824 "tx" => vec![("gasprice", "uint256"), ("origin", "address")],
825 "abi" => vec![
826 ("decode(bytes memory, (...))", "..."),
827 ("encode(...)", "bytes memory"),
828 ("encodePacked(...)", "bytes memory"),
829 ("encodeWithSelector(bytes4, ...)", "bytes memory"),
830 ("encodeWithSignature(string memory, ...)", "bytes memory"),
831 ("encodeCall(function, (...))", "bytes memory"),
832 ],
833 "type" => vec![
836 ("name", "string"),
837 ("creationCode", "bytes memory"),
838 ("runtimeCode", "bytes memory"),
839 ("interfaceId", "bytes4"),
840 ("min", "T"),
841 ("max", "T"),
842 ],
843 "bytes" => vec![("concat(...)", "bytes memory")],
845 "string" => vec![("concat(...)", "string memory")],
846 _ => return None,
847 };
848
849 Some(
850 items
851 .into_iter()
852 .map(|(label, detail)| CompletionItem {
853 label: label.to_string(),
854 kind: Some(CompletionItemKind::PROPERTY),
855 detail: Some(detail.to_string()),
856 ..Default::default()
857 })
858 .collect(),
859 )
860}
861
862#[derive(Debug, Clone, Copy, PartialEq, Eq)]
865enum TypeMetaKind {
866 Contract,
868 Interface,
870 IntegerType,
872 Unknown,
874}
875
876fn classify_type_arg(arg: &str, cache: Option<&CompletionCache>) -> TypeMetaKind {
878 if arg == "int" || arg == "uint" {
880 return TypeMetaKind::IntegerType;
881 }
882 if let Some(suffix) = arg.strip_prefix("uint").or_else(|| arg.strip_prefix("int"))
883 && let Ok(n) = suffix.parse::<u16>()
884 && (8..=256).contains(&n)
885 && n % 8 == 0
886 {
887 return TypeMetaKind::IntegerType;
888 }
889
890 if let Some(c) = cache
892 && let Some(&node_id) = c.name_to_node_id.get(arg)
893 {
894 return match c.contract_kinds.get(&node_id).map(|s| s.as_str()) {
895 Some("interface") => TypeMetaKind::Interface,
896 Some("library") => TypeMetaKind::Contract, _ => TypeMetaKind::Contract,
898 };
899 }
900
901 TypeMetaKind::Unknown
902}
903
904fn type_meta_members(arg: Option<&str>, cache: Option<&CompletionCache>) -> Vec<CompletionItem> {
906 let kind = match arg {
907 Some(a) => classify_type_arg(a, cache),
908 None => TypeMetaKind::Unknown,
909 };
910
911 let items: Vec<(&str, &str)> = match kind {
912 TypeMetaKind::Contract => vec![
913 ("name", "string"),
914 ("creationCode", "bytes memory"),
915 ("runtimeCode", "bytes memory"),
916 ],
917 TypeMetaKind::Interface => vec![("name", "string"), ("interfaceId", "bytes4")],
918 TypeMetaKind::IntegerType => vec![("min", "T"), ("max", "T")],
919 TypeMetaKind::Unknown => vec![
920 ("name", "string"),
921 ("creationCode", "bytes memory"),
922 ("runtimeCode", "bytes memory"),
923 ("interfaceId", "bytes4"),
924 ("min", "T"),
925 ("max", "T"),
926 ],
927 };
928
929 items
930 .into_iter()
931 .map(|(label, detail)| CompletionItem {
932 label: label.to_string(),
933 kind: Some(CompletionItemKind::PROPERTY),
934 detail: Some(detail.to_string()),
935 ..Default::default()
936 })
937 .collect()
938}
939
940fn address_members() -> Vec<CompletionItem> {
942 [
943 ("balance", "uint256", CompletionItemKind::PROPERTY),
944 ("code", "bytes memory", CompletionItemKind::PROPERTY),
945 ("codehash", "bytes32", CompletionItemKind::PROPERTY),
946 ("transfer(uint256)", "", CompletionItemKind::FUNCTION),
947 ("send(uint256)", "bool", CompletionItemKind::FUNCTION),
948 (
949 "call(bytes memory)",
950 "(bool, bytes memory)",
951 CompletionItemKind::FUNCTION,
952 ),
953 (
954 "delegatecall(bytes memory)",
955 "(bool, bytes memory)",
956 CompletionItemKind::FUNCTION,
957 ),
958 (
959 "staticcall(bytes memory)",
960 "(bool, bytes memory)",
961 CompletionItemKind::FUNCTION,
962 ),
963 ]
964 .iter()
965 .map(|(label, detail, kind)| CompletionItem {
966 label: label.to_string(),
967 kind: Some(*kind),
968 detail: if detail.is_empty() {
969 None
970 } else {
971 Some(detail.to_string())
972 },
973 ..Default::default()
974 })
975 .collect()
976}
977
978#[derive(Debug, Clone, PartialEq)]
980pub enum AccessKind {
981 Plain,
983 Call,
985 Index,
987}
988
989#[derive(Debug, Clone, PartialEq)]
991pub struct DotSegment {
992 pub name: String,
993 pub kind: AccessKind,
994 pub call_args: Option<String>,
997}
998
999fn skip_brackets_backwards(bytes: &[u8], pos: usize) -> usize {
1003 let close = bytes[pos];
1004 let open = match close {
1005 b')' => b'(',
1006 b']' => b'[',
1007 _ => return pos,
1008 };
1009 let mut depth = 1u32;
1010 let mut i = pos;
1011 while i > 0 && depth > 0 {
1012 i -= 1;
1013 if bytes[i] == close {
1014 depth += 1;
1015 } else if bytes[i] == open {
1016 depth -= 1;
1017 }
1018 }
1019 i
1020}
1021
1022pub fn parse_dot_chain(line: &str, character: u32) -> Vec<DotSegment> {
1027 let col = character as usize;
1028 if col == 0 {
1029 return vec![];
1030 }
1031
1032 let bytes = line.as_bytes();
1033 let mut segments: Vec<DotSegment> = Vec::new();
1034
1035 let mut pos = col;
1037 if pos > 0 && pos <= bytes.len() && bytes[pos - 1] == b'.' {
1038 pos -= 1;
1039 }
1040
1041 loop {
1042 if pos == 0 {
1043 break;
1044 }
1045
1046 let (kind, call_args) = if bytes[pos - 1] == b')' {
1048 let close = pos - 1; pos = skip_brackets_backwards(bytes, close);
1050 let args_text = String::from_utf8_lossy(&bytes[pos + 1..close])
1052 .trim()
1053 .to_string();
1054 let args = if args_text.is_empty() {
1055 None
1056 } else {
1057 Some(args_text)
1058 };
1059 (AccessKind::Call, args)
1060 } else if bytes[pos - 1] == b']' {
1061 pos -= 1; pos = skip_brackets_backwards(bytes, pos);
1063 (AccessKind::Index, None)
1064 } else {
1065 (AccessKind::Plain, None)
1066 };
1067
1068 let end = pos;
1070 while pos > 0 && (bytes[pos - 1].is_ascii_alphanumeric() || bytes[pos - 1] == b'_') {
1071 pos -= 1;
1072 }
1073
1074 if pos == end {
1075 break;
1077 }
1078
1079 let name = String::from_utf8_lossy(&bytes[pos..end]).to_string();
1080 segments.push(DotSegment {
1081 name,
1082 kind,
1083 call_args,
1084 });
1085
1086 if pos > 0 && bytes[pos - 1] == b'.' {
1088 pos -= 1; } else {
1090 break;
1091 }
1092 }
1093
1094 segments.reverse(); segments
1096}
1097
1098pub fn extract_identifier_before_dot(line: &str, character: u32) -> Option<String> {
1101 let segments = parse_dot_chain(line, character);
1102 segments.last().map(|s| s.name.clone())
1103}
1104
1105#[doc = r"Strip all storage/memory location suffixes from a typeIdentifier to get the base type.
1106Solidity AST uses different suffixes in different contexts:
1107 - `t_struct$_State_$4809_storage_ptr` (UsingForDirective typeName)
1108 - `t_struct$_State_$4809_storage` (mapping value type after extraction)
1109 - `t_struct$_PoolKey_$8887_memory_ptr` (function parameter)
1110All refer to the same logical type. This strips `_ptr` and `_storage`/`_memory`/`_calldata`."]
1111fn strip_type_suffix(type_id: &str) -> &str {
1112 let s = type_id.strip_suffix("_ptr").unwrap_or(type_id);
1113 s.strip_suffix("_storage")
1114 .or_else(|| s.strip_suffix("_memory"))
1115 .or_else(|| s.strip_suffix("_calldata"))
1116 .unwrap_or(s)
1117}
1118
1119fn lookup_using_for(cache: &CompletionCache, type_id: &str) -> Vec<CompletionItem> {
1123 if let Some(items) = cache.using_for.get(type_id) {
1125 return items.clone();
1126 }
1127
1128 let base = strip_type_suffix(type_id);
1130 let variants = [
1131 base.to_string(),
1132 format!("{}_storage", base),
1133 format!("{}_storage_ptr", base),
1134 format!("{}_memory", base),
1135 format!("{}_memory_ptr", base),
1136 format!("{}_calldata", base),
1137 ];
1138 for variant in &variants {
1139 if variant.as_str() != type_id
1140 && let Some(items) = cache.using_for.get(variant.as_str())
1141 {
1142 return items.clone();
1143 }
1144 }
1145
1146 vec![]
1147}
1148
1149fn completions_for_type(cache: &CompletionCache, type_id: &str) -> Vec<CompletionItem> {
1152 if type_id == "t_address" || type_id == "t_address_payable" {
1154 let mut items = address_members();
1155 if let Some(uf) = cache.using_for.get(type_id) {
1157 items.extend(uf.iter().cloned());
1158 }
1159 items.extend(cache.using_for_wildcard.iter().cloned());
1160 return items;
1161 }
1162
1163 let resolved_node_id = extract_node_id_from_type(type_id)
1164 .or_else(|| cache.type_to_node.get(type_id).copied())
1165 .or_else(|| {
1166 type_id
1168 .strip_prefix("__node_id_")
1169 .and_then(|s| s.parse::<u64>().ok())
1170 .map(NodeId)
1171 });
1172
1173 let mut items = Vec::new();
1174 let mut seen_labels: std::collections::HashSet<String> = std::collections::HashSet::new();
1175
1176 if let Some(node_id) = resolved_node_id {
1177 if let Some(method_items) = cache.method_identifiers.get(&node_id) {
1179 for item in method_items {
1180 seen_labels.insert(item.label.clone());
1181 items.push(item.clone());
1182 }
1183 }
1184
1185 if let Some(members) = cache.node_members.get(&node_id) {
1187 for item in members {
1188 if !seen_labels.contains(&item.label) {
1189 seen_labels.insert(item.label.clone());
1190 items.push(item.clone());
1191 }
1192 }
1193 }
1194 }
1195
1196 let is_contract_name = resolved_node_id
1200 .map(|nid| cache.contract_kinds.contains_key(&nid))
1201 .unwrap_or(false);
1202
1203 if !is_contract_name {
1204 let uf_items = lookup_using_for(cache, type_id);
1206 for item in &uf_items {
1207 if !seen_labels.contains(&item.label) {
1208 seen_labels.insert(item.label.clone());
1209 items.push(item.clone());
1210 }
1211 }
1212
1213 for item in &cache.using_for_wildcard {
1215 if !seen_labels.contains(&item.label) {
1216 seen_labels.insert(item.label.clone());
1217 items.push(item.clone());
1218 }
1219 }
1220 }
1221
1222 items
1223}
1224
1225fn resolve_name_to_type_id(cache: &CompletionCache, name: &str) -> Option<String> {
1227 if let Some(tid) = cache.name_to_type.get(name) {
1229 return Some(tid.clone());
1230 }
1231 if let Some(node_id) = cache.name_to_node_id.get(name) {
1233 for (tid, nid) in &cache.type_to_node {
1235 if nid == node_id {
1236 return Some(tid.clone());
1237 }
1238 }
1239 return Some(format!("__node_id_{}", node_id));
1241 }
1242 None
1243}
1244
1245pub fn find_innermost_scope(
1249 cache: &CompletionCache,
1250 byte_pos: usize,
1251 file_id: FileId,
1252) -> Option<NodeId> {
1253 cache
1255 .scope_ranges
1256 .iter()
1257 .find(|r| r.file_id == file_id && r.start <= byte_pos && byte_pos < r.end)
1258 .map(|r| r.node_id)
1259}
1260
1261pub fn resolve_name_in_scope(
1270 cache: &CompletionCache,
1271 name: &str,
1272 byte_pos: usize,
1273 file_id: FileId,
1274) -> Option<String> {
1275 let mut current_scope = find_innermost_scope(cache, byte_pos, file_id)?;
1276
1277 loop {
1279 if let Some(decls) = cache.scope_declarations.get(¤t_scope) {
1281 for decl in decls {
1282 if decl.name == name {
1283 return Some(decl.type_id.clone());
1284 }
1285 }
1286 }
1287
1288 if let Some(bases) = cache.linearized_base_contracts.get(¤t_scope) {
1292 for &base_id in bases.iter().skip(1) {
1293 if let Some(decls) = cache.scope_declarations.get(&base_id) {
1294 for decl in decls {
1295 if decl.name == name {
1296 return Some(decl.type_id.clone());
1297 }
1298 }
1299 }
1300 }
1301 }
1302
1303 match cache.scope_parent.get(¤t_scope) {
1305 Some(&parent_id) => current_scope = parent_id,
1306 None => break, }
1308 }
1309
1310 resolve_name_to_type_id(cache, name)
1313}
1314
1315fn resolve_member_type(
1320 cache: &CompletionCache,
1321 context_type_id: &str,
1322 member_name: &str,
1323 kind: &AccessKind,
1324) -> Option<String> {
1325 let resolved_node_id = extract_node_id_from_type(context_type_id)
1326 .or_else(|| cache.type_to_node.get(context_type_id).copied())
1327 .or_else(|| {
1328 context_type_id
1330 .strip_prefix("__node_id_")
1331 .and_then(|s| s.parse::<u64>().ok())
1332 .map(NodeId)
1333 });
1334
1335 let node_id = resolved_node_id?;
1336
1337 match kind {
1338 AccessKind::Call => {
1339 cache
1341 .function_return_types
1342 .get(&(node_id, member_name.to_string()))
1343 .cloned()
1344 }
1345 AccessKind::Index => {
1346 if let Some(members) = cache.node_members.get(&node_id) {
1348 for member in members {
1349 if member.label == member_name {
1350 if let Some(tid) = cache.name_to_type.get(member_name) {
1352 if tid.starts_with("t_mapping") {
1353 return extract_mapping_value_type(tid);
1354 }
1355 return Some(tid.clone());
1356 }
1357 }
1358 }
1359 }
1360 if let Some(tid) = cache.name_to_type.get(member_name)
1362 && tid.starts_with("t_mapping")
1363 {
1364 return extract_mapping_value_type(tid);
1365 }
1366 None
1367 }
1368 AccessKind::Plain => {
1369 cache.name_to_type.get(member_name).cloned()
1371 }
1372 }
1373}
1374
1375pub struct ScopeContext {
1379 pub byte_pos: usize,
1381 pub file_id: FileId,
1383}
1384
1385fn resolve_name(
1389 cache: &CompletionCache,
1390 name: &str,
1391 scope_ctx: Option<&ScopeContext>,
1392) -> Option<String> {
1393 if let Some(ctx) = scope_ctx {
1394 resolve_name_in_scope(cache, name, ctx.byte_pos, ctx.file_id)
1395 } else {
1396 resolve_name_to_type_id(cache, name)
1397 }
1398}
1399
1400pub fn get_dot_completions(
1402 cache: &CompletionCache,
1403 identifier: &str,
1404 scope_ctx: Option<&ScopeContext>,
1405) -> Vec<CompletionItem> {
1406 if let Some(items) = magic_members(identifier) {
1408 return items;
1409 }
1410
1411 let type_id = resolve_name(cache, identifier, scope_ctx);
1413
1414 if let Some(tid) = type_id {
1415 return completions_for_type(cache, &tid);
1416 }
1417
1418 vec![]
1419}
1420
1421pub fn get_chain_completions(
1424 cache: &CompletionCache,
1425 chain: &[DotSegment],
1426 scope_ctx: Option<&ScopeContext>,
1427) -> Vec<CompletionItem> {
1428 if chain.is_empty() {
1429 return vec![];
1430 }
1431
1432 if chain.len() == 1 {
1434 let seg = &chain[0];
1435
1436 match seg.kind {
1438 AccessKind::Plain => {
1439 return get_dot_completions(cache, &seg.name, scope_ctx);
1440 }
1441 AccessKind::Call => {
1442 if seg.name == "type" {
1444 return type_meta_members(seg.call_args.as_deref(), Some(cache));
1445 }
1446 if let Some(type_id) = resolve_name(cache, &seg.name, scope_ctx) {
1449 return completions_for_type(cache, &type_id);
1450 }
1451 for ((_, fn_name), ret_type) in &cache.function_return_types {
1453 if fn_name == &seg.name {
1454 return completions_for_type(cache, ret_type);
1455 }
1456 }
1457 return vec![];
1458 }
1459 AccessKind::Index => {
1460 if let Some(tid) = resolve_name(cache, &seg.name, scope_ctx)
1462 && tid.starts_with("t_mapping")
1463 && let Some(val_type) = extract_mapping_value_type(&tid)
1464 {
1465 return completions_for_type(cache, &val_type);
1466 }
1467 return vec![];
1468 }
1469 }
1470 }
1471
1472 let first = &chain[0];
1475 let mut current_type = match first.kind {
1476 AccessKind::Plain => resolve_name(cache, &first.name, scope_ctx),
1477 AccessKind::Call => {
1478 resolve_name(cache, &first.name, scope_ctx).or_else(|| {
1480 cache
1481 .function_return_types
1482 .iter()
1483 .find(|((_, fn_name), _)| fn_name == &first.name)
1484 .map(|(_, ret_type)| ret_type.clone())
1485 })
1486 }
1487 AccessKind::Index => {
1488 resolve_name(cache, &first.name, scope_ctx).and_then(|tid| {
1490 if tid.starts_with("t_mapping") {
1491 extract_mapping_value_type(&tid)
1492 } else {
1493 Some(tid)
1494 }
1495 })
1496 }
1497 };
1498
1499 for seg in &chain[1..] {
1501 let ctx_type = match ¤t_type {
1502 Some(t) => t.clone(),
1503 None => return vec![],
1504 };
1505
1506 current_type = resolve_member_type(cache, &ctx_type, &seg.name, &seg.kind);
1507 }
1508
1509 match current_type {
1511 Some(tid) => completions_for_type(cache, &tid),
1512 None => vec![],
1513 }
1514}
1515
1516pub fn get_static_completions() -> Vec<CompletionItem> {
1519 let mut items = Vec::new();
1520
1521 for kw in SOLIDITY_KEYWORDS {
1523 items.push(CompletionItem {
1524 label: kw.to_string(),
1525 kind: Some(CompletionItemKind::KEYWORD),
1526 ..Default::default()
1527 });
1528 }
1529
1530 for (name, detail) in MAGIC_GLOBALS {
1532 items.push(CompletionItem {
1533 label: name.to_string(),
1534 kind: Some(CompletionItemKind::VARIABLE),
1535 detail: Some(detail.to_string()),
1536 ..Default::default()
1537 });
1538 }
1539
1540 for (name, detail) in GLOBAL_FUNCTIONS {
1542 items.push(CompletionItem {
1543 label: name.to_string(),
1544 kind: Some(CompletionItemKind::FUNCTION),
1545 detail: Some(detail.to_string()),
1546 ..Default::default()
1547 });
1548 }
1549
1550 for (name, detail) in ETHER_UNITS {
1552 items.push(CompletionItem {
1553 label: name.to_string(),
1554 kind: Some(CompletionItemKind::UNIT),
1555 detail: Some(detail.to_string()),
1556 ..Default::default()
1557 });
1558 }
1559
1560 for (name, detail) in TIME_UNITS {
1562 items.push(CompletionItem {
1563 label: name.to_string(),
1564 kind: Some(CompletionItemKind::UNIT),
1565 detail: Some(detail.to_string()),
1566 ..Default::default()
1567 });
1568 }
1569
1570 items
1571}
1572
1573pub fn get_general_completions(cache: &CompletionCache) -> Vec<CompletionItem> {
1575 let mut items = cache.names.clone();
1576 items.extend(get_static_completions());
1577 items
1578}
1579
1580pub fn handle_completion(
1590 cache: Option<&CompletionCache>,
1591 source_text: &str,
1592 position: Position,
1593 trigger_char: Option<&str>,
1594 file_id: Option<FileId>,
1595) -> Option<CompletionResponse> {
1596 let lines: Vec<&str> = source_text.lines().collect();
1597 let line = lines.get(position.line as usize)?;
1598
1599 let abs_byte = crate::utils::position_to_byte_offset(source_text, position);
1601 let line_start_byte: usize = source_text[..abs_byte]
1602 .rfind('\n')
1603 .map(|i| i + 1)
1604 .unwrap_or(0);
1605 let col_byte = (abs_byte - line_start_byte) as u32;
1606
1607 let scope_ctx = file_id.map(|fid| ScopeContext {
1609 byte_pos: abs_byte,
1610 file_id: fid,
1611 });
1612
1613 let items = if trigger_char == Some(".") {
1614 let chain = parse_dot_chain(line, col_byte);
1615 if chain.is_empty() {
1616 return None;
1617 }
1618 match cache {
1619 Some(c) => get_chain_completions(c, &chain, scope_ctx.as_ref()),
1620 None => {
1621 if chain.len() == 1 {
1623 let seg = &chain[0];
1624 if seg.name == "type" && seg.kind == AccessKind::Call {
1625 type_meta_members(seg.call_args.as_deref(), None)
1627 } else if seg.kind == AccessKind::Plain {
1628 magic_members(&seg.name).unwrap_or_default()
1629 } else {
1630 vec![]
1631 }
1632 } else {
1633 vec![]
1634 }
1635 }
1636 }
1637 } else {
1638 match cache {
1639 Some(c) => c.general_completions.clone(),
1640 None => get_static_completions(),
1641 }
1642 };
1643
1644 Some(CompletionResponse::List(CompletionList {
1645 is_incomplete: cache.is_none(),
1646 items,
1647 }))
1648}
1649
1650const SOLIDITY_KEYWORDS: &[&str] = &[
1651 "abstract",
1652 "address",
1653 "assembly",
1654 "bool",
1655 "break",
1656 "bytes",
1657 "bytes1",
1658 "bytes4",
1659 "bytes32",
1660 "calldata",
1661 "constant",
1662 "constructor",
1663 "continue",
1664 "contract",
1665 "delete",
1666 "do",
1667 "else",
1668 "emit",
1669 "enum",
1670 "error",
1671 "event",
1672 "external",
1673 "fallback",
1674 "false",
1675 "for",
1676 "function",
1677 "if",
1678 "immutable",
1679 "import",
1680 "indexed",
1681 "int8",
1682 "int24",
1683 "int128",
1684 "int256",
1685 "interface",
1686 "internal",
1687 "library",
1688 "mapping",
1689 "memory",
1690 "modifier",
1691 "new",
1692 "override",
1693 "payable",
1694 "pragma",
1695 "private",
1696 "public",
1697 "pure",
1698 "receive",
1699 "return",
1700 "returns",
1701 "revert",
1702 "storage",
1703 "string",
1704 "struct",
1705 "true",
1706 "type",
1707 "uint8",
1708 "uint24",
1709 "uint128",
1710 "uint160",
1711 "uint256",
1712 "unchecked",
1713 "using",
1714 "view",
1715 "virtual",
1716 "while",
1717];
1718
1719const ETHER_UNITS: &[(&str, &str)] = &[("wei", "1"), ("gwei", "1e9"), ("ether", "1e18")];
1721
1722const TIME_UNITS: &[(&str, &str)] = &[
1724 ("seconds", "1"),
1725 ("minutes", "60 seconds"),
1726 ("hours", "3600 seconds"),
1727 ("days", "86400 seconds"),
1728 ("weeks", "604800 seconds"),
1729];
1730
1731const MAGIC_GLOBALS: &[(&str, &str)] = &[
1732 ("msg", "msg"),
1733 ("block", "block"),
1734 ("tx", "tx"),
1735 ("abi", "abi"),
1736 ("this", "address"),
1737 ("super", "contract"),
1738 ("type", "type information"),
1739];
1740
1741const GLOBAL_FUNCTIONS: &[(&str, &str)] = &[
1742 ("addmod(uint256, uint256, uint256)", "uint256"),
1744 ("mulmod(uint256, uint256, uint256)", "uint256"),
1745 ("keccak256(bytes memory)", "bytes32"),
1746 ("sha256(bytes memory)", "bytes32"),
1747 ("ripemd160(bytes memory)", "bytes20"),
1748 (
1749 "ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s)",
1750 "address",
1751 ),
1752 ("blockhash(uint256 blockNumber)", "bytes32"),
1754 ("blobhash(uint256 index)", "bytes32"),
1755 ("gasleft()", "uint256"),
1756 ("assert(bool condition)", ""),
1758 ("require(bool condition)", ""),
1759 ("require(bool condition, string memory message)", ""),
1760 ("revert()", ""),
1761 ("revert(string memory reason)", ""),
1762 ("selfdestruct(address payable recipient)", ""),
1764];