shape_lsp/completion/
stdlib_methods.rs1use crate::type_inference::{MethodCompletionInfo, extract_type_methods};
22use shape_ast::parser::parse_program;
23use shape_runtime::module_loader::ModuleLoader;
24use shape_runtime::stdlib_metadata::default_stdlib_path;
25use std::collections::HashMap;
26use std::sync::OnceLock;
27
28type MethodMap = HashMap<String, Vec<MethodCompletionInfo>>;
29
30static STDLIB_TYPE_METHODS: OnceLock<MethodMap> = OnceLock::new();
31
32pub fn stdlib_type_methods() -> &'static MethodMap {
37 STDLIB_TYPE_METHODS.get_or_init(load_stdlib_type_methods)
38}
39
40fn load_stdlib_type_methods() -> MethodMap {
41 let mut out: MethodMap = HashMap::new();
42
43 let stdlib_path = default_stdlib_path();
44 if !stdlib_path.is_dir() {
45 return out;
46 }
47
48 let mut loader = ModuleLoader::new();
49 loader.set_stdlib_path(stdlib_path);
50
51 let imports = match loader.list_stdlib_module_imports() {
52 Ok(imports) => imports,
53 Err(_) => return out,
54 };
55
56 for import_path in imports {
57 let Ok(module) = loader.load_module(&import_path) else {
58 continue;
59 };
60 merge_methods(&mut out, extract_type_methods(&module.ast));
61 }
62
63 out
64}
65
66fn merge_methods(dst: &mut MethodMap, src: MethodMap) {
67 for (type_name, methods) in src {
68 let entry = dst.entry(type_name).or_default();
69 for m in methods {
70 if !entry.iter().any(|existing| existing.name == m.name) {
71 entry.push(m);
72 }
73 }
74 }
75}
76
77#[cfg(test)]
80fn load_stdlib_type_methods_uncached() -> MethodMap {
81 load_stdlib_type_methods()
82}
83
84#[allow(dead_code)]
89fn load_stdlib_type_methods_via_walk() -> MethodMap {
90 let mut out: MethodMap = HashMap::new();
91 let stdlib_path = default_stdlib_path();
92 if !stdlib_path.is_dir() {
93 return out;
94 }
95 let mut stack = vec![stdlib_path];
96 while let Some(dir) = stack.pop() {
97 let Ok(entries) = std::fs::read_dir(&dir) else {
98 continue;
99 };
100 for entry in entries.flatten() {
101 let path = entry.path();
102 if path.is_dir() {
103 stack.push(path);
104 } else if path.extension().is_some_and(|e| e == "shape") {
105 if let Ok(src) = std::fs::read_to_string(&path) {
106 if let Ok(program) = parse_program(&src) {
107 merge_methods(&mut out, extract_type_methods(&program));
108 }
109 }
110 }
111 }
112 }
113 out
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 #[test]
121 fn stdlib_vec_methods_include_map_and_filter() {
122 let methods = load_stdlib_type_methods_uncached();
127 let vec_methods = methods.get("Vec").cloned().unwrap_or_default();
128 let names: Vec<&str> = vec_methods.iter().map(|m| m.name.as_str()).collect();
129 assert!(
130 names.contains(&"map"),
131 "expected Vec.map from stdlib extend block, got {:?}",
132 names
133 );
134 assert!(
135 names.contains(&"filter"),
136 "expected Vec.filter from stdlib extend block, got {:?}",
137 names
138 );
139 }
140
141 #[test]
142 fn stdlib_type_methods_cached_call_returns_same_handle() {
143 let a = stdlib_type_methods();
145 let b = stdlib_type_methods();
146 assert!(std::ptr::eq(a, b));
147 }
148}