Closed tobywf closed 2 months ago
I think I have a bit of a handle on this. The templates are parsed to this:
Template {
name: "one.html",
instructions: [
00000 | LoadConst("foobar") [line 1],
00001 | ApplyFilter("nop", 1, 0),
00002 | Emit,
],
blocks: {},
initial_auto_escape: None,
}
Template {
name: "two.html",
instructions: [
00000 | LoadConst("") [line 1],
00001 | ApplyFilter("reverse", 1, 0),
00002 | StoreLocal("foo"),
00003 | LoadConst("one.html"),
00004 | LoadBlocks,
],
blocks: {},
initial_auto_escape: None,
}
Simplifying wildly, LoadBlocks
basically causes the instructions to be appended:
LoadConst(""),
ApplyFilter("reverse", 1, 0),
StoreLocal("foo"),
LoadConst("one.html"),
LoadBlocks ->
LoadConst("foobar"),
ApplyFilter("nop", 1, 0),
Emit,
The problem is that both ApplyFilter
instructions have the same local_id
of 0
, and that filter lookups are cached via loaded_filters
/ get_or_lookup_local
in the VM. Unfortunately, this causes the incorrect filter to be returned.
If I modify the VM to flush loaded_filters
when a LoadBlocks
instruction is processed, or when parent_instructions
are loaded, then the issue goes away. Tests suffer from the same issue due to loaded_tests
:
fn main() {
let mut env = minijinja::Environment::empty();
env.add_test("odd", minijinja::tests::is_odd);
env.add_test("even", minijinja::tests::is_even);
const ONE: &str = "{{ 1 is odd }}";
const TWO: &str = "{% set foo = 1 is even %}{% extends 'one.html' %}";
env.add_template("one.html", ONE).unwrap();
env.add_template("two.html", TWO).unwrap();
let template = env.get_template("one.html").unwrap();
println!("{}", template.render(minijinja::context! {}).unwrap());
let template = env.get_template("two.html").unwrap();
println!("{}", template.render(minijinja::context! {}).unwrap());
}
This prints true
and false
. I'll try and prepare a PR.
Just for obviousness, a set
statement is not required. Any statement that uses a filter or a test can trigger this, e.g.:
const ONE: &str = "{{ 'foobar' | nop }}";
const TWO: &str = "{% extends 'one.html' %}{{ '' | reverse }}";
Well spotted. I will apply a slightly modified version of your PR.
Description
It's probably best to just see the reproduction. As far as I can tell, using
{% set foo = "value" | <filter> %}
in a template that extends another affects filters in the base template.Reproduction steps
Rust minimum working example with
minijinja = "=2.1.1"
inCargo.toml
:This prints "foobar" and "raboof" (!).
It does not matter where the set statement is, so this also produces the same result:
For completeness, Python/Jinja2 3.1.4 minimum working example:
Prints
foobar
andfoobar
as expected.Additional helpful information:
As far as I can see, 0.25.0 works as expected, and 0.26.0 introduced this behaviour.
What did you expect
I'd expect the example to print "foobar" in both cases, as the Python example and version 0.25.0 does.