Skip to content
This repository was archived by the owner on Apr 20, 2023. It is now read-only.

Commit c91c9be

Browse files
authored
Merge pull request #20 from bnorm/generic-component
Transform functions with type parameters
2 parents b75bdb6 + ee2127b commit c91c9be

File tree

3 files changed

+77
-17
lines changed

3 files changed

+77
-17
lines changed

kotlin-react-function-plugin/src/main/kotlin/com/bnorm/react/ReactFunctionCallTransformer.kt

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import java.io.File
2020
import org.jetbrains.kotlin.backend.common.FileLoweringPass
2121
import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext
2222
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
23+
import org.jetbrains.kotlin.backend.common.ir.remapTypeParameters
2324
import org.jetbrains.kotlin.backend.common.ir.setDeclarationsParent
2425
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
2526
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
@@ -56,6 +57,7 @@ import org.jetbrains.kotlin.ir.types.IrType
5657
import org.jetbrains.kotlin.ir.types.IrTypeArgument
5758
import org.jetbrains.kotlin.ir.types.createType
5859
import org.jetbrains.kotlin.ir.util.defaultType
60+
import org.jetbrains.kotlin.ir.util.remapTypes
5961
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
6062
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
6163
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
@@ -125,6 +127,11 @@ class ReactFunctionCallTransformer(
125127
result = false
126128
}
127129

