1use super::method_context::TraitMethodContext;
9use super::{TraitConst, TraitMethod, trait_method_body_lines};
10use crate::miniextendr_impl::ClassSystem;
11use crate::r_class_formatter::emit_s3_generic_guard;
12
13pub(super) struct TraitWrapperOpts {
15 pub(super) class_system: ClassSystem,
17 pub(super) class_has_no_rd: bool,
20 pub(super) internal: bool,
23 pub(super) noexport: bool,
26}
27
28pub(super) fn generate_trait_r_wrapper(
39 type_ident: &syn::Ident,
40 trait_name: &syn::Ident,
41 methods: &[TraitMethod],
42 consts: &[TraitConst],
43 opts: TraitWrapperOpts,
44) -> syn::Result<String> {
45 let TraitWrapperOpts {
46 class_system,
47 class_has_no_rd,
48 internal,
49 noexport,
50 } = opts;
51 let result = match class_system {
52 ClassSystem::Env => generate_trait_env_r_wrapper(type_ident, trait_name, methods, consts)?,
53 ClassSystem::S3 => generate_trait_s3_r_wrapper(type_ident, trait_name, methods, consts),
54 ClassSystem::S4 => generate_trait_s4_r_wrapper(type_ident, trait_name, methods, consts),
55 ClassSystem::S7 => generate_trait_s7_r_wrapper(type_ident, trait_name, methods, consts),
56 ClassSystem::R6 => generate_trait_r6_r_wrapper(type_ident, trait_name, methods, consts),
57 ClassSystem::Vctrs => generate_trait_s3_r_wrapper(type_ident, trait_name, methods, consts),
59 };
60
61 let suppress_all_rd = class_has_no_rd || (noexport && !internal);
69 if suppress_all_rd {
70 if matches!(class_system, ClassSystem::S3 | ClassSystem::Vctrs) {
71 let keep_export = class_has_no_rd;
78 let mut filtered = Vec::new();
79 let mut roxygen_block: Vec<&str> = Vec::new();
80
81 let flush_block = |block: &mut Vec<&str>, out: &mut Vec<String>| {
82 if block.iter().any(|line| line.contains("@method ")) {
83 out.push("#' @noRd".to_string());
84 for &line in block.iter() {
85 if line.contains("@method ")
86 || line.contains("@param ")
87 || (keep_export && line.contains("@export"))
88 {
89 out.push(line.to_string());
90 }
91 }
92 }
93 block.clear();
94 };
95
96 for line in result.lines() {
97 if line.starts_with("#'") {
98 roxygen_block.push(line);
99 continue;
100 }
101
102 if !roxygen_block.is_empty() {
103 flush_block(&mut roxygen_block, &mut filtered);
104 }
105 filtered.push(line.to_string());
106 }
107
108 if !roxygen_block.is_empty() {
109 flush_block(&mut roxygen_block, &mut filtered);
110 }
111
112 Ok(filtered.join("\n"))
113 } else {
114 Ok(result
115 .lines()
116 .filter(|line| !line.starts_with("#'"))
117 .collect::<Vec<_>>()
118 .join("\n"))
119 }
120 } else if internal {
121 let has_export = result.lines().any(|line| line.contains("@export"));
123 let mut processed: Vec<String> = result
124 .lines()
125 .flat_map(|line| {
126 if line.contains("@export") {
127 vec!["#' @keywords internal".to_string()]
128 } else {
129 vec![line.to_string()]
130 }
131 })
132 .collect();
133 if !has_export && let Some(pos) = processed.iter().position(|l| l.starts_with("#'")) {
136 processed.insert(pos, "#' @keywords internal".to_string());
137 }
138 Ok(processed.join("\n"))
139 } else {
140 Ok(result)
141 }
142}
143
144fn generate_trait_env_r_wrapper(
156 type_ident: &syn::Ident,
157 trait_name: &syn::Ident,
158 methods: &[TraitMethod],
159 consts: &[TraitConst],
160) -> syn::Result<String> {
161 use crate::r_wrapper_builder::{DotCallBuilder, RoxygenBuilder};
162
163 let mut lines = Vec::new();
164 let type_str = type_ident.to_string();
165 let trait_str = trait_name.to_string();
166
167 lines.push(format!(
169 "# Trait methods and consts for {} implementing {}",
170 type_ident, trait_name
171 ));
172 lines.push(format!(
173 "# Generated by #[miniextendr] impl {} for {}",
174 trait_name, type_ident
175 ));
176 lines.push(String::new());
177
178 lines.push(format!(
180 "{}${} <- new.env(parent = emptyenv())",
181 type_ident, trait_name
182 ));
183 lines.push(String::new());
184
185 for method in methods {
186 let r_name = method.r_method_name();
187 let ctx = TraitMethodContext::new(method, type_ident, trait_name);
188
189 let roxygen = RoxygenBuilder::new()
191 .name(format!("{}${}${}", type_str, trait_str, r_name))
192 .rdname(&type_str)
193 .build();
194 lines.extend(roxygen);
195
196 if method.has_self {
198 for input in &method.sig.inputs {
199 if let syn::FnArg::Typed(pt) = input
200 && let syn::Pat::Ident(pat_ident) = pt.pat.as_ref()
201 && pat_ident.ident == "x"
202 {
203 return Err(syn::Error::new_spanned(
204 &pat_ident.ident,
205 "trait instance method parameter cannot be named `x` \
206 (collides with self parameter in env-class dispatch)",
207 ));
208 }
209 }
210 }
211
212 let (full_params, call) = if method.has_self {
214 let fp = if ctx.params.is_empty() {
215 "x".to_string()
216 } else {
217 format!("x, {}", ctx.params)
218 };
219 (fp, ctx.instance_call("x"))
220 } else {
221 (ctx.params.clone(), ctx.static_call())
222 };
223
224 lines.push(format!(
226 "{}${}${} <- function({}) {{",
227 type_ident, trait_name, r_name, full_params
228 ));
229 ctx.emit_method_prelude(&mut lines, " ", &r_name);
230 lines.extend(trait_method_body_lines(&call, " "));
231 if method.has_self && method.returns_unit() {
232 lines.push(" invisible(x)".to_string());
233 }
234 lines.push("}".to_string());
235
236 if method.has_self {
238 lines.push(format!(
239 "attr({}${}${}, \".__mx_instance__\") <- TRUE",
240 type_ident, trait_name, r_name
241 ));
242 }
243
244 lines.push(String::new());
245 }
246
247 for trait_const in consts {
249 let const_name = &trait_const.ident;
250 let const_str = const_name.to_string();
251
252 let roxygen = RoxygenBuilder::new()
254 .name(format!("{}${}${}", type_str, trait_str, const_str))
255 .rdname(&type_str)
256 .build();
257 lines.extend(roxygen);
258
259 let c_ident = trait_const.c_wrapper_ident_string(type_ident, trait_name);
261 let call = DotCallBuilder::new(&c_ident).build();
262
263 lines.push(format!(
265 "{}${}${} <- function() {{",
266 type_ident, trait_name, const_name
267 ));
268 lines.push(format!(" {}", call));
269 lines.push("}".to_string());
270 lines.push(String::new());
271 }
272
273 Ok(lines.join("\n"))
274}
275
276fn generate_trait_s3_r_wrapper(
288 type_ident: &syn::Ident,
289 trait_name: &syn::Ident,
290 methods: &[TraitMethod],
291 consts: &[TraitConst],
292) -> String {
293 use crate::r_wrapper_builder::{DotCallBuilder, RoxygenBuilder};
294
295 let mut lines = Vec::new();
296 let type_str = type_ident.to_string();
297 let trait_str = trait_name.to_string();
298
299 lines.push(format!(
301 "# S3 trait methods for {} implementing {}",
302 type_ident, trait_name
303 ));
304 lines.push(format!(
305 "# Generated by #[miniextendr(s3)] impl {} for {}",
306 trait_name, type_ident
307 ));
308 lines.push(String::new());
309
310 let instance_methods: Vec<_> = methods.iter().filter(|m| m.has_self).collect();
312 let static_methods: Vec<_> = methods.iter().filter(|m| !m.has_self).collect();
313
314 for method in &instance_methods {
316 let generic_name = method.r_method_name();
317 let s3_method_name = format!("{}.{}", generic_name, type_str);
318 let ctx = TraitMethodContext::new(method, type_ident, trait_name);
319
320 let generic_roxygen = RoxygenBuilder::new()
323 .title(format!("S3 generic for `{}`", generic_name))
324 .custom(format!("S3 generic for `{}`", generic_name))
325 .name(format!("{}.{}", generic_name, type_str))
326 .rdname(&type_str)
327 .custom("@param x An object")
328 .custom("@param ... Additional arguments passed to methods")
329 .source(format!(
330 "Generated by miniextendr from `impl {} for {}`",
331 trait_name, type_ident
332 ))
333 .export()
334 .build();
335 lines.extend(generic_roxygen);
336
337 lines.push(emit_s3_generic_guard(generic_name.as_str()));
339 lines.push(String::new());
340
341 let mut method_roxygen = RoxygenBuilder::new()
343 .rdname(&type_str)
344 .export()
345 .method(&generic_name, &type_str);
346 for tag in &method.param_tags {
347 method_roxygen = method_roxygen.custom(tag.clone());
348 }
349 lines.extend(method_roxygen.build());
350
351 let full_params = if ctx.params.is_empty() {
353 "x, ...".to_string()
354 } else {
355 format!("x, {}, ...", ctx.params)
356 };
357
358 let call = ctx.instance_call("x");
360
361 lines.push(format!(
363 "{} <- function({}) {{",
364 s3_method_name, full_params
365 ));
366 ctx.emit_method_prelude(&mut lines, " ", &generic_name);
367 lines.extend(trait_method_body_lines(&call, " "));
368 if method.returns_unit() {
370 lines.push(" invisible(x)".to_string());
371 }
372 lines.push("}".to_string());
373
374 lines.push(format!(
377 "if (inherits(get0(\"{generic_name}\", mode = \"function\"), \"S7_generic\")) {{"
378 ));
379 lines.push(format!(
380 " S7::method({generic_name}, S7::new_S3_class(\"{type_str}\")) <- {s3_method_name}"
381 ));
382 lines.push("}".to_string());
383 lines.push(String::new());
384 }
385
386 if !static_methods.is_empty() || !consts.is_empty() {
388 lines.push(format!(
389 "{}${} <- new.env(parent = emptyenv())",
390 type_ident, trait_name
391 ));
392 lines.push(String::new());
393 }
394
395 for method in &static_methods {
397 let r_name = method.r_method_name();
398 let ctx = TraitMethodContext::new(method, type_ident, trait_name);
399
400 lines.push(format!(
402 "#' Static trait method {}::{}()",
403 trait_name, r_name
404 ));
405 let roxygen = RoxygenBuilder::new()
406 .name(format!("{}${}${}", type_str, trait_str, r_name))
407 .rdname(&type_str)
408 .build();
409 lines.extend(roxygen);
410
411 let call = ctx.static_call();
412
413 lines.push(format!(
414 "{}${}${} <- function({}) {{",
415 type_ident, trait_name, r_name, ctx.params
416 ));
417 ctx.emit_method_prelude(&mut lines, " ", &r_name);
418 lines.extend(trait_method_body_lines(&call, " "));
419 lines.push("}".to_string());
420 lines.push(String::new());
421 }
422
423 for trait_const in consts {
425 let const_name = &trait_const.ident;
426 let const_str = const_name.to_string();
427
428 let roxygen = RoxygenBuilder::new()
429 .name(format!("{}${}${}", type_str, trait_str, const_str))
430 .rdname(&type_str)
431 .build();
432 lines.extend(roxygen);
433
434 let c_ident = trait_const.c_wrapper_ident_string(type_ident, trait_name);
435 let call = DotCallBuilder::new(&c_ident).build();
436
437 lines.push(format!(
438 "{}${}${} <- function() {{",
439 type_ident, trait_name, const_name
440 ));
441 lines.push(format!(" {}", call));
442 lines.push("}".to_string());
443 lines.push(String::new());
444 }
445
446 lines.join("\n")
447}
448
449fn generate_trait_s4_r_wrapper(
460 type_ident: &syn::Ident,
461 trait_name: &syn::Ident,
462 methods: &[TraitMethod],
463 consts: &[TraitConst],
464) -> String {
465 use crate::r_wrapper_builder::{DotCallBuilder, RoxygenBuilder};
466
467 let mut lines = Vec::new();
468 let type_str = type_ident.to_string();
469 let trait_str = trait_name.to_string();
470
471 lines.push(format!(
473 "# S4 trait methods for {} implementing {}",
474 type_ident, trait_name
475 ));
476 lines.push(format!(
477 "# Generated by #[miniextendr(s4)] impl {} for {}",
478 trait_name, type_ident
479 ));
480 lines.push(String::new());
481
482 lines.push("#' @importFrom methods setGeneric setMethod".to_string());
486 lines.push(String::new());
487
488 let instance_methods: Vec<_> = methods.iter().filter(|m| m.has_self).collect();
490 let static_methods: Vec<_> = methods.iter().filter(|m| !m.has_self).collect();
491
492 for method in &instance_methods {
494 let method_name = &method.ident;
495 let generic_name = format!("s4_trait_{}_{}", trait_name, method.r_method_name());
496 let ctx = TraitMethodContext::new(method, type_ident, trait_name);
497
498 let full_params = if ctx.params.is_empty() {
500 "x, ...".to_string()
501 } else {
502 format!("x, {}, ...", ctx.params)
503 };
504
505 let mut generic_roxygen = RoxygenBuilder::new()
509 .custom(format!(
510 "S4 generic for trait method `{}::{}`",
511 trait_name, method_name
512 ))
513 .name(&generic_name)
514 .rdname(&type_str)
515 .source(format!(
516 "Generated by miniextendr from `impl {} for {}`",
517 trait_name, type_ident
518 ))
519 .custom(format!("@param x A `{}` object", type_str))
520 .custom("@param ... Additional arguments passed to methods");
521 for tag in &method.param_tags {
522 generic_roxygen = generic_roxygen.custom(tag.clone());
523 }
524 lines.extend(generic_roxygen.export().build());
525
526 lines.push(format!(
531 "if (!exists(\"{generic_name}\", where = topenv(environment()), inherits = FALSE)) methods::setGeneric(\"{generic_name}\", function(x, ...) standardGeneric(\"{generic_name}\"))"
532 ));
533 lines.push(String::new());
534
535 lines.push(format!("#' @rdname {}", type_str));
537 for tag in &method.param_tags {
538 lines.push(format!("#' {}", tag));
539 }
540 lines.push(format!("#' @exportMethod {}", generic_name));
541
542 lines.push(format!(
543 "methods::setMethod(\"{}\", \"{}\", function({}) {{",
544 generic_name, type_str, full_params
545 ));
546 lines.push(" .ptr <- x@ptr".to_string());
548 let s4_call = ctx.instance_call(".ptr");
549 ctx.emit_method_prelude(&mut lines, " ", &method.r_method_name());
550 lines.extend(trait_method_body_lines(&s4_call, " "));
551 if method.returns_unit() {
553 lines.push(" invisible(x)".to_string());
554 }
555 lines.push("})".to_string());
556 lines.push(String::new());
557 }
558
559 for method in &static_methods {
561 let r_name = method.r_method_name();
562 let fn_name = format!("{}_{}_{}", type_str, trait_str, r_name);
563 let ctx = TraitMethodContext::new(method, type_ident, trait_name);
564
565 lines.push(format!(
567 "#' Static trait method {}::{}() for {}",
568 trait_name, r_name, type_str
569 ));
570 let roxygen = RoxygenBuilder::new()
571 .name(&fn_name)
572 .rdname(&type_str)
573 .export()
574 .build();
575 lines.extend(roxygen);
576
577 let call = ctx.static_call();
578
579 lines.push(format!("{} <- function({}) {{", fn_name, ctx.params));
580 ctx.emit_method_prelude(&mut lines, " ", &r_name);
581 lines.extend(trait_method_body_lines(&call, " "));
582 lines.push("}".to_string());
583 lines.push(String::new());
584 }
585
586 for trait_const in consts {
588 let const_name = &trait_const.ident;
589 let fn_name = format!("{}_{}_{}", type_str, trait_str, const_name);
590
591 let roxygen = RoxygenBuilder::new()
592 .name(&fn_name)
593 .rdname(&type_str)
594 .export()
595 .build();
596 lines.extend(roxygen);
597
598 let c_ident = trait_const.c_wrapper_ident_string(type_ident, trait_name);
599 let call = DotCallBuilder::new(&c_ident).build();
600
601 lines.push(format!("{} <- function() {{", fn_name));
602 lines.push(format!(" {}", call));
603 lines.push("}".to_string());
604 lines.push(String::new());
605 }
606
607 lines.join("\n")
608}
609
610fn generate_trait_s7_r_wrapper(
620 type_ident: &syn::Ident,
621 trait_name: &syn::Ident,
622 methods: &[TraitMethod],
623 consts: &[TraitConst],
624) -> String {
625 use crate::r_wrapper_builder::{DotCallBuilder, RoxygenBuilder};
626
627 let mut lines = Vec::new();
628 let type_str = type_ident.to_string();
629 let trait_str = trait_name.to_string();
630 let s7_class_var = format!(".s7_class_{}", type_str);
631
632 lines.push(format!(
634 "# S7 trait methods for {} implementing {}",
635 type_ident, trait_name
636 ));
637 lines.push(format!(
638 "# Generated by #[miniextendr(s7)] impl {} for {}",
639 trait_name, type_ident
640 ));
641 lines.push(String::new());
642
643 lines.push("#' @importFrom S7 new_generic method S7_dispatch".to_string());
648 lines.push(format!("{} <- {}", s7_class_var, type_str));
649 lines.push(String::new());
650
651 let instance_methods: Vec<_> = methods.iter().filter(|m| m.has_self).collect();
653 let static_methods: Vec<_> = methods.iter().filter(|m| !m.has_self).collect();
654
655 for method in &instance_methods {
657 let method_name = &method.ident;
658 let generic_name = format!("s7_trait_{}_{}", trait_name, method.r_method_name());
659 let ctx = TraitMethodContext::new(method, type_ident, trait_name);
660
661 let full_params = if ctx.params.is_empty() {
663 "x, ...".to_string()
664 } else {
665 format!("x, {}, ...", ctx.params)
666 };
667
668 let generic_roxygen = RoxygenBuilder::new()
673 .custom(format!(
674 "S7 generic for trait method `{}::{}`",
675 trait_name, method_name
676 ))
677 .name(&generic_name)
678 .rdname(&type_str)
679 .source(format!(
680 "Generated by miniextendr from `impl {} for {}`",
681 trait_name, type_ident
682 ))
683 .export()
684 .build();
685 lines.extend(generic_roxygen);
686
687 lines.push(format!(
689 "if (!exists(\"{generic_name}\", mode = \"function\")) {{"
690 ));
691 lines.push(format!(
692 " {generic_name} <- S7::new_generic(\"{generic_name}\", \"x\", function(x, ...) S7::S7_dispatch())"
693 ));
694 lines.push("}".to_string());
695 lines.push(String::new());
696
697 lines.push(format!(
699 "S7::method({}, {}) <- function({}) {{",
700 generic_name, s7_class_var, full_params
701 ));
702 lines.push(" .ptr <- x@.ptr".to_string());
704 let s7_call = ctx.instance_call(".ptr");
705 ctx.emit_method_prelude(&mut lines, " ", &method.r_method_name());
706 lines.extend(trait_method_body_lines(&s7_call, " "));
707 if method.returns_unit() {
709 lines.push(" invisible(x)".to_string());
710 }
711 lines.push("}".to_string());
712 lines.push(String::new());
713
714 if !method.no_shortcut {
723 let shortcut_name = format!("{}_{}", type_str, method.r_method_name());
724 let shortcut_formals = if ctx.params.is_empty() {
725 "self, ...".to_string()
726 } else {
727 format!("self, {}, ...", ctx.params)
728 };
729 let shortcut_call = ctx.instance_call("self@.ptr");
730
731 lines.extend(crate::miniextendr_impl::s7_class::shortcut_advisory_lines(
736 &method.r_method_name(),
737 &type_str,
738 ));
739 lines.push(format!("#' @param self A `{}` object.", type_str));
740 for tag in &method.param_tags {
741 lines.push(format!("#' {}", tag));
742 }
743 for formal in crate::roxygen::split_r_formals(&shortcut_formals) {
749 let pname = crate::roxygen::formal_name(formal);
750 if pname == "self" {
751 continue;
752 }
753 let documented = method
754 .param_tags
755 .iter()
756 .any(|t| t.starts_with(&format!("@param {}", pname)));
757 if documented {
758 continue;
759 }
760 if pname == "..." {
761 lines.push(
762 "#' @param ... Additional arguments; ignored by the fast-path shortcut."
763 .to_string(),
764 );
765 } else {
766 lines.push(format!("#' @param {} (undocumented)", pname));
767 }
768 }
769 lines.push(format!("#' @name {}", shortcut_name));
770 lines.push(format!("#' @rdname {}", type_str));
771 lines.push(format!(
772 "#' @source Generated by miniextendr from `impl {} for {}` (`{}` shortcut)",
773 trait_name, type_ident, method_name
774 ));
775 lines.push("#' @export".to_string());
776
777 lines.push(format!(
778 "{} <- function({}) {{",
779 shortcut_name, shortcut_formals
780 ));
781 ctx.emit_method_prelude(&mut lines, " ", &method.r_method_name());
782 lines.extend(trait_method_body_lines(&shortcut_call, " "));
783 if method.returns_unit() {
785 lines.push(" invisible(self)".to_string());
786 }
787 lines.push("}".to_string());
788 lines.push(String::new());
789 }
790 }
791
792 let trait_env_var = format!(".{}__{}", type_ident, trait_name);
795 if !static_methods.is_empty() || !consts.is_empty() {
796 lines.push(format!("{} <- new.env(parent = emptyenv())", trait_env_var));
797 lines.push(String::new());
798 }
799
800 for method in &static_methods {
802 let r_name = method.r_method_name();
803 let ctx = TraitMethodContext::new(method, type_ident, trait_name);
804
805 lines.push(format!(
806 "#' Static trait method {}::{}()",
807 trait_name, r_name
808 ));
809 let roxygen = RoxygenBuilder::new()
810 .name(format!("{}${}${}", type_str, trait_str, r_name))
811 .rdname(&type_str)
812 .build();
813 lines.extend(roxygen);
814
815 let call = ctx.static_call();
816
817 lines.push(format!(
818 "{}${} <- function({}) {{",
819 trait_env_var, r_name, ctx.params
820 ));
821 ctx.emit_method_prelude(&mut lines, " ", &r_name);
822 lines.extend(trait_method_body_lines(&call, " "));
823 lines.push("}".to_string());
824 lines.push(String::new());
825 }
826
827 for trait_const in consts {
829 let const_name = &trait_const.ident;
830 let const_str = const_name.to_string();
831
832 let roxygen = RoxygenBuilder::new()
833 .name(format!("{}${}${}", type_str, trait_str, const_str))
834 .rdname(&type_str)
835 .build();
836 lines.extend(roxygen);
837
838 let c_ident = trait_const.c_wrapper_ident_string(type_ident, trait_name);
839 let call = DotCallBuilder::new(&c_ident).build();
840
841 lines.push(format!("{}${} <- function() {{", trait_env_var, const_name));
842 lines.push(format!(" {}", call));
843 lines.push("}".to_string());
844 lines.push(String::new());
845 }
846
847 if !static_methods.is_empty() || !consts.is_empty() {
850 lines.push(format!(
851 "attr({}, \"{}\") <- {}",
852 type_ident, trait_name, trait_env_var
853 ));
854 lines.push(String::new());
855 }
856
857 lines.join("\n")
858}
859
860fn generate_trait_r6_r_wrapper(
873 type_ident: &syn::Ident,
874 trait_name: &syn::Ident,
875 methods: &[TraitMethod],
876 consts: &[TraitConst],
877) -> String {
878 use crate::r_wrapper_builder::{DotCallBuilder, RoxygenBuilder};
879
880 let mut lines = Vec::new();
881 let type_str = type_ident.to_string();
882 let trait_str = trait_name.to_string();
883
884 lines.push(format!(
886 "# R6 trait methods for {} implementing {}",
887 type_ident, trait_name
888 ));
889 lines.push(format!(
890 "# Generated by #[miniextendr(r6)] impl {} for {}",
891 trait_name, type_ident
892 ));
893 lines.push("# Note: R6 trait methods are standalone functions".to_string());
894 lines.push(String::new());
895
896 let instance_methods: Vec<_> = methods.iter().filter(|m| m.has_self).collect();
898 let static_methods: Vec<_> = methods.iter().filter(|m| !m.has_self).collect();
899
900 for method in &instance_methods {
902 let method_name = &method.ident;
903 let fn_name = format!("r6_trait_{}_{}", trait_name, method.r_method_name());
904 let ctx = TraitMethodContext::new(method, type_ident, trait_name);
905
906 let full_params = if ctx.params.is_empty() {
908 "x".to_string()
909 } else {
910 format!("x, {}", ctx.params)
911 };
912
913 let mut roxygen = RoxygenBuilder::new()
915 .custom(format!(
916 "R6 trait method `{}::{}` for {}",
917 trait_name, method_name, type_str
918 ))
919 .name(&fn_name)
920 .rdname(&type_str)
921 .source(format!(
922 "Generated by miniextendr from `impl {} for {}`",
923 trait_name, type_ident
924 ))
925 .custom(format!("@param x A `{}` object", type_str));
926 for tag in &method.param_tags {
927 roxygen = roxygen.custom(tag.clone());
928 }
929 for formal in crate::roxygen::split_r_formals(&full_params) {
935 let pname = crate::roxygen::formal_name(formal);
936 if pname == "x" {
937 continue;
938 }
939 let documented = method
940 .param_tags
941 .iter()
942 .any(|t| t.trim_start().starts_with(&format!("@param {pname} ")));
943 if documented {
944 continue;
945 }
946 roxygen = if pname == "..." {
947 roxygen.custom("@param ... Additional arguments passed to the method.")
948 } else {
949 roxygen.custom(format!("@param {pname} (undocumented)"))
950 };
951 }
952 lines.extend(roxygen.export().build());
953
954 let call = ctx.instance_call(".ptr");
955
956 lines.push(format!("{} <- function({}) {{", fn_name, full_params));
957 lines.push(" .ptr <- x$.__enclos_env__$private$.ptr".to_string());
959 ctx.emit_method_prelude(&mut lines, " ", &method.r_method_name());
960 lines.extend(trait_method_body_lines(&call, " "));
961 if method.returns_unit() {
963 lines.push(" invisible(x)".to_string());
964 }
965 lines.push("}".to_string());
966 lines.push(String::new());
967 }
968
969 if !static_methods.is_empty() || !consts.is_empty() {
971 lines.push(format!(
972 "{}${} <- new.env(parent = emptyenv())",
973 type_ident, trait_name
974 ));
975 lines.push(String::new());
976 }
977
978 for method in &static_methods {
980 let r_name = method.r_method_name();
981 let ctx = TraitMethodContext::new(method, type_ident, trait_name);
982
983 lines.push(format!(
984 "#' Static trait method {}::{}()",
985 trait_name, r_name
986 ));
987 let roxygen = RoxygenBuilder::new()
988 .name(format!("{}${}${}", type_str, trait_str, r_name))
989 .rdname(&type_str)
990 .build();
991 lines.extend(roxygen);
992
993 let call = ctx.static_call();
994
995 lines.push(format!(
996 "{}${}${} <- function({}) {{",
997 type_ident, trait_name, r_name, ctx.params
998 ));
999 ctx.emit_method_prelude(&mut lines, " ", &r_name);
1000 lines.extend(trait_method_body_lines(&call, " "));
1001 lines.push("}".to_string());
1002 lines.push(String::new());
1003 }
1004
1005 for trait_const in consts {
1007 let const_name = &trait_const.ident;
1008 let const_str = const_name.to_string();
1009
1010 let roxygen = RoxygenBuilder::new()
1011 .name(format!("{}${}${}", type_str, trait_str, const_str))
1012 .rdname(&type_str)
1013 .build();
1014 lines.extend(roxygen);
1015
1016 let c_ident = trait_const.c_wrapper_ident_string(type_ident, trait_name);
1017 let call = DotCallBuilder::new(&c_ident).build();
1018
1019 lines.push(format!(
1020 "{}${}${} <- function() {{",
1021 type_ident, trait_name, const_name
1022 ));
1023 lines.push(format!(" {}", call));
1024 lines.push("}".to_string());
1025 lines.push(String::new());
1026 }
1027
1028 lines.join("\n")
1029}