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 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}