Skip to main content

miniextendr_macros/
return_wrap.rs

1//! One explicit class-wrapping decision for marker and attribute spellings.
2
3use crate::miniextendr_impl::ClassSystem;
4use crate::type_inspect::first_type_argument;
5use proc_macro2::TokenStream;
6use quote::quote;
7use syn::{GenericArgument, PathArguments, ReturnType, Type};
8
9#[derive(#[automatically_derived]
impl ::core::fmt::Debug for Container {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f,
            match self {
                Container::Option => "Option",
                Container::Result => "Result",
                Container::Vec => "Vec",
            })
    }
}Debug, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for Container { }
#[automatically_derived]
impl ::core::clone::Clone for Container {
    #[inline]
    fn clone(&self) -> Self { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for Container { }Copy, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for Container { }
#[automatically_derived]
impl ::core::cmp::PartialEq for Container {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        ::core::intrinsics::discriminant_value(self) ==
            ::core::intrinsics::discriminant_value(other)
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for Container { }Eq)]
10enum Container {
11    Option,
12    Result,
13    Vec,
14}
15
16#[derive(#[automatically_derived]
impl ::core::fmt::Debug for ConversionKind {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f,
            match self {
                ConversionKind::To => "To",
                ConversionKind::From => "From",
            })
    }
}Debug, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for ConversionKind { }
#[automatically_derived]
impl ::core::clone::Clone for ConversionKind {
    #[inline]
    fn clone(&self) -> Self { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for ConversionKind { }Copy, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for ConversionKind { }
#[automatically_derived]
impl ::core::cmp::PartialEq for ConversionKind {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        ::core::intrinsics::discriminant_value(self) ==
            ::core::intrinsics::discriminant_value(other)
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for ConversionKind { }Eq)]
17pub(crate) enum ConversionKind {
18    To,
19    From,
20}
21
22#[derive(#[automatically_derived]
impl ::core::fmt::Debug for ReturnWrap {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field5_finish(f, "ReturnWrap",
            "system", &self.system, "target", &self.target, "containers",
            &self.containers, "typed", &self.typed, "conversion",
            &&self.conversion)
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for ReturnWrap {
    #[inline]
    fn clone(&self) -> Self {
        ReturnWrap {
            system: ::core::clone::Clone::clone(&self.system),
            target: ::core::clone::Clone::clone(&self.target),
            containers: ::core::clone::Clone::clone(&self.containers),
            typed: ::core::clone::Clone::clone(&self.typed),
            conversion: ::core::clone::Clone::clone(&self.conversion),
        }
    }
}Clone)]
23pub struct ReturnWrap {
24    system: ClassSystem,
25    target: String,
26    containers: Vec<Container>,
27    typed: bool,
28    pub(crate) conversion: Option<ConversionKind>,
29}
30
31pub(crate) fn parse_system(value: &syn::LitStr) -> syn::Result<ClassSystem> {
32    value
33        .value()
34        .parse()
35        .map_err(|message| syn::Error::new(value.span(), message))
36}
37
38fn marker_system(name: &syn::Ident) -> Option<ClassSystem> {
39    Some(match name.to_string().as_str() {
40        "WrapAsR6" => ClassSystem::R6,
41        "WrapAsS7" | "ConvertTo" | "ConvertFrom" => ClassSystem::S7,
42        "WrapAsS4" => ClassSystem::S4,
43        "WrapAsS3" => ClassSystem::S3,
44        "WrapAsEnv" => ClassSystem::Env,
45        "WrapAsVctrs" => ClassSystem::Vctrs,
46        _ => return None,
47    })
48}
49
50pub(crate) fn contains_marker(ty: &Type) -> bool {
51    match ty {
52        Type::Path(path) => path.path.segments.iter().any(|segment| {
53            marker_system(&segment.ident).is_some()
54                || match &segment.arguments {
55                    PathArguments::AngleBracketed(args) => args
56                        .args
57                        .iter()
58                        .any(|arg| #[allow(non_exhaustive_omitted_patterns)] match arg {
    GenericArgument::Type(ty) if contains_marker(ty) => true,
    _ => false,
}matches!(arg, GenericArgument::Type(ty) if contains_marker(ty))),
59                    _ => false,
60                }
61        }),
62        Type::Reference(ty) => contains_marker(&ty.elem),
63        Type::Paren(ty) => contains_marker(&ty.elem),
64        Type::Group(ty) => contains_marker(&ty.elem),
65        Type::Slice(ty) => contains_marker(&ty.elem),
66        Type::Array(ty) => contains_marker(&ty.elem),
67        Type::Tuple(ty) => ty.elems.iter().any(contains_marker),
68        _ => false,
69    }
70}
71
72/// Peel a marker under the supported boundary containers, or select the same
73/// plan from `wrap = "..."`. Visibility has already been peeled by the caller.
74pub(crate) fn resolve(
75    output: &ReturnType,
76    attribute: Option<ClassSystem>,
77) -> syn::Result<(Option<ReturnWrap>, ReturnType)> {
78    let ReturnType::Type(arrow, ty) = output else {
79        if attribute.is_some() {
80            return Err(syn::Error::new_spanned(
81                output,
82                "`wrap` requires a named class return value",
83            ));
84        }
85        return Ok((None, output.clone()));
86    };
87    if attribute.is_none() && !contains_marker(ty) {
88        return Ok((None, output.clone()));
89    }
90    let mut leaf = ty.as_ref();
91    let mut containers = Vec::new();
92    while let Type::Path(path) = leaf {
93        let segment = path.path.segments.last().expect("type path has a segment");
94        let container = match segment.ident.to_string().as_str() {
95            "Option" => Container::Option,
96            "Result" => Container::Result,
97            "Vec" => Container::Vec,
98            _ => break,
99        };
100        let PathArguments::AngleBracketed(args) = &segment.arguments else {
101            break;
102        };
103        if container == Container::Result
104            && let Some(GenericArgument::Type(error)) = args.args.iter().nth(1)
105        {
106            if contains_marker(error) {
107                return Err(syn::Error::new_spanned(
108                    error,
109                    "WrapAs* belongs on the successful Result payload",
110                ));
111            }
112            if #[allow(non_exhaustive_omitted_patterns)] match error {
    Type::Tuple(tuple) if tuple.elems.is_empty() => true,
    _ => false,
}matches!(error, Type::Tuple(tuple) if tuple.elems.is_empty()) {
113                return Err(syn::Error::new_spanned(
114                    error,
115                    "explicit class wrapping does not support Result<T, ()>; use a non-unit error type",
116                ));
117            }
118        }
119        let Some(GenericArgument::Type(inner)) = args.args.first() else {
120            break;
121        };
122        containers.push(container);
123        leaf = inner;
124    }
125    if !#[allow(non_exhaustive_omitted_patterns)] match containers.as_slice() {
    [] | [Container::Option] | [Container::Result] | [Container::Vec] |
        [Container::Option, Container::Vec] |
        [Container::Result, Container::Vec] => true,
    _ => false,
}matches!(
126        containers.as_slice(),
127        [] | [Container::Option]
128            | [Container::Result]
129            | [Container::Vec]
130            | [Container::Option, Container::Vec]
131            | [Container::Result, Container::Vec]
132    ) {
133        return Err(syn::Error::new_spanned(
134            ty,
135            "explicit class wrapping supports T, Option<T>, Result<T, E>, Vec<T>, Option<Vec<T>>, or Result<Vec<T>, E>",
136        ));
137    }
138    let marker = if let Type::Path(path) = leaf {
139        path.path
140            .segments
141            .last()
142            .and_then(|segment| marker_system(&segment.ident).map(|system| (system, segment)))
143    } else {
144        None
145    };
146    let conversion =
147        marker
148            .as_ref()
149            .and_then(|(_, segment)| match segment.ident.to_string().as_str() {
150                "ConvertTo" => Some(ConversionKind::To),
151                "ConvertFrom" => Some(ConversionKind::From),
152                _ => None,
153            });
154    let typed = marker.is_some();
155    let (system, payload) = match marker {
156        Some((system, segment)) => {
157            if attribute.is_some_and(|attribute| attribute != system) {
158                return Err(syn::Error::new_spanned(
159                    ty,
160                    "the WrapAs* marker and `wrap` attribute select different class systems",
161                ));
162            }
163            let inner = first_type_argument(segment)
164                .ok_or_else(|| {
165                    syn::Error::new_spanned(segment, "WrapAs* requires one class type argument")
166                })?
167                .clone();
168            if !#[allow(non_exhaustive_omitted_patterns)] match &segment.arguments {
    PathArguments::AngleBracketed(args) if args.args.len() == 1 => true,
    _ => false,
}matches!(&segment.arguments, PathArguments::AngleBracketed(args) if args.args.len() == 1)
169            {
170                return Err(syn::Error::new_spanned(
171                    segment,
172                    "WrapAs* requires exactly one class type argument",
173                ));
174            }
175            (system, inner)
176        }
177        None => (attribute.ok_or_else(|| syn::Error::new_spanned(ty, "place WrapAs* directly around the class payload, inside Option, Result, or Vec"))?, leaf.clone()),
178    };
179    // An `ExternalPtr<Class>` payload names `Class`; the Rust return type keeps
180    // the handle, which converts to the same `EXTPTRSXP` (#1375).
181    let leaf = crate::type_inspect::peel_external_ptr(&payload);
182    let Type::Path(path) = leaf else {
183        return Err(syn::Error::new_spanned(
184            leaf,
185            "explicit wrapping requires an owned, named class type",
186        ));
187    };
188    let segment = path.path.segments.last().expect("type path has a segment");
189    if !#[allow(non_exhaustive_omitted_patterns)] match segment.arguments {
    PathArguments::None => true,
    _ => false,
}matches!(segment.arguments, PathArguments::None) || contains_marker(leaf) {
190        return Err(syn::Error::new_spanned(
191            leaf,
192            "WrapAs* must be closest to the named class type; put containers and visibility outside it",
193        ));
194    }
195    let target = crate::naming::ident_name(&segment.ident);
196    // Validate through shared borrows above, then replace the known payload slot.
197    let mut peeled = (**ty).clone();
198    let mut slot = &mut peeled;
199    for _ in &containers {
200        let Type::Path(path) = slot else {
201            {
    ::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
            format_args!("validated container path")));
}unreachable!("validated container path")
202        };
203        let PathArguments::AngleBracketed(args) = &mut path
204            .path
205            .segments
206            .last_mut()
207            .expect("validated path")
208            .arguments
209        else {
210            {
    ::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
            format_args!("validated container arguments")));
}unreachable!("validated container arguments")
211        };
212        let GenericArgument::Type(inner) = args.args.first_mut().expect("validated payload") else {
213            {
    ::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
            format_args!("validated type argument")));
}unreachable!("validated type argument")
214        };
215        slot = inner;
216    }
217    *slot = payload;
218
219    Ok((
220        Some(ReturnWrap {
221            system,
222            target,
223            containers,
224            typed,
225            conversion,
226        }),
227        ReturnType::Type(*arrow, Box::new(peeled)),
228    ))
229}
230
231impl ReturnWrap {
232    pub(crate) fn system(&self) -> ClassSystem {
233        self.system
234    }
235    pub(crate) fn target(&self) -> &str {
236        &self.target
237    }
238    pub(crate) fn set_target(&mut self, target: String) {
239        self.target = target;
240    }
241    pub(crate) fn has_vector(&self) -> bool {
242        self.containers.contains(&Container::Vec)
243    }
244
245    /// `Option<Class>`, the one wrapped shape without an `IntoR` impl for its
246    /// `Option` (the orphan rule refuses one in the class's crate): the C wrapper
247    /// converts the payload and gives `NULL` for `None`
248    /// (`ReturnHandling::OptionClassPayload`).
249    pub(crate) fn is_optional_class(&self) -> bool {
250        self.containers == [Container::Option]
251    }
252
253    /// Unwrap only syntactic marker values; attributes keep the user's value.
254    pub(crate) fn prepare_value(&self, call: TokenStream) -> TokenStream {
255        fn map(call: TokenStream, containers: &[Container]) -> TokenStream {
256            match containers.split_first() {
257                None => {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Parenthesis,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::ToTokens::to_tokens(&call, &mut _s);
            _s
        });
    ::quote::__private::push_dot(&mut _s);
    ::quote::__private::parse(&mut _s, "0");
    _s
}quote!((#call).0),
258                Some((container, rest)) => {
259                    let inner = map({
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_ident(&mut _s, "__mx_value");
    _s
}quote!(__mx_value), rest);
260                    if *container == Container::Vec {
261                        {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Parenthesis,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::ToTokens::to_tokens(&call, &mut _s);
            _s
        });
    ::quote::__private::push_dot(&mut _s);
    ::quote::__private::push_ident(&mut _s, "into_iter");
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Parenthesis,
        ::quote::__private::TokenStream::new());
    ::quote::__private::push_dot(&mut _s);
    ::quote::__private::push_ident(&mut _s, "map");
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Parenthesis,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::__private::push_or(&mut _s);
            ::quote::__private::push_ident(&mut _s, "__mx_value");
            ::quote::__private::push_or(&mut _s);
            ::quote::ToTokens::to_tokens(&inner, &mut _s);
            _s
        });
    ::quote::__private::push_dot(&mut _s);
    ::quote::__private::push_ident(&mut _s, "collect");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_lt(&mut _s);
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "std");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "vec");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "Vec");
    ::quote::__private::push_lt(&mut _s);
    ::quote::__private::push_underscore(&mut _s);
    ::quote::__private::push_shr(&mut _s);
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Parenthesis,
        ::quote::__private::TokenStream::new());
    _s
}quote!((#call).into_iter().map(|__mx_value| #inner).collect::<::std::vec::Vec<_>>())
262                    } else {
263                        {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Parenthesis,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::ToTokens::to_tokens(&call, &mut _s);
            _s
        });
    ::quote::__private::push_dot(&mut _s);
    ::quote::__private::push_ident(&mut _s, "map");
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Parenthesis,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::__private::push_or(&mut _s);
            ::quote::__private::push_ident(&mut _s, "__mx_value");
            ::quote::__private::push_or(&mut _s);
            ::quote::ToTokens::to_tokens(&inner, &mut _s);
            _s
        });
    _s
}quote!((#call).map(|__mx_value| #inner))
264                    }
265                }
266            }
267        }
268        if self.typed {
269            map(call, &self.containers)
270        } else {
271            call
272        }
273    }
274
275    /// Emit the chosen constructor directly; no write-time registry lookup.
276    pub(crate) fn r_expression(&self, value: &str, self_type: Option<&str>) -> String {
277        let target = if self.target == "Self" {
278            self_type.unwrap_or("Self")
279        } else {
280            &self.target
281        };
282        let wrap = |value: &str| match self.system {
283            ClassSystem::R6 => ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}$new(.ptr = {1})", target,
                value))
    })format!("{target}$new(.ptr = {value})"),
