Skip to main content

geam_core/host/external/
dynamic.rs

1use super::stored::{HostExternalPayloadBuilder, HostExternalPayloadView};
2use crate::host::{HostCall, HostProfile, HostProvider, HostType, HostTypeSequence};
3use crate::provider_support::HostStoredValueFamily;
4
5/// An existential Gleam value retained with its exact specialized type.
6///
7/// Dynamic values belong to external payloads and cannot be moved out of a
8/// shared payload view.
9///
10/// ```compile_fail
11/// use geam_core::HostStoredDynamic;
12///
13/// struct Payload {
14///     value: HostStoredDynamic,
15/// }
16///
17/// fn take(payload: &Payload) -> HostStoredDynamic {
18///     payload.value
19/// }
20/// ```
21///
22/// They are not ordinary host ABI arguments.
23///
24/// ```compile_fail
25/// use geam_core::{HostModule, HostStoredDynamic};
26/// use num_bigint::BigInt;
27///
28/// let _ = HostModule::new("host_support", "host/storage")
29///     .unwrap()
30///     .with_function(
31///         "inject",
32///         |value: HostStoredDynamic| -> BigInt {
33///             let _ = value;
34///             0.into()
35///         },
36///     );
37/// ```
38pub struct HostStoredDynamic {
39    value: crate::runtime::StoredRuntimeValue,
40}
41
42impl HostStoredDynamic {
43    pub(crate) fn new(value: crate::runtime::StoredRuntimeValue) -> Self {
44        Self { value }
45    }
46
47    pub(crate) fn value_family(&self) -> HostStoredValueFamily {
48        self.value.family()
49    }
50
51    pub(crate) fn has_external_schema<Schema>(&self) -> bool
52    where
53        Schema: crate::host::HostExternalSchema,
54    {
55        let crate::plan::ValueType::External(type_) = self.value.type_() else {
56            return false;
57        };
58        let name = type_.type_name();
59        name.package() == Schema::PACKAGE
60            && name.module() == Schema::MODULE
61            && name.name() == Schema::NAME
62            && type_.arguments().len() == Schema::PARAMETER_COUNT
63    }
64
65    #[expect(
66        clippy::result_large_err,
67        reason = "non-tuples retain the original value without another heap allocation"
68    )]
69    pub(crate) fn map_tuple_items<Item>(
70        self,
71        mut map: impl FnMut(Self) -> Item,
72    ) -> Result<Box<[Item]>, Self> {
73        self.value
74            .map_tuple_items(|value| map(Self::new(value)))
75            .map_err(Self::new)
76    }
77
78    pub(super) fn runtime_value(&self) -> &crate::runtime::StoredRuntimeValue {
79        &self.value
80    }
81
82    pub(crate) fn decode<'call, Profile, Provider, Return, Type>(
83        &self,
84        call: &mut HostCall<'call, Profile, Provider, Return>,
85    ) -> Option<Type::Value<'call>>
86    where
87        Profile: HostProfile,
88        Provider: HostProvider<Profile>,
89        Return: HostType,
90        Type: HostType,
91    {
92        let requested = call.resolve_host_type::<Type>()?;
93        if !self.has_type(&requested) {
94            return None;
95        }
96        Some(call.restore_runtime_value::<Type>(&self.value))
97    }
98
99    fn has_type(&self, type_: &crate::plan::ValueType) -> bool {
100        self.value.type_() == type_
101    }
102}
103
104impl<Profile, Arguments> HostExternalPayloadBuilder<'_, Profile, Arguments>
105where
106    Profile: HostProfile,
107    Arguments: HostTypeSequence,
108{
109    /// Retains a typed value for later existential decoding.
110    pub fn store_dynamic<Type>(&mut self, value: Type::Value<'_>) -> HostStoredDynamic
111    where
112        Type: HostType,
113    {
114        HostStoredDynamic::new(
115            self.runtime
116                .retain_stored(crate::host::type_::into_scoped::<Type>(value)),
117        )
118    }
119}
120
121impl<'call, Payload, Arguments> HostExternalPayloadView<'call, Payload, Arguments>
122where
123    Arguments: HostTypeSequence,
124{
125    /// Decodes a retained value when its exact specialized type matches.
126    pub fn decode<Profile, Provider, Return, Type>(
127        &self,
128        call: &mut HostCall<'call, Profile, Provider, Return>,
129        select: impl FnOnce(&Payload) -> &HostStoredDynamic,
130    ) -> Option<Type::Value<'call>>
131    where
132        Profile: HostProfile,
133        Provider: HostProvider<Profile>,
134        Return: HostType,
135        Type: HostType,
136    {
137        select(&self.value).decode::<Profile, Provider, Return, Type>(call)
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    use super::HostStoredDynamic;
144    use crate::plan::ValueType;
145    use num_bigint::BigInt;
146
147    #[test]
148    fn dynamic_value_matches_only_its_exact_specialized_type() {
149        let stored = HostStoredDynamic::new(crate::runtime::StoredRuntimeValue::test_int(
150            BigInt::from(7),
151        ));
152
153        assert!(stored.has_type(&ValueType::Int));
154        assert!(!stored.has_type(&ValueType::String));
155    }
156}