Issue: #6
Question: Do the loop modifiers reversed, limit, and offset depend on the order they're specified in the template?
YES, the order matters in Ruby/Shopify Liquid, but NO, it doesn't matter in the Go implementation.
-
Ruby/Shopify Liquid (v5.10.0):
- Syntax order DOES matter
reversedkeyword only works when placed BEFORE named parameters (limit:andoffset:)- When
reversedcomes after named parameters, it is ignored
-
Go osteele/liquid Implementation:
- Syntax order does NOT matter
- Modifiers are always applied in fixed order:
reversed→offset→limit - All modifiers work regardless of their position in the syntax
Test array: [1, 2, 3, 4, 5]
| Test Case | Template | Ruby Result | Go Result | Match? |
|---|---|---|---|---|
| reversed only | reversed |
54321 |
54321 |
✅ |
| limit only | limit:2 |
12 |
12 |
✅ |
| offset only | offset:2 |
345 |
345 |
✅ |
| reversed + limit | reversed limit:2 |
21 |
54 |
❌ |
| limit + reversed | limit:2 reversed |
12 |
54 |
❌ |
| limit + offset (order 1) | limit:2 offset:1 |
23 |
23 |
✅ |
| offset + limit (order 2) | offset:1 limit:2 |
23 |
23 |
✅ |
| all three (order 1) | reversed limit:2 offset:1 |
32 |
43 |
❌ |
| all three (order 2) | reversed offset:1 limit:2 |
32 |
43 |
❌ |
| all three (order 3) | limit:2 offset:1 reversed |
23 |
43 |
❌ |
| all three (order 4) | offset:1 limit:2 reversed |
23 |
43 |
❌ |
The Ruby implementation appears to:
- Parse
reversedas a boolean flag (only when it appears before named parameters) - Parse
limit:Nandoffset:Nas named parameters - Apply in the order: offset → limit → reversed
Critical Finding: The reversed keyword is ONLY recognized when it appears BEFORE the named parameters:
- ✅
{% for item in array reversed limit:2 %}- reversed works - ❌
{% for item in array limit:2 reversed %}- reversed is IGNORED
This explains the different results:
-
reversed limit:2on [1,2,3,4,5]:- offset=0, limit=2: extract [1,2]
- reversed=true: reverse to [2,1]
- Result:
21
-
limit:2 reversedon [1,2,3,4,5]:- offset=0, limit=2: extract [1,2]
- reversed=false (keyword not recognized): no reversal
- Result:
12
The Go implementation (in tags/iteration_tags.go:225-263):
func applyLoopModifiers(loop expressions.Loop, ctx render.Context, iter iterable) (iterable, error) {
if loop.Reversed {
iter = reverseWrapper{iter}
}
if loop.Offset != nil {
// ... apply offset
iter = offsetWrapper{iter, offset}
}
if loop.Limit != nil {
// ... apply limit
iter = limitWrapper{iter, limit}
}
return iter, nil
}This code:
- Accepts
reversedin any position (it's just a boolean field) - Always applies in the order: reversed → offset → limit
- This gives consistent behavior regardless of syntax order
Example: reversed limit:2 on [1,2,3,4,5]:
- reversed=true: reverse to [5,4,3,2,1]
- offset=0: still [5,4,3,2,1]
- limit=2: extract [5,4]
- Result:
54
The Go implementation is NOT compatible with Ruby/Shopify Liquid when:
reversedis used withlimitoroffset- The syntax order varies
Both implementations have merit:
Ruby's approach (syntax-order-dependent):
- Pros: More flexible - different orders produce different results
- Cons:
- Confusing that
reversedonly works in one position - Not intuitive for users
- Violates principle of least surprise
- Confusing that
Go's approach (fixed application order):
- Pros:
- Consistent regardless of syntax order
- More predictable
- Easier to understand and document
- Cons:
- Different from Ruby reference implementation
- Only one semantic meaning possible
PR #456 (Shopify/liquid#456) claimed to fix reversed limit:2 to produce the "correct" result by applying reversed before limit. However, based on testing Liquid v5.10.0, the current Ruby behavior actually applies:
- offset first
- limit second
- reversed last
This suggests either:
- PR #456 was never merged, or
- It was merged but implemented differently than described, or
- There was a regression
-
Document the current Go behavior clearly - users should know that modifier order in the template doesn't matter
-
Decide on compatibility goal:
- Option A: Match Ruby exactly (including the quirk that
reversedonly works when placed first) - Option B: Keep current behavior and document as a known difference
- Option C: Propose a fix to Ruby/Shopify Liquid to adopt the more logical Go approach
- Option A: Match Ruby exactly (including the quirk that
-
Add test cases to prevent regression and document expected behavior
The investigation included:
tags/iteration_tags_test.go- Go implementation tests (added test cases for combined modifiers)scripts/test_ruby_liquid.rb- Ruby reference implementation test script
The Ruby script can be run to verify the behavior of the Shopify Ruby Liquid implementation.