Skip to main content

aver/
visibility.rs

1use crate::ast::{FnDef, Module, TopLevel, TypeDef, TypeVariant};
2
3/// Type definition collected from a module — backend-agnostic metadata.
4#[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
16/// Collect all type definitions from parsed AST items.
17/// Pure function over AST — no backend state.
18pub 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
43/// Check whether a module item is exposed to the outside.
44/// `exposes = None` means the module uses the default rule (hide `_`-prefixed items).
45/// `exposes = Some(list)` means only items in the explicit list are exposed.
46pub 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
53// ---------------------------------------------------------------------------
54// Module exports — the shared answer to "what can importers see?"
55// ---------------------------------------------------------------------------
56
57/// An exported type definition with its opaque flag.
58pub struct ExportedTypeDef<'a> {
59    pub def: &'a TypeDef,
60    pub is_opaque: bool,
61}
62
63/// Everything a module exports — functions and types that passed visibility filtering.
64pub struct ModuleExports<'a> {
65    pub functions: Vec<&'a FnDef>,
66    pub types: Vec<ExportedTypeDef<'a>>,
67}
68
69/// Extract the module declaration from parsed items.
70pub 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
80/// Collect all exported items from a parsed module.
81/// Applies visibility rules: exposes list, underscore convention, opaque types.
82pub 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
132/// Collect ALL functions and types from a module — no visibility filtering.
133/// Used by codegen which emits full module implementations including private helpers.
134pub 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
170// ---------------------------------------------------------------------------
171// Canonical symbol key construction
172// ---------------------------------------------------------------------------
173
174/// "Module.function" — qualified name for cross-module function references.
175pub fn qualified_name(module: &str, name: &str) -> String {
176    format!("{}.{}", module, name)
177}
178
179/// "Type.member" — type-scoped key (constructor, field, variant).
180pub fn member_key(type_name: &str, member: &str) -> String {
181    format!("{}.{}", type_name, member)
182}
183
184/// "Module.Type.member" — fully-qualified type-scoped key.
185pub fn qualified_member_key(module: &str, type_name: &str, member: &str) -> String {
186    format!("{}.{}.{}", module, type_name, member)
187}
188
189// ---------------------------------------------------------------------------
190// SymbolRegistry — aggregated view of all module exports
191// ---------------------------------------------------------------------------
192
193/// A registered symbol with its canonical name and kind.
194#[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/// All symbols exported by a module tree — canonical source of truth.
231#[derive(Debug, Clone, Default)]
232pub struct SymbolRegistry {
233    pub entries: Vec<SymbolEntry>,
234}
235
236impl SymbolRegistry {
237    /// Build a registry of exported symbols from a set of loaded modules.
238    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    /// Build a registry of ALL symbols (including private) from loaded modules.
248    /// Used by codegen which emits full module implementations.
249    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}