Skip to main content

facet_reflect/partial/partial_api/
lists.rs

1use super::*;
2use crate::AllocatedShape;
3
4////////////////////////////////////////////////////////////////////////////////////////////////////
5// Lists
6////////////////////////////////////////////////////////////////////////////////////////////////////
7impl<const BORROW: bool> Partial<'_, BORROW> {
8    /// Initializes a list (Vec, etc.) if it hasn't been initialized before.
9    /// This is a prerequisite to `begin_push_item`/`set`/`end` or the shorthand
10    /// `push`.
11    ///
12    /// `init_list` does not clear the list if it was previously initialized.
13    /// `init_list` does not push a new frame to the stack, and thus does not
14    /// require `end` to be called afterwards.
15    pub fn init_list(self) -> Result<Self, ReflectError> {
16        self.init_list_with_capacity(0)
17    }
18
19    /// Initializes a list with a capacity hint for pre-allocation.
20    ///
21    /// Like `init_list`, but reserves space for `capacity` elements upfront.
22    /// This reduces allocations when the number of elements is known or estimated.
23    pub fn init_list_with_capacity(mut self, capacity: usize) -> Result<Self, ReflectError> {
24        crate::trace!("init_list_with_capacity({capacity})");
25
26        // Get shape upfront to avoid borrow conflicts
27        let shape = self.frames().last().unwrap().allocated.shape();
28        let frame = self.frames_mut().last_mut().unwrap();
29
30        match &frame.tracker {
31            Tracker::Scalar if !frame.is_init => {
32                // that's good, let's initialize it
33            }
34            Tracker::Scalar => {
35                // is_init is true - initialized (perhaps from a previous round?) but should be a list tracker
36                // Check what kind of shape we have
37                match &shape.def {
38                    Def::List(_) => {
39                        // Regular list type - just update the tracker
40                        frame.tracker = Tracker::List {
41                            current_child: None,
42                            rope: None,
43                        };
44                        return Ok(self);
45                    }
46                    Def::DynamicValue(_) => {
47                        // DynamicValue that was already initialized as an array
48                        // Just update the tracker without deinit (preserve existing elements)
49                        frame.tracker = Tracker::DynamicValue {
50                            state: DynamicValueState::Array {
51                                building_element: false,
52                                pending_elements: Vec::new(),
53                            },
54                        };
55                        return Ok(self);
56                    }
57                    _ => {
58                        return Err(self.err(ReflectErrorKind::OperationFailed {
59                            shape,
60                            operation: "init_list can only be called on List types or DynamicValue",
61                        }));
62                    }
63                }
64            }
65            Tracker::List { .. } => {
66                if frame.is_init {
67                    // already initialized, nothing to do
68                    return Ok(self);
69                }
70            }
71            Tracker::DynamicValue { state } => {
72                // Already initialized as a dynamic array
73                if matches!(state, DynamicValueState::Array { .. }) {
74                    return Ok(self);
75                }
76                // Otherwise (Scalar or other state), we need to deinit before reinitializing.
77                // Must use deinit_for_replace() since we're about to overwrite with a new Array.
78                // This is important for BorrowedInPlace frames where deinit() would early-return
79                // without dropping the existing value.
80                frame.deinit_for_replace();
81            }
82            Tracker::SmartPointerSlice { .. } => {
83                // init_list is kinda superfluous when we're in a SmartPointerSlice state
84                return Ok(self);
85            }
86            _ => {
87                let tracker_kind = frame.tracker.kind();
88                return Err(self.err(ReflectErrorKind::UnexpectedTracker {
89                    message: "init_list called but tracker isn't something list-like",
90                    current_tracker: tracker_kind,
91                }));
92            }
93        };
94
95        // Check that we have a List or DynamicValue
96        match &shape.def {
97            Def::List(list_def) => {
98                // Check that we have init_in_place_with_capacity function
99                let init_fn = match list_def.init_in_place_with_capacity() {
100                    Some(f) => f,
101                    None => {
102                        return Err(self.err(ReflectErrorKind::OperationFailed {
103                            shape,
104                            operation: "list type does not support initialization with capacity",
105                        }));
106                    }
107                };
108
109                // Initialize the list with the given capacity
110                // Need to re-borrow frame after the early returns above
111                let frame = self.frames_mut().last_mut().unwrap();
112                unsafe {
113                    init_fn(frame.data, capacity);
114                }
115
116                // Update tracker to List state and mark as initialized
117                frame.tracker = Tracker::List {
118                    current_child: None,
119                    rope: None,
120                };
121                frame.is_init = true;
122            }
123            Def::DynamicValue(dyn_def) => {
124                // Initialize as a dynamic array
125                // Need to re-borrow frame after the early returns above
126                let frame = self.frames_mut().last_mut().unwrap();
127                unsafe {
128                    (dyn_def.vtable.begin_array)(frame.data);
129                }
130
131                // Update tracker to DynamicValue array state and mark as initialized
132                frame.tracker = Tracker::DynamicValue {
133                    state: DynamicValueState::Array {
134                        building_element: false,
135                        pending_elements: Vec::new(),
136                    },
137                };
138                frame.is_init = true;
139            }
140            _ => {
141                return Err(self.err(ReflectErrorKind::OperationFailed {
142                    shape,
143                    operation: "init_list can only be called on List or DynamicValue types",
144                }));
145            }
146        }
147
148        Ok(self)
149    }
150
151    /// Transitions the frame to Array tracker state.
152    ///
153    /// This is used to prepare a fixed-size array for element initialization.
154    /// Unlike `init_list`, this does not initialize any runtime data - arrays
155    /// are stored inline and don't need a vtable call.
156    ///
157    /// This method is particularly important for zero-length arrays like `[u8; 0]`,
158    /// which have no elements to initialize but still need their tracker state
159    /// to be set correctly for `require_full_initialization` to pass.
160    ///
161    /// `init_array` does not push a new frame to the stack.
162    pub fn init_array(mut self) -> Result<Self, ReflectError> {
163        crate::trace!("init_array()");
164
165        // Get shape upfront to avoid borrow conflicts
166        let shape = self.frames().last().unwrap().allocated.shape();
167
168        // Verify this is an array type
169        let array_def = match &shape.def {
170            Def::Array(array_def) => array_def,
171            _ => {
172                return Err(self.err(ReflectErrorKind::OperationFailed {
173                    shape,
174                    operation: "init_array can only be called on Array types",
175                }));
176            }
177        };
178
179        // Check array size limit
180        if array_def.n > 63 {
181            return Err(self.err(ReflectErrorKind::OperationFailed {
182                shape,
183                operation: "arrays larger than 63 elements are not yet supported",
184            }));
185        }
186
187        let frame = self.frames_mut().last_mut().unwrap();
188        match &frame.tracker {
189            Tracker::Scalar if !frame.is_init => {
190                // Transition to Array tracker
191                frame.tracker = Tracker::Array {
192                    iset: ISet::default(),
193                    current_child: None,
194                };
195            }
196            Tracker::Array { .. } => {
197                // Already in Array state, nothing to do
198            }
199            _ => {
200                return Err(self.err(ReflectErrorKind::OperationFailed {
201                    shape,
202                    operation: "init_array: unexpected tracker state",
203                }));
204            }
205        }
206
207        Ok(self)
208    }
209
210    /// Pushes an element to the list
211    /// The element should be set using `set()` or similar methods, then `pop()` to complete
212    pub fn begin_list_item(mut self) -> Result<Self, ReflectError> {
213        crate::trace!("begin_list_item()");
214
215        // Get immutable data upfront - shape and type_plan are Copy
216        let frame = self.frames().last().unwrap();
217        let shape = frame.allocated.shape();
218        let parent_type_plan = frame.type_plan;
219
220        // Check tracker state immutably first to determine which path to take
221        let tracker_kind = frame.tracker.kind();
222
223        // Check if we're building a smart pointer slice
224        if tracker_kind == crate::error::TrackerKind::SmartPointerSlice {
225            let frame = self.mode.stack_mut().last_mut().unwrap();
226            if let Tracker::SmartPointerSlice {
227                building_item: true,
228                ..
229            } = &frame.tracker
230            {
231                return Err(self.err(ReflectErrorKind::OperationFailed {
232                    shape,
233                    operation: "already building an item, call end() first",
234                }));
235            }
236
237            // Get the element type from the smart pointer's pointee
238            let element_shape = match &shape.def {
239                Def::Pointer(smart_ptr_def) => match smart_ptr_def.pointee() {
240                    Some(pointee_shape) => match &pointee_shape.ty {
241                        Type::Sequence(SequenceType::Slice(slice_type)) => slice_type.t,
242                        _ => {
243                            return Err(self.err(ReflectErrorKind::OperationFailed {
244                                shape,
245                                operation: "smart pointer pointee is not a slice",
246                            }));
247                        }
248                    },
249                    None => {
250                        return Err(self.err(ReflectErrorKind::OperationFailed {
251                            shape,
252                            operation: "smart pointer has no pointee",
253                        }));
254                    }
255                },
256                _ => {
257                    return Err(self.err(ReflectErrorKind::OperationFailed {
258                        shape,
259                        operation: "expected smart pointer definition",
260                    }));
261                }
262            };
263
264            // Allocate space for the element
265            crate::trace!("Pointee is a slice of {element_shape}");
266            let element_layout = match element_shape.layout.sized_layout() {
267                Ok(layout) => layout,
268                Err(_) => {
269                    return Err(self.err(ReflectErrorKind::OperationFailed {
270                        shape: element_shape,
271                        operation: "cannot allocate unsized element",
272                    }));
273                }
274            };
275
276            let element_data = if element_layout.size() == 0 {
277                // For ZST, use a non-null but unallocated pointer
278                PtrUninit::new(NonNull::<u8>::dangling().as_ptr())
279            } else {
280                let element_ptr: *mut u8 = unsafe { ::alloc::alloc::alloc(element_layout) };
281                let Some(element_ptr) = NonNull::new(element_ptr) else {
282                    return Err(self.err(ReflectErrorKind::OperationFailed {
283                        shape,
284                        operation: "failed to allocate memory for list element",
285                    }));
286                };
287                PtrUninit::new(element_ptr.as_ptr())
288            };
289
290            // Get child type plan NodeId for slice items
291            // Navigate: Pointer -> Slice -> element
292            let slice_node_id = self
293                .root_plan
294                .pointer_inner_node_id(parent_type_plan)
295                .expect("TypePlan must have slice node for smart pointer");
296            let child_plan_id = self
297                .root_plan
298                .list_item_node_id(slice_node_id)
299                .expect("TypePlan must have item node for slice");
300
301            // Create and push the element frame
302            crate::trace!("Pushing element frame, which we just allocated");
303            let element_frame = Frame::new(
304                element_data,
305                AllocatedShape::new(element_shape, element_layout.size()),
306                FrameOwnership::Owned,
307                child_plan_id,
308            );
309            self.mode.stack_mut().push(element_frame);
310
311            // Mark that we're building an item and increment the element index
312            let parent_idx = self.mode.stack().len() - 2;
313            if let Tracker::SmartPointerSlice {
314                building_item,
315                current_child,
316                ..
317            } = &mut self.mode.stack_mut()[parent_idx].tracker
318            {
319                let next_idx = current_child.map(|i| i + 1).unwrap_or(0);
320                *current_child = Some(next_idx);
321                crate::trace!("Marking element frame as building item, index={}", next_idx);
322                *building_item = true;
323            }
324
325            return Ok(self);
326        }
327
328        // Check if we're building a DynamicValue array
329        if tracker_kind == crate::error::TrackerKind::DynamicValue {
330            let frame = self.mode.stack().last().unwrap();
331            if let Tracker::DynamicValue {
332                state:
333                    DynamicValueState::Array {
334                        building_element, ..
335                    },
336            } = &frame.tracker
337            {
338                if *building_element {
339                    return Err(self.err(ReflectErrorKind::OperationFailed {
340                        shape,
341                        operation: "already building an element, call end() first",
342                    }));
343                }
344
345                // For DynamicValue arrays, the element shape is the same DynamicValue shape
346                // (Value arrays contain Value elements)
347                let element_shape = shape;
348                let element_layout = match element_shape.layout.sized_layout() {
349                    Ok(layout) => layout,
350                    Err(_) => {
351                        return Err(self.err(ReflectErrorKind::Unsized {
352                            shape: element_shape,
353                            operation: "begin_list_item: calculating element layout",
354                        }));
355                    }
356                };
357
358                let element_data = if element_layout.size() == 0 {
359                    // For ZST, use a non-null but unallocated pointer
360                    PtrUninit::new(NonNull::<u8>::dangling().as_ptr())
361                } else {
362                    let element_ptr: *mut u8 = unsafe { ::alloc::alloc::alloc(element_layout) };
363                    let Some(element_ptr) = NonNull::new(element_ptr) else {
364                        return Err(self.err(ReflectErrorKind::OperationFailed {
365                            shape,
366                            operation: "failed to allocate memory for list element",
367                        }));
368                    };
369                    PtrUninit::new(element_ptr.as_ptr())
370                };
371
372                // For DynamicValue arrays, use the same type plan (self-recursive)
373                let child_plan = parent_type_plan;
374                self.mode.stack_mut().push(Frame::new(
375                    element_data,
376                    AllocatedShape::new(element_shape, element_layout.size()),
377                    FrameOwnership::Owned,
378                    child_plan,
379                ));
380
381                // Mark that we're building an element
382                let parent_idx = self.mode.stack().len() - 2;
383                if let Tracker::DynamicValue {
384                    state:
385                        DynamicValueState::Array {
386                            building_element, ..
387                        },
388                } = &mut self.mode.stack_mut()[parent_idx].tracker
389                {
390                    *building_element = true;
391                }
392
393                return Ok(self);
394            }
395        }
396
397        // Check that we have a List that's been initialized
398        let list_def = match &shape.def {
399            Def::List(list_def) => list_def,
400            _ => {
401                return Err(self.err(ReflectErrorKind::OperationFailed {
402                    shape,
403                    operation: "push can only be called on List or DynamicValue types",
404                }));
405            }
406        };
407
408        // Verify the tracker is in List state and initialized
409        {
410            let frame = self.mode.stack_mut().last_mut().unwrap();
411            match &mut frame.tracker {
412                Tracker::List {
413                    current_child,
414                    rope,
415                    ..
416                } if frame.is_init => {
417                    if current_child.is_some() {
418                        return Err(self.err(ReflectErrorKind::OperationFailed {
419                            shape,
420                            operation: "already pushing an element, call pop() first",
421                        }));
422                    }
423                    // Get the current length to use as the index for path tracking.
424                    // With rope-based storage, elements are stored in the rope until the list
425                    // is finalized, so we use the rope's length if available.
426                    let current_len = if let Some(rope) = rope {
427                        rope.len()
428                    } else {
429                        unsafe { (list_def.vtable.len)(frame.data.assume_init().as_const()) }
430                    };
431                    *current_child = Some(current_len);
432                }
433                _ => {
434                    return Err(self.err(ReflectErrorKind::OperationFailed {
435                        shape,
436                        operation: "must call init_list() before push()",
437                    }));
438                }
439            }
440        }
441
442        // Get the element shape
443        let element_shape = list_def.t();
444
445        // Calculate element layout
446        let element_layout = match element_shape.layout.sized_layout() {
447            Ok(layout) => layout,
448            Err(_) => {
449                return Err(self.err(ReflectErrorKind::Unsized {
450                    shape: element_shape,
451                    operation: "begin_list_item: calculating element layout",
452                }));
453            }
454        };
455
456        // Allocate element in a stable rope chunk.
457        // Rope chunks never move, so frames can be stored for deferred processing.
458        let (element_data, ownership) = {
459            let frame = self.mode.stack_mut().last_mut().unwrap();
460            if element_layout.size() == 0 {
461                // ZST: use dangling pointer, no allocation needed
462                (
463                    PtrUninit::new(NonNull::<u8>::dangling().as_ptr()),
464                    FrameOwnership::Owned,
465                )
466            } else {
467                // Initialize rope lazily on first element
468                let Tracker::List { rope, .. } = &mut frame.tracker else {
469                    unreachable!("already verified List tracker above");
470                };
471                if rope.is_none() {
472                    *rope = Some(ListRope::new(element_layout));
473                }
474                let rope = rope.as_mut().unwrap();
475
476                // Allocate from rope - this returns a stable pointer that won't move
477                let element_ptr = rope.push_uninit();
478                (
479                    PtrUninit::new(element_ptr.as_ptr()),
480                    FrameOwnership::RopeSlot,
481                )
482            }
483        };
484
485        // Get child type plan NodeId for list items - root_plan is free to access now
486        let child_plan_id = self
487            .root_plan
488            .list_item_node_id(parent_type_plan)
489            .expect("TypePlan must have list item node");
490
491        // Push a new frame for the element
492        self.mode.stack_mut().push(Frame::new(
493            element_data,
494            AllocatedShape::new(element_shape, element_layout.size()),
495            ownership,
496            child_plan_id,
497        ));
498
499        Ok(self)
500    }
501}