Hi! I'm not really sure which channel to post this...
# compiler
b
Hi! I'm not really sure which channel to post this to, but I have a weird issue with smart casting based on contracts. I am confident that I am the problem and not the compiler and I need help debugging the issue (it is part of an oss code base).
d
Do you have some relatively simple reproducer?
b
I am just now fiddling on play.kotlinlang.org, but i am only 50% confident i can get an MWE going with the same behaviour. I'll post back
d
It's a known issue: KT-73202
b
I don't think that's it. as I said. I am confident I am messing up, becaause it is a smart cast that works in one directon and not the other (it is a sealed interface with two possbile implementations.) gimme anotehr 10 minutes for a kotlin play
d
If it appears only in presence of annotation on getter/setter then it most likely it
b
not the case here
🆗 1
but i a prreciate it!
I cannot reproduce an MWE, but I can post links to the codebase that exhibits the odd behaviour
d
It would be usefull
b
The class in question is SymmetricEncryptionAlgorithm. It has three type parameters. Then there's a couple of extensions on this class, depending how it is typed. In this case the SealedBoxCreation is what it's all about: The issue arises here in this testcase in line 77: If you look a bit up to line 47 you can see the state of a nested `when`s. These functions all employ contracts defined here. In this particular case, the smart cast somehow contradicts itself and the sealed box is smart cast to one
WithoutNonce
in line 71 and somehow the call in line 77 resolves to a something types with
RequiresNonce
. All other smart casts work as expected
to get those contract to work, I obviously had to create a couple of interfaces that fix one of the type parameters of `SymmetricEncryptionAlgorithm`:
(I am still on-and-off wrestling with a small reproducer)
d
I think I've made the one. Please check that it's correct
b
Yup, line 72 triggers the glitch.
but how?
d
I'm investigating at the moment
b
the reproducer is very handy. thank you!
the odd thing is: it seems to work perfectly fine for the other generics on SymmetricEncryptionAlgorithm.
d
I found, where everything goes wrong (here and line 452 below, if you are interested), but I'm not sure yet what's actually happening. Could you please create an issue with a reproducer above while I'm working on this?
b
On it
There you go: KT-75444 Contracts for Fixing Generics Contradicts Itself
thank you color 1
d
It's actually not about contracts, but about type inference I've managed to reproduce it with plain if's and
is
checks
b
so why does it work for other params?
d
There is a bug in a mechanism of forking the subtyping during the inference in case of intersection types in position of expected types. There are very specific conditions to trigger it, since any change in types might simplify the intersection or even fold it to a regular type
b
Thanks! this must be at least the fifth obscure bug triggered by our codebase! Any suggestions for workarounds until it is fixed?
d
this must be at least the fifth obscure bug triggered by our codebase!
It's actually very good. There are not so many codebases with such a complex inference
Any suggestions for workarounds until it is fixed?
It's complicated, as with any false-negative. The question here is not about how to make something compilable, which is red because of bug, but how to prevent compilation of something bad, which is not reported by the compiler
You can rollback to 2.0.20 or explicitly disable this new behavior regarding intersection types inference with
-XXLanguage:-ConsiderForkPointsWhenCheckingContradictions
compiler flag
b
I know it's a good thing. that's why we participate in the EAP champions program. It's just that we keep hitting these things and it slows us down, Mind you: support from you guys was stellar, but we could not be using KMP in production if your codebase were not open source
-XXLanguage:-ConsiderForkPointsWhenCheckingContradictions
would be required by every consumer of our library, if i understand correctly. That does not help much, because our public API will still be broken. front-end bugs are most annoying, because they make no sense from a programmer's point of view and any possible workarounds also make no sense from a programmer's point of view. I remember one time where the workaround was to create a
var
from a parameter and user it the line below instead of just accessing the parameter
in case that was not clear: thank you very much for everything. I guess i'll be just writing biolerplate extensions on the sub-interfaces in all possible combinations and have thise delegate to the most generic one and make that one private
👍 1
d
would be required by every consumer of our library
Ouch, it could raise the severity to Critical
b
we don't yet have and consumers for that specific API, because it is a part of an in-review pull request. But I did understand correctly, that this bug breaks public APIs and even adding the compiler flag will leave it with a broken API for consumers unless they add the compiler flag?
d
Yes, that's correct
thank you color 1
b
i wonder why this never hit before
d
This feature was introduced in 2.1.0. So there are not much users updated to the compiler with it
b
i get that, but the stdlib uses contracts quite a bit
d
It's not about contracts, but about complex intersection types. Check the issue you've created, I've updated the reproducer so it doesn't include any contracts
b
sorry. yes. but still odd. smart casts are heavily used all over the place and your reproducer is pretty tight
anyways I guess there*s not much I can do except writing four more biolerplate extensions and hope to work around this issue.
d
If you remove any type parameter from any class from an example, then the issue won't reproduce. As I said, the conditions to trigger this a very specific
anyways I guess there*s not much I can do except writing four more biolerplate extensions and hope to work around this issue.
Unfortunately yes
b
thank you very much, for everything! on the off-chance my still-in-evaluation KotlinConf will be accepted after all, this bug would be a nice additional anecdote
👍 1
well, boilerplate code won't get me there, it seems, because it will break the API in other places, when, by definition, only the most generic variant can ever work