1use crate::ast::{FnDef, Module, TopLevel, TypeDef, TypeVariant};
2
3#[derive(Debug, Clone)]
5pub struct ModuleTypeDef {
6 pub bare_name: String,
7 pub kind: ModuleTypeKind,
8}
9
10#[derive(Debug, Clone)]
11pub enum ModuleTypeKind {
12 Record { field_names: Vec<String> },
13 Sum { variant_names: Vec<String> },
14}
15
16pub fn collect_module_types(items: &[TopLevel]) -> Vec<ModuleTypeDef> {
19 items
20 .iter()
21 .filter_map(|item| {
22 let TopLevel::TypeDef(td) = item else {
23 return None;
24 };
25 Some(match td {
26 TypeDef::Product { name, fields, .. } => ModuleTypeDef {
27 bare_name: name.clone(),
28 kind: ModuleTypeKind::Record {
29 field_names: fields.iter().map(|(n, _)| n.clone()).collect(),
30 },
31 },
32 TypeDef::Sum { name, variants, .. } => ModuleTypeDef {
33 bare_name: name.clone(),
34 kind: ModuleTypeKind::Sum {
35 variant_names: variants.iter().map(|v| v.name.clone()).collect(),
36 },
37 },
38 })
39 })
40 .collect()
41}
42
43pub fn is_exposed(name: &str, exposes: Option<&[String]>) -> bool {
47 match exposes {
48 Some(list) => list.iter().any(|e| e == name),
49 None => !name.starts_with('_'),
50 }
51}
52
53pub struct ExportedTypeDef<'a> {
59 pub def: &'a TypeDef,
60 pub is_opaque: bool,
61}
62
63pub struct ModuleExports<'a> {
65 pub functions: Vec<&'a FnDef>,
66 pub types: Vec<ExportedTypeDef<'a>>,
67}
68
69pub fn module_decl(items: &[TopLevel]) -> Option<&Module> {
71 items.iter().find_map(|i| {
72 if let TopLevel::Module(m) = i {
73 Some(m)
74 } else {
75 None
76 }
77 })
78}
79
80pub fn collect_module_exports<'a>(items: &'a [TopLevel]) -> ModuleExports<'a> {
83 let module = module_decl(items);
84
85 let exposes: Option<&[String]> = module.and_then(|m| {
86 if m.exposes.is_empty() {
87 None
88 } else {
89 Some(m.exposes.as_slice())
90 }
91 });
92
93 let opaque_names: Vec<&str> = module
94 .map(|m| m.exposes_opaque.iter().map(|s| s.as_str()).collect())
95 .unwrap_or_default();
96
97 let functions = items
98 .iter()
99 .filter_map(|item| {
100 let TopLevel::FnDef(fd) = item else {
101 return None;
102 };
103 if is_exposed(&fd.name, exposes) {
104 Some(fd)
105 } else {
106 None
107 }
108 })
109 .collect();
110
111 let types = items
112 .iter()
113 .filter_map(|item| {
114 let TopLevel::TypeDef(td) = item else {
115 return None;
116 };
117 let name = match td {
118 TypeDef::Sum { name, .. } | TypeDef::Product { name, .. } => name.as_str(),
119 };
120 let is_opaque = opaque_names.contains(&name);
121 if is_exposed(name, exposes) || is_opaque {
122 Some(ExportedTypeDef { def: td, is_opaque })
123 } else {
124 None
125 }
126 })
127 .collect();
128
129 ModuleExports { functions, types }
130}
131
132pub fn collect_all_module_symbols<'a>(items: &'a [TopLevel]) -> ModuleExports<'a> {
135 let functions = items
136 .iter()
137 .filter_map(|item| {
138 if let TopLevel::FnDef(fd) = item {
139 Some(fd)
140 } else {
141 None
142 }
143 })
144 .collect();
145
146 let module = module_decl(items);
147 let opaque_names: Vec<&str> = module
148 .map(|m| m.exposes_opaque.iter().map(|s| s.as_str()).collect())
149 .unwrap_or_default();
150
151 let types = items
152 .iter()
153 .filter_map(|item| {
154 let TopLevel::TypeDef(td) = item else {
155 return None;
156 };
157 let name = match td {
158 TypeDef::Sum { name, .. } | TypeDef::Product { name, .. } => name.as_str(),
159 };
160 Some(ExportedTypeDef {
161 def: td,
162 is_opaque: opaque_names.contains(&name),
163 })
164 })
165 .collect();
166
167 ModuleExports { functions, types }
168}
169
170pub fn qualified_name(module: &str, name: &str) -> String {
176 format!("{}.{}", module, name)
177}
178
179pub fn member_key(type_name: &str, member: &str) -> String {
181 format!("{}.{}", type_name, member)
182}
183
184pub fn qualified_member_key(module: &str, type_name: &str, member: &str) -> String {
186 format!("{}.{}.{}", module, type_name, member)
187}
188
189#[derive(Debug, Clone)]
195pub struct SymbolEntry {
196 pub id: u32,
197 pub canonical_name: String,
198 pub alias: Option<String>,
199 pub module: String,
200 pub kind: SymbolKind,
201}
202
203#[derive(Debug, Clone)]
204pub enum SymbolKind {
205 Function {
206 name: String,
207 params: Vec<(String, String)>,
208 return_type: String,
209 effects: Vec<String>,
210 },
211 OpaqueType {
212 name: String,
213 },
214 SumType {
215 name: String,
216 variants: Vec<String>,
217 },
218 Constructor {
219 type_name: String,
220 variant_name: String,
221 field_types: Vec<String>,
222 },
223 RecordField {
224 type_name: String,
225 field_name: String,
226 field_type: String,
227 },
228}
229
230#[derive(Debug, Clone, Default)]
232pub struct SymbolRegistry {
233 pub entries: Vec<SymbolEntry>,
234}
235
236impl SymbolRegistry {
237 pub fn from_modules(modules: &[(String, Vec<TopLevel>)]) -> Self {
239 let mut entries = Vec::new();
240 for (module_name, items) in modules {
241 let exports = collect_module_exports(items);
242 Self::collect_from_exports(module_name, &exports, &mut entries);
243 }
244 SymbolRegistry { entries }
245 }
246
247 pub fn from_modules_all(modules: &[(String, Vec<TopLevel>)]) -> Self {
250 let mut entries = Vec::new();
251 for (module_name, items) in modules {
252 let all = collect_all_module_symbols(items);
253 Self::collect_from_exports(module_name, &all, &mut entries);
254 }
255 SymbolRegistry { entries }
256 }
257
258 fn collect_from_exports(
259 module_name: &str,
260 exports: &ModuleExports<'_>,
261 entries: &mut Vec<SymbolEntry>,
262 ) {
263 for fd in &exports.functions {
264 let id = entries.len() as u32;
265 entries.push(SymbolEntry {
266 id,
267 canonical_name: qualified_name(module_name, &fd.name),
268 alias: None,
269 module: module_name.to_string(),
270 kind: SymbolKind::Function {
271 name: fd.name.clone(),
272 params: fd.params.clone(),
273 return_type: fd.return_type.clone(),
274 effects: fd.effects.iter().map(|e| e.node.clone()).collect(),
275 },
276 });
277 }
278
279 for et in &exports.types {
280 match et.def {
281 TypeDef::Sum {
282 name: type_name,
283 variants,
284 ..
285 } => {
286 if et.is_opaque {
287 let id = entries.len() as u32;
288 entries.push(SymbolEntry {
289 id,
290 canonical_name: type_name.clone(),
291 alias: None,
292 module: module_name.to_string(),
293 kind: SymbolKind::OpaqueType {
294 name: type_name.clone(),
295 },
296 });
297 } else {
298 let id = entries.len() as u32;
299 entries.push(SymbolEntry {
300 id,
301 canonical_name: type_name.clone(),
302 alias: None,
303 module: module_name.to_string(),
304 kind: SymbolKind::SumType {
305 name: type_name.clone(),
306 variants: variants.iter().map(|v| v.name.clone()).collect(),
307 },
308 });
309 Self::collect_variant_entries(
310 module_name,
311 type_name.as_str(),
312 variants,
313 entries,
314 );
315 }
316 }
317 TypeDef::Product {
318 name: type_name,
319 fields,
320 ..
321 } => {
322 if et.is_opaque {
323 let id = entries.len() as u32;
324 entries.push(SymbolEntry {
325 id,
326 canonical_name: type_name.clone(),
327 alias: None,
328 module: module_name.to_string(),
329 kind: SymbolKind::OpaqueType {
330 name: type_name.clone(),
331 },
332 });
333 } else {
334 for (field_name, ty_str) in fields {
335 let id = entries.len() as u32;
336 let canonical =
337 qualified_member_key(module_name, type_name, field_name);
338 let alias = member_key(type_name, field_name);
339 entries.push(SymbolEntry {
340 id,
341 canonical_name: canonical,
342 alias: Some(alias),
343 module: module_name.to_string(),
344 kind: SymbolKind::RecordField {
345 type_name: type_name.clone(),
346 field_name: field_name.clone(),
347 field_type: ty_str.clone(),
348 },
349 });
350 }
351 }
352 }
353 }
354 }
355 }
356
357 fn collect_variant_entries(
358 module_name: &str,
359 type_name: &str,
360 variants: &[TypeVariant],
361 entries: &mut Vec<SymbolEntry>,
362 ) {
363 for variant in variants {
364 let id = entries.len() as u32;
365 let canonical = qualified_member_key(module_name, type_name, &variant.name);
366 let alias = member_key(type_name, &variant.name);
367 entries.push(SymbolEntry {
368 id,
369 canonical_name: canonical,
370 alias: Some(alias),
371 module: module_name.to_string(),
372 kind: SymbolKind::Constructor {
373 type_name: type_name.to_string(),
374 variant_name: variant.name.clone(),
375 field_types: variant.fields.clone(),
376 },
377 });
378 }
379 }
380}