Paul King created GROOVY-12209:
----------------------------------

             Summary: NullChecker: track nullness through ?., ?:, and ternary 
results
                 Key: GROOVY-12209
                 URL: https://issues.apache.org/jira/browse/GROOVY-12209
             Project: Groovy
          Issue Type: Improvement
          Components: groovy-typecheckers
            Reporter: Paul King


The incubating {{groovy.typecheckers.NullChecker}} tracks nullability per 
*variable*, and already handles several expression forms: dereferencing a 
{{@Nullable}}-returning method call is flagged, strict mode merges null-literal 
branches of ternaries, and elvis assignments over null-literal fallbacks are 
tracked (see {{testNullableMethodReturnDereference}}, {{testStrictTernary*}}, 
{{testStrictElvis*}}).

However, nullness of expression *results* is not propagated in general — the 
null-literal flows work while nullable-expression flows don't. Confirmed gaps:

* Safe-navigation results: the documented behaviour is unchanged — 
{{input?.length()}} stays OK, since {{?.}} guards the receiver it is applied 
to. The gap is the *result* of a safe navigation used as an unguarded receiver 
afterwards: {{a?.b.c}} parses as {{(a?.b).c}} and throws NPE at runtime when 
{{a}} is null, so it should be flagged ({{a?.b}} is nullable by definition, 
even when {{b}} is {{@NonNull}}), while {{a?.b?.c}} is fine. Currently only 
{{VariableExpression}} and {{MethodCallExpression}} receivers are checked in 
{{checkDereference}}, so a {{PropertyExpression}} receiver passes silently.
* Nullable expressions as arguments: {{isKnownNullable}} recognizes variables 
only, so passing a {{@Nullable}}-returning call or a safe-navigation result 
directly to a {{@NonNull}} parameter (e.g. {{process(repo.findById(id))}}) is 
not caught — only when first assigned to a variable.
* Ternary/elvis over nullable non-literals: {{cond ? maybeNull : other}} and 
{{x ?: maybeNullCall()}} where a branch is a nullable *variable or call* 
(rather than a null literal) are not merged into the result's nullness.
* {{@Nullable}} getters dereferenced via property syntax mid-chain: {{x.a.b}} 
where {{getA()}} is {{@Nullable}} is caught when written as {{x.getA().b}} but 
not in property form.
* Cast and parenthesized expressions should be transparent to nullness in 
{{isKnownNullable}} (they already are in {{isNullExpr}}).

Together with broadened guard recognition (companion issue), this moves the 
checker from "annotated variables" coverage to "annotated APIs used 
idiomatically" coverage — values obtained from framework calls and used inline, 
which is where most real null-mishandling occurs.




--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to