Skip to main content

geam_core/provider/
list.rs

1use crate::host::{
2    ExternalPayloadLease, ExternalPayloadView, HostExternalStore, HostList, HostType,
3};
4use crate::runtime::{
5    StoredRuntimeList, StoredRuntimeListCustomFields, StoredRuntimeListItem,
6    StoredRuntimeListTupleItems,
7};
8use std::marker::PhantomData;
9use std::ops::Deref;
10
11/// A retained, read-only view of one Gleam `List(Item)` value.
12///
13/// Providers cannot construct this type directly. A provider function receives
14/// it through the `#[geam::function]` adapter and can inspect only its length or
15/// one requested item at a time. Returning a received `List` preserves the
16/// original runtime list; return a `Vec<Item>` to construct a new Gleam list.
17pub struct List<Item, Context = MissingListContext> {
18    context: Context,
19    item: PhantomData<fn() -> Item>,
20}
21
22#[doc(hidden)]
23pub struct MissingListContext;
24
25/// The concrete retained-list owner inserted by provider macro expansion.
26#[doc(hidden)]
27pub struct ProviderListContext<'call, HostItem, Decoder> {
28    host: HostList<'call, HostItem>,
29    retained: StoredRuntimeList,
30    decoder: Decoder,
31}
32
33/// An input-only retained List nested inside another source value.
34#[doc(hidden)]
35pub struct ProviderInputListContext<Decoder> {
36    retained: StoredRuntimeList,
37    decoder: Decoder,
38}
39
40/// The statically generated decoder for one exact List item shape.
41#[doc(hidden)]
42pub trait ProviderListItemDecoder<Item> {
43    type View;
44
45    fn decode(&self, value: ProviderListItemValue<'_>) -> Self::View;
46}
47
48/// One requested runtime List item passed to a generated typed decoder.
49#[doc(hidden)]
50pub struct ProviderListItemValue<'value> {
51    value: StoredRuntimeListItem<'value>,
52}
53
54/// Typed access to one provider-owned external payload store.
55#[doc(hidden)]
56pub struct ProviderExternalPayloadAccess<Payload> {
57    store: HostExternalStore<Payload>,
58}
59
60/// An external List item that retains its store entry without cloning the payload.
61#[doc(hidden)]
62pub struct ProviderExternalItem<Payload> {
63    value: ExternalPayloadView<Payload>,
64    lease: ExternalPayloadLease,
65}
66
67/// Profile-independent decoder for one scalar List item.
68#[doc(hidden)]
69#[derive(Clone, Copy)]
70pub struct ProviderScalarListDecoder<Scalar>(PhantomData<fn() -> Scalar>);
71
72/// Profile-independent decoder for one external List item.
73#[doc(hidden)]
74pub struct ProviderExternalListDecoder<Payload> {
75    access: ProviderExternalPayloadAccess<Payload>,
76}
77
78impl<Payload: 'static> Clone for ProviderExternalListDecoder<Payload> {
79    fn clone(&self) -> Self {
80        Self {
81            access: self.access.clone(),
82        }
83    }
84}
85
86/// A consuming view over the elements of one runtime tuple List item.
87#[doc(hidden)]
88pub struct ProviderListTupleItems<'value> {
89    values: StoredRuntimeListTupleItems<'value>,
90}
91
92/// A consuming view over one runtime custom value stored in a List.
93#[doc(hidden)]
94pub struct ProviderListCustomFields<'value> {
95    fields: StoredRuntimeListCustomFields<'value>,
96}
97
98impl<'call, Item, HostItem, Decoder> List<Item, ProviderListContext<'call, HostItem, Decoder>>
99where
100    HostItem: HostType,
101    Decoder: ProviderListItemDecoder<Item>,
102{
103    /// Returns the List length without decoding an item.
104    #[expect(
105        clippy::len_without_is_empty,
106        reason = "the first provider List slice intentionally exposes only len and get"
107    )]
108    pub fn len(&self) -> usize {
109        self.context.retained.len()
110    }
111
112    /// Decodes only the item at `index` through the statically generated codec.
113    pub fn get(&self, index: usize) -> Option<Decoder::View> {
114        self.context.retained.decode_item(index, |value| {
115            self.context.decoder.decode(ProviderListItemValue { value })
116        })
117    }
118
119    #[doc(hidden)]
120    pub fn __geam_into_context(self) -> ProviderListContext<'call, HostItem, Decoder> {
121        self.context
122    }
123}
124
125impl<Item, Decoder> List<Item, ProviderInputListContext<Decoder>>
126where
127    Decoder: ProviderListItemDecoder<Item>,
128{
129    /// Returns the nested List length without decoding an item.
130    #[expect(
131        clippy::len_without_is_empty,
132        reason = "the provider List slice intentionally exposes only len and get"
133    )]
134    pub fn len(&self) -> usize {
135        self.context.retained.len()
136    }
137
138    /// Decodes only the nested List item at `index`.
139    pub fn get(&self, index: usize) -> Option<Decoder::View> {
140        self.context.retained.decode_item(index, |value| {
141            self.context.decoder.decode(ProviderListItemValue { value })
142        })
143    }
144}
145
146impl<'call, HostItem, Decoder> ProviderListContext<'call, HostItem, Decoder>
147where
148    HostItem: HostType,
149{
150    pub(crate) fn new(
151        host: HostList<'call, HostItem>,
152        retained: StoredRuntimeList,
153        decoder: Decoder,
154    ) -> Self {
155        Self {
156            host,
157            retained,
158            decoder,
159        }
160    }
161
162    pub(crate) fn into_list<Item>(self) -> List<Item, Self> {
163        List {
164            context: self,
165            item: PhantomData,
166        }
167    }
168
169    #[doc(hidden)]
170    pub fn into_host(self) -> HostList<'call, HostItem> {
171        self.host
172    }
173}
174
175impl<Decoder> ProviderInputListContext<Decoder> {
176    pub(crate) fn new<Item>(retained: StoredRuntimeList, decoder: Decoder) -> List<Item, Self> {
177        List {
178            context: Self { retained, decoder },
179            item: PhantomData,
180        }
181    }
182}
183
184impl<'value> ProviderListItemValue<'value> {
185    #[doc(hidden)]
186    #[allow(private_bounds)]
187    pub fn into_scalar<Scalar>(self) -> Scalar
188    where
189        Scalar: ProviderListScalar,
190    {
191        Scalar::decode(self.value)
192    }
193
194    #[doc(hidden)]
195    pub fn into_external<Payload>(
196        self,
197        access: &ProviderExternalPayloadAccess<Payload>,
198    ) -> ProviderExternalItem<Payload>
199    where
200        Payload: 'static,
201    {
202        let lease = self.value.into_external_lease();
203        let value = access.store.view(&lease);
204        ProviderExternalItem { value, lease }
205    }
206
207    #[doc(hidden)]
208    pub fn into_tuple(self) -> ProviderListTupleItems<'value> {
209        ProviderListTupleItems {
210            values: self.value.into_tuple_items(),
211        }
212    }
213
214    #[doc(hidden)]
215    pub fn into_custom(self) -> ProviderListCustomFields<'value> {
216        ProviderListCustomFields {
217            fields: self.value.into_custom_fields(),
218        }
219    }
220
221    #[doc(hidden)]
222    pub fn into_list<Item, Decoder>(
223        self,
224        decoder: Decoder,
225    ) -> List<Item, ProviderInputListContext<Decoder>>
226    where
227        Decoder: ProviderListItemDecoder<Item>,
228    {
229        ProviderInputListContext::new(self.value.into_list(), decoder)
230    }
231}
232
233impl ProviderListTupleItems<'_> {
234    #[doc(hidden)]
235    pub fn take_item(&mut self, index: usize) -> ProviderListItemValue<'_> {
236        ProviderListItemValue {
237            value: self.values.take_item(index),
238        }
239    }
240}
241
242impl ProviderListCustomFields<'_> {
243    #[doc(hidden)]
244    pub fn constructor(&self) -> usize {
245        self.fields.constructor()
246    }
247
248    #[doc(hidden)]
249    pub fn take_field(&mut self, index: usize) -> ProviderListItemValue<'_> {
250        ProviderListItemValue {
251            value: self.fields.take_field(index),
252        }
253    }
254}
255
256impl<Payload: 'static> ProviderExternalPayloadAccess<Payload> {
257    pub(crate) fn new(store: &HostExternalStore<Payload>) -> Self {
258        Self {
259            store: store.clone_handle(),
260        }
261    }
262}
263
264impl<Payload: 'static> Clone for ProviderExternalPayloadAccess<Payload> {
265    fn clone(&self) -> Self {
266        Self {
267            store: self.store.clone_handle(),
268        }
269    }
270}
271
272impl<Payload> Deref for ProviderExternalItem<Payload> {
273    type Target = Payload;
274
275    fn deref(&self) -> &Self::Target {
276        &self.value
277    }
278}
279
280impl<Payload> ProviderExternalItem<Payload> {
281    pub(crate) fn new(value: ExternalPayloadView<Payload>, lease: ExternalPayloadLease) -> Self {
282        Self { value, lease }
283    }
284
285    pub(crate) fn into_lease(self) -> ExternalPayloadLease {
286        self.lease
287    }
288}
289
290impl<Scalar> ProviderScalarListDecoder<Scalar> {
291    pub(crate) fn new() -> Self {
292        Self(PhantomData)
293    }
294}
295
296impl<Scalar> ProviderListItemDecoder<Scalar> for ProviderScalarListDecoder<Scalar>
297where
298    Scalar: ProviderListScalar,
299{
300    type View = Scalar;
301
302    fn decode(&self, value: ProviderListItemValue<'_>) -> Self::View {
303        value.into_scalar()
304    }
305}
306
307impl<Payload: 'static> ProviderExternalListDecoder<Payload> {
308    pub fn new(access: ProviderExternalPayloadAccess<Payload>) -> Self {
309        Self { access }
310    }
311}
312
313impl<Payload: 'static> ProviderListItemDecoder<Payload> for ProviderExternalListDecoder<Payload> {
314    type View = ProviderExternalItem<Payload>;
315
316    fn decode(&self, value: ProviderListItemValue<'_>) -> Self::View {
317        value.into_external(&self.access)
318    }
319}
320
321trait ProviderListScalar: Sized {
322    fn decode(value: StoredRuntimeListItem) -> Self;
323}
324
325macro_rules! provider_list_scalar {
326    ($type:ty, $method:ident) => {
327        impl ProviderListScalar for $type {
328            fn decode(value: StoredRuntimeListItem) -> Self {
329                value.$method()
330            }
331        }
332    };
333}
334
335provider_list_scalar!(num_bigint::BigInt, into_int);
336provider_list_scalar!(f64, into_float);
337provider_list_scalar!(ecow::EcoString, into_string);
338provider_list_scalar!(crate::BitArrayValue, into_bit_array);
339provider_list_scalar!(char, into_utf_codepoint);
340provider_list_scalar!(bool, into_bool);
341provider_list_scalar!((), into_nil);
342
343#[cfg(test)]
344mod tests {
345    use super::{ProviderListContext, ProviderListItemDecoder, ProviderListItemValue};
346    use crate::host::{HostList, HostListToken};
347    use crate::runtime::StoredRuntimeList;
348    use num_bigint::BigInt;
349
350    struct IntDecoder;
351
352    impl ProviderListItemDecoder<BigInt> for IntDecoder {
353        type View = BigInt;
354
355        fn decode(&self, value: ProviderListItemValue<'_>) -> Self::View {
356            value.into_scalar()
357        }
358    }
359
360    #[test]
361    fn retained_list_length_and_indexing_decode_only_requested_items() {
362        let retained = StoredRuntimeList::test_ints(vec![1.into(), 2.into()]);
363        let host = HostList::<BigInt>::new(HostListToken::Stored(0));
364        let list = ProviderListContext::new(host, retained, IntDecoder).into_list::<BigInt>();
365
366        assert_eq!(list.context.retained.item_reads(), 0);
367        assert_eq!(list.len(), 2);
368        assert_eq!(list.context.retained.item_reads(), 0);
369        assert_eq!(list.get(1), Some(2.into()));
370        assert_eq!(list.context.retained.item_reads(), 1);
371        assert_eq!(list.get(2), None);
372        assert_eq!(list.context.retained.item_reads(), 2);
373    }
374}