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)