Reputation: 12316
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.
Upvotes: 2
Views: 5579
Reputation: 31214
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")
Upvotes: 1
Reputation: 4786
The only way to do it requires telling the compiler what you want.
fun <T> T.f(x: T) {
}
In order to use it, you have to tell Kotlin what you want the type to be.
"1".f<String>("2") // Okay
"1".f(2) // Okay (see voddan's answer for a good explanation)
"1".f<String>(2) // Fails because 2 isn't a String
"1".f<Int>(2) // Fails because "1" isn't an Int
Upvotes: 6
Reputation: 85936
Your issue is like saying "John is 3 years older than Carl, and Carl is 3 years younger than John" ... you still don't know either of their ages without more information. That's the type of evidence you gave the compiler and then you expected it to guess correctly. The only truth you can get from that information is that John is at least 3 years old and Carl is at least 1 day old.
And this type of assumption is just like the compiler finding the common upper bounds of Any
. It had two strong literal types to chose from and no ability to vary either. How would it decide if the Int
or String
is more important, and at the same time you told it that any T
with upper bounds of Any?
was valid given your type specification. So the safe answer is to see if both literals could meet the criteria of T: Any?
and of course they do, they both have ancestors of Any
. The compiler met all of your criteria, even if you didn't want it to.
If you had tie-breaking criteria, this would work out differently. For example, if you had a return type of T
and a variable of type String
receiving the value, then that would influence the decision of Type inference. This for example produces an error:
fun <T: Any> T.f2(x: T): T = x
val something: String = "1".f2(1) // ERROR
Because now the type T
is anchored by the "left side" of the expression expecting String
without any doubt.
There is also the possibility that this could also be an type inference issue that is not intended, check issues reported in YouTrack or add your own to get a definite answer from the compiler team. I added a feature request as KT-13138 for anchoring a specific type parameter to see how the team responds.
Upvotes: 4
Reputation: 33749
When you call fun <T> T.f(x: T) {}
like "1".f(1)
, the compiler looks for a common super-type of String
and Int
, which is Any
. Then it decides that T is Any
, and issues no error. The only way to influence this process is to specify T
explicitly: "1".f<String>(1)
Since all the checks are performed by the compiler, the issue has nothing to do with type erasure.
Upvotes: 4