¿Por qué Smart Cast falla por el valor anulable después de establecer el valor y verificar si es nulo?

Digamos que tengo la siguiente class:

class MyClass { private var username: String? = null private var projectName: String? = null private var buildNumber: Int = -1 private val presenter: Presenter = Presenter() fun present() { username = "" projectName = "" if (username != null && projectName != null && buildNumber != -1) { presenter.viewReady(this, username, projectName, buildNumber) } else { throw Exception("You did something bad!") } } } 

¿Por qué es que obtengo el error Smart cast to 'String' is impossible, because 'username' is a mutable property that could have been changed by this time ?

¿Tiene algo que ver con no ser seguro para subprocesss?

En base a los documentos de security nulos , pensé que esto funcionaría para cualquiera de 1. ese username y username projectName están configurados en la misma function sobre su uso como params y 2. que su uso como params está envuelto en una statement if que verifica su valor .

El comstackdor de Kotlin no puede probar que el username o el username projectName estén mutados por otro hilo al mismo time. El campo privado no ayuda tampoco, ya que la reflexión puede eludir esto.

La documentation relevante para esto es en Verificaciones de Tipo y Conjuntos :

Tenga en count que las versiones inteligentes no funcionan cuando el comstackdor no puede garantizar que la variable no pueda cambiar entre la verificación y el uso. Más específicamente, los moldes inteligentes son aplicables de acuerdo con las siguientes reglas:

  • val variables locales – siempre;
  • properties val – si la propiedad es privada o interna o la verificación se realiza en el mismo module donde se declara la propiedad. Los lanzamientos inteligentes no son aplicables a las properties abiertas o las properties que tienen getters personalizados;
  • variables locales var – si la variable no se modifica entre el cheque y el uso y no se captura en una lambda que lo modifique;
  • properties var – nunca (porque la variable puede ser modificada en cualquier momento por otro código).

Capture la reference de propiedad en una variable local en su lugar.

que su uso como params está envuelto en una statement if que verifica su valor.

las declaraciones if en Kotlin no "capturan" una propiedad. Cuando declara un enunciado if que incluye una propiedad y lo vuelve a acceder dentro del bloque, el comstackdor puede arrojarlo de manera inteligente. Pero las reglas para acceder siguen siendo las mismas: el comprador será invocado dos veces.

Como @ mEQ5aNLrK3lqs3kfSa5HbvsTWe0nIu señaló, Kotlin no activa el fundido inteligente para las properties var . No estoy seguro de por qué se eligió esta estrategia, ya que private var pueden ser ingeniosas en versiones preliminares, pero eso es lo que tenemos.

Ahora, supongamos que está seguro de que esas properties no están mutadas por otro hilo, ya que son private y no se utiliza ninguna reflexión. De este modo, el control if-null asegura que las properties no contengan nulos, pero el comstackdor de Kotlin no lo cree.

En tal situación, recomendaría encarecidamente usar el operador no-afirmación nula !! :

 presenter.viewReady(this, username!!, projectName!!, buildNumber) 

¡Muchos recomiendan evitar !! para mejorar su estilo de código, pero su significado es literalmente "the compiler is dumb, it is obviously not a null, notify me if I am wrong" . Situaciones como la suya son la razón por la que se introdujo en el idioma en primer lugar.

Existen otras soluciones que incluyen save el valor actual de la propiedad en un val local (explícita o implícitamente a través de funciones auxiliares), pero creo que son inapropiadas aquí, porque no expresan su intención. En algunas situaciones necesita la semántica save-current-value-and-work-with-it, pero aquí necesita la semántica make-the-compiler-trust-the-code, y obviamente no son lo mismo.