From 28995d38d0ed265474345cb1560ccd112b615067 Mon Sep 17 00:00:00 2001 From: Brian Norman Date: Sat, 8 May 2021 15:28:24 -0500 Subject: [PATCH 1/2] Transform functions with type parameters --- .../react/ReactFunctionCallTransformer.kt | 34 ++++++++----- .../main/kotlin/com/bnorm/react/irUtils.kt | 48 ++++++++++++++++--- .../kotlin/com/bnorm/react/RFunctionTest.kt | 22 +++++++++ 3 files changed, 87 insertions(+), 17 deletions(-) diff --git a/kotlin-react-function-plugin/src/main/kotlin/com/bnorm/react/ReactFunctionCallTransformer.kt b/kotlin-react-function-plugin/src/main/kotlin/com/bnorm/react/ReactFunctionCallTransformer.kt index 8f85840..3c6d644 100644 --- a/kotlin-react-function-plugin/src/main/kotlin/com/bnorm/react/ReactFunctionCallTransformer.kt +++ b/kotlin-react-function-plugin/src/main/kotlin/com/bnorm/react/ReactFunctionCallTransformer.kt @@ -20,6 +20,7 @@ import java.io.File import org.jetbrains.kotlin.backend.common.FileLoweringPass import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext +import org.jetbrains.kotlin.backend.common.ir.remapTypeParameters import org.jetbrains.kotlin.backend.common.ir.setDeclarationsParent import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity @@ -56,6 +57,7 @@ import org.jetbrains.kotlin.ir.types.IrType import org.jetbrains.kotlin.ir.types.IrTypeArgument import org.jetbrains.kotlin.ir.types.createType import org.jetbrains.kotlin.ir.util.defaultType +import org.jetbrains.kotlin.ir.util.remapTypes import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid @@ -125,6 +127,11 @@ class ReactFunctionCallTransformer( result = false } + if (declaration.isInline) { + messageCollector.report(CompilerMessageSeverity.ERROR, "RFunction annotated function cannot be inline", location) + result = false + } + if (declaration.extensionReceiverParameter?.type != classes.react.RBuilder) { messageCollector.report(CompilerMessageSeverity.ERROR, "RFunction annotated function must be an extension function of react.RBuilder", location) result = false @@ -148,27 +155,32 @@ class ReactFunctionCallTransformer( val body = (declaration.body as? IrBlockBody) ?: return val parent = declaration.parent as IrDeclarationContainer - val props = context.buildPropsInterface(declaration) - props.parent = parent - newDeclarations.add(props) + val propsClass = context.buildPropsInterface(declaration) + propsClass.parent = parent + newDeclarations.add(propsClass) - val component = buildRFunctionProperty(parent, declaration, props, body) + val component = buildRFunctionProperty(parent, declaration, propsClass, body).apply { + val substitutionMap = (propsClass.typeParameters + declaration.typeParameters).associate { it.symbol to context.irBuiltIns.anyNType } + if (substitutionMap.isNotEmpty()) remapTypes(TypeSubstituteRemapper(substitutionMap)) + } newDeclarations.add(component) - declaration.body = buildNewBody(props, component, declaration) + val propsType = propsClass.symbol.createType(false, propsClass.typeParameters.map { context.irBuiltIns.anyNType as IrTypeArgument }) + declaration.body = buildNewBody(propsClass, propsType, component, declaration) } private fun IrGeneratorContext.buildPropsInterface(declaration: IrSimpleFunction): IrClass { val irClass = buildExternalInterface( name = "${declaration.name}FuncProps", visibility = DescriptorVisibilities.PRIVATE, - superTypes = listOf(classes.react.RProps) + superTypes = listOf(classes.react.RProps), + typeParameters = declaration.typeParameters ) for (valueParameter in declaration.valueParameters) { addExternalVarProperty( container = irClass, name = valueParameter.name, - type = valueParameter.type + type = valueParameter.type.remapTypeParameters(declaration, irClass) ) } return irClass @@ -176,7 +188,7 @@ class ReactFunctionCallTransformer( private fun buildRFunctionProperty(parent: IrDeclarationParent, declaration: IrSimpleFunction, propsClass: IrClass, body: IrBlockBody): IrProperty { val fieldType = classes.react.RClass(propsClass.defaultType) - val name = "${declaration.name}_RFUNC".toUpperCase() + val name = "${declaration.name}_RFUNC".uppercase() return context.buildStaticProperty(parent, fieldType, name) { irExprBody(irCall_rFunction(propsClass.defaultType, "${declaration.name}") { function -> @@ -247,11 +259,11 @@ class ReactFunctionCallTransformer( } } - private fun buildNewBody(propsClass: IrClass, componentProperty: IrProperty, declaration: IrSimpleFunction): IrBody { + private fun buildNewBody(propsClass: IrClass, propsType: IrType, componentProperty: IrProperty, declaration: IrSimpleFunction): IrBody { return context.irBuilder(declaration.symbol).run { irBlockBody { +irCall_invoke( - propsClass.defaultType, + propsType, irGet(declaration.extensionReceiverParameter!!), irCall(componentProperty.getter!!, origin = IrStatementOrigin.GET_PROPERTY) ) { function -> @@ -263,7 +275,7 @@ class ReactFunctionCallTransformer( +irCall(property.setter!!, origin = IrStatementOrigin.EQ).apply { val callee = functions.react.RElementBuilder.attrs.owner.getter!! - this.dispatchReceiver = IrCallImpl(startOffset, endOffset, propsClass.defaultType, callee.symbol, callee.typeParameters.size, callee.valueParameters.size, IrStatementOrigin.GET_PROPERTY).apply { + this.dispatchReceiver = IrCallImpl(startOffset, endOffset, propsType, callee.symbol, callee.typeParameters.size, callee.valueParameters.size, IrStatementOrigin.GET_PROPERTY).apply { this.dispatchReceiver = irGet(rElementBuilder) } this.putValueArgument(0, irGet(valueParameter)) diff --git a/kotlin-react-function-plugin/src/main/kotlin/com/bnorm/react/irUtils.kt b/kotlin-react-function-plugin/src/main/kotlin/com/bnorm/react/irUtils.kt index 657226d..f306f42 100644 --- a/kotlin-react-function-plugin/src/main/kotlin/com/bnorm/react/irUtils.kt +++ b/kotlin-react-function-plugin/src/main/kotlin/com/bnorm/react/irUtils.kt @@ -13,6 +13,7 @@ import org.jetbrains.kotlin.ir.builders.IrGeneratorContext import org.jetbrains.kotlin.ir.builders.declarations.IrFunctionBuilder import org.jetbrains.kotlin.ir.builders.declarations.addGetter import org.jetbrains.kotlin.ir.builders.declarations.addProperty +import org.jetbrains.kotlin.ir.builders.declarations.addTypeParameter import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter import org.jetbrains.kotlin.ir.builders.declarations.buildClass import org.jetbrains.kotlin.ir.builders.declarations.buildField @@ -28,13 +29,19 @@ import org.jetbrains.kotlin.ir.declarations.IrDeclarationParent import org.jetbrains.kotlin.ir.declarations.IrFactory import org.jetbrains.kotlin.ir.declarations.IrProperty import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction +import org.jetbrains.kotlin.ir.declarations.IrTypeParameter +import org.jetbrains.kotlin.ir.declarations.IrTypeParametersContainer import org.jetbrains.kotlin.ir.expressions.IrExpressionBody import org.jetbrains.kotlin.ir.expressions.IrFunctionExpression import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin import org.jetbrains.kotlin.ir.expressions.impl.IrFunctionExpressionImpl import org.jetbrains.kotlin.ir.symbols.IrSymbol +import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol import org.jetbrains.kotlin.ir.symbols.impl.IrSimpleFunctionSymbolImpl import org.jetbrains.kotlin.ir.types.IrType +import org.jetbrains.kotlin.ir.util.TypeRemapper +import org.jetbrains.kotlin.ir.util.defaultType +import org.jetbrains.kotlin.ir.util.substitute import org.jetbrains.kotlin.name.Name fun IrGeneratorContext.irBuilder( @@ -46,16 +53,26 @@ fun IrGeneratorContext.irBuilder( fun IrGeneratorContext.buildExternalInterface( name: String, visibility: DescriptorVisibility = DescriptorVisibilities.PUBLIC, - superTypes: List? = listOf(irBuiltIns.anyType) + superTypes: List? = listOf(irBuiltIns.anyType), + typeParameters: List ): IrClass { val irClass = irFactory.buildClass { this.visibility = visibility - kind = ClassKind.INTERFACE - modality = Modality.ABSTRACT - isExternal = true + this.kind = ClassKind.INTERFACE + this.modality = Modality.ABSTRACT + this.isExternal = true this.name = Name.identifier(name) } superTypes?.let { irClass.superTypes = it } + for (typeParameter in typeParameters) { + irClass.addTypeParameter { + this.name = typeParameter.name + this.origin = typeParameter.origin + this.index = typeParameter.index + this.superTypes.addAll(typeParameter.superTypes) + this.variance = typeParameter.variance + } + } irClass.createImplicitParameterDeclarationWithWrappedDescriptor() return irClass } @@ -74,6 +91,16 @@ PROPERTY name:name visibility:public modality:ABSTRACT [var] correspondingProperty: PROPERTY name:name visibility:public modality:ABSTRACT [var] $this: VALUE_PARAMETER name: type:test.HomeProps VALUE_PARAMETER name: index:0 type:kotlin.String + + +PROPERTY name:items visibility:public modality:ABSTRACT [var] + FUN DEFAULT_PROPERTY_ACCESSOR name: visibility:public modality:ABSTRACT <> ($this:.GenericListProps.GenericListProps>) returnType:kotlin.collections.List.GenericListProps> + correspondingProperty: PROPERTY name:items visibility:public modality:ABSTRACT [var] + $this: VALUE_PARAMETER name: type:.GenericListProps.GenericListProps> + FUN DEFAULT_PROPERTY_ACCESSOR name: visibility:public modality:ABSTRACT <> ($this:.GenericListProps.GenericListProps>, :kotlin.collections.List.GenericListProps>) returnType:kotlin.Unit + correspondingProperty: PROPERTY name:items visibility:public modality:ABSTRACT [var] + $this: VALUE_PARAMETER name: type:.GenericListProps.GenericListProps> + VALUE_PARAMETER name: index:0 type:kotlin.collections.List.GenericListProps> */ val irProperty = container.addProperty { @@ -89,7 +116,7 @@ PROPERTY name:name visibility:public modality:ABSTRACT [var] returnType = type origin = IrDeclarationOrigin.DEFAULT_PROPERTY_ACCESSOR } - irGetter.dispatchReceiverParameter = container.thisReceiver!!.copyTo(irGetter, IrDeclarationOrigin.DEFINED) + irGetter.dispatchReceiverParameter = container.thisReceiver!!.copyTo(irGetter, IrDeclarationOrigin.DEFINED, type = container.defaultType) irGetter.correspondingPropertySymbol = irProperty.symbol val irSetter = irProperty.addSetter { @@ -98,7 +125,7 @@ PROPERTY name:name visibility:public modality:ABSTRACT [var] returnType = irBuiltIns.unitType origin = IrDeclarationOrigin.DEFAULT_PROPERTY_ACCESSOR } - irSetter.dispatchReceiverParameter = container.thisReceiver!!.copyTo(irSetter, IrDeclarationOrigin.DEFINED) + irSetter.dispatchReceiverParameter = container.thisReceiver!!.copyTo(irSetter, IrDeclarationOrigin.DEFINED, type = container.defaultType) irSetter.correspondingPropertySymbol = irProperty.symbol irSetter.addValueParameter { this.name = Name.special("") @@ -199,3 +226,12 @@ internal fun IrFactory.buildFunction(builder: IrFunctionBuilder): IrSimpleFuncti containerSource ) } + +class TypeSubstituteRemapper( + private val substitutionMap: Map + ) : TypeRemapper { + override fun remapType(type: IrType): IrType = type.substitute(substitutionMap) + + override fun enterScope(irTypeParametersContainer: IrTypeParametersContainer) = Unit + override fun leaveScope() = Unit +} diff --git a/kotlin-react-function-plugin/src/test/kotlin/com/bnorm/react/RFunctionTest.kt b/kotlin-react-function-plugin/src/test/kotlin/com/bnorm/react/RFunctionTest.kt index bde419f..1245032 100644 --- a/kotlin-react-function-plugin/src/test/kotlin/com/bnorm/react/RFunctionTest.kt +++ b/kotlin-react-function-plugin/src/test/kotlin/com/bnorm/react/RFunctionTest.kt @@ -23,4 +23,26 @@ fun RBuilder.Welcome(name: String) { assertTrue("var WELCOME_RFUNC" in javascript) assertTrue("WELCOME_RFUNC = rFunction('Welcome'," in javascript) } + + @Test + fun genericComponent() { + val output = compile(""" +import com.bnorm.react.* +import react.* +import react.dom.div + +@RFunction +fun RBuilder.GenericList(items: List, onItem: RBuilder.(T) -> Unit) { + div { + for (item in items) { + onItem(item) + } + } +} +""") + val javascript = output.readText() + assertTrue("function GenericList(_this_, items, onItem)" in javascript) + assertTrue("var GENERICLIST_RFUNC" in javascript) + assertTrue("GENERICLIST_RFUNC = rFunction('GenericList'," in javascript) + } } From ee2127b4933a53dc08afe338b06ac4e378bc403a Mon Sep 17 00:00:00 2001 From: Brian Norman Date: Sat, 8 May 2021 15:32:41 -0500 Subject: [PATCH 2/2] Fix linting error --- .../src/main/kotlin/com/bnorm/react/irUtils.kt | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/kotlin-react-function-plugin/src/main/kotlin/com/bnorm/react/irUtils.kt b/kotlin-react-function-plugin/src/main/kotlin/com/bnorm/react/irUtils.kt index f306f42..285fc52 100644 --- a/kotlin-react-function-plugin/src/main/kotlin/com/bnorm/react/irUtils.kt +++ b/kotlin-react-function-plugin/src/main/kotlin/com/bnorm/react/irUtils.kt @@ -91,16 +91,6 @@ PROPERTY name:name visibility:public modality:ABSTRACT [var] correspondingProperty: PROPERTY name:name visibility:public modality:ABSTRACT [var] $this: VALUE_PARAMETER name: type:test.HomeProps VALUE_PARAMETER name: index:0 type:kotlin.String - - -PROPERTY name:items visibility:public modality:ABSTRACT [var] - FUN DEFAULT_PROPERTY_ACCESSOR name: visibility:public modality:ABSTRACT <> ($this:.GenericListProps.GenericListProps>) returnType:kotlin.collections.List.GenericListProps> - correspondingProperty: PROPERTY name:items visibility:public modality:ABSTRACT [var] - $this: VALUE_PARAMETER name: type:.GenericListProps.GenericListProps> - FUN DEFAULT_PROPERTY_ACCESSOR name: visibility:public modality:ABSTRACT <> ($this:.GenericListProps.GenericListProps>, :kotlin.collections.List.GenericListProps>) returnType:kotlin.Unit - correspondingProperty: PROPERTY name:items visibility:public modality:ABSTRACT [var] - $this: VALUE_PARAMETER name: type:.GenericListProps.GenericListProps> - VALUE_PARAMETER name: index:0 type:kotlin.collections.List.GenericListProps> */ val irProperty = container.addProperty { @@ -229,7 +219,7 @@ internal fun IrFactory.buildFunction(builder: IrFunctionBuilder): IrSimpleFuncti class TypeSubstituteRemapper( private val substitutionMap: Map - ) : TypeRemapper { +) : TypeRemapper { override fun remapType(type: IrType): IrType = type.substitute(substitutionMap) override fun enterScope(irTypeParametersContainer: IrTypeParametersContainer) = Unit