Repository navigation
A class's optional function field fills an interface's optional one - #615
Merged
Merged
Conversation
`class C { fn?: () => int }` seen as `interface I { fn?: () => int }` was a
deliberate compile error: the slot points at a method cell shared by every
instance, holding a thunk that calls `this.fn(...)`, so an unset field read
as present and was called.
Every method cell is now {method, flag offset}, the method first so the
slot is still a ref to it. A method's (or a non-optional field's) offset is
-1; an optional field's is the offset from `this` of its optional's flag.
InterfaceSymbolRefOpLowering reads the offset on an optional read and gives
a null address while the instance's flag is clear. A class's optional field
still cannot fill a required interface field.
The compile-error test is replaced by run tests (AOT, JIT, rc/none corpus):
set, unset, assigned later, assigned undefined, inherited, a parameter, and
a mixed array at one access site.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Writing one (`m.fn = f`) is "saving to constant object" for every implementer: a read binds the interface's `this`, and a write could not be checked against the object behind the interface. The comments claiming a write changes a class's shared method cell are corrected, the lowering's presence check no longer looks for a write, and a test pins the error for the cell case (a class's optional function field). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The guard read isa<OptionalType> of the found field's type before checking the field was found. For a class lacking the field (`class WithoutFn`) the type is null and the cast dereferences it: the compiler segfaulted on Linux (found by ASan), while the MSVC build happened to survive. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ASDAlexander77
added a commit
that referenced
this pull request
Oct 19, 2026
#615 made a method cell {method, flag offset}: an object's thunk cell now has that layout too, with -1 (always present), so a read of an optional interface field finds its offset where it looks for it. 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.
Summary
class C { fn?: () => int }seen asinterface I { fn?: () => int }was a deliberate compile error since #614 ("field type not matching"). The interface slot points at a method cell shared by every instance. That cell holds a thunk callingthis.fn(...), so an unset field read as present and was called.{method, flag offset}, with the method first so the slot is still a ref to it. Methods and non-optional fields store -1, meaning always present. An optional class field stores the offset fromthisof its optional's flag:&(null)->fn+SizeOf(T), which is 32 on x64 and 16 on i686 for the test class.InterfaceSymbolRefOpLoweringreads that offset. It loads the instance's flag only when there is one, and gives a null address (undefined) while the flag is clear. Under rc the thunk is never called on an unset field.m.fn = f) stays "saving to constant object" for every implementer. A read bindsthis, and a write could not be checked against the object behind the interface. The comments that claimed a write changes the shared cell are corrected, andtest-compile-interface-function-field-writepins the error.Tests
00interface_optional_function_typed_field_class_field.ts(AOT, JIT, rc/none corpus). It covers set, unset, assigned later, assignedundefined, two instances sharing the cell, a field between other fields, a parameter, an inherited field, and a mixed array at one access site.21 0instead of21 -1.test-compile-interface-optional-class-function-fieldandinterface-errors/optional_class_function_field.tsare removed.ctest -C Release -j 16 --timeout 300: 4736/4736.Note: under rc some test closures capture the instance they are stored in. That cycle leaks, which the corpus does not check. Bound methods do not hold their receiver yet (fix-rc-bound-this-more), so the tests keep instances in locals.
🤖 Generated with Claude Code