Shopify/liquid · error · Liquid::SyntaxError

Invalid expression type '#

Error message

Invalid expression type '#{invalid_expr}' in range expression

What it means

Liquid::RangeLookup validates that both endpoints of a (a..b) range respond to to_i. If a start or end expression evaluates to something non-numeric (e.g. a string like 'abc' or nil that can't be coerced), it raises Liquid::SyntaxError naming the offending markup.

Solutions

  1. Ensure both range endpoints are integers or numeric strings (e.g. use (1..10) or coerce with plus: 0 / to_i semantics)
  2. Guard the loop: only iterate when bounds are numeric (use {% if x %} with integer checks)
  3. Default undefined variables to a numeric value before the range

Example fix

<!-- before -->
{% for i in (1..count) %}...{% endfor %}  <!-- count = "abc" -->
<!-- after -->
{% assign n = count | plus: 0 %}
{% for i in (1..n) %}...{% endfor %}
Defensive patterns

Strategy: type-guard

Validate before calling

def valid_range?(a, b)
  [a, b].all? { |v| !v.nil? && v.to_s =~ /\A-?\d+\z/ }
end

Type guard

def integer_like?(v)
  !v.nil? && (v.is_a?(Integer) || v.to_s =~ /\A-?\d+\z/)
end

Try / catch

begin
  tpl.render(assigns)
rescue Liquid::SyntaxError => e
  raise unless e.message.include?('range expression')
  # coerce/default the offending variable and re-render
end

Prevention

When it happens

Trigger: Using a range literal (x..y) in tags like {% for i in (1..x) %} where x or the literal evaluates to a non-integer-convertible value, such as a string 'abc', nil, or a hash.

Common situations: Assigning a variable to a string then using it as a range bound; a variable that is nil/undefined due to a typo; loop bounds coming from user/JSON data that isn't numeric.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of Shopify/liquid@807d45a6b3 (2026-09-08). Data as JSON: /api/errors/f7f762e6a85347b0. Report an issue: GitHub.

Appendix: source

Thrown at lib/liquid/range_lookup.rb:17

# frozen_string_literal: true

module Liquid
  class RangeLookup
    def self.parse(start_markup, end_markup, string_scanner, cache = nil)
      start_obj = Expression.parse(start_markup, string_scanner, cache)
      end_obj   = Expression.parse(end_markup, string_scanner, cache)
      if start_obj.respond_to?(:evaluate) || end_obj.respond_to?(:evaluate)
        new(start_obj, end_obj)
      else
        begin
          start_obj.to_i..end_obj.to_i
        rescue NoMethodError
          invalid_expr = start_markup unless start_obj.respond_to?(:to_i)
          invalid_expr ||= end_markup unless end_obj.respond_to?(:to_i)
          if invalid_expr
            raise Liquid::SyntaxError, "Invalid expression type '#{invalid_expr}' in range expression"
          end

          raise
        end
      end
    end

    attr_reader :start_obj, :end_obj

    def initialize(start_obj, end_obj)
      @start_obj = start_obj
      @end_obj   = end_obj
    end

    def evaluate(context)
      start_int = to_integer(context.evaluate(@start_obj))
      end_int   = to_integer(context.evaluate(@end_obj))
      start_int..end_int

View on GitHub (pinned to 807d45a6b3)