Skip to content

Data class for Patch data - #35

Merged
hasumikin merged 2 commits into
masterfrom
fix/patch-data
Oct 20, 2026
Merged

hasumikin merged 2 commits into
masterfrom
fix/patch-data

Conversation

@hasumikin

Copy link
Copy Markdown
Member

Along with fixing RBS

The Differ built each DOM update as an Array (`[:replace, new, old]`,
`[:keep, old_index, new_index, patches]`, `[index, patches]`) and the
Patcher read it by position in 21 places. Such an Array has no element
types a type inference can hold, so every value the Patcher took out of
a patch was untyped, and the whole VDOM update path stayed off the AOT
compiler's typed path.

VDOM::Patch is a Data (mruby-data on mruby; each member is one method,
two patches with the same members are ==, and a patch cannot change
after it is built). `op` names the step and the members a step does not
use are nil, or NONE for a list; one constructor per step builds it
(Patch.replace, .remove, .text, .props, .child, .keep, .insert,
.keyed_children, .update_and_rebind). The Differ emits them, the
Patcher reads the members, and patch_t is Patch in the RBS, with the
member types declared.

Two things the rewrite ran into are fixed with it: `patches +=
child_patches` and the sort of the removes by a block are loops now,
since both boxed the Array for the inference; and the text handed to
createTextNode and nodeValue is a copy, since the content String is
shared with the VNode and the JS boundary takes a copy of its own. The
differ and patcher tests compare Patch values; the whole suite passes on
the wasm build.
@event_listeners (the Integer ids addEventListener answers, removed on
cleanup) and @child_components (Array[Component]) had no declaration,
so a reader of them was untyped. The inline notes on the assignments
say Array[Integer] now, as the declaration does.
@hasumikin
hasumikin merged commit f682bbf into master Oct 20, 2026
1 check passed
@hasumikin
hasumikin deleted the fix/patch-data branch October 20, 2026 09:34
hasumikin added a commit to picoruby/picoruby that referenced this pull request Oct 21, 2026
…ver untyped

A call on a receiver Spinel holds as a box took every def of its name,
in the dependencies too, so `patches.each` on an `Array[Patch]`
parameter reached IndexedDB::Store#each and kept its caller in the
irep, and so did `msgs.each` over the values of a Hash[Symbol,
Array[String]]. RbsIndex reads the gem's and its dependencies' RBS; when
Spinel typed a receiver poly or not at all, resolve_receiver asks the
declaration of the slot: a positional parameter of the def, an instance
variable (an attr counts), a block parameter of an iteration over a
declared Array or Hash (the element, or the key and the value by
position), or the value of a method or attr of a declared class. The
declared type maps to the scopes a call can reach as Spinel's own kinds
do; a union settles only when every member does. Trusting the
declaration is what Spinel does with the same file under --rbs and what
steep checks against the CRuby run.

On funicular, with its patches as a Data (picoruby/funicular#35), the
compiled defs go from 127 / 254 to 159 / 407 of 756, the Differ to 9 of
9 and vdom.rb to 23 of 35; the declared gems' counts move by one
(net-websocket widened 35 -> 36). Host twin SAME (103), twin32 SAME
(87). architecture.md states the rule; aot-plan 1.6 names what still
waits on the VDOM path (receivers behind an is_a? guard) and the next
two steps.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WA5cW6UWZ6UVReAknDAnxj
hasumikin added a commit to picoruby/picoruby that referenced this pull request Oct 21, 2026
The submodule takes picoruby/funicular#35: VDOM::Patch, a Data the
Differ emits and the Patcher reads by member, with mruby-data as a
dependency, and the RBS of the event listener ids and the child
components. The gem builds on the wasm test target with mruby-data in
place of picoruby-data (the previous commit) and its picotest suite
passes there (1535, 0 failures).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WA5cW6UWZ6UVReAknDAnxj
hasumikin added a commit to picoruby/picoruby that referenced this pull request Oct 24, 2026
…ver untyped

A call on a receiver Spinel holds as a box took every def of its name,
in the dependencies too, so `patches.each` on an `Array[Patch]`
parameter reached IndexedDB::Store#each and kept its caller in the
irep, and so did `msgs.each` over the values of a Hash[Symbol,
Array[String]]. RbsIndex reads the gem's and its dependencies' RBS; when
Spinel typed a receiver poly or not at all, resolve_receiver asks the
declaration of the slot: a positional parameter of the def, an instance
variable (an attr counts), a block parameter of an iteration over a
declared Array or Hash (the element, or the key and the value by
position), or the value of a method or attr of a declared class. The
declared type maps to the scopes a call can reach as Spinel's own kinds
do; a union settles only when every member does. Trusting the
declaration is what Spinel does with the same file under --rbs and what
steep checks against the CRuby run.

On funicular, with its patches as a Data (picoruby/funicular#35), the
compiled defs go from 127 / 254 to 159 / 407 of 756, the Differ to 9 of
9 and vdom.rb to 23 of 35; the declared gems' counts move by one
(net-websocket widened 35 -> 36). Host twin SAME (103), twin32 SAME
(87). architecture.md states the rule; aot-plan 1.6 names what still
waits on the VDOM path (receivers behind an is_a? guard) and the next
two steps.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WA5cW6UWZ6UVReAknDAnxj
hasumikin added a commit to picoruby/picoruby that referenced this pull request Oct 25, 2026
…ver untyped

A call on a receiver Spinel holds as a box took every def of its name,
in the dependencies too, so `patches.each` on an `Array[Patch]`
parameter reached IndexedDB::Store#each and kept its caller in the
irep, and so did `msgs.each` over the values of a Hash[Symbol,
Array[String]]. RbsIndex reads the gem's and its dependencies' RBS; when
Spinel typed a receiver poly or not at all, resolve_receiver asks the
declaration of the slot: a positional parameter of the def, an instance
variable (an attr counts), a block parameter of an iteration over a
declared Array or Hash (the element, or the key and the value by
position), or the value of a method or attr of a declared class. The
declared type maps to the scopes a call can reach as Spinel's own kinds
do; a union settles only when every member does. Trusting the
declaration is what Spinel does with the same file under --rbs and what
steep checks against the CRuby run.

On funicular, with its patches as a Data (picoruby/funicular#35), the
compiled defs go from 127 / 254 to 159 / 407 of 756, the Differ to 9 of
9 and vdom.rb to 23 of 35; the declared gems' counts move by one
(net-websocket widened 35 -> 36). Host twin SAME (103), twin32 SAME
(87). architecture.md states the rule; aot-plan 1.6 names what still
waits on the VDOM path (receivers behind an is_a? guard) and the next
two steps.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WA5cW6UWZ6UVReAknDAnxj
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