1pub mod llm_options;
17pub mod runtime_type_tags;
18pub mod shapes;
19pub mod signatures;
20
21#[derive(Debug, Clone, Copy)]
25pub struct BuiltinSignature {
26 pub name: &'static str,
28 pub params: &'static [Param],
32 pub returns: Ty,
35 pub type_params: &'static [&'static str],
38 pub has_rest: bool,
42 pub where_clauses: &'static [(&'static str, &'static str)],
45}
46
47#[derive(Debug, Clone, Copy)]
49pub struct Param {
50 pub name: &'static str,
51 pub ty: Ty,
52 pub optional: bool,
55}
56
57impl Param {
58 pub const fn new(name: &'static str, ty: Ty) -> Self {
59 Self {
60 name,
61 ty,
62 optional: false,
63 }
64 }
65
66 pub const fn optional(name: &'static str, ty: Ty) -> Self {
67 Self {
68 name,
69 ty,
70 optional: true,
71 }
72 }
73}
74
75#[derive(Debug, Clone, Copy)]
80pub enum Ty {
81 Named(&'static str),
85 Generic(&'static str),
88 Any,
91 Optional(&'static Ty),
93 Apply(&'static str, &'static [Ty]),
97 Union(&'static [Ty]),
100 Fn(&'static [Ty], &'static Ty),
103 Shape(&'static [ShapeFieldDescriptor]),
105 SchemaOf(&'static str),
109 Never,
111 LitInt(i64),
113 LitString(&'static str),
115}
116
117#[derive(Debug, Clone, Copy)]
118pub struct ShapeFieldDescriptor {
119 pub name: &'static str,
120 pub ty: Ty,
121 pub optional: bool,
122}
123
124impl ShapeFieldDescriptor {
125 pub const fn new(name: &'static str, ty: Ty) -> Self {
126 Self {
127 name,
128 ty,
129 optional: false,
130 }
131 }
132
133 pub const fn optional(name: &'static str, ty: Ty) -> Self {
134 Self {
135 name,
136 ty,
137 optional: true,
138 }
139 }
140}
141
142impl Ty {
143 pub const fn is_any(&self) -> bool {
145 matches!(self, Ty::Any)
146 }
147}
148
149impl core::fmt::Display for Ty {
150 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
156 match self {
157 Ty::Named(s) | Ty::Generic(s) => f.write_str(s),
158 Ty::Any => f.write_str("any"),
159 Ty::Never => f.write_str("never"),
160 Ty::Optional(inner) => write!(f, "{inner}?"),
161 Ty::Apply(name, args) => {
162 f.write_str(name)?;
163 f.write_str("<")?;
164 for (i, a) in args.iter().enumerate() {
165 if i > 0 {
166 f.write_str(", ")?;
167 }
168 write!(f, "{a}")?;
169 }
170 f.write_str(">")
171 }
172 Ty::Union(parts) => {
173 if let [inner, Ty::Named("nil")] = parts {
177 if !matches!(inner, Ty::Named("nil")) {
178 return write!(f, "{inner}?");
179 }
180 }
181 if let [Ty::Named("int"), Ty::Named("float")] = parts {
182 return f.write_str("number");
183 }
184 for (i, p) in parts.iter().enumerate() {
185 if i > 0 {
186 f.write_str(" | ")?;
187 }
188 write!(f, "{p}")?;
189 }
190 Ok(())
191 }
192 Ty::Fn(params, ret) => {
193 f.write_str("(")?;
194 for (i, p) in params.iter().enumerate() {
195 if i > 0 {
196 f.write_str(", ")?;
197 }
198 write!(f, "{p}")?;
199 }
200 write!(f, ") -> {ret}")
201 }
202 Ty::Shape(fields) => {
203 f.write_str("{")?;
204 for (i, fld) in fields.iter().enumerate() {
205 if i > 0 {
206 f.write_str(", ")?;
207 }
208 let name = fld.name;
209 let ty = &fld.ty;
210 write!(f, "{name}: {ty}")?;
211 if fld.optional {
212 f.write_str("?")?;
213 }
214 }
215 f.write_str("}")
216 }
217 Ty::SchemaOf(t) => write!(f, "Schema<{t}>"),
218 Ty::LitInt(n) => write!(f, "{n}"),
219 Ty::LitString(s) => write!(f, "\"{s}\""),
220 }
221 }
222}
223
224impl BuiltinSignature {
225 pub const fn simple(name: &'static str, params: &'static [Param], returns: Ty) -> Self {
229 Self {
230 name,
231 params,
232 returns,
233 type_params: &[],
234 has_rest: false,
235 where_clauses: &[],
236 }
237 }
238
239 pub const fn variadic(name: &'static str, params: &'static [Param], returns: Ty) -> Self {
242 Self {
243 name,
244 params,
245 returns,
246 type_params: &[],
247 has_rest: true,
248 where_clauses: &[],
249 }
250 }
251
252 pub const fn generic(
256 name: &'static str,
257 type_params: &'static [&'static str],
258 params: &'static [Param],
259 returns: Ty,
260 ) -> Self {
261 Self {
262 name,
263 params,
264 returns,
265 type_params,
266 has_rest: false,
267 where_clauses: &[],
268 }
269 }
270
271 pub fn required_params(&self) -> usize {
273 self.params.iter().filter(|p| !p.optional).count()
274 }
275
276 pub fn is_type_param(&self, name: &str) -> bool {
279 self.type_params.contains(&name)
280 }
281
282 pub fn is_generic(&self) -> bool {
284 !self.type_params.is_empty()
285 }
286
287 pub fn type_param_names(&self) -> Vec<String> {
291 self.type_params.iter().map(|s| (*s).to_string()).collect()
292 }
293
294 pub fn where_clause_strings(&self) -> Vec<(String, String)> {
296 self.where_clauses
297 .iter()
298 .map(|(tp, iface)| ((*tp).to_string(), (*iface).to_string()))
299 .collect()
300 }
301}
302
303impl core::fmt::Display for BuiltinSignature {
304 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
309 if !self.type_params.is_empty() {
310 f.write_str("<")?;
311 for (i, tp) in self.type_params.iter().enumerate() {
312 if i > 0 {
313 f.write_str(", ")?;
314 }
315 f.write_str(tp)?;
316 }
317 if !self.where_clauses.is_empty() {
318 f.write_str(" where ")?;
319 for (i, (tp, iface)) in self.where_clauses.iter().enumerate() {
320 if i > 0 {
321 f.write_str(", ")?;
322 }
323 write!(f, "{tp}: {iface}")?;
324 }
325 }
326 f.write_str("> ")?;
327 }
328 f.write_str(self.name)?;
329 f.write_str("(")?;
330 let last_idx = self.params.len().saturating_sub(1);
331 for (i, p) in self.params.iter().enumerate() {
332 if i > 0 {
333 f.write_str(", ")?;
334 }
335 if self.has_rest && i == last_idx {
336 f.write_str("...")?;
337 }
338 f.write_str(p.name)?;
339 if p.optional {
340 f.write_str("?")?;
341 }
342 let ty = &p.ty;
343 write!(f, ": {ty}")?;
344 }
345 let ret = &self.returns;
346 write!(f, ") -> {ret}")
347 }
348}
349
350#[derive(Debug, Clone, Copy, PartialEq, Eq)]
353pub struct BuiltinMetadata {
354 pub name: &'static str,
355 pub return_types: &'static [&'static str],
356}
357
358pub const TY_ANY: Ty = Ty::Any;
365pub const TY_BOOL: Ty = Ty::Named("bool");
366pub const TY_BYTES: Ty = Ty::Named("bytes");
367pub const TY_CLOSURE: Ty = Ty::Named("closure");
368pub const TY_DECIMAL: Ty = Ty::Named("decimal");
369pub const TY_DICT: Ty = Ty::Named("dict");
370pub const TY_DURATION: Ty = Ty::Named("duration");
371pub const TY_FLOAT: Ty = Ty::Named("float");
372pub const TY_INT: Ty = Ty::Named("int");
373pub const TY_LIST: Ty = Ty::Named("list");
374pub const TY_NEVER: Ty = Ty::Never;
375pub const TY_NIL: Ty = Ty::Named("nil");
376pub const TY_STRING: Ty = Ty::Named("string");
377
378pub const TY_STRING_OR_NIL: Ty = Ty::Union(&[TY_STRING, TY_NIL]);
380pub const TY_INT_OR_NIL: Ty = Ty::Union(&[TY_INT, TY_NIL]);
382pub const TY_DICT_OR_NIL: Ty = Ty::Union(&[TY_DICT, TY_NIL]);
384pub const TY_BYTES_OR_NIL: Ty = Ty::Union(&[TY_BYTES, TY_NIL]);
386pub const TY_NUMBER: Ty = Ty::Union(&[TY_INT, TY_FLOAT]);
388
389#[cfg(test)]
390mod tests {
391 use super::*;
392
393 const APPLY_ARGS: &[Ty] = &[TY_DICT];
394 const FN_PARAMS: &[Ty] = &[TY_INT, TY_STRING];
395 const SHAPE_FIELDS: &[ShapeFieldDescriptor] = &[
396 ShapeFieldDescriptor::new("name", TY_STRING),
397 ShapeFieldDescriptor::optional("age", TY_INT),
398 ];
399
400 #[test]
401 fn ty_display_atomic_and_compound() {
402 assert_eq!(format!("{TY_INT}"), "int");
403 assert_eq!(format!("{TY_ANY}"), "any");
404 assert_eq!(format!("{TY_NEVER}"), "never");
405 assert_eq!(format!("{TY_STRING_OR_NIL}"), "string?");
408 let opt_int = Ty::Optional(&TY_INT);
409 assert_eq!(format!("{opt_int}"), "int?");
410 assert_eq!(format!("{TY_NUMBER}"), "number");
412 let list_dict = Ty::Apply("list", APPLY_ARGS);
413 assert_eq!(format!("{list_dict}"), "list<dict>");
414 let lit_int = Ty::LitInt(42);
415 assert_eq!(format!("{lit_int}"), "42");
416 let lit_str = Ty::LitString("pass");
417 assert_eq!(format!("{lit_str}"), "\"pass\"");
418 let schema_t = Ty::SchemaOf("T");
419 assert_eq!(format!("{schema_t}"), "Schema<T>");
420 let fn_ty = Ty::Fn(FN_PARAMS, &TY_BOOL);
421 assert_eq!(format!("{fn_ty}"), "(int, string) -> bool");
422 let shape = Ty::Shape(SHAPE_FIELDS);
423 assert_eq!(format!("{shape}"), "{name: string, age: int?}");
424 }
425
426 const BASIC_PARAMS: &[Param] = &[Param::new("a", TY_DICT), Param::new("b", TY_DICT)];
427 const REST_PARAMS: &[Param] = &[Param::new("prefix", TY_STRING), Param::new("args", TY_ANY)];
428 const OPT_PARAMS: &[Param] = &[
429 Param::new("receipt", TY_DICT),
430 Param::optional("candidate", TY_ANY),
431 ];
432 const GENERIC_PARAMS: &[Param] = &[Param::new("schema", Ty::SchemaOf("T"))];
433
434 #[test]
435 fn signature_display_basic() {
436 let sig = BuiltinSignature::simple("deep_merge", BASIC_PARAMS, TY_DICT);
437 assert_eq!(format!("{sig}"), "deep_merge(a: dict, b: dict) -> dict");
438 }
439
440 #[test]
441 fn signature_display_with_optional_and_rest() {
442 let sig = BuiltinSignature {
443 name: "io_println",
444 params: REST_PARAMS,
445 returns: TY_NIL,
446 type_params: &[],
447 has_rest: true,
448 where_clauses: &[],
449 };
450 assert_eq!(
451 format!("{sig}"),
452 "io_println(prefix: string, ...args: any) -> nil"
453 );
454
455 let opt_sig =
456 BuiltinSignature::simple("lifecycle_replay_resume_input", OPT_PARAMS, TY_DICT);
457 assert_eq!(
458 format!("{opt_sig}"),
459 "lifecycle_replay_resume_input(receipt: dict, candidate?: any) -> dict"
460 );
461 }
462
463 #[test]
464 fn signature_display_with_generics_and_where() {
465 let sig = BuiltinSignature {
466 name: "schema_parse",
467 params: GENERIC_PARAMS,
468 returns: Ty::Generic("T"),
469 type_params: &["T"],
470 has_rest: false,
471 where_clauses: &[("T", "Decode")],
472 };
473 assert_eq!(
474 format!("{sig}"),
475 "<T where T: Decode> schema_parse(schema: Schema<T>) -> T"
476 );
477 }
478}