Skip to main content

harn_parser/
namespace_signatures.rs

1//! Self-contained namespace call contracts shared by graph and package compilation.
2
3use std::collections::{BTreeMap, BTreeSet, HashSet};
4
5use crate::{NamespaceImportBinding, Node, SNode, TypeExpr, TypePredicate};
6
7const MAX_INLINE_DEPTH: usize = 16;
8
9/// A namespace member's signature with defining-module aliases inlined.
10#[derive(Debug, Clone, PartialEq)]
11pub struct NamespaceMemberSignature {
12    pub param_names: Vec<String>,
13    /// Everything up to the first defaulted parameter is required.
14    pub required_params: usize,
15    pub fn_type: TypeExpr,
16    pub type_predicate: Option<TypePredicate>,
17}
18
19impl NamespaceImportBinding {
20    /// Build the checker's namespace projection from resolved member contracts.
21    pub fn from_member_signatures(
22        module_path: String,
23        members: BTreeSet<String>,
24        signatures: BTreeMap<String, NamespaceMemberSignature>,
25    ) -> Self {
26        let mut binding = Self {
27            module_path,
28            members,
29            member_types: BTreeMap::new(),
30            member_param_names: BTreeMap::new(),
31            member_required_params: BTreeMap::new(),
32            member_type_predicates: BTreeMap::new(),
33        };
34        for (name, signature) in signatures {
35            binding.member_types.insert(name.clone(), signature.fn_type);
36            binding
37                .member_param_names
38                .insert(name.clone(), signature.param_names);
39            binding
40                .member_required_params
41                .insert(name.clone(), signature.required_params);
42            if let Some(predicate) = signature.type_predicate {
43                binding.member_type_predicates.insert(name, predicate);
44            }
45        }
46        binding
47    }
48}
49
50/// Lower a callable using the defining module's alias resolver.
51///
52/// Generic and rest signatures stay gradual. Unresolved or recursive aliases
53/// also stay gradual rather than inventing a structural contract.
54pub fn lower_callable_signature(
55    declaration: &SNode,
56    resolve_alias: impl Fn(&str) -> Option<SNode>,
57) -> Option<NamespaceMemberSignature> {
58    let inner = match &declaration.node {
59        Node::AttributedDecl { inner, .. } => inner.as_ref(),
60        _ => declaration,
61    };
62    let (params, return_type, type_predicate) = match &inner.node {
63        Node::FnDecl {
64            params,
65            return_type,
66            type_predicate,
67            type_params,
68            ..
69        } if type_params.is_empty() => (params, return_type, type_predicate.as_ref()),
70        Node::Pipeline {
71            params,
72            return_type,
73            ..
74        } => (params, return_type, None),
75        _ => return None,
76    };
77    if params.iter().any(|param| param.rest) {
78        return None;
79    }
80    let inline = |ty: &TypeExpr| inline_named_types(ty, &resolve_alias, &mut HashSet::new(), 0);
81    let lowered = params
82        .iter()
83        .map(|param| match &param.type_expr {
84            Some(ty) if param.default_value.is_none() => inline(ty),
85            _ => TypeExpr::Named("any".into()),
86        })
87        .collect();
88    let ret = return_type
89        .as_ref()
90        .map(inline)
91        .unwrap_or(TypeExpr::Named("any".into()));
92    let type_predicate = type_predicate.and_then(|predicate| {
93        let type_expr = inline(&predicate.type_expr);
94        (!contains_gradual_type(&type_expr)).then(|| TypePredicate {
95            parameter: predicate.parameter.clone(),
96            type_expr,
97            one_sided: predicate.one_sided,
98            span: predicate.span,
99        })
100    });
101    Some(NamespaceMemberSignature {
102        param_names: params.iter().map(|param| param.name.clone()).collect(),
103        required_params: params
104            .iter()
105            .position(|param| param.default_value.is_some())
106            .unwrap_or(params.len()),
107        fn_type: TypeExpr::FnType {
108            params: lowered,
109            return_type: Box::new(ret),
110        },
111        type_predicate,
112    })
113}
114
115fn is_builtin_type_name(name: &str) -> bool {
116    matches!(
117        name,
118        "int"
119            | "float"
120            | "string"
121            | "bool"
122            | "nil"
123            | "list"
124            | "dict"
125            | "set"
126            | "closure"
127            | "bytes"
128            | "any"
129            | "unknown"
130            | "never"
131            | "number"
132            | "Harness"
133            | "_"
134    )
135}
136
137fn contains_gradual_type(ty: &TypeExpr) -> bool {
138    match ty {
139        TypeExpr::Named(name) => matches!(name.as_str(), "any" | "unknown" | "_"),
140        TypeExpr::Union(items) | TypeExpr::Intersection(items) | TypeExpr::Tuple(items) => {
141            items.iter().any(contains_gradual_type)
142        }
143        TypeExpr::Shape(fields) => fields
144            .iter()
145            .any(|field| contains_gradual_type(&field.type_expr)),
146        TypeExpr::OpenShape { fields, rests } => {
147            fields
148                .iter()
149                .any(|field| contains_gradual_type(&field.type_expr))
150                || rests.iter().any(contains_gradual_type)
151        }
152        TypeExpr::List(inner)
153        | TypeExpr::Iter(inner)
154        | TypeExpr::Generator(inner)
155        | TypeExpr::Stream(inner)
156        | TypeExpr::Owned(inner) => contains_gradual_type(inner),
157        TypeExpr::DictType(key, value) => {
158            contains_gradual_type(key) || contains_gradual_type(value)
159        }
160        TypeExpr::Applied { args, .. } => args.iter().any(contains_gradual_type),
161        TypeExpr::FnType {
162            params,
163            return_type,
164        } => params.iter().any(contains_gradual_type) || contains_gradual_type(return_type),
165        TypeExpr::Never | TypeExpr::LitString(_) | TypeExpr::LitInt(_) => false,
166    }
167}
168
169fn inline_named_types(
170    ty: &TypeExpr,
171    resolve_alias: &impl Fn(&str) -> Option<SNode>,
172    visited: &mut HashSet<String>,
173    depth: usize,
174) -> TypeExpr {
175    let recurse = |inner: &TypeExpr, visited: &mut HashSet<String>| {
176        inline_named_types(inner, resolve_alias, visited, depth + 1)
177    };
178    match ty {
179        TypeExpr::Named(name) => {
180            if is_builtin_type_name(name) {
181                return ty.clone();
182            }
183            if depth >= MAX_INLINE_DEPTH || !visited.insert(name.clone()) {
184                return TypeExpr::Named("any".into());
185            }
186            let resolved = resolve_alias(name);
187            let body = match resolved.as_ref().map(|decl| &decl.node) {
188                Some(Node::TypeDecl {
189                    type_params,
190                    type_expr,
191                    ..
192                }) if type_params.is_empty() => {
193                    inline_named_types(type_expr, resolve_alias, visited, depth + 1)
194                }
195                _ => TypeExpr::Named("any".into()),
196            };
197            visited.remove(name);
198            body
199        }
200        TypeExpr::Union(items) => {
201            TypeExpr::Union(items.iter().map(|item| recurse(item, visited)).collect())
202        }
203        TypeExpr::Intersection(items) => {
204            TypeExpr::Intersection(items.iter().map(|item| recurse(item, visited)).collect())
205        }
206        TypeExpr::Shape(fields) => TypeExpr::Shape(
207            fields
208                .iter()
209                .map(|field| {
210                    let mut next = field.clone();
211                    next.type_expr = recurse(&field.type_expr, visited);
212                    next
213                })
214                .collect(),
215        ),
216        TypeExpr::List(inner) => TypeExpr::List(Box::new(recurse(inner, visited))),
217        TypeExpr::Iter(inner) => TypeExpr::Iter(Box::new(recurse(inner, visited))),
218        TypeExpr::Owned(inner) => TypeExpr::Owned(Box::new(recurse(inner, visited))),
219        TypeExpr::Tuple(items) => {
220            TypeExpr::Tuple(items.iter().map(|item| recurse(item, visited)).collect())
221        }
222        TypeExpr::DictType(key, value) => TypeExpr::DictType(
223            Box::new(recurse(key, visited)),
224            Box::new(recurse(value, visited)),
225        ),
226        TypeExpr::OpenShape { .. }
227        | TypeExpr::Generator(_)
228        | TypeExpr::Stream(_)
229        | TypeExpr::Applied { .. }
230        | TypeExpr::FnType { .. } => TypeExpr::Named("any".into()),
231        TypeExpr::Never | TypeExpr::LitString(_) | TypeExpr::LitInt(_) => ty.clone(),
232    }
233}