Skip to main content

harn_builtin_meta/
lib.rs

1//! Const-constructible type definitions for Harn builtin signatures.
2//!
3//! Both `harn-parser` (for typechecking) and `harn-vm` (for runtime metadata)
4//! consume these shapes. Living in a dep-free crate lets the parser see the
5//! types without depending on the VM, and lets the `#[harn_builtin]` proc-macro
6//! emit `const` literals that link into either side.
7//!
8//! `Ty::to_type_expr` and friends, which convert into the parser's runtime
9//! `TypeExpr`, live in `harn-parser` since they depend on parser-internal AST.
10
11/// A complete, static description of one builtin: identifier, arity range,
12/// per-parameter types, generic type parameters, return type, and any
13/// where-clause bounds the type checker should enforce on call.
14#[derive(Debug, Clone, Copy)]
15pub struct BuiltinSignature {
16    /// Builtin name as registered in the VM and referenced from Harn source.
17    pub name: &'static str,
18    /// Positional parameters in declaration order. Trailing entries with
19    /// `optional: true` define the lower bound of the arity range; the
20    /// remaining entries plus `has_rest` define the upper bound.
21    pub params: &'static [Param],
22    /// Statically-known return type. Use [`Ty::Any`] when the return is
23    /// genuinely dynamic (e.g. `json_parse`).
24    pub returns: Ty,
25    /// Generic type parameter names declared on this builtin (e.g. `["T"]`
26    /// for `schema_parse<T>`).
27    pub type_params: &'static [&'static str],
28    /// True when the final parameter is variadic (rest). When set, the
29    /// effective arity upper bound is unbounded and the runtime will treat
30    /// trailing args as the rest-list.
31    pub has_rest: bool,
32    /// `where T: Foo` constraints. Each entry binds a generic type
33    /// parameter name to the name of an interface it must implement.
34    pub where_clauses: &'static [(&'static str, &'static str)],
35}
36
37/// One parameter slot inside a [`BuiltinSignature`].
38#[derive(Debug, Clone, Copy)]
39pub struct Param {
40    pub name: &'static str,
41    pub ty: Ty,
42    /// True when this parameter has a default at the call site (so it may
43    /// be omitted). All optional params must be trailing.
44    pub optional: bool,
45}
46
47impl Param {
48    pub const fn new(name: &'static str, ty: Ty) -> Self {
49        Self {
50            name,
51            ty,
52            optional: false,
53        }
54    }
55
56    pub const fn optional(name: &'static str, ty: Ty) -> Self {
57        Self {
58            name,
59            ty,
60            optional: true,
61        }
62    }
63}
64
65/// `const`-friendly type IR used in builtin descriptors. Mirrors the runtime
66/// `TypeExpr` from `harn-parser` but is constructable in `const` position with
67/// no allocation. Convert to `TypeExpr` at the boundary via the parser-side
68/// `Ty::to_type_expr` helper.
69#[derive(Debug, Clone, Copy)]
70pub enum Ty {
71    /// A primitive or user-defined named type: `int`, `string`, `bool`,
72    /// `float`, `nil`, `bytes`, `dict`, `list`, `closure`, `duration`,
73    /// `any`, etc.
74    Named(&'static str),
75    /// Reference to a generic type parameter declared on the enclosing
76    /// signature (e.g. `Generic("T")`).
77    Generic(&'static str),
78    /// Untyped/dynamic. Skips type validation at runtime; the static
79    /// checker treats it as compatible with everything.
80    Any,
81    /// Optional sugar for `T | nil`.
82    Optional(&'static Ty),
83    /// Generic application: `List<T>` is `Apply("list", &[T])`,
84    /// `Result<T, E>` is `Apply("Result", &[T, E])`, `Schema<T>` is
85    /// [`Ty::SchemaOf`].
86    Apply(&'static str, &'static [Ty]),
87    /// Union of N alternatives. Empty unions are rejected by the
88    /// parser-side converter.
89    Union(&'static [Ty]),
90    /// Function type. Stores params and return as references so the literal
91    /// stays `Copy`.
92    Fn(&'static [Ty], &'static Ty),
93    /// Record/shape type with named fields.
94    Shape(&'static [ShapeFieldDescriptor]),
95    /// `Schema<T>` marker — semantically `Apply("Schema", &[Generic(T)])`
96    /// but distinguished so the type checker can pull the bound `T` from
97    /// the *value* of the schema arg (not its declared type).
98    SchemaOf(&'static str),
99    /// Bottom type (no return).
100    Never,
101    /// Integer literal type: `0`, `1`. Assignable to `int`.
102    LitInt(i64),
103    /// String literal type: `"pass"`. Assignable to `string`.
104    LitString(&'static str),
105}
106
107#[derive(Debug, Clone, Copy)]
108pub struct ShapeFieldDescriptor {
109    pub name: &'static str,
110    pub ty: Ty,
111    pub optional: bool,
112}
113
114impl ShapeFieldDescriptor {
115    pub const fn new(name: &'static str, ty: Ty) -> Self {
116        Self {
117            name,
118            ty,
119            optional: false,
120        }
121    }
122
123    pub const fn optional(name: &'static str, ty: Ty) -> Self {
124        Self {
125            name,
126            ty,
127            optional: true,
128        }
129    }
130}
131
132impl Ty {
133    /// True when this type carries no constraints (validation is a no-op).
134    pub const fn is_any(&self) -> bool {
135        matches!(self, Ty::Any)
136    }
137}
138
139impl BuiltinSignature {
140    /// Non-generic, fixed-arity builtin: no type parameters, no rest, no
141    /// where-clause bounds. Covers ~70% of the registry; lets each call
142    /// site stay on a single logical line.
143    pub const fn simple(name: &'static str, params: &'static [Param], returns: Ty) -> Self {
144        Self {
145            name,
146            params,
147            returns,
148            type_params: &[],
149            has_rest: false,
150            where_clauses: &[],
151        }
152    }
153
154    /// Non-generic builtin whose final parameter is variadic (rest).
155    /// Equivalent to [`Self::simple`] with `has_rest: true`.
156    pub const fn variadic(name: &'static str, params: &'static [Param], returns: Ty) -> Self {
157        Self {
158            name,
159            params,
160            returns,
161            type_params: &[],
162            has_rest: true,
163            where_clauses: &[],
164        }
165    }
166
167    /// Generic, fixed-arity builtin: declares type parameters, no rest,
168    /// no where-clause bounds. Use the struct literal directly when both
169    /// generics and where-clauses or rest are needed.
170    pub const fn generic(
171        name: &'static str,
172        type_params: &'static [&'static str],
173        params: &'static [Param],
174        returns: Ty,
175    ) -> Self {
176        Self {
177            name,
178            params,
179            returns,
180            type_params,
181            has_rest: false,
182            where_clauses: &[],
183        }
184    }
185
186    /// Number of required parameters (those without defaults).
187    pub fn required_params(&self) -> usize {
188        self.params.iter().filter(|p| !p.optional).count()
189    }
190
191    /// True when this builtin recognises `name` as one of its declared
192    /// generic type parameters.
193    pub fn is_type_param(&self, name: &str) -> bool {
194        self.type_params.contains(&name)
195    }
196
197    /// True when this builtin declares any generic type parameters.
198    pub fn is_generic(&self) -> bool {
199        !self.type_params.is_empty()
200    }
201
202    /// Materialize the type parameter names as owned strings (for use in
203    /// the type checker's existing scope/binding APIs which key off
204    /// `Vec<String>`).
205    pub fn type_param_names(&self) -> Vec<String> {
206        self.type_params.iter().map(|s| (*s).to_string()).collect()
207    }
208
209    /// Where-clause constraints as `(type_param, interface)` strings.
210    pub fn where_clause_strings(&self) -> Vec<(String, String)> {
211        self.where_clauses
212            .iter()
213            .map(|(tp, iface)| ((*tp).to_string(), (*iface).to_string()))
214            .collect()
215    }
216}
217
218/// Public view of one builtin used by `harn-lint` and other crates that need
219/// just identifier + return-type hints (no parameter types).
220#[derive(Debug, Clone, Copy, PartialEq, Eq)]
221pub struct BuiltinMetadata {
222    pub name: &'static str,
223    pub return_types: &'static [&'static str],
224}
225
226// ---- Convenience constants ----
227//
228// Used pervasively in builtin signature literals to keep individual entries
229// terse. Add new constants here when a type appears repeatedly enough to
230// warrant a shorthand (avoid one-off shorthands).
231
232pub const TY_ANY: Ty = Ty::Any;
233pub const TY_BOOL: Ty = Ty::Named("bool");
234pub const TY_BYTES: Ty = Ty::Named("bytes");
235pub const TY_CLOSURE: Ty = Ty::Named("closure");
236pub const TY_DICT: Ty = Ty::Named("dict");
237pub const TY_DURATION: Ty = Ty::Named("duration");
238pub const TY_FLOAT: Ty = Ty::Named("float");
239pub const TY_INT: Ty = Ty::Named("int");
240pub const TY_LIST: Ty = Ty::Named("list");
241pub const TY_NEVER: Ty = Ty::Never;
242pub const TY_NIL: Ty = Ty::Named("nil");
243pub const TY_STRING: Ty = Ty::Named("string");
244
245/// `string | nil`.
246pub const TY_STRING_OR_NIL: Ty = Ty::Union(&[TY_STRING, TY_NIL]);
247/// `int | nil`.
248pub const TY_INT_OR_NIL: Ty = Ty::Union(&[TY_INT, TY_NIL]);
249/// `dict | nil`.
250pub const TY_DICT_OR_NIL: Ty = Ty::Union(&[TY_DICT, TY_NIL]);
251/// `bytes | nil`.
252pub const TY_BYTES_OR_NIL: Ty = Ty::Union(&[TY_BYTES, TY_NIL]);
253/// `int | float`.
254pub const TY_NUMBER: Ty = Ty::Union(&[TY_INT, TY_FLOAT]);