130+
if (declaration.isInline) {
131+
messageCollector.report(CompilerMessageSeverity.ERROR, "RFunction annotated function cannot be inline", location)
132+
result = false
133+
}
134+
128135
if (declaration.extensionReceiverParameter?.type != classes.react.RBuilder) {
129136
messageCollector.report(CompilerMessageSeverity.ERROR, "RFunction annotated function must be an extension function of react.RBuilder", location)
130137
result = false
@@ -148,35 +155,40 @@ class ReactFunctionCallTransformer(
148155
val body = (declaration.body as? IrBlockBody) ?: return
149156
val parent = declaration.parent as IrDeclarationContainer
150157

151-
val props = context.buildPropsInterface(declaration)
152-
props.parent = parent
153-
newDeclarations.add(props)
158+
val propsClass = context.buildPropsInterface(declaration)
159+
propsClass.parent = parent
160+
newDeclarations.add(propsClass)
154161

155-
val component = buildRFunctionProperty(parent, declaration, props, body)
162+
val component = buildRFunctionProperty(parent, declaration, propsClass, body).apply {
163+
val substitutionMap = (propsClass.typeParameters + declaration.typeParameters).associate { it.symbol to context.irBuiltIns.anyNType }
164+
if (substitutionMap.isNotEmpty()) remapTypes(TypeSubstituteRemapper(substitutionMap))
165+
}
156166
newDeclarations.add(component)
157167

158-
declaration.body = buildNewBody(props, component, declaration)
168+
val propsType = propsClass.symbol.createType(false, propsClass.typeParameters.map { context.irBuiltIns.anyNType as IrTypeArgument })
169+
declaration.body = buildNewBody(propsClass, propsType, component, declaration)
159170
}
160171

161172
private fun IrGeneratorContext.buildPropsInterface(declaration: IrSimpleFunction): IrClass {
162173
val irClass = buildExternalInterface(
163174
name = "${declaration.name}FuncProps",
164175
visibility = DescriptorVisibilities.PRIVATE,
165-
superTypes = listOf(classes.react.RProps)
176+
superTypes = listOf(classes.react.RProps),
177+
typeParameters = declaration.typeParameters
166178
)
167179
for (valueParameter in declaration.valueParameters) {
168180
addExternalVarProperty(
169181
container = irClass,
170182
name = valueParameter.name,
171-
type = valueParameter.type
183+
type = valueParameter.type.remapTypeParameters(declaration, irClass)
172184
)
173185
}
174186
return irClass
175187
}
176188

177189
private fun buildRFunctionProperty(parent: IrDeclarationParent, declaration: IrSimpleFunction, propsClass: IrClass, body: IrBlockBody): IrProperty {
178190
val fieldType = classes.react.RClass(propsClass.defaultType)
179-
val name = "${declaration.name}_RFUNC".toUpperCase()
191+
val name = "${declaration.name}_RFUNC".uppercase()
180192

181193
return context.buildStaticProperty(parent, fieldType, name) {
182194
irExprBody(irCall_rFunction(propsClass.defaultType, "${declaration.name}") { function ->
@@ -247,11 +259,11 @@ class ReactFunctionCallTransformer(
247259
}
248260
}
249261

250-
private fun buildNewBody(propsClass: IrClass, componentProperty: IrProperty, declaration: IrSimpleFunction): IrBody {
262+
private fun buildNewBody(propsClass: IrClass, propsType: IrType, componentProperty: IrProperty, declaration: IrSimpleFunction): IrBody {
251263
return context.irBuilder(declaration.symbol).run {
252264
irBlockBody {
253265
+irCall_invoke(
254-
propsClass.defaultType,
266+
propsType,
255267
irGet(declaration.extensionReceiverParameter!!),
256268
irCall(componentProperty.getter!!, origin = IrStatementOrigin.GET_PROPERTY)
257269
) { function ->
@@ -263,7 +275,7 @@ class ReactFunctionCallTransformer(
263275

264276
+irCall(property.setter!!, origin = IrStatementOrigin.EQ).apply {
265277
val callee = functions.react.RElementBuilder.attrs.owner.getter!!
266-
this.dispatchReceiver = IrCallImpl(startOffset, endOffset, propsClass.defaultType, callee.symbol, callee.typeParameters.size, callee.valueParameters.size, IrStatementOrigin.GET_PROPERTY).apply {
278+
this.dispatchReceiver = IrCallImpl(startOffset, endOffset, propsType, callee.symbol, callee.typeParameters.size, callee.valueParameters.size, IrStatementOrigin.GET_PROPERTY).apply {
267279
this.dispatchReceiver = irGet(rElementBuilder)
268280
}
269281
this.putValueArgument(0, irGet(valueParameter))

kotlin-react-function-plugin/src/main/kotlin/com/bnorm/react/irUtils.kt

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import org.jetbrains.kotlin.ir.builders.IrGeneratorContext
1313
import org.jetbrains.kotlin.ir.builders.declarations.IrFunctionBuilder
1414
import org.jetbrains.kotlin.ir.builders.declarations.addGetter
1515
import org.jetbrains.kotlin.ir.builders.declarations.addProperty
16+
import org.jetbrains.kotlin.ir.builders.declarations.addTypeParameter
1617
import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter
1718
import org.jetbrains.kotlin.ir.builders.declarations.buildClass
1819
import org.jetbrains.kotlin.ir.builders.declarations.buildField
@@ -28,13 +29,19 @@ import org.jetbrains.kotlin.ir.declarations.IrDeclarationParent
2829
import org.jetbrains.kotlin.ir.declarations.IrFactory
2930
import org.jetbrains.kotlin.ir.declarations.IrProperty
3031
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
32+
import org.jetbrains.kotlin.ir.declarations.IrTypeParameter
33+
import org.jetbrains.kotlin.ir.declarations.IrTypeParametersContainer
3134
import org.jetbrains.kotlin.ir.expressions.IrExpressionBody
3235
import org.jetbrains.kotlin.ir.expressions.IrFunctionExpression
3336
import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin
3437
import org.jetbrains.kotlin.ir.expressions.impl.IrFunctionExpressionImpl
3538
import org.jetbrains.kotlin.ir.symbols.IrSymbol
39+
import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol
3640
import org.jetbrains.kotlin.ir.symbols.impl.IrSimpleFunctionSymbolImpl
3741
import org.jetbrains.kotlin.ir.types.IrType
42+
import org.jetbrains.kotlin.ir.util.TypeRemapper
43+
import org.jetbrains.kotlin.ir.util.defaultType
44+
import org.jetbrains.kotlin.ir.util.substitute
3845
import org.jetbrains.kotlin.name.Name
3946

4047
fun IrGeneratorContext.irBuilder(
@@ -46,16 +53,26 @@ fun IrGeneratorContext.irBuilder(
4653
fun IrGeneratorContext.buildExternalInterface(
4754
name: String,
4855
visibility: DescriptorVisibility = DescriptorVisibilities.PUBLIC,
49-
superTypes: List<IrType>? = listOf(irBuiltIns.anyType)
56+
superTypes: List<IrType>? = listOf(irBuiltIns.anyType),
57+
typeParameters: List<IrTypeParameter>
5058
): IrClass {
5159
val irClass = irFactory.buildClass {
5260
this.visibility = visibility
53-
kind = ClassKind.INTERFACE
54-
modality = Modality.ABSTRACT
55-
isExternal = true
61+
this.kind = ClassKind.INTERFACE
62+
this.modality = Modality.ABSTRACT
63+
this.isExternal = true
5664
this.name = Name.identifier(name)
5765
}
5866
superTypes?.let { irClass.superTypes = it }
67+
for (typeParameter in typeParameters) {
68+
irClass.addTypeParameter {
69+
this.name = typeParameter.name
70+
this.origin = typeParameter.origin
71+
this.index = typeParameter.index
72+
this.superTypes.addAll(typeParameter.superTypes)
73+
this.variance = typeParameter.variance
74+
}
75+
}
5976
irClass.createImplicitParameterDeclarationWithWrappedDescriptor()
6077
return irClass
6178
}
@@ -89,7 +106,7 @@ PROPERTY name:name visibility:public modality:ABSTRACT [var]
89106
returnType = type
90107
origin = IrDeclarationOrigin.DEFAULT_PROPERTY_ACCESSOR
91108
}
92-
irGetter.dispatchReceiverParameter = container.thisReceiver!!.copyTo(irGetter, IrDeclarationOrigin.DEFINED)
109+
irGetter.dispatchReceiverParameter = container.thisReceiver!!.copyTo(irGetter, IrDeclarationOrigin.DEFINED, type = container.defaultType)
93110
irGetter.correspondingPropertySymbol = irProperty.symbol
94111

95112
val irSetter = irProperty.addSetter {
@@ -98,7 +115,7 @@ PROPERTY name:name visibility:public modality:ABSTRACT [var]
98115
returnType = irBuiltIns.unitType
99116
origin = IrDeclarationOrigin.DEFAULT_PROPERTY_ACCESSOR
100117
}
101-
irSetter.dispatchReceiverParameter = container.thisReceiver!!.copyTo(irSetter, IrDeclarationOrigin.DEFINED)
118+
irSetter.dispatchReceiverParameter = container.thisReceiver!!.copyTo(irSetter, IrDeclarationOrigin.DEFINED, type = container.defaultType)
102119
irSetter.correspondingPropertySymbol = irProperty.symbol
103120
irSetter.addValueParameter {
104121
this.name = Name.special("<set-?>")
@@ -199,3 +216,12 @@ internal fun IrFactory.buildFunction(builder: IrFunctionBuilder): IrSimpleFuncti
199216
containerSource
200217
)
201218
}
219+
220+
class TypeSubstituteRemapper(
221+
private val substitutionMap: Map<IrTypeParameterSymbol, IrType>
222+
) : TypeRemapper {
223+
override fun remapType(type: IrType): IrType = type.substitute(substitutionMap)
224+
225+
override fun enterScope(irTypeParametersContainer: IrTypeParametersContainer) = Unit
226+
override fun leaveScope() = Unit
227+
}

kotlin-react-function-plugin/src/test/kotlin/com/bnorm/react/RFunctionTest.kt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,26 @@ fun RBuilder.Welcome(name: String) {
2323
assertTrue("var WELCOME_RFUNC" in javascript)
2424
assertTrue("WELCOME_RFUNC = rFunction('Welcome'," in javascript)
2525
}
26+
27+
@Test
28+
fun genericComponent() {
29+
val output = compile("""
30+
import com.bnorm.react.*
31+
import react.*
32+
import react.dom.div
33+
34+
@RFunction
35+
fun <T> RBuilder.GenericList(items: List<T>, onItem: RBuilder.(T) -> Unit) {
36+
div {
37+
for (item in items) {
38+
onItem(item)
39+
}
40+
}
41+
}
42+
""")
43+
val javascript = output.readText()
44+
assertTrue("function GenericList(_this_, items, onItem)" in javascript)
45+
assertTrue("var GENERICLIST_RFUNC" in javascript)
46+
assertTrue("GENERICLIST_RFUNC = rFunction('GenericList'," in javascript)
47+
}
2648
}

0 commit comments

Comments
 (0)