I think you need to mark your generic parameters as out, so variance would be correct (List<Int> could be assigned to variable of type List<Number>, for example). Now about Nil: just use Nothing type, it’s compatible for every type. So your example would be:
fun main(args: Array<String>) {
val l1: List<Int> = Cons(1, Cons(2, Cons(3, Nil())))
val l2: List<Number> = l1
printList(l2)
}
tailrec fun <T> printList(l: List<T>) {
when (l) {
is Cons -> {
print("${l.a} ")
printList(l.b)
}
is Nil -> Unit
}
}
sealed class List<out T>
class Nil : List<Nothing>()
data class Cons<out T>(val a : T, val b : List<T>) : List<T>()