Skip to main content

fory_core/serializer/
collection.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use super::codec::{
19    field_ref_mode, field_type_with_ref_flags, generic_field_type, same_numeric_family, Codec,
20    CodecReadType,
21};
22use super::primitive_list;
23use crate::context::ReadContext;
24use crate::context::WriteContext;
25use crate::error::Error;
26use crate::meta::FieldType;
27use crate::resolver::{RefFlag, RefMode};
28use crate::serializer::{core::read_value_type_info, Serializer};
29use crate::type_id::{self, need_to_write_type_for_field, PRIMITIVE_ARRAY_TYPES};
30
31pub const TRACKING_REF: u8 = 0b1;
32
33pub const HAS_NULL: u8 = 0b10;
34
35// Whether collection elements type is declare type.
36pub const DECL_ELEMENT_TYPE: u8 = 0b100;
37
38//  Whether collection elements type same.
39pub const IS_SAME_TYPE: u8 = 0b1000;
40
41const UNBACKED_CHECK_INTERVAL: usize = 1024;
42
43#[inline(always)]
44fn count_needs_bytes<T, const COUNT_ALLOCATES: bool, const ZST_NO_BACKING: bool>() -> bool {
45    COUNT_ALLOCATES && (!ZST_NO_BACKING || std::mem::size_of::<T>() != 0)
46}
47
48#[inline(always)]
49fn check_collection_len<T, const COUNT_ALLOCATES: bool, const ZST_NO_BACKING: bool>(
50    context: &ReadContext,
51    len: u32,
52    element_read_always_advances: bool,
53) -> Result<(), Error> {
54    let len = len as usize;
55    // Fixed arrays do not allocate from the wire count. Variable Vec-like ZST
56    // owners also have no count-derived backing, while node and bucket owners do.
57    if count_needs_bytes::<T, COUNT_ALLOCATES, ZST_NO_BACKING>() {
58        let required = if element_read_always_advances {
59            len
60        } else {
61            len.saturating_sub(context.remaining_unbacked_container_items())
62        };
63        context.reader.check_bound(required)?;
64    }
65    Ok(())
66}
67
68#[inline(always)]
69pub(super) fn field_read_data_always_advances(field_type: &FieldType) -> bool {
70    matches!(
71        field_type.type_id,
72        type_id::BOOL..=type_id::NAMED_ENUM
73            | type_id::UNION..=type_id::NAMED_UNION
74            | type_id::DURATION..=type_id::FLOAT64_ARRAY
75            | type_id::U128..=type_id::ISIZE_ARRAY
76    )
77}
78
79#[inline(always)]
80pub(super) fn settle_unbacked_items(
81    context: &mut ReadContext,
82    completed: usize,
83    start: usize,
84) -> Result<(), Error> {
85    let consumed = context.reader.get_cursor().saturating_sub(start);
86    context.reserve_unbacked_container_items(completed.saturating_sub(consumed))
87}
88
89#[inline(always)]
90fn value_read_data_always_advances<S: Serializer>(
91    read_type: Option<&std::rc::Rc<crate::TypeInfo>>,
92) -> bool {
93    S::READ_DATA_ALWAYS_ADVANCES
94        && read_type.map_or(true, |type_info| type_info.has_exact_local_schema())
95}
96
97#[inline(always)]
98fn codec_read_data_always_advances<T: 'static, C: Codec<T>>(
99    read_type: Option<&CodecReadType>,
100) -> bool {
101    C::READ_DATA_ALWAYS_ADVANCES
102        && read_type.map_or(true, |read_type| match read_type {
103            CodecReadType::Field(field_type) => field_read_data_always_advances(field_type),
104            CodecReadType::TypeInfo(type_info) => type_info.has_exact_local_schema(),
105        })
106}
107
108#[cold]
109#[inline(never)]
110fn graph_memory_overflow() -> Error {
111    Error::invalid_data("graph memory estimate overflows")
112}
113
114#[cold]
115#[inline(never)]
116fn missing_collection_type() -> Error {
117    Error::type_error("Unable to determine concrete type for polymorphic collection elements")
118}
119
120#[cold]
121#[inline(never)]
122fn primitive_collection_mismatch() -> Error {
123    Error::type_error(
124        "Vec<number> belongs to the `number_array` type, \
125         and Vec<Option<number>> belongs to the `list` type. \
126         You should not read data of type `number_array` as data of type `list`.",
127    )
128}
129
130#[cold]
131#[inline(never)]
132fn collection_type_mismatch(expected: u32, actual: u32) -> Error {
133    Error::type_mismatch(expected, actual)
134}
135
136#[cold]
137#[inline(never)]
138fn non_polymorphic_collection() -> Error {
139    Error::type_error("Type inconsistent, target type is not polymorphic")
140}
141
142#[cold]
143#[inline(never)]
144fn not_primitive_array() -> Error {
145    Error::type_error("array-compatible field is not a primitive array")
146}
147
148#[cold]
149#[inline(never)]
150fn invalid_primitive_array_len() -> Error {
151    Error::invalid_data("Invalid data length")
152}
153
154#[cold]
155#[inline(never)]
156fn list_array_error(message: &'static str) -> Error {
157    Error::type_error(message)
158}
159
160#[inline(always)]
161fn reserve_collection_storage(
162    context: &mut ReadContext,
163    len: u32,
164    elem_bytes: usize,
165) -> Result<(), Error> {
166    let len = len as usize;
167    let bytes = len
168        .checked_mul(elem_bytes)
169        .ok_or_else(graph_memory_overflow)?;
170    context.reserve_graph_memory(bytes)?;
171    Ok(())
172}
173
174pub fn write_collection_type_info(
175    context: &mut WriteContext,
176    collection_type_id: u32,
177) -> Result<(), Error> {
178    context.writer.write_u8(collection_type_id as u8);
179    Ok(())
180}
181
182macro_rules! collection_write_mode {
183    (value, $T:ty, $S:ty, $value:expr, $context:expr, $ref_mode:expr, $write_type:expr, $has_generics:expr) => {
184        <$S as Serializer>::write($value, $context, $ref_mode, $write_type)
185    };
186    (field, $T:ty, $C:ty, $value:expr, $context:expr, $ref_mode:expr, $write_type:expr, $has_generics:expr) => {
187        <$C as Codec<$T>>::write_with_mode($value, $context, $ref_mode, $write_type, $has_generics)
188    };
189}
190
191macro_rules! collection_write_with_info {
192    (value, $T:ty, $S:ty, $value:expr, $context:expr, $ref_mode:expr, $type_info:expr, $has_generics:expr) => {
193        <$S as Serializer>::write_with_type_info($value, $context, $ref_mode, $type_info)
194    };
195    (field, $T:ty, $C:ty, $value:expr, $context:expr, $ref_mode:expr, $type_info:expr, $has_generics:expr) => {
196        <$C as Codec<$T>>::write_with_type_info(
197            $value,
198            $context,
199            $ref_mode,
200            $type_info,
201            $has_generics,
202        )
203    };
204}
205
206macro_rules! collection_reserved_space {
207    (value, $T:ty, $S:ty) => {
208        <$S as Serializer>::reserved_space()
209    };
210    (field, $T:ty, $C:ty) => {
211        <$C as Codec<$T>>::field_reserved_space()
212    };
213}
214
215macro_rules! write_collection_dyn_body {
216    ($layer:ident, $T:ident, $C:ident, $iter:expr, $context:expr, $has_generics:expr) => {{
217        let context = &mut *$context;
218        let has_generics = $has_generics;
219        let elem_static_type_id = $C::static_type_id();
220        let is_elem_declared = has_generics && !need_to_write_type_for_field(elem_static_type_id);
221        let elem_is_polymorphic = $C::IS_POLYMORPHIC;
222        let elem_is_shared_ref = $C::IS_SHARED_REF;
223        let can_preinspect_dynamic_type = !elem_is_polymorphic || !$C::REQUIRES_SCOPED_ACCESS;
224
225        let iter = $iter.into_iter();
226        let mut has_null = elem_is_polymorphic && !can_preinspect_dynamic_type;
227        let mut is_same_type = can_preinspect_dynamic_type;
228        let mut first_type_id: Option<std::any::TypeId> = None;
229
230        if can_preinspect_dynamic_type {
231            for item in iter.clone() {
232                if elem_is_polymorphic {
233                    if let Some(dynamic_type_id) = $C::dynamic_type_id(item)? {
234                        if is_same_type {
235                            if let Some(first_id) = first_type_id {
236                                if first_id != dynamic_type_id {
237                                    is_same_type = false;
238                                }
239                            } else {
240                                first_type_id = Some(dynamic_type_id);
241                            }
242                        }
243                    } else {
244                        has_null = true;
245                    }
246                } else if $C::is_none(item) {
247                    has_null = true;
248                }
249            }
250        }
251
252        if elem_is_polymorphic && is_same_type && first_type_id.is_none() {
253            // All elements are null, so each element must carry its own type metadata.
254            is_same_type = false;
255        }
256
257        let mut header = 0u8;
258        if has_null {
259            header |= HAS_NULL;
260        }
261        if is_elem_declared {
262            header |= DECL_ELEMENT_TYPE;
263        }
264        if is_same_type {
265            header |= IS_SAME_TYPE;
266        }
267        if elem_is_shared_ref {
268            header |= TRACKING_REF;
269        }
270        context.writer.write_u8(header);
271
272        let type_info = if is_same_type && !is_elem_declared {
273            if elem_is_polymorphic {
274                let type_id = first_type_id.ok_or_else(missing_collection_type)?;
275                Some($C::write_type_info_value(context, type_id)?)
276            } else {
277                $C::write_type_info(context)?;
278                None
279            }
280        } else {
281            None
282        };
283        let elem_ref_mode = if elem_is_shared_ref {
284            RefMode::Tracking
285        } else if has_null {
286            RefMode::NullOnly
287        } else {
288            RefMode::None
289        };
290
291        if is_same_type {
292            if let Some(type_info) = type_info.as_ref() {
293                for item in iter {
294                    collection_write_with_info!(
295                        $layer,
296                        $T,
297                        $C,
298                        item,
299                        context,
300                        elem_ref_mode,
301                        type_info,
302                        has_generics
303                    )?;
304                }
305            } else if elem_ref_mode == RefMode::None {
306                if has_generics {
307                    for item in iter {
308                        collection_write_mode!(
309                            $layer,
310                            $T,
311                            $C,
312                            item,
313                            context,
314                            RefMode::None,
315                            false,
316                            true
317                        )?;
318                    }
319                } else {
320                    for item in iter {
321                        $C::write_data(item, context)?;
322                    }
323                }
324            } else {
325                for item in iter {
326                    collection_write_mode!(
327                        $layer,
328                        $T,
329                        $C,
330                        item,
331                        context,
332                        elem_ref_mode,
333                        false,
334                        has_generics
335                    )?;
336                }
337            }
338        } else {
339            for item in iter {
340                collection_write_mode!(
341                    $layer,
342                    $T,
343                    $C,
344                    item,
345                    context,
346                    elem_ref_mode,
347                    true,
348                    has_generics
349                )?;
350            }
351        }
352        Ok(())
353    }};
354}
355
356macro_rules! write_collection_body {
357    (
358        $layer:ident,
359        $T:ident,
360        $C:ident,
361        $iter:expr,
362        $context:expr,
363        $has_generics:expr,
364        $count_allocates:ident,
365        $zst_no_backing:ident
366    ) => {{
367        let context = &mut *$context;
368        let iter = $iter.into_iter();
369        let len = iter.len();
370        context.writer.write_var_u32(len as u32);
371        if len == 0 {
372            return Ok(());
373        }
374        let has_generics = $has_generics;
375        if $C::IS_POLYMORPHIC || $C::IS_SHARED_REF {
376            write_collection_dyn_body!($layer, $T, $C, iter, context, has_generics)?;
377            return Ok(());
378        }
379        let mut header = IS_SAME_TYPE;
380        let mut has_null = false;
381        let elem_static_type_id = $C::static_type_id();
382        let is_elem_declared = has_generics && !need_to_write_type_for_field(elem_static_type_id);
383        if $C::IS_OPTIONAL {
384            for item in iter.clone() {
385                if $C::is_none(item) {
386                    has_null = true;
387                    break;
388                }
389            }
390        }
391        if has_null {
392            header |= HAS_NULL;
393        }
394        if is_elem_declared {
395            header |= DECL_ELEMENT_TYPE;
396            context.writer.write_u8(header);
397        } else {
398            context.writer.write_u8(header);
399            $C::write_type_info(context)?;
400        }
401        context
402            .writer
403            .reserve(len * collection_reserved_space!($layer, $T, $C));
404        if !has_null {
405            if has_generics {
406                for item in iter {
407                    collection_write_mode!(
408                        $layer,
409                        $T,
410                        $C,
411                        item,
412                        context,
413                        RefMode::None,
414                        false,
415                        true
416                    )?;
417                }
418            } else {
419                for item in iter {
420                    $C::write_data(item, context)?;
421                }
422            }
423        } else {
424            // Null detection already inspected nullable holders. The selected
425            // child layer owns the envelope, so each holder is accessed once.
426            for item in iter {
427                collection_write_mode!(
428                    $layer,
429                    $T,
430                    $C,
431                    item,
432                    context,
433                    RefMode::NullOnly,
434                    false,
435                    has_generics
436                )?;
437            }
438        }
439
440        Ok(())
441    }};
442}
443
444pub fn write_collection_value_data<
445    'a,
446    T,
447    S,
448    I,
449    const COUNT_ALLOCATES: bool,
450    const ZST_NO_BACKING: bool,
451>(
452    iter: I,
453    context: &mut WriteContext,
454) -> Result<(), Error>
455where
456    T: 'static + 'a,
457    S: Serializer<Target = T>,
458    I: IntoIterator<Item = &'a T>,
459    I::IntoIter: ExactSizeIterator + Clone,
460{
461    write_collection_body!(
462        value,
463        T,
464        S,
465        iter,
466        context,
467        false,
468        COUNT_ALLOCATES,
469        ZST_NO_BACKING
470    )
471}
472
473pub fn write_collection_data<'a, T, C, I, const COUNT_ALLOCATES: bool, const ZST_NO_BACKING: bool>(
474    iter: I,
475    context: &mut WriteContext,
476    has_generics: bool,
477) -> Result<(), Error>
478where
479    T: 'static + 'a,
480    C: Codec<T>,
481    I: IntoIterator<Item = &'a T>,
482    I::IntoIter: ExactSizeIterator + Clone,
483{
484    write_collection_body!(
485        field,
486        T,
487        C,
488        iter,
489        context,
490        has_generics,
491        COUNT_ALLOCATES,
492        ZST_NO_BACKING
493    )
494}
495
496pub fn read_collection_type_info(
497    context: &mut ReadContext,
498    collection_type_id: u32,
499) -> Result<(), Error> {
500    let remote_collection_type_id = context.reader.read_u8()? as u32;
501    if PRIMITIVE_ARRAY_TYPES.contains(&remote_collection_type_id) {
502        return Err(primitive_collection_mismatch());
503    }
504    if collection_type_id != remote_collection_type_id {
505        return Err(collection_type_mismatch(
506            collection_type_id,
507            remote_collection_type_id,
508        ));
509    }
510    Ok(())
511}
512
513macro_rules! collection_read_type {
514    (value, $T:ty, $S:ty, $context:expr) => {
515        read_value_type_info::<$S>($context)?
516    };
517    (field, $T:ty, $C:ty, $context:expr) => {
518        Some(<$C as Codec<$T>>::read_type_info_value($context)?)
519    };
520}
521
522macro_rules! collection_read_element {
523    (value, $T:ty, $S:ty, $context:expr, $read_type:expr) => {
524        match $read_type {
525            None => <$S as Serializer>::read_data($context),
526            Some(type_info) => {
527                <$S as Serializer>::read_with_type_info($context, RefMode::None, type_info)
528            }
529        }
530    };
531    (field, $T:ty, $C:ty, $context:expr, $read_type:expr) => {
532        match $read_type {
533            None => <$C as Serializer>::read_data($context),
534            Some(CodecReadType::Field(field_type)) => {
535                <$C as Codec<$T>>::read_data_with_type($context, field_type)
536            }
537            Some(CodecReadType::TypeInfo(type_info)) => {
538                <$C as Codec<$T>>::read_data_with_type_info($context, type_info)
539            }
540        }
541    };
542}
543
544macro_rules! element_read_data_always_advances {
545    (value, $T:ty, $S:ty, $read_type:expr) => {
546        value_read_data_always_advances::<$S>($read_type)
547    };
548    (field, $T:ty, $C:ty, $read_type:expr) => {
549        codec_read_data_always_advances::<$T, $C>($read_type)
550    };
551}
552
553macro_rules! collect_with_unbacked_budget {
554    ($R:ty, $T:ty, $len:expr, $context:expr, $read:expr) => {{
555        let mut window_start = $context.reader.get_cursor();
556        let mut window_items = 0usize;
557        let values = (0..$len)
558            .map(|_| {
559                let value: $T = ($read)?;
560                window_items += 1;
561                if window_items == UNBACKED_CHECK_INTERVAL {
562                    settle_unbacked_items($context, window_items, window_start)?;
563                    window_start = $context.reader.get_cursor();
564                    window_items = 0;
565                }
566                Ok(value)
567            })
568            .collect::<Result<$R, Error>>()?;
569        if window_items != 0 {
570            settle_unbacked_items($context, window_items, window_start)?;
571        }
572        Ok(values)
573    }};
574}
575
576macro_rules! read_collection_body {
577    (
578        $layer:ident,
579        $R:ident,
580        $T:ident,
581        $C:ident,
582        $context:expr,
583        $count_allocates:ident,
584        $zst_no_backing:ident
585    ) => {{
586        let context = &mut *$context;
587        let len = context.reader.read_var_u32()?;
588        reserve_collection_storage(context, len, std::mem::size_of::<$T>())?;
589        if len == 0 {
590            return Ok($R::from_iter(std::iter::empty()));
591        }
592        if $C::IS_POLYMORPHIC || $C::IS_SHARED_REF {
593            return read_collection_data_dyn_ref::<$R, $T, $C, $count_allocates, $zst_no_backing>(
594                context, len,
595            );
596        }
597        let header = context.reader.read_u8()?;
598        let declared = (header & DECL_ELEMENT_TYPE) != 0;
599        let read_type = if declared {
600            None
601        } else {
602            collection_read_type!($layer, $T, $C, context)
603        };
604        let has_null = (header & HAS_NULL) != 0;
605        if (header & IS_SAME_TYPE) == 0 {
606            return Err(non_polymorphic_collection());
607        }
608        let element_read_always_advances =
609            has_null || element_read_data_always_advances!($layer, $T, $C, read_type.as_ref());
610        check_collection_len::<$T, $count_allocates, $zst_no_backing>(
611            context,
612            len,
613            element_read_always_advances,
614        )?;
615        if !has_null {
616            if element_read_always_advances {
617                (0..len)
618                    .map(|_| collection_read_element!($layer, $T, $C, context, read_type.as_ref()))
619                    .collect::<Result<$R, Error>>()
620            } else {
621                collect_with_unbacked_budget!(
622                    $R,
623                    $T,
624                    len,
625                    context,
626                    collection_read_element!($layer, $T, $C, context, read_type.as_ref())
627                )
628            }
629        } else {
630            (0..len)
631                .map(|_| {
632                    let flag = context.reader.read_i8()?;
633                    if flag == RefFlag::Null as i8 {
634                        return $C::default_value(context);
635                    }
636                    collection_read_element!($layer, $T, $C, context, read_type.as_ref())
637                })
638                .collect::<Result<$R, Error>>()
639        }
640    }};
641}
642
643pub fn read_collection_value_data<
644    R,
645    T,
646    S,
647    const COUNT_ALLOCATES: bool,
648    const ZST_NO_BACKING: bool,
649>(
650    context: &mut ReadContext,
651) -> Result<R, Error>
652where
653    T: 'static,
654    S: Serializer<Target = T>,
655    R: FromIterator<T>,
656{
657    read_collection_body!(value, R, T, S, context, COUNT_ALLOCATES, ZST_NO_BACKING)
658}
659
660pub fn read_collection_data<R, T, C, const COUNT_ALLOCATES: bool, const ZST_NO_BACKING: bool>(
661    context: &mut ReadContext,
662) -> Result<R, Error>
663where
664    T: 'static,
665    C: Codec<T>,
666    R: FromIterator<T>,
667{
668    read_collection_body!(field, R, T, C, context, COUNT_ALLOCATES, ZST_NO_BACKING)
669}
670
671/// Read a LIST/SET body using the recursive field metadata supplied by its owner.
672pub fn read_collection_data_with_type<
673    R,
674    T,
675    C,
676    const COUNT_ALLOCATES: bool,
677    const ZST_NO_BACKING: bool,
678>(
679    context: &mut ReadContext,
680    remote_field_type: &FieldType,
681) -> Result<R, Error>
682where
683    T: 'static,
684    C: Codec<T>,
685    R: FromIterator<T>,
686{
687    let element_type = generic_field_type(remote_field_type, 0, "collection")?;
688    let len = context.reader.read_var_u32()?;
689    reserve_collection_storage(context, len, std::mem::size_of::<T>())?;
690    if len == 0 {
691        return Ok(R::from_iter(std::iter::empty()));
692    }
693
694    let header = context.reader.read_u8()?;
695    let track_ref = (header & TRACKING_REF) != 0;
696    let same_type = (header & IS_SAME_TYPE) != 0;
697    let has_null = (header & HAS_NULL) != 0;
698    let declared = (header & DECL_ELEMENT_TYPE) != 0;
699    let ref_mode = if track_ref {
700        RefMode::Tracking
701    } else if has_null {
702        RefMode::NullOnly
703    } else {
704        RefMode::None
705    };
706
707    if C::IS_POLYMORPHIC || C::IS_SHARED_REF {
708        if same_type {
709            if declared {
710                let element_type = field_type_with_ref_flags(element_type, has_null, track_ref);
711                let element_read_always_advances = ref_mode != RefMode::None
712                    || (C::READ_DATA_ALWAYS_ADVANCES
713                        && field_read_data_always_advances(&element_type));
714                check_collection_len::<T, COUNT_ALLOCATES, ZST_NO_BACKING>(
715                    context,
716                    len,
717                    element_read_always_advances,
718                )?;
719                if element_read_always_advances {
720                    return (0..len)
721                        .map(|_| C::read_field_with_type(context, &element_type))
722                        .collect::<Result<R, Error>>();
723                }
724                return collect_with_unbacked_budget!(
725                    R,
726                    T,
727                    len,
728                    context,
729                    C::read_field_with_type(context, &element_type)
730                );
731            }
732            let type_info = context.read_any_type_info()?;
733            let element_read_always_advances = ref_mode != RefMode::None
734                || (C::READ_DATA_ALWAYS_ADVANCES && type_info.has_exact_local_schema());
735            check_collection_len::<T, COUNT_ALLOCATES, ZST_NO_BACKING>(
736                context,
737                len,
738                element_read_always_advances,
739            )?;
740            if element_read_always_advances {
741                return (0..len)
742                    .map(|_| C::read_with_type_info(context, ref_mode, &type_info))
743                    .collect::<Result<R, Error>>();
744            }
745            return collect_with_unbacked_budget!(
746                R,
747                T,
748                len,
749                context,
750                C::read_with_type_info(context, ref_mode, &type_info)
751            );
752        }
753        check_collection_len::<T, COUNT_ALLOCATES, ZST_NO_BACKING>(context, len, true)?;
754        return (0..len)
755            .map(|_| C::read(context, ref_mode, true))
756            .collect::<Result<R, Error>>();
757    }
758
759    if !same_type {
760        return Err(non_polymorphic_collection());
761    }
762    if declared {
763        if has_null {
764            check_collection_len::<T, COUNT_ALLOCATES, ZST_NO_BACKING>(context, len, true)?;
765            return (0..len)
766                .map(|_| {
767                    if context.reader.read_i8()? == RefFlag::Null as i8 {
768                        C::default_value(context)
769                    } else {
770                        C::read_data_with_type(context, element_type)
771                    }
772                })
773                .collect::<Result<R, Error>>();
774        }
775        let element_read_data_always_advances =
776            C::READ_DATA_ALWAYS_ADVANCES && field_read_data_always_advances(element_type);
777        check_collection_len::<T, COUNT_ALLOCATES, ZST_NO_BACKING>(
778            context,
779            len,
780            element_read_data_always_advances,
781        )?;
782        if element_read_data_always_advances {
783            return (0..len)
784                .map(|_| C::read_data_with_type(context, element_type))
785                .collect::<Result<R, Error>>();
786        }
787        return collect_with_unbacked_budget!(
788            R,
789            T,
790            len,
791            context,
792            C::read_data_with_type(context, element_type)
793        );
794    }
795
796    let read_type = C::read_type_info_value(context)?;
797    if has_null {
798        check_collection_len::<T, COUNT_ALLOCATES, ZST_NO_BACKING>(context, len, true)?;
799        (0..len)
800            .map(|_| {
801                if context.reader.read_i8()? == RefFlag::Null as i8 {
802                    C::default_value(context)
803                } else {
804                    match &read_type {
805                        super::codec::CodecReadType::Field(field_type) => {
806                            C::read_data_with_type(context, field_type)
807                        }
808                        super::codec::CodecReadType::TypeInfo(type_info) => {
809                            C::read_data_with_type_info(context, type_info)
810                        }
811                    }
812                }
813            })
814            .collect::<Result<R, Error>>()
815    } else {
816        let element_read_data_always_advances =
817            codec_read_data_always_advances::<T, C>(Some(&read_type));
818        check_collection_len::<T, COUNT_ALLOCATES, ZST_NO_BACKING>(
819            context,
820            len,
821            element_read_data_always_advances,
822        )?;
823        if element_read_data_always_advances {
824            (0..len)
825                .map(|_| match &read_type {
826                    super::codec::CodecReadType::Field(field_type) => {
827                        C::read_data_with_type(context, field_type)
828                    }
829                    super::codec::CodecReadType::TypeInfo(type_info) => {
830                        C::read_data_with_type_info(context, type_info)
831                    }
832                })
833                .collect::<Result<R, Error>>()
834        } else {
835            collect_with_unbacked_budget!(
836                R,
837                T,
838                len,
839                context,
840                match &read_type {
841                    super::codec::CodecReadType::Field(field_type) => {
842                        C::read_data_with_type(context, field_type)
843                    }
844                    super::codec::CodecReadType::TypeInfo(type_info) => {
845                        C::read_data_with_type_info(context, type_info)
846                    }
847                }
848            )
849        }
850    }
851}
852
853/// Slow but versatile collection deserialization for dynamic trait object and shared/circular reference.
854pub fn read_collection_data_dyn_ref<
855    R,
856    T,
857    C,
858    const COUNT_ALLOCATES: bool,
859    const ZST_NO_BACKING: bool,
860>(
861    context: &mut ReadContext,
862    len: u32,
863) -> Result<R, Error>
864where
865    T: 'static,
866    C: Serializer<Target = T>,
867    R: FromIterator<T>,
868{
869    // Read header
870    let header = context.reader.read_u8()?;
871    // IMPORTANT: dynamic/shared-ref collection reads still obey the wire
872    // header first. Local Rust type information must not override whether the
873    // sender wrote ref flags for these elements. DO NOT REMOVE this comment.
874    let is_track_ref = (header & TRACKING_REF) != 0;
875    let is_same_type = (header & IS_SAME_TYPE) != 0;
876    let has_null = (header & HAS_NULL) != 0;
877    let is_declared = (header & DECL_ELEMENT_TYPE) != 0;
878
879    // Compute RefMode from flags
880    let elem_ref_mode = if is_track_ref {
881        RefMode::Tracking
882    } else if has_null {
883        RefMode::NullOnly
884    } else {
885        RefMode::None
886    };
887
888    // Read elements
889    if is_same_type {
890        if is_declared {
891            let element_read_always_advances =
892                elem_ref_mode != RefMode::None || C::READ_DATA_ALWAYS_ADVANCES;
893            check_collection_len::<T, COUNT_ALLOCATES, ZST_NO_BACKING>(
894                context,
895                len,
896                element_read_always_advances,
897            )?;
898            if element_read_always_advances {
899                (0..len)
900                    .map(|_| C::read(context, elem_ref_mode, false))
901                    .collect::<Result<R, Error>>()
902            } else {
903                collect_with_unbacked_budget!(
904                    R,
905                    T,
906                    len,
907                    context,
908                    C::read(context, elem_ref_mode, false)
909                )
910            }
911        } else {
912            let type_info = context.read_any_type_info()?;
913            let element_read_always_advances = elem_ref_mode != RefMode::None
914                || (C::READ_DATA_ALWAYS_ADVANCES && type_info.has_exact_local_schema());
915            check_collection_len::<T, COUNT_ALLOCATES, ZST_NO_BACKING>(
916                context,
917                len,
918                element_read_always_advances,
919            )?;
920            if element_read_always_advances {
921                (0..len)
922                    .map(|_| C::read_with_type_info(context, elem_ref_mode, &type_info))
923                    .collect::<Result<R, Error>>()
924            } else {
925                collect_with_unbacked_budget!(
926                    R,
927                    T,
928                    len,
929                    context,
930                    C::read_with_type_info(context, elem_ref_mode, &type_info)
931                )
932            }
933        }
934    } else {
935        check_collection_len::<T, COUNT_ALLOCATES, ZST_NO_BACKING>(context, len, true)?;
936        (0..len)
937            .map(|_| C::read(context, elem_ref_mode, true))
938            .collect::<Result<R, Error>>()
939    }
940}
941
942fn list_element_type_matches_array(
943    list: &FieldType,
944    array: &FieldType,
945    require_unframed_element: bool,
946) -> bool {
947    primitive_list::element_type_id(array.type_id).is_some_and(|element_type_id| {
948        if list.type_id != type_id::LIST
949            || list.generics.len() != 1
950            || list.nullable
951            || list.track_ref
952            || array.nullable
953            || array.track_ref
954        {
955            return false;
956        }
957        let element = &list.generics[0];
958        // Nullable element schema is allowed for list<T?> -> array<T>; actual
959        // null elements fail in the dense-array reader. Ref-tracked
960        // element framing is rejected here because this path stays primitive-only.
961        if require_unframed_element && element.track_ref {
962            return false;
963        }
964        primitive_element_type_matches(element_type_id, element.type_id)
965    })
966}
967
968pub(super) fn compatible_list_array_field(local: &FieldType, remote: &FieldType) -> bool {
969    (local.type_id == type_id::LIST && list_element_type_matches_array(local, remote, false))
970        || (remote.type_id == type_id::LIST && list_element_type_matches_array(remote, local, true))
971}
972
973fn primitive_element_type_matches(array_element_type_id: u32, list_element_type_id: u32) -> bool {
974    array_element_type_id == list_element_type_id
975        || same_numeric_family(array_element_type_id, list_element_type_id)
976}
977
978#[inline(always)]
979fn primitive_element_min_wire_size(element_type_id: u32) -> Option<usize> {
980    match element_type_id {
981        type_id::BOOL
982        | type_id::INT8
983        | type_id::UINT8
984        | type_id::VARINT32
985        | type_id::VARINT64
986        | type_id::VAR_UINT32
987        | type_id::VAR_UINT64 => Some(1),
988        type_id::INT16 | type_id::UINT16 | type_id::FLOAT16 | type_id::BFLOAT16 => Some(2),
989        type_id::INT32
990        | type_id::UINT32
991        | type_id::FLOAT32
992        | type_id::TAGGED_INT64
993        | type_id::TAGGED_UINT64 => Some(4),
994        type_id::INT64 | type_id::UINT64 | type_id::FLOAT64 => Some(8),
995        _ => None,
996    }
997}
998
999fn read_primitive_array_with_codec<T, C>(
1000    context: &mut ReadContext,
1001    remote_field_type: &FieldType,
1002) -> Result<Vec<T>, Error>
1003where
1004    T: 'static,
1005    C: Codec<T>,
1006{
1007    let size_bytes = context.reader.read_var_u32()? as usize;
1008    let elem_size =
1009        primitive_list::element_size(remote_field_type.type_id).ok_or_else(not_primitive_array)?;
1010    if size_bytes % elem_size != 0 {
1011        return Err(invalid_primitive_array_len());
1012    }
1013    context.reader.check_bound(size_bytes)?;
1014    let len = size_bytes / elem_size;
1015    let element_type_id = primitive_list::element_type_id(remote_field_type.type_id)
1016        .ok_or_else(not_primitive_array)?;
1017    reserve_collection_storage(context, len as u32, std::mem::size_of::<T>())?;
1018    let element_type = FieldType::new(element_type_id, false, Vec::new());
1019    let mut vec = Vec::with_capacity(len);
1020    for _ in 0..len {
1021        vec.push(C::read_data_with_type(context, &element_type)?);
1022    }
1023    Ok(vec)
1024}
1025
1026pub(super) fn read_list_as_primitive_vec<T, C>(
1027    context: &mut ReadContext,
1028    remote_field_type: &FieldType,
1029) -> Result<Vec<T>, Error>
1030where
1031    T: 'static,
1032    C: Codec<T>,
1033{
1034    let element_type = generic_field_type(remote_field_type, 0, "list")?;
1035    let len = context.reader.read_var_u32()?;
1036    let len_usize = len as usize;
1037    if len == 0 {
1038        return Ok(Vec::new());
1039    }
1040    let header = context.reader.read_u8()?;
1041    if (header & HAS_NULL) != 0 {
1042        return Err(list_array_error(
1043            "compatible list to array field requires non-null elements",
1044        ));
1045    }
1046    if (header & TRACKING_REF) != 0 {
1047        return Err(list_array_error(
1048            "array-compatible list declares reference-tracked elements",
1049        ));
1050    }
1051    if (header & IS_SAME_TYPE) == 0 {
1052        return Err(list_array_error(
1053            "array-compatible list must declare same-type elements",
1054        ));
1055    }
1056    if (header & DECL_ELEMENT_TYPE) == 0 {
1057        return Err(list_array_error(
1058            "array-compatible list must declare element type",
1059        ));
1060    }
1061    // Validate the header before measuring unread element data. The minimum
1062    // wire-width proof must finish before destination storage is reserved.
1063    let element_min_size =
1064        primitive_element_min_wire_size(element_type.type_id).ok_or_else(|| {
1065            list_array_error("array-compatible list element is not a supported primitive type")
1066        })?;
1067    let min_size_bytes = len_usize
1068        .checked_mul(element_min_size)
1069        .ok_or_else(invalid_primitive_array_len)?;
1070    context.reader.check_bound(min_size_bytes)?;
1071    reserve_collection_storage(context, len, std::mem::size_of::<T>())?;
1072    let mut vec = Vec::with_capacity(len_usize);
1073    for _ in 0..len {
1074        vec.push(C::read_data_with_type(context, element_type)?);
1075    }
1076    Ok(vec)
1077}
1078
1079#[cold]
1080#[inline(never)]
1081pub(super) fn read_vec_compatible_mismatch<T, C>(
1082    context: &mut ReadContext,
1083    local_field_type: &FieldType,
1084    remote_field_type: &FieldType,
1085) -> Result<Option<Vec<T>>, Error>
1086where
1087    T: 'static,
1088    C: Codec<T>,
1089{
1090    if local_field_type.type_id == type_id::LIST
1091        && list_element_type_matches_array(local_field_type, remote_field_type, false)
1092    {
1093        return read_array_data_as_vec_bridge::<T, C>(context, remote_field_type).map(Some);
1094    }
1095    Ok(None)
1096}
1097
1098fn read_array_data_as_vec_bridge<T, C>(
1099    context: &mut ReadContext,
1100    remote_field_type: &FieldType,
1101) -> Result<Vec<T>, Error>
1102where
1103    T: 'static,
1104    C: Codec<T>,
1105{
1106    if field_ref_mode(remote_field_type) != RefMode::None {
1107        let ref_flag = context.reader.read_i8()?;
1108        if ref_flag == RefFlag::Null as i8 {
1109            return Ok(Vec::new());
1110        }
1111    }
1112    if crate::serializer::util::field_need_read_type_info(remote_field_type.type_id) {
1113        let remote = context.reader.read_u8()? as u32;
1114        if remote != remote_field_type.type_id {
1115            return Err(collection_type_mismatch(remote_field_type.type_id, remote));
1116        }
1117    }
1118    read_primitive_array_with_codec::<T, C>(context, remote_field_type)
1119}
1120
1121#[cold]
1122#[inline(never)]
1123pub(super) fn read_primitive_array_vec_mismatch<T, C>(
1124    context: &mut ReadContext,
1125    local_field_type: &FieldType,
1126    remote_field_type: &FieldType,
1127) -> Result<Option<Vec<T>>, Error>
1128where
1129    T: 'static,
1130    C: Codec<T>,
1131{
1132    if remote_field_type.type_id == type_id::LIST
1133        && !remote_field_type.generics.is_empty()
1134        && list_element_type_matches_array(remote_field_type, local_field_type, true)
1135    {
1136        if field_ref_mode(remote_field_type) != RefMode::None {
1137            let ref_flag = context.reader.read_i8()?;
1138            if ref_flag == RefFlag::Null as i8 {
1139                return Ok(Some(Vec::new()));
1140            }
1141        }
1142        return read_list_as_primitive_vec::<T, C>(context, remote_field_type).map(Some);
1143    }
1144    Ok(None)
1145}
1146
1147#[cfg(test)]
1148mod tests {
1149    use super::*;
1150    use crate::serializer::codec::{I32Codec, I64Codec};
1151    use crate::serializer::VecSerializer;
1152    use crate::{Config, Fory, Reader, TypeId, TypeResolver};
1153
1154    #[derive(Clone, Debug, PartialEq)]
1155    struct EmptyValue(u8);
1156
1157    struct EmptyValueSerializer;
1158
1159    impl Serializer for EmptyValueSerializer {
1160        type Target = EmptyValue;
1161
1162        fn write_data(_: &EmptyValue, _: &mut WriteContext) -> Result<(), Error> {
1163            Ok(())
1164        }
1165
1166        fn read_data(_: &mut ReadContext) -> Result<EmptyValue, Error> {
1167            Ok(EmptyValue(0))
1168        }
1169
1170        fn default_value(_: &mut ReadContext) -> Result<EmptyValue, Error> {
1171            Ok(EmptyValue(0))
1172        }
1173
1174        fn static_type_id() -> TypeId {
1175            TypeId::EXT
1176        }
1177    }
1178
1179    #[test]
1180    fn empty_items_use_root_budget() {
1181        let mut fory = Fory::builder().max_unbacked_container_items(2).build();
1182        fory.register_serializer::<EmptyValueSerializer>(200)
1183            .unwrap();
1184        let mut oversized = fory
1185            .serialize_with::<VecSerializer<EmptyValueSerializer>>(&vec![EmptyValue(0); 3])
1186            .unwrap();
1187        // Leave one unread byte so the preallocation gate admits the third item;
1188        // the zero-byte bodies must still exhaust the root work budget.
1189        oversized.push(0);
1190        let error = fory
1191            .deserialize_with::<VecSerializer<EmptyValueSerializer>>(&oversized)
1192            .unwrap_err();
1193        assert!(error.to_string().contains("max_unbacked_container_items"));
1194
1195        let valid = fory
1196            .serialize_with::<VecSerializer<EmptyValueSerializer>>(&vec![EmptyValue(0); 2])
1197            .unwrap();
1198        assert_eq!(
1199            fory.deserialize_with::<VecSerializer<EmptyValueSerializer>>(&valid)
1200                .unwrap(),
1201            vec![EmptyValue(0); 2]
1202        );
1203    }
1204
1205    #[test]
1206    fn positive_items_ignore_zero_budget() {
1207        let fory = Fory::builder().max_unbacked_container_items(0).build();
1208        let value = vec![1i32, 2, 3];
1209        let bytes = fory.serialize_with::<VecSerializer<i32>>(&value).unwrap();
1210        assert_eq!(
1211            fory.deserialize_with::<VecSerializer<i32>>(&bytes).unwrap(),
1212            value
1213        );
1214    }
1215
1216    #[test]
1217    fn count_allocation_uses_allowance() {
1218        let bytes = [3, IS_SAME_TYPE | DECL_ELEMENT_TYPE];
1219        let mut context = ReadContext::new(TypeResolver::default(), Config::default());
1220        context.remaining_graph_memory_bytes = usize::MAX;
1221        context.remaining_unbacked_container_items = 1;
1222        context.attach_reader(Reader::new(&bytes));
1223
1224        let error = read_collection_value_data::<
1225            Vec<EmptyValue>,
1226            EmptyValue,
1227            EmptyValueSerializer,
1228            true,
1229            true,
1230        >(&mut context)
1231        .unwrap_err();
1232        assert!(matches!(error, Error::BufferOutOfBound(..)));
1233        assert_eq!(context.reader.get_cursor(), 2);
1234    }
1235
1236    #[test]
1237    fn settles_periodic_window_and_tail() {
1238        let mut bytes = Vec::new();
1239        let mut writer = crate::Writer::from_buffer(&mut bytes);
1240        writer.write_var_u32(1025);
1241        writer.write_u8(IS_SAME_TYPE | DECL_ELEMENT_TYPE);
1242        writer.write_u8(0);
1243        let mut context = ReadContext::new(TypeResolver::default(), Config::default());
1244        context.remaining_graph_memory_bytes = usize::MAX;
1245        context.remaining_unbacked_container_items = 1024;
1246        context.attach_reader(Reader::new(&bytes));
1247
1248        let error = read_collection_value_data::<
1249            Vec<EmptyValue>,
1250            EmptyValue,
1251            EmptyValueSerializer,
1252            true,
1253            true,
1254        >(&mut context)
1255        .unwrap_err();
1256        assert!(error.to_string().contains("max_unbacked_container_items"));
1257        assert_eq!(context.remaining_unbacked_container_items, 0);
1258    }
1259
1260    #[test]
1261    fn list_array_checks_fixed_width_body() {
1262        let bytes = [2, IS_SAME_TYPE | DECL_ELEMENT_TYPE, 1, 0, 0, 0];
1263        let config = Config::default();
1264        let mut context = ReadContext::new(TypeResolver::default(), config);
1265        let graph_memory = 2 * std::mem::size_of::<i32>();
1266        context.remaining_graph_memory_bytes = graph_memory;
1267        context.attach_reader(Reader::new(&bytes));
1268        let remote = FieldType::new(
1269            type_id::LIST,
1270            false,
1271            vec![FieldType::new(type_id::INT32, false, Vec::new())],
1272        );
1273
1274        let error = read_list_as_primitive_vec::<
1275            i32,
1276            I32Codec<{ type_id::INT32 as u8 }, false, false>,
1277        >(&mut context, &remote)
1278        .unwrap_err();
1279
1280        assert!(matches!(error, Error::BufferOutOfBound(..)));
1281        assert_eq!(context.reader.get_cursor(), 2);
1282        assert_eq!(context.remaining_graph_memory_bytes, graph_memory);
1283    }
1284
1285    #[test]
1286    fn list_array_checks_tagged_body() {
1287        let bytes = [2, IS_SAME_TYPE | DECL_ELEMENT_TYPE, 0, 0, 0, 0];
1288        let config = Config::default();
1289        let mut context = ReadContext::new(TypeResolver::default(), config);
1290        let graph_memory = 2 * std::mem::size_of::<i64>();
1291        context.remaining_graph_memory_bytes = graph_memory;
1292        context.attach_reader(Reader::new(&bytes));
1293        let remote = FieldType::new(
1294            type_id::LIST,
1295            false,
1296            vec![FieldType::new(type_id::TAGGED_INT64, false, Vec::new())],
1297        );
1298
1299        let error = read_list_as_primitive_vec::<
1300            i64,
1301            I64Codec<{ type_id::TAGGED_INT64 as u8 }, false, false>,
1302        >(&mut context, &remote)
1303        .unwrap_err();
1304
1305        assert!(matches!(error, Error::BufferOutOfBound(..)));
1306        assert_eq!(context.reader.get_cursor(), 2);
1307        assert_eq!(context.remaining_graph_memory_bytes, graph_memory);
1308    }
1309}