¿Por qué el tipo de null + null es implícitamente String en Kotlin?

El siguiente código de Kotlin:

val x = null + null 

da como resultado que x sea ​​del tipo String , que es correcto según los documentos de String.plus :

Concatena esta cadena con la representación de cadena del object [otro] dado. Si el receptor o el [otro] object son nulos, se representan como la cadena "nula".

Sin embargo, no entiendo por qué ocurre esto, ¿se debe a alguna característica especial del idioma?

Probablemente porque String?.plus(Any?) Es la única function plus que acepta un tipo anulable como un receptor en la biblioteca de Kotlin. Por lo tanto, cuando llama null + null , el comstackdor tratará el primer null como String? .

Si define una function de extensión donde el tipo de receptor es Int? y el tipo de retorno es Int , entonces x se inferirá como Int .

 public operator fun Int?.plus(other: Any?): Int = 1 val x = null + null 

Si declara otra function similar dentro del mismo file (tipo anulable como el tipo de receptor), cuando llama null + null , causa el error de time de compilation: Overload resolution ambiguity. All these functions match. Overload resolution ambiguity. All these functions match. .

 public operator fun Int?.plus(other: Any?): Int = 1 public operator fun Float?.plus(other: Any?): Float = 1F val x = null + null //compile time error 
 val x = null + null 

Intenta reformular esto de la siguiente manera y encontrarás que respondes:

 val x = null.plus(null) 

Lo siguiente es lo que IntelliJ muestra como la firma del método plus :

 public operator fun String?.plus(other: Any?): String 

Entonces, ¿el primer null se trata como String? escriba y luego, cuando intente agregar más, el método de plus arriba es la única coincidencia que tiene. Imprimir x dará como resultado nullnull

Necesitamos comenzar con el tipo de Nothing . Este tipo tiene exactamente cero valores posibles. Es un tipo inferior , y es un subtipo de cualquier otro tipo (no debe confundirse con Any , que es un supertipo de cualquier otro tipo ). Nothing se puede obligar a nada a ningún tipo para que pueda hacer cosas como estas:

 fun doStuff(a: Int): String = TODO("this typechecks") 

¿Pasar al tipo de Nothing? , que significa Nothing o null . Tiene 0 + 1 valores posibles. ¿Entonces null tiene un tipo de Nothing? . Nothing? puede ser coaccionado con cualquier tipo que admita nulos, para que pueda hacer cosas como estas:

 var name: String? = null 

Aquí null : Nothing? se ve obligado a String? .

Por alguna razón, desafortunadamente, esta function está definida en stdlib :

 operator fun String?.plus(other: Any?): String 

que permite null + null aprovechando las reglas de coerción que mencioné anteriormente