Nullability in Java

Brian Goetz brian.goetz at oracle.com
Wed Oct 2 12:51:34 UTC 2024


This aligns pretty well with what we’ve been discussing.  Your principle (1) is key, because this is what enables adding nullity annotations to be source-compatible, and we want to minimize the friction to adding nullity annotations.  This means warnings, not errors, backed up by dynamic checks.  

From a language perspective, the states `String` and `String?` are semantically identical; the only difference between them is what warnings might be emitted.  

I don’t see the point of excluding locals (or indeed, any) declarations from being null-markable.  (Some are already implicitly null-restricted, such as the throws clause.)  Doing so will creates “gates” that impede the flow of type information, which would undermine your principle (3). 

> On Oct 2, 2024, at 3:16 AM, Remi Forax <forax at univ-mlv.fr> wrote:
> 
> Hello,
> i would like a propose a semantics adding a null-analysis to Java.
> 
> Principles
> - 1/ the compiler should emit a warning only if a NullPointerException will occur if the value is null
> - 2/ the syntax should explicitly indicate where a NPE can occur
> - 3/ the compiler should be smart enough to avoid bothering users when a NPE can not occur (by example: after a "if (a != null)" a should be considered as non-null)
> 
> Nice to have
> - while the internal representation is a 3 states (String!, String? and String), it should be cool if users can only denote two states to simplify the action a user can do
> - the change of the JLS should be confined to a section
> 
> Proposal:
> - on field, methods, casts, arrays and type arguments, the null-analysis marker '!' can be used to denote a non-null type.
>  A NPE will be raised
>  - if a null value is stored in such field, array,
>  - if a null value is casted to a non-null type,
>  - if a null value is pass as argument of a method parameter typed with a non-null type
> 
>  Example:
>    class Foo {
>      String! s;
>      String![] array = ...;
> 
>      void m(String! a) {} // NPE
> 
>      void main() {
>        s = aMethodThatReturnsNull();  // NPE
>        array[0] = aMethodThatReturnsNull();  // NPE
>        var o = (String!) aMethodThatReturnsNull();  // NPE
>        m(aMethodThatReturnsNull());   
>      }
>    }
> 
> - on field, methods, casts, arrays and type arguments, the null-analysis marker'?' can be used to denote a nullable type.
> 
> - Inside methods, local variable declaration are NOT annotated with nullability marker, the nullability is inferred,
>  so the analysis
>   - can be smarter than the current type-checking, especially the nullability of a local variable can change depending on the control flow
>   - the analysis can be separated from the type checking in the spec (but not in the compiler)
>   - even if the proposed semantics is not non-null by default, a user will not have to annotate a lot of locus, mostly only fields and methods.
> 
>  Example:
>   class Bar {
>     String! aMethodThatCannotReturnsNull() { ... }
>     String? aMethodThatReturnsNull() { return null; }
> 
>     void main() {
>       String s = aMethodThatCannotReturnsNull();  // the type of 's' is inferred as String!
>       String s2 = aMethodThatReturnsNull();       // the type of 's2' is inferred as String?
>       if (s2 != null) {
>         // here the type of s2 is String!
>       }
>     }
>   } 
> 
> - on fields and methods, every type wich is not a ! (a non-null type) is a ? (a nullable type)
>  if there is already a '?' or '!' in the same compilation unit (same file).
>  Having a '?' or '!' somewhere acts as an opt-in to the null-analysis mechanism, it guarantee that
>   - until you opt-in, no warnings can be raised
>   - if you opt-in, the user model has only two denotable kinds of nullability, String! and String?, the latter can written String
> 
> - A type variable is nullable by default and propagate the nullability information if a user has opt-in to the null-analysis.
>  At declaration site, E is equivalent to E?, which really means E extends Object?
> 
>  Example:
>   class Foo<E?> {  // a '?' is present so the user as opt-in to E propagating the nullability information
>     void m(E! e) { }  // m() can not be called with null
>     void m2(E e) { }  // equivalent to void m2(E?)  
>   )  
> 
> - As discussed previously, a field with a non-null type must be initialized in the constructor before the call to the super constructor call.
> 
> - @SuppressWarnings("null-analysis") allows to suppress any null warnings so even if a user has opt-in to the null analysis it can still
>  incrementally had the null marker later. This also allows add null markers on public methods (on the API) without having any warnings
>  in the implementation.
> 
> regards,
> Rémi
> 



More information about the valhalla-spec-observers mailing list