Skip to main content

geam_core/provider/
advanced.rs

1use crate::host::{
2    HostCall, HostExternalEquality, HostExternalHashing, HostExternalInspection, HostListType,
3    HostProfile, HostProvider, HostStoredDynamic, HostStoredType, HostStoredValue, HostType,
4    HostTypeIndex0, HostTypeIndexNext,
5};
6use crate::provider::{
7    List, ProviderConstructions, ProviderExternalDeclaration, ProviderInputValue,
8    ProviderListContext, ProviderListInputCodec, ProviderListInputValue, ProviderNoConstructions,
9    ProviderOutputValue, ProviderStoredOwner, ProviderValue, ProviderValueContext, Value,
10};
11use ecow::EcoString;
12use std::marker::PhantomData;
13
14/// Source-equality access for retained values in one immutable payload.
15pub type Equality<'value> = HostExternalEquality<'value>;
16
17/// Source-hash access for retained values in one immutable payload.
18pub type Hashing<'value> = HostExternalHashing<'value>;
19
20/// Source-inspection access for retained values in one immutable payload.
21pub type Inspection<'value> = HostExternalInspection<'value>;
22
23/// One retained source value owned by an advanced external payload.
24///
25/// The payload type is the owner brand. The argument index identifies the
26/// corresponding source type parameter. Values are created only by
27/// [`super::Call::store`] followed by the generated external boundary.
28pub struct Retained<Owner, Index> {
29    value: HostStoredValue<HostStoredType<Index>>,
30    owner: PhantomData<fn() -> Owner>,
31}
32
33/// One existential source value owned by an advanced external payload.
34///
35/// The exact specialized source type stays sealed inside Geam. Providers can
36/// inspect its broad family, confirm a generated external declaration, or
37/// request an exact typed restore through an active [`crate::provider::Call`].
38pub struct StoredDynamic<Owner> {
39    value: HostStoredDynamic,
40    owner: PhantomData<fn() -> Owner>,
41}
42
43/// An existing external source value with its original runtime identity.
44///
45/// This advanced input is useful when a provider must pass an external value
46/// to a callback or return it unchanged. Dereferencing it borrows the ordinary
47/// Rust payload; consuming it preserves the original source handle.
48pub type External<Payload> = crate::provider::ProviderExternalItem<Payload>;
49
50/// Static pass-through into existential retention without materialization.
51#[doc(hidden)]
52pub trait ProviderDynamicValue<'call, Profile, Provider, Return>
53where
54    Profile: HostProfile,
55    Provider: HostProvider<Profile>,
56    Return: HostType,
57{
58    type Host: HostType;
59
60    fn into_host(
61        self,
62        call: &mut HostCall<'call, Profile, Provider, Return>,
63    ) -> <Self::Host as HostType>::Value<'call>;
64}
65
66impl<'call, Profile, Provider, Return, Type, Host>
67    ProviderDynamicValue<'call, Profile, Provider, Return>
68    for Value<Type, ProviderValueContext<'call, Host>>
69where
70    Profile: HostProfile,
71    Provider: HostProvider<Profile>,
72    Return: HostType,
73    Host: HostType,
74{
75    type Host = Host;
76
77    fn into_host(
78        self,
79        _call: &mut HostCall<'call, Profile, Provider, Return>,
80    ) -> <Self::Host as HostType>::Value<'call> {
81        self.into_host()
82    }
83}
84
85impl<'call, Profile, Provider, Return, Type> ProviderDynamicValue<'call, Profile, Provider, Return>
86    for Type
87where
88    Profile: HostProfile,
89    Provider: HostProvider<Profile>,
90    Return: HostType,
91    Type: ProviderValue<OutputRequirements = ProviderNoConstructions>
92        + ProviderOutputValue<Profile, Provider, Return>,
93{
94    type Host = Type::Host;
95
96    fn into_host(
97        self,
98        call: &mut HostCall<'call, Profile, Provider, Return>,
99    ) -> <Self::Host as HostType>::Value<'call> {
100        self.into_host(call, &ProviderConstructions::none())
101    }
102}
103
104impl<'call, Profile, Provider, Return, Item, HostItem, Decoder>
105    ProviderDynamicValue<'call, Profile, Provider, Return>
106    for List<Item, ProviderListContext<'call, HostItem, Decoder>>
107where
108    Profile: HostProfile,
109    Provider: HostProvider<Profile>,
110    Return: HostType,
111    HostItem: HostType,
112    Decoder: crate::provider::ProviderListItemDecoder<Item>,
113{
114    type Host = HostListType<HostItem>;
115
116    fn into_host(
117        self,
118        _call: &mut HostCall<'call, Profile, Provider, Return>,
119    ) -> <Self::Host as HostType>::Value<'call> {
120        self.__geam_into_context().into_host()
121    }
122}
123
124/// Broad runtime family of one existentially retained source value.
125#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
126pub enum DynamicKind {
127    Int,
128    Float,
129    String,
130    BitArray,
131    UtfCodepoint,
132    Bool,
133    Nil,
134    List,
135    Tuple,
136    Custom,
137    External,
138    Function,
139}
140
141impl DynamicKind {
142    fn from_family(family: crate::provider_support::HostStoredValueFamily) -> Self {
143        match family {
144            crate::provider_support::HostStoredValueFamily::Int => Self::Int,
145            crate::provider_support::HostStoredValueFamily::Float => Self::Float,
146            crate::provider_support::HostStoredValueFamily::String => Self::String,
147            crate::provider_support::HostStoredValueFamily::BitArray => Self::BitArray,
148            crate::provider_support::HostStoredValueFamily::UtfCodepoint => Self::UtfCodepoint,
149            crate::provider_support::HostStoredValueFamily::Bool => Self::Bool,
150            crate::provider_support::HostStoredValueFamily::Nil => Self::Nil,
151            crate::provider_support::HostStoredValueFamily::List => Self::List,
152            crate::provider_support::HostStoredValueFamily::Tuple => Self::Tuple,
153            crate::provider_support::HostStoredValueFamily::Custom => Self::Custom,
154            crate::provider_support::HostStoredValueFamily::External => Self::External,
155            crate::provider_support::HostStoredValueFamily::Function => Self::Function,
156        }
157    }
158}
159
160/// Static input conversion used by exact existential restores.
161#[doc(hidden)]
162pub trait ProviderDynamicInput<Profile, Provider, Return>
163where
164    Profile: HostProfile,
165    Provider: HostProvider<Profile>,
166    Return: HostType,
167{
168    type Host: HostType;
169    type View<'call>;
170
171    fn from_host<'call>(
172        call: &mut HostCall<'call, Profile, Provider, Return>,
173        value: <Self::Host as HostType>::Value<'call>,
174    ) -> Self::View<'call>;
175}
176
177impl<Profile, Provider, Return, Type> ProviderDynamicInput<Profile, Provider, Return> for Type
178where
179    Profile: HostProfile,
180    Provider: HostProvider<Profile>,
181    Return: HostType,
182    Type: ProviderValue,
183    Type::Input: ProviderInputValue<Profile, Provider, Return> + ProviderValue<Host = Type::Host>,
184{
185    type Host = Type::Host;
186    type View<'call> = Type::Input;
187
188    fn from_host<'call>(
189        call: &mut HostCall<'call, Profile, Provider, Return>,
190        value: <Self::Host as HostType>::Value<'call>,
191    ) -> Self::View<'call> {
192        Type::Input::from_host(call, value)
193    }
194}
195
196impl<Profile, Provider, Return, Item> ProviderDynamicInput<Profile, Provider, Return> for List<Item>
197where
198    Profile: HostProfile,
199    Provider: HostProvider<Profile>,
200    Return: HostType,
201    Item: ProviderValue<ListInput = Item>,
202    Item::ListInput: ProviderListInputCodec<Profile>,
203{
204    type Host = HostListType<Item::Host>;
205    type View<'call> = List<
206        Item,
207        ProviderListContext<
208            'call,
209            Item::Host,
210            <Item::ListInput as ProviderListInputValue>::Decoder,
211        >,
212    >;
213
214    fn from_host<'call>(
215        call: &mut HostCall<'call, Profile, Provider, Return>,
216        value: <Self::Host as HostType>::Value<'call>,
217    ) -> Self::View<'call> {
218        let decoder = <Item::ListInput as ProviderListInputCodec<Profile>>::decoder(call);
219        call.provider_list(value, decoder)
220    }
221}
222
223/// The first source type-argument position of an advanced external payload.
224pub type Index0 = HostTypeIndex0;
225
226/// The source type-argument position after `Index`.
227pub type Next<Index> = HostTypeIndexNext<Index>;
228
229/// Context-aware source semantics for an advanced retained payload declared
230/// with `#[geam::external(name = "...", retained)]`.
231pub trait RetainedExternalPayload: 'static {
232    fn source_equal(&self, context: &Equality<'_>, other: &Self) -> bool;
233
234    fn source_hash(&self, context: &Hashing<'_>) -> u64;
235
236    fn inspect(&self, context: &Inspection<'_>) -> EcoString;
237}
238
239impl<Owner, Index> Retained<Owner, Index>
240where
241    Owner: ProviderStoredOwner,
242{
243    pub(crate) fn new(value: HostStoredValue<HostStoredType<Index>>) -> Self {
244        Self {
245            value,
246            owner: PhantomData,
247        }
248    }
249
250    pub(crate) fn host(&self) -> &HostStoredValue<HostStoredType<Index>> {
251        &self.value
252    }
253
254    /// Compares two retained values with Gleam source equality.
255    pub fn source_equal(&self, context: &Equality<'_>, other: &Self) -> bool {
256        context.stored_values_equal(&self.value, &other.value)
257    }
258
259    /// Hashes this retained value consistently with Gleam source equality.
260    pub fn source_hash(&self, context: &Hashing<'_>) -> u64 {
261        context.stored_value_hash(&self.value)
262    }
263
264    /// Inspects this retained value with Gleam source formatting.
265    pub fn inspect(&self, context: &Inspection<'_>) -> EcoString {
266        context.inspect_stored_value(&self.value)
267    }
268}
269
270impl<Owner> StoredDynamic<Owner>
271where
272    Owner: ProviderStoredOwner,
273{
274    pub(crate) fn new(value: HostStoredDynamic) -> Self {
275        Self {
276            value,
277            owner: PhantomData,
278        }
279    }
280
281    pub(crate) fn host(&self) -> &HostStoredDynamic {
282        &self.value
283    }
284
285    /// Returns the broad source family without exposing its runtime type.
286    pub fn kind(&self) -> DynamicKind {
287        DynamicKind::from_family(self.value.value_family())
288    }
289
290    /// Confirms one generated external declaration without exposing names.
291    pub fn is_external<Declaration>(&self) -> bool
292    where
293        Declaration: ProviderExternalDeclaration,
294    {
295        self.value.has_external_schema::<Declaration::Schema>()
296    }
297
298    /// Consumes a retained tuple and retains each element under the same owner.
299    ///
300    /// Non-tuples are returned unchanged.
301    #[expect(
302        clippy::result_large_err,
303        reason = "non-tuples retain the original value without another heap allocation"
304    )]
305    pub fn into_tuple_items(self) -> Result<Box<[Self]>, Self> {
306        self.value.map_tuple_items(Self::new).map_err(Self::new)
307    }
308
309    /// Compares two existential values with Gleam source equality.
310    pub fn source_equal(&self, context: &Equality<'_>, other: &Self) -> bool {
311        context.dynamic_values_equal(&self.value, &other.value)
312    }
313
314    /// Hashes this existential value consistently with Gleam source equality.
315    pub fn source_hash(&self, context: &Hashing<'_>) -> u64 {
316        context.dynamic_value_hash(&self.value)
317    }
318
319    /// Inspects this existential value with Gleam source formatting.
320    pub fn inspect(&self, context: &Inspection<'_>) -> EcoString {
321        context.inspect_dynamic_value(&self.value)
322    }
323}
324
325#[cfg(test)]
326mod tests {
327    use super::{DynamicKind, Index0, Retained};
328    use crate::host::{
329        HostExternalEquality, HostExternalHashing, HostExternalInspection, HostStoredType,
330        HostStoredValue,
331    };
332    use crate::runtime::StoredRuntimeValue;
333
334    struct Payload;
335
336    impl crate::provider::ProviderStoredOwner for Payload {}
337
338    fn retained(value: i64) -> Retained<Payload, Index0> {
339        Retained::new(HostStoredValue::<HostStoredType<Index0>>::new(
340            StoredRuntimeValue::test_int(value.into()),
341        ))
342    }
343
344    #[test]
345    fn dynamic_kind_covers_every_retained_runtime_family() {
346        use crate::provider_support::HostStoredValueFamily;
347
348        assert_eq!(
349            DynamicKind::from_family(HostStoredValueFamily::Int),
350            DynamicKind::Int
351        );
352        assert_eq!(
353            DynamicKind::from_family(HostStoredValueFamily::Float),
354            DynamicKind::Float
355        );
356        assert_eq!(
357            DynamicKind::from_family(HostStoredValueFamily::String),
358            DynamicKind::String
359        );
360        assert_eq!(
361            DynamicKind::from_family(HostStoredValueFamily::BitArray),
362            DynamicKind::BitArray,
363        );
364        assert_eq!(
365            DynamicKind::from_family(HostStoredValueFamily::UtfCodepoint),
366            DynamicKind::UtfCodepoint,
367        );
368        assert_eq!(
369            DynamicKind::from_family(HostStoredValueFamily::Bool),
370            DynamicKind::Bool
371        );
372        assert_eq!(
373            DynamicKind::from_family(HostStoredValueFamily::Nil),
374            DynamicKind::Nil
375        );
376        assert_eq!(
377            DynamicKind::from_family(HostStoredValueFamily::List),
378            DynamicKind::List
379        );
380        assert_eq!(
381            DynamicKind::from_family(HostStoredValueFamily::Tuple),
382            DynamicKind::Tuple
383        );
384        assert_eq!(
385            DynamicKind::from_family(HostStoredValueFamily::Custom),
386            DynamicKind::Custom
387        );
388        assert_eq!(
389            DynamicKind::from_family(HostStoredValueFamily::External),
390            DynamicKind::External,
391        );
392        assert_eq!(
393            DynamicKind::from_family(HostStoredValueFamily::Function),
394            DynamicKind::Function,
395        );
396    }
397
398    #[test]
399    fn retained_values_delegate_each_source_operation_to_its_narrow_context() {
400        let first = retained(7);
401        let different = retained(8);
402        let stored_equal =
403            |left: &StoredRuntimeValue, right: &StoredRuntimeValue| std::ptr::eq(left, right);
404        let stored_hash = |_: &StoredRuntimeValue| 17;
405        let stored_inspect = |_: &StoredRuntimeValue| "Int(7)".into();
406        let equality = HostExternalEquality::new(&stored_equal);
407        let hashing = HostExternalHashing::new(&stored_hash);
408        let inspection = HostExternalInspection::new(&stored_inspect);
409
410        assert!(first.source_equal(&equality, &first));
411        assert!(!first.source_equal(&equality, &different));
412        assert_eq!(first.source_hash(&hashing), 17);
413        assert_eq!(first.inspect(&inspection), "Int(7)");
414    }
415}