Nulls and generics


#1
fun <TReturn: Any?> 
PsiElement.acceptFooPsiVisitorIfPossible( visitor: FooPsiVisitor<TReturn>,
                                          defaultReturnValue: TReturn = null ): TReturn
{
    if( this is FooPsiElement )
    {
        return this.accept( visitor )
    }
    return defaultReturnValue
}

The default parameter value of null isn’t accepted: Null can not be a non-null type TReturn.

Rather than tediously spelling out each of the issues this example touches on, I’ll assume it can be understood and I’ll just ask: How is this supposed to be accomplished without just writing this in Java?

Thanks.


#2

TReturn can be substituted with both nullable and non-nullable types. In the latter case it would be incorrect to use null as its default value. However you can declare the variable defaultReturnValue as TReturn? and it would be allowed to take null as the default value.


#3

On second thought, at least the first of the list of issues I feel this brings up was already written in a comment in my code. So…copy-and-paste to the rescue!:

Grr… Kotlin doesn’t allow this (Kotlin version: 1.1-M04). IMO, the default argument value of null should immediately set a bound on the type inference to only nullable types. No? Only when the call site provides a different, non-null, value for defaultReturnValue would that bound be removed, allowing type inference to assume a non-null type for TReturn.

I’m actually not immediately sure how this can be accomplished with Kotlin’s current behavior. Writing it in Java is probably the only way. :\


#4

Thank you for the response lya.

So far I’ve already recognized that that seems to be the interpretation the compiler has made of this code and that that is why it is complaining, but first of all I feel, instead, that assigning a null default value to the parameter immediately tells the compiler that TReturn can only be inferred to be nullable type.

If at the call-site, a non-null value is provided for defaultReturnValue, only then can it expand its inference to include non-nullable types within the possible set of types for TReturn.

Does that not sound like the logical treatment of this?


#5

The second issue came about when considering what the workaround would be for this.

I want the return value of the function to only be contained within the actual range of values of the actual type of TReturn. If I were to force the parameter & return type of this function to be TReturn? as you suggest (and as I had considered), the battle is lost. The caller just ends up with a return value it can’t use in places typed as TReturn (it just passes the problem down the line). Instead, the user of this function should have the error be presented to them as an error in their specification of the defaultReturnValue argument; the compiler should present an error to the user that defaultReturnValue is not a possible value of TReturn (since at the call-site the compiler will actually know what TReturn is—since the type of visitor will be known).

Along the path of consideration for workarounds was creating to versions of the function:

  1. A single parameter version that doesn’t take a user-provided default return value (using null as an assumed default return value) that can only be called with nullable TReturnType-s.

  2. One that does take a user-provided parameter defaultReturnValue. This version solves the figuring of the default return value by leaving that to the user to explicitly provide (ie. there is no default value provided for that parameter).

Then I realized that this workaround would not be possible as AFAIK there is no syntax for specifying an exclusively nullable type in that specialized first version of the function. Is there, maybe?

That was the second issue that arose.


#6

Oh, now I see one of the rabbit holes. It took explicitly writing that above-quoted assumption to notice what one of the challenges to my logic is here: even in my expected treatment of this, the compiler doesn’t necessarily know that the type parameter of the FooPsiVisitor used in its first argument visitor is the definitive informer of what type TReturn is, does it?

D’oh.

(::dreaming:: It would be nice to have a facility to indicate that once such part of a signature is the definitive informer of a type parameter :open_mouth:)


Solutions were there all along, but only JetBrains can use them
#7

That’s an interesting idea, at least I couldn’t find a contradiction in it readily (though I don’t have much experience in designing overload resolution and inference).

Currently the compiler doesn’t work that way: the signature of the function is fixed during the compilation and at the call site types are inferred according to the constraints specified in that signature. Default value of an argument is an implementation detail of the function and cannot affect type inference of its usages.


Type inference in lambda default argument
#8

Interesting idea indeed. But it isn’t easy to implement because currently compiler work via different way(like said @ilya.gorbunov). But it possible.
Anyway, you can file new design issue here