Skip to main content

geam_core/host/
module.rs

1use super::{
2    FallibleHostFunction, HostExternalBinding, HostExternalSchema, HostExternalTypeSchema,
3    HostFunction, HostFunctionDefinition, HostFunctionImplementation, HostFunctionSchema,
4    HostProfile, HostProvider, HostRegistrationError, ScopedConstructingHostFunction,
5    ScopedDivergingHostFunction, ScopedHostFunction, StatelessHostProfile,
6};
7use ecow::EcoString;
8use gleam_compiler_core::analyse::name::check_name_case;
9use gleam_compiler_core::ast::SrcSpan;
10use gleam_compiler_core::parse::lexer::string_to_keyword;
11use gleam_compiler_core::type_::PRELUDE_MODULE_NAME;
12use gleam_compiler_core::type_::error::Named;
13use std::collections::{BTreeMap, BTreeSet};
14use std::sync::Arc;
15
16pub struct HostModule<Profile: HostProfile = StatelessHostProfile> {
17    identity: HostModuleIdentity,
18    functions: RegisteredFunctions<Profile>,
19}
20
21pub struct HostProviderModule<Profile: HostProfile> {
22    identity: HostModuleIdentity,
23    functions: RegisteredFunctions<Profile>,
24    external_types: RegisteredExternalTypes,
25}
26
27pub struct HostProviderSet<Profile: HostProfile = StatelessHostProfile> {
28    modules: Vec<HostModule<Profile>>,
29    providers: Vec<HostProviderModule<Profile>>,
30}
31
32struct HostModuleIdentity {
33    package: EcoString,
34    module: EcoString,
35}
36
37pub(crate) struct RegisteredHostModule {
38    pub(crate) package: EcoString,
39    pub(crate) module: EcoString,
40    pub(crate) functions: Vec<RegisteredHostFunction>,
41}
42
43pub(crate) struct RegisteredHostProviderModule {
44    pub(crate) package: EcoString,
45    pub(crate) module: EcoString,
46    pub(crate) functions: Vec<RegisteredHostFunction>,
47    pub(crate) external_types: Vec<HostExternalTypeSchema>,
48}
49
50pub(crate) struct RegisteredHostFunction {
51    schema: HostFunctionSchema,
52    constructions: super::RegisteredHostConstructions,
53    implementation: RegisteredHostImplementationId,
54}
55
56#[derive(Clone, Copy)]
57pub(crate) struct RegisteredHostImplementationId(usize);
58
59pub(crate) struct RegisteredHostImplementations<Profile: HostProfile> {
60    functions: Vec<Arc<HostFunctionImplementation<Profile>>>,
61}
62
63struct RegisteredFunctions<Profile: HostProfile> {
64    functions: Vec<HostFunctionDefinition<Profile>>,
65}
66
67struct RegisteredExternalTypes {
68    types: Vec<HostExternalTypeSchema>,
69}
70
71impl HostModule<StatelessHostProfile> {
72    pub fn new(
73        package: impl Into<EcoString>,
74        module: impl Into<EcoString>,
75    ) -> Result<Self, HostRegistrationError> {
76        Self::new_for_profile(package, module)
77    }
78}
79
80impl<Profile: HostProfile> HostModule<Profile> {
81    pub fn new_for_profile(
82        package: impl Into<EcoString>,
83        module: impl Into<EcoString>,
84    ) -> Result<Self, HostRegistrationError> {
85        HostModuleIdentity::new(package.into(), module.into()).map(|identity| Self {
86            identity,
87            functions: RegisteredFunctions::new(),
88        })
89    }
90
91    pub fn with_function<Arguments, Return, Function>(
92        mut self,
93        name: impl Into<EcoString>,
94        function: Function,
95    ) -> Result<Self, HostRegistrationError>
96    where
97        Function: HostFunction<Arguments, Return>,
98    {
99        self.functions
100            .register(&self.identity.module, name.into(), |name| {
101                HostFunctionDefinition::new(name, function)
102            })
103            .map(|()| self)
104    }
105
106    pub fn with_fallible_function<Arguments, Return, Function>(
107        mut self,
108        name: impl Into<EcoString>,
109        function: Function,
110    ) -> Result<Self, HostRegistrationError>
111    where
112        Function: FallibleHostFunction<Arguments, Return>,
113    {
114        self.functions
115            .register(&self.identity.module, name.into(), |name| {
116                HostFunctionDefinition::new_fallible(name, function)
117            })
118            .map(|()| self)
119    }
120
121    pub fn with_scoped_function<Provider, Arguments, Return, Function>(
122        mut self,
123        name: impl Into<EcoString>,
124        function: Function,
125    ) -> Result<Self, HostRegistrationError>
126    where
127        Provider: HostProvider<Profile>,
128        Function: ScopedHostFunction<Profile, Provider, Arguments, Return>,
129    {
130        self.functions
131            .register(&self.identity.module, name.into(), |name| {
132                HostFunctionDefinition::new_scoped::<Provider, _, _, _>(name, function)
133            })
134            .map(|()| self)
135    }
136
137    pub fn with_scoped_diverging_function<Provider, Arguments, Return, Function>(
138        mut self,
139        name: impl Into<EcoString>,
140        function: Function,
141    ) -> Result<Self, HostRegistrationError>
142    where
143        Provider: HostProvider<Profile>,
144        Function: ScopedDivergingHostFunction<Profile, Provider, Arguments, Return>,
145    {
146        self.functions
147            .register(&self.identity.module, name.into(), |name| {
148                HostFunctionDefinition::new_scoped_diverging::<Provider, _, _, _>(name, function)
149            })
150            .map(|()| self)
151    }
152
153    pub fn package(&self) -> &EcoString {
154        &self.identity.package
155    }
156
157    pub fn module(&self) -> &EcoString {
158        &self.identity.module
159    }
160
161    pub fn functions(&self) -> impl ExactSizeIterator<Item = &HostFunctionSchema> {
162        self.functions.schemas()
163    }
164}
165
166impl<Profile: HostProfile> HostProviderModule<Profile> {
167    pub fn new(
168        package: impl Into<EcoString>,
169        module: impl Into<EcoString>,
170    ) -> Result<Self, HostRegistrationError> {
171        HostModuleIdentity::new(package.into(), module.into()).map(|identity| Self {
172            identity,
173            functions: RegisteredFunctions::new(),
174            external_types: RegisteredExternalTypes::new(),
175        })
176    }
177
178    pub fn with_function<Arguments, Return, Function>(
179        mut self,
180        name: impl Into<EcoString>,
181        function: Function,
182    ) -> Result<Self, HostRegistrationError>
183    where
184        Function: HostFunction<Arguments, Return>,
185    {
186        self.functions
187            .register(&self.identity.module, name.into(), |name| {
188                HostFunctionDefinition::new(name, function)
189            })
190            .map(|()| self)
191    }
192
193    pub fn with_fallible_function<Arguments, Return, Function>(
194        mut self,
195        name: impl Into<EcoString>,
196        function: Function,
197    ) -> Result<Self, HostRegistrationError>
198    where
199        Function: FallibleHostFunction<Arguments, Return>,
200    {
201        self.functions
202            .register(&self.identity.module, name.into(), |name| {
203                HostFunctionDefinition::new_fallible(name, function)
204            })
205            .map(|()| self)
206    }
207
208    pub fn with_scoped_function<Provider, Arguments, Return, Function>(
209        mut self,
210        name: impl Into<EcoString>,
211        function: Function,
212    ) -> Result<Self, HostRegistrationError>
213    where
214        Provider: HostProvider<Profile>,
215        Function: ScopedHostFunction<Profile, Provider, Arguments, Return>,
216    {
217        self.functions
218            .register(&self.identity.module, name.into(), |name| {
219                HostFunctionDefinition::new_scoped::<Provider, _, _, _>(name, function)
220            })
221            .map(|()| self)
222    }
223
224    /// Registers a scoped callback and the exact intermediate types it may construct.
225    pub fn with_scoped_function_and_constructions<
226        Provider,
227        Arguments,
228        Return,
229        Constructions,
230        Function,
231    >(
232        mut self,
233        name: impl Into<EcoString>,
234        function: Function,
235    ) -> Result<Self, HostRegistrationError>
236    where
237        Provider: HostProvider<Profile>,
238        Constructions: crate::host::HostTypeSequence,
239        Function:
240            ScopedConstructingHostFunction<Profile, Provider, Arguments, Return, Constructions>,
241    {
242        self.functions
243            .register(&self.identity.module, name.into(), |name| {
244                HostFunctionDefinition::new_scoped_with_constructions::<
245                    Provider,
246                    Arguments,
247                    Return,
248                    Constructions,
249                    Function,
250                >(name, function)
251            })
252            .map(|()| self)
253    }
254
255    pub fn with_scoped_diverging_function<Provider, Arguments, Return, Function>(
256        mut self,
257        name: impl Into<EcoString>,
258        function: Function,
259    ) -> Result<Self, HostRegistrationError>
260    where
261        Provider: HostProvider<Profile>,
262        Function: ScopedDivergingHostFunction<Profile, Provider, Arguments, Return>,
263    {
264        self.functions
265            .register(&self.identity.module, name.into(), |name| {
266                HostFunctionDefinition::new_scoped_diverging::<Provider, _, _, _>(name, function)
267            })
268            .map(|()| self)
269    }
270
271    pub fn with_external_type<Provider, Schema>(mut self) -> Result<Self, HostRegistrationError>
272    where
273        Schema: HostExternalSchema,
274        Provider: HostExternalBinding<Profile, Schema>,
275    {
276        let schema = HostExternalTypeSchema::of::<Schema>();
277        self.external_types
278            .register(&self.identity.module, schema)
279            .map(|()| self)
280    }
281
282    pub fn package(&self) -> &EcoString {
283        &self.identity.package
284    }
285
286    pub fn module(&self) -> &EcoString {
287        &self.identity.module
288    }
289
290    pub fn functions(&self) -> impl ExactSizeIterator<Item = &HostFunctionSchema> {
291        self.functions.schemas()
292    }
293
294    pub fn external_types(&self) -> impl ExactSizeIterator<Item = &HostExternalTypeSchema> {
295        self.external_types.schemas()
296    }
297}
298
299impl<Profile: HostProfile> HostProviderSet<Profile> {
300    pub fn new(
301        modules: impl IntoIterator<Item = HostModule<Profile>>,
302    ) -> Result<Self, HostRegistrationError> {
303        Self::with_providers(modules, Vec::<HostProviderModule<Profile>>::new())
304    }
305
306    pub fn with_providers(
307        modules: impl IntoIterator<Item = HostModule<Profile>>,
308        providers: impl IntoIterator<Item = HostProviderModule<Profile>>,
309    ) -> Result<Self, HostRegistrationError> {
310        let modules = modules.into_iter().collect::<Vec<_>>();
311        let providers = providers.into_iter().collect::<Vec<_>>();
312        let identities = modules
313            .iter()
314            .map(|module| (&module.identity.package, &module.identity.module))
315            .chain(
316                providers
317                    .iter()
318                    .map(|module| (&module.identity.package, &module.identity.module)),
319            )
320            .collect::<Vec<_>>();
321        validate_module_identities(&identities).map(|()| Self { modules, providers })
322    }
323
324    pub fn modules(&self) -> impl ExactSizeIterator<Item = &HostModule<Profile>> {
325        self.modules.iter()
326    }
327
328    pub fn providers(&self) -> impl ExactSizeIterator<Item = &HostProviderModule<Profile>> {
329        self.providers.iter()
330    }
331
332    pub(crate) fn select_source_providers(
333        mut self,
334        source_modules: &BTreeSet<(EcoString, EcoString)>,
335    ) -> Self {
336        self.providers.retain(|provider| {
337            source_modules.contains(&(
338                provider.identity.package.clone(),
339                provider.identity.module.clone(),
340            ))
341        });
342        self
343    }
344
345    pub(crate) fn into_registered(
346        self,
347    ) -> (
348        Vec<RegisteredHostModule>,
349        Vec<RegisteredHostProviderModule>,
350        RegisteredHostImplementations<Profile>,
351    ) {
352        let mut implementations = RegisteredHostImplementations::new();
353        let mut modules = Vec::with_capacity(self.modules.len());
354        for module in self.modules {
355            modules.push(RegisteredHostModule {
356                package: module.identity.package,
357                module: module.identity.module,
358                functions: module.functions.into_registered(&mut implementations),
359            });
360        }
361        let mut providers = Vec::with_capacity(self.providers.len());
362        for provider in self.providers {
363            providers.push(RegisteredHostProviderModule {
364                package: provider.identity.package,
365                module: provider.identity.module,
366                functions: provider.functions.into_registered(&mut implementations),
367                external_types: provider.external_types.into_vec(),
368            });
369        }
370        (modules, providers, implementations)
371    }
372}
373
374impl HostModuleIdentity {
375    fn new(package: EcoString, module: EcoString) -> Result<Self, HostRegistrationError> {
376        validate_module_name(&module)?;
377        Ok(Self { package, module })
378    }
379}
380
381fn validate_module_name(module: &EcoString) -> Result<(), HostRegistrationError> {
382    let valid = module != PRELUDE_MODULE_NAME
383        && !module.is_empty()
384        && module.split('/').all(|segment| {
385            !segment.is_empty()
386                && string_to_keyword(segment).is_none()
387                && check_name_case(
388                    SrcSpan::new(0, 0),
389                    &EcoString::from(segment),
390                    Named::Function,
391                )
392                .is_ok()
393        });
394    if valid {
395        Ok(())
396    } else {
397        Err(HostRegistrationError::InvalidModuleName {
398            module: module.clone(),
399        })
400    }
401}
402
403fn validate_module_identities(
404    identities: &[(&EcoString, &EcoString)],
405) -> Result<(), HostRegistrationError> {
406    let mut modules = BTreeMap::new();
407    for (package, module) in identities {
408        if let Some(first_package) = modules.insert((*module).clone(), (*package).clone()) {
409            return Err(HostRegistrationError::DuplicateModule {
410                module: (*module).clone(),
411                first_package,
412                second_package: (*package).clone(),
413            });
414        }
415    }
416    Ok(())
417}
418
419impl<Profile: HostProfile> RegisteredFunctions<Profile> {
420    fn new() -> Self {
421        Self {
422            functions: Vec::new(),
423        }
424    }
425
426    fn register(
427        &mut self,
428        module: &EcoString,
429        name: EcoString,
430        definition: impl FnOnce(
431            EcoString,
432        )
433            -> Result<HostFunctionDefinition<Profile>, HostRegistrationError>,
434    ) -> Result<(), HostRegistrationError> {
435        if string_to_keyword(&name).is_some()
436            || check_name_case(SrcSpan::new(0, 0), &name, Named::Function).is_err()
437        {
438            return Err(HostRegistrationError::InvalidFunctionName {
439                module: module.clone(),
440                function: name,
441            });
442        }
443        if self
444            .functions
445            .iter()
446            .any(|function| function.schema().name() == &name)
447        {
448            return Err(HostRegistrationError::DuplicateFunction {
449                module: module.clone(),
450                function: name,
451            });
452        }
453        let definition = definition(name)?;
454        self.functions.push(definition);
455        Ok(())
456    }
457
458    fn schemas(&self) -> impl ExactSizeIterator<Item = &HostFunctionSchema> {
459        self.functions.iter().map(HostFunctionDefinition::schema)
460    }
461
462    fn into_registered(
463        self,
464        implementations: &mut RegisteredHostImplementations<Profile>,
465    ) -> Vec<RegisteredHostFunction> {
466        let mut registered = Vec::with_capacity(self.functions.len());
467        for function in self.functions {
468            registered.push(implementations.register(function));
469        }
470        registered
471    }
472}
473
474impl RegisteredExternalTypes {
475    fn new() -> Self {
476        Self { types: Vec::new() }
477    }
478
479    fn register(
480        &mut self,
481        module: &EcoString,
482        schema: HostExternalTypeSchema,
483    ) -> Result<(), HostRegistrationError> {
484        let name = schema.name().clone();
485        if check_name_case(SrcSpan::new(0, 0), &name, Named::Type).is_err() {
486            return Err(HostRegistrationError::InvalidExternalTypeName {
487                module: module.clone(),
488                type_: name,
489            });
490        }
491        if self
492            .types
493            .iter()
494            .any(|registered| registered.name() == &name)
495        {
496            return Err(HostRegistrationError::DuplicateExternalType {
497                module: module.clone(),
498                type_: name,
499            });
500        }
501        self.types.push(schema);
502        Ok(())
503    }
504
505    fn schemas(&self) -> impl ExactSizeIterator<Item = &HostExternalTypeSchema> {
506        self.types.iter()
507    }
508
509    fn into_vec(self) -> Vec<HostExternalTypeSchema> {
510        self.types
511    }
512}
513
514impl RegisteredHostModule {
515    pub(crate) fn package(&self) -> &EcoString {
516        &self.package
517    }
518
519    pub(crate) fn module(&self) -> &EcoString {
520        &self.module
521    }
522
523    pub(crate) fn functions(&self) -> impl ExactSizeIterator<Item = &HostFunctionSchema> {
524        self.functions.iter().map(RegisteredHostFunction::schema)
525    }
526
527    pub(crate) fn into_parts(self) -> (EcoString, EcoString, Vec<RegisteredHostFunction>) {
528        (self.package, self.module, self.functions)
529    }
530}
531
532impl RegisteredHostProviderModule {
533    pub(crate) fn into_parts(
534        self,
535    ) -> (
536        EcoString,
537        EcoString,
538        Vec<RegisteredHostFunction>,
539        Vec<HostExternalTypeSchema>,
540    ) {
541        (
542            self.package,
543            self.module,
544            self.functions,
545            self.external_types,
546        )
547    }
548}
549
550impl RegisteredHostFunction {
551    pub(crate) fn schema(&self) -> &HostFunctionSchema {
552        &self.schema
553    }
554
555    pub(crate) fn into_parts(
556        self,
557    ) -> (
558        HostFunctionSchema,
559        super::RegisteredHostConstructions,
560        RegisteredHostImplementationId,
561    ) {
562        (self.schema, self.constructions, self.implementation)
563    }
564}
565
566impl<Profile: HostProfile> RegisteredHostImplementations<Profile> {
567    fn new() -> Self {
568        Self {
569            functions: Vec::new(),
570        }
571    }
572
573    fn register(&mut self, definition: HostFunctionDefinition<Profile>) -> RegisteredHostFunction {
574        let (schema, constructions, implementation) = definition.into_parts();
575        let id = RegisteredHostImplementationId(self.functions.len());
576        self.functions.push(Arc::new(implementation));
577        RegisteredHostFunction {
578            schema,
579            constructions,
580            implementation: id,
581        }
582    }
583
584    pub(crate) fn implementation(
585        &self,
586        id: RegisteredHostImplementationId,
587    ) -> Arc<HostFunctionImplementation<Profile>> {
588        Arc::clone(&self.functions[id.0])
589    }
590}
591
592#[cfg(test)]
593mod tests {
594    use super::{HostModule, HostProviderModule, HostProviderSet, RegisteredFunctions};
595    use crate::host::function::CallArguments;
596    use crate::host::test::{TestHostCallRuntime, TestHostProfile, TestRunState};
597    use crate::host::{
598        ExternalTestProfile, ExternalTestRunState, ExternalTestStores, HostCall,
599        HostCallCompletion, HostCallError, HostExternalBinding, HostExternalSchema,
600        HostExternalStorage, HostExternalStore, HostFailure, HostFunctionDefinition, HostProvider,
601        HostRegistrationError, HostScopedValue, HostStoredValue, StatelessHostProfile,
602        expect_never_implementation, expect_value_implementation,
603    };
604    use crate::plan::ValueType;
605    use ecow::EcoString;
606    use num_bigint::BigInt;
607    use std::cell::Cell;
608    use std::collections::BTreeSet;
609    use std::convert::Infallible;
610
611    struct Counter;
612
613    struct CounterSchema;
614
615    struct InvalidCounterSchema;
616
617    struct CounterStorage;
618
619    struct InvalidCounterStorage;
620
621    impl HostProvider<TestHostProfile> for Counter {
622        type State = usize;
623
624        fn project(state: &mut TestRunState) -> &mut Self::State {
625            &mut state.counter
626        }
627    }
628
629    impl HostProvider<ExternalTestProfile> for Counter {
630        type State = ();
631
632        fn project(state: &mut ExternalTestRunState) -> &mut Self::State {
633            &mut state.provider
634        }
635    }
636
637    impl HostExternalSchema for CounterSchema {
638        const PACKAGE: &'static str = "application";
639        const MODULE: &'static str = "main";
640        const NAME: &'static str = "Counter";
641        const PARAMETER_COUNT: usize = 1;
642    }
643
644    impl HostExternalStorage<ExternalTestProfile, CounterSchema> for CounterStorage {
645        type Payload = usize;
646
647        fn store(stores: &ExternalTestStores) -> &HostExternalStore<Self::Payload> {
648            &stores.indices
649        }
650
651        fn source_equal(
652            _: &crate::host::HostExternalEquality<'_>,
653            left: &Self::Payload,
654            right: &Self::Payload,
655        ) -> bool {
656            left == right
657        }
658
659        fn source_hash(_: &crate::host::HostExternalHashing<'_>, value: &Self::Payload) -> u64 {
660            *value as u64
661        }
662
663        fn inspect(
664            _: &crate::host::HostExternalInspection<'_>,
665            value: &Self::Payload,
666        ) -> EcoString {
667            value.to_string().into()
668        }
669    }
670
671    impl HostExternalBinding<ExternalTestProfile, CounterSchema> for Counter {
672        type Storage = CounterStorage;
673    }
674
675    impl HostExternalSchema for InvalidCounterSchema {
676        const PACKAGE: &'static str = "application";
677        const MODULE: &'static str = "main";
678        const NAME: &'static str = "counter";
679        const PARAMETER_COUNT: usize = 0;
680    }
681
682    impl HostExternalStorage<ExternalTestProfile, InvalidCounterSchema> for InvalidCounterStorage {
683        type Payload = usize;
684
685        fn store(stores: &ExternalTestStores) -> &HostExternalStore<Self::Payload> {
686            &stores.indices
687        }
688
689        fn source_equal(
690            _: &crate::host::HostExternalEquality<'_>,
691            left: &Self::Payload,
692            right: &Self::Payload,
693        ) -> bool {
694            left == right
695        }
696
697        fn source_hash(_: &crate::host::HostExternalHashing<'_>, value: &Self::Payload) -> u64 {
698            *value as u64
699        }
700
701        fn inspect(
702            _: &crate::host::HostExternalInspection<'_>,
703            value: &Self::Payload,
704        ) -> EcoString {
705            value.to_string().into()
706        }
707    }
708
709    impl HostExternalBinding<ExternalTestProfile, InvalidCounterSchema> for Counter {
710        type Storage = InvalidCounterStorage;
711    }
712
713    fn increment<'call>(
714        mut call: HostCall<'call, TestHostProfile, Counter, BigInt>,
715    ) -> Result<HostCallCompletion<'call, BigInt>, HostCallError> {
716        *call.state() += 1;
717        let value = BigInt::from(*call.state());
718        Ok(call.return_value(value))
719    }
720
721    fn stop<'call>(
722        mut call: HostCall<'call, TestHostProfile, Counter, BigInt>,
723    ) -> Result<Infallible, HostCallError> {
724        *call.state() += 1;
725        Err(HostFailure::new("stopped").into())
726    }
727
728    #[test]
729    fn provider_set_exposes_source_less_and_source_backed_schemas() {
730        let module = HostModule::new("host_support", "host/math")
731            .expect("module should be valid")
732            .with_function("add", <BigInt as std::ops::Add>::add)
733            .expect("function should be valid");
734        let provider = HostProviderModule::<StatelessHostProfile>::new("application", "main")
735            .expect("provider module should be valid")
736            .with_function("checked", BigInt::default)
737            .expect("fallible function should be valid");
738        let hosts = HostProviderSet::with_providers([module], [provider])
739            .expect("host module identities should be unique");
740
741        let module = hosts.modules().next().expect("module should exist");
742        let provider = hosts.providers().next().expect("provider should exist");
743
744        assert_eq!(module.package(), "host_support");
745        assert_eq!(module.module(), "host/math");
746        assert_eq!(module.functions().next().expect("function").name(), "add");
747        assert_eq!(provider.package(), "application");
748        assert_eq!(provider.module(), "main");
749        assert_eq!(
750            provider
751                .functions()
752                .next()
753                .expect("function")
754                .type_()
755                .return_(),
756            &ValueType::Int,
757        );
758    }
759
760    #[test]
761    fn source_provider_selection_precedes_compact_implementation_registration() {
762        let module = HostModule::new("host_support", "host/math")
763            .expect("source-less module should be valid")
764            .with_function("add", <BigInt as std::ops::Add>::add)
765            .expect("source-less function should be valid");
766        let unused = HostProviderModule::<StatelessHostProfile>::new("application", "unused")
767            .expect("unused provider should be valid")
768            .with_function("value", BigInt::default)
769            .expect("unused provider function should be valid");
770        let first = HostProviderModule::<StatelessHostProfile>::new("application", "first")
771            .expect("first provider should be valid")
772            .with_function("value", BigInt::default)
773            .expect("first provider function should be valid");
774        let second = HostProviderModule::<StatelessHostProfile>::new("dependency", "second")
775            .expect("second provider should be valid")
776            .with_function("value", BigInt::default)
777            .expect("second provider function should be valid");
778        let selected = BTreeSet::from([
779            (EcoString::from("application"), EcoString::from("first")),
780            (EcoString::from("dependency"), EcoString::from("second")),
781        ]);
782        let hosts = HostProviderSet::with_providers([module], [unused, first, second])
783            .expect("host module identities should be unique")
784            .select_source_providers(&selected);
785        let (modules, providers, implementations) = hosts.into_registered();
786
787        assert_eq!(modules.len(), 1);
788        assert_eq!(providers.len(), 2);
789        assert_eq!(providers[0].package, "application");
790        assert_eq!(providers[0].module, "first");
791        assert_eq!(providers[0].functions[0].implementation.0, 1);
792        assert_eq!(providers[1].package, "dependency");
793        assert_eq!(providers[1].module, "second");
794        assert_eq!(providers[1].functions[0].implementation.0, 2);
795        assert_eq!(implementations.functions.len(), 3);
796    }
797
798    #[test]
799    fn provider_module_exposes_registered_external_type_schemas() {
800        let provider = HostProviderModule::<ExternalTestProfile>::new("application", "main")
801            .expect("provider module should be valid")
802            .with_external_type::<Counter, CounterSchema>()
803            .expect("external type should be valid");
804        let schema = provider
805            .external_types()
806            .next()
807            .expect("external type should be registered");
808
809        assert_eq!(schema.package(), "application");
810        assert_eq!(schema.module(), "main");
811        assert_eq!(schema.name(), "Counter");
812        assert_eq!(schema.parameter_count(), 1);
813
814        let hosts = HostProviderSet::with_providers(
815            Vec::<HostModule<ExternalTestProfile>>::new(),
816            [provider],
817        )
818        .expect("provider module should be unique");
819        let (_, mut providers, _) = hosts.into_registered();
820        let (_, _, _, external_types) = providers
821            .pop()
822            .expect("provider module should be registered")
823            .into_parts();
824
825        assert_eq!(
826            external_types,
827            [crate::host::HostExternalTypeSchema::of::<CounterSchema>()]
828        );
829    }
830
831    #[test]
832    fn rejects_invalid_and_duplicate_external_type_registrations() {
833        assert_eq!(
834            HostProviderModule::<ExternalTestProfile>::new("application", "main")
835                .expect("provider module should be valid")
836                .with_external_type::<Counter, InvalidCounterSchema>()
837                .err(),
838            Some(HostRegistrationError::InvalidExternalTypeName {
839                module: "main".into(),
840                type_: "counter".into(),
841            }),
842        );
843        assert_eq!(
844            HostProviderModule::<ExternalTestProfile>::new("application", "main")
845                .expect("provider module should be valid")
846                .with_external_type::<Counter, CounterSchema>()
847                .expect("first external type should be valid")
848                .with_external_type::<Counter, CounterSchema>()
849                .err(),
850            Some(HostRegistrationError::DuplicateExternalType {
851                module: "main".into(),
852                type_: "Counter".into(),
853            }),
854        );
855    }
856
857    #[test]
858    fn external_storage_protocol_projects_payload_semantics() {
859        let stores = ExternalTestStores::default();
860        let mut state = ExternalTestRunState::default();
861        let equal =
862            |_: &crate::runtime::StoredRuntimeValue, _: &crate::runtime::StoredRuntimeValue| false;
863        let source_hash = |_: &crate::runtime::StoredRuntimeValue| 0;
864        let inspect = |_: &crate::runtime::StoredRuntimeValue| EcoString::new();
865        let equality = crate::host::HostExternalEquality::new(&equal);
866        let hashing = crate::host::HostExternalHashing::new(&source_hash);
867        let inspection = crate::host::HostExternalInspection::new(&inspect);
868        let stored = HostStoredValue::<BigInt>::new(crate::runtime::StoredRuntimeValue::test_int(
869            BigInt::from(7),
870        ));
871
872        assert!(std::ptr::eq(
873            <Counter as HostProvider<ExternalTestProfile>>::project(&mut state),
874            &state.provider,
875        ));
876        assert_eq!(inspection.inspect_stored_value(&stored), "");
877        assert!(std::ptr::eq(
878            <CounterStorage as HostExternalStorage<ExternalTestProfile, CounterSchema>>::store(
879                &stores,
880            ),
881            &stores.indices,
882        ));
883        assert!(<CounterStorage as HostExternalStorage<
884            ExternalTestProfile,
885            CounterSchema,
886        >>::source_equal(&equality, &7, &7),);
887        assert_eq!(
888            <CounterStorage as HostExternalStorage<ExternalTestProfile, CounterSchema>>::source_hash(
889                &hashing, &7,
890            ),
891            7,
892        );
893        assert_eq!(
894            <CounterStorage as HostExternalStorage<ExternalTestProfile, CounterSchema>>::inspect(
895                &inspection,
896                &7,
897            ),
898            "7",
899        );
900        assert!(std::ptr::eq(
901            <InvalidCounterStorage as HostExternalStorage<
902                ExternalTestProfile,
903                InvalidCounterSchema,
904            >>::store(&stores),
905            &stores.indices,
906        ));
907        assert!(<InvalidCounterStorage as HostExternalStorage<
908            ExternalTestProfile,
909            InvalidCounterSchema,
910        >>::source_equal(&equality, &8, &8),);
911        assert_eq!(
912            <InvalidCounterStorage as HostExternalStorage<
913                ExternalTestProfile,
914                InvalidCounterSchema,
915            >>::source_hash(&hashing, &8,),
916            8,
917        );
918        assert_eq!(
919            <InvalidCounterStorage as HostExternalStorage<
920                ExternalTestProfile,
921                InvalidCounterSchema,
922            >>::inspect(&inspection, &8,),
923            "8",
924        );
925    }
926
927    #[test]
928    fn scoped_registration_projects_provider_state() {
929        let provider = HostProviderModule::<TestHostProfile>::new("application", "main")
930            .expect("provider module should be valid")
931            .with_scoped_function::<Counter, _, _, _>("increment", increment)
932            .expect("scoped function should be valid");
933
934        assert_eq!(provider.functions().len(), 1);
935        let hosts =
936            HostProviderSet::with_providers(Vec::<HostModule<TestHostProfile>>::new(), [provider])
937                .expect("provider module should be unique");
938        let (_, mut providers, implementations) = hosts.into_registered();
939        let (_, _, mut definitions, _) = providers
940            .pop()
941            .expect("provider module should be registered")
942            .into_parts();
943        let (_, _, implementation) = definitions
944            .pop()
945            .expect("scoped function should be registered")
946            .into_parts();
947        let registered_implementation = implementations.implementation(implementation);
948        let implementation = expect_value_implementation(registered_implementation.as_ref());
949        let mut state = TestRunState {
950            counter: 41,
951            unrelated: true,
952        };
953        let arguments = CallArguments::new(Vec::new(), Vec::new());
954        let mut runtime = TestHostCallRuntime::new(&mut state, arguments);
955
956        let token = implementation
957            .call(&mut runtime)
958            .expect("scoped function should succeed");
959        assert_eq!(token.family, crate::host::HostValueFamily::Int);
960        drop(runtime);
961        assert_eq!(state.counter, 42);
962        assert!(state.unrelated);
963    }
964
965    #[test]
966    fn scoped_diverging_provider_registration_preserves_the_source_return_type() {
967        let provider = HostProviderModule::<TestHostProfile>::new("application", "main")
968            .expect("provider module should be valid")
969            .with_scoped_diverging_function::<Counter, (), BigInt, _>("stop", stop)
970            .expect("scoped diverging function should be valid");
971
972        let schema = provider
973            .functions()
974            .next()
975            .expect("scoped diverging function should have a schema");
976        assert_eq!(schema.name(), "stop");
977        assert_eq!(schema.type_().argument_types(), []);
978        assert_eq!(schema.type_().return_(), &ValueType::Int);
979
980        let hosts =
981            HostProviderSet::with_providers(Vec::<HostModule<TestHostProfile>>::new(), [provider])
982                .expect("provider module should be unique");
983        let (_, mut providers, implementations) = hosts.into_registered();
984        let (_, _, mut definitions, _) = providers
985            .pop()
986            .expect("provider module should be registered")
987            .into_parts();
988        let (_, _, implementation) = definitions
989            .pop()
990            .expect("scoped diverging function should be registered")
991            .into_parts();
992        let registered = implementations.implementation(implementation);
993        let implementation = expect_never_implementation(registered.as_ref());
994        let mut state = TestRunState::default();
995        let arguments = CallArguments::new(Vec::new(), Vec::new());
996        let mut runtime = TestHostCallRuntime::new(&mut state, arguments);
997
998        assert_eq!(
999            implementation
1000                .call(&mut runtime)
1001                .expect_err("scoped diverging function should fail")
1002                .to_string(),
1003            "stopped",
1004        );
1005        drop(runtime);
1006        assert_eq!(state.counter, 1);
1007    }
1008
1009    #[test]
1010    fn source_less_profile_registration_invokes_fallible_and_scoped_callbacks() {
1011        let module = HostModule::<TestHostProfile>::new_for_profile("host_support", "host/state")
1012            .expect("module should be valid")
1013            .with_fallible_function("checked", || {
1014                Result::<BigInt, HostFailure>::Ok(BigInt::from(7))
1015            })
1016            .expect("fallible function should be valid")
1017            .with_scoped_function::<Counter, _, _, _>("increment", increment)
1018            .expect("scoped function should be valid");
1019        let hosts = HostProviderSet::new([module]).expect("host module should be unique");
1020        let (mut modules, _, implementations) = hosts.into_registered();
1021        let (_, _, definitions) = modules
1022            .pop()
1023            .expect("host module should be registered")
1024            .into_parts();
1025        let mut definitions = definitions.into_iter();
1026        let (_, _, checked) = definitions
1027            .next()
1028            .expect("fallible function should be registered")
1029            .into_parts();
1030        let checked_implementation = implementations.implementation(checked);
1031        let checked = expect_value_implementation(checked_implementation.as_ref());
1032        let (_, _, increment) = definitions
1033            .next()
1034            .expect("scoped function should be registered")
1035            .into_parts();
1036        let increment_implementation = implementations.implementation(increment);
1037        let increment = expect_value_implementation(increment_implementation.as_ref());
1038        let mut state = TestRunState {
1039            counter: 9,
1040            unrelated: true,
1041        };
1042
1043        let mut runtime =
1044            TestHostCallRuntime::new(&mut state, CallArguments::new(Vec::new(), Vec::new()));
1045        assert_eq!(
1046            checked
1047                .call(&mut runtime)
1048                .expect("fallible function should succeed")
1049                .family,
1050            crate::host::HostValueFamily::Int,
1051        );
1052        assert_eq!(
1053            runtime.completed(),
1054            Some(&HostScopedValue::Int(BigInt::from(7))),
1055        );
1056        drop(runtime);
1057        let mut runtime =
1058            TestHostCallRuntime::new(&mut state, CallArguments::new(Vec::new(), Vec::new()));
1059        let token = increment
1060            .call(&mut runtime)
1061            .expect("scoped function should succeed");
1062        assert_eq!(token.family, crate::host::HostValueFamily::Int);
1063        drop(runtime);
1064        assert_eq!(state.counter, 10);
1065        assert!(state.unrelated);
1066    }
1067
1068    #[test]
1069    fn rejects_invalid_module_and_function_names() {
1070        assert_eq!(
1071            HostModule::<TestHostProfile>::new_for_profile("host_support", "").err(),
1072            Some(HostRegistrationError::InvalidModuleName { module: "".into() }),
1073        );
1074        assert_eq!(
1075            HostProviderModule::<TestHostProfile>::new("host_support", "gleam").err(),
1076            Some(HostRegistrationError::InvalidModuleName {
1077                module: "gleam".into(),
1078            }),
1079        );
1080        assert_eq!(
1081            HostModule::<TestHostProfile>::new_for_profile("host_support", "host/math")
1082                .expect("module should be valid")
1083                .with_function("Add", <BigInt as std::ops::Add>::add)
1084                .err(),
1085            Some(HostRegistrationError::InvalidFunctionName {
1086                module: "host/math".into(),
1087                function: "Add".into(),
1088            }),
1089        );
1090        assert_eq!(
1091            HostModule::<StatelessHostProfile>::new("host_support", "").err(),
1092            Some(HostRegistrationError::InvalidModuleName { module: "".into() }),
1093        );
1094        assert_eq!(
1095            HostProviderModule::<StatelessHostProfile>::new("host_support", "gleam").err(),
1096            Some(HostRegistrationError::InvalidModuleName {
1097                module: "gleam".into(),
1098            }),
1099        );
1100        assert_eq!(
1101            HostModule::<StatelessHostProfile>::new("host_support", "host/math")
1102                .expect("module should be valid")
1103                .with_function("Add", <BigInt as std::ops::Add>::add)
1104                .err(),
1105            Some(HostRegistrationError::InvalidFunctionName {
1106                module: "host/math".into(),
1107                function: "Add".into(),
1108            }),
1109        );
1110    }
1111
1112    #[test]
1113    fn rejects_duplicate_functions_and_module_identities() {
1114        assert_eq!(
1115            HostModule::<TestHostProfile>::new_for_profile("host_support", "host/math")
1116                .expect("module should be valid")
1117                .with_function("add", <BigInt as std::ops::Add>::add)
1118                .expect("function should be valid")
1119                .with_function("add", <BigInt as std::ops::Add>::add)
1120                .err(),
1121            Some(HostRegistrationError::DuplicateFunction {
1122                module: "host/math".into(),
1123                function: "add".into(),
1124            }),
1125        );
1126        let module = HostModule::<StatelessHostProfile>::new("host_support", "host/math")
1127            .expect("module should be valid")
1128            .with_function("add", <BigInt as std::ops::Add>::add)
1129            .expect("function should be valid");
1130        assert_eq!(
1131            module
1132                .with_function("add", <BigInt as std::ops::Add>::add)
1133                .err(),
1134            Some(HostRegistrationError::DuplicateFunction {
1135                module: "host/math".into(),
1136                function: "add".into(),
1137            }),
1138        );
1139
1140        let module = HostModule::new("first", "host/math").expect("module should be valid");
1141        let provider =
1142            HostProviderModule::new("second", "host/math").expect("provider should be valid");
1143        assert_eq!(
1144            HostProviderSet::<StatelessHostProfile>::with_providers([module], [provider]).err(),
1145            Some(HostRegistrationError::DuplicateModule {
1146                module: "host/math".into(),
1147                first_package: "first".into(),
1148                second_package: "second".into(),
1149            }),
1150        );
1151    }
1152
1153    #[test]
1154    fn function_name_and_duplicate_validation_precede_definition_assembly() {
1155        let module = EcoString::from("host/generic");
1156        let mut functions = RegisteredFunctions::<TestHostProfile>::new();
1157        let assembly_count = Cell::new(0);
1158        let assemble = |name| {
1159            assembly_count.set(assembly_count.get() + 1);
1160            if name == "sparse" {
1161                Err(HostRegistrationError::NonContiguousTypeParameters {
1162                    function: name,
1163                    parameters: vec![2].into_boxed_slice(),
1164                })
1165            } else {
1166                HostFunctionDefinition::new(name, || true)
1167            }
1168        };
1169
1170        let invalid_name = functions.register(&module, "case".into(), assemble).err();
1171        assert_eq!(assembly_count.get(), 0);
1172
1173        functions
1174            .register(&module, "identity".into(), assemble)
1175            .expect("the first valid definition should be assembled");
1176        assert_eq!(assembly_count.get(), 1);
1177
1178        let duplicate = functions
1179            .register(&module, "identity".into(), assemble)
1180            .err();
1181        assert_eq!(assembly_count.get(), 1);
1182
1183        let definition_error = functions.register(&module, "sparse".into(), assemble).err();
1184        assert_eq!(assembly_count.get(), 2);
1185
1186        assert_eq!(
1187            invalid_name,
1188            Some(HostRegistrationError::InvalidFunctionName {
1189                module: "host/generic".into(),
1190                function: "case".into(),
1191            }),
1192        );
1193        assert_eq!(
1194            duplicate,
1195            Some(HostRegistrationError::DuplicateFunction {
1196                module: "host/generic".into(),
1197                function: "identity".into(),
1198            }),
1199        );
1200        assert_eq!(
1201            definition_error,
1202            Some(HostRegistrationError::NonContiguousTypeParameters {
1203                function: "sparse".into(),
1204                parameters: vec![2].into_boxed_slice(),
1205            }),
1206        );
1207    }
1208}