Commit d327bf1f0 for llama.cpp
commit d327bf1f0780f87b8c01b367b61853b0c291d7c4
Author: Joseph Patricio <122133686+JoeyPatricio@users.noreply.github.com>
Date: Sat Oct 24 13:54:46 2026 -0700
jinja : fix select and selectattr argument handling (#31059)
* jinja : fix select and selectattr argument handling
* jinja : address review feedback
* jinja : Simplified test attr
diff --git a/common/jinja/value.cpp b/common/jinja/value.cpp
index 0840e05b8..96b9bd03d 100644
--- a/common/jinja/value.cpp
+++ b/common/jinja/value.cpp
@@ -299,74 +299,53 @@ static value & get_attribute(const value & val, const value & attr, value & defa
return default_val;
}
-template<bool is_reject>
-static value selectattr(const func_args & args) {
- args.ensure_count(2, 4);
- args.ensure_vals<value_array, value_string, value_string, value_string>(true, true, false, false);
+template<bool is_reject, bool lookup_attr>
+static value select_or_reject(const func_args & args) {
+ // example: array | select("odd") -> test_is_odd(item)
+ // example: array | selectattr("status", "equalto", "active") -> test_is_equalto(item.status, "active")
+ constexpr size_t test_pos = lookup_attr ? 2 : 1;
+ args.ensure_count(test_pos);
+ args.ensure_val<value_array>(args.get_pos(0));
+ value attr;
+ if constexpr (lookup_attr) {
+ attr = args.get_pos(1);
+ args.ensure_val<value_string>(attr);
+ }
+
+ auto & builtins = global_builtins();
+ auto test_it = builtins.end();
+ if (args.count() > test_pos) {
+ args.ensure_val<value_string>(args.get_pos(test_pos));
+ std::string test_name = args.get_pos(test_pos)->as_string().str();
+ test_it = builtins.find("test_is_" + test_name);
+ if (test_it == builtins.end()) {
+ throw raised_exception(args.func_name + ": unknown test '" + test_name + "'");
+ }
+ }
auto arr = args.get_pos(0)->as_array();
- auto attribute = args.get_pos(1);
auto out = mk_val<value_array>();
value val_default = mk_val<value_undefined>();
- if (args.count() == 2) {
- // example: array | selectattr("active")
- for (const auto & item : arr) {
- value attr_val = get_attribute(item, attribute, val_default);
- bool is_selected = attr_val->as_bool();
- if constexpr (is_reject) is_selected = !is_selected;
- if (is_selected) out->push_back(item);
- }
- return out;
-
- } else if (args.count() == 3) {
- // example: array | selectattr("equalto", "text")
- // translated to: test_is_equalto(item, "text")
- std::string test_name = args.get_pos(1)->as_string().str();
- value test_val = args.get_pos(2);
- auto & builtins = global_builtins();
- auto it = builtins.find("test_is_" + test_name);
- if (it == builtins.end()) {
- throw raised_exception("selectattr: unknown test '" + test_name + "'");
- }
- auto test_fn = it->second;
- for (const auto & item : arr) {
- func_args test_args(args.ctx);
- test_args.push_back(item); // current object
- test_args.push_back(test_val); // extra argument
- value test_result = test_fn(test_args);
- bool is_selected = test_result->as_bool();
- if constexpr (is_reject) is_selected = !is_selected;
- if (is_selected) out->push_back(item);
- }
- return out;
-
- } else if (args.count() == 4) {
- // example: array | selectattr("status", "equalto", "active")
- // translated to: test_is_equalto(item.status, "active")
- std::string test_name = args.get_pos(2)->as_string().str();
- auto extra_arg = args.get_pos(3);
- auto & builtins = global_builtins();
- auto it = builtins.find("test_is_" + test_name);
- if (it == builtins.end()) {
- throw raised_exception("selectattr: unknown test '" + test_name + "'");
+ for (const auto & item : arr) {
+ value val = item;
+ if constexpr (lookup_attr) {
+ val = get_attribute(item, attr, val_default);
}
- auto test_fn = it->second;
- for (const auto & item : arr) {
- value attr_val = get_attribute(item, attribute, val_default);
+ bool is_selected;
+ if (test_it == builtins.end()) {
+ is_selected = val->as_bool();
+ } else {
func_args test_args(args.ctx);
- test_args.push_back(attr_val); // attribute value
- test_args.push_back(extra_arg); // extra argument
- value test_result = test_fn(test_args);
- bool is_selected = test_result->as_bool();
- if constexpr (is_reject) is_selected = !is_selected;
- if (is_selected) out->push_back(item);
+ test_args.push_back(val);
+ for (size_t i = test_pos + 1; i < args.count(); i++) {
+ test_args.push_back(args.get_pos(i));
+ }
+ is_selected = test_it->second(test_args)->as_bool();
}
- return out;
- } else {
- throw raised_exception("selectattr: invalid number of arguments");
+ if constexpr (is_reject) is_selected = !is_selected;
+ if (is_selected) out->push_back(item);
}
-
return out;
}
@@ -1095,10 +1074,10 @@ const func_builtins & value_array_t::get_builtins() const {
auto arr = slice(val->as_array(), start, stop, step);
return is_val<value_tuple>(val) ? mk_val<value_tuple>(std::move(arr)) : mk_val<value_array>(std::move(arr));
}},
- {"selectattr", selectattr<false>},
- {"select", selectattr<false>},
- {"rejectattr", selectattr<true>},
- {"reject", selectattr<true>},
+ {"selectattr", select_or_reject<false, true>},
+ {"select", select_or_reject<false, false>},
+ {"rejectattr", select_or_reject<true, true>},
+ {"reject", select_or_reject<true, false>},
{"join", [](const func_args & args) -> value {
args.ensure_count(1, 3);
if (!is_val<value_array>(args.get_pos(0))) {
diff --git a/tests/test-jinja.cpp b/tests/test-jinja.cpp
index 99552a8f7..eb05f8780 100644
--- a/tests/test-jinja.cpp
+++ b/tests/test-jinja.cpp
@@ -1804,6 +1804,54 @@ static void test_array_methods(testing & t) {
"z y "
);
+ test_template(t, "array|select with test",
+ "{{ items|select('odd')|join(',') }}",
+ {{"items", json::array({1, 2, 3, 4, 5})}},
+ "1,3,5"
+ );
+
+ test_template(t, "array|reject with test",
+ "{{ items|reject('odd')|join(',') }}",
+ {{"items", json::array({1, 2, 3, 4, 5})}},
+ "2,4"
+ );
+
+ test_template(t, "array|select without test",
+ "{{ items|select|join(',') }}",
+ {{"items", json::array({0, 1, "", "a", nullptr, false})}},
+ "1,a"
+ );
+
+ test_template(t, "array|selectattr with test",
+ "{% for p in items|selectattr('type', 'defined') %}{{ p.type }} {% endfor %}",
+ {{"items", json::array({
+ {{"type", "string"}},
+ {{"enum", json::array({1})}},
+ {{"type", "integer"}}
+ })}},
+ "string integer "
+ );
+
+ test_template(t, "array|rejectattr with test",
+ "{{ items|rejectattr('type', 'defined')|list|length }}",
+ {{"items", json::array({
+ {{"type", "string"}},
+ {{"enum", json::array({1})}},
+ {{"type", "integer"}}
+ })}},
+ "1"
+ );
+
+ test_template(t, "array|selectattr with attr named like a test",
+ "{% for p in items|selectattr('odd', 'even') %}{{ p.odd }} {% endfor %}",
+ {{"items", json::array({
+ {{"odd", 1}},
+ {{"odd", 2}},
+ {{"odd", 4}}
+ })}},
+ "2 4 "
+ );
+
test_template(t, "array|tojson",
"{{ arr|tojson }}",
{{"arr", json::array({1, 2, 3})}},