Keats / tera

A template engine for Rust based on Jinja2/Django
http://keats.github.io/tera/
MIT License
3.54k stars 283 forks source link

Macro unexpectedly refers to an outer variable instead of an argument #770

Open agate-pris opened 2 years ago

agate-pris commented 2 years ago

Hi!

My recursive macro call causes unexpected stack overflow.

I have followed the official documentation and wrote template carefully so that infinite recursion does not occur. I am not sure what I am doing wrong.

Can someone please tell me what is wrong with the following template?

{% import "macros.txt" as macros %}

{{ macros::r(name=name, schema=schema) }}
{%- macro r(name, schema) -%}
{{ name }}
    {%- if schema.properties -%}
        {%- for name, schema in schema.properties %}
{{ self::r(name=name, schema=schema) }}
        {%- endfor -%}
    {%- endif -%}
{%- endmacro -%}
use std::str::FromStr;

fn main() {
    let tera = tera::Tera::new("*.txt").unwrap();
    let s = "{
        \"name\": \"a\",
        \"schema\": {
            \"properties\": {
                \"foo\": {
                }
            }
        } 
    }";
    let v = tera::Value::from_str(s).unwrap();
    let c = tera::Context::from_value(v).unwrap();
    let render = tera.render("template.txt", &c);
    println!("{:?}", render);
}
$ cargo run
   Compiling tera_test v0.1.0 (C:\Users\agate-pris\tera_test)
    Finished dev [unoptimized + debuginfo] target(s) in 1.32s
     Running `target\debug\tera_test.exe`

thread 'main' has overflowed its stack
error: process didn't exit successfully: `target\debug\tera_test.exe` (exit code: 0xc00000fd, STATUS_STACK_OVERFLOW)
agate-pris commented 2 years ago

It may be self-resolving.

It can explicitly interrupt the loop by changing. It will be seen that the result is different from my expectation.

{%- import "macros.txt" as macros -%}
{%- set n = 5 -%}
{{- macros::r(n=n, name=name, schema=schema) -}}

{%- macro r(n, name, schema) -%}
    {%- if n > 0 -%}
{{ name }}
        {%- if schema.properties -%}
            {%- for name, schema in schema.properties %}
{{ self::r(n=n-1, name=name, schema=schema) }}
            {%- endfor -%}
        {%- endif -%}
    {%- endif -%}
{%- endmacro -%}
a
foo
foo
foo
foo
agate-pris commented 2 years ago

OK, it resolved.

I have found that if the outer name of a macro call conflicts with the name of the macro argument, the outer name of a macro call takes precedence.

This can have unexpected results.

If this is the intended design, please close this issue.

Thank you.

{%- import "macros.txt" as macros -%}
{{- macros::r(arg_name=name, arg_schema=schema) -}}
{%- macro r(arg_name, arg_schema) -%}
{{ arg_name }}
    {%- if arg_schema.properties -%}
        {%- for name, schema in schema.properties %}
{{ self::r(arg_name=name, arg_schema=schema) }}
        {%- endfor -%}
    {%- endif -%}
{%- endmacro -%}