https://kotlinlang.org logo
Title
s

samir

11/02/2018, 11:07 AM
Should the second snippet not give a compiler error?
val imap = mapOf("a" to "a")
// val mmap = imap as MutableMap

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

    imap["b"] = "b" //runtime error java.lang.UnsupportedOperationException
d

Denis A

11/02/2018, 11:11 AM
you cannot change immutable map
val imap = mutableMapOf(“a” to “a”)
imap[“b”] = “b”
e

Egor Trutenko

11/02/2018, 11:12 AM
What's with
mmap
? What is it for?
a

arve

11/02/2018, 11:34 AM
This also does not give compile error:
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

Egor Trutenko

11/02/2018, 11:38 AM
@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:
val imap = mapOf("a" to "a")
val mmap = imap as MutableMap
mmap["b"] = "b" // works
👍 1
a

arve

11/02/2018, 11:41 AM
That was what I would have expected as well. But why does my snippet above compile?
j

Jakub Aniola

11/02/2018, 11:42 AM
not sure what u want to achieve, but u can do this
val mmap: MutableMap = mapOf("a" to "a")
mmap["b"] = "b" // also works
e

Egor Trutenko

11/02/2018, 11:44 AM
@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

arve

11/02/2018, 11:45 AM
All right. My expectation would be that the statement was thrown away 😛
e

Egor Trutenko

11/02/2018, 11:45 AM
I was actually surprized a little bit too, I didn't know that smartcasts could work that way. A bug, perhaps? 🤔
a

arve

11/02/2018, 11:46 AM
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

Egor Trutenko

11/02/2018, 11:48 AM
oh God, I just realized that OP's example implies smartcast misbehavior too
u

uhe

11/02/2018, 11:57 AM
This all looks like it's working as intended. Smart casts have worked like this since I can remember.
g

gildor

11/02/2018, 12:12 PM
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

samir

11/02/2018, 12:20 PM
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

Egor Trutenko

11/02/2018, 12:21 PM
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

samir

11/02/2018, 12:23 PM
@egor But should not only mmap be treated as a MutableMap?
e

Egor Trutenko

11/02/2018, 12:24 PM
@samir yes it should. But smartcast doesn't think so
s

samir

11/02/2018, 12:29 PM
So is this a bug(improvment on smartcast) or expected behaviour?
g

gildor

11/02/2018, 12:31 PM
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

samir

11/02/2018, 12:33 PM
Ok, thanks(all) for the clarification
g

gildor

11/02/2018, 12:37 PM
Any Kotlin Map can be casted to MutableMap without runtime exception, because on runtime only java.util.Map exists
e

Egor Trutenko

11/02/2018, 1:27 PM
It actually doesn't seem like a proper behavior, the one that you want to expect, does it?
g

gildor

11/02/2018, 1:27 PM
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

Egor Trutenko

11/02/2018, 1:42 PM
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

gildor

11/02/2018, 1:43 PM
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

Egor Trutenko

11/02/2018, 1:47 PM
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

samir

11/02/2018, 1:48 PM
Would probably prefer if smartcast don’t assume on
imap
-> compiler error.
e

Egor Trutenko

11/02/2018, 1:50 PM
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

gildor

11/02/2018, 1:58 PM
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

samir

11/02/2018, 2:03 PM
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

Egor Trutenko

11/02/2018, 2:03 PM
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

samir

11/02/2018, 2:03 PM
imap as MutableMap
statement changes compiler behavior on
imap
feels error prone.
g

gildor

11/02/2018, 2:03 PM
I see what you mean. But this is power of smartcast
e

Egor Trutenko

11/02/2018, 2:04 PM
How you can accidentally cast something
And still you get runtime exception from something that you expect not to fail
g

gildor

11/02/2018, 2:05 PM
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

Egor Trutenko

11/02/2018, 2:07 PM
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

gildor

11/02/2018, 2:07 PM
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

Egor Trutenko

11/02/2018, 2:15 PM
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

gildor

11/02/2018, 2:17 PM
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

Egor Trutenko

11/02/2018, 2:18 PM
No, I meant figuratively
g

gildor

11/02/2018, 2:18 PM
Anyone who rely on implementation detail of any API already in trouble
e

Egor Trutenko

11/02/2018, 2:18 PM
That only JS could have more unpredictable behavior
g

gildor

11/02/2018, 2:19 PM
This is how use sun.misc.Unsafe
I see no inconsistency here, it's very consistent behavior
e

Egor Trutenko

11/02/2018, 2:21 PM
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

gildor

11/02/2018, 2:26 PM
What is "success of collection editing"?
e

Egor Trutenko

11/02/2018, 2:27 PM
uhhh, it's kinda weird I can't rephrase that
g

gildor

11/02/2018, 2:27 PM
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

Egor Trutenko

11/02/2018, 2:28 PM
"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