Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot use extendrsrc with a macro that contains #[extendr] #350

Open
JosiahParry opened this issue Apr 9, 2024 · 5 comments
Open

Cannot use extendrsrc with a macro that contains #[extendr] #350

JosiahParry opened this issue Apr 9, 2024 · 5 comments
Milestone

Comments

@JosiahParry
Copy link
Contributor

JosiahParry commented Apr 9, 2024

```{extendrsrc macro}
macro_rules! make_heck_fn {
    ($fn_name:ident) => {
        #[extendr]
        /// @export
        fn $fn_name(x: Strings) -> Strings {
            x.into_iter()
                .map(|xi| match xi.is_na() {
                    true => Rstr::na(),
                    false => Rstr::from(xi.as_str().$fn_name()),
                })
                .collect::<Strings>()
        }
    };
}
```
Error in `purrr::map2()`:
ℹ In index: 1.
Caused by error in `extract_meta()`:
! Rust code contains invalid attribute macros.
• ✖ No valid `fn` or `impl` block found in the following sample:
•         #[extendr]
•           fn $fn_name(x: Strings) -> Strings {
•               
Backtrace:
  1. global .main()
  2. execute(...)
  3. rmarkdown::render(...)
  4. knitr::knit(knit_input, knit_output, envir = envir, quiet = quiet)
  5. knitr:::process_file(text, output)
     ...
       at rextendr/R/make_module_macro.R:22:3
 24. purrr::map2(start, end, ~extract_meta(clean_lns[.x:.y]))
 25. purrr:::map2_("list", .x, .y, .f, ..., .progress = .progress)
 29. rextendr (local) .f(.x[[i]], .y[[i]], ...)
 30. rextendr:::extract_meta(clean_lns[.x:.y])
                                                                                                             
Execution halted
@Ilia-Kosenkov
Copy link
Member

Ilia-Kosenkov commented Apr 9, 2024

It should not work like this if I am not mistaken, the contents of extendrsrc go into a generated rust function.

It could also be that it does not understand macros (which is definitely on another level).

@JosiahParry
Copy link
Contributor Author

Its worth noting that it does compile the macro iff #[extendr] is not present and it can even be called later down (pretty neat)!

@Ilia-Kosenkov
Copy link
Member

Ok I get it. The issue here is two-fold. We generate exports based on simple scanning for #[extendr] attribute. Here we find it in the macros. We do not do macros expansion so it captures macros declaration verbatim, which is garbage in this case (because of function name interpolation). We can solve it as follows:

  • Introduce syntactic trick e.g. ##[extendr] to disable metadata capture (and replace all of these with #[extendr] before compiling)
  • Introduce (if it is not added yet) support for providing extendr export block explicitly (like you do in package code)
  • By combining all of these you can actually achieve somewhat similar results, but at the cost of perhaps having another macros expanded within export block (if that even works)

@Ilia-Kosenkov
Copy link
Member

Looking at this, now I believe it could be related to function name starting with $.

@Ilia-Kosenkov Ilia-Kosenkov added this to the v0.4.0 milestone Aug 8, 2024
@Ilia-Kosenkov
Copy link
Member

It's not a 'bug', it's a 'feature'. We cannot parse deep into things like macros (since we do simple pattern search and not AST traversal), but your issue can be bypassed. You can disable export module generation and do exports yourself:

sample.Rmd
---
title: "Test Macros"
output: markdown_document
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE)
library(rextendr)
```

```{extendrsrc macro, engine.opts = list(generate_module_macro = FALSE)}
  macro_rules! make_heck_fn {
      ($fn_name:ident) => {
          #[extendr]
          /// @export
          fn $fn_name(x: Strings) -> Strings {
              x.into_iter()
                  .map(|xi| match xi.is_na() {
                      true => Rstr::na(),
                      false => Rstr::from(xi.as_str().$fn_name()),
                  })
                  .collect::<Strings>()
          }
      };
    }

  trait SomeTrait {
    fn some_fn(&self) -> String;
  }

  impl SomeTrait for &str {
    fn some_fn(&self) -> String {
      format!("'{}' is a hecking string!", self)
    }
  }

  make_heck_fn!(some_fn);

  extendr_module!{
    mod rextendr;
    fn some_fn;
  }
```

```{r}
some_fn(c("a", "b", NA))
```

knits into

sample.md
---
title: "Test Macros"
output: markdown_document
---




``` rust
  macro_rules! make_heck_fn {
      ($fn_name:ident) => {
          #[extendr]
          /// @export
          fn $fn_name(x: Strings) -> Strings {
              x.into_iter()
                  .map(|xi| match xi.is_na() {
                      true => Rstr::na(),
                      false => Rstr::from(xi.as_str().$fn_name()),
                  })
                  .collect::<Strings>()
          }
      };
    }

  trait SomeTrait {
    fn some_fn(&self) -> String;
  }

  impl SomeTrait for &str {
    fn some_fn(&self) -> String {
      format!("'{}' is a hecking string!", self)
    }
  }

  make_heck_fn!(some_fn);

  extendr_module!{
    mod rextendr;
    fn some_fn;
  }
```

``` r
some_fn(c("a", "b", NA))
```

```
## [1] "'a' is a hecking string!" "'b' is a hecking string!"
## [3] NA
```

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants