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