djc / askama

Type-safe, compiled Jinja-like templates for Rust
Apache License 2.0
3.47k stars 219 forks source link

Macro with unknown variants in match block compiles #903

Closed gregorydemay closed 11 months ago

gregorydemay commented 1 year ago

Summary

Macro with a match block behaves unexpectedly: when a variant is not recognized, it seems that the first variant in the enum definition is taken instead of failing compilation.

Environment

Observed with askama version 0.12.1 and rustc 1.73.0 (stable-aarch64-apple-darwin unchanged)

Steps to reproduce

Based on the hello world starter template described here:

In templates/hello.html: consider a macro which based on an enum variant displays one link or another:

{% macro etherscan_link -%}
{% match network %}
  {%- when Sepolia -%}
  <a href="https://sepolia.etherscan.io">Link</a>
  {%- when Mainnet -%}
  <a href="https://etherscan.io/address">Link</a>
{% endmatch %}
{%- endmacro %}

Here is the {% call etherscan_link() %}

In main.rs, the file is adjusted to contain the enum definition with 2 variants and the template is instantiated with the second variant.

use askama::Template;

enum EthereumNetwork{
    Sepolia,
    Mainnet
}

#[derive(Template)]
#[template(path = "hello.html")]
struct HelloTemplate<'a> {
    network: EthereumNetwork,
    name: &'a str,
}

fn main() {
    let hello = HelloTemplate { name: "world", network: EthereumNetwork::Mainnet };
    println!("{}", hello.render().unwrap());
}

Expected Result

cargo runs should return

Here is the <a href="https://etherscan.io/address">Link</a>

or compilation should fail if the match pattern in the macro is not recognized.

Actual Result

cargo run wrongly returns

Here is the <a href="https://sepolia.etherscan.io">Link</a>

which would be the link for EthereumNetwork::Sepolia, even though the template is instantiated with the other variant EthereumNetwork::Mainnet.

The same behaviour can be observed if the match pattern in the macro is completely wrong

{% macro etherscan_link -%}
{% match network %}
  {%- when Wrong1 -%}
  <a href="https://sepolia.etherscan.io">Link</a>
  {%- when Wrong2 -%}
  <a href="https://etherscan.io/address">Link</a>
{% endmatch %}
{%- endmacro %}

Workaround

For the macro to function as expected it seems like the full path in each variant is required:

{% macro etherscan_link -%}
{% match network %}
  {%- when EthereumNetwork::Sepolia -%}
  <a href="https://sepolia.etherscan.io">Link</a>
  {%- when EthereumNetwork::Mainnet -%}
  <a href="https://etherscan.io/address">Link</a>
{% endmatch %}
{%- endmacro %}

In that case cargo run returns the expected link

Here is the <a href="https://etherscan.io/address">Link</a>
djc commented 1 year ago

This is basically what happens in Rust code, too. You should be able to write analogous Rust code, which will fail the same way.

gregorydemay commented 12 months ago

This is basically what happens in Rust code, too. You should be able to write analogous Rust code, which will fail the same way.

I think here is depends exactly on how the match pattern is written. For example (see Rust playground):

#[derive(Debug)]
enum EthereumNetwork{
    Sepolia,
    Mainnet
}

fn main() {
   let network = EthereumNetwork::Mainnet;

   let url = match &network {
       Sepolia => "https://sepolia.etherscan.io",
       Mainnet => "https://etherscan.io/address"
   };

   println!("URL {} for network {:?}", url, network);
}

does not compile

Compiling playground v0.0.1 (/playground)
error[E0170]: pattern binding `Sepolia` is named the same as one of the variants of the type `EthereumNetwork`
  --> src/main.rs:11:8
   |
11 |        Sepolia => "https://sepolia.etherscan.io",
   |        ^^^^^^^ help: to match on the variant, qualify the path: `EthereumNetwork::Sepolia`
   |
   = note: `#[deny(bindings_with_variant_name)]` on by default

It's true that if the match pattern is totally wrong like in here

let url = match &network {
      Wrong1 => "https://sepolia.etherscan.io",
      Wrong2 => "https://etherscan.io/address"
 };

then it does compile, but with some useful warnings

Compiling playground v0.0.1 (/playground)
warning: unreachable pattern
  --> src/main.rs:17:7
   |
16 |       Wrong1 => "https://sepolia.etherscan.io",
   |       ------ matches any value
17 |       Wrong2 => "https://etherscan.io/address"
   |       ^^^^^^ unreachable pattern
   |
   = note: `#[warn(unreachable_patterns)]` on by default
djc commented 12 months ago

Sure -- if you want to make a PR to improve on the current situation I'm open to that but I won't have time to work on this anytime soon.

gregorydemay commented 11 months ago

Sure -- if you want to make a PR to improve on the current situation

I don't really know the internals of Askama, could you point me in the right direction? I can then try to come up with something when I have time.

djc commented 11 months ago

Code generation for match blocks is here:

https://github.com/djc/askama/blob/main/askama_derive/src/generator.rs#L544

Happy to answer other questions if you have them.

Kijewski commented 11 months ago

Duplicate of https://github.com/djc/askama/issues/711. Adding #[warn(unreachable_patterns)] in generated code is suppressed.