KMP-NativeCoroutines icon indicating copy to clipboard operation
KMP-NativeCoroutines copied to clipboard

Inferred Types Cause Crash

Open litclimbing opened this issue 2 years ago • 6 comments

I just started using the library and ran into a weird issue. If I have a property in my class that has an inferred type, the build crashes. Explicitly adding the type makes it work.

I'm using version 0.11.3-new-mm with Kotlin 1.6.10

For example I have a property in a User class of fullName that I defined like this.

    val fullName = when {
        firstName == null && lastName == null -> null
        firstName == null -> lastName
        lastName == null -> firstName
        else -> "$firstName $lastName"
    }

If I try to build I get the following error.

> Task :shared:compileKotlinIosX64 FAILED
ERROR: Exception while analyzing expression at (41,9) in /Users/justin/AndroidStudioProjects/LitHangboard/shared/src/commonMain/kotlin/com/litclimbing/hangboard/model/User.kt

Attachments:
expression.kt
firstName
org.jetbrains.kotlin.utils.KotlinExceptionWithAttachments: Exception while analyzing expression at (41,9) in /Users/justin/AndroidStudioProjects/LitHangboard/shared/src/commonMain/kotlin/com/litclimbing/hangboard/model/User.kt
	at org.jetbrains.kotlin.types.expressions.ExpressionTypingVisitorDispatcher.logOrThrowException(ExpressionTypingVisitorDispatcher.java:246)
	at org.jetbrains.kotlin.types.expressions.ExpressionTypingVisitorDispatcher.lambda$getTypeInfo$0(ExpressionTypingVisitorDispatcher.java:224)
	at org.jetbrains.kotlin.util.PerformanceCounter.time(PerformanceCounter.kt:101)
	at org.jetbrains.kotlin.types.expressions.ExpressionTypingVisitorDispatcher.getTypeInfo(ExpressionTypingVisitorDispatcher.java:164)
	at org.jetbrains.kotlin.types.expressions.ExpressionTypingVisitorDispatcher.getTypeInfo(ExpressionTypingVisitorDispatcher.java:134)
	at org.jetbrains.kotlin.types.expressions.ExpressionTypingUtils.getTypeInfoOrNullType(ExpressionTypingUtils.java:166)
	at org.jetbrains.kotlin.types.expressions.BasicExpressionTypingVisitor.visitEquality(BasicExpressionTypingVisitor.java:1148)
	at org.jetbrains.kotlin.types.expressions.BasicExpressionTypingVisitor.visitBinaryExpression(BasicExpressionTypingVisitor.java:1078)
	at org.jetbrains.kotlin.types.expressions.ExpressionTypingVisitorDispatcher.visitBinaryExpression(ExpressionTypingVisitorDispatcher.java:403)
	at org.jetbrains.kotlin.types.expressions.ExpressionTypingVisitorDispatcher$ForDeclarations.visitBinaryExpression(ExpressionTypingVisitorDispatcher.java:46)
	at org.jetbrains.kotlin.psi.KtBinaryExpression.accept(KtBinaryExpression.java:35)
	at org.jetbrains.kotlin.types.expressions.ExpressionTypingVisitorDispatcher.lambda$getTypeInfo$0(ExpressionTypingVisitorDispatcher.java:175)
	at org.jetbrains.kotlin.util.PerformanceCounter.time(PerformanceCounter.kt:101)
	at org.jetbrains.kotlin.types.expressions.ExpressionTypingVisitorDispatcher.getTypeInfo(ExpressionTypingVisitorDispatcher.java:164)
	at org.jetbrains.kotlin.types.expressions.ExpressionTypingVisitorDispatcher.getTypeInfo(ExpressionTypingVisitorDispatcher.java:134)
	at org.jetbrains.kotlin.types.expressions.ExpressionTypingUtils.getTypeInfoOrNullType(ExpressionTypingUtils.java:166)
	at org.jetbrains.kotlin.types.expressions.BasicExpressionTypingVisitor.visitBooleanOperationExpression(BasicExpressionTypingVisitor.java:1244)
	at org.jetbrains.kotlin.types.expressions.BasicExpressionTypingVisitor.visitBinaryExpression(BasicExpressionTypingVisitor.java:1091)
	at org.jetbrains.kotlin.types.expressions.ExpressionTypingVisitorDispatcher.visitBinaryExpression(ExpressionTypingVisitorDispatcher.java:403)
	at org.jetbrains.kotlin.types.expressions.ExpressionTypingVisitorDispatcher$ForDeclarations.visitBinaryExpression(ExpressionTypingVisitorDispatcher.java:46)
	at org.jetbrains.kotlin.psi.KtBinaryExpression.accept(KtBinaryExpression.java:35)
	at org.jetbrains.kotlin.types.expressions.ExpressionTypingVisitorDispatcher.lambda$getTypeInfo$0(ExpressionTypingVisitorDispatcher.java:175)
	at org.jetbrains.kotlin.util.PerformanceCounter.time(PerformanceCounter.kt:101)
	at org.jetbrains.kotlin.types.expressions.ExpressionTypingVisitorDispatcher.getTypeInfo(ExpressionTypingVisitorDispatcher.java:164)
	at org.jetbrains.kotlin.types.expressions.ExpressionTypingVisitorDispatcher.getTypeInfo(ExpressionTypingVisitorDispatcher.java:134)
	at org.jetbrains.kotlin.types.expressions.PatternMatchingTypingVisitor.checkTypeForExpressionCondition(PatternMatchingTypingVisitor.kt:566)
	at org.jetbrains.kotlin.types.expressions.PatternMatchingTypingVisitor.access$checkTypeForExpressionCondition(PatternMatchingTypingVisitor.kt:48)
	at org.jetbrains.kotlin.types.expressions.PatternMatchingTypingVisitor$checkWhenCondition$1.visitWhenConditionWithExpression(PatternMatchingTypingVisitor.kt:546)
	at org.jetbrains.kotlin.psi.KtVisitorVoid.visitWhenConditionWithExpression(KtVisitorVoid.java:1017)
	at org.jetbrains.kotlin.psi.KtVisitorVoid.visitWhenConditionWithExpression(KtVisitorVoid.java:21)
	at org.jetbrains.kotlin.psi.KtWhenConditionWithExpression.accept(KtWhenConditionWithExpression.java:36)
	at org.jetbrains.kotlin.psi.KtElementImpl.accept(KtElementImpl.java:51)
	at org.jetbrains.kotlin.types.expressions.PatternMatchingTypingVisitor.checkWhenCondition(PatternMatchingTypingVisitor.kt:493)
	at org.jetbrains.kotlin.types.expressions.PatternMatchingTypingVisitor.analyzeWhenEntryConditions(PatternMatchingTypingVisitor.kt:475)
	at org.jetbrains.kotlin.types.expressions.PatternMatchingTypingVisitor.analyzeConditionsInWhenEntries(PatternMatchingTypingVisitor.kt:346)
	at org.jetbrains.kotlin.types.expressions.PatternMatchingTypingVisitor.visitWhenExpression(PatternMatchingTypingVisitor.kt:220)
	at org.jetbrains.kotlin.types.expressions.PatternMatchingTypingVisitor.visitWhenExpression(PatternMatchingTypingVisitor.kt:88)
	at org.jetbrains.kotlin.types.expressions.ExpressionTypingVisitorDispatcher.visitWhenExpression(ExpressionTypingVisitorDispatcher.java:326)
	at org.jetbrains.kotlin.types.expressions.ExpressionTypingVisitorDispatcher$ForDeclarations.visitWhenExpression(ExpressionTypingVisitorDispatcher.java:46)
	at org.jetbrains.kotlin.psi.KtWhenExpression.accept(KtWhenExpression.java:50)
	at org.jetbrains.kotlin.types.expressions.ExpressionTypingVisitorDispatcher.lambda$getTypeInfo$0(ExpressionTypingVisitorDispatcher.java:175)
	at org.jetbrains.kotlin.util.PerformanceCounter.time(PerformanceCounter.kt:101)
	at org.jetbrains.kotlin.types.expressions.ExpressionTypingVisitorDispatcher.getTypeInfo(ExpressionTypingVisitorDispatcher.java:164)
	at org.jetbrains.kotlin.types.expressions.ExpressionTypingVisitorDispatcher.getTypeInfo(ExpressionTypingVisitorDispatcher.java:134)
	at org.jetbrains.kotlin.types.expressions.ExpressionTypingVisitorDispatcher.getTypeInfo(ExpressionTypingVisitorDispatcher.java:146)
	at org.jetbrains.kotlin.types.expressions.ExpressionTypingServices.getTypeInfo(ExpressionTypingServices.java:120)
	at org.jetbrains.kotlin.types.expressions.ExpressionTypingServices.getTypeInfo(ExpressionTypingServices.java:95)
	at org.jetbrains.kotlin.types.expressions.ExpressionTypingServices.getType(ExpressionTypingServices.java:137)
	at org.jetbrains.kotlin.types.expressions.ExpressionTypingServices.safeGetType(ExpressionTypingServices.java:80)
	at org.jetbrains.kotlin.resolve.VariableTypeAndInitializerResolver.resolveInitializerType(VariableTypeAndInitializerResolver.kt:184)
	at org.jetbrains.kotlin.resolve.VariableTypeAndInitializerResolver.access$resolveInitializerType(VariableTypeAndInitializerResolver.kt:27)
	at org.jetbrains.kotlin.resolve.VariableTypeAndInitializerResolver$resolveTypeNullable$1.invoke(VariableTypeAndInitializerResolver.kt:92)
	at org.jetbrains.kotlin.resolve.VariableTypeAndInitializerResolver$resolveTypeNullable$1.invoke(VariableTypeAndInitializerResolver.kt:85)
	at org.jetbrains.kotlin.storage.LockBasedStorageManager$LockBasedLazyValue.invoke(LockBasedStorageManager.java:408)
	at org.jetbrains.kotlin.storage.LockBasedStorageManager$LockBasedNotNullLazyValue.invoke(LockBasedStorageManager.java:527)
	at org.jetbrains.kotlin.types.DeferredType.getDelegate(DeferredType.java:106)
	at org.jetbrains.kotlin.types.WrappedType.getConstructor(KotlinType.kt:127)
	at com.rickclephas.kmp.nativecoroutines.compiler.utils.FlowKt.isFlowType(Flow.kt:24)
	at com.rickclephas.kmp.nativecoroutines.compiler.utils.FlowKt.getHasFlowType(Flow.kt:32)
	at com.rickclephas.kmp.nativecoroutines.compiler.utils.CoroutinesPropertyKt.isCoroutinesProperty(CoroutinesProperty.kt:8)
	at com.rickclephas.kmp.nativecoroutines.compiler.utils.CoroutinesPropertyKt.getNeedsNativeProperty(CoroutinesProperty.kt:14)
	at com.rickclephas.kmp.nativecoroutines.compiler.KmpNativeCoroutinesSyntheticResolveExtension.getSyntheticPropertiesNames(KmpNativeCoroutinesSyntheticResolveExtension.kt:65)