284            ClassSystem::S7 => ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}(.ptr = {1})", target, value))
    })format!("{target}(.ptr = {value})"),
285            ClassSystem::S4 => ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("methods::new(\"{0}\", ptr = {1})",
                target, value))
    })format!("methods::new(\"{target}\", ptr = {value})"),
286            ClassSystem::S3 | ClassSystem::Env => {
287                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("structure({0}, class = \"{1}\")",
                value, target))
    })format!("structure({value}, class = \"{target}\")")
288            }
289            ClassSystem::Vctrs => {
290                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("structure({0}, class = unique(c(\"{1}\", class({0}))))",
                value, target))
    })format!("structure({value}, class = unique(c(\"{target}\", class({value}))))")
291            }
292        };
293        let wrapped = if self.containers.contains(&Container::Vec) {
294            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("lapply({1}, function(.item) {0})",
                wrap(".item"), value))
    })format!("lapply({value}, function(.item) {})", wrap(".item"))
295        } else {
296            wrap(value)
297        };
298        // `None` arrives as `NULL` (an `Option` converts as a function's does);
299        // no constructor takes it, and `lapply()` would turn it into `list()`.
300        if self.containers.first() == Some(&Container::Option) {
301            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("if (is.null({0})) NULL else {1}",
                value, wrapped))
    })format!("if (is.null({value})) NULL else {wrapped}")
