Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Filters and binary OR operations are easily confused #892

Open
GuillaumeGomez opened this issue Oct 26, 2023 · 11 comments
Open

Filters and binary OR operations are easily confused #892

GuillaumeGomez opened this issue Oct 26, 2023 · 11 comments

Comments

@GuillaumeGomez
Copy link
Contributor

GuillaumeGomez commented Oct 26, 2023

{{ -2|abs }}

is not the same as

{{ -2 | abs }}

and it's quite a huge issue as there is no reason for that and it doesn't seem to be documented (my bad, it's documented, still remains a big issue in my opinion). Could we instead turn this binary OR operation into a filter instead? That could also allow to catch a few cases like a & b.

What do you think?

@djc
Copy link
Owner

djc commented Oct 26, 2023

Could we instead turn this binary OR operation into a filter instead? That could also allow to catch a few cases like a & b.

Not sure what you're proposing here. In general I agree it's a wart in the template language but I so far haven't thought of a good alternative.

@GuillaumeGomez
Copy link
Contributor Author

Let me write code to show what I have in mind:

{{ a | b }}

would then become:

{{ a | binary_or(b) }}

Where binary_op would be a built-in filter looking like this:

fn binary_or<T: std::ops::BitOr>(a: T, b: T) -> T::Output {
    a | b
}

Alternatively, we can be a bit more lax (I hope this work exists in english...) and do:

fn binary_or<T: std::ops::BitOr, U: std::ops::BitOr>(a: T, b: U) -> T::Output {
    a | (b as T)
}

If we do this, we should do the same for AND (&), XOR (^), NOT (~), left shift (<<) and right shift (>>).

@djc
Copy link
Owner

djc commented Oct 27, 2023

Honestly, I'm not motivated to spend many cycles on this. Yes, it's a wart. Yes, it's ugly and a little bit surprising. But given that:

  1. Use of binary or in templates should be pretty rare
  2. It mostly just works if people use conventional formatting for both templates and Rust code
  3. Any alternative would have discoverability issues

I just don't feel like this is worth spending much time on.

@GuillaumeGomez
Copy link
Contributor Author

Use of binary or in templates should be pretty rare

That's pretty much why I think it should be done.

It mostly just works if people use conventional formatting for both templates and Rust code

Well, not really because I discovered it the hard way when working on the tera->askama switch on docs.rs. This is the only case where whitespace actually change the behaviour.

Any alternative would have discoverability issues

I think it's acceptable (but this is just my opinion, and a very biased one too).

If you're okay with the change itself, I can do it.

@djc
Copy link
Owner

djc commented Oct 27, 2023

Cool to hear that you're porting docs.rs! Was there any discussion on this that I can read up on? I found the PR but didn't see any issues.

What about alternative solutions? What if we had a better error message? (For example, if the right-hand value is a literal or a variable, we throw an error explaining the problem.)

@GuillaumeGomez
Copy link
Contributor Author

Cool to hear that you're porting docs.rs! Was there any discussion on this that I can read up on? I found the PR but didn't see any issues.

There was no discussion. It's just me going through the issues I encountered while porting it (and it's still not done for issues I need to understand and potentially fix in askama).

What about alternative solutions? What if we had a better error message? (For example, if the right-hand value is a literal or a variable, we throw an error explaining the problem.)