=====REMOVED SECTION=====
Caused by: java.lang.AssertionError: Recursion detected in a lazy value under LockBasedStorageManager@5dc0fce (TopDownAnalyzer for Konan)
	at org.jetbrains.kotlin.resolve.lazy.descriptors.LazyClassMemberScope.get_allNames(LazyClassMemberScope.kt:178)
	at org.jetbrains.kotlin.resolve.lazy.descriptors.LazyClassMemberScope.definitelyDoesNotContainName(LazyClassMemberScope.kt:199)
	at org.jetbrains.kotlin.resolve.calls.tower.TowerResolver$Task.mayFitForName(TowerResolver.kt:265)
	at org.jetbrains.kotlin.resolve.calls.tower.TowerResolver$Task.mayFitForName(TowerResolver.kt:258)
	at org.jetbrains.kotlin.resolve.calls.tower.TowerResolver$Task.processImplicitReceiver(TowerResolver.kt:230)
	at org.jetbrains.kotlin.resolve.calls.tower.TowerResolver$Task.run$processScope(TowerResolver.kt:198)
	at org.jetbrains.kotlin.resolve.calls.tower.TowerResolver$Task.run(TowerResolver.kt:209)
	at org.jetbrains.kotlin.resolve.calls.tower.TowerResolver.run(TowerResolver.kt:99)
	at org.jetbrains.kotlin.resolve.calls.tower.TowerResolver.runResolve(TowerResolver.kt:86)
	at org.jetbrains.kotlin.resolve.calls.KotlinCallResolver.resolveCall(KotlinCallResolver.kt:75)
	at org.jetbrains.kotlin.resolve.calls.tower.PSICallResolver.runResolutionAndInference(PSICallResolver.kt:101)
	at org.jetbrains.kotlin.resolve.calls.CallResolver.doResolveCallOrGetCachedResults(CallResolver.java:601)
	at org.jetbrains.kotlin.resolve.calls.CallResolver.lambda$computeTasksAndResolveCall$0(CallResolver.java:213)
	at org.jetbrains.kotlin.util.PerformanceCounter.time(PerformanceCounter.kt:101)
	at org.jetbrains.kotlin.resolve.calls.CallResolver.computeTasksAndResolveCall(CallResolver.java:211)
	at org.jetbrains.kotlin.resolve.calls.CallResolver.computeTasksAndResolveCall(CallResolver.java:199)
	at org.jetbrains.kotlin.resolve.calls.CallResolver.resolveSimpleProperty(CallResolver.java:140)
	at org.jetbrains.kotlin.resolve.calls.CallExpressionResolver.getVariableType(CallExpressionResolver.kt:111)
	at org.jetbrains.kotlin.resolve.calls.CallExpressionResolver.getSimpleNameExpressionTypeInfo(CallExpressionResolver.kt:146)
	at org.jetbrains.kotlin.resolve.calls.CallExpressionResolver.getSimpleNameExpressionTypeInfo(CallExpressionResolver.kt:135)
	at org.jetbrains.kotlin.types.expressions.BasicExpressionTypingVisitor.visitSimpleNameExpression(BasicExpressionTypingVisitor.java:172)
	at org.jetbrains.kotlin.types.expressions.ExpressionTypingVisitorDispatcher.visitSimpleNameExpression(ExpressionTypingVisitorDispatcher.java:333)
	at org.jetbrains.kotlin.types.expressions.ExpressionTypingVisitorDispatcher$ForDeclarations.visitSimpleNameExpression(ExpressionTypingVisitorDispatcher.java:46)
	at org.jetbrains.kotlin.psi.KtNameReferenceExpression.accept(KtNameReferenceExpression.kt:59)
	at org.jetbrains.kotlin.types.expressions.ExpressionTypingVisitorDispatcher.lambda$getTypeInfo$0(ExpressionTypingVisitorDispatcher.java:175)
	... 310 more
e: Compilation failed: Exception while analyzing expression at (41,9) in /Users/justin/AndroidStudioProjects/LitHangboard/shared/src/commonMain/kotlin/com/litclimbing/hangboard/model/User.kt

FAILURE: Build failed with an exception.

If I instead change it to the following (adding the String? type), everything works as expected.

    val fullName: String? = when {
        firstName == null && lastName == null -> null
        firstName == null -> lastName
        lastName == null -> firstName
        else -> "$firstName $lastName"
    }

litclimbing avatar Mar 17 '22 16:03 litclimbing

Looking into it more it seems like this is likely caused by a conflict with another plugin. Here are the plugins I'm using.

plugins {
    kotlin("multiplatform")
    kotlin("native.cocoapods")
    id("com.android.library")
    id("com.rickclephas.kmp.nativecoroutines") version "0.11.3-new-mm"
}

I believe these are the minimum required for a KMM project.

litclimbing avatar Mar 17 '22 16:03 litclimbing

Thanks for the report! I assume fullName is a property of a public class, right? The recursion error you are seeing is actually a known problem (and similar to #4 and #23).

It's caused by the return type check on the public declarations. The plugin needs to check the return type to see if it is some kind of Flow.

Unfortunately there is no good way to solve this expect for explicitly defining the return type of public declarations.

rickclephas avatar Mar 17 '22 19:03 rickclephas

Yeah, it's a property of a public data class. Am I correct in that this only happens when you have 2+ plugins that are checking types? Like Plugin A wants to know the type and since it isn't explicit, the rest of the plugins are run as they could change it. Plugin B is then invoked which wants to know the type so all the plugins are run which invokes plugin A, etc.

litclimbing avatar Mar 24 '22 17:03 litclimbing

Would it be possible to add a build flag to ignore everything by default except for stuff with the annotation of @NativeCoroutine or similar? I'm not sure if this would solve the issue but I think it would at-least isolate it to the annotated values.

litclimbing avatar Mar 24 '22 17:03 litclimbing

You are correct in that 2+ plugin could cause a recursion error, however there is a check that should prevent such recursions.

The actual cause of these recursion errors is a conflict in how the compiler and the plugin work. The plugin checks the return type of public declarations. This will trigger the compiler to resolve the return type if it isn't specified explicitly. Depending on the code that resolution might trigger a recursion error in the compiler internals.

Using an annotation like you mentioned would indeed limit the issue to only those declarations. However I don't really like the idea of having to annotate the declarations to be honest. The whole idea of the plugin is that you don't have to write any extra code 🙂.

Note: with @NativeCoroutinesIgnore you can tell the compiler to ignore a specific declaration.

IMO a better solution would be to just ignore declarations with implicit return types. I am just not sure if there is a reliable way to determine if a return type is implicit or not (will have to do some testing).

rickclephas avatar Mar 24 '22 17:03 rickclephas

I definitely appreciate that the idea is to prevent having to write more code, but with this but it takes me a while to track down each place I forgot to make the type explicit when I move onto the iOS side.

I don't know if ignoring implicit return types is a great idea but maybe the best solution. It would be frustrating to write something like

private val mutableFlow = MutableStateFlow("")
val flow = mutableFlow.asStateFlow()

and have no idea why it doesn't work. I guess it is better than crashing randomly though. It would just have to be made painfully obvious in the docs. I don't know if a plugin could make IDE warnings for this but that would be helpful.

litclimbing avatar Mar 25 '22 16:03 litclimbing