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

Paul King commented on GROOVY-12442:
------------------------------------

Delivered in [PR #2996|https://github.com/apache/groovy/pull/2996] (commits 
b84e0fe3a3, f665ee7032, fa35d67935) for 7.0.0-alpha-1.

*Operators*
* \{{x ?? y}}: returns \{{x}} unless it is \{{null}}, otherwise \{{y}}. Groovy 
Truth is not involved, so \{{0}}, \{{false}}, \{{''}}, \{{[]}} and \{{[:]}} are 
kept. \{{x}} is evaluated exactly once and \{{y}} only when \{{x}} is 
\{{null}}. When \{{x}} has a primitive type it can never be \{{null}}, so 
\{{y}} is not evaluated at all.
* \{{x ??= y}}: the null-coalescing assignment operator, which the description 
listed as a possible follow-up, is included. It assigns \{{y}} only when \{{x}} 
is \{{null}}. A primitive target is never assigned and \{{y}} is not evaluated.
* Both work in dynamic Groovy, \{{@TypeChecked}} and \{{@CompileStatic}}. Under 
static type checking, the inferred type follows the same rules as Elvis.

*Precedence*
{\{??}} has the same precedence as the ternary and Elvis operators and, like 
them, is right-associative:
* \{{a ?? b ?? c}} is \{{a ?? (b ?? c)}}
* \{{a ?: b ?? c}} is \{{a ?: (b ?? c)}}
* \{{0 ?? 1 ? 'yes' : 'no'}} is \{{0 ?? (1 ? 'yes' : 'no')}}

This differs from C#, JavaScript and Swift, where \{{??}} binds more tightly 
than the ternary operator. The user guide points this out and recommends 
parentheses when combining them.

*Lexing notes*
* \{{a??[0]}} is now \{{a ?? [0]}}, a null-coalescing expression with a list 
literal as the fallback. It was previously a syntax error (\{{'?[' requires an 
expression before it}}). Write \{{a?[0]}} for safe indexing.
* \{{??.}} is still the existing safe-chain-dot operator, being the longer 
match, so \{{a??.b}} is safe-chain navigation and \{{a??.b ?? c}} is \{{(a??.b) 
?? c}}. Put a space after \{{??}} if the right-hand side starts with \{{.}}.

*AST and tooling*
* \{{??}} is represented by \{{ElvisOperatorExpression}} with 
\{{isNullCoalescing()}} returning \{{true}}; its condition is \{{x != null}} 
instead of the Groovy Truth of \{{x}}. The new three-argument constructor 
\{{ElvisOperatorExpression(base, otherwise, nullCoalescing)}} and 
\{{GeneralUtils.nullCoalesceX(base, otherwise)}} create it. The existing 
two-argument constructor and \{{elvisX}} keep Elvis semantics, so existing AST 
code is unaffected.
* \{{ElvisOperatorExpression.getText()}} now renders \{{a ?? b}} or \{{a ?: b}}.
* The console's AST-to-script view renders \{{??}} correctly.
* \{{??=}} is \{{Types.NULL_COALESCE_EQUAL}}.

*Documentation*
New "Null-coalescing operator" and "Null-coalescing assignment operator" 
sections in the operators chapter, with \{{??}} and \{{??=}} added to the 
precedence table, and grammar reference updates.

*Not included*
{\{obj.prop ??= value}} evaluates the receiver \{{obj}} twice. It calls the 
getter and setter once each, and the setter runs even when the current value is 
kept. This is inherited from \{{?=}} and is tracked for both operators in 
GROOVY-12515.

> 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
>            Assignee: Paul King
>            Priority: Minor
>             Fix For: 7.0.0-alpha-1
>
>
> 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