{"record":{"id":"f7f762e6a85347b0","repo":"Shopify/liquid","slug":"invalid-expression-type-invalid-expr-in-range","errorCode":null,"errorMessage":"Invalid expression type '#{invalid_expr}' in range expression","messagePattern":"Invalid expression type '#(.+?)' in range expression","errorType":"exception","errorClass":"Liquid::SyntaxError","httpStatus":null,"severity":"error","filePath":"lib/liquid/range_lookup.rb","lineNumber":17,"sourceCode":"# frozen_string_literal: true\n\nmodule Liquid\n  class RangeLookup\n    def self.parse(start_markup, end_markup, string_scanner, cache = nil)\n      start_obj = Expression.parse(start_markup, string_scanner, cache)\n      end_obj   = Expression.parse(end_markup, string_scanner, cache)\n      if start_obj.respond_to?(:evaluate) || end_obj.respond_to?(:evaluate)\n        new(start_obj, end_obj)\n      else\n        begin\n          start_obj.to_i..end_obj.to_i\n        rescue NoMethodError\n          invalid_expr = start_markup unless start_obj.respond_to?(:to_i)\n          invalid_expr ||= end_markup unless end_obj.respond_to?(:to_i)\n          if invalid_expr\n            raise Liquid::SyntaxError, \"Invalid expression type '#{invalid_expr}' in range expression\"\n          end\n\n          raise\n        end\n      end\n    end\n\n    attr_reader :start_obj, :end_obj\n\n    def initialize(start_obj, end_obj)\n      @start_obj = start_obj\n      @end_obj   = end_obj\n    end\n\n    def evaluate(context)\n      start_int = to_integer(context.evaluate(@start_obj))\n      end_int   = to_integer(context.evaluate(@end_obj))\n      start_int..end_int","sourceCodeStart":1,"sourceCodeEnd":35,"githubUrl":"https://github.com/Shopify/liquid/blob/807d45a6b3d4568e64e86b375e3702df2c7c860c/lib/liquid/range_lookup.rb#L1-L35","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Ensure both range endpoints are integers or numeric strings (e.g. use (1..10) or coerce with plus: 0 / to_i semantics)","Guard the loop: only iterate when bounds are numeric (use {% if x %} with integer checks)","Default undefined variables to a numeric value before the range"],"exampleFix":"<!-- before -->\n{% for i in (1..count) %}...{% endfor %}  <!-- count = \"abc\" -->\n<!-- after -->\n{% assign n = count | plus: 0 %}\n{% for i in (1..n) %}...{% endfor %}","handlingStrategy":"type-guard","validationCode":"def valid_range?(a, b)\n  [a, b].all? { |v| !v.nil? && v.to_s =~ /\\A-?\\d+\\z/ }\nend","typeGuard":"def integer_like?(v)\n  !v.nil? && (v.is_a?(Integer) || v.to_s =~ /\\A-?\\d+\\z/)\nend","tryCatchPattern":"begin\n  tpl.render(assigns)\nrescue Liquid::SyntaxError => e\n  raise unless e.message.include?('range expression')\n  # coerce/default the offending variable and re-render\nend","preventionTips":["Coerce loop bounds with plus: 0 before ranges","Default undefined variables to numeric values","Validate external data used as range bounds"],"tags":["liquid","template-engine","range","type-error"],"backgroundTag":"invalid-argument-value","analyzedSha":"807d45a6b3d4568e64e86b375e3702df2c7c860c","analyzedAt":"2026-09-08T11:31:38.917Z","contentChangedAt":"2026-09-08T11:31:38.917Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}