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    /// Top-level entry for a `record` (product) type. Lets
219    /// `Type::Named("MyRecord")` resolve to a canonical
220    /// `Module.MyRecord` the same way sum types do, instead of
221    /// being implicit-from-its-fields and forcing per-backend
222    /// special-casing of the bare name.
223    ProductType {
224        name: String,
225        fields: Vec<String>,
226    },
227    Constructor {
228        type_name: String,
229        variant_name: String,
230        field_types: Vec<String>,
231    },
232    RecordField {
233        type_name: String,
234        field_name: String,
235        field_type: String,
236    },
237}
238
239/// All symbols exported by a module tree — canonical source of truth.
240#[derive(Debug, Clone, Default)]
241pub struct SymbolRegistry {
242    pub entries: Vec<SymbolEntry>,
243}
244
245impl SymbolRegistry {
246    /// Build a registry of exported symbols from a set of loaded modules.
247    pub fn from_modules(modules: &[(String, Vec<TopLevel>)]) -> Self {
248        let mut entries = Vec::new();
249        for (module_name, items) in modules {
250            let exports = collect_module_exports(items);
251            Self::collect_from_exports(module_name, &exports, &mut entries);
252        }
253        SymbolRegistry { entries }
254    }
255
256    /// Build a registry of ALL symbols (including private) from loaded modules.
257    /// Used by codegen which emits full module implementations.
258    pub fn from_modules_all(modules: &[(String, Vec<TopLevel>)]) -> Self {
259        let mut entries = Vec::new();
260        for (module_name, items) in modules {
261            let all = collect_all_module_symbols(items);
262            Self::collect_from_exports(module_name, &all, &mut entries);
263        }
264        SymbolRegistry { entries }
265    }
266
267    fn collect_from_exports(
268        module_name: &str,
269        exports: &ModuleExports<'_>,
270        entries: &mut Vec<SymbolEntry>,
271    ) {
272        for fd in &exports.functions {
273            let id = entries.len() as u32;
274            entries.push(SymbolEntry {
275                id,
276                canonical_name: qualified_name(module_name, &fd.name),
277                alias: None,
278                module: module_name.to_string(),
279                kind: SymbolKind::Function {
280                    name: fd.name.clone(),
281                    params: fd.params.clone(),
282                    return_type: fd.return_type.clone(),
283                    effects: fd.effects.iter().map(|e| e.node.clone()).collect(),
284                },
285            });
286        }
287
288        for et in &exports.types {
289            match et.def {
290                TypeDef::Sum {
291                    name: type_name,
292                    variants,
293                    ..
294                } => {
295                    if et.is_opaque {
296                        let id = entries.len() as u32;
297                        entries.push(SymbolEntry {
298                            id,
299                            canonical_name: qualified_name(module_name, type_name),
300                            alias: Some(type_name.clone()),
301                            module: module_name.to_string(),
302                            kind: SymbolKind::OpaqueType {
303                                name: type_name.clone(),
304                            },
305                        });
306                    } else {
307                        let id = entries.len() as u32;
308                        entries.push(SymbolEntry {
309                            id,
310                            canonical_name: qualified_name(module_name, type_name),
311                            alias: Some(type_name.clone()),
312                            module: module_name.to_string(),
313                            kind: SymbolKind::SumType {
314                                name: type_name.clone(),
315                                variants: variants.iter().map(|v| v.name.clone()).collect(),
316                            },
317                        });
318                        Self::collect_variant_entries(
319                            module_name,
320                            type_name.as_str(),
321                            variants,
322                            entries,
323                        );
324                    }
325                }
326                TypeDef::Product {
327                    name: type_name,
328                    fields,
329                    ..
330                } => {
331                    if et.is_opaque {
332                        let id = entries.len() as u32;
333                        entries.push(SymbolEntry {
334                            id,
335                            canonical_name: qualified_name(module_name, type_name),
336                            alias: Some(type_name.clone()),
337                            module: module_name.to_string(),
338                            kind: SymbolKind::OpaqueType {
339                                name: type_name.clone(),
340                            },
341                        });
342                    } else {
343                        // Record types also get a top-level entry alongside their
344                        // per-field entries. Pre-A3 they were implicit (only the
345                        // RecordField entries existed), which left no SymbolEntry
346                        // for `Type::Named("MyRecord")` to resolve against and
347                        // forced every backend to special-case the bare name.
348                        let id = entries.len() as u32;
349                        entries.push(SymbolEntry {
350                            id,
351                            canonical_name: qualified_name(module_name, type_name),
352                            alias: Some(type_name.clone()),
353                            module: module_name.to_string(),
354                            kind: SymbolKind::ProductType {
355                                name: type_name.clone(),
356                                fields: fields.iter().map(|(n, _)| n.clone()).collect(),
357                            },
358                        });
359                        for (field_name, ty_str) in fields {
360                            let id = entries.len() as u32;
361                            let canonical =
362                                qualified_member_key(module_name, type_name, field_name);
363                            let alias = member_key(type_name, field_name);
364                            entries.push(SymbolEntry {
365                                id,
366                                canonical_name: canonical,
367                                alias: Some(alias),
368                                module: module_name.to_string(),
369                                kind: SymbolKind::RecordField {
370                                    type_name: type_name.clone(),
371                                    field_name: field_name.clone(),
372                                    field_type: ty_str.clone(),
373                                },
374                            });
375                        }
376                    }
377                }
378            }
379        }
380    }
381
382    fn collect_variant_entries(
383        module_name: &str,
384        type_name: &str,
385        variants: &[TypeVariant],
386        entries: &mut Vec<SymbolEntry>,
387    ) {
388        for variant in variants {
389            let id = entries.len() as u32;
390            let canonical = qualified_member_key(module_name, type_name, &variant.name);
391            let alias = member_key(type_name, &variant.name);
392            entries.push(SymbolEntry {
393                id,
394                canonical_name: canonical,
395                alias: Some(alias),
396                module: module_name.to_string(),
397                kind: SymbolKind::Constructor {
398                    type_name: type_name.to_string(),
399                    variant_name: variant.name.clone(),
400                    field_types: variant.fields.clone(),
401                },
402            });
403        }
404    }
405}