Skip to content

fix(liquid)!: size, first and last pseudo-properties only with dot notation - #101

Merged
cappuc merged 1 commit into
mainfrom
fix/bracket-pseudo-properties
Oct 19, 2026
Merged

cappuc merged 1 commit into
mainfrom
fix/bracket-pseudo-properties

Conversation

@cappuc

@cappuc cappuc commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Summary

size, first and last resolve as pseudo-properties only with dot notation, like Shopify Liquid.

 {{ list.size }}        → 3            (unchanged)
-{{ list["size"] }}     → 3
+{{ list["size"] }}     → ""           (undefined variable in strict mode)
 {{ hash["size"] }}     → hash's "size" key   (unchanged)
 {{ list[key] }}        → ""           (unchanged: dynamic keys never were pseudo-properties)
ExpressionParser / VariableLookup::fromMarkup
  ["size"] | ["first"] | ["last"]  → new BracketKey('size')   # other keys stay plain strings
walkLookupParts
  BracketKey → plain key lookup, skips the pseudo-property fallback
RenderContext::findPathOverride
  BracketKey → plain key when matching override paths   # set('a.size') also overrides a["size"]

BracketKey is exportable, so compiled templates rebuild it as new BracketKey('size'). VariableLookup::toString() writes it back as ["size"], so RenderContext::get((string) $lookup) round-trips.

