Skip to content

A class's optional function field fills an interface's optional one - #615

Merged
ASDAlexander77 merged 3 commits into
mainfrom
fix-optional-class-fn-field-presence
Oct 19, 2026
Merged

ASDAlexander77 merged 3 commits into
mainfrom
fix-optional-class-fn-field-presence

Conversation

@ASDAlexander77

Copy link
Copy Markdown
Owner

Summary

class C { fn?: () => int } seen as interface 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 calling this.fn(...), so an unset field read as present and was called.

  • Per-instance presence: every method cell is now {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 from this of its optional's flag: &(null)->fn + SizeOf(T), which is 32 on x64 and 16 on i686 for the test class.
  • Lowering: on an optional read, InterfaceSymbolRefOpLowering reads 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.
  • Still an error: a class's optional field cannot fill a required interface field.
  • Read-only interface function fields: writing one (m.fn = f) stays "saving to constant object" for every implementer. A read binds this, 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, and test-compile-interface-function-field-write pins the error.

Tests

  • New 00interface_optional_function_typed_field_class_field.ts (AOT, JIT, rc/none corpus). It covers set, unset, assigned later, assigned undefined, two instances sharing the cell, a field between other fields, a parameter, an inherited field, and a mixed array at one access site.
  • RED before: the compile error, then (with only the guard dropped) JIT printed 21 0 instead of 21 -1.
  • test-compile-interface-optional-class-function-field and interface-errors/optional_class_function_field.ts are removed.
  • Full Release 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

ASDAlexander77 and others added 3 commits October 19, 2026 15:36
`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
ASDAlexander77 merged commit ecb39cf into main Oct 19, 2026
2 checks passed
@ASDAlexander77
ASDAlexander77 deleted the fix-optional-class-fn-field-presence branch October 19, 2026 15:40
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>
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