Repository navigation
Conversation
MeguminBOT
requested changes
Sep 30, 2026
MeguminBOT
left a comment
Owner
There was a problem hiding this comment.
Reproduced: a read through null answers null in both modes, and a compiled write does
nothing. Raising is what Haxe and hscript do, so the direction is right. The trouble is that
scripts can rely on today's behaviour, most visibly as a null check one level too deep:
if (level.player.sprite != null) ... // level.player is still null hereThat runs today and throws with this PR. On eval, h.kept == null, if (h.kept != null),
h.a.b, a null static's field and f().x all go from null to an error. Nothing that works
today may break, so could you put the raise behind a switch that is off by default, a Config flag
like strictAccess or a define, so a host opts in?
Two smaller things while you're there:
- The error is
EInvalidAccess, whose hint says the field "exists but is not visible from a
script ... Aprivatemember is only reachable from inside the type that declares it". For a
null object that sends people the wrong way; the null case wants its own message. - HashLink's compiled code isn't covered: the
hl-bytecodecolumn answers0for both reads and
Null accessfor the write, before and after. Fine to leave for later, but worth saying in the
description, which says "interpreted and compiled".
The interpreter read a segment after a null value as the next part of a type path, found no such type, and answered null. Compiled code read host fields through Reflect.getProperty, which answers null for a null object, and wrote through Reflect.setProperty, which does nothing. Both now raise Invalid access to field, as the interpreter already did for a write. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Scripts rely on a read through null answering null, often as a check one level too deep, so the raise is off by default and a host opts in. The null case gets its own error, ENullAccess, worded "Null access to field f" with a hint about the null object, instead of EInvalidAccess, whose hint is about private members. The interpreter's existing raises on a call or a write through null use it too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tutez64
force-pushed
the
bugfix/null-field-access
branch
from
October 2, 2026 16:39
d019982 to
91c0d72
Compare
Contributor
Author
|
Done in 91c0d72 (on top of the original commit, and the branch is rebased on main):
|
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.
Reading a field of a null object answered
nullinstead of raising, in both modes:resolveFieldwalksa.b.csegment by segment and treats a nullgotas a path still being resolved. After a variable, a call or a field that held null, the next segment was looked up as the typeh.kept, found nothing, and the chain answered null. It now tracks whethergotholds a value; a null value followed by a field raisesEInvalidAccess, or answers null for?.. Paths that are still types or packages (haxe.ds.Option.None) are unchanged.Reflect.getProperty, which answers null for a null object, and a write throughReflect.setProperty, which does nothing. Both now go through a small runtime helper,hxscript.runtime.Fields, which raises the interpreter'sInvalid access to field <f>before delegating toReflect. Reads on a live object are unchanged.Three corpus cases (host field read, host property read, host field write on null). Before this change the read cases answered
nullin every column. With it, eval-interp, hxcpp-interp, hxcpp-cppia and hxcpp-cppia-jit all answerInvalid access to field kept/scaled. Corpus totals: 348 ok / 5 threw on the hxcpp columns; eval-interp also has the pre-existingkilledon "an abstract Map through its alias" (Instance prototype not found: haxe.ds.Map).test/run.sh eval cpp: 421 / 421 / 440 passed. HashLink not run.🤖 Generated with Claude Code