302        } else {
303            wrapped
304        }
305    }
306}
307
308#[cfg(test)]
309mod tests {
310    use super::*;
311    use quote::ToTokens;
312
313    #[test]
314    fn marker_and_attribute_share_each_system_and_container_plan() {
315        for (marker, system) in [
316            ("WrapAsR6", ClassSystem::R6),
317            ("WrapAsS7", ClassSystem::S7),
318            ("WrapAsS4", ClassSystem::S4),
319            ("WrapAsS3", ClassSystem::S3),
320            ("WrapAsEnv", ClassSystem::Env),
321            ("WrapAsVctrs", ClassSystem::Vctrs),
322        ] {
323            for shape in [
324                "{}",
325                "Option<{}>",
326                "Result<{}, String>",
327                "Vec<{}>",
328                "Option<Vec<{}>>",
329                "Result<Vec<{}>, String>",
330            ] {
331                let typed: ReturnType = syn::parse_str(&format!(
332                    "-> {}",
333                    shape.replace("{}", &format!("miniextendr_api::{marker}<Board>"))
334                ))
335                .unwrap();
336                let plain: ReturnType =
337                    syn::parse_str(&format!("-> {}", shape.replace("{}", "Board"))).unwrap();
338                let (a, output) = resolve(&typed, None).unwrap();
339                let (b, _) = resolve(&plain, Some(system)).unwrap();
340                assert_eq!(
341                    output.to_token_stream().to_string(),
342                    plain.to_token_stream().to_string()
343                );
344                assert_eq!(
345                    a.unwrap().r_expression(".val", None),
346                    b.unwrap().r_expression(".val", None)
347                );
348            }
349        }
350    }
351    /// An `Option` return is `NULL` for `None` (#2088): the wrap lets it through.
352    #[test]
353    fn optional_wraps_let_null_through() {
354        for (shape, guarded) in [
355            ("{}", false),
356            ("Option<{}>", true),
357            ("Result<{}, String>", false),
358            ("Vec<{}>", false),
359            ("Option<Vec<{}>>", true),
360            ("Result<Vec<{}>, String>", false),
361        ] {
362            let output: ReturnType =
363                syn::parse_str(&format!("-> {}", shape.replace("{}", "WrapAsR6<Board>"))).unwrap();
364            let (plan, _) = resolve(&output, None).unwrap();
365            let plan = plan.unwrap();
366            assert_eq!(plan.is_optional_class(), shape == "Option<{}>", "{shape}");
367            let expr = plan.r_expression(".val", None);
368            assert_eq!(
369                expr.starts_with("if (is.null(.val)) NULL else "),
370                guarded,
371                "{shape}: {expr}"
372            );
373        }
374    }
375
376    #[test]
377    fn rejects_misplaced_markers_and_conflicting_systems() {
378        for (output, message) in [
379            ("-> WrapAsR6<Vec<Board>>", "closest to the named class"),
380            (
381                "-> Result<Board, WrapAsR6<Problem>>",
382                "successful Result payload",
383            ),
384            (
385                "-> Option<Option<WrapAsR6<Board>>>",
386                "supports T, Option<T>",
387            ),
388            ("-> Result<WrapAsR6<Board>, ()>", "non-unit error type"),
389            (
390                "-> (WrapAsR6<Board>, i32)",
391                "directly around the class payload",
392            ),
393            ("-> WrapAsR6<&Board>", "owned, named class type"),
394            ("-> WrapAsR6<WrapAsS7<Board>>", "closest to the named class"),
395        ] {
396            let output = syn::parse_str(output).unwrap();
397            assert!(
398                resolve(&output, None)
399                    .unwrap_err()
400                    .to_string()
401                    .contains(message)
402            );
403        }
404        let output = syn::parse_quote!(-> WrapAsR6<Board>);
405        assert!(
406            resolve(&output, Some(ClassSystem::S7))
407                .unwrap_err()
408                .to_string()
409                .contains("different class systems")
410        );
411        assert!(resolve(&output, Some(ClassSystem::R6)).is_ok());
412    }
413
414    /// An `ExternalPtr<Class>` payload wraps as `Class` in both spellings and
415    /// keeps the handle as the Rust return type, like inferred wrapping (#1375).
416    #[test]
417    fn external_ptr_payload_names_its_class() {
418        for (output, attribute, peeled) in [
419            (
420                "-> WrapAsR6<ExternalPtr<Board>>",
421                None,
422                "-> ExternalPtr<Board>",
423            ),
424            (
425                "-> ExternalPtr<Board>",
426                Some(ClassSystem::R6),
427                "-> ExternalPtr<Board>",
428            ),
429            (
430                "-> Option<Vec<WrapAsR6<ExternalPtr<Board>>>>",
431                None,
432                "-> Option<Vec<ExternalPtr<Board>>>",
433            ),
434        ] {
435            let (plan, output) = resolve(&syn::parse_str(output).unwrap(), attribute).unwrap();
436            let expected: ReturnType = syn::parse_str(peeled).unwrap();
437            assert_eq!(
438                output.to_token_stream().to_string(),
439                expected.to_token_stream().to_string()
440            );
441            assert!(
442                plan.unwrap()
443                    .r_expression(".val", None)
444                    .contains("Board$new(.ptr = ")
445            );
446        }
447    }
448
449    #[test]
450    fn explicit_self_and_qualified_targets_do_not_use_registry_placeholders() {
451        for (output, target) in [
452            ("-> WrapAsR6<Self>", "ActualClass"),
453            ("-> WrapAsR6<other::Board>", "Board"),
454        ] {
455            let (plan, _) = resolve(&syn::parse_str(output).unwrap(), None).unwrap();
456            assert_eq!(
457                plan.unwrap().r_expression(".val", Some("ActualClass")),
458                format!("{target}$new(.ptr = .val)")
459            );
460        }
461    }
462}