1pub mod contracts;
17pub mod host_capabilities;
18pub mod llm_options;
19pub mod predicate;
20pub mod runtime_type_tags;
21pub mod shapes;
22pub mod signatures;
23
24pub use contracts::{
25 wire_identifier_key, BuiltinContract, BuiltinExposure, CapabilityId, EffectAccess,
26 EffectAuthorization, EffectKind, EffectSpec, ResourceSelector,
27};
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub struct BuiltinSignature {
34 pub name: &'static str,
36 pub params: &'static [Param],
40 pub returns: Ty,
43 pub type_params: &'static [&'static str],
46 pub has_rest: bool,
50 pub where_clauses: &'static [(&'static str, &'static str)],
53 pub projection: Option<RecordProjection>,
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59pub enum RecordProjection {
60 Pick { source: usize, keys: usize },
61}
62
63impl BuiltinSignature {
64 pub const fn with_name(self, name: &'static str) -> Self {
66 Self { name, ..self }
67 }
68
69 pub const fn with_projection(self, projection: RecordProjection) -> Self {
70 Self {
71 projection: Some(projection),
72 ..self
73 }
74 }
75}
76
77#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79pub struct Param {
80 pub name: &'static str,
81 pub ty: Ty,
82 pub optional: bool,
85}
86
87impl Param {
88 pub const fn new(name: &'static str, ty: Ty) -> Self {
89 Self {
90 name,
91 ty,
92 optional: false,
93 }
94 }
95
96 pub const fn optional(name: &'static str, ty: Ty) -> Self {
97 Self {
98 name,
99 ty,
100 optional: true,
101 }
102 }
103}
104
105#[derive(Debug, Clone, Copy, PartialEq, Eq)]
110pub enum Ty {
111 Named(&'static str),
115 Generic(&'static str),
118 Any,
121 Optional(&'static Ty),
123 Apply(&'static str, &'static [Ty]),
127 Union(&'static [Ty]),
130 Fn(&'static [Ty], &'static Ty),
133 Shape(&'static [ShapeFieldDescriptor]),
136 OpenShape(&'static [ShapeFieldDescriptor], &'static [Ty]),
145 SchemaOf(&'static str),
149 Never,
151 LitInt(i64),
153 LitString(&'static str),
155}
156
157#[derive(Debug, Clone, Copy, PartialEq, Eq)]
158pub struct ShapeFieldDescriptor {
159 pub name: &'static str,
160 pub ty: Ty,
161 pub optional: bool,
162}
163
164impl ShapeFieldDescriptor {
165 pub const fn new(name: &'static str, ty: Ty) -> Self {
166 Self {
167 name,
168 ty,
169 optional: false,
170 }
171 }
172
173 pub const fn optional(name: &'static str, ty: Ty) -> Self {
174 Self {
175 name,
176 ty,
177 optional: true,
178 }
179 }
180}
181
182impl Ty {
183 pub const fn is_any(&self) -> bool {
185 matches!(self, Ty::Any)
186 }
187}
188
189fn write_shape_members(
195 f: &mut core::fmt::Formatter<'_>,
196 fields: &[ShapeFieldDescriptor],
197 rests: &[Ty],
198) -> core::fmt::Result {
199 for (i, fld) in fields.iter().enumerate() {
200 if i > 0 {
201 f.write_str(", ")?;
202 }
203 let name = fld.name;
204 let ty = &fld.ty;
205 let optional = if fld.optional { "?" } else { "" };
206 write!(f, "{name}{optional}: {ty}")?;
207 }
208 for (i, rest) in rests.iter().enumerate() {
209 if i > 0 || !fields.is_empty() {
210 f.write_str(", ")?;
211 }
212 write!(f, "...{rest}")?;
213 }
214 Ok(())
215}
216
217impl core::fmt::Display for Ty {
218 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
224 match self {
225 Ty::Named(s) | Ty::Generic(s) => f.write_str(s),
226 Ty::Any => f.write_str("any"),
227 Ty::Never => f.write_str("never"),
228 Ty::Optional(inner) => write!(f, "{inner}?"),
229 Ty::Apply(name, args) => {
230 f.write_str(name)?;
231 f.write_str("<")?;
232 for (i, a) in args.iter().enumerate() {
233 if i > 0 {
234 f.write_str(", ")?;
235 }
236 write!(f, "{a}")?;
237 }
238 f.write_str(">")
239 }
240 Ty::Union(parts) => {
241 if let [inner, Ty::Named("nil")] = parts {
245 if !matches!(inner, Ty::Named("nil")) {
246 return write!(f, "{inner}?");
247 }
248 }
249 if let [Ty::Named("int"), Ty::Named("float")] = parts {
250 return f.write_str("number");
251 }
252 for (i, p) in parts.iter().enumerate() {
253 if i > 0 {
254 f.write_str(" | ")?;
255 }
256 write!(f, "{p}")?;
257 }
258 Ok(())
259 }
260 Ty::Fn(params, ret) => {
261 f.write_str("(")?;
262 for (i, p) in params.iter().enumerate() {
263 if i > 0 {
264 f.write_str(", ")?;
265 }
266 write!(f, "{p}")?;
267 }
268 write!(f, ") -> {ret}")
269 }
270 Ty::Shape(fields) => {
271 f.write_str("{")?;
272 write_shape_members(f, fields, &[])?;
273 f.write_str("}")
274 }
275 Ty::OpenShape(fields, rests) => {
276 f.write_str("{")?;
277 write_shape_members(f, fields, rests)?;
278 f.write_str("}")
279 }
280 Ty::SchemaOf(t) => write!(f, "Schema<{t}>"),
281 Ty::LitInt(n) => write!(f, "{n}"),
282 Ty::LitString(s) => write!(f, "\"{s}\""),
283 }
284 }
285}
286
287impl BuiltinSignature {
288 pub const fn simple(name: &'static str, params: &'static [Param], returns: Ty) -> Self {
292 Self {
293 name,
294 params,
295 returns,
296 type_params: &[],
297 has_rest: false,
298 where_clauses: &[],
299 projection: None,
300 }
301 }
302
303 pub const fn variadic(name: &'static str, params: &'static [Param], returns: Ty) -> Self {
306 Self {
307 name,
308 params,
309 returns,
310 type_params: &[],
311 has_rest: true,
312 where_clauses: &[],
313 projection: None,
314 }
315 }
316
317 pub const fn generic(
321 name: &'static str,
322 type_params: &'static [&'static str],
323 params: &'static [Param],
324 returns: Ty,
325 ) -> Self {
326 Self {
327 name,
328 params,
329 returns,
330 type_params,
331 has_rest: false,
332 where_clauses: &[],
333 projection: None,
334 }
335 }
336
337 pub fn required_params(&self) -> usize {
339 self.params.iter().filter(|p| !p.optional).count()
340 }
341
342 pub fn is_type_param(&self, name: &str) -> bool {
345 self.type_params.contains(&name)
346 }
347
348 pub fn is_generic(&self) -> bool {
350 !self.type_params.is_empty()
351 }
352
353 pub fn type_param_names(&self) -> Vec<String> {
357 self.type_params.iter().map(|s| (*s).to_string()).collect()
358 }
359
360 pub fn where_clause_strings(&self) -> Vec<(String, String)> {
362 self.where_clauses
363 .iter()
364 .map(|(tp, iface)| ((*tp).to_string(), (*iface).to_string()))
365 .collect()
366 }
367}
368
369impl core::fmt::Display for BuiltinSignature {
370 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
375 if !self.type_params.is_empty() {
376 f.write_str("<")?;
377 for (i, tp) in self.type_params.iter().enumerate() {
378 if i > 0 {
379 f.write_str(", ")?;
380 }
381 f.write_str(tp)?;
382 }
383 if !self.where_clauses.is_empty() {
384 f.write_str(" where ")?;
385 for (i, (tp, iface)) in self.where_clauses.iter().enumerate() {
386 if i > 0 {
387 f.write_str(", ")?;
388 }
389 write!(f, "{tp}: {iface}")?;
390 }
391 }
392 f.write_str("> ")?;
393 }
394 f.write_str(self.name)?;
395 f.write_str("(")?;
396 let last_idx = self.params.len().saturating_sub(1);
397 for (i, p) in self.params.iter().enumerate() {
398 if i > 0 {
399 f.write_str(", ")?;
400 }
401 if self.has_rest && i == last_idx {
402 f.write_str("...")?;
403 }
404 f.write_str(p.name)?;
405 if p.optional {
406 f.write_str("?")?;
407 }
408 let ty = &p.ty;
409 write!(f, ": {ty}")?;
410 }
411 let ret = &self.returns;
412 write!(f, ") -> {ret}")
413 }
414}
415
416#[derive(Debug, Clone, Copy, PartialEq, Eq)]
419pub struct BuiltinMetadata {
420 pub name: &'static str,
421 pub return_types: &'static [&'static str],
422}
423
424pub const TY_ANY: Ty = Ty::Any;
431pub const TY_BOOL: Ty = Ty::Named("bool");
432pub const TY_BYTES: Ty = Ty::Named("bytes");
433pub const TY_CLOSURE: Ty = Ty::Named("closure");
434pub const TY_DECIMAL: Ty = Ty::Named("decimal");
435pub const TY_DICT: Ty = Ty::Named("dict");
436pub const TY_DURATION: Ty = Ty::Named("duration");
437pub const TY_FLOAT: Ty = Ty::Named("float");
438pub const TY_INT: Ty = Ty::Named("int");
439pub const TY_LIST: Ty = Ty::Named("list");
440pub const TY_NEVER: Ty = Ty::Never;
441pub const TY_NIL: Ty = Ty::Named("nil");
442pub const TY_RESOURCE: Ty = Ty::Named("resource");
443pub const TY_STRING: Ty = Ty::Named("string");
444
445pub const TY_STRING_OR_NIL: Ty = Ty::Union(&[TY_STRING, TY_NIL]);
447pub const TY_INT_OR_NIL: Ty = Ty::Union(&[TY_INT, TY_NIL]);
449pub const TY_DICT_OR_NIL: Ty = Ty::Union(&[TY_DICT, TY_NIL]);
451pub const TY_BYTES_OR_NIL: Ty = Ty::Union(&[TY_BYTES, TY_NIL]);
453pub const TY_NUMBER: Ty = Ty::Union(&[TY_INT, TY_FLOAT]);
455
456#[cfg(test)]
457mod tests {
458 use super::*;
459
460 const APPLY_ARGS: &[Ty] = &[TY_DICT];
461 const FN_PARAMS: &[Ty] = &[TY_INT, TY_STRING];
462 const SHAPE_FIELDS: &[ShapeFieldDescriptor] = &[
463 ShapeFieldDescriptor::new("name", TY_STRING),
464 ShapeFieldDescriptor::optional("age", TY_INT),
465 ];
466
467 const OPEN_SHAPE_RESTS: &[Ty] = &[TY_DICT];
468
469 #[test]
470 fn wire_identifier_keys_stay_unique_across_capabilities() {
471 let mut seen = std::collections::HashMap::new();
476 for capability in CapabilityId::ALL {
477 let key = wire_identifier_key(capability.field_name());
478 if let Some(other) = seen.insert(key.clone(), capability.field_name()) {
479 panic!(
480 "`{other}` and `{}` both normalize to `{key}`",
481 capability.field_name()
482 );
483 }
484 }
485 }
486
487 #[test]
488 fn host_namespace_resolves_the_separatorless_spelling() {
489 assert_eq!(
490 CapabilityId::from_host_namespace("prmonitor"),
491 Some(CapabilityId::PrMonitor)
492 );
493 assert_eq!(
494 CapabilityId::from_host_namespace("PrMonitor"),
495 Some(CapabilityId::PrMonitor)
496 );
497 assert_eq!(
498 CapabilityId::from_host_namespace("pr_monitor"),
499 Some(CapabilityId::PrMonitor)
500 );
501 assert_eq!(CapabilityId::from_host_namespace("not_a_capability"), None);
502 assert_eq!(CapabilityId::from_field_name("prmonitor"), None);
505 }
506
507 #[test]
508 fn ty_display_atomic_and_compound() {
509 assert_eq!(format!("{TY_INT}"), "int");
510 assert_eq!(format!("{TY_ANY}"), "any");
511 assert_eq!(format!("{TY_NEVER}"), "never");
512 assert_eq!(format!("{TY_STRING_OR_NIL}"), "string?");
515 let opt_int = Ty::Optional(&TY_INT);
516 assert_eq!(format!("{opt_int}"), "int?");
517 assert_eq!(format!("{TY_NUMBER}"), "number");
519 let list_dict = Ty::Apply("list", APPLY_ARGS);
520 assert_eq!(format!("{list_dict}"), "list<dict>");
521 let lit_int = Ty::LitInt(42);
522 assert_eq!(format!("{lit_int}"), "42");
523 let lit_str = Ty::LitString("pass");
524 assert_eq!(format!("{lit_str}"), "\"pass\"");
525 let schema_t = Ty::SchemaOf("T");
526 assert_eq!(format!("{schema_t}"), "Schema<T>");
527 let fn_ty = Ty::Fn(FN_PARAMS, &TY_BOOL);
528 assert_eq!(format!("{fn_ty}"), "(int, string) -> bool");
529 let shape = Ty::Shape(SHAPE_FIELDS);
530 assert_eq!(format!("{shape}"), "{name: string, age?: int}");
535 let open = Ty::OpenShape(SHAPE_FIELDS, OPEN_SHAPE_RESTS);
536 assert_eq!(format!("{open}"), "{name: string, age?: int, ...dict}");
537 let tail_only = Ty::OpenShape(&[], OPEN_SHAPE_RESTS);
538 assert_eq!(format!("{tail_only}"), "{...dict}");
539 }
540
541 const BASIC_PARAMS: &[Param] = &[Param::new("a", TY_DICT), Param::new("b", TY_DICT)];
542 const REST_PARAMS: &[Param] = &[Param::new("prefix", TY_STRING), Param::new("args", TY_ANY)];
543 const OPT_PARAMS: &[Param] = &[
544 Param::new("receipt", TY_DICT),
545 Param::optional("candidate", TY_ANY),
546 ];
547 const GENERIC_PARAMS: &[Param] = &[Param::new("schema", Ty::SchemaOf("T"))];
548
549 #[test]
550 fn signature_display_basic() {
551 let sig = BuiltinSignature::simple("deep_merge", BASIC_PARAMS, TY_DICT);
552 assert_eq!(format!("{sig}"), "deep_merge(a: dict, b: dict) -> dict");
553 }
554
555 #[test]
556 fn signature_display_with_optional_and_rest() {
557 let sig = BuiltinSignature {
558 name: "io_println",
559 params: REST_PARAMS,
560 returns: TY_NIL,
561 type_params: &[],
562 has_rest: true,
563 where_clauses: &[],
564 projection: None,
565 };
566 assert_eq!(
567 format!("{sig}"),
568 "io_println(prefix: string, ...args: any) -> nil"
569 );
570
571 let opt_sig =
572 BuiltinSignature::simple("lifecycle_replay_resume_input", OPT_PARAMS, TY_DICT);
573 assert_eq!(
574 format!("{opt_sig}"),
575 "lifecycle_replay_resume_input(receipt: dict, candidate?: any) -> dict"
576 );
577 }
578
579 #[test]
580 fn signature_display_with_generics_and_where() {
581 let sig = BuiltinSignature {
582 name: "schema_parse",
583 params: GENERIC_PARAMS,
584 returns: Ty::Generic("T"),
585 type_params: &["T"],
586 has_rest: false,
587 where_clauses: &[("T", "Decode")],
588 projection: None,
589 };
590 assert_eq!(
591 format!("{sig}"),
592 "<T where T: Decode> schema_parse(schema: Schema<T>) -> T"
593 );
594 }
595}