Some Eclipse Foundation services are deprecated, or will be soon. Please ensure you've read this important communication.

Bug 490339

Summary: Optional.orElse() doesn't accept null argument when parameter type is @NonNull
Product: [Eclipse Project] JDT Reporter: Colin Bartolome <cbartolo>
Component: CoreAssignee: Stephan Herrmann <stephan.herrmann>
Status: VERIFIED WORKSFORME QA Contact:
Severity: normal    
Priority: P3 CC: manoj.palat, slaurent, stephan.herrmann
Version: 4.5.2   
Target Milestone: 4.6 M7   
Hardware: PC   
OS: Linux   
Whiteboard:
Attachments:
Description Flags
OrElseNull.java shows the problem none

Description Colin Bartolome CLA 2016-03-23 17:13:11 EDT
Created attachment 260551 [details]
OrElseNull.java shows the problem

The documentation for Optional.orElse() explicitly allows code to pass a null value as the argument, which will cause null to be returned if the Optional is empty. In some cases, though, type inference produces an Optional of type <@NonNull T> and Eclipse forbids us from passing null in this case. This tends to come up when calling Stream.findFirst() on a stream that started off being of type <@NonNull T>. The attached code shows an example similar to production code that's going to need a workaround.
Comment 1 Stephan Herrmann CLA 2016-03-23 17:48:32 EDT
(In reply to Colin Bartolome from comment #0)
> Created attachment 260551 [details]
> OrElseNull.java shows the problem
> 
> The documentation for Optional.orElse() explicitly allows code to pass a
> null value as the argument,

Yes, the documentation says so. The issue is: compilers don't read documentation :)  but we do have a solution, hang on ...

> In some cases, though, type inference produces an
> Optional of type <@NonNull T> and Eclipse forbids us from passing null in
> this case. 

Correct, if you have an Optional<@NonNull Bar> then orElse automatically has this signature:

  @NonNull Bar orElse(@NonNull Bar other)


The tool for resolving this is help.eclipse.org/topic/org.eclipse.jdt.doc.user/tasks/task-using_external_null_annotations.htm

More specifically: configure your project as explained in the help, then navigate to the parameter of Optional.orElse and invoke the "Annotate" command (Ctrl+1) and choose "Annotate as '@Nullable T'". The same for the return type of the same method.

Henceforth the compiler will know about this contract and no longer complains about wrong usage of orElse().


Similar information (with pictures) can also be found in http://www.eclipse.org/eclipse/news/4.5/jdt.php#Annotations
Comment 2 Stephan Herrmann CLA 2016-03-23 17:50:14 EDT
I should add that you need Eclipse 4.5 or newer for the solution (the last link in comment 1 already hinted at this).
Comment 3 Colin Bartolome CLA 2016-03-23 18:08:16 EDT
I got my Eclipse version wrong in the initial report; this happens in 4.5.2, not 4.4.2. Thanks for the info about the external annotations!
Comment 4 Manoj N Palat CLA 2016-04-27 05:00:28 EDT
Verified for Eclipse Neon 4.6 M7 Build id: I20160425-1300
Comment 5 Sylvain Laurent CLA 2016-07-20 17:04:28 EDT
(In reply to Stephan Herrmann from comment #1)
> More specifically: configure your project as explained in the help, then
> navigate to the parameter of Optional.orElse and invoke the "Annotate"
> command (Ctrl+1) and choose "Annotate as '@Nullable T'". The same for the
> return type of the same method.

Actually this would break the contract of orElse, which returns a T. So, if you have a Optional<@NonNull String>, then orElse is supposed to return a @NonNull String...

Ideally the signature of orElse should be (with T coming from the class declaration Optional<T> ):

public <U super T> U orElse(final U other)

but this is not allowed by the java language :-(

I currently use a workaround with this helper method :

    public static <T> T optionalGetOrElse(final Optional<@NonNull T> opt, final T orElse) {
        if (opt.isPresent()) {
            return opt.get();
        }
        return orElse;
    }


It works with such usage :
        final @Nullable String nullableEntry = null;
        final String nullableString = NullCheckUtils.optionalGetOrElse(opt, nullableEntry);
        // on next line, eclipse sees a "potential null pointer access"
        nullableString.toString();


But weirdly eclipse 4.6 final fails to detect this :
        final String nullableString = NullCheckUtils.optionalGetOrElse(opt, null);
        // on next line, eclipse should have detected an error, but eclipse 4.6 does not
        nullableString.toString();

I also tried this signature for my optionalGetOrElse method:
public static <U, T extends U> U optionalGetOrElse(final Optional<T> opt, final U orElse) 

but then eclipse does not detect any error in both usages :-(
Comment 6 Stephan Herrmann CLA 2016-07-21 03:56:52 EDT
(In reply to Sylvain Laurent from comment #5)
> (In reply to Stephan Herrmann from comment #1)
> > More specifically: configure your project as explained in the help, then
> > navigate to the parameter of Optional.orElse and invoke the "Annotate"
> > command (Ctrl+1) and choose "Annotate as '@Nullable T'". The same for the
> > return type of the same method.
> 
> Actually this would break the contract of orElse, which returns a T. So, if
> you have a Optional<@NonNull String>, then orElse is supposed to return a
> @NonNull String...

That's not how I read the javadoc. 'other' is explicitly allowed to be null hence the parameter and return type of 'orElse' should read '@Nullable T'. My advice from comment 1 only formalizes an existing contract.


> But weirdly eclipse 4.6 final fails to detect this :
>         final String nullableString = NullCheckUtils.optionalGetOrElse(opt,
> null);
>         // on next line, eclipse should have detected an error, but eclipse
> 4.6 does not
>         nullableString.toString();

I see, so a '@Nullable T' is taken into account during type inference, but 'null' seems not to be. If so, this could be improved.

May I ask you to file a new enhancement request with a self-contained example for this? TIA.
Comment 7 Sylvain Laurent CLA 2016-07-26 16:44:39 EDT
done: Bug 498530