Skip to content

Commit

Permalink
Fix a regression in PartialEq/Eq in 2.3.0 (#584)
Browse files Browse the repository at this point in the history
  • Loading branch information
mitsuhiko authored Sep 17, 2024
1 parent 4cf56e9 commit 76811cb
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 23 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

All notable changes to MiniJinja are documented here.

## 2.3.1

- Fixes a regresion in `PartialEq` / `Eq` in `Value` caused by changes
in 2.3.0. #584

## 2.3.0

- Fixes some compiler warnings in Rust 1.81. #575
Expand Down
28 changes: 10 additions & 18 deletions minijinja/src/value/merge_object.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::collections::BTreeSet;
use std::sync::Arc;

use crate::value::{Enumerator, Object, ObjectExt, Value};
use crate::value::{Enumerator, Object, Value};

/// Utility struct used by [`context!`](crate::context) to merge
/// multiple values.
Expand All @@ -17,22 +17,14 @@ impl Object for MergeObject {
}

fn enumerate(self: &Arc<Self>) -> Enumerator {
self.mapped_enumerator(|this| {
let mut seen = BTreeSet::new();
Box::new(
this.0
.iter()
.flat_map(|v| v.try_iter().ok())
.flatten()
.filter_map(move |v| {
if seen.contains(&v) {
None
} else {
seen.insert(v.clone());
Some(v)
}
}),
)
})
// we collect here the whole internal object once on iteration so that
// we have an enumerator with a known length.
let items = self
.0
.iter()
.flat_map(|v| v.try_iter().ok())
.flatten()
.collect::<BTreeSet<_>>();
Enumerator::Iter(Box::new(items.into_iter()))
}
}
30 changes: 26 additions & 4 deletions minijinja/src/value/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -493,12 +493,34 @@ impl PartialEq for Value {
}
match (a.repr(), b.repr()) {
(ObjectRepr::Map, ObjectRepr::Map) => {
if a.enumerator_len() != b.enumerator_len() {
// only if we have known lengths can we compare the enumerators
// ahead of time. This function has a fallback for when a
// map has an unknown length. That's generally a bad idea, but
// it makes sense supporting regardless as silent failures are
// not a lot of fun.
let mut need_length_fallback = true;
if let (Some(a_len), Some(b_len)) =
(a.enumerator_len(), b.enumerator_len())
{
if a_len != b_len {
return false;
}
need_length_fallback = false;
}
let mut a_count = 0;
if !a.try_iter_pairs().map_or(false, |mut ak| {
ak.all(|(k, v1)| {
a_count += 1;
b.get_value(&k).map_or(false, |v2| v1 == v2)
})
}) {
return false;
}
a.try_iter_pairs().map_or(false, |mut ak| {
ak.all(|(k, v1)| b.get_value(&k).map_or(false, |v2| v1 == v2))
})
if !need_length_fallback {
true
} else {
a_count == b.try_iter().map_or(0, |x| x.count())
}
}
(
ObjectRepr::Seq | ObjectRepr::Iterable,
Expand Down
64 changes: 63 additions & 1 deletion minijinja/tests/test_value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ 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, ErrorKind};
use minijinja::{args, context, render, Environment, Error, ErrorKind};

#[test]
fn test_sort() {
Expand Down Expand Up @@ -1060,6 +1060,68 @@ fn test_float_eq() {
assert_ne!(xa, xb);
}

#[test]
fn test_eq_regression() {
// merged objects used to not have a length. let's make sure that they have
let vars = context! {};
let new_vars = context! {..vars.clone()};
assert_eq!(vars.len(), Some(0));
assert_eq!(new_vars.len(), Some(0));
assert_eq!(&vars, &new_vars);

// we also want to make sure that objects with unknown lengths are properly checked.
#[derive(Debug)]
struct MadMap;

impl Object for MadMap {
fn get_value(self: &Arc<Self>, key: &Value) -> Option<Value> {
match key.as_str()? {
"a" => Some(Value::from(1)),
"b" => Some(Value::from(2)),
_ => None,
}
}

fn enumerate(self: &Arc<Self>) -> Enumerator {
let mut idx = 0;
Enumerator::Iter(Box::new(std::iter::from_fn(move || {
let new_idx = {
idx += 1;
idx
};
match new_idx {
1 => Some(Value::from("a")),
2 => Some(Value::from("b")),
_ => None,
}
})))
}
}

let normal_map = context! {
a => 1,
b => 2
};
let mad_map = Value::from_object(MadMap);
assert_eq!(mad_map.len(), None);
assert_eq!(mad_map, normal_map);
assert_eq!(normal_map, mad_map);
assert_ne!(
mad_map,
context! {
a => 1,
b => 2,
c => 3,
}
);
assert_ne!(
mad_map,
context! {
a => 1,
}
);
}

#[test]
fn test_sorting() {
let mut values = vec![
Expand Down

0 comments on commit 76811cb

Please sign in to comment.