Repository navigation
Write a class's function field through an interface (h.f = g) - #711
Merged
Merged
Conversation
A class field holding a function fills an interface's function-typed field
through a thunk cell, so `h.f = show` had nowhere to store and failed with
"saving to constant object" under every memory model.
The cell is now {method, flag offset, field offset}. The field offset is the
class field's, when that field is stored as the interface field's function
made a hybrid (`f: (v: T) => void` in both), else -1. A store through the
interface clones the field's InterfaceSymbolRef with
INTERFACE_FUNCTION_FIELD_WRITE_ATTR and a ref to that hybrid; the lowering
gives `this` + the cell's field offset, or null for a method's cell or an
object literal's field. Which object is behind the interface is known only
at run time, so a null address stops the program with a message saying that
only a class field holding the function can be assigned (USER decision
2026-10-28: class fields are writable, methods and object literals are not).
An optional interface function field (`fn?:`) is still refused at compile
time, now with a message that says so.
-mm=own: the store has no ts.PropertyRef for collectFieldFamilies to see; the
cell initializer's PropertyRef of the field (its offset) is neither a load nor
a store, so the field's family is open. Spec section updated.
Tests: 00interface_function_field_write.ts (gc, own AOT+JIT, rc/none corpus),
interface-function-field-write/{method,literal}.ts runtime aborts under the
four models.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
When a class field holding a function fills an interface's function-typed field, assigning to that field through the interface didn't compile, under every memory model:
The interface's slot for
fpoints at a cell holding a thunk that callsthis.f(...), so a store through the interface had no field to write to.Fix
The user decided on 2026-10-28 that class fields are writable through the interface and methods and object literals are not. This narrows the 2026-10-19 rule that every interface function field is read-only.
{method, flag offset, field offset}.f: (v: T) => void.MLIRTypeHelper::getInterfaceFieldFunctionAsHybridbuilds that hybrid type.-1there: a method's, a field of another type, an optional field, and an object literal's thunk cell.mlirGenSaveInterfaceFunctionFieldhandlesh.f = x.ts.If.InterfaceSymbolRefwithINTERFACE_FUNCTION_FIELD_WRITE_ATTRand a ref to the hybrid type, then stores through it with the usual load/store path, which does the rc retain/release.thisplus the cell's field offset. It is null for a method's cell (offset-1) and for an untagged slot, which is an object literal's field.fn?:would also have to set the instance's presence flag. The error now says an optional function-typed interface field can't be assigned through the interface; assign it on the object.-mm=own
collectFieldFamiliesdoesn't see the store through the interface, because there is nots.PropertyRefthere. The family stays sound because the cell initializer takes ats.PropertyRefof the field to compute its offset. That use is neither a load nor a store, so it opens the field's family.On main this costs no precision, because a class cast to an interface already escapes. Under §58 (#710), a class seen through an interface can keep its field families. That PR would then need to treat the cell as a store only when a write-marked
InterfaceSymbolReffor that field exists in the module; this is noted in the spec.In the interface-facts scan, the new write ref is used by a cast, for the null test, so it counts as unknown. That is sound and only affects programs that store through an interface, which didn't compile before.
Tests
tests/00interface_function_field_write.ts:interface-function-field-write/{method,literal}.ts: the run-time stop under gc, rc, none and own (JIT).interface-errors/function_field_write.ts: the optional case, with the new message.ctest -C Release: 6063/6063 passed (Windows, Release).🤖 Generated with Claude Code