1use std::collections::{BTreeMap, HashSet};
17use std::path::Path;
18
19use harn_parser::{Node, SNode, TypeExpr, TypePredicate, TypedParam};
20
21use crate::{callable_decl_name, normalize_path, ModuleGraph};
22
23const MAX_INLINE_DEPTH: usize = 16;
29
30#[derive(Debug, Clone, PartialEq)]
37pub struct NamespaceMemberSignature {
38 pub param_names: Vec<String>,
39 pub required_params: usize,
44 pub fn_type: TypeExpr,
45 pub type_predicate: Option<TypePredicate>,
47}
48
49fn is_builtin_type_name(name: &str) -> bool {
51 matches!(
52 name,
53 "int"
54 | "float"
55 | "string"
56 | "bool"
57 | "nil"
58 | "list"
59 | "dict"
60 | "set"
61 | "closure"
62 | "bytes"
63 | "any"
64 | "unknown"
65 | "never"
66 | "number"
67 | "Harness"
68 | "_"
69 )
70}
71
72fn contains_gradual_type(ty: &TypeExpr) -> bool {
73 match ty {
74 TypeExpr::Named(name) => matches!(name.as_str(), "any" | "unknown" | "_"),
75 TypeExpr::Union(items) | TypeExpr::Intersection(items) | TypeExpr::Tuple(items) => {
76 items.iter().any(contains_gradual_type)
77 }
78 TypeExpr::Shape(fields) => fields
79 .iter()
80 .any(|field| contains_gradual_type(&field.type_expr)),
81 TypeExpr::OpenShape { fields, rests } => {
82 fields
83 .iter()
84 .any(|field| contains_gradual_type(&field.type_expr))
85 || rests.iter().any(contains_gradual_type)
86 }
87 TypeExpr::List(inner)
88 | TypeExpr::Iter(inner)
89 | TypeExpr::Generator(inner)
90 | TypeExpr::Stream(inner)
91 | TypeExpr::Owned(inner) => contains_gradual_type(inner),
92 TypeExpr::DictType(key, value) => {
93 contains_gradual_type(key) || contains_gradual_type(value)
94 }
95 TypeExpr::Applied { args, .. } => args.iter().any(contains_gradual_type),
96 TypeExpr::FnType {
97 params,
98 return_type,
99 } => params.iter().any(contains_gradual_type) || contains_gradual_type(return_type),
100 TypeExpr::Never | TypeExpr::LitString(_) | TypeExpr::LitInt(_) => false,
101 }
102}
103
104impl ModuleGraph {
105 pub(crate) fn namespace_member_signatures(
113 &self,
114 importer: &Path,
115 module_path: &Path,
116 member_names: &[String],
117 ) -> BTreeMap<String, NamespaceMemberSignature> {
118 let mut out = BTreeMap::new();
119 for name in member_names {
120 let mut visited = HashSet::new();
121 let sibling_decl = crate::sibling_module_access(importer, module_path)
122 .then(|| self.modules.get(&normalize_path(module_path)))
123 .flatten()
124 .filter(|module| module.sibling_exports.contains(name))
125 .and_then(|module| {
126 module
127 .callable_declarations
128 .iter()
129 .find(|decl| callable_decl_name(decl) == Some(name.as_str()))
130 .cloned()
131 });
132 let Some(decl) = sibling_decl
133 .or_else(|| self.find_exported_callable_decl(module_path, name, &mut visited))
134 else {
135 continue;
136 };
137 let origin = self
141 .export_definition_of(module_path, name)
142 .map_or_else(|| normalize_path(module_path), |site| site.file);
143 if let Some(signature) = self.lower_callable_signature(&origin, &decl) {
144 out.insert(name.clone(), signature);
145 }
146 }
147 out
148 }
149
150 fn lower_callable_signature(
151 &self,
152 origin: &Path,
153 decl: &SNode,
154 ) -> Option<NamespaceMemberSignature> {
155 let inner = match &decl.node {
156 Node::AttributedDecl { inner, .. } => inner.as_ref(),
157 _ => decl,
158 };
159 let (params, return_type, type_predicate) = match &inner.node {
160 Node::FnDecl {
161 params,
162 return_type,
163 type_predicate,
164 type_params,
165 ..
166 } => {
167 if !type_params.is_empty() {
172 return None;
173 }
174 (params, return_type, type_predicate.as_ref())
175 }
176 Node::Pipeline {
177 params,
178 return_type,
179 ..
180 } => (params, return_type, None),
181 _ => return None,
182 };
183 if params.iter().any(|param| param.rest) {
186 return None;
187 }
188 let lowered: Vec<TypeExpr> = params
189 .iter()
190 .map(|param| self.lower_param_type(origin, param))
191 .collect();
192 let ret = return_type
193 .as_ref()
194 .map(|ty| self.inline_named_types(origin, ty, &mut HashSet::new(), 0))
195 .unwrap_or(TypeExpr::Named("any".into()));
196 let type_predicate = type_predicate.and_then(|predicate| {
197 let type_expr =
198 self.inline_named_types(origin, &predicate.type_expr, &mut HashSet::new(), 0);
199 (!contains_gradual_type(&type_expr)).then(|| TypePredicate {
200 parameter: predicate.parameter.clone(),
201 type_expr,
202 one_sided: predicate.one_sided,
203 span: predicate.span,
204 })
205 });
206 Some(NamespaceMemberSignature {
207 param_names: params.iter().map(|param| param.name.clone()).collect(),
208 required_params: params
209 .iter()
210 .position(|param| param.default_value.is_some())
211 .unwrap_or(params.len()),
212 fn_type: TypeExpr::FnType {
213 params: lowered,
214 return_type: Box::new(ret),
215 },
216 type_predicate,
217 })
218 }
219
220 fn lower_param_type(&self, origin: &Path, param: &TypedParam) -> TypeExpr {
224 let Some(declared) = ¶m.type_expr else {
225 return TypeExpr::Named("any".into());
226 };
227 if param.default_value.is_some() {
228 return TypeExpr::Named("any".into());
229 }
230 self.inline_named_types(origin, declared, &mut HashSet::new(), 0)
231 }
232
233 fn inline_named_types(
242 &self,
243 origin: &Path,
244 ty: &TypeExpr,
245 visited: &mut HashSet<String>,
246 depth: usize,
247 ) -> TypeExpr {
248 let recurse = |graph: &Self, inner: &TypeExpr, visited: &mut HashSet<String>| {
249 graph.inline_named_types(origin, inner, visited, depth + 1)
250 };
251 match ty {
252 TypeExpr::Named(name) => {
253 if is_builtin_type_name(name) {
254 return ty.clone();
255 }
256 if depth >= MAX_INLINE_DEPTH || !visited.insert(name.clone()) {
257 return TypeExpr::Named("any".into());
258 }
259 let resolved = self
260 .find_exported_type_decl(origin, name, &mut HashSet::new())
261 .or_else(|| self.local_type_decl(origin, name));
262 let body = match resolved.as_ref().map(|decl| &decl.node) {
263 Some(Node::TypeDecl {
264 type_params,
265 type_expr,
266 ..
267 }) if type_params.is_empty() => {
268 self.inline_named_types(origin, type_expr, visited, depth + 1)
269 }
270 _ => TypeExpr::Named("any".into()),
271 };
272 visited.remove(name);
273 body
274 }
275 TypeExpr::Union(items) => TypeExpr::Union(
276 items
277 .iter()
278 .map(|item| recurse(self, item, visited))
279 .collect(),
280 ),
281 TypeExpr::Intersection(items) => TypeExpr::Intersection(
282 items
283 .iter()
284 .map(|item| recurse(self, item, visited))
285 .collect(),
286 ),
287 TypeExpr::Shape(fields) => TypeExpr::Shape(
288 fields
289 .iter()
290 .map(|field| {
291 let mut next = field.clone();
292 next.type_expr = recurse(self, &field.type_expr, visited);
293 next
294 })
295 .collect(),
296 ),
297 TypeExpr::List(inner) => TypeExpr::List(Box::new(recurse(self, inner, visited))),
298 TypeExpr::Iter(inner) => TypeExpr::Iter(Box::new(recurse(self, inner, visited))),
299 TypeExpr::Owned(inner) => TypeExpr::Owned(Box::new(recurse(self, inner, visited))),
300 TypeExpr::Tuple(items) => TypeExpr::Tuple(
301 items
302 .iter()
303 .map(|item| recurse(self, item, visited))
304 .collect(),
305 ),
306 TypeExpr::DictType(key, value) => TypeExpr::DictType(
307 Box::new(recurse(self, key, visited)),
308 Box::new(recurse(self, value, visited)),
309 ),
310 TypeExpr::OpenShape { .. }
315 | TypeExpr::Generator(_)
316 | TypeExpr::Stream(_)
317 | TypeExpr::Applied { .. }
318 | TypeExpr::FnType { .. } => TypeExpr::Named("any".into()),
319 TypeExpr::Never | TypeExpr::LitString(_) | TypeExpr::LitInt(_) => ty.clone(),
320 }
321 }
322
323 fn local_type_decl(&self, module_path: &Path, name: &str) -> Option<SNode> {
324 let module = self
325 .modules
326 .get(module_path)
327 .or_else(|| self.modules.get(&normalize_path(module_path)))?;
328 module
329 .type_declarations
330 .iter()
331 .find(|decl| crate::type_decl_name(decl) == Some(name))
332 .cloned()
333 }
334}