1pub mod contracts;
17pub mod host_capabilities;
18pub mod llm_options;
19pub mod runtime_type_tags;
20pub mod shapes;
21pub mod signatures;
22
23pub use contracts::{
24 wire_identifier_key, BuiltinContract, BuiltinExposure, CapabilityId, EffectAccess, EffectKind,
25 EffectSpec, ResourceSelector,
26};
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub struct BuiltinSignature {
33 pub name: &'static str,
35 pub params: &'static [Param],
39 pub returns: Ty,
42 pub type_params: &'static [&'static str],
45 pub has_rest: bool,
49 pub where_clauses: &'static [(&'static str, &'static str)],
52}
53
54impl BuiltinSignature {
55 pub const fn with_name(self, name: &'static str) -> Self {
57 Self { name, ..self }
58 }
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63pub struct Param {
64 pub name: &'static str,
65 pub ty: Ty,
66 pub optional: bool,
69}
70
71impl Param {
72 pub const fn new(name: &'static str, ty: Ty) -> Self {
73 Self {
74 name,
75 ty,
76 optional: false,
77 }
78 }
79
80 pub const fn optional(name: &'static str, ty: Ty) -> Self {
81 Self {
82 name,
83 ty,
84 optional: true,
85 }
86 }
87}
88
89#[derive(Debug, Clone, Copy, PartialEq, Eq)]
94pub enum Ty {
95 Named(&'static str),
99 Generic(&'static str),
102 Any,
105 Optional(&'static Ty),
107 Apply(&'static str, &'static [Ty]),
111 Union(&'static [Ty]),
114 Fn(&'static [Ty], &'static Ty),
117 Shape(&'static [ShapeFieldDescriptor]),
120 OpenShape(&'static [ShapeFieldDescriptor], &'static [Ty]),
129 SchemaOf(&'static str),
133 Never,
135 LitInt(i64),
137 LitString(&'static str),
139}
140
141#[derive(Debug, Clone, Copy, PartialEq, Eq)]
142pub struct ShapeFieldDescriptor {
143 pub name: &'static str,
144 pub ty: Ty,
145 pub optional: bool,
146}
147
148impl ShapeFieldDescriptor {
149 pub const fn new(name: &'static str, ty: Ty) -> Self {
150 Self {
151 name,
152 ty,
153 optional: false,
154 }
155 }
156
157 pub const fn optional(name: &'static str, ty: Ty) -> Self {
158 Self {
159 name,
160 ty,
161 optional: true,
162 }
163 }
164}
165
166impl Ty {
167 pub const fn is_any(&self) -> bool {
169 matches!(self, Ty::Any)
170 }
171}
172
173fn write_shape_members(
179 f: &mut core::fmt::Formatter<'_>,
180 fields: &[ShapeFieldDescriptor],
181 rests: &[Ty],
182) -> core::fmt::Result {
183 for (i, fld) in fields.iter().enumerate() {
184 if i > 0 {
185 f.write_str(", ")?;
186 }
187 let name = fld.name;
188 let ty = &fld.ty;
189 let optional = if fld.optional { "?" } else { "" };
190 write!(f, "{name}{optional}: {ty}")?;
191 }
192 for (i, rest) in rests.iter().enumerate() {
193 if i > 0 || !fields.is_empty() {
194 f.write_str(", ")?;
195 }
196 write!(f, "...{rest}")?;
197 }
198 Ok(())
199}
200
201impl core::fmt::Display for Ty {
202 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
208 match self {
209 Ty::Named(s) | Ty::Generic(s) => f.write_str(s),
210 Ty::Any => f.write_str("any"),
211 Ty::Never => f.write_str("never"),
212 Ty::Optional(inner) => write!(f, "{inner}?"),
213 Ty::Apply(name, args) => {
214 f.write_str(name)?;
215 f.write_str("<")?;
216 for (i, a) in args.iter().enumerate() {
217 if i > 0 {
218 f.write_str(", ")?;
219 }
220 write!(f, "{a}")?;
221 }
222 f.write_str(">")
223 }
224 Ty::Union(parts) => {
225 if let [inner, Ty::Named("nil")] = parts {
229 if !matches!(inner, Ty::Named("nil")) {
230 return write!(f, "{inner}?");
231 }
232 }
233 if let [Ty::Named("int"), Ty::Named("float")] = parts {
234 return f.write_str("number");
235 }
236 for (i, p) in parts.iter().enumerate() {
237 if i > 0 {
238 f.write_str(" | ")?;
239 }
240 write!(f, "{p}")?;
241 }
242 Ok(())
243 }
244 Ty::Fn(params, ret) => {
245 f.write_str("(")?;
246 for (i, p) in params.iter().enumerate() {
247 if i > 0 {
248 f.write_str(", ")?;
249 }
250 write!(f, "{p}")?;
251 }
252 write!(f, ") -> {ret}")
253 }
254 Ty::Shape(fields) => {
255 f.write_str("{")?;
256 write_shape_members(f, fields, &[])?;
257 f.write_str("}")
258 }
259 Ty::OpenShape(fields, rests) => {
260 f.write_str("{")?;
261 write_shape_members(f, fields, rests)?;
262 f.write_str("}")
263 }
264 Ty::SchemaOf(t) => write!(f, "Schema<{t}>"),
265 Ty::LitInt(n) => write!(f, "{n}"),
266 Ty::LitString(s) => write!(f, "\"{s}\""),
267 }
268 }
269}
270
271impl BuiltinSignature {
272 pub const fn simple(name: &'static str, params: &'static [Param], returns: Ty) -> Self {
276 Self {
277 name,
278 params,
279 returns,
280 type_params: &[],
281 has_rest: false,
282 where_clauses: &[],
283 }
284 }
285
286 pub const fn variadic(name: &'static str, params: &'static [Param], returns: Ty) -> Self {
289 Self {
290 name,
291 params,
292 returns,
293 type_params: &[],
294 has_rest: true,
295 where_clauses: &[],
296 }
297 }
298
299 pub const fn generic(
303 name: &'static str,
304 type_params: &'static [&'static str],
305 params: &'static [Param],
306 returns: Ty,
307 ) -> Self {
308 Self {
309 name,
310 params,
311 returns,
312 type_params,
313 has_rest: false,
314 where_clauses: &[],
315 }
316 }
317
318 pub fn required_params(&self) -> usize {
320 self.params.iter().filter(|p| !p.optional).count()
321 }
322
323 pub fn is_type_param(&self, name: &str) -> bool {
326 self.type_params.contains(&name)
327 }
328
329 pub fn is_generic(&self) -> bool {
331 !self.type_params.is_empty()
332 }
333
334 pub fn type_param_names(&self) -> Vec<String> {
338 self.type_params.iter().map(|s| (*s).to_string()).collect()
339 }
340
341 pub fn where_clause_strings(&self) -> Vec<(String, String)> {
343 self.where_clauses
344 .iter()
345 .map(|(tp, iface)| ((*tp).to_string(), (*iface).to_string()))
346 .collect()
347 }
348}
349
350impl core::fmt::Display for BuiltinSignature {
351 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
356 if !self.type_params.is_empty() {
357 f.write_str("<")?;
358 for (i, tp) in self.type_params.iter().enumerate() {
359 if i > 0 {
360 f.write_str(", ")?;
361 }
362 f.write_str(tp)?;
363 }
364 if !self.where_clauses.is_empty() {
365 f.write_str(" where ")?;
366 for (i, (tp, iface)) in self.where_clauses.iter().enumerate() {
367 if i > 0 {
368 f.write_str(", ")?;
369 }
370 write!(f, "{tp}: {iface}")?;
371 }
372 }
373 f.write_str("> ")?;
374 }
375 f.write_str(self.name)?;
376 f.write_str("(")?;
377 let last_idx = self.params.len().saturating_sub(1);
378 for (i, p) in self.params.iter().enumerate() {
379 if i > 0 {
380 f.write_str(", ")?;
381 }
382 if self.has_rest && i == last_idx {
383 f.write_str("...")?;
384 }
385 f.write_str(p.name)?;
386 if p.optional {
387 f.write_str("?")?;
388 }
389 let ty = &p.ty;
390 write!(f, ": {ty}")?;
391 }
392 let ret = &self.returns;
393 write!(f, ") -> {ret}")
394 }
395}
396
397#[derive(Debug, Clone, Copy, PartialEq, Eq)]
400pub struct BuiltinMetadata {
401 pub name: &'static str,
402 pub return_types: &'static [&'static str],
403}
404
405pub const TY_ANY: Ty = Ty::Any;
412pub const TY_BOOL: Ty = Ty::Named("bool");
413pub const TY_BYTES: Ty = Ty::Named("bytes");
414pub const TY_CLOSURE: Ty = Ty::Named("closure");
415pub const TY_DECIMAL: Ty = Ty::Named("decimal");
416pub const TY_DICT: Ty = Ty::Named("dict");
417pub const TY_DURATION: Ty = Ty::Named("duration");
418pub const TY_FLOAT: Ty = Ty::Named("float");
419pub const TY_INT: Ty = Ty::Named("int");
420pub const TY_LIST: Ty = Ty::Named("list");
421pub const TY_NEVER: Ty = Ty::Never;
422pub const TY_NIL: Ty = Ty::Named("nil");
423pub const TY_RESOURCE: Ty = Ty::Named("resource");
424pub const TY_STRING: Ty = Ty::Named("string");
425
426pub const TY_STRING_OR_NIL: Ty = Ty::Union(&[TY_STRING, TY_NIL]);
428pub const TY_INT_OR_NIL: Ty = Ty::Union(&[TY_INT, TY_NIL]);
430pub const TY_DICT_OR_NIL: Ty = Ty::Union(&[TY_DICT, TY_NIL]);
432pub const TY_BYTES_OR_NIL: Ty = Ty::Union(&[TY_BYTES, TY_NIL]);
434pub const TY_NUMBER: Ty = Ty::Union(&[TY_INT, TY_FLOAT]);
436
437#[cfg(test)]
438mod tests {
439 use super::*;
440
441 const APPLY_ARGS: &[Ty] = &[TY_DICT];
442 const FN_PARAMS: &[Ty] = &[TY_INT, TY_STRING];
443 const SHAPE_FIELDS: &[ShapeFieldDescriptor] = &[
444 ShapeFieldDescriptor::new("name", TY_STRING),
445 ShapeFieldDescriptor::optional("age", TY_INT),
446 ];
447
448 const OPEN_SHAPE_RESTS: &[Ty] = &[TY_DICT];
449
450 #[test]
451 fn wire_identifier_keys_stay_unique_across_capabilities() {
452 let mut seen = std::collections::HashMap::new();
457 for capability in CapabilityId::ALL {
458 let key = wire_identifier_key(capability.field_name());
459 if let Some(other) = seen.insert(key.clone(), capability.field_name()) {
460 panic!(
461 "`{other}` and `{}` both normalize to `{key}`",
462 capability.field_name()
463 );
464 }
465 }
466 }
467
468 #[test]
469 fn host_namespace_resolves_the_separatorless_spelling() {
470 assert_eq!(
471 CapabilityId::from_host_namespace("prmonitor"),
472 Some(CapabilityId::PrMonitor)
473 );
474 assert_eq!(
475 CapabilityId::from_host_namespace("PrMonitor"),
476 Some(CapabilityId::PrMonitor)
477 );
478 assert_eq!(
479 CapabilityId::from_host_namespace("pr_monitor"),
480 Some(CapabilityId::PrMonitor)
481 );
482 assert_eq!(CapabilityId::from_host_namespace("not_a_capability"), None);
483 assert_eq!(CapabilityId::from_field_name("prmonitor"), None);
486 }
487
488 #[test]
489 fn ty_display_atomic_and_compound() {
490 assert_eq!(format!("{TY_INT}"), "int");
491 assert_eq!(format!("{TY_ANY}"), "any");
492 assert_eq!(format!("{TY_NEVER}"), "never");
493 assert_eq!(format!("{TY_STRING_OR_NIL}"), "string?");
496 let opt_int = Ty::Optional(&TY_INT);
497 assert_eq!(format!("{opt_int}"), "int?");
498 assert_eq!(format!("{TY_NUMBER}"), "number");
500 let list_dict = Ty::Apply("list", APPLY_ARGS);
501 assert_eq!(format!("{list_dict}"), "list<dict>");
502 let lit_int = Ty::LitInt(42);
503 assert_eq!(format!("{lit_int}"), "42");
504 let lit_str = Ty::LitString("pass");
505 assert_eq!(format!("{lit_str}"), "\"pass\"");
506 let schema_t = Ty::SchemaOf("T");
507 assert_eq!(format!("{schema_t}"), "Schema<T>");
508 let fn_ty = Ty::Fn(FN_PARAMS, &TY_BOOL);
509 assert_eq!(format!("{fn_ty}"), "(int, string) -> bool");
510 let shape = Ty::Shape(SHAPE_FIELDS);
511 assert_eq!(format!("{shape}"), "{name: string, age?: int}");
516 let open = Ty::OpenShape(SHAPE_FIELDS, OPEN_SHAPE_RESTS);
517 assert_eq!(format!("{open}"), "{name: string, age?: int, ...dict}");
518 let tail_only = Ty::OpenShape(&[], OPEN_SHAPE_RESTS);
519 assert_eq!(format!("{tail_only}"), "{...dict}");
520 }
521
522 const BASIC_PARAMS: &[Param] = &[Param::new("a", TY_DICT), Param::new("b", TY_DICT)];
523 const REST_PARAMS: &[Param] = &[Param::new("prefix", TY_STRING), Param::new("args", TY_ANY)];
524 const OPT_PARAMS: &[Param] = &[
525 Param::new("receipt", TY_DICT),
526 Param::optional("candidate", TY_ANY),
527 ];
528 const GENERIC_PARAMS: &[Param] = &[Param::new("schema", Ty::SchemaOf("T"))];
529
530 #[test]
531 fn signature_display_basic() {
532 let sig = BuiltinSignature::simple("deep_merge", BASIC_PARAMS, TY_DICT);
533 assert_eq!(format!("{sig}"), "deep_merge(a: dict, b: dict) -> dict");
534 }
535
536 #[test]
537 fn signature_display_with_optional_and_rest() {
538 let sig = BuiltinSignature {
539 name: "io_println",
540 params: REST_PARAMS,
541 returns: TY_NIL,
542 type_params: &[],
543 has_rest: true,
544 where_clauses: &[],
545 };
546 assert_eq!(
547 format!("{sig}"),
548 "io_println(prefix: string, ...args: any) -> nil"
549 );
550
551 let opt_sig =
552 BuiltinSignature::simple("lifecycle_replay_resume_input", OPT_PARAMS, TY_DICT);
553 assert_eq!(
554 format!("{opt_sig}"),
555 "lifecycle_replay_resume_input(receipt: dict, candidate?: any) -> dict"
556 );
557 }
558
559 #[test]
560 fn signature_display_with_generics_and_where() {
561 let sig = BuiltinSignature {
562 name: "schema_parse",
563 params: GENERIC_PARAMS,
564 returns: Ty::Generic("T"),
565 type_params: &["T"],
566 has_rest: false,
567 where_clauses: &[("T", "Decode")],
568 };
569 assert_eq!(
570 format!("{sig}"),
571 "<T where T: Decode> schema_parse(schema: Schema<T>) -> T"
572 );
573 }
574}