groostav
04/25/2024, 12:36 AMDontInferAny
so that I can avoid a call to Collection.minus<T>(it: T)
inferring T
is Any
?
I used to have this code
data class SimpleDomainValueType(name: String);
data class Customer(simples: List<SimpleDomainValueType>);
fun businessLogic(cust: Customer){
val specialSimples: List<SimpleDomainValueType> = doBusinessLogic()
val result = specialSimples- cust.simples
}
Then I did a refactor
data class SimpleDomainValueType(name: String);
class SimpleDomainValueCollection {
operator fun get(index: Int): SimpleDomainValueType = ...
val size: Int get() = ...
}
data class Customer(simples: SimpleDomainValueCollection);
And I spent 30 minutes tracking down that the line val result = specialSauceItems - cust.simples
still compiles, because minus
is effectively becoming
val result = specialSimples.minusElement<Any>(cust.simples)
which is definitely not what I want.
I suspect its not what the overwhelming majority of people want.
I would think we could tell kotlin something like:
public operator fun <@DontInferAny T> Iterable<T>.minus(elements: Iterable<T>): List<T>
to tell kotlin that Any
is not an acceptable resolution for T
here.
---
this would blow up a lot of existing code, especially people using raw-typed lists or similar 🙃 but it makes my life easier and thats all that matters so i want it.Joffrey
04/25/2024, 1:12 AM@DontInferComparable
, and probably half a dozen more.
A more interesting question for me is, how do you use this resulting list? Didn't you get a compile error further down the line anyway? Having a List<Any>
is not really a problem in itself. Most likely the problem comes when you do stuff with it, so you would quite quickly get a wrong type somewhere.groostav
04/25/2024, 1:13 AMDontInferSerializable.
And thats a good point, the fact that the inferred variable type for result
is now List<Any>
instead of List<DomainType>
would likely induce a compiler error most times. In my specific case it gets passed into a kind of string renderer that isn't doing a strong type check, and it is inducing the cryptic runtime error that i tracked down for 30 minutesYoussef Shoaib [MOD]
04/25/2024, 1:30 AMOnlyInputTypes
does what you want.jw
04/25/2024, 1:56 AM@NoInfer
which is nice https://youtrack.jetbrains.com/issue/KT-54642jw
04/25/2024, 1:57 AM@OnlyInputTypes
, but it's in the same realmjw
04/25/2024, 1:58 AMgroostav
04/25/2024, 2:00 AM@NoInfer
on operator fun <@NoInfer T> Collection.minus(elem: T)
would mean that every invocation of minus requires an explicit type which would mean you could never use it as an operator, only as a method (IE you could no longer write things - elem
you would be forced to write things.minus<DomainType>(elem)
)groostav
04/25/2024, 2:01 AMjw
04/25/2024, 2:01 AMGat Tag
04/25/2024, 6:27 AM@DontInfer(Any::class)
?
My very limited time writing K2 compiler plugins makes me think this functionality could be implemented as a K2 plugin (But the validation would only work if the compiler plugin was included, otherwise there would be no validation and the annotation would be ignored.Gat Tag
04/25/2024, 6:33 AMmikhail.zarechenskiy
04/25/2024, 1:27 PMOnlyInputTypes
and other internal annotations should be productized, we definitely have room for improvement inside our stdlib by annotating—with softer rules—required functions that greatly rely on elements' equality.
Speaking of the NoInfer
annotation—from my perspective, it mostly makes sense for functions that infer their types based only on the expected type. This might be especially important for reified functions that perform some casts inside their bodies, and an 'implicit' change of the inferred type can have some unexpected consequences
// it'd be better to have NoInfer annotation to always specify type arguments explicitly
inline fun <reified T> Any.deserializeLike(): T {
return this as T
}
foo(x.deserializeLike()) // deserializeLike depends on foo, not on x
bar(x.deserializeLike()) // deserializeLike depends on bar, not on x
groostav
04/26/2024, 12:32 AMval inferred = some-expression
doStuff(inferred)
A core assumption behind type inference is that if `some-expression`'s return type changes, the compiler will introduce sufficient errors further down without the need for the user to explicitly denote the type when writing val inferred
.
In my specific case that didn't happen, the kotlin compiler did a reasonable job, i think, in changing the type of result
from List<DomainType>
to List<Any>
, but the problem for me was that List<Any>
was actually a sufficiently close-enough type to avoid causing a compile time problem, instead introducing bizarre runtime problems.
I guess what I'm advocating for, (in an already built system thats difficult to change), is that type inference be generous when inferring non-generic types and much more whine-ee when inferring generic types.
At the very least I would think IDEA can have a "suspicious type inference" warning anytime it sees something like List<Any>
as an inferred type; in most instances I'd wager that inference is wrong.Chris Fillmore
04/26/2024, 2:43 AMnoImplicitAny
, in the sense that you must be explicit