use std::collections::{BTreeMap, HashSet};
use std::path::Path;
use harn_parser::{Node, SNode, TypeExpr, TypedParam};
use crate::{normalize_path, ModuleGraph};
const MAX_INLINE_DEPTH: usize = 16;
#[derive(Debug, Clone, PartialEq)]
pub struct NamespaceMemberSignature {
pub param_names: Vec<String>,
pub required_params: usize,
pub fn_type: TypeExpr,
}
fn is_builtin_type_name(name: &str) -> bool {
matches!(
name,
"int"
| "float"
| "string"
| "bool"
| "nil"
| "list"
| "dict"
| "set"
| "closure"
| "bytes"
| "any"
| "unknown"
| "never"
| "number"
| "Harness"
| "_"
)
}
impl ModuleGraph {
pub(crate) fn namespace_member_signatures(
&self,
module_path: &Path,
member_names: &[String],
) -> BTreeMap<String, NamespaceMemberSignature> {
let mut out = BTreeMap::new();
for name in member_names {
let mut visited = HashSet::new();
let Some(decl) = self.find_exported_callable_decl(module_path, name, &mut visited)
else {
continue;
};
let origin = self
.export_definition_of(module_path, name)
.map_or_else(|| normalize_path(module_path), |site| site.file);
if let Some(signature) = self.lower_callable_signature(&origin, &decl) {
out.insert(name.clone(), signature);
}
}
out
}
fn lower_callable_signature(
&self,
origin: &Path,
decl: &SNode,
) -> Option<NamespaceMemberSignature> {
let inner = match &decl.node {
Node::AttributedDecl { inner, .. } => inner.as_ref(),
_ => decl,
};
let (params, return_type) = match &inner.node {
Node::FnDecl {
params,
return_type,
type_params,
..
} => {
if !type_params.is_empty() {
return None;
}
(params, return_type)
}
Node::Pipeline {
params,
return_type,
..
} => (params, return_type),
_ => return None,
};
if params.iter().any(|param| param.rest) {
return None;
}
let lowered: Vec<TypeExpr> = params
.iter()
.map(|param| self.lower_param_type(origin, param))
.collect();
let ret = return_type
.as_ref()
.map(|ty| self.inline_named_types(origin, ty, &mut HashSet::new(), 0))
.unwrap_or(TypeExpr::Named("any".into()));
Some(NamespaceMemberSignature {
param_names: params.iter().map(|param| param.name.clone()).collect(),
required_params: params
.iter()
.position(|param| param.default_value.is_some())
.unwrap_or(params.len()),
fn_type: TypeExpr::FnType {
params: lowered,
return_type: Box::new(ret),
},
})
}
fn lower_param_type(&self, origin: &Path, param: &TypedParam) -> TypeExpr {
let Some(declared) = ¶m.type_expr else {
return TypeExpr::Named("any".into());
};
if param.default_value.is_some() {
return TypeExpr::Named("any".into());
}
self.inline_named_types(origin, declared, &mut HashSet::new(), 0)
}
fn inline_named_types(
&self,
origin: &Path,
ty: &TypeExpr,
visited: &mut HashSet<String>,
depth: usize,
) -> TypeExpr {
let recurse = |graph: &Self, inner: &TypeExpr, visited: &mut HashSet<String>| {
graph.inline_named_types(origin, inner, visited, depth + 1)
};
match ty {
TypeExpr::Named(name) => {
if is_builtin_type_name(name) {
return ty.clone();
}
if depth >= MAX_INLINE_DEPTH || !visited.insert(name.clone()) {
return TypeExpr::Named("any".into());
}
let resolved = self
.find_exported_type_decl(origin, name, &mut HashSet::new())
.or_else(|| self.local_type_decl(origin, name));
let body = match resolved.as_ref().map(|decl| &decl.node) {
Some(Node::TypeDecl {
type_params,
type_expr,
..
}) if type_params.is_empty() => {
self.inline_named_types(origin, type_expr, visited, depth + 1)
}
_ => TypeExpr::Named("any".into()),
};
visited.remove(name);
body
}
TypeExpr::Union(items) => TypeExpr::Union(
items
.iter()
.map(|item| recurse(self, item, visited))
.collect(),
),
TypeExpr::Intersection(items) => TypeExpr::Intersection(
items
.iter()
.map(|item| recurse(self, item, visited))
.collect(),
),
TypeExpr::Shape(fields) => TypeExpr::Shape(
fields
.iter()
.map(|field| {
let mut next = field.clone();
next.type_expr = recurse(self, &field.type_expr, visited);
next
})
.collect(),
),
TypeExpr::List(inner) => TypeExpr::List(Box::new(recurse(self, inner, visited))),
TypeExpr::Iter(inner) => TypeExpr::Iter(Box::new(recurse(self, inner, visited))),
TypeExpr::Owned(inner) => TypeExpr::Owned(Box::new(recurse(self, inner, visited))),
TypeExpr::Tuple(items) => TypeExpr::Tuple(
items
.iter()
.map(|item| recurse(self, item, visited))
.collect(),
),
TypeExpr::DictType(key, value) => TypeExpr::DictType(
Box::new(recurse(self, key, visited)),
Box::new(recurse(self, value, visited)),
),
TypeExpr::OpenShape { .. }
| TypeExpr::Generator(_)
| TypeExpr::Stream(_)
| TypeExpr::Applied { .. }
| TypeExpr::FnType { .. } => TypeExpr::Named("any".into()),
TypeExpr::Never | TypeExpr::LitString(_) | TypeExpr::LitInt(_) => ty.clone(),
}
}
fn local_type_decl(&self, module_path: &Path, name: &str) -> Option<SNode> {
let module = self
.modules
.get(module_path)
.or_else(|| self.modules.get(&normalize_path(module_path)))?;
module
.type_declarations
.iter()
.find(|decl| crate::type_decl_name(decl) == Some(name))
.cloned()
}
}