Repository: askama-rs/askama (Rust)

Issue:
Inconsistent (or incorrect) reference semantics of (probably) `?`
Thanks for fixing #386, however as it turns out, my attempt to simplify the demonstration code revealed unrelated problem, and my original problem was not fixed. It looks more related to `?` now, but I'm no longer sure.

Here's more elaborate demonstration, closer to my code:
```rust
use askama::Template;
use serde_json::Value;

#[derive(Template)]
#[template(
    ext = "html",
    source = "
{%- macro p(v) %}{% if v == \"foo\" %}{% endif %}{% endmacro -%}

{%- if v.as_str().unwrap() == \"foo\" %}{% endif -%}
{%- call p(v.as_str().unwrap()) -%}

{%- if v.as_str().ok_or(\"error\")? == \"foo\" %}{% endif -%}
{%- call p(v.as_str().ok_or(\"error\")?) -%}

{%- let v1 = v.as_str().unwrap() %}
{%- if v1 == \"foo\" %}{% endif -%}
{%- call p(v1) -%}

{%- let v2 = v.as_str().ok_or(\"error\")? %}
{%- if v2 == \"foo\" %}{% endif -%}
{%- call p(v2) -%}
"
)]
struct Test {
    v: serde_json::Value,
}

fn main() {
    println!("{}", Test{v: Value::String("foo".to_string())}.render().unwrap());
}
```

like the previous example, this worked with askama 0.12 - all cases were treated as `&str`.

With askama 0.13.0 (both release and master after #386 fix), however, commented out lines do not compile, because an extra reference is added:
```rust
use askama::Template;
use serde_json::Value;

#[derive(Template)]
#[template(
    ext = "html",
    source = "
{%- macro p(v) %}{% if v == \"foo\" %}{% endif %}{% endmacro -%}

{%- if v.as_str().unwrap() == \"foo\" %}{% endif -%}
{%- call p(v.as_str().unwrap()) -%}

{%- if v.as_str().ok_or(\"error\")? == \"foo\" %}{% endif -%}
{#%- call p(v.as_str().ok_or(\"error\")?) -%#}

{%- let v1 = v.as_str().unwrap() %}
{%- if v1 == \"foo\" %}{% endif -%}
{%- call p(v1) -%}

{%- let v2 = v.as_str().ok_or(\"error\")? %}
{#%- if v2 == \"foo\" %}{% endif -%#}
{#%- call p(v2) -%#}
"
)]
struct Test {
    v: serde_json::Value,
}

fn main() {
    println!("{}", Test{v: Value::String("foo".to_string())}.render().unwrap());
}
```
So now it looks more like the problem is with `?` operator, which, the same way as unwrap, returns `&str`, but it's mistreated and another reference is added both when it's passed to macro and when it's used in `let` expression (but not when it's used in expression directly).


Discussion:
Gonna take a look at it in the next days then.
Code can be reduced to:

```rust
#[derive(Template)]
#[template(
    source = r#"
{%- let v2 = v.as_str().ok_or("error")? %}
{%- if v2 == "foo" %}{% endif -%}
"
)]
"#,
    ext = "txt"
)]
struct X {
    v: B,
}

struct B;
impl B {
    fn as_str(&self) -> Option<&'static str> { Some("") }
}
```
Fix confirmed, thanks a lot!



Patch:
diff --git a/askama_derive/src/generator/node.rs b/askama_derive/src/generator/node.rs
index 7c4eea983..a194cb801 100644
--- a/askama_derive/src/generator/node.rs
+++ b/askama_derive/src/generator/node.rs
@@ -967,7 +967,8 @@ impl<'a> Generator<'a, '_> {
         self.visit_target(buf, true, true, &l.var);
         // If it's not taking the ownership of a local variable or copyable, then we need to add
         // a reference.
-        let (before, after) = if !matches!(**val, Expr::Var(name) if self.locals.get(name).is_some())
+        let (before, after) = if !matches!(**val, Expr::Try(..))
+            && !matches!(**val, Expr::Var(name) if self.locals.get(name).is_some())
             && !is_copyable(val)
         {
             ("&(", ")")
diff --git a/book/src/template_syntax.md b/book/src/template_syntax.md
index 7da466982..c5fb35f93 100644
--- a/book/src/template_syntax.md
+++ b/book/src/template_syntax.md
@@ -74,6 +74,7 @@ to prevent changing ownership. The rules are as follows:
  * If the value is a variable defined in the templates, it WILL NOT BE put behind a reference.
  * If the value has a filter applied to it (`x|capitalize`), it WILL NOT BE put behind a reference.
  * If the value is a field (`x.y`), it WILL BE put behind a reference.
+ * If the expression ends with a question mark (like `x?`), it WILL NOT BE put behind a reference.
 
 ## Filters
 


Test patch:
diff --git a/testing/tests/vars.rs b/testing/tests/vars.rs
index 5e3b4325f..5a5afb06e 100644
--- a/testing/tests/vars.rs
+++ b/testing/tests/vars.rs
@@ -196,3 +196,29 @@ fn test_moving_local_vars() {
     };
     assert_eq!(x.render().unwrap(), "ABCDEFG");
 }
+
+// Ensure that if a variable initialization expression ends with a `?`, we don't put it behind
+// a reference.
+#[test]
+fn test_variable_from_question_mark_init_expr() {
+    #[derive(Template)]
+    #[template(
+        source = r#"
+{%- let v2 = v.as_str().ok_or("error")? %}
+{%- if v2 == "foo" %}1{% endif -%}
+"#,
+        ext = "txt"
+    )]
+    struct X {
+        v: B,
+    }
+
+    struct B;
+    impl B {
+        fn as_str(&self) -> Option<&'static str> {
+            Some("foo")
+        }
+    }
+
+    assert_eq!(X { v: B }.render().unwrap(), "1");
+}