Skip to content

Analysis: Follow state into the locals it is assigned to - #2289

Merged
marcoroth merged 1 commit into
mainfrom
analysis/derived-state
Aug 19, 2026
Merged

Analysis: Follow state into the locals it is assigned to#2289
marcoroth merged 1 commit into
mainfrom
analysis/derived-state

Conversation

@marcoroth

Copy link
Copy Markdown
Owner

This pull request teaches the dependency analysis that a local assigned from a piece of state carries that state.

<% total = @items.size %>
<p><%= total %></p>

total is @items, counted. Matching is by name, so nothing connected the two and total was attributed to nothing, which meant herb dependencies and herb actionview flow left the paragraph out of what @items reaches.

Follow up on #2288, which did the same for the names a block binds. This is the other way a state gets a second name.

An assignment binds the name only when its right hand side reads the state, and the name carries it for as long as the assignment is in scope.

<% a = @items.size %>
<% b = a * 2 %>
<p><%= b %></p>

a carries @items and b carries a, so b reaches back to @items through the chain.

Three cases that look like assignments and are not:

  • <% other = 5 %> binds nothing, because nothing on the right reads the state.
  • <% if total == @items.size %> is a comparison, and total is left alone.
  • A local assigned inside a block stops carrying at the end of that block, the same rule the block parameters follow.

A template reads state and then names it something else. `<% total = @items.size %>`
puts `@items` in `total`, and `<%= total %>` was attributed to nothing, so
changing `@items` looked like it touched one node when it decides two. This is
the same shape as a block parameter, which carries state under another name for
as long as its block is open, and it is bound the same way.

A local carries whatever its right hand side reads, which makes chains work
without anything extra: `a = @items.size` then `b = a * 2` puts both `a` and `b`
in reach of `@items`. Only the right hand side counts, so `@items == other` binds
nothing, since a comparison assigns nothing.

Scope is the block that made the assignment. A local first assigned inside a
block is gone when the block closes, the way Ruby has it, so an alias set is
snapshotted on entry and restored on exit instead of subtracting what was added.

A partial's declared locals are its state, and `dependency_index` enumerated
only instance variables and constants, so asking what a partial's `query` reaches
returned nothing at all. They are enumerated now.

All three languages, with the same five templates asserted in each. Rust and
TypeScript reach the assignment through the prism node they already carry, and
slice the right hand side by the offsets it gives them.
@marcoroth marcoroth added the reactivity Reactive ERB templates: diff and re-render only what changed label Aug 19, 2026
@github-actions github-actions Bot added ruby Ruby source for the gem and its libraries typescript TypeScript source across the javascript/ packages rbs RBS type signatures in sig/ rubygem The herb RubyGem and its packaging rust Rust bindings and the Herb Rust crate analysis labels Aug 19, 2026
@nx-cloud

nx-cloud Bot commented Aug 19, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit 73127af

Command Status Duration Result
nx run-many -t test --all --parallel --exclude=... ✅ Succeeded 3m 37s View ↗
nx run-many -t build --all --exclude=herb-langu... ✅ Succeeded 51s View ↗
nx build @herb-tools/tailwind-class-sorter ✅ Succeeded 1s View ↗

💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗


☁️ Nx Cloud last updated this comment at 2026-08-19 00:56:51 UTC

@github-actions

github-actions Bot commented Aug 19, 2026

Copy link
Copy Markdown

🌿 Interactive Playground and Documentation Preview

A preview deployment has been built for this pull request. Try out the changes live in the interactive playground:


🌱 Grown from commit 73127af


✅ Preview deployment has been cleaned up.

@pkg-pr-new

pkg-pr-new Bot commented Aug 19, 2026

Copy link
Copy Markdown
npx https://pkg.pr.new/@herb-tools/formatter@2289
npx https://pkg.pr.new/@herb-tools/language-server@2289
npx https://pkg.pr.new/@herb-tools/linter@2289

commit: 73127af

@marcoroth
marcoroth merged commit 8f838a9 into main Aug 19, 2026
33 of 34 checks passed
@marcoroth
marcoroth deleted the analysis/derived-state branch August 19, 2026 00:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

analysis rbs RBS type signatures in sig/ reactivity Reactive ERB templates: diff and re-render only what changed ruby Ruby source for the gem and its libraries rubygem The herb RubyGem and its packaging rust Rust bindings and the Herb Rust crate typescript TypeScript source across the javascript/ packages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant