Allow functions that take Nothing as parameter to be unimplemented

It’s often the case that when you implement a generic interface, the generic type is used to control the types of some of the parameters of some function:

interface SomeGenericInterface<I> {
  fun accept(input: I): I
}

Occasionally, the type will be Nothing, which today you would still have to implement (if you don’t implement it, you get a compile error)

object Implementation : SomeGenericInterface<Nothing> {
  override fun accept(input: Nothing): Nothing {
    error("This function can never be invoked.")
  }
}

Given that a value of type Nothing can never be produced, functions which accept Nothing as one of its parameters can never be invoked. It would be nice if the compiler “knew” that and would not issue an error if you did not bother to implement those functions. I.e. I would like for this to be allowed by the compiler:

object Implementation : SomeGenericInterface<Nothing> {
  override fun accept(input: Nothing): Nothing
  /*
   * Compiler doesn't complain about missing body for
   * functions it knows can never be invoked.
   */
}

Or perhaps even this:

object Implementation : SomeGenericInterface<Nothing> {
  /*
   * Compiler doesn't complain about unimplemented
   * abstract member, because it knows that member can
   * never be invoked.
   */
}

I believe this change would be backwards compatible, in the sense that all currently valid programs remain valid. This would just increase the number of valid programs. It would also save the programmer from doing mindless busywork that produces no value (providing a body implementation that properly type-checks).

2 Likes

I definitely see why this is appealing, but I think I’d still want the implementation to be explicit with how it fails. Like if you’re implementing a List<Nothing>, I would hope get(index: Int): Nothing throws an IndexOutOfBoundsException instead of an unspecific IllegalStateException

Yeah this would only apply if the function in question takes Nothing as an input. A method that returns Nothing as an output still needs an implementation – that implementation might be to go into an infinite loop, or throw an exception, or kill the JVM or something.

2 Likes

Also to be clear, I’m not proposing that we force the body to always be absent. I.e. if someone chose to implement a function which takes Nothing as a parameter, that’s still valid (this is part of my “all previously valid programs are still valid” claim). I’m just saying the programmer doesn’t have to provide an implementation.

Given the nature of type erasure, this just isn’t the case:

object Implementation : SomeGenericInterface<Nothing> {
  override fun accept(input: Nothing): Nothing  //don't have to implement it
}

fun main(){
    val shadyCast = Implementation as SomeGenericInterface<String>

     shadyCast.accept("boom?") 
}

You can still invoke the function, the result of the above invocation would be undocumented.

“booms” are common when doing shady casts. Consider the following (valid) program:

interface SomeGenericInterface<I> {
	fun accept(input: I): I
}

object Implementation: SomeGenericInterface<String> {
	override fun accept(input: String): String {
		return input
	}
}

fun main() {
	val shadyCast = Implementation as SomeGenericInterface<Int>
	shadyCast.accept(5)
}

This program type checks, and if you run it on the JVM, you get a ClassCastException:

Exception in thread "main" java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.String (java.lang.Integer and java.lang.String are in module java.base of loader 'bootstrap')

Therefore, it seems pretty reasonable that if you tried to run the program

object Implementation : SomeGenericInterface<Nothing> {
  override fun accept(input: Nothing): Nothing  //don't have to implement it
}

fun main(){
  val shadyCast = Implementation as SomeGenericInterface<String>
  shadyCast.accept("boom?") 
}

It would type check, but when ran, you’d similarly get a ClassCastException:

Exception in thread "main" java.lang.ClassCastException: class java.lang.String cannot be cast to class java.lang.Void (java.lang.String and java.lang.Void are in module java.base of loader 'bootstrap')

Incidentally, the above ClassCastException is the one you get whenever you try to cast something to Nothing, e.g.

fun main() {
	"Hello" as Nothing
}

Actually, I did a bit more tests, and it turns out it is the case, even with type erasure. Consider the following program (which is valid, type checks and compiles today):

interface SomeGenericInterface<I> {
	fun accept(input: I): Unit
}

object Implementation: SomeGenericInterface<Nothing> {
	override fun accept(input: Nothing): Unit {
		println("I don't actually look at my input, so if you COULD invoke me, I'd run.")
	}
}

fun main() {
	val shadyCast = Implementation as SomeGenericInterface<String>
	shadyCast.accept("no boom?")
}

Note that I’ve changed the interface to return Unit instead of whatever the generic type is.

In principle, the body of the function could be executed, since we never actually use the input parameter. However, if you actually execute it, you’ll find that the body does not excute:

Exception in thread "main" java.lang.ClassCastException: class java.lang.String cannot be cast to class java.lang.Void (java.lang.String and java.lang.Void are in module java.base of loader 'bootstrap')

So it would seem that, no, you cannot invoke a function which takes Nothing as a parameter, even if you try to use shady casts and try to take advantage of generic type erasure.

@NebuPookins do you have a concrete example where this would be useful?
The toy example you made doesn’t much help as you’re delcating a class which has no invokable memeber.

I ran into this issue in my “real world project”, but the codebase is rather big, so it’s hard to share the code. I can describe it in the abstract.

I’m building a strongly-typed state machine, where each State describes the possible Actions you can take while in that state, which would transition you to other States. So my State class takes a generic type variable describing the type of Actions allowed there, i.e. State<A: Action>, and it declares a function fun update(action: A): State<*> which accepts some Action and returns the next State you would end up in. The idea is that I want it to be a compile error to send an inappropriate Action to a State that could not accept that Action, instead of having it be a RuntimeException.

Some States are “terminal” meaning once you arrive in that state, there are no actions you can take and you can no longer transition anywhere. I’ve been modelling this as having State<Nothing>, and right now my update functions for those cases have just been TODO().

That is a brillant use of kotlins type system. I think I will steal that idea at some point. Not sure if I’m ever going to implement a state system but I guess this could be used in different places.


I really like the idea. You should create a youtrack issue for that. I wouldn’t be surprised if this gets implemented in the future. https://kotl.in/issue

1 Like

Thank you, I created it at https://youtrack.jetbrains.com/issue/KT-46824

1 Like