Should the second snippet not give a compiler erro...
# announcements
s
Should the second snippet not give a compiler error?
Copy code
val imap = mapOf("a" to "a")
// val mmap = imap as MutableMap

    imap["b"] = "b" //compiler error
Copy code
val imap = mapOf("a" to "a")
    val mmap = imap as MutableMap

    imap["b"] = "b" //runtime error java.lang.UnsupportedOperationException
d
you cannot change immutable map
val imap = mutableMapOf(“a” to “a”)
imap[“b”] = “b”
e
What's with
mmap
? What is it for?
a
This also does not give compile error:
Copy code
val imap = mapOf("a" to "a")
imap as MutableMap // Won't compile if i remove this
imap["b"] = "b" // Compiles just fine, but obviously runtime error
seems like
as
acts more like a statement than an expression 🤔
e
@arve un contraire,
as
is a pure expression - it returns a value of another type, it does not modify existing one. It is pretty much impossible. It is supposed to work like this:
Copy code
val imap = mapOf("a" to "a")
val mmap = imap as MutableMap
mmap["b"] = "b" // works
👍 1
a
That was what I would have expected as well. But why does my snippet above compile?
j
not sure what u want to achieve, but u can do this
Copy code
val mmap: MutableMap = mapOf("a" to "a")
mmap["b"] = "b" // also works
e
@arve due to smartcast. By statement
imap as MutableMap
(even though it did not have obvious effects) you presumed that
imap
is actually mutable and compiler took it for granted.
a
All right. My expectation would be that the statement was thrown away 😛
e
I was actually surprized a little bit too, I didn't know that smartcasts could work that way. A bug, perhaps? 🤔
a
It certainly feels like a bug if
as
is supposed to be an expression
especially given OP's example, i would not expect the original map to be affected (not that it is in practice, but during compile time it apparently is)
e
oh God, I just realized that OP's example implies smartcast misbehavior too
u
This all looks like it's working as intended. Smart casts have worked like this since I can remember.
g
This behaviour happening because actually
MutableMap
doesn’t exist on Java side and will be replaced with java’s Map interface (every map is “mutable” on Java side from types point of view) So Kotlin mapOf function returns instance of SingletonMap which is immutable, than you cast it to Map inteface (which is valid cast), so Smartcast now sure that this type is MutableMap, but when you call “SingletonMap.put” UnsupportedOperationException will be thrown on runtime
s
I don’t want to achieve anything with the code I am just wondering why I don’t get a compiler error in the second example since it should behave as in the first example.
imap[“b”] = “b” is used in both.
imap as MutableMap
should not change imap.
e
This ticket https://youtrack.jetbrains.com/issue/KT-15138 And ones it references explain the issue pretty well, also @gildor mentions
SingletonMap
. That's the issue as well
@samir smartcasting does that. Factually,
imap
is left intact after
as
, but smartcast system thinks that now you can use
imap
as
MutableMap
, because you already assumed that it is
MutableMap
.
It doesn't produce unchecked cast warning because
MutableMap
overrides
Map
s
@egor But should not only mmap be treated as a MutableMap?
e
@samir yes it should. But smartcast doesn't think so
s
So is this a bug(improvment on smartcast) or expected behaviour?
g
it’s expected behavior
smartcast does everything correctly. Because if this cast will fail it will be safe to use any mutable map members
Problem that you cannot represent MutableMap in byte code, such interface doesn’t exist
s
Ok, thanks(all) for the clarification
g
Any Kotlin Map can be casted to MutableMap without runtime exception, because on runtime only java.util.Map exists
e
It actually doesn't seem like a proper behavior, the one that you want to expect, does it?
g
Why?
It probably make sense to have some IDE warning about any casting or type check of MutableMap
Anyway, what is your proposed behaviour in this case? Throw ClassCastException
e
This discussion already took place in Telegram Kotlin chat and honestly I don't know what is it to be done apart from changing
mapOf
behavior (though, this is actually impossible now) for it not to return
SingletonMap
. You also can't throw
ClassCastException
, because casts are not broken. There is required a total reimagining either of the whole collections-related things or of the type system. Or it's just to accept that
mapOf/1
,
listOf/1
etc. work that way and from time to time explain it. But imho it's still rather silly that one of the most usable languages has such issue
g
what is it to be done apart from changing
mapOf
behavior (though, this is actually impossible now) for it not to return
SingletonMap
I don’t understand how this somehow help? You still have any other implementations of Map, not only in Kotlin, any method can return you kotlin.Map and it can be any type
This is just a problem of Java and Java interop
you cannot fix all problems of Java, but Kotlin tries it best
And you also forgot, that this is common problem in Java, all Java collections has this problem
also why anyone would cast type to MutableMap?
e
The main issue is that you can accidentally cast result of
mapOf
to
MutableMap
, which will result in notorious behavior. Yes, there's a bigger problem of Java interop, but you usually don't encounter it because you work with types you are aware of.
s
Would probably prefer if smartcast don’t assume on
imap
-> compiler error.
e
also why anyone would cast type to MutableMap?
Don't ask me, I never use mutable collections :^D But such questions and issues come up, right? Meaning some people, at least initially, suffer from it
g
Smartcast is not related to this issue. Imagine if you cast some unknown list to MutableMap (which is already not safe, runtime operation) and just return it from a function, no smartcast involved, but it would be the same behavior
How you can accidentally cast something? It's explicit operation
I agree, this can be improved by some IDE warning about nature of cast and type check for MutableMap, at least in some cases
don't encounter it because you work with types you are aware of.
What do you mean?
Also I don't understand why do you think that crash with ClassCastException is better than crash with UnsupportedOperationException?
s
I think it is odd that it changes the behaviour of the original variable. Makes sense if
mmap
execute put but not when
imap
does it
e
Why do you think that I think that
ClassCastException
is better? I explicitly said that it is nowhere to be thrown, nor there is the reason for it
s
imap as MutableMap
statement changes compiler behavior on
imap
feels error prone.
g
I see what you mean. But this is power of smartcast
e
How you can accidentally cast something
And still you get runtime exception from something that you expect not to fail
g
Casting also throws runtime exception
I explicitly said that it is nowhere to be thrown, nor there is the reason for it
Why? This is implementation detail only. Next version of Kotlin can return immutable map always
Or another backend (for example Native) can return actual List which not implement MutableMap
Because there is no Java Interop problems
e
Because casts are fine, according to JB's explanation: there is only
Map
existing in runtime, you can't (probably can't) fix it by adding or changing interfaces
g
And if Kotlin would have own collections, such operation would always throw ClassCastException
No, it's not fine
It's possible
Map existing on runtime correct, and implementation can throw any error on any method
So you just bypassed Kotlin types protection from using mutable methods of Map, so now in the same situation as in java
imap as MutableMap
statement changes compiler behavior on
imap
feels error prone.
Casting collection is error prone in any case. Kotlin just couldn't protect you from using existing Java APIs in a wrong way
e
As I said, I would like to see this problem solved, because it is behavior inconsistency of JS level, but I don't know how. At the very least, casts could work better 🤔
g
Of course I agree that this is not perfect behavior, but you can do nothing in this case. This is price that you pay to have great Java interop and performance no need to have own collections, no need to cover them from/to java ones
But this is not a problem of cast, how cast can work better in this case?
This is inconsistent with JS because just has own Map implementation
e
No, I meant figuratively
g
Anyone who rely on implementation detail of any API already in trouble
e
That only JS could have more unpredictable behavior
g
This is how use sun.misc.Unsafe
I see no inconsistency here, it's very consistent behavior
e
I don't think it is. Success of collection editing relies on how many arguments you passed to
mapOf
, like, what? Again, apart from the fact that it's not really smart to cast maps to mutable maps. There are circumstances and there are people with Java background, who haven't yet got used to immutable collections
g
What is "success of collection editing"?
e
uhhh, it's kinda weird I can't rephrase that
g
People with Java background should understand that they shouldn't just use any random collection instance returned from external API as mutable
Instead you should create a new instance of collection with content of this collection
e
"success of collection editing" is a successful (that is, without exceptions or problems) mutation of a collection
I mean, I don't really care that there's such an issue. It's somewhat nasty, but neither I nor anybody else gonna encounter it in production. But still it is a language problem which is to be resolved only by means of documentation and mythical coding conventions. In this particular case Kotlin fails in its main purpose - artificially create boundaries so that programmers don't shoot their legs off. But, yeah, I agree on that
This is price that you pay to have great Java interop and performance