From 526af22574fc854f23c3f59eb5ecb7886b9221ac Mon Sep 17 00:00:00 2001 From: Tobi Lutke Date: Wed, 11 Mar 2026 08:47:21 -0400 Subject: [PATCH] add invoke_single fast path for no-arg filter invocation, avoids splat alloc --- lib/liquid/context.rb | 6 ++++++ lib/liquid/strainer_template.rb | 14 ++++++++++++++ lib/liquid/variable.rb | 2 +- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/lib/liquid/context.rb b/lib/liquid/context.rb index 433b6d00..c2009507 100644 --- a/lib/liquid/context.rb +++ b/lib/liquid/context.rb @@ -109,6 +109,12 @@ module Liquid strainer.invoke(method, *args).to_liquid end + # Fast path for single-argument filter invocation (the most common case: + # {{ value | filter }}) — avoids *args splat allocation. + def invoke_single(method, input) + strainer.invoke_single(method, input).to_liquid + end + # Push new local scope on the stack. use Context#stack instead def push(new_scope = {}) @scopes.unshift(new_scope) diff --git a/lib/liquid/strainer_template.rb b/lib/liquid/strainer_template.rb index ca0626dd..b3db4dbd 100644 --- a/lib/liquid/strainer_template.rb +++ b/lib/liquid/strainer_template.rb @@ -58,5 +58,19 @@ module Liquid rescue ::ArgumentError => e raise Liquid::ArgumentError, e.message, e.backtrace end + + # Fast path for single-argument (no extra args) filter invocation. + # Avoids *args splat allocation for the common {{ value | filter }} case. + def invoke_single(method, input) + if self.class.invokable?(method) + send(method, input) + elsif @context.strict_filters + raise Liquid::UndefinedFilter, "undefined filter #{method}" + else + input + end + rescue ::ArgumentError => e + raise Liquid::ArgumentError, e.message, e.backtrace + end end end diff --git a/lib/liquid/variable.rb b/lib/liquid/variable.rb index 41f00e7b..7762ae66 100644 --- a/lib/liquid/variable.rb +++ b/lib/liquid/variable.rb @@ -306,7 +306,7 @@ module Liquid @filters.each do |filter_name, filter_args, filter_kwargs| if filter_args.empty? && !filter_kwargs - obj = context.invoke(filter_name, obj) + obj = context.invoke_single(filter_name, obj) else filter_args = evaluate_filter_expressions(context, filter_args, filter_kwargs) obj = context.invoke(filter_name, obj, *filter_args)