JS: support SAM conversion
The SAM adapter is generate on declaration site. This is different from the JVM approach. `external fun interface` is banned for now. Reusing interface declaration for the adapter is a hack which reduces code size and makes importing/exporting the adapter effortless.
This commit is contained in:
Generated
+15
@@ -9073,6 +9073,16 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest {
|
||||
runTest("compiler/testData/codegen/box/funInterface/basicFunInterfaceConversion.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("funInterfaceInheritance.kt")
|
||||
public void testFunInterfaceInheritance() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/funInterface/funInterfaceInheritance.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("multimodule.kt")
|
||||
public void testMultimodule() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/funInterface/multimodule.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("nullableSam.kt")
|
||||
public void testNullableSam() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/funInterface/nullableSam.kt");
|
||||
@@ -9083,6 +9093,11 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest {
|
||||
runTest("compiler/testData/codegen/box/funInterface/partialSam.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("primitiveConversions.kt")
|
||||
public void testPrimitiveConversions() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/funInterface/primitiveConversions.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("receiverEvaluatedOnce.kt")
|
||||
public void testReceiverEvaluatedOnce() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/funInterface/receiverEvaluatedOnce.kt");
|
||||
|
||||
+15
@@ -9073,6 +9073,16 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
|
||||
runTest("compiler/testData/codegen/box/funInterface/basicFunInterfaceConversion.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("funInterfaceInheritance.kt")
|
||||
public void testFunInterfaceInheritance() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/funInterface/funInterfaceInheritance.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("multimodule.kt")
|
||||
public void testMultimodule() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/funInterface/multimodule.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("nullableSam.kt")
|
||||
public void testNullableSam() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/funInterface/nullableSam.kt");
|
||||
@@ -9083,6 +9093,11 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
|
||||
runTest("compiler/testData/codegen/box/funInterface/partialSam.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("primitiveConversions.kt")
|
||||
public void testPrimitiveConversions() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/funInterface/primitiveConversions.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("receiverEvaluatedOnce.kt")
|
||||
public void testReceiverEvaluatedOnce() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/funInterface/receiverEvaluatedOnce.kt");
|
||||
|
||||
@@ -131,6 +131,8 @@ public final class Namer {
|
||||
|
||||
public static final String IMPORTS_FOR_INLINE_PROPERTY = "$$importsForInline$$";
|
||||
|
||||
public static final String SAM_FIELD_NAME = "function$";
|
||||
|
||||
@NotNull
|
||||
public static String getFunctionTag(@NotNull CallableDescriptor functionDescriptor, @NotNull JsConfig config) {
|
||||
String intrinsicTag = ArrayFIF.INSTANCE.getTag(functionDescriptor, config);
|
||||
|
||||
@@ -119,6 +119,8 @@ class ClassTranslator private constructor(
|
||||
enumInitFunction.body.statements += JsAstUtils.asSyntheticStatement(initInvocation.source(companionDescriptor.source.getPsi()))
|
||||
}
|
||||
|
||||
maybeConvertInterfaceToSamAdapter(context, constructorFunction)
|
||||
|
||||
translatePrimaryConstructor(constructorFunction, context, delegationTranslator)
|
||||
addMetadataObject()
|
||||
addMetadataType()
|
||||
@@ -562,6 +564,42 @@ class ClassTranslator private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun maybeConvertInterfaceToSamAdapter(context: TranslationContext, constructorFunction: JsFunction) {
|
||||
if (!descriptor.isFun) return
|
||||
|
||||
val paramName = context.scope().declareFreshName("f")
|
||||
constructorFunction.parameters += JsParameter(paramName)
|
||||
constructorFunction.body.statements += JsBinaryOperation(
|
||||
JsBinaryOperator.ASG,
|
||||
pureFqn(Namer.SAM_FIELD_NAME, JsThisRef()),
|
||||
JsNameRef(paramName)
|
||||
).makeStmt()
|
||||
|
||||
val samDescriptor = descriptor.unsubstitutedMemberScope
|
||||
.getContributedDescriptors(DescriptorKindFilter.FUNCTIONS)
|
||||
.filterIsInstance<FunctionDescriptor>()
|
||||
.single { it.modality === Modality.ABSTRACT }
|
||||
|
||||
val function = context.getFunctionObject(samDescriptor)
|
||||
val innerContext = context.newDeclaration(samDescriptor).translateAndAliasParameters(samDescriptor, function.parameters)
|
||||
|
||||
if (samDescriptor.isSuspend) {
|
||||
function.fillCoroutineMetadata(innerContext, samDescriptor, hasController = false)
|
||||
}
|
||||
|
||||
val parameters = listOfNotNull(samDescriptor.extensionReceiverParameter) +
|
||||
samDescriptor.valueParameters +
|
||||
listOfNotNull(innerContext.continuationParameterDescriptor)
|
||||
|
||||
val arguments = parameters.map {
|
||||
TranslationUtils.coerce(innerContext, innerContext.getAliasForDescriptor(it)!!, context.currentModule.builtIns.anyType)
|
||||
}
|
||||
|
||||
function.body = JsBlock(JsReturn(JsInvocation(pureFqn(Namer.SAM_FIELD_NAME, JsThisRef()), arguments)))
|
||||
|
||||
context.addDeclarationStatement(context.addFunctionToPrototype(descriptor, samDescriptor, function))
|
||||
}
|
||||
|
||||
private fun <T : JsNode> T.withDefaultLocation(): T = apply { source = classDeclaration }
|
||||
|
||||
companion object {
|
||||
|
||||
+3
-1
@@ -66,7 +66,9 @@ fun TranslationContext.translateAndAliasParameters(
|
||||
for (valueParameter in descriptor.valueParameters) {
|
||||
val name = getNameForDescriptor(valueParameter)
|
||||
val tmpName = JsScope.declareTemporaryName(name.ident).also { it.descriptor = valueParameter }
|
||||
aliases[valueParameter] = JsAstUtils.pureFqn(tmpName, null)
|
||||
val parameterRef = JsAstUtils.pureFqn(tmpName, null)
|
||||
parameterRef.type = valueParameter.type
|
||||
aliases[valueParameter] = parameterRef
|
||||
targetList += JsParameter(tmpName).apply { hasDefaultValue = valueParameter.hasDefaultValue() }
|
||||
}
|
||||
|
||||
|
||||
@@ -545,6 +545,21 @@ public final class TranslationUtils {
|
||||
}
|
||||
}
|
||||
|
||||
// SAM conversion
|
||||
if ((FunctionTypesKt.isFunctionTypeOrSubtype(from) || FunctionTypesKt.isSuspendFunctionTypeOrSubtype(from))) {
|
||||
ClassifierDescriptor d = to.getConstructor().getDeclarationDescriptor();
|
||||
if (d instanceof ClassDescriptor && ((ClassDescriptor)d).isFun()) {
|
||||
JsName constructorName = context.getInlineableInnerNameForDescriptor(d.getOriginal());
|
||||
if (to.isMarkedNullable()) {
|
||||
JsConditional c = TranslationUtils.notNullConditional(value, new JsNullLiteral(), context);
|
||||
c.setThenExpression(new JsNew(new JsNameRef(constructorName), Collections.singletonList(c.getThenExpression())));
|
||||
value = c;
|
||||
} else {
|
||||
value = new JsNew(new JsNameRef(constructorName), Collections.singletonList(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MetadataProperties.setType(value, to);
|
||||
return value;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user