tera icon indicating copy to clipboard operation
tera copied to clipboard

Inheritance breaks macro imports with "Macro namespace `macros` was not found in template `parent`"

Open nabijaczleweli opened this issue 5 years ago • 7 comments

Tested on 5dc12afc4e0e506183898fc56d3bdc9ce6a0b6d7 (current HEAD) and 1.3.1 off Crates.io.

Given the following setup:

{# parent #}
{% block henlo %}
henlo
{% endblock %}

{# macro #}
{% macro benlo() %}
trenlo
{% endmacro benlo %}

{# child #}
{% extends "parent" %}
{% import "macro" as macros %}

{% block henlo %}
{{ super() }}
menlo
{{ macros::benlo() }}
{% endblock %}

Running this (based on the "basic" example):

extern crate tera;
#[macro_use]
extern crate lazy_static;

use std::error::Error;
use tera::{Context, Tera};

lazy_static! {
    pub static ref TEMPLATES: Tera = {
        let mut tera = match Tera::new("templates/**/*") {
            Ok(t) => t,
            Err(e) => {
                println!("Parsing error(s): {}", e);
                ::std::process::exit(1);
            }
        };
        tera.autoescape_on(vec!["html", ".sql"]);
        tera
    };
}

fn main() {
    let context = Context::new();
    match TEMPLATES.render("child", &context) {
        Ok(s) => println!("{:?}", s),
        Err(e) => {
            println!("Error: {}", e);
            let mut cause = e.source();
            while let Some(e) = cause {
                println!("Reason: {}", e);
                cause = e.source();
            }
        }
    };
}

Yields

nabijaczleweli@tarta:~/uwu/tera/examples$ cargo run --example ekspiacja
    Finished dev [unoptimized + debuginfo] target(s) in 0.06s
     Running `/home/nabijaczleweli/uwu/tera/target/debug/examples/ekspiacja`
Error: Failed to render 'child' (error happened in 'parent').
Reason: Macro namespace `macros` was not found in template `parent`. Have you maybe forgotten to import it, or misspelled it?

which is ungood

However, removing the inheritance from child:

{# child, once again for the second time #}
{% import "macro" as macros %}

{% block henlo %}
menlo
{{ macros::benlo() }}
{% endblock %}

Yields

nabijaczleweli@tarta:~/uwu/tera/examples$ cargo run --example ekspiacja
    Finished dev [unoptimized + debuginfo] target(s) in 0.06s
     Running `/home/nabijaczleweli/uwu/tera/target/debug/examples/ekspiacja`
"\nmenlo\n\ntrenlo\n\n\n"

as expected

nabijaczleweli avatar Jul 08 '20 00:07 nabijaczleweli

Sorry for the late reply. I haven't checked the cause of the error but macros are meant to be defined on separate files and not really used that way, I wouldn't be surprised some weird interactions are happening there

Keats avatar Jul 25 '20 14:07 Keats

Well, the examples only define macros in separate files, so that's a good start.

nabijaczleweli avatar Jul 25 '20 16:07 nabijaczleweli

I think I've encountered the same bug. Can't quite make sense from the example so my case might be slightly different, but these probably have the same root cause; looks like the namespaces aren't tracked properly when the caller is using inheritance. My case has one more level of macros. The error message about a missing macro is pointing to a caller template, not the macro template. The macro files have only macros in them.

I've got page-a.html that extends base.html. In a block from the base, a macro (outer) is used from macro_a_level0.html. This macro calls a macro (inner) from macro_a_level1.html, which again calls a macro from that file (deeper).

The self::deeper() invocation fails because Tera is looking for deeper() in page_a.html.

Here's a full repository with a minimal-ish repro to help with the many files: https://github.com/sooda/bugs/tree/tera/macro-namespace-inheritance - just cargo run to see what happens. [edit: link changed a bit]

Pasting a few relevant bits below. Output:

let rendered_a = tera.render("page_a.html", &cx);
let rendered_b = tera.render("page_b.html", &cx);
// A: Err(Error { kind: Msg("Failed to render \'page_a.html\': error while rendering macro `level1::inner`"), source: Some(Error { kind: Msg("Macro `self::deeper` not found in template `page_a.html`"), source: None }) })
println!("A: {:?}", rendered_a);
// B: Ok("\n\n\n\nhello from b\n\n\n\n\n")
println!("B: {:?}", rendered_b);

Both should work, but only the B variant does. The difference between page-a.html and page-b.html is that page-b.html includes also the innermost macro file. That's an acceptable workaround for me.

$ diff -u page_a.html page_b.html
--- page_a.html 2020-08-11 22:49:06.065862910 +0300
+++ page_b.html 2020-08-11 22:49:28.049015276 +0300
@@ -1,5 +1,6 @@
 {% extends "base.html" %}
-{% import "macro_a_level0.html" as level0 %}
+{% import "macro_b_level1.html" as level1 %}
+{% import "macro_b_level0.html" as level0 %}
 {% block content %}
 {{ level0::outer() }}
 {% endblock content %}

The other files are essentially identical between a and b.

sooda avatar Aug 11 '20 20:08 sooda

Here's another interesting repro case: multiple macro files imported from the rendered page, but no import recursion. https://github.com/sooda/bugs/tree/tera/macro-namespace-inheritance-v2

Case: page_a.html includes macro_bar.html as bar and macro_a_foo.html as foo, calls foo::outer(). outer() calls self::inner() that's in the same file. Expected: inner is found and gets rendered. Result:

Err(Error { kind: Msg("Failed to render \'page_a.html\': error while rendering macro `foo::outer`"), source: Some(Error { kind: Msg("Macro `self::inner` not found in template `page_a.html`"), source: None }) })

Changing self:: to foo:: "fixes" this: foo::inner() is obviously found in page_a.html because that page imports foo.

Deleting the bar import "fixes" this too as page_b.html in the above repo demonstrates, but what if its contents are really needed? (macro_bar.html needs to have some content for the bug to occur; this didn't repro with an empty file.)

Swapping the order of the foo and bar imports would also fix things in this particular case, but if both macro files use self::, there is no order that would work for both simultaneously. The first trick to change self to the name of the import does help though.

sooda avatar Aug 13 '20 16:08 sooda

@sooda thanks for the debugging! Can you recreate those examples as testcases in a PR?

Keats avatar Aug 14 '20 09:08 Keats

These occurred in normal use so there wasn't much debugging needed :) the actual bug still needs to be found and fixed.

See PR #548 for two commits. The first one is for the latter case and the second one is for the nested more complicated trouble. Not all of those tests obviously pass yet. I've add a few comments between the lines to explain the details.

sooda avatar Aug 14 '20 13:08 sooda

Looks like the issue is that loading macro namespaces in Tera is currently a bit stupid. I have no recollection at all of that part of code so anyone will go as fast as me if they want to fix it. Thanks to @sooda there are tests for it in https://github.com/Keats/tera/pull/548

Note to myself: Tera v2 should have fewer restrictions for macros in general

Keats avatar Dec 02 '20 18:12 Keats