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.
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) {
...
}
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:
- 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)
- 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.
FYI: If you tried an extension property then you probably ran into KT-10364. From kotlin - Fix generic type to the type of the first parameter - Stack Overflow
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")