Skip to content

Commit

Permalink
Pass arguments by reference when useful and fix an undefined bug (#657)
Browse files Browse the repository at this point in the history
  • Loading branch information
mitsuhiko authored Dec 11, 2024
1 parent ac00ef3 commit 06dbe20
Show file tree
Hide file tree
Showing 6 changed files with 48 additions and 73 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ All notable changes to MiniJinja are documented here.
- Added `truncate` filter to `minijinja-contrib`. #647
- Added `wordcount` filter to `minijinja-contrib`. #649
- Added `wordwrap` filter to `minijinja-contrib`. #651
- Some tests and filters now pass borrowed values for performance reasons
and a bug was fixed that caused undefined values in strict undefined
mode not to work with tests. #657

## 2.5.0

Expand Down
51 changes: 26 additions & 25 deletions minijinja/src/filters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,9 +264,9 @@ pub fn safe(v: String) -> Value {
/// this filter escapes with the format that is native to the format or HTML
/// otherwise. This means that if the auto escape setting is set to
/// `Json` for instance then this filter will serialize to JSON instead.
pub fn escape(state: &State, v: Value) -> Result<Value, Error> {
pub fn escape(state: &State, v: &Value) -> Result<Value, Error> {
if v.is_safe() {
return Ok(v);
return Ok(v.clone());
}

// this tries to use the escaping flag of the current scope, then
Expand All @@ -284,7 +284,7 @@ pub fn escape(state: &State, v: Value) -> Result<Value, Error> {
None => String::new(),
};
let mut out = Output::with_string(&mut rv);
ok!(write_escaped(&mut out, auto_escape, &v));
ok!(write_escaped(&mut out, auto_escape, v));
Ok(Value::from_safe_string(rv))
}

Expand Down Expand Up @@ -385,7 +385,7 @@ mod builtins {
/// <p>Search results: {{ results|length }}
/// ```
#[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
pub fn length(v: Value) -> Result<usize, Error> {
pub fn length(v: &Value) -> Result<usize, Error> {
v.len().ok_or_else(|| {
Error::new(
ErrorKind::InvalidOperation,
Expand Down Expand Up @@ -420,7 +420,7 @@ mod builtins {
/// * `by`: set to `"value"` to sort by value. Defaults to `"key"`.
/// * `reverse`: set to `true` to sort in reverse.
#[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
pub fn dictsort(v: Value, kwargs: Kwargs) -> Result<Value, Error> {
pub fn dictsort(v: &Value, kwargs: Kwargs) -> Result<Value, Error> {
if v.kind() == ValueKind::Map {
let mut rv = Vec::with_capacity(v.len().unwrap_or(0));
let iter = ok!(v.try_iter());
Expand Down Expand Up @@ -478,7 +478,7 @@ mod builtins {
/// </dl>
/// ```
#[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
pub fn items(v: Value) -> Result<Value, Error> {
pub fn items(v: &Value) -> Result<Value, Error> {
if v.kind() == ValueKind::Map {
let mut rv = Vec::with_capacity(v.len().unwrap_or(0));
let iter = ok!(v.try_iter());
Expand All @@ -503,7 +503,7 @@ mod builtins {
/// {% endfor %}
/// ```
#[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
pub fn reverse(v: Value) -> Result<Value, Error> {
pub fn reverse(v: &Value) -> Result<Value, Error> {
v.reverse()
}

Expand All @@ -521,7 +521,7 @@ mod builtins {

/// Joins a sequence by a character
#[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
pub fn join(val: Value, joiner: Option<Cow<'_, str>>) -> Result<String, Error> {
pub fn join(val: &Value, joiner: Option<Cow<'_, str>>) -> Result<String, Error> {
if val.is_undefined() || val.is_none() {
return Ok(String::new());
}
Expand Down Expand Up @@ -599,11 +599,11 @@ mod builtins {
/// <p>{{ my_variable|default("my_variable was not defined") }}</p>
/// ```
#[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
pub fn default(value: Value, other: Option<Value>) -> Value {
pub fn default(value: &Value, other: Option<Value>) -> Value {
if value.is_undefined() {
other.unwrap_or_else(|| Value::from(""))
} else {
value
value.clone()
}
}

Expand Down Expand Up @@ -640,12 +640,12 @@ mod builtins {
/// {{ "42"|int == 42 }} -> true
/// ```
#[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
pub fn int(value: Value) -> Result<Value, Error> {
pub fn int(value: &Value) -> Result<Value, Error> {
match &value.0 {
ValueRepr::Undefined | ValueRepr::None => Ok(Value::from(0)),
ValueRepr::Bool(x) => Ok(Value::from(*x as u64)),
ValueRepr::U64(_) | ValueRepr::I64(_) | ValueRepr::U128(_) | ValueRepr::I128(_) => {
Ok(value)
Ok(value.clone())
}
ValueRepr::F64(v) => Ok(Value::from(*v as i128)),
ValueRepr::String(..) | ValueRepr::SmallStr(_) => {
Expand All @@ -663,7 +663,7 @@ mod builtins {
ErrorKind::InvalidOperation,
format!("cannot convert {} to integer", value.kind()),
)),
ValueRepr::Invalid(_) => value.validate(),
ValueRepr::Invalid(_) => value.clone().validate(),
}
}

Expand All @@ -673,7 +673,7 @@ mod builtins {
/// {{ "42.5"|float == 42.5 }} -> true
/// ```
#[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
pub fn float(value: Value) -> Result<Value, Error> {
pub fn float(value: &Value) -> Result<Value, Error> {
match &value.0 {
ValueRepr::Undefined | ValueRepr::None => Ok(Value::from(0.0)),
ValueRepr::Bool(x) => Ok(Value::from(*x as u64 as f64)),
Expand All @@ -683,8 +683,8 @@ mod builtins {
.parse::<f64>()
.map(Value::from)
.map_err(|err| Error::new(ErrorKind::InvalidOperation, err.to_string())),
ValueRepr::Invalid(_) => value.validate(),
_ => as_f64(&value, true).map(Value::from).ok_or_else(|| {
ValueRepr::Invalid(_) => value.clone().validate(),
_ => as_f64(value, true).map(Value::from).ok_or_else(|| {
Error::new(
ErrorKind::InvalidOperation,
format!("cannot convert {} to float", value.kind()),
Expand Down Expand Up @@ -728,7 +728,7 @@ mod builtins {
/// {{ value['key'] == value|attr('key') }} -> true
/// ```
#[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
pub fn attr(value: Value, key: &Value) -> Result<Value, Error> {
pub fn attr(value: &Value, key: &Value) -> Result<Value, Error> {
value.get_item(key)
}

Expand Down Expand Up @@ -769,7 +769,7 @@ mod builtins {
/// </dl>
/// ```
#[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
pub fn first(value: Value) -> Result<Value, Error> {
pub fn first(value: &Value) -> Result<Value, Error> {
if let Some(s) = value.as_str() {
Ok(s.chars().next().map_or(Value::UNDEFINED, Value::from))
} else if let Some(mut iter) = value.as_object().and_then(|x| x.try_iter()) {
Expand Down Expand Up @@ -895,9 +895,9 @@ mod builtins {
///
/// If the string has been marked as safe, that value is preserved.
#[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
pub fn string(value: Value) -> Value {
pub fn string(value: &Value) -> Value {
if value.kind() == ValueKind::String {
value
value.clone()
} else {
value.to_string().into()
}
Expand All @@ -912,7 +912,7 @@ mod builtins {
/// {{ 42|bool }} -> true
/// ```
#[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
pub fn bool(value: Value) -> bool {
pub fn bool(value: &Value) -> bool {
value.is_true()
}

Expand Down Expand Up @@ -1056,7 +1056,7 @@ mod builtins {
/// ```
#[cfg_attr(docsrs, doc(cfg(all(feature = "builtins", feature = "json"))))]
#[cfg(feature = "json")]
pub fn tojson(value: Value, indent: Option<Value>, args: Kwargs) -> Result<Value, Error> {
pub fn tojson(value: &Value, indent: Option<Value>, args: Kwargs) -> Result<Value, Error> {
let indent = match indent {
Some(indent) => Some(indent),
None => ok!(args.get("indent")),
Expand Down Expand Up @@ -1163,7 +1163,7 @@ mod builtins {
/// ```
#[cfg_attr(docsrs, doc(cfg(all(feature = "builtins", feature = "urlencode"))))]
#[cfg(feature = "urlencode")]
pub fn urlencode(value: Value) -> Result<String, Error> {
pub fn urlencode(value: &Value) -> Result<String, Error> {
const SET: &percent_encoding::AsciiSet = &percent_encoding::NON_ALPHANUMERIC
.remove(b'/')
.remove(b'.')
Expand Down Expand Up @@ -1549,7 +1549,7 @@ mod builtins {
/// the resulting list will have `["CA", "NY"]`. This can be disabled by
/// passing `case_sensitive=True`.
#[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
pub fn unique(values: Vec<Value>, kwargs: Kwargs) -> Result<Value, Error> {
pub fn unique(state: &State, values: Value, kwargs: Kwargs) -> Result<Value, Error> {
use std::collections::BTreeSet;

let attr = ok!(kwargs.get::<Option<&str>>("attribute"));
Expand All @@ -1559,7 +1559,8 @@ mod builtins {
let mut rv = Vec::new();
let mut seen = BTreeSet::new();

for item in values {
let iter = ok!(state.undefined_behavior().try_iter(values));
for item in iter {
let value_to_compare = if let Some(attr) = attr {
item.get_path_or_default(attr, &Value::UNDEFINED)
} else {
Expand Down
30 changes: 15 additions & 15 deletions minijinja/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ impl BoxedTest {
/// ```jinja
/// {{ 42 is undefined }} -> false
/// ```
pub fn is_undefined(v: Value) -> bool {
pub fn is_undefined(v: &Value) -> bool {
v.is_undefined()
}

Expand All @@ -211,7 +211,7 @@ pub fn is_undefined(v: Value) -> bool {
/// ```jinja
/// {{ 42 is defined }} -> true
/// ```
pub fn is_defined(v: Value) -> bool {
pub fn is_defined(v: &Value) -> bool {
!v.is_undefined()
}

Expand All @@ -220,7 +220,7 @@ pub fn is_defined(v: Value) -> bool {
/// ```jinja
/// {{ none is none }} -> true
/// ```
pub fn is_none(v: Value) -> bool {
pub fn is_none(v: &Value) -> bool {
v.is_none()
}

Expand All @@ -231,7 +231,7 @@ pub fn is_none(v: Value) -> bool {
/// ```
///
/// This filter is also registered with the `escaped` alias.
pub fn is_safe(v: Value) -> bool {
pub fn is_safe(v: &Value) -> bool {
v.is_safe()
}

Expand All @@ -250,7 +250,7 @@ mod builtins {
/// {{ true is boolean }} -> true
/// ```
#[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
pub fn is_boolean(v: Value) -> bool {
pub fn is_boolean(v: &Value) -> bool {
v.kind() == ValueKind::Bool
}

Expand Down Expand Up @@ -280,8 +280,8 @@ mod builtins {
/// {{ 42 is divisibleby(2) }} -> true
/// ```
#[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
pub fn is_divisibleby(v: Value, other: Value) -> bool {
match coerce(&v, &other, false) {
pub fn is_divisibleby(v: &Value, other: &Value) -> bool {
match coerce(v, other, false) {
Some(CoerceResult::I128(a, b)) => (a % b) == 0,
Some(CoerceResult::F64(a, b)) => (a % b) == 0.0,
_ => false,
Expand All @@ -295,7 +295,7 @@ mod builtins {
/// {{ "42" is number }} -> false
/// ```
#[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
pub fn is_number(v: Value) -> bool {
pub fn is_number(v: &Value) -> bool {
matches!(v.kind(), ValueKind::Number)
}

Expand All @@ -306,7 +306,7 @@ mod builtins {
/// {{ 42.0 is integer }} -> false
/// ```
#[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
pub fn is_integer(v: Value) -> bool {
pub fn is_integer(v: &Value) -> bool {
v.is_integer()
}

Expand All @@ -317,7 +317,7 @@ mod builtins {
/// {{ 42.0 is float }} -> true
/// ```
#[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
pub fn is_float(v: Value) -> bool {
pub fn is_float(v: &Value) -> bool {
matches!(v.0, crate::value::ValueRepr::F64(_))
}

Expand All @@ -328,7 +328,7 @@ mod builtins {
/// {{ 42 is string }} -> false
/// ```
#[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
pub fn is_string(v: Value) -> bool {
pub fn is_string(v: &Value) -> bool {
matches!(v.kind(), ValueKind::String)
}

Expand All @@ -339,7 +339,7 @@ mod builtins {
/// {{ 42 is sequence }} -> false
/// ```
#[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
pub fn is_sequence(v: Value) -> bool {
pub fn is_sequence(v: &Value) -> bool {
matches!(v.kind(), ValueKind::Seq)
}

Expand All @@ -350,7 +350,7 @@ mod builtins {
/// {{ 42 is iterable }} -> false
/// ```
#[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
pub fn is_iterable(v: Value) -> bool {
pub fn is_iterable(v: &Value) -> bool {
v.try_iter().is_ok()
}

Expand All @@ -361,7 +361,7 @@ mod builtins {
/// {{ [1, 2, 3] is mapping }} -> false
/// ```
#[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
pub fn is_mapping(v: Value) -> bool {
pub fn is_mapping(v: &Value) -> bool {
matches!(v.kind(), ValueKind::Map)
}

Expand Down Expand Up @@ -494,7 +494,7 @@ mod builtins {
#[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
#[cfg(feature = "builtins")]
pub fn is_in(state: &State, value: &Value, other: &Value) -> Result<bool, Error> {
ok!(state.undefined_behavior().assert_iterable(value));
ok!(state.undefined_behavior().assert_iterable(other));
Ok(crate::value::ops::contains(other, value)
.map(|value| value.is_true())
.unwrap_or(false))
Expand Down
20 changes: 2 additions & 18 deletions minijinja/src/value/argtypes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use std::ops::{Deref, DerefMut};
use std::sync::Arc;

use crate::error::{Error, ErrorKind};
use crate::utils::UndefinedBehavior;
use crate::value::{
DynObject, ObjectRepr, Packed, SmallStr, StringType, Value, ValueKind, ValueMap, ValueRepr,
};
Expand Down Expand Up @@ -158,18 +157,10 @@ pub trait ArgType<'a> {

#[doc(hidden)]
fn from_state_and_value(
state: Option<&'a State>,
_state: Option<&'a State>,
value: Option<&'a Value>,
) -> Result<(Self::Output, usize), Error> {
if value.map_or(false, |x| x.is_undefined())
&& state.map_or(false, |x| {
matches!(x.undefined_behavior(), UndefinedBehavior::Strict)
})
{
Err(Error::from(ErrorKind::UndefinedError))
} else {
Ok((ok!(Self::from_value(value)), 1))
}
Ok((ok!(Self::from_value(value)), 1))
}

#[doc(hidden)]
Expand Down Expand Up @@ -960,13 +951,6 @@ impl TryFrom<Value> for Kwargs {
impl<'a> ArgType<'a> for Value {
type Output = Self;

fn from_state_and_value(
_state: Option<&'a State>,
value: Option<&'a Value>,
) -> Result<(Self::Output, usize), Error> {
Ok((ok!(Self::from_value(value)), 1))
}

fn from_value(value: Option<&'a Value>) -> Result<Self, Error> {
match value {
Some(value) => Ok(value.clone()),
Expand Down
2 changes: 1 addition & 1 deletion minijinja/tests/test_environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ fn test_unknown_method_callback() {
env.set_unknown_method_callback(|_state, value, method, args| {
if value.kind() == ValueKind::Map && method == "items" {
from_args::<()>(args)?;
minijinja::filters::items(value.clone())
minijinja::filters::items(value)
} else {
Err(Error::from(ErrorKind::UnknownMethod))
}
Expand Down
Loading

0 comments on commit 06dbe20

Please sign in to comment.