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:
Anton Bannykh
2020-02-14 16:12:50 +03:00
parent 2742e643c1
commit 23e218396e
18 changed files with 249 additions and 7 deletions
@@ -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");
@@ -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 {
@@ -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;
}