Skip to main content

miniextendr_api/from_r/
borrow.rs

1//! Native vector borrow metadata and pre-conversion alias checks.
2
3use crate::gc_protect::ProtectScope;
4use crate::{R_xlen_t, SEXP, SEXPTYPE, SexpExt};
5
6/// The native vector storage borrowed by a conversion.
7///
8/// Homogeneous containers add list levels; tuples describe each positional
9/// field separately. Optional NULLs require no extra level. Owned fields have
10/// no descriptor and never participate in alias checks.
11#[derive(#[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for NativeBorrow { }
#[automatically_derived]
impl ::core::clone::Clone for NativeBorrow {
    #[inline]
    fn clone(&self) -> Self {
        let _: ::core::clone::AssertParamIsClone<BorrowShape>;
        let _: ::core::clone::AssertParamIsClone<usize>;
        *self
    }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for NativeBorrow { }Copy, #[automatically_derived]
impl ::core::fmt::Debug for NativeBorrow {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field2_finish(f, "NativeBorrow",
            "shape", &self.shape, "list_depth", &&self.list_depth)
    }
}Debug, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for NativeBorrow { }
#[automatically_derived]
impl ::core::cmp::PartialEq for NativeBorrow {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        self.shape == other.shape && self.list_depth == other.list_depth
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for NativeBorrow {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<BorrowShape>;
        let _: ::core::cmp::AssertParamIsEq<usize>;
    }
}Eq)]
12pub struct NativeBorrow {
13    shape: BorrowShape,
14    list_depth: usize,
15}
16
17#[derive(#[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for BorrowShape { }
#[automatically_derived]
impl ::core::clone::Clone for BorrowShape {
    #[inline]
    fn clone(&self) -> Self {
        let _: ::core::clone::AssertParamIsClone<SEXPTYPE>;
        let _: ::core::clone::AssertParamIsClone<bool>;
        let _:
                ::core::clone::AssertParamIsClone<&'static [Option<NativeBorrow>]>;
        *self
    }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for BorrowShape { }Copy, #[automatically_derived]
impl ::core::fmt::Debug for BorrowShape {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        match self {
            Self::Vector {
                sexptype: __self_0, mutable: __self_1, scalar: __self_2 } =>
                ::core::fmt::Formatter::debug_struct_field3_finish(f,
                    "Vector", "sexptype", __self_0, "mutable", __self_1,
                    "scalar", &__self_2),
            Self::Tuple(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Tuple",
                    &__self_0),
        }
    }
}Debug, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for BorrowShape { }
#[automatically_derived]
impl ::core::cmp::PartialEq for BorrowShape {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        ::core::intrinsics::discriminant_value(self) ==
                ::core::intrinsics::discriminant_value(other) &&
            match (self, other) {
                (Self::Vector {
                    sexptype: __self_0, mutable: __self_1, scalar: __self_2 },
                    Self::Vector {
                    sexptype: __arg1_0, mutable: __arg1_1, scalar: __arg1_2 })
                    =>
                    __self_1 == __arg1_1 && __self_2 == __arg1_2 &&
                        __self_0 == __arg1_0,
                (Self::Tuple(__self_0), Self::Tuple(__arg1_0)) =>
                    __self_0 == __arg1_0,
                _ => unsafe { ::core::intrinsics::unreachable() }
            }
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for BorrowShape {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<SEXPTYPE>;
        let _: ::core::cmp::AssertParamIsEq<bool>;
        let _: ::core::cmp::AssertParamIsEq<&'static [Option<NativeBorrow>]>;
    }
}Eq)]
18enum BorrowShape {
19    Vector {
20        sexptype: SEXPTYPE,
21        mutable: bool,
22        scalar: bool,
23    },
24    Tuple(&'static [Option<NativeBorrow>]),
25}
26
27impl NativeBorrow {
28    /// A reference to the only element of a native R vector.
29    pub const fn scalar(sexptype: SEXPTYPE, mutable: bool) -> Self {
30        Self {
31            shape: BorrowShape::Vector {
32                sexptype,
33                mutable,
34                scalar: true,
35            },
36            list_depth: 0,
37        }
38    }
39
40    /// A slice over a native R vector, with empty vectors excluded from conflicts.
41    pub const fn slice(sexptype: SEXPTYPE, mutable: bool) -> Self {
42        Self {
43            shape: BorrowShape::Vector {
44                sexptype,
45                mutable,
46                scalar: false,
47            },
48            list_depth: 0,
49        }
50    }
51
52    /// Borrow metadata for a positional R list with exactly this many fields.
53    /// Returns `None` when every field is owned.
54    pub const fn tuple(fields: &'static [Option<Self>]) -> Option<Self> {
55        let mut index = 0;
56        while index < fields.len() {
57            if fields[index].is_some() {
58                return Some(Self {
59                    shape: BorrowShape::Tuple(fields),
60                    list_depth: 0,
61                });
62            }
63            index += 1;
64        }
65        None
66    }
67
68    /// Forward an element conversion through one R list level.
69    pub const fn in_list(borrow: Option<Self>) -> Option<Self> {
70        match borrow {
71            Some(mut borrow) => {
72                borrow.list_depth += 1;
73                Some(borrow)
74            }
75            None => None,
76        }
77    }
78
79    fn conflicts_with(self, other: Self) -> bool {
80        match (self.shape, other.shape) {
81            (BorrowShape::Tuple(fields), _) => {
82                fields.iter().flatten().any(|a| a.conflicts_with(other))
83            }
84            (_, BorrowShape::Tuple(fields)) => {
85                fields.iter().flatten().any(|b| self.conflicts_with(*b))
86            }
87            (
88                BorrowShape::Vector {
89                    sexptype: a,
90                    mutable: am,
91                    ..
92                },
93                BorrowShape::Vector {
94                    sexptype: b,
95                    mutable: bm,
96                    ..
97                },
98            ) => (am || bm) && a == b,
99        }
100    }
101
102    fn needs_internal_check(self) -> bool {
103        if self.list_depth > 0 {
104            return self.conflicts_with(self);
105        }
106        match self.shape {
107            BorrowShape::Tuple(fields) => needs_check(fields),
108            BorrowShape::Vector { .. } => false,
109        }
110    }
111
112    fn accepts_length(self, length: R_xlen_t) -> bool {
113        match self.shape {
114            BorrowShape::Vector { scalar: true, .. } => length == 1,
115            BorrowShape::Vector { scalar: false, .. } => length > 0,
116            BorrowShape::Tuple(_) => false,
117        }
118    }
119}
120
121/// Whether any conversion pair can alias, without inspecting or allocating in R.
122///
123/// Generated worker wrappers use this before entering R unwind protection so
124/// owned/custom conversions and shared-only signatures need no extra boundary.
125#[doc(hidden)]
126pub fn needs_check(borrows: &[Option<NativeBorrow>]) -> bool {
127    for (i, borrow) in borrows.iter().enumerate() {
128        let Some(a) = borrow else { continue };
129        if a.needs_internal_check() {
130            return true;
131        }
132        if borrows[i + 1..]
133            .iter()
134            .flatten()
135            .any(|b| a.conflicts_with(*b))
136        {
137            return true;
138        }
139    }
140    false
141}
142
143/// Reject every conflicting argument pair before any Rust borrow is constructed.
144///
145/// # Safety
146///
147/// Run on R's main thread inside an R unwind-protected wrapper. Argument SEXPs
148/// must stay reachable for the whole call. Metadata must describe the actual
149/// conversions of the corresponding arguments.
150#[doc(hidden)]
151pub unsafe fn assert_no_aliases<const N: usize>(
152    args: [(SEXP, &str); N],
153    borrows: [Option<NativeBorrow>; N],
154) {
155    let mut errors = Vec::new();
156    for i in 0..N {
157        let Some(a) = borrows[i] else { continue };
158        for j in i..N {
159            let Some(b) = borrows[j] else { continue };
160            let same_arg = i == j;
161            if !a.conflicts_with(b) || (same_arg && !a.needs_internal_check()) {
162                continue;
163            }
164            let mut path_a = Vec::new();
165            let mut path_b = Vec::new();
166            let aliases = unsafe {
167                visit_leaves(
168                    args[i].0,
169                    a,
170                    &mut path_a,
171                    &mut |leaf_a, indices_a, borrow_a| {
172                        visit_leaves(
173                            args[j].0,
174                            b,
175                            &mut path_b,
176                            &mut |leaf_b, indices_b, borrow_b| {
177                                (!same_arg || indices_a < indices_b)
178                                    && leaf_a == leaf_b
179                                    && borrow_a.conflicts_with(borrow_b)
180                            },
181                        )
182                    },
183                )
184            };
185            if aliases {
186                let name_a = args[i].1;
187                let name_b = args[j].1;
188                errors.push(if same_arg {
189                    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("parameter `{0}` contains duplicate elements that borrow the same non-empty R vector mutably",
                name_a))
    })format!("parameter `{name_a}` contains duplicate elements that borrow the same non-empty R vector mutably")
190                } else {
191                    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("parameters `{0}` and `{1}` borrow the same non-empty R object, and at least one borrows it mutably; pass distinct vectors",
                name_a, name_b))
    })format!("parameters `{name_a}` and `{name_b}` borrow the same non-empty R object, and at least one borrows it mutably; pass distinct vectors")
192                });
193            }
194        }
195    }
196    if !errors.is_empty() {
    {
        ::core::panicking::panic_fmt(format_args!("aliasing native arguments: {0}",
                errors.join("; ")));
    }
};assert!(
197        errors.is_empty(),
198        "aliasing native arguments: {}",
199        errors.join("; ")
200    );
201}
202
203/// Keep each current ALTREP/list element rooted while the other argument is
204/// visited. Protection depth follows type nesting, not the number of elements.
205unsafe fn visit_leaves(
206    sexp: SEXP,
207    borrow: NativeBorrow,
208    path: &mut Vec<R_xlen_t>,
209    visit: &mut impl FnMut(SEXP, &[R_xlen_t], NativeBorrow) -> bool,
210) -> bool {
211    if borrow.list_depth == 0
212        && let BorrowShape::Vector { sexptype, .. } = borrow.shape
213    {
214        return sexp.type_of() == sexptype
215            && borrow.accepts_length(sexp.xlength())
216            && visit(sexp, path, borrow);
217    }
218    if sexp.type_of() != SEXPTYPE::VECSXP {
219        return false;
220    }
221    let length = sexp.xlength();
222    if borrow.list_depth == 0
223        && let BorrowShape::Tuple(fields) = borrow.shape
224        && usize::try_from(length).ok() != Some(fields.len())
225    {
226        // Conversion will report the shape error before borrowing any field.
227        return false;
228    }
229    for index in 0..length {
230        let child_borrow = if borrow.list_depth > 0 {
231            NativeBorrow {
232                list_depth: borrow.list_depth - 1,
233                ..borrow
234            }
235        } else {
236            let BorrowShape::Tuple(fields) = borrow.shape else {
237                ::core::panicking::panic("internal error: entered unreachable code")unreachable!()
238            };
239            let Some(field) = fields[usize::try_from(index).unwrap()] else {
240                continue;
241            };
242            field
243        };
244        let scope = unsafe { ProtectScope::new() };
245        let child = unsafe { scope.protect(sexp.vector_elt(index)).get() };
246        path.push(index);
247        let found = unsafe { visit_leaves(child, child_borrow, path, visit) };
248        path.pop();
249        if found {
250            return true;
251        }
252    }
253    false
254}
255
256#[cfg(test)]
257mod tests {
258    use super::*;
259    use crate::from_r::TryFromSexp;
260
261    #[test]
262    fn native_borrow_metadata_follows_actual_converters() {
263        macro_rules! check {
264            ($t:ty, $sexptype:ident) => {{
265                let shared = Some(NativeBorrow::scalar(SEXPTYPE::$sexptype, false));
266                let mutable = Some(NativeBorrow::scalar(SEXPTYPE::$sexptype, true));
267                assert_eq!(<&$t>::NATIVE_BORROW, shared);
268                assert_eq!(<&mut $t>::NATIVE_BORROW, mutable);
269                assert_eq!(<Option<&$t>>::NATIVE_BORROW, shared);
270                assert_eq!(<Option<&mut $t>>::NATIVE_BORROW, mutable);
271                assert_eq!(
272                    <Vec<&mut $t>>::NATIVE_BORROW,
273                    NativeBorrow::in_list(mutable)
274                );
275                assert_eq!(
276                    <Box<[Option<&mut $t>]>>::NATIVE_BORROW,
277                    NativeBorrow::in_list(mutable)
278                );
279                assert_eq!(
280                    <Option<Vec<Vec<&mut $t>>>>::NATIVE_BORROW,
281                    NativeBorrow::in_list(NativeBorrow::in_list(mutable))
282                );
283                let slice = Some(NativeBorrow::slice(SEXPTYPE::$sexptype, true));
284                assert_eq!(<&mut [$t]>::NATIVE_BORROW, slice);
285                assert_eq!(
286                    <Box<[Option<&mut [$t]>]>>::NATIVE_BORROW,
287                    NativeBorrow::in_list(slice)
288                );
289                assert_eq!(<Vec<$t>>::NATIVE_BORROW, None);
290                assert_eq!(<Box<[$t]>>::NATIVE_BORROW, None);
291            }};
292        }
293        check!(i32, INTSXP);
294        check!(f64, REALSXP);
295        check!(u8, RAWSXP);
296        check!(crate::RLogical, LGLSXP);
297        check!(crate::Rcomplex, CPLXSXP);
298        type HiddenBorrow = Option<Vec<Vec<&'static mut i32>>>;
299        assert!(HiddenBorrow::NATIVE_BORROW.is_some());
300    }
301
302    #[test]
303    fn forwarding_wrappers_preserve_native_borrow_metadata() {
304        type Leaf = &'static mut i32;
305        let scalar = Leaf::NATIVE_BORROW;
306        let list = NativeBorrow::in_list(scalar);
307        assert_eq!(<Result<Leaf, ()>>::NATIVE_BORROW, scalar);
308        assert_eq!(<crate::Missing<Leaf>>::NATIVE_BORROW, scalar);
309        assert_eq!(
310            <std::collections::HashMap<String, Leaf>>::NATIVE_BORROW,
311            list
312        );
313        assert_eq!(
314            <Option<std::collections::BTreeMap<String, Leaf>>>::NATIVE_BORROW,
315            list
316        );
317        assert_eq!(
318            <Vec<std::collections::HashMap<String, Leaf>>>::NATIVE_BORROW,
319            NativeBorrow::in_list(list)
320        );
321        assert_eq!(
322            <std::borrow::Cow<'static, [i32]> as TryFromSexp>::NATIVE_BORROW,
323            None
324        );
325        assert_eq!(
326            <std::borrow::Cow<'_, [i32]> as crate::TryFromRoot<'_>>::NATIVE_BORROW,
327            <&[i32]>::NATIVE_BORROW
328        );
329        assert_eq!(
330            <crate::RCow<'static, i32>>::NATIVE_BORROW,
331            <&[i32]>::NATIVE_BORROW
332        );
333        #[cfg(feature = "indexmap")]
334        assert_eq!(<indexmap::IndexMap<String, Leaf>>::NATIVE_BORROW, list);
335    }
336
337    #[test]
338    fn custom_reference_conversions_default_to_no_native_borrow() {
339        struct RLogical;
340        impl TryFromSexp for &RLogical {
341            type Error = ();
342            fn try_from_sexp(_: SEXP) -> Result<Self, Self::Error> {
343                unreachable!()
344            }
345        }
346        assert_eq!(<&RLogical>::NATIVE_BORROW, None);
347        assert_eq!(<String>::NATIVE_BORROW, None);
348    }
349
350    #[test]
351    fn metadata_avoids_unneeded_walks_and_preserves_length_rules() {
352        let shared = NativeBorrow::scalar(SEXPTYPE::INTSXP, false);
353        let mutable = NativeBorrow::scalar(SEXPTYPE::INTSXP, true);
354        assert!(!needs_check(&[]));
355        assert!(!needs_check(&[None, Some(shared), Some(shared)]));
356        assert!(!needs_check(&[Some(mutable)]));
357        assert!(!needs_check(&[
358            Some(mutable),
359            Some(NativeBorrow::slice(SEXPTYPE::REALSXP, true))
360        ]));
361        assert!(needs_check(&[Some(mutable), Some(shared)]));
362        assert!(needs_check(&[NativeBorrow::in_list(Some(mutable))]));
363        assert!(mutable.accepts_length(1));
364        assert!(!mutable.accepts_length(0));
365        assert!(!mutable.accepts_length(2));
366        assert!(NativeBorrow::slice(SEXPTYPE::INTSXP, true).accepts_length(2));
367        assert!(!NativeBorrow::slice(SEXPTYPE::INTSXP, true).accepts_length(0));
368    }
369
370    #[test]
371    fn tuple_metadata_keeps_field_shapes_and_owned_positions() {
372        type Pair = (&'static mut i32, &'static i32);
373        assert_eq!(
374            Pair::NATIVE_BORROW,
375            NativeBorrow::tuple(&[<&mut i32>::NATIVE_BORROW, <&i32>::NATIVE_BORROW])
376        );
377        assert!(needs_check(&[Pair::NATIVE_BORROW]));
378        assert_eq!(<(i32, String)>::NATIVE_BORROW, None);
379        assert!(!needs_check(&[<(i32, &mut i32)>::NATIVE_BORROW]));
380        assert!(!needs_check(&[<(&i32, &i32)>::NATIVE_BORROW]));
381        assert!(!needs_check(&[<(&mut i32, &f64)>::NATIVE_BORROW]));
382        assert!(needs_check(&[<((i32, &mut i32), (&i32,))>::NATIVE_BORROW]));
383        assert!(needs_check(&[
384            <(&mut [i32], String)>::NATIVE_BORROW,
385            <&[i32]>::NATIVE_BORROW,
386        ]));
387        assert!(needs_check(&[<(Vec<&mut i32>,)>::NATIVE_BORROW,]));
388        assert!(needs_check(&[NativeBorrow::in_list(Pair::NATIVE_BORROW)]));
389    }
390}