miniextendr_api/externalptr/sidecar.rs
1//! Typed sidecar fields: [`Sidecar<T>`] keeps a Rust-typed value in the
2//! external pointer's `prot` list.
3//!
4//! A Rust struct cannot root an R value: the GC doesn't trace Rust memory, so
5//! a `SEXP` stored in a struct field is freed as soon as R drops its own
6//! references. A `Sidecar<T>` field holds no data of its own once the struct
7//! is wrapped. Its value lives in the external pointer's protection list,
8//! after the type ID and the user slot, so the pointer roots it for as long as
9//! the pointer is reachable, and `saveRDS()` writes it with the pointer. The
10//! struct holds nothing that points back at its pointer: the value is read
11//! and written through the `ExternalPtr<T>` handle, which keeps the pointer
12//! alive for as long as it exists (#1856).
13//!
14//! The type is named in one place (this module) and re-exported from
15//! [`crate::externalptr`]; its final name is #1857.
16
17use std::any::{Any, TypeId};
18use std::cell::{Ref, RefCell, RefMut};
19use std::collections::HashMap;
20use std::fmt;
21use std::marker::PhantomData;
22use std::sync::Arc;
23
24use super::{
25 NO_HANDLE_CLASS, PROT_TYPE_ID_INDEX, PROT_VEC_LEN, TypedExternal, receiver_class,
26 refuse_restored, unwrap_class_handle,
27};
28use crate::condition::{ConditionCall, RCondition};
29use crate::from_r::TryFromSexp;
30use crate::into_r::IntoR;
31use crate::sys::{R_ExternalPtrAddr, R_ExternalPtrProtected};
32use crate::{R_xlen_t, SEXP, SEXPTYPE, SexpExt};
33
34// region: Sidecar<T>
35
36/// A sidecar field whose value R roots: it lives in the external pointer's
37/// protection list, typed as `T` on the Rust side.
38///
39/// Declare it as `#[r_data] pub keys: Sidecar<Vec<i32>>` on a
40/// `#[derive(ExternalPtr)]` struct. The derive generates accessors as
41/// associated functions of the struct, with the field's visibility. They take
42/// an owner handle, which keeps the pointer alive, not the struct:
43///
44/// | `#[r_data(...)]` | Rust accessors |
45/// |---|---|
46/// | `ref` | `fn keys(ptr: &impl SidecarOwner<Self>) -> T` |
47/// | `mut` | `fn set_keys(ptr: &mut impl SidecarOwnerMut<Self>, value: T)` |
48/// | `ref, mut`, or a bare `#[r_data]` | both |
49///
50/// Rust code sees no `SEXP`s, slot indices or other R plumbing: a plain
51/// `fn new() -> Self` constructor fills the field with [`Sidecar::new`], and
52/// a method that needs the value takes a view as its receiver
53/// (`self: ExternalPtrRef<'_, Self>` / `mut self: ExternalPtrMut<'_, Self>`)
54/// and calls `Self::keys(&self)` / `Self::set_keys(&mut self, v)`; other Rust
55/// code writes `Engine::keys(&ptr)`. The R side keeps its own accessors whatever the
56/// option: for a `pub` field, `Type_get_keys(x)` / `Type_set_keys(x, value)`,
57/// an R6 active binding (`r6(r_data_accessors)`), an S7 property
58/// (`s7(r_data_accessors)`), or `x$keys` / `x$keys <- value` on an S3, S4 or
59/// env class (`s3(r_data_accessors)` & co.). The R setter validates the new
60/// value with `TryFromSexp::<T>` and stores the value R gave.
61///
62/// ```ignore
63/// #[derive(ExternalPtr)]
64/// #[externalptr(r6)]
65/// pub struct Engine {
66/// n: i32,
67/// #[r_data] _r: RSidecar,
68/// #[r_data(ref, mut)] pub keys: Sidecar<Vec<i32>>,
69/// #[r_data(ref)] pub labels: Sidecar<Vec<String>>,
70/// #[r_data(ref, mut)] cache: Sidecar<Option<List>>,
71/// }
72///
73/// #[miniextendr(r6(r_data_accessors))]
74/// impl Engine {
75/// pub fn new(n: i32) -> Self {
76/// Engine {
77/// n,
78/// _r: RSidecar,
79/// keys: Sidecar::new((1..=n).collect()),
80/// labels: Sidecar::new(vec![]),
81/// cache: Sidecar::default(),
82/// }
83/// }
84/// pub fn key_count(self: ExternalPtrRef<'_, Self>) -> usize { Self::keys(&self).len() }
85/// pub fn remember(mut self: ExternalPtrMut<'_, Self>, value: List) {
86/// Self::set_cache(&mut self, Some(value))
87/// }
88/// }
89///
90/// // From any other Rust code holding the handle:
91/// let mut ptr = ExternalPtr::new(Engine::new(3));
92/// Engine::set_keys(&mut ptr, vec![9]);
93/// assert_eq!(Engine::keys(&ptr), vec![9]);
94/// ```
95///
96/// The accessors take [`super::ExternalPtrRef`] / [`super::ExternalPtrMut`]
97/// views (receivers, arguments, `borrow()` / `borrow_mut()`) or an
98/// `ExternalPtr`, which they borrow for the call: shared for the getter,
99/// exclusive for the setter. A plain `&self` receiver has no route back to
100/// an R owner.
101///
102/// # Values
103///
104/// - `T: IntoR + TryFromSexp`, with `T::Error: Display` for the getter's panic
105/// message.
106/// - A getter converts the slot with `TryFromSexp`, so a converting type
107/// (`Vec<i32>`, `String`) copies on every read. A conversion failure is a
108/// panic naming the field and the type; the framework turns it into an R
109/// error.
110/// - `Sidecar<SEXP>`, `Sidecar<List>` and `Sidecar<DataFrame>` keep an
111/// arbitrary R object unconverted. The getter returns a view that the slot
112/// roots only until the next write to that field; don't hold it across one.
113/// - [`Sidecar::new`] and [`Sidecar::replace`] convert their value with
114/// `IntoR` at once and root the R object it gives until a slot holds it
115/// (#2216). A fresh `SEXP` or list passed to them is safe from that moment;
116/// it needs its own protection only before the call.
117///
118/// # Lifecycle
119///
120/// - **Pending.** [`Sidecar::new(v)`](Sidecar::new) and [`Default`]
121/// (`Sidecar::new(T::default())`) convert `v` to its R object and hold it
122/// as a pending value in the struct, rooted in a main-thread pool until a
123/// slot holds it or the last copy drops. That is the only state a `Sidecar`
124/// has: before the struct is wrapped, after `*ptr = Engine::new(..)`, after
125/// `ExternalPtr::into_inner`. They run on R's main thread, or on the
126/// managed worker, which hands the conversion to the main thread as
127/// `ExternalPtr::new` does; any other thread panics. Like an owning
128/// `ExternalPtr`, the last copy of a pending value must be dropped on R's
129/// main thread or its managed worker.
130/// - **Flushed when wrapped.** `ExternalPtr::new` and the trait ABI's `mx_wrap`
131/// move every pending value into the pointer's protection list. From then
132/// on the value lives in R only, and the field is empty.
133/// - **Flushed on every access to the field.** The handle getter and setter,
134/// and the R getter and setter, first move that field's pending value into
135/// its slot (the setters drop it: the write supersedes it). So
136/// `*ptr.borrow_mut() = Engine::new(..)` or
137/// `ptr.borrow_mut().keys = Sidecar::new(v)` takes effect at the next
138/// access from either side. A
139/// `saveRDS()` before any access writes the old slot value.
140/// - **Loaded when the value leaves its pointer.** `ExternalPtr::into_inner`
141/// and `into_raw` read each slot of the pointer being consumed back into its
142/// field as a pending value (unless one is pending already), rooted as a
143/// new one is, so wrapping the value again keeps the values.
144/// - **A `self -> Self` method** leaves the values in the pointer across the
145/// call; the write-back flushes only what the method set with
146/// `Sidecar::new`. `Self { n: .., ..self }` keeps the values,
147/// `Self::new(..)` replaces them.
148/// - **Clone.** `Sidecar::clone` shares the pending R object and its root,
149/// which is none once the struct is wrapped. `ExternalPtr::clone` and
150/// `clone_from` copy the slot values as R objects, so the two pointers
151/// share them until one side writes the field: every write replaces the
152/// slot, and R copies a shared value before modifying it in place. A struct
153/// cloned outside its handle carries no sidecar values; clone the handle.
154/// - **`*ptr = new` and `mem::swap(&mut *a, &mut *b)`** are plain Rust
155/// moves. The sidecar values stay in each pointer's protection list; only
156/// pending values travel with the struct.
157/// - **After `readRDS()`** there is no Rust value. The standalone R
158/// accessors (`Type_get_f()` / `Type_set_f()`), the R6 active bindings and
159/// the S7 properties read and write the protection list directly, checked
160/// by the type ID stored with it. The `$` / `[[` / `$<-` / `[[<-` field
161/// methods of `s3(r_data_accessors)` & co. refuse the restored pointer
162/// instead, with the classed restored-object errors: they pass
163/// `require_live` to the same accessor (#1891).
164///
165/// Use the accessors on R's main thread only. Off the main thread an
166/// accessor panics with a clear message before it touches R.
167///
168/// # A save names the values by their R names
169///
170/// A saved object (`saveRDS()`, `serialize()`) stores one slot per `Sidecar`
171/// field, named after the field's R name: the `#[r_data(name = "..")]` value,
172/// else the Rust field name (#2187, #2215). After a reload, the standalone R
173/// accessors, the R6 bindings, the S7 properties and a rebuild hook
174/// (`#[externalptr(rebuild = path)]`, through [`super::Saved`]) find each
175/// field's slot by that name. So the R name is the save format:
176///
177/// - Reordering, adding or removing `Sidecar` fields keeps earlier saves
178/// readable. A field the save has no slot for raises the classed
179/// [`RESTORED_MISSING_SLOT_CLASS`](super::RESTORED_MISSING_SLOT_CLASS)
180/// error (or reads as `None` through a hook's `Self::try_<field>(saved)`).
181/// - Renaming the Rust field keeps them readable too, as long as the R name
182/// stays: a field without `name = ..` keeps its old ident as
183/// `#[r_data(name = "old")]`.
184/// - Renaming the R name orphans the field's slot in every earlier save: the
185/// field reads as one the save lacks.
186/// - Retyping a field keeps its name, so an earlier save's slot is read as the
187/// new type; a getter that can't convert it panics naming the field.
188///
189/// A save from another version of the crate (the `version` in its
190/// `Cargo.toml`, which the stored type ID records) is refused with the classed
191/// `miniextendr_restored_other_version` error (#1872), unless the type
192/// rebuilds it (`rebuild = path, any_version`).
193///
194/// # Methods that need sidecar values take a view
195///
196/// A `&self` / `&mut self` method sees the struct, which holds no reference
197/// to its pointer, so it can't reach the values. Take a view instead:
198/// `self: ExternalPtrRef<'_, Self>` to read, `mut self: ExternalPtrMut<'_, Self>`
199/// to write (`Deref` still gives the struct fields). The same holds for a
200/// struct moved out of its pointer through `&mut` (`mem::replace`,
201/// `mem::take`): it carries nothing to follow, so nothing can read a freed R
202/// object (#1856). A trait method takes its owner as
203/// `self: ExternalPtrRef<'_, Self>` or `self: ExternalPtrMut<'_, Self>`
204/// ([`ExternalPtrRef`](crate::ExternalPtrRef)), over an `ExternalPtr` or a
205/// trait-ABI object, and passes it to the same accessors. A `&self` trait
206/// method, such as one of a standard trait with a fixed signature, and the
207/// high-level ALTREP data traits receive only the struct (#1880).
208/// ALTREP derives also accept `#[r_data] Sidecar<T>` fields: with
209/// `#[altrep(manual, no_lowlevel)]`, low-level callbacks recover the handle
210/// through [`crate::altrep_data1_as_unchecked`] and call the associated
211/// accessors. Those derives emit no R class field wrappers.
212///
213/// A plain `SEXP` field under `#[r_data]` is a compile error: nothing would
214/// root it. Use `Sidecar<SEXP>`.
215pub struct Sidecar<T> {
216 /// The value the field holds while it is not in a pointer's slot.
217 pending: RefCell<Option<Pending>>,
218 /// The field's type: its accessors convert the slot to it, and the
219 /// struct's auto traits follow it as if it held a `T`.
220 _value: PhantomData<T>,
221}
222
223/// A value on its way into a slot: its R object, rooted from the moment the
224/// `Sidecar` holds it until a slot owns the GC edge (#2216).
225#[derive(Clone)]
226struct Pending {
227 sexp: SEXP,
228 root: Arc<PendingRoot>,
229 /// Made by [`Sidecar::replace`]: in a struct a rebuild hook returns, the
230 /// value replaces the slot the save kept (#2187).
231 replace: bool,
232}
233
234/// Only the pool key crosses threads, as with an owning `ExternalPtr`.
235/// Cloning a pending value needs no R call; the last copy releases the root.
236struct PendingRoot(crate::protect_pool::ProtectKey);
237
238impl Drop for PendingRoot {
239 fn drop(&mut self) {
240 let key = self.0;
241 crate::worker::with_r_thread(move || super::unroot_owned(key));
242 }
243}
244
245impl<T: IntoR + 'static> Sidecar<T> {
246 /// A sidecar field holding `value`, pending until the struct is wrapped
247 /// in an `ExternalPtr`.
248 ///
249 /// Converts `value` with `IntoR` and roots the R object until a slot
250 /// holds it, so a fresh R value is safe from here on.
251 ///
252 /// # Panics
253 ///
254 /// Off R's main thread and its managed worker, before converting; or
255 /// when the conversion fails.
256 pub fn new(value: T) -> Self {
257 Self::pending(value, false)
258 }
259
260 /// A sidecar field holding `value`, which replaces the slot a save kept
261 /// when a rebuild hook (`#[externalptr(rebuild = path)]`) returns it.
262 ///
263 /// A rebuilt object keeps each slot its save has: the hook's
264 /// [`Sidecar::new`] values are placeholders for those fields, and fill only
265 /// the fields the save lacks. A hook that recomputes a field's value, and
266 /// wants the object to hold the new value rather than the saved one,
267 /// returns it as `Sidecar::replace(value)`. Anywhere else it is
268 /// `Sidecar::new(value)`. Converts and roots `value` as
269 /// [`Sidecar::new`] does, with the same panics.
270 pub fn replace(value: T) -> Self {
271 Self::pending(value, true)
272 }
273
274 /// Converts `value` and roots its R object on R's main thread: inline
275 /// there, through the main thread from the managed worker (#2216).
276 fn pending(value: T, replace: bool) -> Self {
277 assert!(
278 crate::worker::is_r_main_thread() || crate::worker::has_worker_context(),
279 "`Sidecar::{}` converts its value on R's main thread: call it there or on \
280 the managed worker, not from a thread the package spawned",
281 if replace { "replace" } else { "new" },
282 );
283 // `root_owned` roots the fresh object across the pool's growth.
284 let (sexp, key) = crate::worker::with_r_thread(move || {
285 let sexp = value.into_sexp();
286 (sexp, super::root_owned(sexp))
287 });
288 Self {
289 pending: RefCell::new(Some(Pending {
290 sexp,
291 root: Arc::new(PendingRoot(key)),
292 replace,
293 })),
294 _value: PhantomData,
295 }
296 }
297}
298
299impl<T> Sidecar<T> {
300 /// Whether the pending value was made by [`Sidecar::replace`].
301 fn replaces_saved(&self) -> bool {
302 self.pending
303 .borrow()
304 .as_ref()
305 .is_some_and(|pending| pending.replace)
306 }
307
308 /// Drops a pending value, which a write to the slot supersedes.
309 fn discard_pending(&self) {
310 *self.pending.borrow_mut() = None;
311 }
312
313 /// Moves a pending value into slot `index` of `prot`, if there is one.
314 /// On R's main thread; `set_vector_elt` allocates nothing.
315 fn flush_into(&self, prot: SEXP, index: usize) {
316 let Some(Pending { sexp, root, .. }) = self.pending.borrow_mut().take() else {
317 return;
318 };
319 prot.set_vector_elt(prot_index(index), sexp);
320 // The slot owns the GC edge now.
321 drop(root);
322 }
323
324 /// Reads slot `index` of `prot` back into the field as its pending value,
325 /// unless one is pending already. On R's main thread.
326 fn load_from(&self, prot: SEXP, index: usize) {
327 if self.pending.borrow().is_some() {
328 return;
329 }
330 // The old owner still roots the slot while the pool grows. Root the
331 // slot itself, not the old protection list, so independent pending
332 // fields do not retain one another's values.
333 let sexp = prot.vector_elt(prot_index(index));
334 *self.pending.borrow_mut() = Some(Pending {
335 sexp,
336 root: Arc::new(PendingRoot(super::root_owned(sexp))),
337 replace: false,
338 });
339 }
340}
341
342impl<T> Clone for Sidecar<T> {
343 /// A `Sidecar` sharing this field's pending R object, and its root, if
344 /// any. The value in a pointer's slot is not copied: clone the
345 /// `ExternalPtr`.
346 fn clone(&self) -> Self {
347 Self {
348 pending: RefCell::new(self.pending.borrow().clone()),
349 _value: PhantomData,
350 }
351 }
352}
353
354impl<T: Default + IntoR + 'static> Default for Sidecar<T> {
355 /// `Sidecar::new(T::default())`.
356 fn default() -> Self {
357 Self::new(T::default())
358 }
359}
360
361impl<T> fmt::Debug for Sidecar<T> {
362 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
363 f.debug_struct("Sidecar")
364 .field("pending", &self.pending.borrow().is_some())
365 .finish()
366 }
367}
368
369/// The element of `prot` at `at`, the slot of `Sidecar` field `name`,
370/// converted to `T`.
371fn convert_slot<T: TryFromSexp>(prot: SEXP, at: R_xlen_t, name: &str, owner_type: &str) -> T
372where
373 <T as TryFromSexp>::Error: fmt::Display,
374{
375 let slot = prot.vector_elt(at);
376 match T::try_from_sexp(slot) {
377 Ok(value) => value,
378 Err(err) => panic!(
379 "sidecar field `{name}` of `{owner_type}` holds an R value that does not convert to `{}`: {err}",
380 std::any::type_name::<T>(),
381 ),
382 }
383}
384
385/// Panics unless this is R's main thread. No R API: the message names the
386/// field and the owning type.
387fn assert_main_thread(name: &str, owner_type: &str) {
388 if !crate::worker::is_r_main_thread() {
389 panic!(
390 "sidecar field `{name}` of `{owner_type}`: its accessors run on R's main thread only; \
391 this call came from another thread (a `worker` method, or a thread the \
392 package spawned)",
393 );
394 }
395}
396
397// endregion
398
399// region: The handle's view of the fields
400
401/// A `Sidecar<T>` field, as the handle sees it through the
402/// `TypedExternal::__mx_visit_sidecars` hook the derive emits. Every method
403/// runs on R's main thread.
404#[doc(hidden)]
405pub trait SidecarField {
406 /// Moves a pending value into slot `index` of `prot`.
407 fn __mx_flush(&self, prot: SEXP, index: usize);
408 /// Reads slot `index` of `prot` back into the field as its pending value,
409 /// unless one is pending already.
410 fn __mx_load(&self, prot: SEXP, index: usize);
411 /// Drops a pending value, which a write to the slot supersedes.
412 fn __mx_discard_pending(&self);
413 /// Whether the pending value was made by [`Sidecar::replace`].
414 fn __mx_replaces_saved(&self) -> bool;
415}
416
417impl<T> SidecarField for Sidecar<T> {
418 fn __mx_flush(&self, prot: SEXP, index: usize) {
419 self.flush_into(prot, index);
420 }
421
422 fn __mx_load(&self, prot: SEXP, index: usize) {
423 self.load_from(prot, index);
424 }
425
426 fn __mx_discard_pending(&self) {
427 self.discard_pending();
428 }
429
430 fn __mx_replaces_saved(&self) -> bool {
431 self.replaces_saved()
432 }
433}
434
435/// Moves every pending value of `value`'s `Sidecar` fields into its slot of
436/// `prot`, the protection list of the pointer holding `value`. On R's main
437/// thread.
438pub(crate) fn flush_sidecars<T: TypedExternal>(value: &T, prot: SEXP) {
439 value.__mx_visit_sidecars(&mut |index, _, field| field.__mx_flush(prot, index));
440}
441
442/// Reads every slot of `prot`, the protection list of the pointer `value` is
443/// leaving, back into its `Sidecar` field as a pending value (unless one is
444/// pending already). On R's main thread.
445pub(crate) fn load_sidecars<T: TypedExternal>(value: &T, prot: SEXP) {
446 value.__mx_visit_sidecars(&mut |index, _, field| field.__mx_load(prot, index));
447}
448
449/// Drops every pending value of `value`'s `Sidecar` fields.
450pub(crate) fn discard_pending_sidecars<T: TypedExternal>(value: &T) {
451 value.__mx_visit_sidecars(&mut |_, _, field| field.__mx_discard_pending());
452}
453
454/// Flushes a freshly wrapped value's pending values into its pointer's
455/// protection list: the three construction sites and
456/// `restore_after_consuming` call this once the `EXTPTRSXP` exists.
457///
458/// # Safety
459///
460/// On R's main thread; `any_raw` must own a `RefCell<T>`, and `owner` must be
461/// the live `EXTPTRSXP` holding it, with a `prot` list built by `ExternalPtr`.
462pub(super) unsafe fn init_sidecars<T: TypedExternal>(any_raw: *mut Box<dyn Any>, owner: SEXP) {
463 let value = unsafe { &*any_raw }
464 .downcast_ref::<RefCell<T>>()
465 .expect("ExternalPtr stores a T at construction")
466 .borrow();
467 let prot = unsafe { R_ExternalPtrProtected(owner) };
468 unsafe { name_slots::<T>(prot) };
469 flush_sidecars(&*value, prot);
470}
471
472/// Initializes the slots of a trait-ABI owner using the ordinary sidecar
473/// layout and visitation hook. The Rust allocation remains erased storage;
474/// it must never be wrapped as `ExternalPtr<T>` (which expects `Any` storage).
475///
476/// # Safety
477/// On R's main thread inside an R unwind boundary. `owner` must be the
478/// caller-rooted external pointer owning `value`, with its finalizer already
479/// registered.
480pub(crate) unsafe fn init_erased_sidecars<T: TypedExternal>(value: &T, owner: SEXP) {
481 unsafe {
482 let type_id = super::type_id_symbol_unchecked::<T>();
483 let prot =
484 crate::sys::Rf_allocVector_unchecked(SEXPTYPE::VECSXP, super::prot_vec_len::<T>());
485 // Attach before the next allocation (the slot names' attribute cell):
486 // the owner roots `prot` from here on.
487 crate::sys::R_SetExternalPtrProtected_unchecked(owner, prot);
488 prot.set_vector_elt_unchecked(PROT_TYPE_ID_INDEX, type_id);
489 name_slots::<T>(prot);
490 flush_sidecars(value, prot);
491 }
492}
493
494/// Copies every `Sidecar` slot of the pointer `source` into the pointer
495/// `target`: the two share the R values until one side writes a field.
496///
497/// # Safety
498///
499/// On R's main thread; both must be live `EXTPTRSXP`s built by `ExternalPtr`
500/// for a `T`. `set_vector_elt` allocates nothing.
501pub(super) unsafe fn copy_sidecar_slots<T: TypedExternal>(source: SEXP, target: SEXP) {
502 let from = unsafe { R_ExternalPtrProtected(source) };
503 let to = unsafe { R_ExternalPtrProtected(target) };
504 for index in 0..T::R_SLOT_COUNT {
505 let at = prot_index(index);
506 to.set_vector_elt(at, from.vector_elt(at));
507 }
508}
509
510/// Reads `T`'s `Sidecar` field `index` of the object `ptr` holds, after
511/// moving the field's pending value into the slot.
512///
513/// Called by the getters `#[derive(ExternalPtr)]` generates
514/// (`T::field(&ptr)`); `field` selects the struct's `Sidecar` field.
515///
516/// # Panics
517///
518/// Off R's main thread, naming the field, before touching R; or when the
519/// slot does not convert to `T`.
520#[doc(hidden)]
521pub fn sidecar_get<O, T>(
522 ptr: &impl super::SidecarOwner<O>,
523 index: usize,
524 name: &'static str,
525 field: fn(&O) -> &Sidecar<T>,
526) -> T
527where
528 O: TypedExternal,
529 T: IntoR + TryFromSexp,
530 <T as TryFromSexp>::Error: fmt::Display,
531{
532 assert_main_thread(name, O::TYPE_NAME);
533 // SAFETY (both arms): on R's main thread; the handle's EXTPTRSXP is live
534 // and was built for an `O`.
535 match ptr.__mx_read() {
536 super::SidecarRead::Live(value) => {
537 let prot = unsafe { R_ExternalPtrProtected(super::ExternalPtrRef::as_sexp(&value)) };
538 field(&value).flush_into(prot, index);
539 convert_slot::<T>(prot, prot_index(index), name, O::TYPE_NAME)
540 }
541 // A rebuild hook reads what the save kept, by the field's name;
542 // nothing is pending.
543 super::SidecarRead::Saved(sexp) => {
544 let prot = unsafe { R_ExternalPtrProtected(sexp) };
545 match saved_slot(prot, index, name) {
546 Some(at) => convert_slot::<T>(prot, at, name, O::TYPE_NAME),
547 None => missing_slot::<O>(name),
548 }
549 }
550 }
551}
552
553/// `T`'s `Sidecar` field `index`, named `name`, as the save `saved` kept it,
554/// or `None` when the save has no slot for it: a field added after the
555/// version that saved the object.
556///
557/// Called by the `try_<field>` getters `#[derive(ExternalPtr)]` generates for
558/// a type with a rebuild hook.
559///
560/// # Panics
561///
562/// Off R's main thread, naming the field; or when the slot does not convert
563/// to `T`.
564#[doc(hidden)]
565pub fn sidecar_try_saved<O, T>(
566 saved: &super::Saved<'_, O>,
567 index: usize,
568 name: &'static str,
569) -> Option<T>
570where
571 O: TypedExternal,
572 T: TryFromSexp,
573 <T as TryFromSexp>::Error: fmt::Display,
574{
575 assert_main_thread(name, O::TYPE_NAME);
576 // SAFETY: on R's main thread; `saved` holds a live EXTPTRSXP.
577 let prot = unsafe { R_ExternalPtrProtected(saved.as_sexp()) };
578 saved_slot(prot, index, name).map(|at| convert_slot::<T>(prot, at, name, O::TYPE_NAME))
579}
580
581/// Stores `value` in `T`'s `Sidecar` field `index` of the object `ptr` holds,
582/// dropping the field's pending value, which this write supersedes.
583///
584/// Called by the setters `#[derive(ExternalPtr)]` generates
585/// (`T::set_field(&mut ptr, value)`); `field` selects the struct's `Sidecar`
586/// field.
587///
588/// # Panics
589///
590/// Off R's main thread, naming the field, before touching R.
591#[doc(hidden)]
592pub fn sidecar_set<O, T>(
593 ptr: &mut impl super::SidecarOwnerMut<O>,
594 index: usize,
595 name: &'static str,
596 field: fn(&O) -> &Sidecar<T>,
597 value: T,
598) where
599 O: TypedExternal,
600 T: IntoR,
601{
602 assert_main_thread(name, O::TYPE_NAME);
603 let target = ptr.__mx_write();
604 // SAFETY: as in `sidecar_get`. Storing allocates nothing.
605 let prot = unsafe { R_ExternalPtrProtected(super::ExternalPtrMut::as_sexp(&target)) };
606 field(&target).discard_pending();
607 prot.set_vector_elt(prot_index(index), value.into_sexp());
608}
609
610// endregion
611
612// region: Slot names (#2187)
613
614thread_local! {
615 /// Each type's slot names, preserved for the session and shared by every
616 /// protection list of the type: `""` for the type ID and the user slot,
617 /// then the `Sidecar` fields' R names, in slot order.
618 static SLOT_NAMES: RefCell<HashMap<TypeId, SEXP>> = RefCell::new(HashMap::new());
619}
620
621/// Names `prot`'s slots after `T`'s `Sidecar` fields. A save then keeps the
622/// names, so the version that reads it finds each field by name
623/// ([`saved_slot`]), whatever fields it added, removed or reordered since.
624/// The names vector is the type's own, built once; this allocates only the
625/// attribute's cell.
626///
627/// # Safety
628///
629/// On R's main thread, with `prot` a rooted protection list built for a `T`.
630pub(crate) unsafe fn name_slots<T: TypedExternal>(prot: SEXP) {
631 let cached = SLOT_NAMES.with_borrow(|names| names.get(&TypeId::of::<T>()).copied());
632 let names = cached.unwrap_or_else(|| {
633 // Preserved before it is filled, so the CHARSXPs it receives are
634 // reachable as they are made.
635 let names =
636 unsafe { crate::sys::Rf_allocVector(SEXPTYPE::STRSXP, super::prot_vec_len::<T>()) };
637 unsafe { crate::sys::R_PreserveObject(names) };
638 for (index, name) in T::SIDECAR_NAMES.iter().enumerate() {
639 names.set_string_elt(prot_index(index), SEXP::charsxp(name));
640 }
641 SLOT_NAMES.with_borrow_mut(|cache| cache.insert(TypeId::of::<T>(), names));
642 names
643 });
644 prot.set_names(names);
645}
646
647/// The position in the saved protection list `prot` of `Sidecar` field
648/// `index`, named `name`: found by name when the save carries slot names,
649/// else by position (a save by this version made before slots were named).
650/// `None` when the save has no slot for the field.
651pub(crate) fn saved_slot(prot: SEXP, index: usize, name: &str) -> Option<R_xlen_t> {
652 let names = prot.get_attr(SEXP::names_symbol());
653 if names.type_of() == SEXPTYPE::STRSXP && names.xlength() == prot.xlength() {
654 (PROT_VEC_LEN..names.xlength()).find(|&at| names.string_elt_str(at) == Some(name))
655 } else {
656 Some(prot_index(index)).filter(|&at| at < prot.xlength())
657 }
658}
659
660/// Whether the saved protection list `prot` carries slot names.
661pub(crate) fn has_slot_names(prot: SEXP) -> bool {
662 let names = prot.get_attr(SEXP::names_symbol());
663 names.type_of() == SEXPTYPE::STRSXP && names.xlength() == prot.xlength()
664}
665
666/// Raises the classed error for a read of `Sidecar` field `name` that a save
667/// of an `O` has no slot for.
668fn missing_slot<O: TypedExternal>(name: &str) -> ! {
669 RCondition::Error {
670 message: format!(
671 "the saved `{}` has no `{name}` field: the version that saved it did not have it",
672 O::TYPE_NAME
673 ),
674 class: vec![super::RESTORED_MISSING_SLOT_CLASS.to_owned()],
675 data: Some(vec![(
676 "field".to_owned(),
677 crate::RValue::Character(vec![Some(name.to_owned())]),
678 )]),
679 call: ConditionCall::Inherit,
680 }
681 .raise()
682}
683
684// endregion
685
686// region: The R accessors
687
688/// The protection-list position of `Sidecar` field `index`.
689pub(super) fn prot_index(index: usize) -> R_xlen_t {
690 let index = R_xlen_t::try_from(index).expect("sidecar index exceeds R_xlen_t::MAX");
691 PROT_VEC_LEN + index
692}
693
694/// Whether `sym` is the symbol of `T`'s type ID.
695///
696/// Compares names rather than installing `T`'s symbol, so it allocates nothing.
697fn is_type_id_symbol<T: TypedExternal>(sym: SEXP) -> bool {
698 if sym.type_of() != SEXPTYPE::SYMSXP {
699 return false;
700 }
701 let id = T::TYPE_ID_CSTR
702 .strip_suffix(b"\0")
703 .unwrap_or(T::TYPE_ID_CSTR);
704 let printname = sym.printname();
705 // SAFETY: a CHARSXP's data holds `len` bytes.
706 let name =
707 unsafe { std::slice::from_raw_parts(printname.r_char().cast::<u8>(), printname.len()) };
708 name == id
709}
710
711/// The external pointer an R accessor's receiver `x` carries: `x` itself, or
712/// the pointer an R6 / S4 / S7 handle, an environment or a list holds in
713/// `.ptr`, the shapes an instance method's receiver takes
714/// ([`unwrap_class_handle`]). So `Type_get_f(x)` and the `$` / `[[` field
715/// methods of `r_data_accessors` read a list that carries the handle (#1848).
716/// A bare pointer costs one `TYPEOF` compare; an S4 receiver reads its `ptr`
717/// slot through `methods::slot()`, which allocates. The pointer is reachable
718/// from `x`, which the caller's `.Call()` frame roots.
719///
720/// # Panics
721///
722/// When `x` carries no external pointer, with the classed
723/// [`NO_HANDLE_CLASS`] error ([`refuse_no_handle`]).
724///
725/// # Safety
726///
727/// Must be called from R's main thread with a valid `x`.
728unsafe fn sidecar_receiver<T: TypedExternal>(x: SEXP) -> SEXP {
729 if x.type_of() == SEXPTYPE::EXTPTRSXP {
730 return x;
731 }
732 match unsafe { unwrap_class_handle(x) } {
733 Some(ptr) => ptr,
734 None => refuse_no_handle::<T>(x),
735 }
736}
737
738/// What an R accessor's receiver holds instead of a live `T` (#1920).
739#[derive(Clone, Copy)]
740enum Refused {
741 /// No external pointer at all ([`refuse_no_handle`]).
742 NoPointer,
743 /// A pointer without an address that `T` did not build.
744 NullPointer,
745 /// A live pointer to another type.
746 OtherType,
747}
748
749/// The message of a receiver refusal (#1920): the type's own
750/// ([`TypedExternal::FIELD_ERROR_MESSAGE`]), else one naming `receiver`'s R
751/// class, which is what the user sees (the field methods dispatched on it),
752/// else, for a receiver without a class, one naming the Rust type.
753fn refusal_message<T: TypedExternal>(receiver: SEXP, refused: Refused) -> String {
754 if let Some(message) = T::FIELD_ERROR_MESSAGE {
755 return message.to_owned();
756 }
757 match (receiver_class(receiver), refused) {
758 (Some(class), Refused::NoPointer) => format!("this `{class}` object holds no pointer"),
759 (Some(class), Refused::NullPointer) => {
760 format!("this `{class}` object holds a null pointer")
761 }
762 (Some(class), Refused::OtherType) => {
763 format!("this `{class}` object holds a pointer of another type")
764 }
765 (None, Refused::NoPointer) => format!(
766 "expected ExternalPtr<{}>, got a non-external-pointer object",
767 T::TYPE_NAME
768 ),
769 (None, Refused::NullPointer) => format!(
770 "expected ExternalPtr<{}>, got a null external pointer",
771 T::TYPE_NAME
772 ),
773 (None, Refused::OtherType) => format!("expected ExternalPtr<{}>", T::TYPE_NAME),
774 }
775}
776
777/// Raises the error for a receiver that carries no external pointer (#1901):
778/// [`NO_HANDLE_CLASS`], with the type's field-error classes
779/// ([`TypedExternal::FIELD_ERROR_CLASS`], #1899) in front, and for a list or
780/// an environment, the shape an earlier release of a package may have
781/// written to disk, its restored classes
782/// ([`TypedExternal::RESTORED_ERROR_CLASS`]) in front of those: a package
783/// refuses such a save as it refuses a restored pointer, with its restored
784/// message ([`TypedExternal::RESTORED_ERROR_MESSAGE`]) when it sets one.
785/// Another receiver (an atomic vector given the class) was never saved, so
786/// it gets neither (#1919).
787#[cold]
788fn refuse_no_handle<T: TypedExternal>(receiver: SEXP) -> ! {
789 let saved_shape = matches!(receiver.type_of(), SEXPTYPE::VECSXP | SEXPTYPE::ENVSXP);
790 let restored = if saved_shape {
791 T::RESTORED_ERROR_CLASS
792 } else {
793 &[]
794 };
795 let message = match T::RESTORED_ERROR_MESSAGE {
796 Some(message) if saved_shape => message.to_owned(),
797 _ => refusal_message::<T>(receiver, Refused::NoPointer),
798 };
799 std::panic::panic_any(RCondition::Error {
800 message,
801 class: no_handle_classes(restored, T::FIELD_ERROR_CLASS),
802 data: None,
803 call: ConditionCall::Inherit,
804 })
805}
806
807/// The classes of [`refuse_no_handle`]: `restored`, then `field_error`, then
808/// [`NO_HANDLE_CLASS`], each class once at its last place. The two lists
809/// usually end in the package's one parent class (`["pkg_saved",
810/// "pkg_error"]` and `["pkg_field", "pkg_error"]`), which then follows both
811/// specific classes, as a class vector goes from specific to general.
812fn no_handle_classes(restored: &[&str], field_error: &[&str]) -> Vec<String> {
813 let all: Vec<&str> = restored
814 .iter()
815 .chain(field_error)
816 .chain(&[NO_HANDLE_CLASS])
817 .copied()
818 .collect();
819 all.iter()
820 .enumerate()
821 .filter(|&(i, c)| !all[i + 1..].contains(c))
822 .map(|(_, c)| (*c).to_owned())
823 .collect()
824}
825
826/// Raises the error for a receiver whose pointer is not a `T` (a pointer of
827/// another type, or one without an address that `T` did not build), worded
828/// by [`refusal_message`], with the type's field-error classes
829/// ([`TypedExternal::FIELD_ERROR_CLASS`], #1899) in front. A type without
830/// them gets the plain panic.
831#[cold]
832fn refuse_other_type<T: TypedExternal>(receiver: SEXP, refused: Refused) -> ! {
833 let message = refusal_message::<T>(receiver, refused);
834 if T::FIELD_ERROR_CLASS.is_empty() {
835 panic!("{message}");
836 }
837 std::panic::panic_any(RCondition::Error {
838 message,
839 class: T::FIELD_ERROR_CLASS
840 .iter()
841 .map(|c| (*c).to_owned())
842 .collect(),
843 data: None,
844 call: ConditionCall::Inherit,
845 })
846}
847
848/// Reads the `require_live` flag the R accessors' `.Call()`s pass: `TRUE`
849/// from the `$` / `[[` / `$<-` / `[[<-` field methods, which refuse a
850/// restored pointer, `FALSE` from the standalone accessors, the R6 bindings
851/// and the S7 properties, which read a save (#1891). Anything but `TRUE`
852/// reads as `false`.
853#[doc(hidden)]
854pub fn sidecar_require_live(flag: SEXP) -> bool {
855 flag.as_logical() == Some(true)
856}
857
858/// The `prot` list of the external pointer `x` carries
859/// ([`sidecar_receiver`]), after checking that it points to a `T`, and the
860/// live `T`'s `RefCell` when it still has an address. Allocates nothing for a
861/// pointer, a list or an environment receiver.
862///
863/// A live pointer is checked by `Any::downcast`. A pointer whose address is
864/// NULL (restored from a saved session by `readRDS()` / `unserialize()`)
865/// still holds its sidecar values, so without `require_live` it is accepted
866/// when its stored type ID is `T`'s; one another version of the crate saved
867/// is refused with the classed
868/// [`RESTORED_OTHER_VERSION_CLASS`](super::RESTORED_OTHER_VERSION_CLASS)
869/// error. With `require_live` (the field methods of `r_data_accessors`)
870/// every restored pointer is refused through [`refuse_restored`], so a save
871/// by this version gets [`RESTORED_NO_VALUE_CLASS`](super::RESTORED_NO_VALUE_CLASS).
872///
873/// # Panics
874///
875/// When `x` carries no external pointer to a `T`, or, with `require_live`,
876/// no live one.
877///
878/// # Safety
879///
880/// Must be called from R's main thread with a valid `x`.
881unsafe fn checked_prot<'a, T: TypedExternal>(
882 receiver: SEXP,
883 require_live: bool,
884) -> (SEXP, Option<&'a RefCell<T>>) {
885 let x = unsafe { sidecar_receiver::<T>(receiver) };
886 let mut any_raw = unsafe { R_ExternalPtrAddr(x) }.cast::<Box<dyn Any>>();
887 // A reader that needs the value rebuilds a restored pointer first, when
888 // the type has a hook; a `Sidecar` read takes the saved slot as it is.
889 // Another version's save, under `any_version`, is rebuilt by any reader:
890 // the rebuild lays its slots out as this version's (#2187).
891 if any_raw.is_null()
892 && (require_live
893 || T::REBUILDS_ANY_VERSION
894 && matches!(
895 unsafe { super::classify_null_address::<T>(x) },
896 super::NullAddress::OtherVersion { .. }
897 ))
898 && unsafe { super::rebuild_restored::<T>(x, receiver) }
899 {
900 any_raw = unsafe { R_ExternalPtrAddr(x) }.cast::<Box<dyn Any>>();
901 }
902 // Read after a rebuild, which gives the pointer a new list (#2187).
903 let prot = unsafe { R_ExternalPtrProtected(x) };
904 let live = if any_raw.is_null() {
905 if require_live
906 || !unsafe { super::stored_type_id_symbol(x) }.is_some_and(is_type_id_symbol::<T>)
907 {
908 // A save by this version (`require_live`) or by another version
909 // raises its own error here.
910 unsafe { refuse_restored::<T>(x, receiver) };
911 refuse_other_type::<T>(receiver, Refused::NullPointer);
912 }
913 None
914 } else {
915 if unsafe { super::stored_type_id_symbol(x) }.is_none() {
916 refuse_other_type::<T>(receiver, Refused::OtherType);
917 }
918 match unsafe { &*any_raw }.downcast_ref::<RefCell<T>>() {
919 Some(cell) => Some(cell),
920 None => refuse_other_type::<T>(receiver, Refused::OtherType),
921 }
922 };
923 // A save read by name may hold fewer slots than the type has fields:
924 // its reads look the field up ([`saved_slot`]).
925 if (live.is_some() || !has_slot_names(prot))
926 && !usize::try_from(prot_index(T::R_SLOT_COUNT)).is_ok_and(|len| len <= prot.len())
927 {
928 panic!(
929 "ExternalPtr<{}> has no sidecar slots: its protection list was not built by `ExternalPtr`",
930 T::TYPE_NAME
931 );
932 }
933 (prot, live)
934}
935
936/// The live `T` of the external pointer `x` carries ([`sidecar_receiver`]),
937/// borrowed shared, for the R getters of a struct field (a field that is not
938/// a `Sidecar`). A pointer without an address is refused: with the classed
939/// restored-object errors when `T` built it ([`refuse_restored`]), else as a
940/// null pointer ([`refusal_message`]).
941///
942/// # Panics
943///
944/// When `x` carries no live external pointer to a `T`, or with the
945/// `miniextendr_borrow_conflict` R error while a running call holds the value
946/// mutably.
947///
948/// # Safety
949///
950/// Must be called from R's main thread with a valid `x`; the borrow must end
951/// while `x` keeps its value.
952#[doc(hidden)]
953pub unsafe fn sidecar_r_struct<'a, T: TypedExternal>(x: SEXP) -> Ref<'a, T> {
954 super::borrow_value(unsafe { sidecar_r_cell::<T>(x) })
955}
956
957/// The live `T` of the external pointer `x` carries, borrowed mutably, for
958/// the R setters of a struct field; see [`sidecar_r_struct`].
959///
960/// # Panics
961///
962/// When `x` carries no live external pointer to a `T`, or with the
963/// `miniextendr_borrow_conflict` R error while a running call holds the value.
964///
965/// # Safety
966///
967/// Must be called from R's main thread with a valid `x`; the borrow must end
968/// while `x` keeps its value.
969#[doc(hidden)]
970pub unsafe fn sidecar_r_struct_mut<'a, T: TypedExternal>(x: SEXP) -> RefMut<'a, T> {
971 super::borrow_value_mut(unsafe { sidecar_r_cell::<T>(x) })
972}
973
974/// The body of [`sidecar_r_struct`] and [`sidecar_r_struct_mut`]: the
975/// `RefCell` of the struct behind the pointer `x` carries
976/// ([`sidecar_receiver`]).
977unsafe fn sidecar_r_cell<'a, T: TypedExternal>(receiver: SEXP) -> &'a RefCell<T> {
978 let x = unsafe { sidecar_receiver::<T>(receiver) };
979 let mut any_raw = unsafe { R_ExternalPtrAddr(x) }.cast::<Box<dyn Any>>();
980 if any_raw.is_null() && unsafe { super::rebuild_restored::<T>(x, receiver) } {
981 any_raw = unsafe { R_ExternalPtrAddr(x) }.cast::<Box<dyn Any>>();
982 }
983 if any_raw.is_null() {
984 // A save by this or another version raises its own error here.
985 unsafe { refuse_restored::<T>(x, receiver) };
986 refuse_other_type::<T>(receiver, Refused::NullPointer);
987 }
988 if unsafe { super::stored_type_id_symbol(x) }.is_none() {
989 refuse_other_type::<T>(receiver, Refused::OtherType);
990 }
991 match unsafe { &*any_raw }.downcast_ref::<RefCell<T>>() {
992 Some(cell) => cell,
993 None => refuse_other_type::<T>(receiver, Refused::OtherType),
994 }
995}
996
997/// Checks that `x` carries an external pointer to a `T` ([`sidecar_receiver`]),
998/// live or (without `require_live`) read back by `readRDS()`, before a setter
999/// validates its value.
1000///
1001/// # Panics
1002///
1003/// When `x` carries no external pointer to a `T` ([`checked_prot`]), or with
1004/// the `miniextendr_borrow_conflict` R error while a running call holds the
1005/// value.
1006///
1007/// # Safety
1008///
1009/// Must be called from R's main thread with a valid `x`.
1010#[doc(hidden)]
1011pub unsafe fn sidecar_r_check<T: TypedExternal>(x: SEXP, require_live: bool) {
1012 let (_, live) = unsafe { checked_prot::<T>(x, require_live) };
1013 if let Some(cell) = live {
1014 drop(super::borrow_value_mut(cell));
1015 }
1016}
1017
1018/// Whether the receiver `x` (a pointer, or an object carrying one) still has
1019/// its Rust value, for the field methods of a type with
1020/// `#[externalptr(restored(refuse = "every_read"))]` (#1901), which ask
1021/// before a read that never reaches an accessor (`names()`, a name that is
1022/// not a field).
1023///
1024/// With `raise`, a receiver without one is refused as the accessors refuse
1025/// it ([`checked_prot`] with `require_live`: the type's restored error, the
1026/// no-pointer error, or the other-type error) and a live pointer of another
1027/// type is refused too, so the result is always `true`. Without it
1028/// (`.DollarNames()`, which serves completion and never raises) the answer
1029/// is whether `x` carries a pointer with an address: a list or an
1030/// environment without one, or an atomic vector, has none.
1031///
1032/// # Safety
1033///
1034/// Must be called from R's main thread with a valid `x`.
1035#[doc(hidden)]
1036pub unsafe fn sidecar_r_live<T: TypedExternal>(x: SEXP, raise: bool) -> bool {
1037 if raise {
1038 let _ = unsafe { checked_prot::<T>(x, true) };
1039 return true;
1040 }
1041 let ptr = if x.type_of() == SEXPTYPE::EXTPTRSXP {
1042 Some(x)
1043 } else {
1044 unsafe { unwrap_class_handle(x) }
1045 };
1046 ptr.is_some_and(|ptr| !unsafe { R_ExternalPtrAddr(ptr) }.is_null())
1047}
1048
1049/// Reads `T`'s `Sidecar` field `index` from the external pointer `x` carries
1050/// ([`sidecar_receiver`]).
1051///
1052/// Called by the R getters `#[derive(ExternalPtr)]` generates. A live
1053/// struct's pending value for the field is flushed into the slot first; a
1054/// pointer without an address (after `readRDS()`) reads its protection list
1055/// as is, unless `require_live` refuses it ([`checked_prot`]).
1056///
1057/// # Panics
1058///
1059/// When `x` carries no external pointer to a `T`, or with the
1060/// `miniextendr_borrow_conflict` R error while a running call holds the value
1061/// mutably.
1062///
1063/// # Safety
1064///
1065/// Must be called from R's main thread with a valid `x`.
1066#[doc(hidden)]
1067pub unsafe fn sidecar_r_get<T: TypedExternal>(x: SEXP, index: usize, require_live: bool) -> SEXP {
1068 let (prot, live) = unsafe { checked_prot::<T>(x, require_live) };
1069 let Some(cell) = live else {
1070 // A restored pointer: the slot of the field's name (#2187).
1071 let name = T::SIDECAR_NAMES[index];
1072 return match saved_slot(prot, index, name) {
1073 Some(at) => prot.vector_elt(at),
1074 None => missing_slot::<T>(name),
1075 };
1076 };
1077 super::borrow_value(cell).__mx_visit_sidecars(&mut |i, _, field| {
1078 if i == index {
1079 field.__mx_flush(prot, i);
1080 }
1081 });
1082 prot.vector_elt(prot_index(index))
1083}
1084
1085/// Stores `value`, which the caller validated with `TryFromSexp::<T>`, in
1086/// `T`'s `Sidecar` field `index` of the external pointer `x` carries
1087/// ([`sidecar_receiver`]).
1088///
1089/// Called by the R setters `#[derive(ExternalPtr)]` generates, after
1090/// [`sidecar_r_check`] with the same `require_live`. A live struct's pending
1091/// value for the field is dropped, since this write supersedes it. The store
1092/// allocates nothing; reading an S4 receiver's slot does, and `value`, an
1093/// argument of the setter's `.Call()`, is rooted by that call.
1094///
1095/// # Panics
1096///
1097/// When `x` carries no external pointer to a `T`, or with the
1098/// `miniextendr_borrow_conflict` R error while a running call holds the
1099/// value.
1100///
1101/// # Safety
1102///
1103/// Must be called from R's main thread with valid `x` and `value`.
1104#[doc(hidden)]
1105pub unsafe fn sidecar_r_set<T: TypedExternal>(
1106 x: SEXP,
1107 index: usize,
1108 value: SEXP,
1109 require_live: bool,
1110) {
1111 let (prot, live) = unsafe { checked_prot::<T>(x, require_live) };
1112 let Some(cell) = live else {
1113 // A restored pointer: the slot of the field's name (#2187).
1114 let name = T::SIDECAR_NAMES[index];
1115 match saved_slot(prot, index, name) {
1116 Some(at) => prot.set_vector_elt(at, value),
1117 None => missing_slot::<T>(name),
1118 }
1119 return;
1120 };
1121 super::borrow_value_mut(cell).__mx_visit_sidecars(&mut |i, _, field| {
1122 if i == index {
1123 field.__mx_discard_pending();
1124 }
1125 });
1126 prot.set_vector_elt(prot_index(index), value);
1127}
1128
1129// endregion
1130
1131#[cfg(test)]
1132mod tests {
1133 use super::no_handle_classes;
1134
1135 /// A parent class both lists share follows both specific classes.
1136 #[test]
1137 fn no_handle_classes_keep_each_class_at_its_last_place() {
1138 assert_eq!(
1139 no_handle_classes(&["pkg_saved", "pkg_error"], &["pkg_field", "pkg_error"]),
1140 [
1141 "pkg_saved",
1142 "pkg_field",
1143 "pkg_error",
1144 "miniextendr_no_handle"
1145 ]
1146 );
1147 assert_eq!(no_handle_classes(&[], &[]), ["miniextendr_no_handle"]);
1148 assert_eq!(
1149 no_handle_classes(&["pkg_error"], &[]),
1150 ["pkg_error", "miniextendr_no_handle"]
1151 );
1152 }
1153}