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_DECIMAL: Ty = Ty::Named("decimal");
367pub const TY_DICT: Ty = Ty::Named("dict");
368pub const TY_DURATION: Ty = Ty::Named("duration");
369pub const TY_FLOAT: Ty = Ty::Named("float");
370pub const TY_INT: Ty = Ty::Named("int");
371pub const TY_LIST: Ty = Ty::Named("list");
372pub const TY_NEVER: Ty = Ty::Never;
373pub const TY_NIL: Ty = Ty::Named("nil");
374pub const TY_STRING: Ty = Ty::Named("string");
375
376pub const TY_STRING_OR_NIL: Ty = Ty::Union(&[TY_STRING, TY_NIL]);
378pub const TY_INT_OR_NIL: Ty = Ty::Union(&[TY_INT, TY_NIL]);
380pub const TY_DICT_OR_NIL: Ty = Ty::Union(&[TY_DICT, TY_NIL]);
382pub const TY_BYTES_OR_NIL: Ty = Ty::Union(&[TY_BYTES, TY_NIL]);
384pub const TY_NUMBER: Ty = Ty::Union(&[TY_INT, TY_FLOAT]);
386
387#[cfg(test)]
388mod tests {
389 use super::*;
390
391 const APPLY_ARGS: &[Ty] = &[TY_DICT];
392 const FN_PARAMS: &[Ty] = &[TY_INT, TY_STRING];
393 const SHAPE_FIELDS: &[ShapeFieldDescriptor] = &[
394 ShapeFieldDescriptor::new("name", TY_STRING),
395 ShapeFieldDescriptor::optional("age", TY_INT),
396 ];
397
398 #[test]
399 fn ty_display_atomic_and_compound() {
400 assert_eq!(format!("{TY_INT}"), "int");
401 assert_eq!(format!("{TY_ANY}"), "any");
402 assert_eq!(format!("{TY_NEVER}"), "never");
403 assert_eq!(format!("{TY_STRING_OR_NIL}"), "string?");
406 let opt_int = Ty::Optional(&TY_INT);
407 assert_eq!(format!("{opt_int}"), "int?");
408 assert_eq!(format!("{TY_NUMBER}"), "number");
410 let list_dict = Ty::Apply("list", APPLY_ARGS);
411 assert_eq!(format!("{list_dict}"), "list<dict>");
412 let lit_int = Ty::LitInt(42);
413 assert_eq!(format!("{lit_int}"), "42");
414 let lit_str = Ty::LitString("pass");
415 assert_eq!(format!("{lit_str}"), "\"pass\"");
416 let schema_t = Ty::SchemaOf("T");
417 assert_eq!(format!("{schema_t}"), "Schema<T>");
418 let fn_ty = Ty::Fn(FN_PARAMS, &TY_BOOL);
419 assert_eq!(format!("{fn_ty}"), "(int, string) -> bool");
420 let shape = Ty::Shape(SHAPE_FIELDS);
421 assert_eq!(format!("{shape}"), "{name: string, age: int?}");
422 }
423
424 const BASIC_PARAMS: &[Param] = &[Param::new("a", TY_DICT), Param::new("b", TY_DICT)];
425 const REST_PARAMS: &[Param] = &[Param::new("prefix", TY_STRING), Param::new("args", TY_ANY)];
426 const OPT_PARAMS: &[Param] = &[
427 Param::new("receipt", TY_DICT),
428 Param::optional("candidate", TY_ANY),
429 ];
430 const GENERIC_PARAMS: &[Param] = &[Param::new("schema", Ty::SchemaOf("T"))];
431
432 #[test]
433 fn signature_display_basic() {
434 let sig = BuiltinSignature::simple("deep_merge", BASIC_PARAMS, TY_DICT);
435 assert_eq!(format!("{sig}"), "deep_merge(a: dict, b: dict) -> dict");
436 }
437
438 #[test]
439 fn signature_display_with_optional_and_rest() {
440 let sig = BuiltinSignature {
441 name: "io_println",
442 params: REST_PARAMS,
443 returns: TY_NIL,
444 type_params: &[],
445 has_rest: true,
446 where_clauses: &[],
447 };
448 assert_eq!(
449 format!("{sig}"),
450 "io_println(prefix: string, ...args: any) -> nil"
451 );
452
453 let opt_sig =
454 BuiltinSignature::simple("lifecycle_replay_resume_input", OPT_PARAMS, TY_DICT);
455 assert_eq!(
456 format!("{opt_sig}"),
457 "lifecycle_replay_resume_input(receipt: dict, candidate?: any) -> dict"
458 );
459 }
460
461 #[test]
462 fn signature_display_with_generics_and_where() {
463 let sig = BuiltinSignature {
464 name: "schema_parse",
465 params: GENERIC_PARAMS,
466 returns: Ty::Generic("T"),
467 type_params: &["T"],
468 has_rest: false,
469 where_clauses: &[("T", "Decode")],
470 };
471 assert_eq!(
472 format!("{sig}"),
473 "<T where T: Decode> schema_parse(schema: Schema<T>) -> T"
474 );
475 }
476}