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