1use std::collections::{BTreeMap, HashSet};
17use std::path::Path;
18
19use harn_parser::{Node, SNode, TypeExpr, TypePredicate, TypedParam};
20
21use crate::{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 module_path: &Path,
115 member_names: &[String],
116 ) -> BTreeMap<String, NamespaceMemberSignature> {
117 let mut out = BTreeMap::new();
118 for name in member_names {
119 let mut visited = HashSet::new();
120 let Some(decl) = self.find_exported_callable_decl(module_path, name, &mut visited)
121 else {
122 continue;
123 };
124 let origin = self
128 .export_definition_of(module_path, name)
129 .map_or_else(|| normalize_path(module_path), |site| site.file);
130 if let Some(signature) = self.lower_callable_signature(&origin, &decl) {
131 out.insert(name.clone(), signature);
132 }
133 }
134 out
135 }
136
137 fn lower_callable_signature(
138 &self,
139 origin: &Path,
140 decl: &SNode,
141 ) -> Option<NamespaceMemberSignature> {
142 let inner = match &decl.node {
143 Node::AttributedDecl { inner, .. } => inner.as_ref(),
144 _ => decl,
145 };
146 let (params, return_type, type_predicate) = match &inner.node {
147 Node::FnDecl {
148 params,
149 return_type,
150 type_predicate,
151 type_params,
152 ..
153 } => {
154 if !type_params.is_empty() {
159 return None;
160 }
161 (params, return_type, type_predicate.as_ref())
162 }
163 Node::Pipeline {
164 params,
165 return_type,
166 ..
167 } => (params, return_type, None),
168 _ => return None,
169 };
170 if params.iter().any(|param| param.rest) {
173 return None;
174 }
175 let lowered: Vec<TypeExpr> = params
176 .iter()
177 .map(|param| self.lower_param_type(origin, param))
178 .collect();
179 let ret = return_type
180 .as_ref()
181 .map(|ty| self.inline_named_types(origin, ty, &mut HashSet::new(), 0))
182 .unwrap_or(TypeExpr::Named("any".into()));
183 let type_predicate = type_predicate.and_then(|predicate| {
184 let type_expr =
185 self.inline_named_types(origin, &predicate.type_expr, &mut HashSet::new(), 0);
186 (!contains_gradual_type(&type_expr)).then(|| TypePredicate {
187 parameter: predicate.parameter.clone(),
188 type_expr,
189 one_sided: predicate.one_sided,
190 span: predicate.span,
191 })
192 });
193 Some(NamespaceMemberSignature {
194 param_names: params.iter().map(|param| param.name.clone()).collect(),
195 required_params: params
196 .iter()
197 .position(|param| param.default_value.is_some())
198 .unwrap_or(params.len()),
199 fn_type: TypeExpr::FnType {
200 params: lowered,
201 return_type: Box::new(ret),
202 },
203 type_predicate,
204 })
205 }
206
207 fn lower_param_type(&self, origin: &Path, param: &TypedParam) -> TypeExpr {
211 let Some(declared) = ¶m.type_expr else {
212 return TypeExpr::Named("any".into());
213 };
214 if param.default_value.is_some() {
215 return TypeExpr::Named("any".into());
216 }
217 self.inline_named_types(origin, declared, &mut HashSet::new(), 0)
218 }
219
220 fn inline_named_types(
229 &self,
230 origin: &Path,
231 ty: &TypeExpr,
232 visited: &mut HashSet<String>,
233 depth: usize,
234 ) -> TypeExpr {
235 let recurse = |graph: &Self, inner: &TypeExpr, visited: &mut HashSet<String>| {
236 graph.inline_named_types(origin, inner, visited, depth + 1)
237 };
238 match ty {
239 TypeExpr::Named(name) => {
240 if is_builtin_type_name(name) {
241 return ty.clone();
242 }
243 if depth >= MAX_INLINE_DEPTH || !visited.insert(name.clone()) {
244 return TypeExpr::Named("any".into());
245 }
246 let resolved = self
247 .find_exported_type_decl(origin, name, &mut HashSet::new())
248 .or_else(|| self.local_type_decl(origin, name));
249 let body = match resolved.as_ref().map(|decl| &decl.node) {
250 Some(Node::TypeDecl {
251 type_params,
252 type_expr,
253 ..
254 }) if type_params.is_empty() => {
255 self.inline_named_types(origin, type_expr, visited, depth + 1)
256 }
257 _ => TypeExpr::Named("any".into()),
258 };
259 visited.remove(name);
260 body
261 }
262 TypeExpr::Union(items) => TypeExpr::Union(
263 items
264 .iter()
265 .map(|item| recurse(self, item, visited))
266 .collect(),
267 ),
268 TypeExpr::Intersection(items) => TypeExpr::Intersection(
269 items
270 .iter()
271 .map(|item| recurse(self, item, visited))
272 .collect(),
273 ),
274 TypeExpr::Shape(fields) => TypeExpr::Shape(
275 fields
276 .iter()
277 .map(|field| {
278 let mut next = field.clone();
279 next.type_expr = recurse(self, &field.type_expr, visited);
280 next
281 })
282 .collect(),
283 ),
284 TypeExpr::List(inner) => TypeExpr::List(Box::new(recurse(self, inner, visited))),
285 TypeExpr::Iter(inner) => TypeExpr::Iter(Box::new(recurse(self, inner, visited))),
286 TypeExpr::Owned(inner) => TypeExpr::Owned(Box::new(recurse(self, inner, visited))),
287 TypeExpr::Tuple(items) => TypeExpr::Tuple(
288 items
289 .iter()
290 .map(|item| recurse(self, item, visited))
291 .collect(),
292 ),
293 TypeExpr::DictType(key, value) => TypeExpr::DictType(
294 Box::new(recurse(self, key, visited)),
295 Box::new(recurse(self, value, visited)),
296 ),
297 TypeExpr::OpenShape { .. }
302 | TypeExpr::Generator(_)
303 | TypeExpr::Stream(_)
304 | TypeExpr::Applied { .. }
305 | TypeExpr::FnType { .. } => TypeExpr::Named("any".into()),
306 TypeExpr::Never | TypeExpr::LitString(_) | TypeExpr::LitInt(_) => ty.clone(),
307 }
308 }
309
310 fn local_type_decl(&self, module_path: &Path, name: &str) -> Option<SNode> {
311 let module = self
312 .modules
313 .get(module_path)
314 .or_else(|| self.modules.get(&normalize_path(module_path)))?;
315 module
316 .type_declarations
317 .iter()
318 .find(|decl| crate::type_decl_name(decl) == Some(name))
319 .cloned()
320 }
321}