1pub fn completion_symbols() -> &'static [&'static str] {
2 fiber::completion_symbols()
3}
4
5#[cfg_attr(not(test), allow(dead_code))]
8pub(crate) const LANGUAGE_BUILTINS: &[(&str, &[&str])] = &[
9 (
10 "evaluation",
11 &[
12 "quote",
13 "syntax-quote",
14 "do",
15 "if",
16 "let",
17 "letfn",
18 "binding",
19 "loop",
20 "recur",
21 "throw",
22 "try",
23 "fn",
24 ],
25 ),
26 (
27 "definitions",
28 &[
29 "def",
30 "declare",
31 "var",
32 "set!",
33 "defmacro",
34 ],
35 ),
36 ("namespaces", &["ns", "ns+", "require", "alias"]),
37 ("interop", &["new", "field", "."]),
38];
39
40pub(crate) fn invoke_function_sync(
41 function: Rc<Function>,
42 arguments: Vec<Value>,
43) -> Result<Value, String> {
44 fiber::invoke_function_sync(function, arguments)
45}
46
47#[derive(Debug, Clone, PartialEq, Eq)]
48pub struct ExtensionValue {
49 pub provider: String,
50 pub type_name: String,
51 pub handle: u64,
52}
53
54#[derive(Debug, Clone, PartialEq)]
58pub struct NamedField {
59 pub name: String,
60 pub properties: Option<Form>,
61 pub schema: Form,
62}
63
64impl NamedField {
65 pub(crate) fn from_form(form: &Form, kind: &str) -> Result<Self, String> {
66 let Form::Vector(parts) = form else {
67 return Err(format!("{kind} fields must be symbols or [name schema] vectors"));
68 };
69 let (name, properties, schema) = match parts.as_slice() {
70 [Form::Symbol(name), schema] => (name, None, schema),
71 [Form::Symbol(name), Form::Map(properties), schema] => {
72 (name, Some(Form::Map(properties.clone())), schema)
73 }
74 _ => {
75 return Err(format!(
76 "{kind} fields must be [name schema] or [name properties schema]"
77 ))
78 }
79 };
80 if name.is_empty() || name.contains('/') {
81 return Err(format!("{kind} field names must be unqualified symbols"));
82 }
83 Ok(Self {
84 name: name.clone(),
85 properties,
86 schema: schema.clone(),
87 })
88 }
89
90 pub(crate) fn legacy(name: &str) -> Self {
91 Self {
92 name: name.to_owned(),
93 properties: None,
94 schema: Form::Keyword("any".into()),
95 }
96 }
97
98 pub(crate) fn from_value(value: &Value, kind: &str) -> Result<Self, String> {
99 match value {
100 Value::String(name) if !name.is_empty() && !name.contains('/') => {
101 Ok(Self::legacy(name))
102 }
103 Value::Vector(_) | Value::Tuple(_) => {
104 let form = value_to_form(value)?;
105 Self::from_form(&form, kind)
106 }
107 _ => Err(format!(
108 "{kind} fields must contain field names or field specification vectors"
109 )),
110 }
111 }
112
113 pub(crate) fn schema_form(&self) -> Form {
114 let mut parts = vec![Form::Keyword(self.name.clone())];
115 if let Some(properties) = &self.properties {
116 parts.push(properties.clone());
117 }
118 parts.push(self.schema.clone());
119 Form::Vector(parts)
120 }
121}
122
123#[derive(Debug, Clone, PartialEq)]
127pub struct NamedDeclaration {
128 pub name: String,
129 pub mutable: bool,
130 pub fields: Vec<NamedField>,
131 pub schema: Form,
132 pub positional_constructor: String,
133 pub map_constructor: String,
134}
135
136impl NamedDeclaration {
137 pub(crate) fn new(name: String, mutable: bool, fields: Vec<NamedField>, schema: Form) -> Self {
138 let local_name = name.rsplit('/').next().unwrap_or(&name).to_owned();
139 Self {
140 name,
141 mutable,
142 fields,
143 schema,
144 positional_constructor: format!("->{local_name}"),
145 map_constructor: format!("map->{local_name}"),
146 }
147 }
148}
149
150pub(crate) fn named_value_schema_form(
151 type_name: &str,
152 mutable: bool,
153 fields: &[NamedField],
154) -> Form {
155 let mut parts = vec![Form::Keyword("struct".into())];
156 if mutable {
157 parts.push(Form::Map(vec![(
158 Form::Keyword("mutable?".into()),
159 Form::Bool(true),
160 )]));
161 }
162 parts.push(Form::List(vec![
163 Form::Symbol("var".into()),
164 Form::Symbol(type_name.to_owned()),
165 ]));
166 parts.extend(fields.iter().map(NamedField::schema_form));
167 Form::Vector(parts)
168}
169
170#[derive(Debug, Clone)]
171pub struct StructType {
172 pub name: String,
173 pub fields: Vec<String>,
174 pub declaration: Option<Rc<NamedDeclaration>>,
175}
176
177impl StructType {
178 pub(crate) fn detached(name: String, fields: Vec<String>) -> Self {
179 Self {
180 name,
181 fields,
182 declaration: None,
183 }
184 }
185}
186
187#[derive(Debug, Clone)]
188pub struct MutableType {
189 pub name: String,
190 pub fields: Vec<String>,
191 pub declaration: Option<Rc<NamedDeclaration>>,
192}
193
194impl MutableType {
195 #[cfg(test)]
196 pub(crate) fn detached(name: String, fields: Vec<String>) -> Self {
197 Self {
198 name,
199 fields,
200 declaration: None,
201 }
202 }
203}
204
205#[derive(Debug, Clone)]
206pub struct StructValue {
207 pub ty: Rc<StructType>,
208 pub values: POrderedMap<Value, Value>,
209 pub metadata: Option<Rc<Metadata>>,
210}
211
212#[derive(Debug, Clone)]
213pub struct MutableValue {
214 pub ty: Rc<MutableType>,
215 pub values: Rc<RefCell<Vec<Value>>>,
216 pub metadata: Option<Rc<Metadata>>,
217}
218
219#[derive(Debug, Clone)]
220pub struct GuestProtocol {
221 pub name: String,
222 pub methods: HashMap<String, usize>,
223 pub parents: Vec<String>,
224}
225
226#[derive(Debug, Clone)]
227pub struct NativeType {
228 pub name: String,
229 pub methods: Vec<String>,
230 pub availability: NativeAvailability,
231 pub capability: Option<String>,
232 pub metadata: Option<Rc<Metadata>>,
233}
234
235#[derive(Debug, Clone)]
236pub struct RuntimeSchema {
237 pub form: Form,
238 pub ast: crate::kernel::SchemaType,
239 pub origin: Option<KernelVar<Value>>,
240}
241
242#[derive(Clone)]
243struct PackageCatalogEntry {
244 descriptor: Value,
245 name: Option<String>,
246 namespaces: Vec<String>,
247 root: Option<std::path::PathBuf>,
248 state: String,
249 pending: Option<Promise>,
250}
251
252#[derive(Clone, Default)]
253pub struct PackageCatalog {
254 entries: Rc<RefCell<HashMap<String, PackageCatalogEntry>>>,
255}
256
257impl PackageCatalog {
258 pub fn register(
259 &self,
260 coordinate: String,
261 name: Option<String>,
262 descriptor: Value,
263 namespaces: Vec<String>,
264 root: Option<std::path::PathBuf>,
265 ) {
266 self.entries.borrow_mut().insert(
267 coordinate,
268 PackageCatalogEntry {
269 descriptor,
270 name,
271 namespaces,
272 root,
273 state: "available".into(),
274 pending: None,
275 },
276 );
277 }
278
279 fn catalog_value(&self) -> Value {
280 let mut entries = self
281 .entries
282 .borrow()
283 .iter()
284 .map(|(coordinate, entry)| {
285 (
286 Value::String(coordinate.clone()),
287 package_descriptor_state(&entry.descriptor, &entry.state),
288 )
289 })
290 .collect::<Vec<_>>();
291 entries.sort_by(|(left, _), (right, _)| left.display().cmp(&right.display()));
292 Value::OrderedMap(Box::new(POrderedMap::from_iter(entries)))
293 }
294
295 fn find(&self, target: &str) -> Option<(String, Value)> {
296 self.entries
297 .borrow()
298 .iter()
299 .find_map(|(coordinate, entry)| {
300 (coordinate == target
301 || entry.name.as_deref() == Some(target)
302 || entry.namespaces.iter().any(|namespace| namespace == target))
303 .then(|| {
304 (
305 coordinate.clone(),
306 package_descriptor_state(&entry.descriptor, &entry.state),
307 )
308 })
309 })
310 }
311
312 pub fn contains_namespace(&self, namespace: &str) -> bool {
313 self.entries
314 .borrow()
315 .values()
316 .any(|entry| entry.namespaces.iter().any(|name| name == namespace))
317 }
318
319 fn coordinate_for_namespace(&self, namespace: &str) -> Option<String> {
320 self.entries
321 .borrow()
322 .iter()
323 .find_map(|(coordinate, entry)| {
324 entry
325 .namespaces
326 .iter()
327 .any(|name| name == namespace)
328 .then(|| coordinate.clone())
329 })
330 }
331
332 fn state(&self, coordinate: &str) -> Option<String> {
333 self.entries
334 .borrow()
335 .get(coordinate)
336 .map(|entry| entry.state.clone())
337 }
338
339 fn read(&self, descriptor: &Value, relative: &str) -> Result<Vec<u8>, String> {
340 let coordinate = package_descriptor_coordinate(descriptor)
341 .ok_or("std.native.Package/read descriptor requires :package/coordinate")?;
342 let expected_version = package_descriptor_version(descriptor)
343 .ok_or("std.native.Package/read descriptor requires :package/version")?;
344 let entries = self.entries.borrow();
345 let entry = entries
346 .get(&coordinate)
347 .ok_or_else(|| format!("package/not-installed: {coordinate}"))?;
348 let actual_version = package_descriptor_version(&entry.descriptor)
349 .ok_or_else(|| format!("package/invalid-descriptor: {coordinate}"))?;
350 if expected_version != actual_version {
351 return Err(format!(
352 "package/version-mismatch: {coordinate} expected {expected_version}, installed {actual_version}"
353 ));
354 }
355 let root = entry
356 .root
357 .as_ref()
358 .ok_or_else(|| format!("package/content-unavailable: {coordinate}"))?;
359 let path = std::path::Path::new(relative);
360 if path.is_absolute()
361 || path.as_os_str().is_empty()
362 || path.components().any(|component| {
363 matches!(component, std::path::Component::ParentDir | std::path::Component::RootDir | std::path::Component::Prefix(_))
364 })
365 {
366 return Err("std.native.Package/read path must be a non-empty safe relative path".into());
367 }
368 std::fs::read(root.join(path)).map_err(|error| {
369 format!(
370 "package/content-read-failed: {coordinate} {relative}: {error}"
371 )
372 })
373 }
374
375 pub(crate) fn set_state(&self, coordinate: &str, state: &str) {
376 if let Some(entry) = self.entries.borrow_mut().get_mut(coordinate) {
377 entry.state = state.into();
378 }
379 }
380
381 fn pending(&self, coordinate: &str) -> Option<Promise> {
382 self.entries
383 .borrow()
384 .get(coordinate)
385 .and_then(|entry| entry.pending.clone())
386 }
387
388 fn set_pending(&self, coordinate: &str, pending: Option<Promise>) {
389 if let Some(entry) = self.entries.borrow_mut().get_mut(coordinate) {
390 entry.pending = pending;
391 }
392 }
393}
394
395fn package_descriptor_state(descriptor: &Value, state: &str) -> Value {
396 let Value::OrderedMap(values) = descriptor else {
397 return descriptor.clone();
398 };
399 Value::OrderedMap(Box::new(POrderedMap::from_iter(
400 values
401 .iter()
402 .map(|(key, value)| (key.clone(), value.clone()))
403 .chain(std::iter::once((
404 Value::Keyword("package/state".into()),
405 Value::Keyword(state.into()),
406 ))),
407 )))
408}
409
410fn package_descriptor_coordinate(descriptor: &Value) -> Option<String> {
411 let Value::OrderedMap(values) = descriptor else {
412 return None;
413 };
414 match values.get(&Value::Keyword("package/coordinate".into())) {
415 Some(Value::String(coordinate)) => Some(coordinate.clone()),
416 Some(Value::Symbol(coordinate)) => Some(coordinate.as_str().to_owned()),
417 _ => None,
418 }
419}
420
421fn package_descriptor_version(descriptor: &Value) -> Option<String> {
422 let Value::OrderedMap(values) = descriptor else {
423 return None;
424 };
425 match values.get(&Value::Keyword("package/version".into())) {
426 Some(Value::String(version)) => Some(version.clone()),
427 _ => None,
428 }
429}
430
431#[derive(Debug, Clone, Copy, PartialEq, Eq)]
432pub enum NativeAvailability {
433 Portable,
434 CapabilityGated,
435 InventoryOnly,
436}
437
438#[derive(Debug, Clone, Copy, PartialEq, Eq)]
439pub struct NativeOperationDeclaration {
440 pub name: &'static str,
441 pub arity: u16,
442}
443
444pub type NativeProvider = fn(&str, &str) -> Result<Value, String>;
445
446#[derive(Debug, Clone, Copy)]
447pub struct NativeDeclaration {
448 pub namespace: &'static str,
449 pub name: &'static str,
450 pub methods: &'static [&'static str],
451 pub whole_wasm_methods: &'static [NativeOperationDeclaration],
452 pub provider: NativeProvider,
453 pub availability: NativeAvailability,
454 pub capability: Option<&'static str>,
455}
456
457impl NativeDeclaration {
458 pub fn qualified_name(self) -> String {
459 format!("{}.{}", self.namespace, self.name)
460 }
461
462 pub fn method(self, name: &str) -> bool {
463 self.methods.iter().any(|method| *method == name)
464 }
465
466 pub fn whole_wasm_method(self, name: &str) -> Option<NativeOperationDeclaration> {
467 self.whole_wasm_methods
468 .iter()
469 .copied()
470 .find(|method| method.name == name)
471 }
472}
473
474pub const NATIVE_DECLARATIONS: &[NativeDeclaration] = DECLARATIONS_DECLARATIONS;
475
476pub fn native_declarations() -> &'static [NativeDeclaration] {
477 NATIVE_DECLARATIONS
478}
479
480pub(crate) fn native_descriptor_value(declaration: NativeDeclaration) -> Value {
481 Value::NativeType(Rc::new(NativeType {
482 name: declaration.qualified_name(),
483 methods: declaration
484 .methods
485 .iter()
486 .map(|method| (*method).to_owned())
487 .collect(),
488 availability: declaration.availability,
489 capability: declaration.capability.map(str::to_owned),
490 metadata: None,
491 }))
492}
493
494pub fn native_type_values() -> Vec<(String, Value)> {
495 NATIVE_DECLARATIONS
496 .iter()
497 .map(|declaration| {
498 (declaration.qualified_name(), native_descriptor_value(*declaration))
499 })
500 .collect()
501}
502
503pub fn native_manifest() -> Vec<String> {
509 let mut manifest = NATIVE_DECLARATIONS
510 .iter()
511 .filter(|declaration| declaration.namespace == "std.native")
512 .map(|declaration| {
513 let mut methods = declaration
514 .methods
515 .iter()
516 .map(|method| format!("{}/{method}", declaration.qualified_name()))
517 .collect::<Vec<_>>();
518 methods.sort();
519 format!(
520 "native|{}|{}|{}|annotation|{}",
521 declaration.qualified_name(),
522 native_availability_name(declaration.availability),
523 declaration.capability.unwrap_or_default(),
524 methods.join(",")
525 )
526 })
527 .collect::<Vec<_>>();
528 manifest.sort();
529 manifest
530}
531
532pub fn protocol_manifest() -> Vec<String> {
538 let mut manifest = protocol_declarations()
539 .iter()
540 .map(|declaration| {
541 let mut parents = declaration
542 .parents
543 .iter()
544 .map(|parent| (*parent).to_owned())
545 .collect::<Vec<_>>();
546 parents.sort();
547 let mut methods = declaration
548 .methods
549 .iter()
550 .map(|method| {
551 format!(
552 "{}/{}:{}",
553 declaration.runtime_name(),
554 method.name,
555 protocol_arity_name(method.arity)
556 )
557 })
558 .collect::<Vec<_>>();
559 methods.sort();
560 format!(
561 "protocol|{}|{}|{}|{}|annotation|{}|{}",
562 declaration.runtime_name(),
563 declaration.name,
564 protocol_availability_name(declaration.availability),
565 declaration.capability.unwrap_or_default(),
566 parents.join(","),
567 methods.join(",")
568 )
569 })
570 .collect::<Vec<_>>();
571 manifest.sort();
572 manifest
573}
574
575fn native_availability_name(availability: NativeAvailability) -> &'static str {
576 match availability {
577 NativeAvailability::Portable => "portable",
578 NativeAvailability::CapabilityGated => "capability-gated",
579 NativeAvailability::InventoryOnly => "inventory-only",
580 }
581}
582
583fn protocol_availability_name(
584 availability: crate::lang::protocol::ProtocolAvailability,
585) -> &'static str {
586 match availability {
587 crate::lang::protocol::ProtocolAvailability::Portable => "portable",
588 crate::lang::protocol::ProtocolAvailability::CapabilityGated => "capability-gated",
589 crate::lang::protocol::ProtocolAvailability::InventoryOnly => "inventory-only",
590 }
591}
592
593fn protocol_arity_name(arity: crate::lang::protocol::ProtocolArity) -> String {
594 match arity {
595 crate::lang::protocol::ProtocolArity::Fixed(value) => value.to_string(),
596 crate::lang::protocol::ProtocolArity::Variadic { .. } => "-1".into(),
597 }
598}
599
600pub(crate) fn protocol_declarations() -> &'static [crate::lang::protocol::ProtocolDeclaration] {
601 crate::lang::protocol::protocol_declarations()
602}
603
604pub fn builtin_protocol_namespace(protocol: &str) -> String {
605 let simple = protocol.strip_prefix("std.foundation/").unwrap_or(protocol);
606 crate::lang::protocol::find_protocol(simple)
607 .map(|declaration| declaration.runtime_name())
608 .unwrap_or_else(|| {
609 if simple.starts_with("std.protocol.") {
610 simple.to_owned()
611 } else {
612 format!("std.protocol.{}.{}", simple.to_ascii_lowercase(), simple)
613 }
614 })
615}
616
617pub(crate) fn builtin_protocol_name(protocol: &str) -> String {
618 let simple = protocol.strip_prefix("std.foundation/").unwrap_or(protocol);
619 crate::lang::protocol::find_protocol(simple)
620 .map(|declaration| declaration.runtime_name())
621 .unwrap_or_else(|| protocol.to_owned())
622}
623
624pub(crate) fn canonical_protocol_name(protocol: &str) -> String {
625 builtin_protocol_name(protocol)
626}
627
628pub(crate) fn canonical_intrinsic_protocol_symbol(symbol: &str) -> Option<String> {
629 let (protocol, method) = symbol.rsplit_once('/')?;
630 let canonical = canonical_protocol_name(protocol);
631 (canonical != protocol).then(|| format!("{canonical}/{method}"))
632}
633
634pub(crate) fn canonical_native_symbol(symbol: &str) -> Option<String> {
639 if let Some(method) = symbol.strip_prefix("file/") {
644 return Some(format!("std.native.File/{method}"));
645 }
646 if let Some(method) = symbol.strip_prefix("os/") {
647 return Some(format!("std.native.OS/{method}"));
648 }
649 if NATIVE_DECLARATIONS
650 .iter()
651 .any(|declaration| declaration.namespace == "std.native" && declaration.name == symbol)
652 {
653 return Some(format!("std.native.{symbol}"));
654 }
655 let (native_type, method) = symbol.rsplit_once('/')?;
656 NATIVE_DECLARATIONS
657 .iter()
658 .any(|declaration| declaration.namespace == "std.native" && declaration.name == native_type)
659 .then(|| format!("std.native.{native_type}/{method}"))
660}
661
662pub(crate) fn canonical_intrinsic_symbol(symbol: &str) -> Option<String> {
663 canonical_intrinsic_protocol_symbol(symbol).or_else(|| canonical_native_symbol(symbol))
664}
665
666pub(crate) fn canonical_intrinsic_callable_symbol(symbol: &str) -> Option<String> {
671 let canonical = canonical_intrinsic_symbol(symbol).unwrap_or_else(|| symbol.to_owned());
672 if let Some((native_type, method)) = canonical.rsplit_once('/') {
673 if NATIVE_DECLARATIONS.iter().any(|declaration| {
674 declaration.qualified_name() == native_type && declaration.method(method)
675 }) {
676 return Some(canonical);
677 }
678 }
679 let (namespace, method) = canonical.split_once('/')?;
680 protocol_declarations()
681 .iter()
682 .find(|declaration| declaration.runtime_name() == namespace)
683 .filter(|declaration| declaration.methods.iter().any(|candidate| candidate.name == method))
684 .map(|_| canonical)
685}
686
687pub(crate) fn bytecode_callable_value(name: &str) -> Result<Value, String> {
691 let canonical = canonical_intrinsic_callable_symbol(name)
692 .ok_or_else(|| format!("unknown canonical builtin: {name}"))?;
693 let registry = namespace_registry()?;
694 registry
695 .resolve(&crate::lang::data::Symbol::parse(&canonical))
696 .map(|var| var.deref_value())
697 .ok_or_else(|| format!("unbound canonical builtin: {canonical}"))
698}
699
700pub fn foundation_protocol_values() -> Vec<(String, Value)> {
701 protocol_declarations()
702 .iter()
703 .filter(|declaration| declaration.availability.is_guest_visible())
704 .map(|declaration| {
705 (
706 declaration.name.to_owned(),
707 Value::Protocol(Rc::new(guest_protocol(*declaration))),
708 )
709 })
710 .collect()
711}
712
713pub fn builtin_protocol_method_values() -> Vec<(String, String, Value)> {
714 protocol_declarations()
715 .iter()
716 .filter(|declaration| declaration.availability.is_guest_visible())
717 .flat_map(|declaration| {
718 declaration.methods.iter().map(move |method| {
719 let protocol_name = declaration.runtime_name();
720 let namespace = protocol_name.clone();
721 let method_name = method.name.to_owned();
722 let display_name = format!("{namespace}/{}", method.name);
723 let arity_display_name = display_name.clone();
724 let (minimum_arity, maximum_arity) = method.arity.range();
725 let value = if protocol_name == "std.protocol.ideref.IDeref" && method.name == "deref" {
726 native_protocol_fiber_function(
727 &display_name,
728 &protocol_name,
729 &method_name,
730 minimum_arity,
731 maximum_arity.is_none(),
732 {
733 let protocol_name = protocol_name.clone();
734 let method_name = method_name.clone();
735 move |arguments| protocol_call(&protocol_name, &method_name, &arguments)
736 },
737 protocol_deref_fiber,
738 )
739 } else if protocol_name == "std.protocol.icoroutine.ICoroutine"
740 && method.name == "resume"
741 {
742 native_protocol_fiber_function(
743 &display_name,
744 &protocol_name,
745 &method_name,
746 minimum_arity,
747 maximum_arity.is_none(),
748 {
749 let protocol_name = protocol_name.clone();
750 let method_name = method_name.clone();
751 move |arguments| protocol_call(&protocol_name, &method_name, &arguments)
752 },
753 protocol_coroutine_resume_fiber,
754 )
755 } else {
756 native_variadic_function(&display_name, move |arguments| {
757 if arguments.len() < minimum_arity
758 || maximum_arity.is_some_and(|maximum| arguments.len() > maximum)
759 {
760 let expected = match maximum_arity {
761 Some(maximum) if maximum == minimum_arity => {
762 minimum_arity.to_string()
763 }
764 Some(maximum) => format!("{minimum_arity} to {maximum}"),
765 None => format!("at least {minimum_arity}"),
766 };
767 return Err(format!(
768 "protocol/arity: {arity_display_name} expects {expected} arguments, received {}",
769 arguments.len()
770 ));
771 }
772 protocol_call(&protocol_name, &method_name, &arguments)
773 })
774 };
775 (
776 namespace,
777 method.name.to_owned(),
778 value,
779 )
780 })
781 })
782 .collect()
783}
784
785fn guest_protocol(declaration: crate::lang::protocol::ProtocolDeclaration) -> GuestProtocol {
786 GuestProtocol {
787 name: declaration.runtime_name(),
788 methods: declaration
789 .methods
790 .iter()
791 .map(|method| (method.name.to_owned(), method.arity.guest_arity()))
792 .collect(),
793 parents: declaration
794 .parents
795 .iter()
796 .map(|parent| {
797 crate::lang::protocol::find_protocol(parent)
798 .map(|declaration| declaration.runtime_name())
799 .unwrap_or_else(|| (*parent).to_owned())
800 })
801 .collect(),
802 }
803}
804
805#[cfg(test)]
806mod native_work_protocol_tests {
807 use super::*;
808
809 fn methods(name: &str) -> Vec<(&'static str, usize)> {
810 protocol_declarations()
811 .iter()
812 .find(|declaration| declaration.name == name)
813 .map(|declaration| {
814 declaration
815 .methods
816 .iter()
817 .map(|method| (method.name, method.arity.guest_arity()))
818 .collect()
819 })
820 .expect("protocol must exist")
821 }
822
823 fn protocol(name: &str) -> Rc<GuestProtocol> {
824 foundation_protocol_values()
825 .into_iter()
826 .find(|(candidate, _)| candidate == name)
827 .and_then(|(_, value)| match value {
828 Value::Protocol(protocol) => Some(protocol),
829 _ => None,
830 })
831 .expect("protocol value must exist")
832 }
833
834 #[test]
835 fn protocol_aliases_resolve_to_annotation_owned_namespaces() {
836 let namespaces = crate::core::minimal_namespace_registry();
837 let assoc = namespaces
838 .resolve(&crate::lang::data::Symbol::parse("IAssoc/assoc"))
839 .expect("annotated protocol alias");
840 assert_eq!(
841 assoc.symbol().as_str(),
842 "std.protocol.iassoc.IAssoc/assoc"
843 );
844 assert_eq!(
845 crate::lang::protocol::find_protocol("IAssoc")
846 .expect("annotated protocol")
847 .runtime_name(),
848 "std.protocol.iassoc.IAssoc"
849 );
850 assert_eq!(
851 canonical_native_symbol("Base/vec"),
852 Some("std.native.Base/vec".into())
853 );
854 assert_eq!(canonical_native_symbol("std.native/Base"), None);
855 assert_eq!(
856 canonical_native_symbol("Coroutine"),
857 Some("std.native.Coroutine".into())
858 );
859 assert_eq!(
860 canonical_native_symbol("file/join"),
861 Some("std.native.File/join".into())
862 );
863 assert_eq!(
864 canonical_native_symbol("os/cwd"),
865 Some("std.native.OS/cwd".into())
866 );
867 }
868
869 #[test]
870 fn native_registry_rejects_unknown_annotated_methods() {
871 let error = crate::core::native_type_function_value("String", "missing").unwrap_err();
872 assert_eq!(
873 error,
874 "unknown annotated native method: std.native.String/missing"
875 );
876 }
877
878 #[test]
879 fn native_work_protocol_methods_are_stable() {
880 assert_eq!(methods("IWork"), vec![("work-spec", 1)]);
881 assert_eq!(methods("IWorkExecutor"), vec![("work-execute", 2)]);
882 assert_eq!(
883 methods("IWorkStore"),
884 vec![("work-query", 2), ("work-transact", 2)]
885 );
886 assert_eq!(methods("IWorkRef"), vec![("work-id", 1)]);
887 assert_eq!(
888 methods("IWorkHost"),
889 vec![("work-submit", 4), ("work-resolve", 2)]
890 );
891 assert_eq!(
892 methods("IWorkRun"),
893 vec![
894 ("work-status", 1),
895 ("work-result", 1),
896 ("work-events", 2),
897 ("work-cancel", 2),
898 ]
899 );
900 }
901
902 #[test]
903 fn native_work_protocol_parents_match_the_lifecycle_contract() {
904 assert!(protocol("IWorkExecutor").parents.is_empty());
905 assert!(protocol("IWorkStore").parents.is_empty());
906 assert_eq!(
907 protocol("IWorkHost").parents,
908 vec![crate::lang::protocol::find_protocol("IComponent")
909 .expect("annotated protocol")
910 .runtime_name()]
911 );
912 assert_eq!(
913 protocol("IWorkRun").parents,
914 vec![
915 crate::lang::protocol::find_protocol("IWorkRef")
916 .expect("annotated protocol")
917 .runtime_name(),
918 crate::lang::protocol::find_protocol("IClosed")
919 .expect("annotated protocol")
920 .runtime_name(),
921 ]
922 );
923 }
924}