Skip to main content

hara_native/core/
registry.rs

1pub fn completion_symbols() -> &'static [&'static str] {
2    fiber::completion_symbols()
3}
4
5/// Closed accounting inventory for evaluator/compiler forms. These are not a
6/// native type and do not create Vars in a `std.native.Builtins` namespace.
7#[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/// One field in a named value declaration. The runtime only needs the name
55/// for storage, while the source declaration keeps the schema and optional
56/// field properties beside it until the type Var is published.
57#[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/// Canonical declaration data shared by a named type, its constructors, and
124/// the schema exposed through the type Var.  The runtime keeps this beside
125/// the type object rather than registering a second schema-owned identity.
126#[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    state: String,
248    pending: Option<Promise>,
249}
250
251#[derive(Clone, Default)]
252pub struct PackageCatalog {
253    entries: Rc<RefCell<HashMap<String, PackageCatalogEntry>>>,
254}
255
256impl PackageCatalog {
257    pub fn register(
258        &self,
259        coordinate: String,
260        name: Option<String>,
261        descriptor: Value,
262        namespaces: Vec<String>,
263    ) {
264        self.entries.borrow_mut().insert(
265            coordinate,
266            PackageCatalogEntry {
267                descriptor,
268                name,
269                namespaces,
270                state: "available".into(),
271                pending: None,
272            },
273        );
274    }
275
276    fn catalog_value(&self) -> Value {
277        let mut entries = self
278            .entries
279            .borrow()
280            .iter()
281            .map(|(coordinate, entry)| {
282                (
283                    Value::String(coordinate.clone()),
284                    package_descriptor_state(&entry.descriptor, &entry.state),
285                )
286            })
287            .collect::<Vec<_>>();
288        entries.sort_by(|(left, _), (right, _)| left.display().cmp(&right.display()));
289        Value::OrderedMap(Box::new(POrderedMap::from_iter(entries)))
290    }
291
292    fn find(&self, target: &str) -> Option<(String, Value)> {
293        self.entries
294            .borrow()
295            .iter()
296            .find_map(|(coordinate, entry)| {
297                (coordinate == target
298                    || entry.name.as_deref() == Some(target)
299                    || entry.namespaces.iter().any(|namespace| namespace == target))
300                .then(|| {
301                    (
302                        coordinate.clone(),
303                        package_descriptor_state(&entry.descriptor, &entry.state),
304                    )
305                })
306            })
307    }
308
309    pub fn contains_namespace(&self, namespace: &str) -> bool {
310        self.entries
311            .borrow()
312            .values()
313            .any(|entry| entry.namespaces.iter().any(|name| name == namespace))
314    }
315
316    fn coordinate_for_namespace(&self, namespace: &str) -> Option<String> {
317        self.entries
318            .borrow()
319            .iter()
320            .find_map(|(coordinate, entry)| {
321                entry
322                    .namespaces
323                    .iter()
324                    .any(|name| name == namespace)
325                    .then(|| coordinate.clone())
326            })
327    }
328
329    fn state(&self, coordinate: &str) -> Option<String> {
330        self.entries
331            .borrow()
332            .get(coordinate)
333            .map(|entry| entry.state.clone())
334    }
335
336    fn set_state(&self, coordinate: &str, state: &str) {
337        if let Some(entry) = self.entries.borrow_mut().get_mut(coordinate) {
338            entry.state = state.into();
339        }
340    }
341
342    fn pending(&self, coordinate: &str) -> Option<Promise> {
343        self.entries
344            .borrow()
345            .get(coordinate)
346            .and_then(|entry| entry.pending.clone())
347    }
348
349    fn set_pending(&self, coordinate: &str, pending: Option<Promise>) {
350        if let Some(entry) = self.entries.borrow_mut().get_mut(coordinate) {
351            entry.pending = pending;
352        }
353    }
354}
355
356fn package_descriptor_state(descriptor: &Value, state: &str) -> Value {
357    let Value::OrderedMap(values) = descriptor else {
358        return descriptor.clone();
359    };
360    Value::OrderedMap(Box::new(POrderedMap::from_iter(
361        values
362            .iter()
363            .map(|(key, value)| (key.clone(), value.clone()))
364            .chain(std::iter::once((
365                Value::Keyword("package/state".into()),
366                Value::Keyword(state.into()),
367            ))),
368    )))
369}
370
371fn package_descriptor_coordinate(descriptor: &Value) -> Option<String> {
372    let Value::OrderedMap(values) = descriptor else {
373        return None;
374    };
375    match values.get(&Value::Keyword("package/coordinate".into())) {
376        Some(Value::String(coordinate)) => Some(coordinate.clone()),
377        Some(Value::Symbol(coordinate)) => Some(coordinate.as_str().to_owned()),
378        _ => None,
379    }
380}
381
382#[derive(Debug, Clone, Copy, PartialEq, Eq)]
383pub enum NativeAvailability {
384    Portable,
385    CapabilityGated,
386    InventoryOnly,
387}
388
389#[derive(Debug, Clone, Copy, PartialEq, Eq)]
390pub struct NativeOperationDeclaration {
391    pub name: &'static str,
392    pub arity: u16,
393}
394
395pub type NativeProvider = fn(&str, &str) -> Result<Value, String>;
396
397#[derive(Debug, Clone, Copy)]
398pub struct NativeDeclaration {
399    pub namespace: &'static str,
400    pub name: &'static str,
401    pub methods: &'static [&'static str],
402    pub whole_wasm_methods: &'static [NativeOperationDeclaration],
403    pub provider: NativeProvider,
404    pub availability: NativeAvailability,
405    pub capability: Option<&'static str>,
406}
407
408impl NativeDeclaration {
409    pub fn qualified_name(self) -> String {
410        format!("{}.{}", self.namespace, self.name)
411    }
412
413    pub fn method(self, name: &str) -> bool {
414        self.methods.iter().any(|method| *method == name)
415    }
416
417    pub fn whole_wasm_method(self, name: &str) -> Option<NativeOperationDeclaration> {
418        self.whole_wasm_methods
419            .iter()
420            .copied()
421            .find(|method| method.name == name)
422    }
423}
424
425pub const NATIVE_DECLARATIONS: &[NativeDeclaration] = DECLARATIONS_DECLARATIONS;
426
427pub fn native_declarations() -> &'static [NativeDeclaration] {
428    NATIVE_DECLARATIONS
429}
430
431pub(crate) fn native_descriptor_value(declaration: NativeDeclaration) -> Value {
432    Value::NativeType(Rc::new(NativeType {
433        name: declaration.qualified_name(),
434        methods: declaration
435            .methods
436            .iter()
437            .map(|method| (*method).to_owned())
438            .collect(),
439        availability: declaration.availability,
440        capability: declaration.capability.map(str::to_owned),
441        metadata: None,
442    }))
443}
444
445pub fn native_type_values() -> Vec<(String, Value)> {
446    NATIVE_DECLARATIONS
447        .iter()
448        .map(|declaration| {
449            (declaration.name.to_owned(), native_descriptor_value(*declaration))
450        })
451        .collect()
452}
453
454/// Returns the closed native declaration surface in a stable, comparison-friendly form.
455///
456/// This is intentionally a derived inspection view. The annotations remain the source of
457/// truth; the manifest only makes the Rust and Java declaration surfaces comparable in tests
458/// and diagnostics.
459pub fn native_manifest() -> Vec<String> {
460    let mut manifest = NATIVE_DECLARATIONS
461        .iter()
462        .map(|declaration| {
463            let mut methods = declaration
464                .methods
465                .iter()
466                .map(|method| format!("std.native.{}/{}", declaration.name, method))
467                .collect::<Vec<_>>();
468            methods.sort();
469            format!(
470                "native|std.native.{}|{}|{}|annotation|{}",
471                declaration.name,
472                native_availability_name(declaration.availability),
473                declaration.capability.unwrap_or_default(),
474                methods.join(",")
475            )
476        })
477        .collect::<Vec<_>>();
478    manifest.sort();
479    manifest
480}
481
482/// Returns the closed annotated protocol surface in a stable, comparison-friendly form.
483///
484/// Protocol method arities use the declaration arity (`-1` for variadic methods), and method
485/// entries carry their canonical runtime origin. Inherited methods are not copied into a child;
486/// the parent list is part of the manifest instead.
487pub fn protocol_manifest() -> Vec<String> {
488    let mut manifest = protocol_declarations()
489        .iter()
490        .map(|declaration| {
491            let mut parents = declaration
492                .parents
493                .iter()
494                .map(|parent| (*parent).to_owned())
495                .collect::<Vec<_>>();
496            parents.sort();
497            let mut methods = declaration
498                .methods
499                .iter()
500                .map(|method| {
501                    format!(
502                        "{}/{}:{}",
503                        declaration.runtime_name(),
504                        method.name,
505                        protocol_arity_name(method.arity)
506                    )
507                })
508                .collect::<Vec<_>>();
509            methods.sort();
510            format!(
511                "protocol|{}|{}|{}|{}|annotation|{}|{}",
512                declaration.runtime_name(),
513                declaration.name,
514                protocol_availability_name(declaration.availability),
515                declaration.capability.unwrap_or_default(),
516                parents.join(","),
517                methods.join(",")
518            )
519        })
520        .collect::<Vec<_>>();
521    manifest.sort();
522    manifest
523}
524
525fn native_availability_name(availability: NativeAvailability) -> &'static str {
526    match availability {
527        NativeAvailability::Portable => "portable",
528        NativeAvailability::CapabilityGated => "capability-gated",
529        NativeAvailability::InventoryOnly => "inventory-only",
530    }
531}
532
533fn protocol_availability_name(
534    availability: crate::lang::protocol::ProtocolAvailability,
535) -> &'static str {
536    match availability {
537        crate::lang::protocol::ProtocolAvailability::Portable => "portable",
538        crate::lang::protocol::ProtocolAvailability::CapabilityGated => "capability-gated",
539        crate::lang::protocol::ProtocolAvailability::InventoryOnly => "inventory-only",
540    }
541}
542
543fn protocol_arity_name(arity: crate::lang::protocol::ProtocolArity) -> String {
544    match arity {
545        crate::lang::protocol::ProtocolArity::Fixed(value) => value.to_string(),
546        crate::lang::protocol::ProtocolArity::Variadic { .. } => "-1".into(),
547    }
548}
549
550pub(crate) fn protocol_declarations() -> &'static [crate::lang::protocol::ProtocolDeclaration] {
551    crate::lang::protocol::protocol_declarations()
552}
553
554pub fn builtin_protocol_namespace(protocol: &str) -> String {
555    let simple = protocol.strip_prefix("std.foundation/").unwrap_or(protocol);
556    crate::lang::protocol::find_protocol(simple)
557        .map(|declaration| declaration.runtime_name())
558        .unwrap_or_else(|| {
559            if simple.starts_with("std.protocol.") {
560                simple.to_owned()
561            } else {
562                format!("std.protocol.{}.{}", simple.to_ascii_lowercase(), simple)
563            }
564        })
565}
566
567pub(crate) fn builtin_protocol_name(protocol: &str) -> String {
568    let simple = protocol.strip_prefix("std.foundation/").unwrap_or(protocol);
569    crate::lang::protocol::find_protocol(simple)
570        .map(|declaration| declaration.runtime_name())
571        .unwrap_or_else(|| protocol.to_owned())
572}
573
574pub(crate) fn canonical_protocol_name(protocol: &str) -> String {
575    builtin_protocol_name(protocol)
576}
577
578pub(crate) fn canonical_intrinsic_protocol_symbol(symbol: &str) -> Option<String> {
579    let (protocol, method) = symbol.rsplit_once('/')?;
580    let canonical = canonical_protocol_name(protocol);
581    (canonical != protocol).then(|| format!("{canonical}/{method}"))
582}
583
584/// Resolves the short spelling of an annotated native type to its registered
585/// namespace. Protocol names deliberately do not go through this function:
586/// their aliases are installed from the protocol declaration registry and
587/// ordinary namespace resolution must handle them like every other alias.
588pub(crate) fn canonical_native_symbol(symbol: &str) -> Option<String> {
589    // `file/*` is the long-standing lowercase spelling used by the portable
590    // Hara library.  Keep it as a compatibility alias for the annotated
591    // `std.native.File` surface so source compilation and tree evaluation
592    // agree on the same callable identity.
593    if let Some(method) = symbol.strip_prefix("file/") {
594        return Some(format!("std.native.File/{method}"));
595    }
596    if let Some(method) = symbol.strip_prefix("os/") {
597        return Some(format!("std.native.OS/{method}"));
598    }
599    if NATIVE_DECLARATIONS
600        .iter()
601        .any(|declaration| declaration.name == symbol)
602    {
603        return Some(format!("std.native.{symbol}"));
604    }
605    let (native_type, method) = symbol.rsplit_once('/')?;
606    NATIVE_DECLARATIONS
607        .iter()
608        .any(|declaration| declaration.name == native_type)
609        .then(|| format!("std.native.{native_type}/{method}"))
610}
611
612pub(crate) fn canonical_intrinsic_symbol(symbol: &str) -> Option<String> {
613    canonical_intrinsic_protocol_symbol(symbol).or_else(|| canonical_native_symbol(symbol))
614}
615
616/// Returns the canonical identity of a callable owned by the native or
617/// protocol registries. Ordinary Foundation functions deliberately do not
618/// appear here: they must resolve through their namespace Vars after
619/// `std.foundation` has been loaded.
620pub(crate) fn canonical_intrinsic_callable_symbol(symbol: &str) -> Option<String> {
621    let canonical = canonical_intrinsic_symbol(symbol).unwrap_or_else(|| symbol.to_owned());
622    if let Some(native) = canonical.strip_prefix("std.native.") {
623        let (native_type, method) = native.split_once('/')?;
624        if NATIVE_DECLARATIONS.iter().any(|declaration| {
625            declaration.name == native_type && declaration.method(method)
626        }) {
627            return Some(canonical);
628        }
629    }
630    let (namespace, method) = canonical.split_once('/')?;
631    protocol_declarations()
632        .iter()
633        .find(|declaration| declaration.runtime_name() == namespace)
634        .filter(|declaration| declaration.methods.iter().any(|candidate| candidate.name == method))
635        .map(|_| canonical)
636}
637
638/// Resolves a canonical native/protocol callable for bytecode instructions.
639/// The registry is the only source of these values; no unqualified fallback
640/// catalog is consulted.
641pub(crate) fn bytecode_callable_value(name: &str) -> Result<Value, String> {
642    let canonical = canonical_intrinsic_callable_symbol(name)
643        .ok_or_else(|| format!("unknown canonical builtin: {name}"))?;
644    let registry = namespace_registry()?;
645    registry
646        .resolve(&crate::lang::data::Symbol::parse(&canonical))
647        .map(|var| var.deref_value())
648        .ok_or_else(|| format!("unbound canonical builtin: {canonical}"))
649}
650
651pub fn foundation_protocol_values() -> Vec<(String, Value)> {
652    protocol_declarations()
653        .iter()
654        .filter(|declaration| declaration.availability.is_guest_visible())
655        .map(|declaration| {
656            (
657                declaration.name.to_owned(),
658                Value::Protocol(Rc::new(guest_protocol(*declaration))),
659            )
660        })
661        .collect()
662}
663
664pub fn builtin_protocol_method_values() -> Vec<(String, String, Value)> {
665    protocol_declarations()
666        .iter()
667        .filter(|declaration| declaration.availability.is_guest_visible())
668        .flat_map(|declaration| {
669            declaration.methods.iter().map(move |method| {
670                let protocol_name = declaration.runtime_name();
671                let namespace = protocol_name.clone();
672                let method_name = method.name.to_owned();
673                let display_name = format!("{namespace}/{}", method.name);
674                let arity_display_name = display_name.clone();
675                let (minimum_arity, maximum_arity) = method.arity.range();
676                let value = if protocol_name == "std.protocol.ideref.IDeref" && method.name == "deref" {
677                    native_protocol_fiber_function(
678                        &display_name,
679                        &protocol_name,
680                        &method_name,
681                        minimum_arity,
682                        maximum_arity.is_none(),
683                        {
684                            let protocol_name = protocol_name.clone();
685                            let method_name = method_name.clone();
686                            move |arguments| protocol_call(&protocol_name, &method_name, &arguments)
687                        },
688                        protocol_deref_fiber,
689                    )
690                } else if protocol_name == "std.protocol.icoroutine.ICoroutine"
691                    && method.name == "resume"
692                {
693                    native_protocol_fiber_function(
694                        &display_name,
695                        &protocol_name,
696                        &method_name,
697                        minimum_arity,
698                        maximum_arity.is_none(),
699                        {
700                            let protocol_name = protocol_name.clone();
701                            let method_name = method_name.clone();
702                            move |arguments| protocol_call(&protocol_name, &method_name, &arguments)
703                        },
704                        protocol_coroutine_resume_fiber,
705                    )
706                } else {
707                    native_variadic_function(&display_name, move |arguments| {
708                        if arguments.len() < minimum_arity
709                            || maximum_arity.is_some_and(|maximum| arguments.len() > maximum)
710                        {
711                            let expected = match maximum_arity {
712                                Some(maximum) if maximum == minimum_arity => {
713                                    minimum_arity.to_string()
714                                }
715                                Some(maximum) => format!("{minimum_arity} to {maximum}"),
716                                None => format!("at least {minimum_arity}"),
717                            };
718                            return Err(format!(
719                                "protocol/arity: {arity_display_name} expects {expected} arguments, received {}",
720                                arguments.len()
721                            ));
722                        }
723                        protocol_call(&protocol_name, &method_name, &arguments)
724                    })
725                };
726                (
727                    namespace,
728                    method.name.to_owned(),
729                    value,
730                )
731            })
732        })
733        .collect()
734}
735
736fn guest_protocol(declaration: crate::lang::protocol::ProtocolDeclaration) -> GuestProtocol {
737    GuestProtocol {
738        name: declaration.runtime_name(),
739        methods: declaration
740            .methods
741            .iter()
742            .map(|method| (method.name.to_owned(), method.arity.guest_arity()))
743            .collect(),
744        parents: declaration
745            .parents
746            .iter()
747            .map(|parent| {
748                crate::lang::protocol::find_protocol(parent)
749                    .map(|declaration| declaration.runtime_name())
750                    .unwrap_or_else(|| (*parent).to_owned())
751            })
752            .collect(),
753    }
754}
755
756#[cfg(test)]
757mod native_work_protocol_tests {
758    use super::*;
759
760    fn methods(name: &str) -> Vec<(&'static str, usize)> {
761        protocol_declarations()
762            .iter()
763            .find(|declaration| declaration.name == name)
764            .map(|declaration| {
765                declaration
766                    .methods
767                    .iter()
768                    .map(|method| (method.name, method.arity.guest_arity()))
769                    .collect()
770            })
771            .expect("protocol must exist")
772    }
773
774    fn protocol(name: &str) -> Rc<GuestProtocol> {
775        foundation_protocol_values()
776            .into_iter()
777            .find(|(candidate, _)| candidate == name)
778            .and_then(|(_, value)| match value {
779                Value::Protocol(protocol) => Some(protocol),
780                _ => None,
781            })
782            .expect("protocol value must exist")
783    }
784
785    #[test]
786    fn protocol_aliases_resolve_to_annotation_owned_namespaces() {
787        let namespaces = crate::core::minimal_namespace_registry();
788        let assoc = namespaces
789            .resolve(&crate::lang::data::Symbol::parse("IAssoc/assoc"))
790            .expect("annotated protocol alias");
791        assert_eq!(
792            assoc.symbol().as_str(),
793            "std.protocol.iassoc.IAssoc/assoc"
794        );
795        assert_eq!(
796            crate::lang::protocol::find_protocol("IAssoc")
797                .expect("annotated protocol")
798                .runtime_name(),
799            "std.protocol.iassoc.IAssoc"
800        );
801        assert_eq!(
802            canonical_native_symbol("Base/vec"),
803            Some("std.native.Base/vec".into())
804        );
805        assert_eq!(canonical_native_symbol("std.native/Base"), None);
806        assert_eq!(
807            canonical_native_symbol("Coroutine"),
808            Some("std.native.Coroutine".into())
809        );
810        assert_eq!(
811            canonical_native_symbol("file/join"),
812            Some("std.native.File/join".into())
813        );
814        assert_eq!(
815            canonical_native_symbol("os/cwd"),
816            Some("std.native.OS/cwd".into())
817        );
818    }
819
820    #[test]
821    fn native_registry_rejects_unknown_annotated_methods() {
822        let error = crate::core::native_type_function_value("String", "missing").unwrap_err();
823        assert_eq!(
824            error,
825            "unknown annotated native method: std.native.String/missing"
826        );
827    }
828
829    #[test]
830    fn native_work_protocol_methods_are_stable() {
831        assert_eq!(methods("IWork"), vec![("work-spec", 1)]);
832        assert_eq!(methods("IWorkExecutor"), vec![("work-execute", 2)]);
833        assert_eq!(
834            methods("IWorkStore"),
835            vec![("work-query", 2), ("work-transact", 2)]
836        );
837        assert_eq!(methods("IWorkRef"), vec![("work-id", 1)]);
838        assert_eq!(
839            methods("IWorkHost"),
840            vec![("work-submit", 4), ("work-resolve", 2)]
841        );
842        assert_eq!(
843            methods("IWorkRun"),
844            vec![
845                ("work-status", 1),
846                ("work-result", 1),
847                ("work-events", 2),
848                ("work-cancel", 2),
849            ]
850        );
851    }
852
853    #[test]
854    fn native_work_protocol_parents_match_the_lifecycle_contract() {
855        assert!(protocol("IWorkExecutor").parents.is_empty());
856        assert!(protocol("IWorkStore").parents.is_empty());
857        assert_eq!(
858            protocol("IWorkHost").parents,
859            vec![crate::lang::protocol::find_protocol("IComponent")
860                .expect("annotated protocol")
861                .runtime_name()]
862        );
863        assert_eq!(
864            protocol("IWorkRun").parents,
865            vec![
866                crate::lang::protocol::find_protocol("IWorkRef")
867                    .expect("annotated protocol")
868                    .runtime_name(),
869                crate::lang::protocol::find_protocol("IClosed")
870                    .expect("annotated protocol")
871                    .runtime_name(),
872            ]
873        );
874    }
875}