It's the first thing I thought about but that still is pretty bad and pretty hard to catch and some cases will remain problematic. For examples, you have both a local variable and a filter named x, you can't catch such cases and you might run the wrong operation and can only catch it when checking expanded code (that's how I discovered this difference).

@djc
Copy link
Owner

djc commented Oct 27, 2023

Are you saying you first encountered this while having a local variable and a filter named x? So that's a chain of edge cases IMO:

  • Using spaces around the filter operator which doesn't seem like the conventional style in either Askama or Jinja (but apparently is the default in Tera)
  • Using binary or in the first place
  • Have a variable and a filter of the same name in scope

I understand you might be frustrated by this issue but I'm still not convinced you've come up with a solution that is better.

@GuillaumeGomez
Copy link
Contributor Author

Are you saying you first encountered this while having a local variable and a filter named x?

No, I was just adding another example showing why better errors is actually not (in my opinion) a better solution. There are corner cases and the code to handle that would be quite tricky.

Using spaces around the filter operator which doesn't seem like the conventional style in either Askama or Jinja (but apparently is the default in Tera)

Good point. But then not allowing the OR operator would then make the error detection very easy and we could then suggest (I'm fine to not allow having whitespace character around |, just unhappy with it going through and failing silently):

  • to remove whitespace characters to use the filter
  • add a | if it was meant to be an OR condition
  • use binary_or filter if they want to use the binary operator

Using binary or in the first place

That sounds like something very rare in jinja templates and in my opinion is a good enough justification to turn it into a built-in filter instead.

Have a variable and a filter of the same name in scope

I think it's pretty uncommon but it's a possibility.

I understand you might be frustrated by this issue but I'm still not convinced you've come up with a solution that is better.

My main point is to turn the binary operators into built-in filters so that we can improve errors. If you have ideas, I'm very open to discuss them. I'd just like to not discover this bug after checking expanded code. ^^'

@djc
Copy link
Owner

djc commented Oct 27, 2023

Have a variable and a filter of the same name in scope

I think it's pretty uncommon but it's a possibility.

So if you agree that having a variable and filter of the same name in scope seems uncommon then I think raising a compile-time error as suggested in a previous comment would be a nice improvement.

No, I was just adding another example showing why better errors is actually not (in my opinion) a better solution. There are corner cases and the code to handle that would be quite tricky.

I feel like the strategy I outlined is reasonable and not too tricky to implement? I agree that it's not a perfect solution, but I think the one thing we can agree on is that there is no perfect solution here.

Also previously you suggested that if we do this for binary or we should also do this for other binary operators which makes this a bigger change. And note that this would be a backwards incompatible change, so even people who have perfectly good templates where | and | are used as implemented now would have to change their templates.

My main point is to turn the binary operators into built-in filters so that we can improve errors. If you have ideas, I'm very open to discuss them. I'd just like to not discover this bug after checking expanded code. ^^'

I agree that finding this out from the output being incorrect is undesirable. I just think there's no obviously better solution.

@GuillaumeGomez
Copy link
Contributor Author

So if you agree that having a variable and filter of the same name in scope seems uncommon then I think raising a compile-time error as suggested in a previous comment would be a nice improvement.

I feel like the strategy I outlined is reasonable and not too tricky to implement? I agree that it's not a perfect solution, but I think the one thing we can agree on is that there is no perfect solution here.

I don't like this approach as there is no way to ensure that what we're checking is actually valid because we don't have enough information at parsing-time, meaning we'll have to postpone it when generating code, which is again not great.

Also previously you suggested that if we do this for binary or we should also do this for other binary operators which makes this a bigger change. And note that this would be a backwards incompatible change, so even people who have perfectly good templates where | and | are used as implemented now would have to change their templates.

That's why we have semver. ;)

I agree that finding this out from the output being incorrect is undesirable. I just think there's no obviously better solution.

Tricky indeed as it is a matter of personal taste. 😆 For me, using built-in filter is better but I can understand you don't share my opinion. That said, I won't implement the new compile-time check because it's not something I believe will fix my issue but I'm curious to see how you will do it.

@malteneuss
Copy link
Contributor

As an Askama beginner i just spent too much time banging my head against the wall why a built-in filter couldn't be found:

error[E0609]: no field `truncate` on type `&MyTemplate<'a>`
 --> ...
  |
9 | #[derive(Template)]
  |          ^^^^^^^^ unknown field
  |
  = note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info)

Let's improve the documentation a tiny bit by emphasizing the usage: #1087

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants