Why is the typing for MutableMap methods so weak?

Recently working with a Kotlin code base, I had to change the key type of a MutableMap instance and was expecting the compiler to show me type errors where inappropriate keys were being used with the map.

For example:

   val map = mutableMapOf<String, Int>("one" to 1)
   val d: Double = 1.234

Then this is a type error:

   map[d] = 5   // type error

But all of these compile fine:

   if (map.containsKey(d)) ...
   if (map.contains(d)) ...
   if (d in map) ...

All variants I guess of the same thing?

In my particular case, what caused me more pain and frustration was that this also raises no error or warning:

   map.remove(d)

Grrrr!!! It’s like programming in Python again. Finding mistakes like the above is surely one of the most compelling aspects of static typing.

I mean if it’s illegal to insert an item with a Double key value, why is it not illegal to remove an item with a Double key value? Is there any way of removing stuff from a MutableMap by key that is type safe without iterating over every element?

I remember that when generics were introduced into Java that some of the collections interface methods were not strengthened in terms of type safety in order to allow easier migration of legacy code to generic but what is the justification for Kotlin to NOT check for these sort of type errors?

That’s inherited from Java API I think.

To know why it is done that way, you can start by understanding why it’s the same phenomenon in Java:

Are you using the latest kotlin version?

I am getting a compiler and IDE warning when trying out your code, although it seems like the type inference here is failing and should throw an error instead.

Hi Alexis, thanks for your reply. Yes, I’m aware of the Java precedence for this as stated in the last paragraph of my initial message although I had forgotten that it even affects even simple value lookup.

I guess, I’m wondering why the Kotlin library designers decided to inherit this part of the Java collections API while replacing large areas (e.g. the Java streams stuff) with a superior API.

