Skip to content

Commit

Permalink
IC & Coroutines: Do not box suspend operator fun invoke receiver
Browse files Browse the repository at this point in the history
if it is called using parens and not by calling 'invoke' method.

Use underlying type when calling continuation constructor if suspend
function is method inside inline class.

 #KT-43505 Fixed
 #KT-39437 Fixed
  • Loading branch information
ilmirus committed Nov 26, 2020
1 parent 4e33421 commit 9ed5b8f
Show file tree
Hide file tree
Showing 12 changed files with 331 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -375,22 +375,17 @@ private StackValue coerceAndBoxInlineClassIfNeeded(KtElement selector, StackValu
FunctionDescriptor functionDescriptor = (FunctionDescriptor) descriptor;
if (!functionDescriptor.isSuspend()) return stackValue;

// When we call suspend operator fun invoke using parens, we cannot box receiver as return type inline class
if (resolvedCall instanceof VariableAsFunctionResolvedCall &&
functionDescriptor.isOperator() && functionDescriptor.getName().getIdentifier().equals("invoke")
) return stackValue;

KotlinType unboxedInlineClass = CoroutineCodegenUtilKt
.originalReturnTypeOfSuspendFunctionReturningUnboxedInlineClass(functionDescriptor, typeMapper);

StackValue stackValueToWrap = stackValue;
KotlinType originalKotlinType;
if (unboxedInlineClass != null) {
originalKotlinType = unboxedInlineClass;
} else {
originalKotlinType = stackValueToWrap.kotlinType;
}
Type originalType;
if (unboxedInlineClass != null) {
originalType = typeMapper.mapType(unboxedInlineClass);
} else {
originalType = stackValueToWrap.type;
}
KotlinType originalKotlinType = unboxedInlineClass != null ? unboxedInlineClass : stackValueToWrap.kotlinType;
Type originalType = unboxedInlineClass != null ? typeMapper.mapType(unboxedInlineClass) : stackValueToWrap.type;

stackValue = new StackValue(originalType, originalKotlinType) {
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext
import org.jetbrains.kotlin.resolve.inline.isEffectivelyInlineOnly
import org.jetbrains.kotlin.resolve.isInlineClass
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
import org.jetbrains.kotlin.resolve.jvm.diagnostics.OtherOrigin
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
Expand Down Expand Up @@ -92,7 +93,9 @@ class SuspendFunctionGenerationStrategy(
sourceFile = declaration.containingKtFile.name,
shouldPreserveClassInitialization = constructorCallNormalizationMode.shouldPreserveClassInitialization,
needDispatchReceiver = originalSuspendDescriptor.dispatchReceiverParameter != null,
internalNameForDispatchReceiver = containingClassInternalNameOrNull(),
internalNameForDispatchReceiver = (originalSuspendDescriptor.containingDeclaration as? ClassDescriptor)?.let {
if (it.isInlineClass()) state.typeMapper.mapType(it).internalName else null
} ?: containingClassInternalNameOrNull(),
languageVersionSettings = languageVersionSettings,
disableTailCallOptimizationForFunctionReturningUnit = originalSuspendDescriptor.returnType?.isUnit() == true &&
originalSuspendDescriptor.overriddenDescriptors.isNotEmpty() &&
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// WITH_RUNTIME

import kotlin.coroutines.*

fun builder(c: suspend () -> Unit) {
c.startCoroutine(Continuation(EmptyCoroutineContext) {
it.getOrThrow()
})
}

inline class IC(val a: Any?)

class GetResult {
suspend operator fun invoke() = IC("OK")
}

inline class IC1(val a: String) {
suspend operator fun invoke() = IC(a)
}

fun box(): String {
var res = "FAIL 1"
builder {
val getResult = GetResult()
res = getResult().a as String
}
if (res != "OK") return "FAIL 1 $res"

res = "FAIL 2"
builder {
val getResult = GetResult()
res = getResult.invoke().a as String
}
if (res != "OK") return "FAIL 2 $res"

res = "FAIL 3"
builder {
res = GetResult()().a as String
}
if (res != "OK") return "FAIL 3 $res"

res = "FAIL 4"
builder {
val getResult = IC1("OK")
res = getResult().a as String
}
if (res != "OK") return "FAIL 4 $res"

res = "FAIL 5"
builder {
val getResult = IC1("OK")
res = getResult.invoke().a as String
}
if (res != "OK") return "FAIL 5 $res"

res = "FAIL 6"
builder {
res = IC1("OK")().a as String
}
if (res != "OK") return "FAIL 6 $res"
return res
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// WITH_RUNTIME

import kotlin.coroutines.*

fun builder(c: suspend () -> Unit) {
c.startCoroutine(Continuation(EmptyCoroutineContext) {
it.getOrThrow()
})
}

inline class IC(val a: Any?)

var c: Continuation<Any>? = null

suspend fun <T> suspendMe(): T = suspendCoroutine {
@Suppress("UNCHECKED_CAST")
c = it as Continuation<Any>
}

class GetResult {
suspend operator fun invoke(): IC = suspendMe()
}

inline class IC1(val a: String) {
suspend operator fun invoke(): IC = suspendMe()
}

fun box(): String {
var res = "FAIL 1"
builder {
val getResult = GetResult()
res = getResult().a as String
}
c?.resume(IC("OK"))
if (res != "OK") return "FAIL 1 $res"

res = "FAIL 2"
builder {
val getResult = GetResult()
res = getResult.invoke().a as String
}
c?.resume(IC("OK"))
if (res != "OK") return "FAIL 2 $res"

res = "FAIL 3"
builder {
res = GetResult()().a as String
}
c?.resume(IC("OK"))
if (res != "OK") return "FAIL 3 $res"

res = "FAIL 4"
builder {
val getResult = IC1("OK")
res = getResult().a as String
}
c?.resume(IC("OK"))
if (res != "OK") return "FAIL 4 $res"

res = "FAIL 5"
builder {
val getResult = IC1("OK")
res = getResult.invoke().a as String
}
c?.resume(IC("OK"))
if (res != "OK") return "FAIL 5 $res"

res = "FAIL 6"
builder {
res = IC1("OK")().a as String
}
c?.resume(IC("OK"))
if (res != "OK") return "FAIL 6 $res"
return res
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// WITH_RUNTIME
// WITH_COROUTINES

import kotlin.coroutines.*
import helpers.*

var result = "FAIL"

fun builder(c: suspend () -> Unit) {
c.startCoroutine(handleExceptionContinuation {
result = it.message!!
})
}

inline class IC(val a: Any?)

var c: Continuation<Any>? = null

suspend fun <T> suspendMe(): T = suspendCoroutine {
@Suppress("UNCHECKED_CAST")
c = it as Continuation<Any>
}

class GetResult {
suspend operator fun invoke(): IC = suspendMe()
}

inline class IC1(val a: String) {
suspend operator fun invoke(): IC = suspendMe()
}

fun box(): String {
builder {
val getResult = GetResult()
getResult()
}
c?.resumeWithException(IllegalStateException("OK"))
if (result != "OK") return "FAIL 1 $result"

result = "FAIL 2"
builder {
val getResult = GetResult()
getResult.invoke()
}
c?.resumeWithException(IllegalStateException("OK"))
if (result != "OK") return "FAIL 2 $result"

result = "FAIL 3"
builder {
GetResult()()
}
c?.resumeWithException(IllegalStateException("OK"))
if (result != "OK") return "FAIL 3 $result"

result = "FAIL 4"
builder {
val getResult = IC1("OK")
getResult()
}
c?.resumeWithException(IllegalStateException("OK"))
if (result != "OK") return "FAIL 4 $result"

result = "FAIL 5"
builder {
val getResult = IC1("OK")
getResult.invoke()
}
c?.resumeWithException(IllegalStateException("OK"))
if (result != "OK") return "FAIL 5 $result"

result = "FAIL 6"
builder {
IC1("OK")()
}
c?.resumeWithException(IllegalStateException("OK"))
if (result != "OK") return "FAIL 6 $result"
return result
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 9ed5b8f

Please sign in to comment.