[ 
https://issues.apache.org/jira/browse/GROOVY-12442?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18131946#comment-18131946
 ] 

ASF GitHub Bot commented on GROOVY-12442:
-----------------------------------------

paulk-asert commented on PR #2996:
URL: https://github.com/apache/groovy/pull/2996#issuecomment-6402791787

   Thanks Daniel, a thorough pass. All addressed in the latest commit except 
the two scope items noted below.
   
   1. **Console render.** Fixed. 
`AstNodeToScriptAdapter.visitShortTernaryExpression` now visits the true 
expression (the operand for both forms) and prints ` ?? ` or ` ?: ` according 
to `isNullCoalescing()`, so `a ?? b` round-trips as `a ?? b` rather than `a != 
null ?: b`. `AstNodeToScriptAdapterTest.testNullCoalescingOperator` locks it 
beside the Elvis case.
   
   2. **Other consumers.** I went through every in-tree 
`visitShortTernaryExpression` override and `ElvisOperatorExpression` 
construction. Apart from the console adapter, none interprets truth semantics: 
`SecureASTCustomizer`, the macro matchers and `ScriptToTreeNodeAdapter` 
authorise, match or label by class, so they're right for both forms. Two 
additions all the same: `ElvisOperatorExpression.getText()` now gives `a ?: b` 
/ `a ?? b` instead of the inherited `(a != null) ? a : b`, which is what error 
messages show; and `GeneralUtils.nullCoalesceX(base, otherwise)` sits beside 
`elvisX` so transform authors don't have to remember the three-arg constructor. 
I'd keep the single node class: codegen, the STC compound-assignment rewrite 
and `transformExpression` all share one path, and `instanceof 
ElvisOperatorExpression` plus the flag describes it honestly.
   
   3. **STC coverage.** `NullCoalescingOperatorTest` now parameterises 
`@TypeChecked` as well as `@CompileStatic` (29 cases). `STCAssignmentTest` has 
four `??=` cases mirroring the `?=` ones from GROOVY-10419/10628: setter 
through `with`, a getter/setter pair, kept falsy `Integer`/`int`/`String` plus 
the assigned-when-null path, and the type mismatch. They run under 
`@TypeChecked` and again under static compilation via 
`AssignmentsStaticCompileTest`, and the setter case exercises the 
`ensureValidSetter` line Codecov flagged. `TypesTest` asserts `isAssignment` 
and `ofType` for `NULL_COALESCE_EQUAL`.
   
   4. **`a??[0]`.** Agreed it needs calling out. I'll note it in the 7.0 
release notes and on GROOVY-12442 rather than here.
   
   5. **Property LHS once-evaluation.** You're right about the contract: 
`obj.prop ??= value` evaluates the receiver twice and the getter and setter 
once each, in all three modes. That's inherited from `?=` (verified on 6.0.0), 
so I've filed it separately as GROOVY-12515 covering both operators; the fix is 
the GROOVY-12099 temporary-slot treatment applied to `PropertyExpression`. In 
this PR, `testAssignToPropertyCallsAccessorsOnce` asserts the parts that are 
right (getter once, setter once) in all three modes. It also documents that, as 
for `?=`, the setter runs whether or not the fallback is used.
   
   6. **Shared base node.** The class javadoc now says the boolean and true 
expressions share the base and that codegen evaluates it once. Worth noting the 
double visit isn't new: Elvis has always wrapped the same base in its 
`BooleanExpression`, so `CodeVisitorSupport` visited it twice before this 
change too. What's new is that the condition is a real `COMPARE_NOT_EQUAL` 
binary, hence the note.
   
   7. **Mixed parser AST.** `ParserPositiveSyntaxTest` now has the trees for `a 
?: b ?? c` and `a ?? b ?: c`, with `nullCoalescing` on the right node.
   
   Nits: `StaticTypeCheckingSupport` javadoc lists `NULL_COALESCE_EQUAL`, the 
`ensureValidSetter` comment covers both operators, and the 
`ElvisOperatorExpression` javadoc introduces `??` up front with its 7.0.0 since.




