Skip to content

feat: null-safe navigation (a?.b) and null-coalescing (a ?? b) - #707

Open
killme2008 wants to merge 1 commit into
masterfrom
feature/null-safe-navigation
Open

feat: null-safe navigation (a?.b) and null-coalescing (a ?? b)#707
killme2008 wants to merge 1 commit into
masterfrom
feature/null-safe-navigation

Conversation

@killme2008

Copy link
Copy Markdown
Owner

Closes #608.

What

Adds two operators to AviatorScript:

  • Null-safe navigation a?.b — if the guarded value is null, the property navigation short-circuits to nil instead of throwing.
  • Null-coalescing a ?? b — returns a when it is not null, otherwise evaluates and returns b (right operand is strictly short-circuited).
a?.b?.c        // nil if a or a.b is null
user?.name ?? 'Anonymous'

Semantics

Follows JS / Groovy: each ?. guards only its own segment. a?.b.c guards a only — if a.b is null, navigating .c still throws. Guard the whole chain with a?.b?.c.

?? only tests for nil; it does not catch exceptions. The right operand is not evaluated when the left is non-null.

Design

?. adds no new runtime operator. The lexer folds a?.b into a single variable token, normalizing the lexeme to keep a per-segment ? marker (a?.b → segments ["a?", "b"]). This marker rides along the existing variable-name path all the way to Reflector, so AviatorJavaType and both code generators need no constructor changes. Reflector.fastGetProperty strips the ?, and short-circuits to null on a null intermediate value only for marked segments.

?? is a parser-level short-circuiting operator (like &&/||), placed at ternary precedence and right-associative. All five CodeGenerator implementations emit the short-circuit; the interpreter uses a new BranchIfNotNilIR. OptimizeCodeGenerator records/replays it via delegate tokens, avoiding a new OperatorType.

Public getVariableNames() / getVariableFullNames() strip the marker, so a?.b is reported as a.b. Assignment to a null-safe target (a?.b = 1) is a syntax error.

Tests

NullSafeUnitTest + NullSafeInterpretUnitTest cover both eval modes (ASM + interpreter) and both optimize levels (EVAL + COMPILE): null root / mid-chain, unguarded null throws, Java-bean getters, ?? value/short-circuit/right-associativity, ternary regression, assignment rejection, and variable-name normalization. Full suite green (892 existing + new).

Known limitations (follow-ups)

  • ?. combined with dynamic or mid-chain array indexing (foo.bars[0]?.name) is not supported yet (syntax error).
  • No Elvis ?: — its token collides with the existing ternary "?:"; ?? covers the default-value use case.

Implements issue #608.

- Lexer folds `?.` into the variable lexeme, keeping a per-segment '?'
  marker (e.g. "a?.b" -> segments ["a?", "b"]).
- Reflector short-circuits to null on a null intermediate value when the
  segment is marked null-safe, instead of throwing NPE.
- Parser recognizes the right-associative, short-circuiting `??` operator
  and rejects assignment to a null-safe target.
- All code generators (ASM, interpreter, optimize, lambda, none) emit the
  short-circuit for `??`; a new BranchIfNotNilIR backs the interpreter.
- Public variable-name APIs strip the '?' marker ("a?.b" reported as "a.b").

Semantics follow JS/Groovy: each `?.` guards only its own segment; use
`a?.b?.c` to guard the whole chain.
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.

[建议]支持null-safety引用变量

1 participant