You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Phase 2 was intended to normalize lowered call semantics on top of
phase 1 PROPERTY_REF.
The target was:
make receiver-bearing calls explicit
reduce parser/generator shape recovery around calls
keep optional ownership on OPTIONAL_CHAIN
prepare a clean path for fixing grouped optional calls like
(o?.m)()
Landed Work
Phase 2 delivered the following.
1. Call contract tightening
The parser/generator contract is stricter than it was before:
PROPERTY_REF is the direct source form for receiver-bearing calls
METHOD_CALL is the receiver-bearing call form
FUNCTION_CALL is the plain value-call form
comma still strips receiver semantics
grouping still preserves receiver semantics
This removed some of the earlier silent recovery from incidental AST
shape.
2. Optional-chain helper cleanup
Optional-call and optional-target helper paths were split and localized.
The important result is that optional-chain state is no longer scattered
across unrelated parser/generator code paths. The remaining transitional
preserve-state handling is concentrated in a small set of helpers.
3. Narrow call-with-this runtime path
The grouped optional-call bug was fixed with a narrow runtime extension:
FUNCTION_FRAME_THIS
This lets codegen pass:
resolved callee
explicit this
for grouped optional plain-call cases without redesigning the entire call
model.
This path now fixes:
(o?.m)()
(o?.[k])()
the full optional-call-preserves-this test262 matrix
4. Regression coverage
Local unit coverage now includes the receiver-preserving optional-call
matrix that previously existed only in test262.
That matters because this area is easy to regress during parser/generator
cleanup, especially around grouping and optional ownership boundaries.
Resulting Invariants
At phase-2 closeout, these invariants hold.
Call lowering
direct property call sources lower through PROPERTY_REF
METHOD_CALL.left is expected to be PROPERTY_REF
FUNCTION_CALL may carry explicit this only for the grouped
optional-call preservation case
Grouping and value boundaries
grouping preserves receiver-bearing semantics
comma strips receiver-bearing semantics
Optional ownership
OPTIONAL_CHAIN remains the short-circuit owner
inner call mode remains plain vs receiver-bearing
optional ownership is not encoded as a call-mode variant
What Is Still Transitional
Phase 2 intentionally did not eliminate all transitional state.
The remaining transitional pieces are:
METHOD_CALL.u.object is still used as optional-call preserve state
FUNCTION_CALL.u.object is now also used for the narrow grouped
optional-call this handoff
OBJECT_VALUE is still used as the preserve wrapper node for
optional-chain state
This is acceptable for phase 2 because:
the state is now localized
the runtime behavior is correct
the remaining coupling is explicit and auditable
It is not yet the final lowered-AST design.
What Phase 2 Did Not Solve
Phase 2 did not attempt to solve all call modeling issues.
Specifically, it did not:
replace the current call node family with a unified call node
replace u.object transitional call metadata with a dedicated
semantic field layout
remove OBJECT_VALUE preserve wrappers from optional chaining
redesign bytecode around a general call-with-explicit-receiver model
Those belong to a later phase if we still want a cleaner lowered call IR.
Recommended Phase 3 Ownership
If work continues, phase 3 should be treated as structural cleanup, not
bug triage.
Recommended phase-3 targets:
separate transitional call metadata from generic u.object
reuse
reduce or remove OBJECT_VALUE as the optional preserve wrapper
decide whether FUNCTION_FRAME_THIS remains a narrow special path or
becomes part of a more general explicit call-with-receiver model
make the remaining optional-call preserve contract less dependent on
METHOD_CALL back-references
The key point is:
phase 2 already achieved the needed behavior and normalization
phase 3 should only be started if the goal is architectural cleanup
Closeout Assessment
Phase 2 is complete enough to stop here.
Reasons:
the original grouped optional-call this bug is fixed
the behavior is pinned by local unit tests and test262 coverage
parser/generator recovery is materially reduced compared to the
starting point
the remaining transitional state is localized rather than scattered
This split should be semantic only. For plain reads, PROPERTY_REF
should still generate the same bytecode as current PROPERTY reads if
the node is routed through a read path.
2. Parser lowering points
Parser should emit PROPERTY_REF when a property access is used in any
context that may require reference semantics:
method-call source
assignment target
delete target
optional method-call source
pre/post increment target
pre/post decrement target
tagged template receiver source
for ... in / for ... of property lvalue, if supported
Parser can still emit plain PROPERTY for value-only access.
The parser should do this by retroactive in-place upgrade from
PROPERTY to PROPERTY_REF, not by trying to predict the consuming
context during first parse.
3. Generator dispatch
Add explicit generator handling for PROPERTY_REF.
Important rule:
generator should never need to inspect a plain PROPERTY and wonder
whether it is really target-like
4. Serializer and side-effect helpers
Update:
AST serialization
njs_parser_has_side_effect() only as needed
PROPERTY_REF itself should remain side-effect free. Only call/update/
assignment/delete nodes remain side-effecting.
5. Delete interaction
Keep the existing mutation pattern.
Recommended rule:
PROPERTY used by delete first upgrades to PROPERTY_REF
PROPERTY_REF is then mutated to PROPERTY_DELETE
This keeps delete lowering consistent with the existing parser style and
avoids introducing a separate delete-only reference model.
Expected generator cleanup
This phase should simplify:
method call input handling
delete handling
assignment target selection
increment/decrement target handling
Risk
medium
Success criteria
existing property read behavior unchanged
delete and assignment behavior unchanged
generator has fewer token_type == PROPERTY plus context-specific
reinterpretation branches
Phase 2: Normalize Call Lowering
Goals
make call semantics explicit
remove call-mode recovery from AST shape
prepare for receiver-preserving optional call fixes
Files
src/njs_parser.h
src/njs_parser.c
src/njs_generator.c
maybe small updates in:
src/njs_vmcode.h
src/njs_vmcode.c
src/njs_disassembler.c
Options inside this phase
Option 2A: keep current call tokens initially
Keep:
NJS_TOKEN_FUNCTION_CALL
NJS_TOKEN_METHOD_CALL
But normalize them:
FUNCTION_CALL always means plain call
METHOD_CALL always means receiver-aware call
input shape must already be lowered and valid
This is the lower-risk first step.
Option 2B: unify under NJS_TOKEN_CALL
Introduce one call token with mode field.
This is cleaner, but larger.
Recommendation
Use Option 2A first.
Reason:
smaller parser churn
smaller serializer churn
still yields most of the benefit
Required parser behavior
The parser should only create:
METHOD_CALL from PROPERTY_REF
FUNCTION_CALL from value-only callees
receiver-aware tagged template call from PROPERTY_REF
That gives a clean invariant:
if a call needs receiver semantics, the AST already says so
Non-property expressions always produce value semantics, not
PROPERTY_REF.
This is what makes:
(a.b)()
preserve this
(0, a.b)()
lose this
without special cases in later codegen.
Generator behavior target
njs_generate_method_call() should stop recovering hidden semantics and
should assume:
its input already contains the correct receiver-bearing source
The transition plan should also explicitly remove the current
u.object-backreference dependency used by optional method calls once
PROPERTY_REF plus explicit optional ownership can encode the same
meaning directly.
Optional VM improvement
Current frame split:
FUNCTION_FRAME
METHOD_FRAME
Short term:
keep both if needed
Medium term:
add explicit "call with known callee + known this" path
This VM improvement is recommended but not required to complete phase 2.
Risk
medium
Success criteria
no generator-side call ownership analysis beyond direct node fields
call behavior stable for:
plain function call
property method call
parenthesized non-optional method call
Phase 3: Rework Optional Chaining On Top Of PROPERTY_REF
Goals
make optional chaining a true short-circuit owner
preserve receiver semantics through parenthesized optional access when
required
remove remaining optional-call AST recovery
Files
src/njs_parser.c
src/njs_parser.h
src/njs_generator.c
src/test/njs_unit_test.c
Parser target behavior
The parser should distinguish:
optional value access
optional reference-bearing access
This matters because:
o?.m
may later be used as plain value
(o?.m)()
must preserve receiver-aware call intent if the design chooses to
preserve it semantically
Recommended model
OPTIONAL_CHAIN should carry enough information to say:
what base is tested
whether the continuation yields:
value
PROPERTY_REF
call node
This ownership should be assigned retroactively when the consuming
context becomes known, not assumed at initial chain creation time.
Generator target behavior
Generator should no longer:
inspect optional-chain descendants trying to recover method ownership
Instead it should:
emit nullish short-circuit
seed continuation indexes from explicit lowered node meaning
preserve receiver temp only when node meaning says so
Key semantic cases to validate
o?.m()
o?.m?.()
(o?.m)()
(o?.["m"])()
o?.m\x``
delete o?.a?.b
a?.b.c
a.b?.c.d
(a?.b).c
a?.b?.c?.d
delete a?.b?.c?.d
o?.a = 1 remains invalid if grammar says so
Risk
high relative to prior phases
Success criteria
optional-chain code becomes local and declarative
parenthesized receiver-preserving optional call becomes fixable
without parser/generator hacks
Phase 4: Rework Assignment Lowering
Goals
constrain assignment inputs to lowered target shapes
simplify simple, operation, and logical assignment families
Files
src/njs_parser.c
src/njs_generator.c
src/test/njs_unit_test.c
Parser target behavior
Assignment nodes should no longer accept an arbitrary "lvalue-like"
expression and force generator to branch later.
The parser should already have normalized:
variable target
property reference target
This phase should explicitly include update expressions:
obj.prop++
++obj.prop
obj.prop--
--obj.prop
Generator target behavior
njs_generate_assignment(),
njs_generate_operation_assignment(), and
njs_generate_logical_assignment() should branch on explicit target
kind, not on arbitrary syntax node shape.
Benefits
clearer logical assignment lowering
simpler delete/assignment separation
fewer jump-placement mistakes caused by hidden target distinctions
increment/decrement lowering can use the same explicit target model
Risk
medium
Success criteria
assignment code paths are organized by target kind and operator kind
property-target logic is shared where possible
Phase 5: Remove Transitional Recovery Paths
Goals
delete compatibility code
tighten invariants
make the lowered AST contract explicit
Files
src/njs_parser.c
src/njs_generator.c
docs/tests as needed
Cleanup targets
AST shape recovery helpers that should no longer exist