> Introduce a null-coalescing operator (??) that provides a default value only 
> when the left-hand operand is null, independently of Groovy Truth.
> -----------------------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: GROOVY-12442
>                 URL: https://issues.apache.org/jira/browse/GROOVY-12442
>             Project: Groovy
>          Issue Type: New Feature
>          Components: syntax
>            Reporter: Jefferson H
>            Priority: Minor
>
> h2. Motivation
> Groovy currently provides the Elvis operator ({{{}?:{}}}) for default values. 
> However, its behavior relies on Groovy Truth, which treats values such as 
> {{{}0{}}}, {{{}false{}}}, empty strings, and empty collections as false.
> This behavior is useful in many situations, but it can also lead to 
> unexpected results when those values are intentional and valid.
> *For example:*
>  
> {code:java}
> def retries = 0
> def result = retries ?: 3
> assert result == 3 {code}
>  
> In this case, {{0}} is a valid configuration value representing no retries, 
> but the Elvis operator replaces it with {{{}3{}}}.
> Currently, developers must use an explicit null check:
>  
> {code:java}
> def result = retries != null ? retries : 3 {code}
>  
> This introduces unnecessary verbosity for a common use case.
> h2. Proposed Syntax
> Introduce the {{??}} operator:
>  
> {code:java}
> def result = value ?? defaultValue {code}
>  
> The right-hand expression should be evaluated only when the left-hand 
> expression evaluates to {{{}null{}}}.
> h2. Expected Behavior
>  
> {code:java}
> assert (null ?? 10) == 10
> assert (0 ?? 10) == 0
> assert (false ?? true) == false
> assert ('' ?? 'default') == ''
> assert ([] ?? [1, 2]) == []
> assert ([:] ?? [enabled: true]) == [:] {code}
> Short-circuit evaluation
>  
> The left operand should be evaluated exactly once, and the right operand 
> should only be evaluated when necessary.
> {code:java}
> def calls = 0
> def fallback = {
>     calls++
>     return 10
> }
> assert (0 ?? fallback()) == 0
> assert calls == 0
> assert (null ?? fallback()) == 10
> assert calls == 1
>   {code}
> h2. Comparison with Existing Elvis Operator
> ||Value||{{value ?: default}}||{{value ?? default}}||
> |{{null}}|default|default|
> |{{0}}|default|{{0}}|
> |{{false}}|default|{{false}}|
> |{{''}}|default|{{''}}|
> |{{[]}}|default|{{[]}}|
> |Non-empty value|original|original|
> h2. Benefits
>  * Provides explicit null-only fallback semantics.
>  * Preserves valid falsy values without additional conditional expressions.
>  * Improves readability and reduces boilerplate.
>  * Complements the existing Elvis operator rather than replacing it.
>  * Aligns Groovy with familiar null-coalescing syntax found in languages such 
> as C#, JavaScript, and PHP.
>  * Helps avoid subtle configuration bugs involving counters, flags, and empty 
> values.
> h2. Compatibility Considerations
> This proposal does not require changing the existing behavior of Groovy 
> Truth, {{{}?:{}}}, or {{{}?={}}}.
> The new operator should work with dynamically typed and statically compiled 
> Groovy code.
> Operator precedence, associativity, interaction with other conditional 
> operators, and parser compatibility would need to be defined as part of the 
> implementation.
> h2. Optional Future Enhancement
> A null-coalescing assignment operator ({{{}??={}}}) could be considered 
> separately:
> {code:java}
> def retries = 0
> retries ??= 3
> assert retries == 0 {code}
> The initial proposal focuses only on the {{??}} operator.
> h2. Acceptance Criteria
>  * {{??}} returns the left-hand value whenever it is not {{{}null{}}}.
>  * {{??}} returns the right-hand value when the left-hand value is 
> {{{}null{}}}.
>  * The left-hand expression is evaluated exactly once.
>  * The right-hand expression is evaluated lazily.
>  * Falsy non-null values are preserved.
>  * Existing Elvis operator behavior remains unchanged.
>  * The operator is supported by the Groovy parser and static compilation.
>  * Tests and language documentation are added.



--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

Reply via email to