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

Comparison confusion between false and nil #568

Open
edev opened this issue Nov 23, 2024 · 0 comments
Open

Comparison confusion between false and nil #568

edev opened this issue Nov 23, 2024 · 0 comments

Comments

@edev
Copy link

edev commented Nov 23, 2024

liquid-rust version: 0.26.9
rust version: rustc 1.82.0 (f6e511eec 2024-10-15)
OS: Debian 12 (bookworm)

I believe I have found a bug: liquid-rust seems to equate false and nil.

My reasons for believing this is a bug are as follows:

  1. The observed behavior differs from that of Shopify's gem.
  2. The observed behavior surprises me.
  3. The observed behavior is inconsistent (as the code below demonstrates).

I've included Ruby and Rust sample code along with their outputs. I apologize for the long MWEs, but I thought clarity was more important than brevity in this case.

Also, please note that the examples below use copied and pasted templates between Ruby and Rust to ensure consistency; I haven't even touched the template indentation to match both languages' styles.

Shopify (Ruby, using gem liquid version 5.5.1)

require "liquid"

def false_first
  raw_template = \
    "{% if x == true -%}
      if:  true
    {%- elsif x == false -%}
      if: false
    {%- elsif x == nil -%}
      if:   nil
    {%- else -%}
      if:  else
    {%- endif -%}
    ; {% case x -%}
      {%- when true -%}
        case: true
      {%- when false -%}
        case: false
      {%- when nil -%}
        case: nil
      {%- else -%}
        case: else
    {%- endcase %}"

  template = Liquid::Template.parse(raw_template)
  
  puts "=== Comparison order: true, false, nil ==="
  puts "[true]  #{template.render('x' => true)}"
  puts "[false] #{template.render('x' => false)}"
  puts "[nil]   #{template.render('x' => nil)}"
  puts
end

def nil_first
  raw_template = \
    "{% if x == true -%}
      if:  true
    {%- elsif x == nil -%}
      if:   nil
    {%- elsif x == false -%}
      if: false
    {%- else -%}
      if:  else
    {%- endif -%}
    ; {% case x -%}
      {%- when true -%}
        case: true
      {%- when nil -%}
        case: nil
      {%- when false -%}
        case: false
      {%- else -%}
        case: else
    {%- endcase %}"

  template = Liquid::Template.parse(raw_template)
  
  puts "=== Comparison order: true, nil, false ==="
  puts "[true]  #{template.render('x' => true)}"
  puts "[false] #{template.render('x' => false)}"
  puts "[nil]   #{template.render('x' => nil)}"
  puts
end

false_first
nil_first

Ruby output

=== Comparison order: true, false, nil ===
[true]  if:  true; case: true
[false] if: false; case: false
[nil]   if:   nil; case: nil

=== Comparison order: true, nil, false ===
[true]  if:  true; case: true
[false] if: false; case: false
[nil]   if:   nil; case: nil

Rust (using liquid-rust version 0.26.9):

fn main() {
    false_first();
    nil_first();
}

fn false_first() {
    let raw_template =
        "{% if x == true -%}
          if:  true
        {%- elsif x == false -%}
          if: false
        {%- elsif x == nil -%}
          if:   nil
        {%- else -%}
          if:  else
        {%- endif -%}
        ; {% case x -%}
          {%- when true -%}
            case: true
          {%- when false -%}
            case: false
          {%- when nil -%}
            case: nil
          {%- else -%}
            case: else
        {%- endcase %}";

    let template = liquid::ParserBuilder::with_stdlib()
        .build()
        .unwrap()
        .parse(raw_template)
        .unwrap();

    println!("=== Comparison order: true, false, nil ===");
    println!("[true]        {}", template.render(&liquid::object!({ "x": true })).unwrap());
    println!("[false]       {}", template.render(&liquid::object!({ "x": false })).unwrap());
    println!("[Some(true)]  {}", template.render(&liquid::object!({ "x": Some(true) })).unwrap());
    println!("[Some(false)] {}", template.render(&liquid::object!({ "x": Some(false) })).unwrap());
    println!("[None]        {}", template.render(&liquid::object!({ "x": None::<bool> })).unwrap());
    println!();
}

fn nil_first() {
    let raw_template =
        "{% if x == true -%}
          if:  true
        {%- elsif x == nil -%}
          if:   nil
        {%- elsif x == false -%}
          if: false
        {%- else -%}
          if:  else
        {%- endif -%}
        ; {% case x -%}
          {%- when true -%}
            case: true
          {%- when nil -%}
            case: nil
          {%- when false -%}
            case: false
          {%- else -%}
            case: else
        {%- endcase %}";

    let template = liquid::ParserBuilder::with_stdlib()
        .build()
        .unwrap()
        .parse(raw_template)
        .unwrap();

    println!("=== Comparison order: true, nil, false ===");
    println!("[true]        {}", template.render(&liquid::object!({ "x": true })).unwrap());
    println!("[false]       {}", template.render(&liquid::object!({ "x": false })).unwrap());
    println!("[Some(true)]  {}", template.render(&liquid::object!({ "x": Some(true) })).unwrap());
    println!("[Some(false)] {}", template.render(&liquid::object!({ "x": Some(false) })).unwrap());
    println!("[None]        {}", template.render(&liquid::object!({ "x": None::<bool> })).unwrap());
    println!();
}

Rust output

=== Comparison order: true, false, nil ===
[true]        if:  true; case: true
[false]       if: false; case: false
[Some(true)]  if:  true; case: true
[Some(false)] if: false; case: false
[None]        if: false; case: false

=== Comparison order: true, nil, false ===
[true]        if:  true; case: true
[false]       if:   nil; case: nil
[Some(true)]  if:  true; case: true
[Some(false)] if:   nil; case: nil
[None]        if:   nil; case: nil

Expected output

=== Comparison order: true, false, nil ===
[true]        if:  true; case: true
[false]       if: false; case: false
[Some(true)]  if:  true; case: true
[Some(false)] if: false; case: false
[None]        if:   nil; case: nil

=== Comparison order: true, nil, false ===
[true]        if:  true; case: true
[false]       if: false; case: false
[Some(true)]  if:  true; case: true
[Some(false)] if: false; case: false
[None]        if:   nil; case: nil

Use case

I have an optional Boolean value and need to distinguish Some(true), Some(false), and None.

Workaround

I map the value to a string literal before passing it to liquid::object!, and then I perform string comparison in my template, e.g. "nil" instead of nil.

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

1 participant