Evidence

  • Before: {{ value["size"] }}|{{ value["first"] }} with value = "Hello" → 5|H
    After: |, like Shopify Liquid 5.13. In Shopify's variable_lookup.rb, command flags are set only for non-bracket lookups.
  • The tests that asserted the old behaviour are updated, and the new cases cover a hash with a real size key, dynamic keys and the RenderContext::get() round-trip. Parsed, compiled and strict variants all run. 3,818 tests pass.
  • Before (after rebasing on fix(context)!: path overrides instead of outer-scope lookup fallback #100): with a = {size: "original"} and set('a.size', 'override'), a["size"] → original, while a.size → override.
    After: both → override. Covered by dotted keys override bracket pseudo-property keys.
  • The serialize, var-export and compiled template caches all render {{ v["size"] }}|{{ v.size }}|{{ h["size"] }} as |2|9.
  • No Liquid template in our sites, CMS themes or laravel-cms uses ["size"], ["first"] or ["last"].
  • PHPBench A/B against main (measured before the rebase on fix(context)!: path overrides instead of outer-scope lookup fallback #100): about −1% on ThemeBench render and stream; OperationBench ranges from −1.3% to 0%.

Merge Danger

Door: two-way

Blast Radius: templates

Templates using a["size"], a["first"] or a["last"] on arrays or strings now get an empty value instead of the pseudo-property. Replace them with dot notation. The README entry documenting the old difference is removed.

Rebased on main after #100: the changelog conflict is resolved and findPathOverride() treats a BracketKey lookup as its plain value.

@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

PHPBench comparison (PHP 8.3)

PHP 8.3 | Runner ubuntu24 | Base e2abc5f | PR f789070 | 10 iterations x 20 revs | 1 warmup

Positive ops/s is faster. RSD above 5% is marked high.

Benchmark Base ops/s PR ops/s Delta ops/s RSD (base / PR) Delta memory Memory %
ThemeBench::benchCompile 203.40 ops/s 202.13 ops/s -0.63% 0.98% / 0.72% -224.00 B -0.01%
ThemeBench::benchParse 652.09 ops/s 653.72 ops/s +0.25% 1.28% / 1.16% -192.00 B -0.00%
ThemeBench::benchRender 553.09 ops/s 543.82 ops/s -1.68% 1.00% / 0.74% -256.00 B -0.00%
ThemeBench::benchRenderCompiled 666.10 ops/s 650.44 ops/s -2.35% 1.34% / 1.07% -272.00 B -0.00%
ThemeBench::benchStream 467.99 ops/s 469.37 ops/s +0.30% 1.89% / 0.72% -256.00 B -0.00%
ThemeBench::benchStreamCompiled 585.20 ops/s 576.27 ops/s -1.53% 1.12% / 0.74% -272.00 B -0.00%
ThemeBench::benchTokenize 1,364.30 ops/s 1,364.65 ops/s +0.03% 2.06% / 1.64% -192.00 B -0.00%
  • Improved (> +2%): 0

  • Neutral (within ±2%): 6

  • Regressed (< -2%): 1 (median -2.35%)

  • Worst throughput regression: ThemeBench::benchRenderCompiled (-2.35%)

  • Total memory change: -0.00%

  • Regression threshold (PHPBENCH_MAX_REG): 5.00%

  • Threshold status: PASSED

@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

PHPBench comparison (PHP 8.5)

PHP 8.5 | Runner ubuntu24 | Base e2abc5f | PR f789070 | 10 iterations x 20 revs | 1 warmup

Positive ops/s is faster. RSD above 5% is marked high.

Benchmark Base ops/s PR ops/s Delta ops/s RSD (base / PR) Delta memory Memory %
ThemeBench::benchCompile 132.96 ops/s 133.92 ops/s +0.72% 0.74% / 0.44% -224.00 B -0.01%
ThemeBench::benchParse 429.89 ops/s 430.20 ops/s +0.07% 0.80% / 0.57% -192.00 B -0.00%
ThemeBench::benchRender 351.39 ops/s 348.46 ops/s -0.83% 1.08% / 0.32% -256.00 B -0.00%
ThemeBench::benchRenderCompiled 419.16 ops/s 413.16 ops/s -1.43% 0.41% / 0.96% -272.00 B -0.00%
ThemeBench::benchStream 296.48 ops/s 294.62 ops/s -0.63% 0.31% / 0.51% -256.00 B -0.00%
ThemeBench::benchStreamCompiled 365.38 ops/s 364.64 ops/s -0.20% 0.41% / 1.26% -272.00 B -0.00%
ThemeBench::benchTokenize 785.50 ops/s 785.86 ops/s +0.05% 0.93% / 0.62% -192.00 B -0.00%
  • Improved (> +2%): 0

  • Neutral (within ±2%): 7

  • Regressed (< -2%): 0

  • Worst throughput regression: n/a

  • Total memory change: -0.00%

  • Regression threshold (PHPBENCH_MAX_REG): 5.00%

  • Threshold status: PASSED

@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

PHPBench comparison (PHP 8.2)

PHP 8.2 | Runner ubuntu24 | Base e2abc5f | PR f789070 | 10 iterations x 20 revs | 1 warmup

Positive ops/s is faster. RSD above 5% is marked high.

Benchmark Base ops/s PR ops/s Delta ops/s RSD (base / PR) Delta memory Memory %
ThemeBench::benchCompile 106.80 ops/s 106.56 ops/s -0.23% 0.51% / 1.39% -224.00 B -0.01%
ThemeBench::benchParse 362.80 ops/s 363.92 ops/s +0.31% 0.99% / 1.61% -192.00 B -0.00%
ThemeBench::benchRender 278.96 ops/s 274.48 ops/s -1.61% 0.99% / 1.32% -256.00 B -0.00%
ThemeBench::benchRenderCompiled 326.42 ops/s 320.89 ops/s -1.70% 1.19% / 1.05% -272.00 B -0.00%
ThemeBench::benchStream 230.71 ops/s 229.27 ops/s -0.62% 0.84% / 0.76% -256.00 B -0.00%
ThemeBench::benchStreamCompiled 289.17 ops/s 287.03 ops/s -0.74% 0.76% / 0.74% -272.00 B -0.00%
ThemeBench::benchTokenize 727.20 ops/s 723.06 ops/s -0.57% 1.12% / 0.81% -192.00 B -0.00%
  • Improved (> +2%): 0

  • Neutral (within ±2%): 7

  • Regressed (< -2%): 0

  • Worst throughput regression: n/a

  • Total memory change: -0.00%

  • Regression threshold (PHPBENCH_MAX_REG): 5.00%

  • Threshold status: PASSED

@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

PHPBench comparison (PHP 8.4)

PHP 8.4 | Runner ubuntu24 | Base e2abc5f | PR f789070 | 10 iterations x 20 revs | 1 warmup

Positive ops/s is faster. RSD above 5% is marked high.

Benchmark Base ops/s PR ops/s Delta ops/s RSD (base / PR) Delta memory Memory %
ThemeBench::benchCompile 168.43 ops/s 165.80 ops/s -1.56% 2.29% / 1.83% -224.00 B -0.01%
ThemeBench::benchParse 521.00 ops/s 525.99 ops/s +0.96% 1.16% / 1.63% -192.00 B -0.00%
ThemeBench::benchRender 418.40 ops/s 426.86 ops/s +2.02% 2.23% / 1.72% -256.00 B -0.00%
ThemeBench::benchRenderCompiled 480.31 ops/s 505.63 ops/s +5.27% 1.44% / 1.92% -272.00 B -0.00%
ThemeBench::benchStream 340.87 ops/s 359.82 ops/s +5.56% 1.98% / 1.91% -256.00 B -0.00%
ThemeBench::benchStreamCompiled 448.73 ops/s 438.87 ops/s -2.20% 1.53% / 1.60% -272.00 B -0.00%
ThemeBench::benchTokenize 948.08 ops/s 954.25 ops/s +0.65% 2.26% / 1.92% -192.00 B -0.00%
  • Improved (> +2%): 3 (median +5.27%)

  • Neutral (within ±2%): 3

  • Regressed (< -2%): 1 (median -2.20%)

  • Worst throughput regression: ThemeBench::benchStreamCompiled (-2.20%)

  • Total memory change: -0.00%

  • Regression threshold (PHPBENCH_MAX_REG): 5.00%

  • Threshold status: PASSED

…h dot notation

Like Shopify Liquid, a["size"] is a plain key lookup. Bracket keys named
size, first or last are parsed as BracketKey so lookup evaluation can tell
them apart from dot notation.
@cappuc
cappuc force-pushed the fix/bracket-pseudo-properties branch from 20ef11c to f789070 Compare October 19, 2026 09:45
@cappuc
cappuc merged commit 787922b into main Oct 19, 2026
14 checks passed
@cappuc
cappuc deleted the fix/bracket-pseudo-properties branch October 19, 2026 10:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant