Skip to main content

geam_core/host/
function.rs

1mod adapter;
2mod argument;
3mod return_;
4
5use crate::host::{HostProfile, HostProvider};
6use crate::plan::{FunctionType, TypeScheme};
7use ecow::EcoString;
8use std::collections::BTreeSet;
9use std::fmt;
10
11#[cfg(test)]
12pub(crate) use argument::CallArguments;
13pub(crate) use argument::{
14    HostBitArrayArgumentSlot, HostBoolArgumentSlot, HostCallArguments, HostCustomArgumentSlot,
15    HostExternalArgumentSlot, HostFloatArgumentSlot, HostFunctionArgumentSlot, HostIntArgumentSlot,
16    HostListArgumentSlot, HostNilArgumentSlot, HostParameter, HostStringArgumentSlot,
17    HostTupleArgumentSlot, HostUtfCodepointArgumentSlot, HostValueArgumentSlot,
18};
19pub(crate) use return_::HostNeverFunction;
20pub(crate) use return_::{HostFunctionImplementation, HostValueFunction};
21#[cfg(test)]
22pub(crate) use return_::{expect_never_implementation, expect_value_implementation};
23
24/// A Rust function that can be registered as a Geam host function.
25///
26/// Owned host functions accept zero through seven scalar arguments. Supported
27/// Rust values are `BigInt`, `f64`, `EcoString`, `BitArrayValue`, `char`,
28/// `bool`, and `()`. A host function returns one value from the same set, or
29/// `Infallible` when it cannot return successfully.
30///
31/// Scoped host functions use the same arity boundary with the typed
32/// `HostTypeParameter`, `HostListType`, `HostTupleType`, and `HostCustomType`
33/// language. Their compound values remain borrowed through one `HostCall`.
34///
35/// ```compile_fail
36/// use geam_core::HostModule;
37/// use num_bigint::BigInt;
38///
39/// let _ = HostModule::new("host_support", "host/math")
40///     .unwrap()
41///     .with_function(
42///         "too_many",
43///         |_: BigInt,
44///          _: BigInt,
45///          _: BigInt,
46///          _: BigInt,
47///          _: BigInt,
48///          _: BigInt,
49///          _: BigInt,
50///          _: BigInt|
51///          -> BigInt { 0.into() },
52///     );
53/// ```
54///
55/// ```compile_fail
56/// use geam_core::HostModule;
57///
58/// let _ = HostModule::new("host_support", "host/math")
59///     .unwrap()
60///     .with_function("unsupported", |value: i64| value);
61/// ```
62pub trait HostFunction<Arguments, Return>:
63    adapter::HostFunctionAdapter<Arguments, Return> + Send + Sync + 'static
64{
65}
66
67pub trait FallibleHostFunction<Arguments, Return>:
68    adapter::FallibleHostFunctionAdapter<Arguments, Return> + Send + Sync + 'static
69{
70}
71
72pub trait ScopedHostFunction<Profile, Provider, Arguments, Return>:
73    adapter::ScopedHostFunctionAdapter<Profile, Provider, Arguments, Return> + Send + Sync + 'static
74where
75    Profile: HostProfile,
76    Provider: HostProvider<Profile>,
77{
78}
79
80/// A scoped host function with statically registered intermediate value types.
81///
82/// Implementations receive [`crate::HostConstructions`] after the active
83/// [`crate::HostCall`] and before the source arguments.
84pub trait ScopedConstructingHostFunction<Profile, Provider, Arguments, Return, Constructions>:
85    adapter::ScopedConstructingHostFunctionAdapter<
86        Profile,
87        Provider,
88        Arguments,
89        Return,
90        Constructions,
91    > + Send
92    + Sync
93    + 'static
94where
95    Profile: HostProfile,
96    Provider: HostProvider<Profile>,
97    Constructions: crate::host::HostTypeSequence,
98{
99}
100
101pub trait ScopedDivergingHostFunction<Profile, Provider, Arguments, Return>:
102    adapter::ScopedDivergingHostFunctionAdapter<Profile, Provider, Arguments, Return>
103    + Send
104    + Sync
105    + 'static
106where
107    Profile: HostProfile,
108    Provider: HostProvider<Profile>,
109{
110}
111
112impl<Function, Arguments, Return> HostFunction<Arguments, Return> for Function where
113    Function: adapter::HostFunctionAdapter<Arguments, Return> + Send + Sync + 'static
114{
115}
116
117impl<Function, Arguments, Return> FallibleHostFunction<Arguments, Return> for Function where
118    Function: adapter::FallibleHostFunctionAdapter<Arguments, Return> + Send + Sync + 'static
119{
120}
121
122impl<Profile, Provider, Function, Arguments, Return>
123    ScopedHostFunction<Profile, Provider, Arguments, Return> for Function
124where
125    Profile: HostProfile,
126    Provider: HostProvider<Profile>,
127    Function: adapter::ScopedHostFunctionAdapter<Profile, Provider, Arguments, Return>
128        + Send
129        + Sync
130        + 'static,
131{
132}
133
134impl<Profile, Provider, Function, Arguments, Return, Constructions>
135    ScopedConstructingHostFunction<Profile, Provider, Arguments, Return, Constructions> for Function
136where
137    Profile: HostProfile,
138    Provider: HostProvider<Profile>,
139    Constructions: crate::host::HostTypeSequence,
140    Function: adapter::ScopedConstructingHostFunctionAdapter<
141            Profile,
142            Provider,
143            Arguments,
144            Return,
145            Constructions,
146        > + Send
147        + Sync
148        + 'static,
149{
150}
151
152impl<Profile, Provider, Function, Arguments, Return>
153    ScopedDivergingHostFunction<Profile, Provider, Arguments, Return> for Function
154where
155    Profile: HostProfile,
156    Provider: HostProvider<Profile>,
157    Function: adapter::ScopedDivergingHostFunctionAdapter<Profile, Provider, Arguments, Return>
158        + Send
159        + Sync
160        + 'static,
161{
162}
163
164#[derive(Clone, PartialEq, Eq)]
165pub struct HostFunctionSchema {
166    name: EcoString,
167    scheme: TypeScheme,
168    layout: Box<[HostParameter]>,
169    parameters: Box<[crate::host::HostTypeDescriptor]>,
170    return_: crate::host::HostTypeDescriptor,
171    custom_schemas: Box<[crate::host::HostCustomTypeSchema]>,
172    external_schemas: Box<[crate::host::HostExternalTypeSchema]>,
173    type_: FunctionType,
174}
175
176struct HostFunctionSchemaRegistration {
177    layout: Box<[HostParameter]>,
178    parameters: Box<[crate::host::HostTypeDescriptor]>,
179    return_: crate::host::HostTypeDescriptor,
180    custom_schemas: Box<[crate::host::HostCustomTypeSchema]>,
181}
182
183pub(crate) struct HostFunctionDefinition<Profile: HostProfile> {
184    schema: HostFunctionSchema,
185    constructions: RegisteredHostConstructions,
186    implementation: HostFunctionImplementation<Profile>,
187}
188
189pub(crate) struct RegisteredHostConstructions {
190    types: Box<[crate::host::HostTypeDescriptor]>,
191    custom_schemas: Box<[crate::host::HostCustomTypeSchema]>,
192    external_schemas: Box<[crate::host::HostExternalTypeSchema]>,
193}
194
195impl HostFunctionSchema {
196    pub fn name(&self) -> &EcoString {
197        &self.name
198    }
199
200    pub fn type_(&self) -> &FunctionType {
201        &self.type_
202    }
203
204    pub fn scheme(&self) -> &TypeScheme {
205        &self.scheme
206    }
207
208    pub(crate) fn parameters(&self) -> &[crate::host::HostTypeDescriptor] {
209        &self.parameters
210    }
211
212    pub(crate) fn layout(&self) -> &[HostParameter] {
213        &self.layout
214    }
215
216    pub(crate) fn return_type(&self) -> &crate::host::HostTypeDescriptor {
217        &self.return_
218    }
219
220    pub(crate) fn custom_schemas(&self) -> &[crate::host::HostCustomTypeSchema] {
221        &self.custom_schemas
222    }
223
224    pub(crate) fn external_schemas(&self) -> &[crate::host::HostExternalTypeSchema] {
225        &self.external_schemas
226    }
227
228    fn from_registration(
229        name: EcoString,
230        registration: HostFunctionSchemaRegistration,
231    ) -> Result<Self, crate::HostRegistrationError> {
232        let argument_types = registration
233            .parameters
234            .iter()
235            .map(crate::host::HostTypeDescriptor::value_type)
236            .collect();
237        let return_type = registration.return_.value_type();
238        let mut type_parameters = BTreeSet::new();
239        for parameter in &registration.parameters {
240            parameter.collect_type_parameters(&mut type_parameters);
241        }
242        registration
243            .return_
244            .collect_type_parameters(&mut type_parameters);
245        let type_parameters = type_parameters.into_iter().collect::<Vec<_>>();
246        if type_parameters.iter().copied().ne(0..type_parameters.len()) {
247            return Err(crate::HostRegistrationError::NonContiguousTypeParameters {
248                function: name,
249                parameters: type_parameters.into_boxed_slice(),
250            });
251        }
252        let mut external_schemas = Vec::new();
253        let mut external_identities = std::collections::HashSet::new();
254        for parameter in &registration.parameters {
255            parameter.collect_external_schemas(&mut external_schemas, &mut external_identities);
256        }
257        registration
258            .return_
259            .collect_external_schemas(&mut external_schemas, &mut external_identities);
260        Ok(Self {
261            name,
262            scheme: TypeScheme::new(type_parameters.len()),
263            layout: registration.layout,
264            parameters: registration.parameters,
265            return_: registration.return_,
266            custom_schemas: registration.custom_schemas,
267            external_schemas: external_schemas.into_boxed_slice(),
268            type_: FunctionType::new(argument_types, return_type),
269        })
270    }
271}
272
273impl RegisteredHostConstructions {
274    fn new(
275        types: Box<[crate::host::HostTypeDescriptor]>,
276        custom_schemas: Box<[crate::host::HostCustomTypeSchema]>,
277    ) -> Self {
278        let mut external_schemas = Vec::new();
279        let mut external_identities = std::collections::HashSet::new();
280        for type_ in &types {
281            type_.collect_external_schemas(&mut external_schemas, &mut external_identities);
282        }
283        Self {
284            types,
285            custom_schemas,
286            external_schemas: external_schemas.into_boxed_slice(),
287        }
288    }
289
290    pub(crate) fn empty() -> Self {
291        Self::new(Box::new([]), Box::new([]))
292    }
293
294    pub(crate) fn types(&self) -> &[crate::host::HostTypeDescriptor] {
295        &self.types
296    }
297
298    pub(crate) fn custom_schemas(&self) -> &[crate::host::HostCustomTypeSchema] {
299        &self.custom_schemas
300    }
301
302    pub(crate) fn external_schemas(&self) -> &[crate::host::HostExternalTypeSchema] {
303        &self.external_schemas
304    }
305
306    fn unbound_type_parameters(&self, parameter_count: usize) -> Box<[usize]> {
307        let mut parameters = BTreeSet::new();
308        for type_ in &self.types {
309            type_.collect_type_parameters(&mut parameters);
310        }
311        parameters
312            .into_iter()
313            .filter(|parameter| *parameter >= parameter_count)
314            .collect::<Vec<_>>()
315            .into_boxed_slice()
316    }
317}
318
319impl fmt::Debug for HostFunctionSchema {
320    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
321        let mut debug = formatter.debug_struct("HostFunctionSchema");
322        debug
323            .field("name", &self.name)
324            .field("scheme", &self.scheme)
325            .field("type_", &self.type_);
326        if !self.custom_schemas.is_empty() {
327            debug.field("custom_schemas", &self.custom_schemas);
328        }
329        if !self.external_schemas.is_empty() {
330            debug.field("external_schemas", &self.external_schemas);
331        }
332        debug.finish()
333    }
334}
335
336impl<Profile: HostProfile> HostFunctionDefinition<Profile> {
337    pub(crate) fn new<Arguments, Return, Function>(
338        name: EcoString,
339        function: Function,
340    ) -> Result<Self, crate::HostRegistrationError>
341    where
342        Function: HostFunction<Arguments, Return>,
343    {
344        let registration = <Function as adapter::HostFunctionAdapter<Arguments, Return>>::register::<
345            Profile,
346        >(function);
347        Self::from_registration(name, registration)
348    }
349
350    pub(crate) fn new_fallible<Arguments, Return, Function>(
351        name: EcoString,
352        function: Function,
353    ) -> Result<Self, crate::HostRegistrationError>
354    where
355        Function: FallibleHostFunction<Arguments, Return>,
356    {
357        let registration =
358            <Function as adapter::FallibleHostFunctionAdapter<Arguments, Return>>::register::<
359                Profile,
360            >(function);
361        Self::from_registration(name, registration)
362    }
363
364    pub(crate) fn new_scoped<Provider, Arguments, Return, Function>(
365        name: EcoString,
366        function: Function,
367    ) -> Result<Self, crate::HostRegistrationError>
368    where
369        Provider: HostProvider<Profile>,
370        Function: ScopedHostFunction<Profile, Provider, Arguments, Return>,
371    {
372        let registration = <Function as adapter::ScopedHostFunctionAdapter<
373            Profile,
374            Provider,
375            Arguments,
376            Return,
377        >>::register(function);
378        Self::from_registration(name, registration)
379    }
380
381    pub(crate) fn new_scoped_with_constructions<
382        Provider,
383        Arguments,
384        Return,
385        Constructions,
386        Function,
387    >(
388        name: EcoString,
389        function: Function,
390    ) -> Result<Self, crate::HostRegistrationError>
391    where
392        Provider: HostProvider<Profile>,
393        Constructions: crate::host::HostTypeSequence,
394        Function:
395            ScopedConstructingHostFunction<Profile, Provider, Arguments, Return, Constructions>,
396    {
397        let registration = <Function as adapter::ScopedConstructingHostFunctionAdapter<
398            Profile,
399            Provider,
400            Arguments,
401            Return,
402            Constructions,
403        >>::register(function);
404        let construction_types =
405            <Constructions as crate::host::HostAbiTypeSequence>::descriptors().into_boxed_slice();
406        let mut custom_schemas = Vec::new();
407        let mut visited = std::collections::HashSet::new();
408        <Constructions as crate::host::HostAbiTypeSequence>::collect_custom_schemas(
409            &mut custom_schemas,
410            &mut visited,
411        );
412        let constructions =
413            RegisteredHostConstructions::new(construction_types, custom_schemas.into_boxed_slice());
414        Self::from_registration_with_constructions(name, registration, constructions)
415    }
416
417    pub(crate) fn new_scoped_diverging<Provider, Arguments, Return, Function>(
418        name: EcoString,
419        function: Function,
420    ) -> Result<Self, crate::HostRegistrationError>
421    where
422        Provider: HostProvider<Profile>,
423        Function: ScopedDivergingHostFunction<Profile, Provider, Arguments, Return>,
424    {
425        let registration = <Function as adapter::ScopedDivergingHostFunctionAdapter<
426            Profile,
427            Provider,
428            Arguments,
429            Return,
430        >>::register(function);
431        Self::from_registration(name, registration)
432    }
433
434    fn from_registration(
435        name: EcoString,
436        registration: adapter::HostFunctionRegistration<Profile>,
437    ) -> Result<Self, crate::HostRegistrationError> {
438        Self::from_registration_with_constructions(
439            name,
440            registration,
441            RegisteredHostConstructions::empty(),
442        )
443    }
444
445    fn from_registration_with_constructions(
446        name: EcoString,
447        registration: adapter::HostFunctionRegistration<Profile>,
448        constructions: RegisteredHostConstructions,
449    ) -> Result<Self, crate::HostRegistrationError> {
450        let schema = HostFunctionSchemaRegistration {
451            layout: registration.parameters,
452            parameters: registration.parameter_types,
453            return_: registration.return_type,
454            custom_schemas: registration.custom_schemas,
455        };
456        let schema = HostFunctionSchema::from_registration(name, schema)?;
457        let unbound = constructions.unbound_type_parameters(schema.scheme().parameters().len());
458        if !unbound.is_empty() {
459            return Err(
460                crate::HostRegistrationError::UnboundConstructionTypeParameters {
461                    function: schema.name().clone(),
462                    parameters: unbound,
463                },
464            );
465        }
466        Ok(Self {
467            schema,
468            constructions,
469            implementation: registration.implementation,
470        })
471    }
472
473    pub(crate) fn schema(&self) -> &HostFunctionSchema {
474        &self.schema
475    }
476
477    pub(crate) fn into_parts(
478        self,
479    ) -> (
480        HostFunctionSchema,
481        RegisteredHostConstructions,
482        HostFunctionImplementation<Profile>,
483    ) {
484        (self.schema, self.constructions, self.implementation)
485    }
486}
487
488#[cfg(test)]
489mod tests {
490    use super::{HostFunctionDefinition, HostFunctionSchema, RegisteredHostConstructions};
491    use crate::BitArrayValue;
492    use crate::host::function::argument::CallArguments;
493    use crate::host::test::{TestHostCallRuntime, TestHostProfile, TestRunState};
494    use crate::host::{
495        HostCall, HostCallCompletion, HostCallError, HostCustomConstructorSchema,
496        HostCustomFieldSchema, HostCustomTypeSchema, HostExternalTypeSchema, HostListType,
497        HostProvider, HostRegistrationError, HostSchemaType, HostScopedValue, HostTypeDescriptor,
498        HostTypeIndex0, HostTypeList, HostTypeListEnd, HostValueFamily,
499        expect_value_implementation,
500    };
501    use crate::plan::ValueType;
502    use ecow::EcoString;
503    use num_bigint::BigInt;
504
505    struct ConstructionProvider;
506
507    impl HostProvider<TestHostProfile> for ConstructionProvider {
508        type State = usize;
509
510        fn project(state: &mut TestRunState) -> &mut Self::State {
511            &mut state.counter
512        }
513    }
514
515    fn ready<'call>(
516        call: HostCall<'call, TestHostProfile, ConstructionProvider, bool>,
517    ) -> Result<HostCallCompletion<'call, bool>, HostCallError> {
518        Ok(call.return_value(true))
519    }
520
521    type ConstructionTypes = HostTypeList<HostListType<BigInt>, HostTypeListEnd>;
522
523    fn ready_with_constructions<'call>(
524        call: HostCall<'call, TestHostProfile, ConstructionProvider, bool>,
525        constructions: crate::HostConstructions<'call, ConstructionTypes>,
526    ) -> Result<HostCallCompletion<'call, bool>, HostCallError> {
527        let _ = constructions.at::<HostTypeIndex0>();
528        Ok(call.return_value(true))
529    }
530
531    #[test]
532    fn definition_assembles_schema_and_int_implementation_together() {
533        let definition = HostFunctionDefinition::new(
534            "choose".into(),
535            |condition: bool, left: BigInt, right: BigInt| {
536                if condition { left } else { right }
537            },
538        )
539        .expect("contiguous scalar function should register");
540
541        assert_eq!(definition.schema().name(), "choose");
542        assert_eq!(
543            definition.schema().type_().argument_types(),
544            [ValueType::Bool, ValueType::Int, ValueType::Int],
545        );
546        assert_eq!(definition.schema().type_().return_(), &ValueType::Int);
547        assert_eq!(definition.schema().return_type(), &HostTypeDescriptor::Int);
548
549        let (_, _, implementation) = definition.into_parts();
550        let implementation = expect_value_implementation(&implementation);
551        let mut state = TestRunState::default();
552        let arguments = CallArguments::new(vec![10.into(), 20.into()], vec![false]);
553        let mut runtime = TestHostCallRuntime::new(&mut state, arguments);
554        assert_eq!(
555            implementation.call(&mut runtime).map(|token| token.family),
556            Ok(HostValueFamily::Int),
557        );
558        assert_eq!(
559            runtime.completed(),
560            Some(&HostScopedValue::Int(BigInt::from(20))),
561        );
562        let arguments = CallArguments::new(vec![10.into(), 20.into()], vec![true]);
563        let mut runtime = TestHostCallRuntime::new(&mut state, arguments);
564        assert_eq!(
565            implementation.call(&mut runtime).map(|token| token.family),
566            Ok(HostValueFamily::Int),
567        );
568        assert_eq!(
569            runtime.completed(),
570            Some(&HostScopedValue::Int(BigInt::from(10))),
571        );
572    }
573
574    #[test]
575    fn definition_assembles_schema_and_bool_implementation_together() {
576        let definition =
577            HostFunctionDefinition::new("is_positive".into(), |value: BigInt| value > 0.into())
578                .expect("monomorphic function should register");
579
580        assert_eq!(definition.schema().name(), "is_positive");
581        assert_eq!(
582            definition.schema().type_().argument_types(),
583            [ValueType::Int],
584        );
585        assert_eq!(definition.schema().type_().return_(), &ValueType::Bool);
586        assert_eq!(definition.schema().return_type(), &HostTypeDescriptor::Bool);
587
588        let (_, _, implementation) = definition.into_parts();
589        let implementation = expect_value_implementation(&implementation);
590        let mut state = TestRunState::default();
591        let arguments = CallArguments::new(vec![1.into()], Vec::new());
592        let mut runtime = TestHostCallRuntime::new(&mut state, arguments);
593        assert_eq!(
594            implementation.call(&mut runtime).map(|token| token.family),
595            Ok(HostValueFamily::Bool),
596        );
597        assert_eq!(runtime.completed(), Some(&HostScopedValue::Bool(true)));
598    }
599
600    #[test]
601    fn definition_assembles_every_scalar_parameter_from_one_layout() {
602        let definition: HostFunctionDefinition<TestHostProfile> = HostFunctionDefinition::new(
603            "consume".into(),
604            |_: BigInt, _: f64, _: EcoString, _: BitArrayValue, _: char, _: bool, (): ()| (),
605        )
606        .expect("monomorphic scalar function should register");
607
608        assert_eq!(
609            definition.schema().type_().argument_types(),
610            [
611                ValueType::Int,
612                ValueType::Float,
613                ValueType::String,
614                ValueType::BitArray,
615                ValueType::UtfCodepoint,
616                ValueType::Bool,
617                ValueType::Nil,
618            ],
619        );
620        assert_eq!(definition.schema().type_().return_(), &ValueType::Nil);
621        assert_eq!(definition.schema().return_type(), &HostTypeDescriptor::Nil);
622
623        let (_, _, implementation) = definition.into_parts();
624        let implementation = expect_value_implementation(&implementation);
625        let arguments = CallArguments::new(vec![1.into()], vec![true]).with_scalar_values(
626            vec![1.5],
627            vec!["one".into()],
628            vec![BitArrayValue::from_bytes(vec![1])],
629            vec!['A'],
630            1,
631        );
632        let mut state = TestRunState::default();
633        let mut runtime = TestHostCallRuntime::new(&mut state, arguments);
634        assert_eq!(
635            implementation.call(&mut runtime).map(|token| token.family),
636            Ok(HostValueFamily::Nil),
637        );
638        assert_eq!(runtime.completed(), Some(&HostScopedValue::Nil));
639    }
640
641    #[test]
642    fn schema_clone_contains_only_the_registered_signature() {
643        let definition: HostFunctionDefinition<TestHostProfile> =
644            HostFunctionDefinition::new("negate".into(), <bool as std::ops::Not>::not)
645                .expect("monomorphic function should register");
646        let schema = definition.schema().clone();
647
648        assert_eq!(schema, *definition.schema());
649        assert_eq!(schema.name(), "negate");
650        assert_eq!(schema.type_().argument_types(), [ValueType::Bool],);
651        assert_eq!(schema.type_().return_(), &ValueType::Bool);
652        assert_eq!(
653            format!("{schema:?}"),
654            r#"HostFunctionSchema { name: "negate", scheme: TypeScheme { parameters: [] }, type_: FunctionType { arguments: [Bool], return_: Bool } }"#,
655        );
656    }
657
658    #[test]
659    fn hidden_construction_types_stay_outside_the_public_function_schema() {
660        let plain = HostFunctionDefinition::new_scoped::<ConstructionProvider, (), bool, _>(
661            "ready".into(),
662            ready,
663        )
664        .expect("plain scoped function should register");
665        let with_constructions = HostFunctionDefinition::new_scoped_with_constructions::<
666            ConstructionProvider,
667            (),
668            bool,
669            ConstructionTypes,
670            _,
671        >("ready".into(), ready_with_constructions)
672        .expect("scoped function with hidden constructions should register");
673        let (schema, constructions, constructing_implementation) = with_constructions.into_parts();
674
675        assert_eq!(schema, *plain.schema());
676        assert_eq!(schema.scheme(), &crate::plan::TypeScheme::new(0));
677        assert_eq!(schema.type_(), plain.schema().type_());
678        assert_eq!(
679            constructions.types(),
680            [HostTypeDescriptor::List(Box::new(HostTypeDescriptor::Int))],
681        );
682        assert!(constructions.custom_schemas().is_empty());
683        assert!(constructions.external_schemas().is_empty());
684
685        let (_, _, plain_implementation) = plain.into_parts();
686        for implementation in [&plain_implementation, &constructing_implementation] {
687            let implementation = expect_value_implementation(implementation);
688            let mut state = TestRunState::default();
689            assert!(std::ptr::eq(
690                ConstructionProvider::project(&mut state),
691                &state.counter,
692            ));
693            let arguments = CallArguments::new(Vec::new(), Vec::new());
694            let mut runtime = TestHostCallRuntime::new(&mut state, arguments);
695            assert_eq!(
696                implementation.call(&mut runtime).map(|token| token.family),
697                Ok(HostValueFamily::Bool),
698            );
699            assert_eq!(runtime.completed(), Some(&HostScopedValue::Bool(true)));
700        }
701    }
702
703    #[test]
704    fn registered_constructions_report_parameters_outside_the_function_scheme() {
705        let constructions = RegisteredHostConstructions::new(
706            vec![
707                HostTypeDescriptor::List(Box::new(HostTypeDescriptor::Parameter(0))),
708                HostTypeDescriptor::Parameter(2),
709                HostTypeDescriptor::Parameter(2),
710            ]
711            .into_boxed_slice(),
712            Box::new([]),
713        );
714
715        assert_eq!(
716            constructions.unbound_type_parameters(0),
717            vec![0, 2].into_boxed_slice(),
718        );
719        assert_eq!(
720            constructions.unbound_type_parameters(1),
721            vec![2].into_boxed_slice(),
722        );
723        assert_eq!(
724            constructions.unbound_type_parameters(3),
725            Vec::<usize>::new().into_boxed_slice(),
726        );
727    }
728
729    #[test]
730    fn schema_debug_includes_custom_definitions_not_derived_from_the_function_type() {
731        let custom_schema = HostCustomTypeSchema::new(
732            "host_shapes",
733            "host/shape",
734            "Shape",
735            0,
736            [HostCustomConstructorSchema::new(
737                "Circle",
738                [HostCustomFieldSchema::new(
739                    Some("radius"),
740                    HostSchemaType::Float,
741                )],
742            )],
743        );
744        let return_ = HostTypeDescriptor::Custom {
745            schema: custom_schema.clone(),
746            arguments: Box::new([]),
747        };
748        let schema = HostFunctionSchema {
749            name: "origin".into(),
750            scheme: crate::plan::TypeScheme::new(0),
751            layout: Box::new([]),
752            parameters: Box::new([]),
753            type_: crate::plan::FunctionType::new(Vec::new(), return_.value_type()),
754            return_,
755            custom_schemas: vec![custom_schema].into_boxed_slice(),
756            external_schemas: Box::new([]),
757        };
758
759        assert_eq!(
760            format!("{schema:?}"),
761            r#"HostFunctionSchema { name: "origin", scheme: TypeScheme { parameters: [] }, type_: FunctionType { arguments: [], return_: Custom(CustomType { name: CustomTypeName { package: "host_shapes", module: "host/shape", name: "Shape" }, arguments: [] }) }, custom_schemas: [HostCustomTypeSchema { package: "host_shapes", module: "host/shape", name: "Shape", parameter_count: 0, constructors: [HostCustomConstructorSchema { name: "Circle", fields: [HostCustomFieldSchema { label: Some("radius"), type_: Float }] }] }] }"#,
762        );
763    }
764
765    #[test]
766    fn schema_debug_includes_external_definitions_not_derived_from_the_function_type() {
767        let external_schema =
768            HostExternalTypeSchema::new("host_shapes", "host/resource", "Resource", 1);
769        let return_ = HostTypeDescriptor::External {
770            schema: external_schema.clone(),
771            arguments: vec![HostTypeDescriptor::Parameter(0)].into_boxed_slice(),
772        };
773        let schema = HostFunctionSchema {
774            name: "resource".into(),
775            scheme: crate::plan::TypeScheme::new(1),
776            layout: Box::new([]),
777            parameters: Box::new([]),
778            type_: crate::plan::FunctionType::new(Vec::new(), return_.value_type()),
779            return_,
780            custom_schemas: Box::new([]),
781            external_schemas: vec![external_schema].into_boxed_slice(),
782        };
783
784        assert_eq!(
785            format!("{schema:?}"),
786            r#"HostFunctionSchema { name: "resource", scheme: TypeScheme { parameters: [TypeParameterId(0)] }, type_: FunctionType { arguments: [], return_: External(ExternalType { name: ExternalTypeName { package: "host_shapes", module: "host/resource", name: "Resource" }, arguments: [Parameter(TypeParameterId(0))] }) }, external_schemas: [HostExternalTypeSchema { package: "host_shapes", module: "host/resource", name: "Resource", parameter_count: 1 }] }"#,
787        );
788    }
789
790    #[test]
791    fn definition_rejects_non_contiguous_type_parameter_indices_before_allocating_a_scheme() {
792        let mut registration = <_ as super::adapter::HostFunctionAdapter<(), bool>>::register::<
793            TestHostProfile,
794        >(|| true);
795        registration.return_type = HostTypeDescriptor::Parameter(2);
796        let error = HostFunctionDefinition::from_registration("identity".into(), registration)
797            .err()
798            .expect("sparse type parameters should be rejected");
799
800        assert_eq!(
801            error,
802            HostRegistrationError::NonContiguousTypeParameters {
803                function: "identity".into(),
804                parameters: vec![2].into_boxed_slice(),
805            },
806        );
807        assert_eq!(
808            error.to_string(),
809            "host function identity uses type parameter indices [2]; indices must be contiguous from zero",
810        );
811    }
812}