miniextendr_macros/
s7_conversion.rs1use crate::miniextendr_impl::{ClassSystem, ParsedMethod, ReceiverKind};
4use crate::return_wrap::ConversionKind;
5use syn::{GenericArgument, PathArguments, Type};
6
7pub(crate) fn configure(
8 method: &mut ParsedMethod,
9 original: &syn::ImplItemFn,
10 system: ClassSystem,
11 implementing_type: &syn::Ident,
12) -> syn::Result<()> {
13 let marker = method.return_wrap.as_ref().and_then(|wrap| wrap.conversion);
14 let attrs = &mut method.method_attrs;
15 if marker.is_none() && attrs.s7.convert_to.is_none() && attrs.s7.convert_from.is_none() {
16 return Ok(());
17 }
18 let error = |message| syn::Error::new_spanned(&original.sig, message);
19 if system != ClassSystem::S7 {
20 return Err(error(
21 "S7 conversion markers and attributes require an inherent S7 impl",
22 ));
23 }
24 if attrs.serialize || attrs.unwrap_in_r {
25 return Err(error(
26 "S7 class conversion cannot be combined with serialize or unwrap_in_r",
27 ));
28 }
29 if attrs.s7.convert_to.is_some() && attrs.s7.convert_from.is_some() {
30 return Err(error(
31 "cannot specify both `convert_from` and `convert_to` on the same method; convert_from is for static methods, convert_to is for instance methods",
32 ));
33 }
34 let kind = marker.unwrap_or_else(|| {
35 if attrs.s7.convert_to.is_some() {
36 ConversionKind::To
37 } else {
38 ConversionKind::From
39 }
40 });
41 let params: Vec<_> = original
42 .sig
43 .inputs
44 .iter()
45 .filter_map(|arg| {
46 if let syn::FnArg::Typed(param) = arg {
47 Some(param)
48 } else {
49 None
50 }
51 })
52 .collect();
53 match kind {
54 ConversionKind::To => {
55 if method.env == ReceiverKind::None || !params.is_empty() {
56 return Err(error(
57 "convert_to requires an instance method with no additional parameters",
58 ));
59 }
60 if attrs.s7.convert_from.is_some() {
61 return Err(error("ConvertTo disagrees with the convert_from attribute"));
62 }
63 }
64 ConversionKind::From => {
65 if method.env != ReceiverKind::None || params.len() != 1 {
66 return Err(error(
67 "convert_from requires a static method with exactly one source parameter",
68 ));
69 }
70 if attrs.s7.convert_to.is_some() {
71 return Err(error("ConvertFrom disagrees with the convert_to attribute"));
72 }
73 }
74 }
75 if method.return_wrap.is_none() {
76 method.return_wrap =
77 crate::return_wrap::resolve(&method.sig.output, Some(ClassSystem::S7))?.0;
78 }
79 let wrap = method
80 .return_wrap
81 .as_mut()
82 .expect("a conversion has a named return payload");
83 if wrap.system() != ClassSystem::S7 {
84 return Err(error(
85 "S7 conversion cannot select another return class system",
86 ));
87 }
88 if wrap.has_vector() {
89 return Err(error(
90 "S7 conversion returns one class instance, not a vector of class instances",
91 ));
92 }
93 match kind {
94 ConversionKind::To => {
95 if marker.is_some() {
96 let target = if wrap.target() == "Self" {
97 crate::naming::ident_name(implementing_type)
98 } else {
99 wrap.target().to_owned()
100 };
101 if attrs
102 .s7
103 .convert_to
104 .as_ref()
105 .is_some_and(|name| name != &target)
106 {
107 return Err(error("ConvertTo target and convert_to attribute disagree"));
108 }
109 attrs.s7.convert_to = Some(target);
110 }
111 let target = attrs
112 .s7
113 .convert_to
114 .as_ref()
115 .expect("convert_to target was selected");
116 wrap.set_target(crate::r_class_formatter::class_ref_or_verbatim(target));
117 }
118 ConversionKind::From => {
119 if wrap.target() != "Self"
120 && wrap.target() != crate::naming::ident_name(implementing_type)
121 {
122 return Err(error(
123 "convert_from must return Self or the enclosing class type",
124 ));
125 }
126 if marker.is_some() {
127 let source = source_class(¶ms[0].ty).ok_or_else(|| error("ConvertFrom needs a named source class parameter, such as `&Source` or `ExternalPtr<Source>`"))?;
128 let source = if source == "Self" {
129 crate::naming::ident_name(implementing_type)
130 } else {
131 source
132 };
133 if attrs
134 .s7
135 .convert_from
136 .as_ref()
137 .is_some_and(|name| name != &source)
138 {
139 return Err(error(
140 "ConvertFrom source parameter and convert_from attribute disagree",
141 ));
142 }
143 attrs.s7.convert_from = Some(source);
144 }
145 wrap.set_target("Self".to_owned());
146 }
147 }
148 wrap.conversion = Some(kind);
149 Ok(())
150}
151
152fn source_class(ty: &Type) -> Option<String> {
153 if let Type::Reference(reference) = ty {
154 return source_class(&reference.elem);
155 }
156 let Type::Path(path) = ty else { return None };
157 let segment = path.path.segments.last()?;
158 if segment.ident == "ExternalPtr"
159 || segment.ident == "ExternalPtrRef"
160 || segment.ident == "ExternalPtrMut"
161 {
162 let PathArguments::AngleBracketed(args) = &segment.arguments else {
163 return None;
164 };
165 let inner = args.args.iter().find_map(|arg| match arg {
166 GenericArgument::Type(inner) => Some(inner),
167 _ => None,
168 })?;
169 return source_class(inner);
170 }
171 if !#[allow(non_exhaustive_omitted_patterns)] match segment.arguments {
PathArguments::None => true,
_ => false,
}matches!(segment.arguments, PathArguments::None) {
172 return None;
173 }
174 Some(crate::naming::ident_name(&segment.ident))
175}