Is it possible to fix generic type to the type of the first parameter?


#1

I want to write an extension function which will be available on any type and accept parameter of the same type or subtype, but not a completely different type.

I tried naive approach but it didn’t work:

fun <T> T.f(x: T) {
}

fun main(args: Array<String>) {
  "1".f("1") // ok
  "1".f(1) // should be error
}

It seems that compiler just uses Any for T. I want T to be fixed to receiver type.


#2

What you need is two generic types. The type of x should be U where U is a subtype of T:

fun <T, U:T> T.f(x: U) {
  ...
}

#3

I tried this, it didn’t work. 1.f("1") or "1".f(1) compiles fine. It seems that there’s no way to do exactly that. Basically I’ve found 2 ways to do something similar:

  1. Return object of function using T. It’ll resolve when f1() called and then type will be fixed:
fun <T> T.f1(): (T) -> Unit = {x ->
}

  1.f1()(2)

This leads to use of () which is ugly. I tried to use extension property instead of function, but it didn’t work, I think, that it requires generic properties from 1.1. Also originally I wanted to use infix function and it’s of course impossible with that syntax. Compare:

1 f 2
// vs
1.f()(2)
  1. Use generic class without co- or cotra-variance as second argument:
class value<T>(val value: T)

fun <T> T.f2(value: value<T>) {
}

1.f2(value(1)) // ok
1.f2(value("1")) // error

Compiler can’t cast value<String> to value<Any> so it must use exact type. That’s why it works. Unfortunately if argument type is subtype of receiver type, it doesn’t work either, one must use value<supertype>(subtypeValue) to make it work. But on the other hand, infix notation is possible and looks good enough.

I still think that there should be a way to fix generic type to one argument type. It makes DSL much better. If I’m using T.function syntax to extend a class, I want T to be exactly this class, not its arbitrary superclass, it just doesn’t make sense for me.

Basically I’m working on mocking library prototype, which would use the following syntax:

val l: List<String> = mock {
  it.get(0) returns "s"
  it.get(1) throws IllegalArgumentException("error")
  it.size returns 1
}

It looks really beautiful but not type-checked. And any other solution leads to ugly code.


#4

FYI: If you tried an extension property then you probably ran into KT-10364. From http://stackoverflow.com/a/38445684/3255152:

You can fix T to the receiver type by making f an extension property that returns an invokable object:

val <T> T.f: (T) -> Unit
    get() = { x -> }

fun main(vararg args: String) {
“1”.f(“1”) // will be OK once KT-10364 is resolved
"1".f(1) // error: The integer literal does not conform to the expected type String
}

Unfortunately "1".f("1") currently causes an error: “Type mismatch: inferred type is String but T was expected”. This is a compiler issue. See KT-10364. See also KT-13139. You can vote on and/or watch the issues for updates. Until this is fixed you can still do the following:

"1".f.invoke("1")
/* or */
("1".f)("1")
/* or */
val f = "1".f
f("1")