miniextendr_macros/miniextendr_impl/
s4_class.rs1use super::ParsedImpl;
4
5pub fn generate_s4_r_wrapper(parsed_impl: &ParsedImpl) -> String {
21 use crate::r_class_formatter::{
22 ClassDocBuilder, MethodContext, MethodDocBuilder, ParsedImplExt, should_export_from_tags,
23 };
24
25 let class_name = parsed_impl.class_name();
26 let type_ident = &parsed_impl.type_ident;
27 let class_doc_tags = &parsed_impl.doc_tags;
28 let class_has_no_rd = crate::roxygen::has_roxygen_tag(class_doc_tags, "noRd");
30 let should_export =
31 should_export_from_tags(class_doc_tags, parsed_impl.noexport || parsed_impl.internal);
32
33 let mut lines = Vec::new();
34
35 let has_export = crate::roxygen::has_roxygen_tag(class_doc_tags, "export");
37 lines.extend(
38 ClassDocBuilder::new(&class_name, type_ident, class_doc_tags, "S4")
39 .with_imports("@importFrom methods setClass setGeneric setMethod new")
40 .with_export_control(parsed_impl.internal, parsed_impl.noexport)
41 .build(),
42 );
43 if let Some(lc_import) = crate::lifecycle::collect_lifecycle_imports(
45 parsed_impl
46 .methods
47 .iter()
48 .filter_map(|m| m.method_attrs.lifecycle.as_ref()),
49 ) {
50 let insert_pos = lines.len().saturating_sub(1);
51 lines.insert(insert_pos, format!("#' {}", lc_import));
52 }
53 if !has_export {
55 lines.pop();
56 }
57 if !class_has_no_rd {
58 lines.push(format!(
59 "#' @slot ptr External pointer to Rust `{}` struct",
60 type_ident
61 ));
62 }
63 lines.push(format!(
64 "methods::setClass(\"{}\", slots = c(ptr = \"externalptr\"))",
65 class_name
66 ));
67 lines.push(String::new());
68
69 if let Some(ctx) = parsed_impl.constructor_context() {
71 lines.push(ctx.source_comment(type_ident));
72 if !class_has_no_rd {
74 let mx_doc = ctx.match_arg_doc_placeholders();
76 let method_doc =
77 MethodDocBuilder::new(&class_name, "new", type_ident, &ctx.method.doc_tags)
78 .with_r_params(&ctx.params)
79 .with_match_arg_doc_placeholders(&mx_doc)
80 .with_r_name(class_name.clone());
81 lines.extend(method_doc.build());
82 }
83 if should_export {
85 lines.push("#' @export".to_string());
86 }
87
88 lines.push(format!("{} <- function({}) {{", class_name, ctx.params));
89 for line in ctx.missing_prelude() {
90 lines.push(format!(" {}", line));
91 }
92 for check in ctx.precondition_checks() {
93 lines.push(format!(" {}", check));
94 }
95 for line in ctx.match_arg_prelude() {
97 lines.push(format!(" {}", line));
98 }
99 lines.push(format!(" .val <- {}", ctx.static_call()));
100 lines.extend(crate::method_return_builder::condition_check_lines(" "));
101 lines.push(format!(" methods::new(\"{}\", ptr = .val)", class_name));
102 lines.push("}".to_string());
103 lines.push(String::new());
104 }
105
106 for method in parsed_impl.instance_methods() {
109 let start = method.ident.span().start();
110 lines.push(format!(
111 "# {}::{} ({}:{})",
112 type_ident,
113 method.ident,
114 start.line,
115 start.column + 1,
116 ));
117 let method_name = if let Some(ref generic) = method.method_attrs.generic {
118 generic.clone()
119 } else {
120 format!("s4_{}", method.ident)
121 };
122 let ctx = MethodContext::new(method, type_ident, parsed_impl.label());
128 let call = ctx.instance_call("x@ptr");
129 let full_params = ctx.instance_formals(true);
130
131 if !class_has_no_rd {
137 let qualified_name = format!("{}-{}", class_name, method_name);
138 let method_doc =
139 MethodDocBuilder::new(&class_name, &method_name, type_ident, &method.doc_tags)
140 .with_suppress_params()
141 .with_r_name(qualified_name);
142 let mut doc_lines = method_doc.build();
143 doc_lines.push(format!("#' @aliases {},{}-method", method_name, class_name));
145 lines.extend(doc_lines);
146 }
147
148 lines.push(format!(
153 "if (!methods::isGeneric(\"{0}\")) methods::setGeneric(\"{0}\", function(x, ...) standardGeneric(\"{0}\"))",
154 method_name
155 ));
156
157 if should_export {
159 lines.push(format!("#' @exportMethod {}", method_name));
160 }
161
162 let strategy = crate::ReturnStrategy::for_method(method);
163 let body_lines = crate::MethodReturnBuilder::new(call)
164 .with_strategy(strategy)
165 .with_class_name(class_name.clone())
166 .build_s4_body();
167
168 let what = format!("{}.{}", method_name, class_name);
169 lines.push(format!(
170 "methods::setMethod(\"{}\", \"{}\", function({}) {{",
171 method_name, class_name, full_params
172 ));
173 ctx.emit_method_prelude(&mut lines, " ", &what);
174 lines.extend(body_lines);
175 lines.push("})".to_string());
176 lines.push(String::new());
177 }
178
179 for ctx in parsed_impl.static_method_contexts() {
181 lines.push(ctx.source_comment(type_ident));
182 let method_name = ctx.method.r_method_name();
183 let fn_name = format!("{}_{}", class_name, method_name);
184
185 if !class_has_no_rd {
187 let mx_doc = ctx.match_arg_doc_placeholders();
188 let method_doc =
189 MethodDocBuilder::new(&class_name, &method_name, type_ident, &ctx.method.doc_tags)
190 .with_r_params(&ctx.params)
191 .with_match_arg_doc_placeholders(&mx_doc)
192 .with_r_name(fn_name.clone());
193 lines.extend(method_doc.build());
194 }
195 if should_export {
197 lines.push("#' @export".to_string());
198 }
199
200 lines.push(format!("{} <- function({}) {{", fn_name, ctx.params));
201
202 ctx.emit_method_prelude(&mut lines, " ", &fn_name);
203
204 let strategy = crate::ReturnStrategy::for_method(ctx.method);
205 let return_expr = crate::MethodReturnBuilder::new(ctx.static_call())
206 .with_strategy(strategy)
207 .with_class_name(class_name.clone())
208 .build_s4_inline();
209 lines.push(format!(" {}", return_expr));
210
211 lines.push("}".to_string());
212 lines.push(String::new());
213 }
214
215 lines.join("\n")
216}