use formalang::ast::Visibility;
use formalang::ir::{EnumId, FunctionId, IrModule, StructId};
#[derive(Debug, Default, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct PublicSurface {
pub exports: Vec<FunctionId>,
pub imports: Vec<FunctionId>,
pub exported_structs: Vec<StructId>,
pub exported_enums: Vec<EnumId>,
}
#[must_use]
pub fn survey(module: &IrModule) -> PublicSurface {
let mut surface = PublicSurface::default();
for (idx, f) in module.functions.iter().enumerate() {
let id = FunctionId(u32_from_index(idx));
if f.is_extern() {
surface.imports.push(id);
continue;
}
if f.name.starts_with("__") {
continue;
}
surface.exports.push(id);
}
for (idx, s) in module.structs.iter().enumerate() {
if !matches!(s.visibility, Visibility::Public) {
continue;
}
let id = StructId(u32_from_index(idx));
if Some(id) == module.prelude_array_id()
|| Some(id) == module.prelude_dictionary_id()
|| Some(id) == module.prelude_range_id()
{
continue;
}
if s.fields.is_empty() {
continue;
}
surface.exported_structs.push(id);
}
for (idx, e) in module.enums.iter().enumerate() {
if !matches!(e.visibility, Visibility::Public) {
continue;
}
let id = EnumId(u32_from_index(idx));
if Some(id) == module.prelude_optional_id() {
continue;
}
surface.exported_enums.push(id);
}
surface
}
const fn u32_from_index(idx: usize) -> u32 {
if idx > u32::MAX as usize {
u32::MAX
} else {
#[expect(
clippy::cast_possible_truncation,
reason = "guarded by the bound check above"
)]
let cast = idx as u32;
cast
}
}