askama icon indicating copy to clipboard operation
askama copied to clipboard

{% match %} behavior is very confusing when wrong {% when %} values given

Open itamarst opened this issue 3 years ago • 14 comments

Consider the following working templte:

{% match team.subscription_state %}
    {% when SubscriptionState::New %}
    New!
    {% when SubscriptionState::Active %}
    Active!
{% endmatch %}

Originally I had mistakenly omitted the enum type, so I had:

{% match team.subscription_state %}
    {% when New %}
    New!
    {% when Active %}
    Active!
{% endmatch %}

Instead of complaining "I don't know what New is", it just silently always chose the first entry.

itamarst avatar Aug 03 '22 15:08 itamarst

Can you show what the generated code look like? I definitely agree that this seems like a surprising result!

djc avatar Aug 03 '22 19:08 djc

It is impossible to determine what enum/struct type the value is. E.g.

match x.try_into() {
    Ok(_) => …,
    Err(_) => …
}

only compiles, because the prelude contains pub use Result::{Ok, Err};, but it's impossible to do the same for every possible type.

Kijewski avatar Aug 03 '22 22:08 Kijewski

Note that this is just the same thing that happens if you write this in Rust:

match team.subscription_state {
    New => "New!",
    Active => "Active!",
}

Will always yield "New!", because the match statement will interpret New as a variable binding the value of subscription_state instead of any variant name.

We could supposedly return an error at compile time if the variant is not one of Ok, Err, Some, None and not a path, but this would be bad for people who actually expect the current behavior (by using locally imported enum variants).

So I don't think we want to change this behavior, because it just matches what Rust does.

djc avatar Aug 04 '22 11:08 djc

Maybe we could emit #[warn(unreachable_patterns)] in the generated code? Won't break existing code, but at least warn the user that something might be off. I haven't tested if the explicit opt-in to the warning is still silently ignored in generated code, though.

Kijewski avatar Aug 04 '22 13:08 Kijewski

Nope, not even buf.writeln("#![forbid(unreachable_patterns)]")?; in generator::impl_template() works.

Kijewski avatar Aug 04 '22 15:08 Kijewski

That's a fun footgun. Maybe warning about unused variables would catch that? And, checking, yes it does in Rust, but if unreachable patterns warning won't help in Askama, neither would that warning...

itamarst avatar Aug 04 '22 16:08 itamarst

One option that only just now came to my mind:

{% match x %}
    {% when Some(x) %}
        x = {{x}}
    {% when None {} %}
        None
{% endmatch %}

Unit structs and unit variants can be used with Name {}, as if they were a regular struct with no elements. This can be used to differentiate unit variants and variables, but it has to be done by the user, because askama would not be able to figure out if the user actually meant to use an upper case variable name.

Kijewski avatar Jun 12 '23 10:06 Kijewski

How do we write a when clause with more than one alternative? The book does not say.

martinellison avatar Nov 01 '23 06:11 martinellison

@martinellison I'm not entirely sure if I interpret what you mean correctly, so feel free to correct me. Are you asking how to use match and when with tuples, i.e. to match on multiple things in the same arm/when?

If yes, then consider the following Rust code:

let foo: Option<bool> = ...;
let bar: Option<bool> = ...;

match (foo bar) {
    (Some(foo), Some(false)) => {}
    (Some(true), b) => {}
    (None, Some(b)) => {}
    _ => {},
}

In Askama, that can be expressed like this:

{% match (foo, bar) %}
    {% when (Some(foo), Some(false)) %}
        ...
    {% when (Some(true), b) %}
        ...
    {% when (None, Some(b)) %}
        ...
    {% else %}
        ...
{% endmatch %}

vallentin avatar Nov 01 '23 14:11 vallentin

Thanks, that is what I meant. Perhaps you could add this to the syntax section of the book.

Regards, Martin Ellison (he/him)

On Wed, 1 Nov 2023, 22:37 Christian Vallentin, @.***> wrote:

@martinellison https://github.com/martinellison I'm not entirely sure if I interpret what you mean correctly, so feel free to correct me. Are you asking how to use match and when with tuples, i.e. to match on multiple things in the same arm/when?

If yes, then consider the following Rust code:

