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

Changed Ord to have a more consistent order #579

Merged
merged 2 commits into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,18 @@

All notable changes to MiniJinja are documented here.

## 2.3.1

- Fixes some compiler warnings in Rust 1.81. #575

## 2.3.0

- Fixes some compiler warnings in Rust 1.81. #575
- Fixes incorrect ordering of maps when the keys of those maps
were not in consistent order. #569
- Implemented the missing `groupby` filter. #570
- The `unique` filter now is case insensitive by default like in
Jinja2 and supports an optional flag to make it case sensitive.
It also now lets one check individual attributes instead of
values. #571
- Changed sort order of `Ord` to avoid accidentally non total order
that could cause panics on Rust 1.81. #579

## 2.2.0

Expand Down
56 changes: 32 additions & 24 deletions minijinja/src/value/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -540,7 +540,11 @@ fn f64_total_cmp(left: f64, right: f64) -> Ordering {

impl Ord for Value {
fn cmp(&self, other: &Self) -> Ordering {
let value_ordering = match (&self.0, &other.0) {
let kind_ordering = self.kind().cmp(&other.kind());
if matches!(kind_ordering, Ordering::Less | Ordering::Greater) {
return kind_ordering;
}
match (&self.0, &other.0) {
(ValueRepr::None, ValueRepr::None) => Ordering::Equal,
(ValueRepr::Undefined, ValueRepr::Undefined) => Ordering::Equal,
(ValueRepr::String(ref a, _), ValueRepr::String(ref b, _)) => a.cmp(b),
Expand All @@ -550,34 +554,38 @@ impl Ord for Value {
Some(ops::CoerceResult::F64(a, b)) => f64_total_cmp(a, b),
Some(ops::CoerceResult::I128(a, b)) => a.cmp(&b),
Some(ops::CoerceResult::Str(a, b)) => a.cmp(b),
None => match (self.kind(), other.kind()) {
(ValueKind::Seq, ValueKind::Seq) => match (self.try_iter(), other.try_iter()) {
(Ok(a), Ok(b)) => a.cmp(b),
_ => self.len().cmp(&other.len()),
},
(ValueKind::Map, ValueKind::Map) => {
if let (Some(a), Some(b)) = (self.as_object(), other.as_object()) {
if a.is_same_object(b) {
Ordering::Equal
} else {
// This is not really correct. Because the keys can be in arbitrary
// order this could just sort really weirdly as a result. However
// we don't want to pay the cost of actually sorting the keys for
// ordering so we just accept this for now.
match (a.try_iter_pairs(), b.try_iter_pairs()) {
(Some(a), Some(b)) => a.cmp(b),
_ => self.len().cmp(&other.len()),
None => {
if let (Some(a), Some(b)) = (self.as_object(), other.as_object()) {
if a.is_same_object(b) {
Ordering::Equal
} else {
match (a.repr(), b.repr()) {
(ObjectRepr::Map, ObjectRepr::Map) => {
// This is not really correct. Because the keys can be in arbitrary
// order this could just sort really weirdly as a result. However
// we don't want to pay the cost of actually sorting the keys for
// ordering so we just accept this for now.
match (a.try_iter_pairs(), b.try_iter_pairs()) {
(Some(a), Some(b)) => a.cmp(b),
_ => unreachable!(),
}
}
(
ObjectRepr::Seq | ObjectRepr::Iterable,
ObjectRepr::Seq | ObjectRepr::Iterable,
) => match (a.try_iter(), b.try_iter()) {
(Some(a), Some(b)) => a.cmp(b),
_ => unreachable!(),
},
(_, _) => unreachable!(),
}
} else {
unreachable!();
}
} else {
unreachable!()
}
_ => Ordering::Equal,
},
}
},
};
value_ordering.then((self.kind() as usize).cmp(&(other.kind() as usize)))
}
}
}

Expand Down
4 changes: 2 additions & 2 deletions minijinja/tests/snapshots/test_templates__vm@coerce.txt.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
source: minijinja/tests/test_templates.rs
description: "{{ \"World\"[0] == \"W\" }}\n{{ \"W\" == \"W\" }}\n{{ 1.0 == 1 }}\n{{ 1 != 2 }}\n{{ none == none }}\n{{ none != undefined }}\n{{ undefined == undefined }}\n{{ true == true }}\n{{ 1 == true }}\n{{ 0 == false }}\n{{ 1 != 0 }}\n{{ \"a\" < \"b\" }}\n{{ \"a\"[0] < \"b\" }}\n{{ false < true }}\n{{ 0 < true }}\n{{ [0, 0] == [0, 0] }}\n{{ [\"a\"] == [\"a\"[0]] }}"
info: {}
input_file: minijinja/tests/inputs/coerce.txt
---
true
true
Expand All @@ -17,7 +18,6 @@ true
true
true
true
false
true
true
true

4 changes: 2 additions & 2 deletions minijinja/tests/snapshots/test_templates__vm@filters.txt.snap
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ sort: [1, 2, 4, 9, 111]
sort-reverse: [111, 9, 4, 2, 1]
sort-case-insensitive: ["a", "B", "C", "z"]
sort-case-sensitive: ["B", "C", "a", "z"]
sort-case-insensitive-mixed: [false, 0, true, 1, "false", "False", "true", "True"]
sort-case-sensitive-mixed: [false, 0, true, 1, "False", "True", "false", "true"]
sort-case-insensitive-mixed: [false, true, 0, 1, "false", "False", "true", "True"]
sort-case-sensitive-mixed: [false, true, 0, 1, "False", "True", "false", "true"]
sort-attribute [{"name": "a"}, {"name": "b"}]
d: true
json: {"a":"b","c":"d"}
Expand Down
74 changes: 70 additions & 4 deletions minijinja/tests/test_value.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, LinkedList, VecDeque};
use std::sync::Arc;

use insta::assert_snapshot;
use insta::{assert_debug_snapshot, assert_snapshot};
use similar_asserts::assert_eq;

use minijinja::value::{DynObject, Enumerator, Kwargs, Object, ObjectRepr, Rest, Value};
use minijinja::{args, render, Environment, Error};
use minijinja::{args, render, Environment, Error, ErrorKind};

#[test]
fn test_sort() {
Expand Down Expand Up @@ -69,13 +69,13 @@ fn test_sort_different_types() {
[
undefined,
none,
false,
true,
-inf,
-100,
-75.0,
-50.0,
false,
0,
true,
1,
30,
80,
Expand Down Expand Up @@ -1059,3 +1059,69 @@ fn test_float_eq() {
let xb = Value::from(i64::MAX as f64);
assert_ne!(xa, xb);
}

#[test]
fn test_sorting() {
let mut values = vec![
Value::from(-f64::INFINITY),
Value::from(1.0),
Value::from(f64::NAN),
Value::from(f64::INFINITY),
Value::from(42.0),
Value::from(41),
Value::from(128),
Value::from(-2),
Value::from(-5.0),
Value::from(32i32),
Value::from(true),
Value::from(false),
Value::from(vec![1, 2, 3]),
Value::from(vec![1, 2, 3, 4]),
Value::from(vec![1]),
Value::from("whatever"),
Value::from("floats"),
Value::from("the"),
Value::from("boat"),
Value::UNDEFINED,
Value::from(()),
Value::from(Error::new(ErrorKind::InvalidOperation, "shit hit the fan")),
];
values.sort();
assert_debug_snapshot!(&values, @r###"
[
undefined,
none,
false,
true,
-inf,
-5.0,
-2,
1.0,
32,
41,
42.0,
128,
inf,
NaN,
"boat",
"floats",
"the",
"whatever",
[
1,
],
[
1,
2,
3,
],
[
1,
2,
3,
4,
],
<invalid value: invalid operation: shit hit the fan>,
]
"###);
}