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}