Unsafe smart casts for open or custom getter

As we all know, smart cast is something like this:

fun main(args: Array<String>) {
	val a: Any = 233
	// a + 1 // error
	if (a is Int) println(a + 1) // OK
}

It also works with properties of objects.

But It seems not works with open/custom getters:

interface A { val a: Any }
class B(override val a: Any) : A
fun main(ags: Array<String>) {
  val a: A = B(2333)
  if (a.a is Int) print(a.a + 1) // error
}

I guess the reason is: the getter maybe returns different value in different times. So it cannot be smart cast.

But I think their can be something like ‘unsafe’: which the compiler ignore these kinds of issues.

Also, ‘unsafe’ will work with nullable variables:

var foo: SomeClass? = something
if (foo == null) return
foo.bar // here foo.bar is smartly cast as NotNull

How about that?

Sorry for my English.

If you call a get, the value can change in the class. Example

class B {
  val a
      get() : Any {
        if (random() % 2 == 0) 
          return  "1"
       else
          return 1.0
    }
}

If you save the value in a var, the smart check works

interface A { val a: Any }
class B(override val a: Any) : A
fun main(ags: Array<String>) {
  val a: A = B(2333)
  val aa = a.a
  if (aa is Int) print(aa + 1) // work
}

I know exactly the issue, but sometimes they’re logically impossible to change. what I want is to tell the compiler that do not check this problem. It can be successfully compiled as unsafe code.

We have no plans to add such an “unsafe” feature to the language. If you want to write unsafe code, you can use !! to convert any value from a nullable to a non-null type.

interface A { val a: Any }
class B(override val a: Any) : A
fun main(ags: Array<String>) {
  val a: A = B(2333)
  val aa = a.a
  if (aa is Int) print(aa + 1) // work
}

In situations like this where I need to capture a value from a property and use the value multiple times I often use the let method:

fun main(ags: Array<String>) {
  val a: A = B(2333)
  a.a.let { if (it is Int) print(it + 1) }
}

or sometimes use multiple calls in a fluent API (note that takeIf is only in 1.1.0 and above):

fun main(ags: Array<String>) {
  val a: A = B(2333)
  a.a
      .takeIf { it is Int }
      ?.let { it + 1 }
      ?.let { print(it) }
}

Those calls are all inlined so are equivalent to first version.

2 Likes