Repository: askama-rs/askama (Rust)

Issue:
macros fail on invocation when arguments are rust keywords
**I am using git main**

I can define this macro without issue

```handlebars
{% macro foo(type="text") %}
    <input type="{{ type }}"/>
{% endmacro foo %}

{# or this one #}

{% macro foo(type) %}
    <input type="{{ type }}"/>
{% endmacro foo %}
```
But calling them produces an error (error shown below).
```handlebars
{% call foo("input") %}
{% call foo(type="input") %}
```

```
error: expected identifier, found keyword `type`
  --> [..path]
   |
30 | #[derive(Template, Debug)]
   |          ^^^^^^^^ expected identifier, found keyword
   |
   = note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info)

error: proc-macro derive produced unparsable tokens
  --> [..path]
   |
30 | #[derive(Template, Debug)]
   |          ^^^^^^^^

error[E0609]: no field `r#type` on type `&[Struct with Template derived]`
  --> [..path]
   |
30 | #[derive(Template, Debug)]
   |          ^^^^^^^^ unknown field
   |
   = note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info)

```

Did not see any issues created in regards to this, or mention in the docs that it should be avoided. Just wanted to point it out in case it does not align with intentions.

As a user, I feel the error should at least come from the definition, and not the invocation. It does seem like the keyword is properly escaped in at least some capacity (the error references `r#type`).

Current workaround, naming the argument something else


Discussion:
Thank you for the bug report and providing a minimal reproducible example!

Confirmed, there is a `let type = …;` in the generated code, which is obviously invalid code. At some place the variable name is written without safe-guarding it with a call to [`normalize_identifier()`](https://github.com/rinja-rs/rinja/blob/b856eb8d44b9021fcd7ab123bcf8e5d1ef0d0f3a/rinja_derive/src/generator.rs#L2974-L2998). Most likely inside [`visit_call()`](https://github.com/rinja-rs/rinja/blob/b856eb8d44b9021fcd7ab123bcf8e5d1ef0d0f3a/rinja_derive/src/generator.rs#L1994-L2051).

Maybe you would like to investigate further and provide a bug fix, @chrisp60?
That sounds straightforward. I will take a look and submit a PR (as time allows). Thanks for the hints on where to start.



Patch:
diff --git a/rinja_derive/src/generator.rs b/rinja_derive/src/generator.rs
index 4161e6642..f512f61ab 100644
--- a/rinja_derive/src/generator.rs
+++ b/rinja_derive/src/generator.rs
@@ -848,8 +848,11 @@ impl<'a> Generator<'a> {
                             ("", "")
                         };
                         value.write(this.visit_expr_root(ctx, expr)?);
-                        buf.write(format_args!("let {arg} = {before}{value}{after};"));
-                        this.locals.insert_with_default(Cow::Borrowed(arg));
+                        // We need to normalize the arg to write it, thus we need to add it to
+                        // locals in the normalized manner
+                        let normalized_arg = normalize_identifier(arg);
+                        buf.write(format_args!("let {} = {before}{value}{after};", normalized_arg));
+                        this.locals.insert_with_default(Cow::Borrowed(normalized_arg));
                     }
                 }
             }


Test patch:
diff --git a/testing/tests/macro.rs b/testing/tests/macro.rs
index d977826e3..9a4b22476 100644
--- a/testing/tests/macro.rs
+++ b/testing/tests/macro.rs
@@ -282,3 +282,79 @@ fn test_default_value5() {
 
     assert_eq!(MacroDefaultValue5.render().unwrap(), "3 3\n1 1\n1 2\n");
 }
+
+// This test ensures...
+// * macro arguments can be named as rust keywords (e.g. "type", "as", "ref", etc.).
+// * the keyword is escaped at definition.
+// * the keyword is escaped at invocation.
+#[test]
+fn test_rust_keywords_as_args() {
+    #[derive(Template)]
+    #[template(
+        source = "
+{%- macro input(type) -%}
+{{ type }}
+{% endmacro -%}
+
+{%- call input(1) -%}
+{%- call input(type=1) -%}
+",
+        ext = "html"
+    )]
+    struct MacroRustKwArgs;
+
+    // primarily checking for compilation
+    assert_eq!(MacroRustKwArgs.render().unwrap(), "1\n1\n");
+}
+
+// This test ensures...
+// * macro arguments can be named as rust keywords (e.g. "type", "as", "ref", etc.).
+// * the argument has a default value.
+// * the keyword is escaped at definition.
+// * the keyword is escaped at invocation.
+#[test]
+fn test_rust_keywords_as_args_with_default() {
+    #[derive(Template)]
+    #[template(
+        source = "
+{%- macro input(type=1) -%}
+{{ type }}
+{% endmacro -%}
+
+{%- call input() -%}
+{%- call input(1) -%}
+{%- call input(type=1) -%}
+",
+        ext = "html"
+    )]
+    struct MacroRustKwArgsDefault;
+
+    // primarily checking for compilation
+    assert_eq!(MacroRustKwArgsDefault.render().unwrap(), "1\n1\n1\n");
+}
+
+// This test ensures...
+// * macro arguments can be named as rust keywords (e.g. "type", "as", "ref", etc.).
+// * a non-literal expression is given as default
+// * the keyword is escaped at definition.
+// * the keyword is escaped at invocation.
+#[test]
+fn test_rust_keywords_as_args_with_default_expr() {
+    #[derive(Template)]
+    #[template(
+        source = "
+{%- macro input(type=1+2) -%}
+{{ type }}
+{% endmacro -%}
+
+{%- call input() -%}
+{%- call input(1) -%}
+{%- call input(type=1) -%}
+",
+        ext = "html"
+    )]
+    struct MacroRustKwArgsDefaultExpr;
+
+    // primarily checking for compilation
+    assert_eq!(MacroRustKwArgsDefaultExpr.render().unwrap(), "3\n1\n1\n");
+}