The only real reason for Java to offer a broken API in this regard was to ease the migration of pre-1.5 code bases to use the generic versions of util collections. All other statically typed languages with generics (e.g. Scala, C#, Swift, Rust, C++, etc.) that I know of provide properly typed generic collection APIs.

I always thought they made the wrong call here in Java (long term pain for short term gain) and I was unpleasantly surprised that Kotlin doesn’t provide replacements or alternatives for this broken (IMO) aspect of Java’s collections.

1 Like

@chipays - actually it’s weirder than I thought. I’ve been using the 1.4.0 Maven plugin and also IntelliJ CE (with Kotlin plugin 1.4.10).

Initial findings: all the code above compiles fine under Intellij and maven, except the “d in map” code.

If I include “d in map” piece of code, Intellij doesn’t highlight it as an error but if I manually rebuild the project, then the compiler throws an exception:

The root cause java.lang.IllegalStateException was thrown at: org.jetbrains.kotlin.codegen.state.KotlinTypeMapper$typeMappingConfiguration$1.processErrorType(KotlinTypeMapper.kt:110)
	at org.jetbrains.kotlin.codegen.ExpressionCodegen.genQualified(ExpressionCodegen.java:333)
	at org.jetbrains.kotlin.codegen.ExpressionCodegen.genStatement(ExpressionCodegen.java:416)
	at org.jetbrains.kotlin.codegen.ExpressionCodegen.gen(ExpressionCodegen.java:373)
	at org.jetbrains.kotlin.codegen.ExpressionCodegen.returnExpression(ExpressionCodegen.java:1760)
	at org.jetbrains.kotlin.codegen.FunctionGenerationStrategy$FunctionDefault.doGenerateBody(FunctionGenerationStrategy.java:64)
	...

Same with trying to build with the maven Kotlin plugin.

This is starting to look more like a bug to me rather than a question of API design.

All cases you have listed currently (in Kotlin 1.4.0) result in a compiler warning:

Type inference failed. The value of the type parameter K should be mentioned in input types (argument types, receiver type or expected type). Try to specify it explicitly.

This was an error in the previous versions of Kotlin, but we had problems implementing this compiler check in the new type inference landed in 1.4 (it produced some false positive errors), so we decided to temporary lower the severity of this check to warning. We’ll return the severity back to error as soon as we sort out these problems in the new type inference.

2 Likes

@ilya.gorbunov - thanks - good to know. I’ve come back to Kotlin after about a year’s break and was surprised that I hadn’t remembered this behaviour in previous versions - I’m fairly sure I would have noticed.

In case it’s helpful, here’s the full compiler exception triggered by trying to compile code like:

    var b = 1.5 in mutableMapOf("one" to 1)
Kotlin: [Internal Error] org.jetbrains.kotlin.codegen.CompilationException: Back-end (JVM) Internal error: Failed to generate expression: KtBlockExpression
File being compiled: (22,14) in ...
The root cause java.lang.IllegalStateException was thrown at: org.jetbrains.kotlin.codegen.state.KotlinTypeMapper$typeMappingConfiguration$1.processErrorType(KotlinTypeMapper.kt:110)
        at org.jetbrains.kotlin.codegen.ExpressionCodegen.genQualified(ExpressionCodegen.java:333)
        at org.jetbrains.kotlin.codegen.ExpressionCodegen.genStatement(ExpressionCodegen.java:416)
        at org.jetbrains.kotlin.codegen.ExpressionCodegen.gen(ExpressionCodegen.java:373)
        at org.jetbrains.kotlin.codegen.ExpressionCodegen.returnExpression(ExpressionCodegen.java:1760)
        at org.jetbrains.kotlin.codegen.FunctionGenerationStrategy$FunctionDefault.doGenerateBody(FunctionGenerationStrategy.java:64)
        at org.jetbrains.kotlin.codegen.FunctionGenerationStrategy$CodegenBased.generateBody(FunctionGenerationStrategy.java:86)
        at org.jetbrains.kotlin.codegen.FunctionCodegen.generateMethodBody(FunctionCodegen.java:639)
        at org.jetbrains.kotlin.codegen.FunctionCodegen.generateMethodBody(FunctionCodegen.java:467)
        at org.jetbrains.kotlin.codegen.FunctionCodegen.generateMethod(FunctionCodegen.java:249)
        at org.jetbrains.kotlin.codegen.FunctionCodegen.generateMethod(FunctionCodegen.java:165)
        at org.jetbrains.kotlin.codegen.FunctionCodegen.gen(FunctionCodegen.java:136)
        at org.jetbrains.kotlin.codegen.MemberCodegen.genSimpleMember(MemberCodegen.java:196)
        at org.jetbrains.kotlin.codegen.PackagePartCodegen.generateBody(PackagePartCodegen.java:95)
        at org.jetbrains.kotlin.codegen.MemberCodegen.generate(MemberCodegen.java:128)
        at org.jetbrains.kotlin.codegen.PackageCodegenImpl.generateFile(PackageCodegenImpl.java:149)
        at org.jetbrains.kotlin.codegen.PackageCodegenImpl.generate(PackageCodegenImpl.java:70)
        at org.jetbrains.kotlin.codegen.DefaultCodegenFactory.generatePackage(CodegenFactory.kt:88)
        at org.jetbrains.kotlin.codegen.DefaultCodegenFactory.generateModule(CodegenFactory.kt:67)
        at org.jetbrains.kotlin.codegen.KotlinCodegenFacade.compileCorrectFiles(KotlinCodegenFacade.java:35)
        at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.generate(KotlinToJVMBytecodeCompiler.kt:616)
        at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:203)
        at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:164)
        at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:51)
        at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:86)
        at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:44)
        at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:98)
        at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1474)
        at sun.reflect.GeneratedMethodAccessor96.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:357)
        at sun.rmi.transport.Transport$1.run(Transport.java:200)
        at sun.rmi.transport.Transport$1.run(Transport.java:197)
        at java.security.AccessController.doPrivileged(Native Method)
        at sun.rmi.transport.Transport.serviceCall(Transport.java:196)
        at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:573)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:834)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:688)
        at java.security.AccessController.doPrivileged(Native Method)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:687)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.IllegalStateException: Error type encountered: [ERROR : Type for 1.5 in mutableMapOf("one" to 1)] (ErrorType).
        at org.jetbrains.kotlin.codegen.state.KotlinTypeMapper$typeMappingConfiguration$1.processErrorType(KotlinTypeMapper.kt:110)
        at org.jetbrains.kotlin.load.kotlin.TypeSignatureMappingKt.mapType(typeSignatureMapping.kt:101)
        at org.jetbrains.kotlin.codegen.state.KotlinTypeMapper.mapType(KotlinTypeMapper.kt:262)
        at org.jetbrains.kotlin.codegen.state.KotlinTypeMapper.mapType$default(KotlinTypeMapper.kt:256)
        at org.jetbrains.kotlin.codegen.state.KotlinTypeMapper.mapType(KotlinTypeMapper.kt)
        at org.jetbrains.kotlin.codegen.ExpressionCodegen.asmType(ExpressionCodegen.java:472)
        at org.jetbrains.kotlin.codegen.ExpressionCodegen.getVariableTypeNoSharing(ExpressionCodegen.java:1364)
        at org.jetbrains.kotlin.codegen.ExpressionCodegen.getVariableType(ExpressionCodegen.java:1345)
        at org.jetbrains.kotlin.codegen.ExpressionCodegen.putLocalVariableIntoFrameMap(ExpressionCodegen.java:1394)
        at org.jetbrains.kotlin.codegen.ExpressionCodegen.putDescriptorIntoFrameMap(ExpressionCodegen.java:1377)
        at org.jetbrains.kotlin.codegen.ExpressionCodegen.generateBlock(ExpressionCodegen.java:1285)
        at org.jetbrains.kotlin.codegen.ExpressionCodegen.generateBlock(ExpressionCodegen.java:1239)
        at org.jetbrains.kotlin.codegen.CodegenStatementVisitor.visitBlockExpression(CodegenStatementVisitor.java:56)
        at org.jetbrains.kotlin.codegen.CodegenStatementVisitor.visitBlockExpression(CodegenStatementVisitor.java:22)
        at org.jetbrains.kotlin.psi.KtBlockExpression.accept(KtBlockExpression.java:78)
        at org.jetbrains.kotlin.codegen.ExpressionCodegen.genQualified(ExpressionCodegen.java:311)
        ... 42 more

Thanks for the full exception stack. It looks very similar to KT-39880.