miniextendr_macros/miniextendr_impl/
r6_class.rs1use super::{ParsedImpl, ParsedMethod};
12use crate::r_class_formatter::class_ref_or_verbatim;
13
14fn active_setter_precondition_checks(setter: &ParsedMethod) -> Vec<String> {
30 let Some(mut value_arg) = setter.sig.inputs.iter().find_map(|arg| match arg {
31 syn::FnArg::Typed(pat_type) => Some(pat_type.clone()),
32 syn::FnArg::Receiver(_) => None,
33 }) else {
34 return Vec::new();
35 };
36
37 let mut per_param = setter.method_attrs.per_param.clone();
38 if let syn::Pat::Ident(pat_ident) = value_arg.pat.as_mut() {
39 let rust_name = pat_ident.ident.to_string();
40 if rust_name != "value" {
41 if let Some(attrs) = per_param.remove(&rust_name) {
42 per_param.insert("value".to_string(), attrs);
43 }
44 pat_ident.ident = syn::Ident::new("value", pat_ident.ident.span());
45 }
46 }
47
48 let mut inputs: syn::punctuated::Punctuated<syn::FnArg, syn::Token![,]> =
49 syn::punctuated::Punctuated::new();
50 inputs.push(syn::FnArg::Typed(value_arg));
51
52 crate::r_class_formatter::build_method_precondition_checks(
53 &inputs,
54 &per_param,
55 setter.method_attrs.coerce,
56 )
57}
58
59pub(crate) const MX_INHERITED_PARAM_PREFIX: &str = ".__MX_INHERITED_PARAM__(";
64
65pub fn generate_r6_r_wrapper(parsed_impl: &ParsedImpl) -> String {
82 use crate::r_class_formatter::{ClassDocBuilder, MethodDocBuilder, ParsedImplExt};
83
84 let class_name = parsed_impl.class_name();
85 let type_ident = &parsed_impl.type_ident;
86 let class_doc_tags = &parsed_impl.doc_tags;
87
88 let has_self_returning_methods = parsed_impl
90 .methods
91 .iter()
92 .filter(|m| m.should_include())
93 .any(|m| m.returns_self());
94
95 let mut lines = Vec::new();
96
97 lines.extend(
99 ClassDocBuilder::new(&class_name, type_ident, class_doc_tags, "R6")
100 .with_imports("@importFrom R6 R6Class")
101 .with_export_control(parsed_impl.internal, parsed_impl.noexport)
102 .build(),
103 );
104 if let Some(lc_import) = crate::lifecycle::collect_lifecycle_imports(
106 parsed_impl
107 .methods
108 .iter()
109 .filter_map(|m| m.method_attrs.lifecycle.as_ref()),
110 ) {
111 let insert_pos = lines.len().saturating_sub(1);
113 lines.insert(insert_pos, format!("#' {}", lc_import));
114 }
115
116 if has_self_returning_methods && !crate::roxygen::has_roxygen_tag(class_doc_tags, "param .ptr")
118 {
119 let insert_pos = lines.len().saturating_sub(1);
121 lines.insert(
122 insert_pos,
123 "#' @param .ptr Internal pointer (used by static methods, not for direct use)."
124 .to_string(),
125 );
126 }
127 if let Some(ref parent) = parsed_impl.r6_inherit {
131 let parent_ref = class_ref_or_verbatim(parent);
132 lines.push(format!(
133 "{} <- R6::R6Class(\"{}\", inherit = {},",
134 class_name, class_name, parent_ref
135 ));
136 } else {
137 lines.push(format!("{} <- R6::R6Class(\"{}\",", class_name, class_name));
138 }
139
140 if parsed_impl.r6_portable == Some(false) {
142 lines.push(" portable = FALSE,".to_string());
143 }
144
145 lines.push(" public = list(".to_string());
147
148 let class_param_names = &parsed_impl.class_param_names;
152
153 let public_method_contexts: Vec<_> = parsed_impl.public_instance_method_contexts().collect();
155 let has_public_methods = !public_method_contexts.is_empty();
156
157 if let Some(ctx) = parsed_impl.constructor_context() {
161 lines.push(format!(" {}", ctx.source_comment(type_ident)));
162 let has_description = ctx
165 .method
166 .doc_tags
167 .iter()
168 .any(|t| t.starts_with("@description ") || t.starts_with("@title "));
169 if !has_description {
170 lines.push(format!(
171 " #' @description Create a new `{}`.",
172 class_name
173 ));
174 }
175 for tag in &ctx.method.doc_tags {
176 for line in tag.lines() {
177 let line = if line.starts_with("@title ") {
178 line.replacen("@title ", "@description ", 1)
179 } else {
180 line.to_string()
181 };
182 lines.push(format!(" #' {}", line));
183 }
184 }
185 let ctor_mx_doc = ctx.match_arg_doc_placeholders();
189 for param in ctx.params.split(", ").filter(|p| !p.is_empty()) {
190 let param_name = param.split('=').next().unwrap_or(param).trim();
191 if param_name == ".ptr" {
192 continue;
193 }
194 let already_documented = ctx
195 .method
196 .doc_tags
197 .iter()
198 .any(|t| t.starts_with(&format!("@param {}", param_name)));
199 if !already_documented {
200 if class_param_names.contains(param_name) {
202 continue;
203 }
204 let body = ctor_mx_doc
207 .get(param_name)
208 .map(String::as_str)
209 .unwrap_or("(no documentation available)");
210 lines.push(format!(" #' @param {} {}", param_name, body));
211 }
212 }
213
214 let comma = if has_public_methods { "," } else { "" };
216
217 let ctor_preconditions = ctx.precondition_checks();
219
220 let ctor_match_arg = ctx.match_arg_prelude();
223
224 if has_self_returning_methods {
225 let full_params = if ctx.params.is_empty() {
226 ".ptr = NULL".to_string()
227 } else {
228 format!("{}, .ptr = NULL", ctx.params)
229 };
230 lines.push(format!(" initialize = function({}) {{", full_params));
231 if !ctor_preconditions.is_empty() || !ctor_match_arg.is_empty() {
233 lines.push(" if (is.null(.ptr)) {".to_string());
234 for check in &ctor_preconditions {
235 lines.push(format!(" {}", check));
236 }
237 for line in &ctor_match_arg {
238 lines.push(format!(" {}", line));
239 }
240 lines.push(" }".to_string());
241 }
242 lines.push(" if (!is.null(.ptr)) {".to_string());
243 lines.push(" private$.ptr <- .ptr".to_string());
244 lines.push(" } else {".to_string());
245 lines.push(format!(" .val <- {}", ctx.static_call()));
246 for check_line in crate::method_return_builder::condition_check_lines(" ") {
248 lines.push(check_line);
249 }
250 lines.push(" private$.ptr <- .val".to_string());
251 lines.push(" }".to_string());
252 lines.push(format!(" }}{}", comma));
253 } else {
254 lines.push(format!(" initialize = function({}) {{", ctx.params));
255 for check in &ctor_preconditions {
256 lines.push(format!(" {}", check));
257 }
258 for line in &ctor_match_arg {
259 lines.push(format!(" {}", line));
260 }
261 lines.push(format!(" .val <- {}", ctx.static_call()));
262 lines.extend(crate::method_return_builder::condition_check_lines(
263 " ",
264 ));
265 lines.push(" private$.ptr <- .val".to_string());
266 lines.push(format!(" }}{}", comma));
267 }
268 } else if has_self_returning_methods {
269 let comma = if has_public_methods { "," } else { "" };
272 lines.push(format!(
273 " #' @description Create a new `{}`.",
274 class_name
275 ));
276 lines.push(" initialize = function(.ptr = NULL) {".to_string());
277 lines.push(" if (!is.null(.ptr)) {".to_string());
278 lines.push(" private$.ptr <- .ptr".to_string());
279 lines.push(" }".to_string());
280 lines.push(format!(" }}{}", comma));
281 }
282
283 for (i, ctx) in public_method_contexts.iter().enumerate() {
285 let comma = if i < public_method_contexts.len() - 1 {
286 ","
287 } else {
288 ""
289 };
290
291 lines.push(format!(" {}", ctx.source_comment(type_ident)));
292 let r_name = ctx.method.r_method_name();
295 let has_description = ctx
296 .method
297 .doc_tags
298 .iter()
299 .any(|t| t.starts_with("@description ") || t.starts_with("@title "));
300 if !has_description {
301 lines.push(format!(" #' @description Method `{}`.", r_name));
302 }
303 for tag in &ctx.method.doc_tags {
304 for line in tag.lines() {
305 let line = if line.starts_with("@title ") {
306 line.replacen("@title ", "@description ", 1)
307 } else {
308 line.to_string()
309 };
310 lines.push(format!(" #' {}", line));
311 }
312 }
313 let method_mx_doc = ctx.match_arg_doc_placeholders();
319 let r_method_name = ctx.method.r_method_name();
320 for param in ctx.params.split(", ").filter(|p| !p.is_empty()) {
321 let param_name = param.split('=').next().unwrap_or(param).trim();
322 let already_documented = ctx
323 .method
324 .doc_tags
325 .iter()
326 .any(|t| t.starts_with(&format!("@param {}", param_name)));
327 if !already_documented {
328 if class_param_names.contains(param_name) {
330 continue;
331 }
332 let body = method_mx_doc
333 .get(param_name)
334 .map(String::as_str)
335 .unwrap_or("(no documentation available)");
336 if let Some(ref parent) = parsed_impl.r6_inherit
340 && body == "(no documentation available)"
341 {
342 lines.push(format!(
343 " #' {}class=\"{}\", parent=\"{}\", method=\"{}\", param=\"{}\")",
344 MX_INHERITED_PARAM_PREFIX, class_name, parent, r_method_name, param_name,
345 ));
346 } else {
347 lines.push(format!(" #' @param {} {}", param_name, body));
348 }
349 }
350 }
351 lines.push(format!(" {} = function({}) {{", r_name, ctx.params));
352
353 let what = format!("{}${}", class_name, r_name);
354 ctx.emit_method_prelude(&mut lines, " ", &what);
355
356 let call = ctx.instance_call("private$.ptr");
357 let strategy = crate::ReturnStrategy::for_method(ctx.method);
358 let return_builder = crate::MethodReturnBuilder::new(call)
359 .with_strategy(strategy)
360 .with_class_name(class_name.clone())
361 .with_indent(6); lines.extend(return_builder.build_r6_body());
363
364 lines.push(format!(" }}{}", comma));
365 }
366
367 lines.push(" ),".to_string());
368
369 lines.push(" private = list(".to_string());
371
372 for ctx in parsed_impl.private_instance_method_contexts() {
374 lines.push(format!(" {}", ctx.source_comment(type_ident)));
375 lines.push(format!(
376 " {} = function({}) {{",
377 ctx.method.r_method_name(),
378 ctx.params
379 ));
380
381 if let Some(ref entry) = ctx.method.method_attrs.r_entry {
383 for line in entry.lines() {
384 lines.push(format!(" {}", line));
385 }
386 }
387 if let Some(ref on_exit) = ctx.method.method_attrs.r_on_exit {
389 lines.push(format!(" {}", on_exit.to_r_code()));
390 }
391 for line in ctx.match_arg_prelude() {
394 lines.push(format!(" {}", line));
395 }
396 if let Some(ref post) = ctx.method.method_attrs.r_post_checks {
398 for line in post.lines() {
399 lines.push(format!(" {}", line));
400 }
401 }
402
403 let call = ctx.instance_call("private$.ptr");
404 let strategy = crate::ReturnStrategy::for_method(ctx.method);
405 let return_builder = crate::MethodReturnBuilder::new(call)
406 .with_strategy(strategy)
407 .with_class_name(class_name.clone())
408 .with_indent(6);
409 lines.extend(return_builder.build_r6_body());
410
411 lines.push(" },".to_string());
412 }
413
414 if let Some(finalizer) = parsed_impl.finalizer() {
416 let c_ident = finalizer
417 .c_wrapper_ident(type_ident, parsed_impl.label())
418 .to_string();
419 let finalize_call = crate::r_wrapper_builder::DotCallBuilder::new(&c_ident)
420 .null_call_attribution()
421 .with_self("private$.ptr")
422 .build();
423 lines.push(format!(" finalize = function() {finalize_call},"));
424 }
425
426 if let Some(dc_method) = parsed_impl
428 .methods
429 .iter()
430 .find(|m| m.method_attrs.r6.deep_clone && m.should_include())
431 {
432 let c_ident = dc_method
433 .c_wrapper_ident(type_ident, parsed_impl.label())
434 .to_string();
435 let deep_clone_call = crate::r_wrapper_builder::DotCallBuilder::new(&c_ident)
436 .null_call_attribution()
437 .with_self("private$.ptr")
438 .with_args(&["name", "value"])
439 .build();
440 lines.push(format!(
441 " deep_clone = function(name, value) {deep_clone_call},"
442 ));
443 }
444
445 lines.push(" .ptr = NULL".to_string());
447 lines.push(" ),".to_string());
448
449 let active_method_contexts: Vec<_> = parsed_impl.active_instance_method_contexts().collect();
451 if !active_method_contexts.is_empty() {
452 lines.push(" active = list(".to_string());
453
454 for (i, ctx) in active_method_contexts.iter().enumerate() {
455 let comma = if i < active_method_contexts.len() - 1 {
456 ","
457 } else {
458 ""
459 };
460
461 let method_name = ctx.method.r_method_name();
464 let method_noexport =
465 ctx.method.method_attrs.noexport || ctx.method.method_attrs.internal;
466 if method_noexport {
467 lines.push(format!(" #' @field {} (internal)", method_name));
475 } else if ctx.method.doc_tags.is_empty() {
476 lines.push(format!(" #' @field {} Active binding.", method_name));
477 } else {
478 for tag in &ctx.method.doc_tags {
479 for (line_idx, line) in tag.lines().enumerate() {
480 let line = if line_idx == 0 {
482 if let Some(desc) = line.strip_prefix("@description ") {
483 format!("@field {} {}", method_name, desc)
484 } else if let Some(desc) = line.strip_prefix("@title ") {
485 format!("@field {} {}", method_name, desc)
486 } else if !line.starts_with('@') {
487 format!("@field {} {}", method_name, line)
489 } else {
490 line.to_string()
491 }
492 } else {
493 line.to_string()
495 };
496 lines.push(format!(" #' {}", line));
497 }
498 }
499 }
500
501 let prop_name = ctx
503 .method
504 .method_attrs
505 .r6
506 .prop
507 .clone()
508 .unwrap_or_else(|| ctx.method.r_method_name());
509
510 let setter = parsed_impl.find_setter_for_prop(&prop_name);
512
513 if let Some(setter_method) = setter {
514 lines.push(format!(" {} = function(value) {{", prop_name));
517 lines.push(" if (missing(value)) {".to_string());
518
519 let getter_call = ctx.instance_call("private$.ptr");
522 let getter_strategy = crate::ReturnStrategy::for_method(ctx.method);
523 let getter_builder = crate::MethodReturnBuilder::new(getter_call)
524 .with_strategy(getter_strategy)
525 .with_class_name(class_name.clone())
526 .with_indent(8);
527 lines.extend(getter_builder.build_r6_body());
528
529 lines.push(" } else {".to_string());
530
531 for check in active_setter_precondition_checks(setter_method) {
535 lines.push(format!(" {}", check));
536 }
537
538 let setter_c_ident = setter_method
543 .c_wrapper_ident(type_ident, parsed_impl.label.as_deref())
544 .to_string();
545 let setter_call = crate::r_wrapper_builder::DotCallBuilder::new(&setter_c_ident)
546 .with_self("private$.ptr")
547 .with_args(&["value"])
548 .build();
549 lines.push(format!(" .val <- {}", setter_call));
550 lines.extend(crate::method_return_builder::condition_check_lines(
551 " ",
552 ));
553 lines.push(" invisible(self)".to_string());
554
555 lines.push(" }".to_string());
556 lines.push(format!(" }}{}", comma));
557 } else {
558 lines.push(format!(" {} = function() {{", prop_name));
561
562 let call = ctx.instance_call("private$.ptr");
563 let strategy = crate::ReturnStrategy::for_method(ctx.method);
564 let return_builder = crate::MethodReturnBuilder::new(call)
565 .with_strategy(strategy)
566 .with_class_name(class_name.clone())
567 .with_indent(6); lines.extend(return_builder.build_r6_body());
569
570 lines.push(format!(" }}{}", comma));
571 }
572 }
573
574 lines.push(" ),".to_string());
575 }
576
577 let lock_objects = parsed_impl.r6_lock_objects.unwrap_or(true);
579 let lock_class = parsed_impl.r6_lock_class.unwrap_or(false);
580 let cloneable = parsed_impl.r6_cloneable.unwrap_or(false);
581 lines.push(format!(
582 " lock_objects = {},",
583 if lock_objects { "TRUE" } else { "FALSE" }
584 ));
585 lines.push(format!(
586 " lock_class = {},",
587 if lock_class { "TRUE" } else { "FALSE" }
588 ));
589 lines.push(format!(
590 " cloneable = {}",
591 if cloneable { "TRUE" } else { "FALSE" }
592 ));
593 lines.push(")".to_string());
594
595 if parsed_impl.r_data_accessors {
597 let type_name = type_ident.to_string();
598 lines.push(format!(
599 ".rdata_active_bindings_{}({})",
600 type_name, class_name
601 ));
602 }
603
604 let class_has_no_rd = crate::roxygen::has_roxygen_tag(class_doc_tags, "noRd")
608 || (parsed_impl.noexport && !parsed_impl.internal);
609
610 for ctx in parsed_impl.static_method_contexts() {
612 let method_name = ctx.method.r_method_name();
613 let static_method_name = format!("{}${}", class_name, method_name);
614 lines.push(String::new());
615
616 lines.push(ctx.source_comment(type_ident));
617 let method_doc =
618 MethodDocBuilder::new(&class_name, &method_name, type_ident, &ctx.method.doc_tags)
619 .with_name_prefix("$")
620 .with_class_no_rd(class_has_no_rd);
621 lines.extend(method_doc.build());
622
623 lines.push(format!(
624 "{} <- function({}) {{",
625 static_method_name, ctx.params
626 ));
627
628 let what = format!("{}${}", class_name, method_name);
629 ctx.emit_method_prelude(&mut lines, " ", &what);
630
631 let strategy = crate::ReturnStrategy::for_method(ctx.method);
632 let return_builder = crate::MethodReturnBuilder::new(ctx.static_call())
633 .with_strategy(strategy)
634 .with_class_name(class_name.clone());
635 lines.extend(return_builder.build_r6_body());
636
637 lines.push("}".to_string());
638 }
639
640 lines.join("\n")
641}