let foo: Option = ...;let bar: Option = ...; match (foo bar) { (Some(foo), Some(false)) => {} (Some(true), b) => {} (None, Some(b)) => {} _ => {},}

In Askama, that can be expressed like this:

{% match (foo, bar) %} {% when (Some(foo), Some(false)) %} ... {% when (Some(true), b) %} ... {% when (None, Some(b)) %} ... {% else %} ...{% endmatch %}

— Reply to this email directly, view it on GitHub https://github.com/djc/askama/issues/711#issuecomment-1789071214, or unsubscribe https://github.com/notifications/unsubscribe-auth/ACEA3AT77DTW7SZU62XLEEDYCJNAPAVCNFSM55PIYET2U5DIOJSWCZC7NNSXTN2JONZXKZKDN5WW2ZLOOQ5TCNZYHEYDOMJSGE2A . You are receiving this because you were mentioned.Message ID: @.***>

martinellison avatar Nov 02 '23 05:11 martinellison

Sorry that is not what I meant.

I mean the case of

enum Suit {Clubs, Diamonds, Hearts, Spades}
match suit {Suit::Clubs | Suit::Diamonds => ...

Regards, Martin Ellison (he/him)

On Thu, 2 Nov 2023, 13:27 Martin Ellison, @.***> wrote:

Thanks, that is what I meant. Perhaps you could add this to the syntax section of the book.

Regards, Martin Ellison (he/him)

On Wed, 1 Nov 2023, 22:37 Christian Vallentin, @.***> wrote:

@martinellison https://github.com/martinellison I'm not entirely sure if I interpret what you mean correctly, so feel free to correct me. Are you asking how to use match and when with tuples, i.e. to match on multiple things in the same arm/when?

If yes, then consider the following Rust code:

let foo: Option = ...;let bar: Option = ...; match (foo bar) { (Some(foo), Some(false)) => {} (Some(true), b) => {} (None, Some(b)) => {} _ => {},}

In Askama, that can be expressed like this:

{% match (foo, bar) %} {% when (Some(foo), Some(false)) %} ... {% when (Some(true), b) %} ... {% when (None, Some(b)) %} ... {% else %} ...{% endmatch %}

— Reply to this email directly, view it on GitHub https://github.com/djc/askama/issues/711#issuecomment-1789071214, or unsubscribe https://github.com/notifications/unsubscribe-auth/ACEA3AT77DTW7SZU62XLEEDYCJNAPAVCNFSM55PIYET2U5DIOJSWCZC7NNSXTN2JONZXKZKDN5WW2ZLOOQ5TCNZYHEYDOMJSGE2A . You are receiving this because you were mentioned.Message ID: @.***>

martinellison avatar Nov 02 '23 05:11 martinellison

Yeah, unfortunately we don't support | in patterns for now. If you want to work on this, the parser is here: https://github.com/djc/askama/blob/main/askama_parser/src/node.rs#L263. If you want to take a whack at designing a syntax for this and submitting a PR, I'd be happy to review it.

djc avatar Nov 06 '23 08:11 djc

Dirkjan

OK thanks, I'm looking into it. It seems that Tera and Jinja do not implement match at all. Probably using 'or' for '|' might be good, as askama seems to use keywords rather than symbols. I'll investigate further.

Regards, Martin

On Mon, 6 Nov 2023 at 16:53, Dirkjan Ochtman @.***> wrote:

Yeah, unfortunately we don't support | in patterns for now. If you want to work on this, the parser is here: https://github.com/djc/askama/blob/main/askama_parser/src/node.rs#L263. If you want to take a whack at designing a syntax for this and submitting a PR, I'd be happy to review it.

— Reply to this email directly, view it on GitHub https://github.com/djc/askama/issues/711#issuecomment-1794340598, or unsubscribe https://github.com/notifications/unsubscribe-auth/ACEA3AS6WKELYWNSHBQGCOTYDCQQDAVCNFSM55PIYET2U5DIOJSWCZC7NNSXTN2JONZXKZKDN5WW2ZLOOQ5TCNZZGQZTIMBVHE4A . You are receiving this because you were mentioned.Message ID: @.***>

martinellison avatar Nov 08 '23 03:11 martinellison