djc / askama

Type-safe, compiled Jinja-like templates for Rust

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Rust macro has no access to parameters passed into `{% macro f(param) %}`

jonathanslenders opened this issue · comments

Not sure if this is a question, feature request or a bug.

From the documentation it's not clear to me in what scope rust macros are evaluated.
For instance, I have this Askama macro, which calls a Rust macro internally:

{% macro stop(entity) %}
    {{ create_action!(entity, LightOn, {brightness:Some(0.1), color_temperature: None})|json }}
{% endmacro %}

In some situations this works perfectly, while in other situations this does not and results in a "cannot find value entity in this scope". (entity being the argument passed to the Askama macro.)

It does work for one situation where this macro is called in another template within a loop like this: {% for entity in entities.values() %}. In any case, I've the impression that it doesn't pick up the argument passed to the Askama macro.

Is that understanding correct? Is there something we can improve here to make this possible?

That certainly sounds surprising. Do you have a minimal reproducing example?

This is the simplest reproducing example I could get:

macro_rules! test_macro{
    ($entity:expr) => {
        {
            println!("{:?}", &$entity);
        }
    }
}

#[derive(Template)]
#[template(source = "{{ test_macro!(entity) }}", ext = "txt")]
struct TestTemplate<'a> {
    entity: &'a str,
}

which results in:

error[E0425]: cannot find value `entity` in this scope
  --> src/.../main.rs:29:10
   |
29 | #[derive(Template)]

It looks like it works if I call the macro using self.entity, but this is not what I want, because I'd like to reuse the template in a situation where it's nested in a loop.

Oh I see the issue. Since it's a macro, we can't know if entity is from self or just a token (for a name you're generating for example?). I think this code would work in all cases:

let entity = entity;
{{ test_macro!(entity) }}

I assume you meant:

{% let entity = entity; %}
{{ test_macro!(entity) }}

which seems to work indeed.

Would it be possible for Askama to automatically bind all struct fields from the template struct to local variables with the same name so that macros will work automatically?

I assume you meant:
...

Yes I did. ^^'

Would it be possible for Askama to automatically bind all struct fields from the template struct to local variables with the same name so that macros will work automatically?

It'd be problematic for different reasons:

  • If you have a big struct, it'll generate a lot of variables that might not be used. Lot of dead code in short, meaning bigger code to compile, meaning longer compile time.
  • It could lead to surprising/unwanted usage of variables. For example you use a variable named x in a template you include. Variable you're supposed to define yourself which you do normally because of the error. But if you type has an x field, then it'll just be a surprising (and likely unwanted) result.

What I suggest is instead to add your use case as an example for macros in the askama book. What do you think?

Thanks for the feedback! I understand. Feel free to add it to the book!