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