diff --git a/gradle.properties b/gradle.properties index ed90159..ad99b12 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ #################################################################################################### root.project.name=kopy main.project.name=kopy-compiler -project.group=com.javiersc.kotlin.kopy +project.group=com.javiersc.kotlin #################################################################################################### ### CODE ANALYSIS ### #################################################################################################### @@ -38,3 +38,5 @@ org.gradle.parallel=true #org.gradle.unsafe.configuration-cache-problems=warn #org.gradle.unsafe.configuration-cache.max-problems=100 signing.gnupg.executable=gpg +semver.stage=snapshot +semver.checkClean=false diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cea1752..dbb03d1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] -hubdle = "0.5.0+2.0.0-dev-5387-SNAPSHOT" -hubdleCatalog = "0.1.14+2.0.0-dev-5387-SNAPSHOT" -javiersc-kotlin-kotlinCompilerExtensions = "0.1.1+2.0.0-dev-5387-SNAPSHOT" +hubdle = "0.5.0+2.0.0-dev-10501-SNAPSHOT" +hubdleCatalog = "0.1.18+2.0.0-dev-10501-SNAPSHOT" +javiersc-kotlin-kotlinCompilerExtensions = "0.1.1+2.0.0-dev-10501-SNAPSHOT" javiersc-kotlin-kotlinStdlibAndTest = "0.1.0-SNAPSHOT" [libraries] diff --git a/kopy-compiler/api/library.api b/kopy-compiler/api/library.api deleted file mode 100644 index c211e1e..0000000 --- a/kopy-compiler/api/library.api +++ /dev/null @@ -1,4 +0,0 @@ -public final class Empty { - public fun ()V -} - diff --git a/kopy-compiler/main/kotlin/com/javiersc/kotlin/kopy/compiler/KopyCommandLineProcessor.kt b/kopy-compiler/main/kotlin/com/javiersc/kotlin/kopy/compiler/KopyCommandLineProcessor.kt index 116f176..4c55027 100644 --- a/kopy-compiler/main/kotlin/com/javiersc/kotlin/kopy/compiler/KopyCommandLineProcessor.kt +++ b/kopy-compiler/main/kotlin/com/javiersc/kotlin/kopy/compiler/KopyCommandLineProcessor.kt @@ -1,7 +1,7 @@ package com.javiersc.kotlin.kopy.compiler -import com.javiersc.kotlin.kopy.kopy.compiler.KopyCompilerProjectData.Group -import com.javiersc.kotlin.kopy.kopy.compiler.KopyCompilerProjectData.Name +import com.javiersc.kotlin.kopy.compiler.KopyCompilerProjectData.Group +import com.javiersc.kotlin.kopy.compiler.KopyCompilerProjectData.Name import org.jetbrains.kotlin.compiler.plugin.AbstractCliOption import org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor import org.jetbrains.kotlin.config.CompilerConfiguration diff --git a/kopy-compiler/main/kotlin/com/javiersc/kotlin/kopy/compiler/fir/FirKopyExtension.kt b/kopy-compiler/main/kotlin/com/javiersc/kotlin/kopy/compiler/fir/FirKopyExtension.kt index 5c30fd9..2eefb20 100644 --- a/kopy-compiler/main/kotlin/com/javiersc/kotlin/kopy/compiler/fir/FirKopyExtension.kt +++ b/kopy-compiler/main/kotlin/com/javiersc/kotlin/kopy/compiler/fir/FirKopyExtension.kt @@ -1,5 +1,7 @@ package com.javiersc.kotlin.kopy.compiler.fir +import com.javiersc.kotlin.kopy.compiler.fir.checker.FirKopyCheckerExtension +import com.javiersc.kotlin.kopy.compiler.fir.generation.FirKopySupertypeGenerationExtension import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrar @@ -12,7 +14,11 @@ internal class FirKopyExtension( registerGenerators() } - private fun ExtensionRegistrarContext.registerCheckers() {} + private fun ExtensionRegistrarContext.registerCheckers() { + +::FirKopyCheckerExtension + } - private fun ExtensionRegistrarContext.registerGenerators() {} + private fun ExtensionRegistrarContext.registerGenerators() { + +::FirKopySupertypeGenerationExtension + } } diff --git a/kopy-compiler/main/kotlin/com/javiersc/kotlin/kopy/compiler/fir/checker/FirKopyCheckerExtension.kt b/kopy-compiler/main/kotlin/com/javiersc/kotlin/kopy/compiler/fir/checker/FirKopyCheckerExtension.kt new file mode 100644 index 0000000..c7f9472 --- /dev/null +++ b/kopy-compiler/main/kotlin/com/javiersc/kotlin/kopy/compiler/fir/checker/FirKopyCheckerExtension.kt @@ -0,0 +1,48 @@ +package com.javiersc.kotlin.kopy.compiler.fir.checker + +import com.javiersc.kotlin.compiler.extensions.common.fqName +import com.javiersc.kotlin.kopy.KopyFunctionInvoke +import org.jetbrains.kotlin.diagnostics.DiagnosticReporter +import org.jetbrains.kotlin.fir.FirSession +import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext +import org.jetbrains.kotlin.fir.analysis.checkers.expression.ExpressionCheckers +import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirCallChecker +import org.jetbrains.kotlin.fir.analysis.extensions.FirAdditionalCheckersExtension +import org.jetbrains.kotlin.fir.expressions.FirCall +import org.jetbrains.kotlin.fir.extensions.FirDeclarationPredicateRegistrar +import org.jetbrains.kotlin.fir.resolve.fqName + +internal class FirKopyCheckerExtension( + session: FirSession, +) : FirAdditionalCheckersExtension(session) { + + override val expressionCheckers: ExpressionCheckers = + object : ExpressionCheckers() { + override val callCheckers: Set = + setOf( + object : FirCallChecker() { + override fun check( + expression: FirCall, + context: CheckerContext, + reporter: DiagnosticReporter + ) { + if ( + expression.annotations.any { + it.fqName(session) == fqName() + } + ) + println() + } + } + ) + } + + override fun FirDeclarationPredicateRegistrar.registerPredicates() { + // DeclarationPredicate.create { + // // metaAnnotated(fqName(), includeItself = true) + // // parentAnnotated(fqName()) + // // annotated(fqName()) + // // ancestorAnnotated(fqName()) + // } + } +} diff --git a/kopy-compiler/main/kotlin/com/javiersc/kotlin/kopy/compiler/fir/generation/FirKopySupertypeGenerationExtension.kt b/kopy-compiler/main/kotlin/com/javiersc/kotlin/kopy/compiler/fir/generation/FirKopySupertypeGenerationExtension.kt new file mode 100644 index 0000000..85df835 --- /dev/null +++ b/kopy-compiler/main/kotlin/com/javiersc/kotlin/kopy/compiler/fir/generation/FirKopySupertypeGenerationExtension.kt @@ -0,0 +1,45 @@ +package com.javiersc.kotlin.kopy.compiler.fir.generation + +import com.javiersc.kotlin.compiler.extensions.common.classId +import com.javiersc.kotlin.compiler.extensions.common.fqName +import com.javiersc.kotlin.compiler.extensions.fir.createFirResolvedTypeRef +import com.javiersc.kotlin.compiler.extensions.fir.toConeType +import com.javiersc.kotlin.kopy.Kopy as KopyAnnotation +import com.javiersc.kotlin.kopy.runtime.Kopyable +import org.jetbrains.kotlin.fir.FirSession +import org.jetbrains.kotlin.fir.declarations.FirClassLikeDeclaration +import org.jetbrains.kotlin.fir.declarations.utils.classId +import org.jetbrains.kotlin.fir.expressions.FirAnnotation +import org.jetbrains.kotlin.fir.extensions.FirDeclarationPredicateRegistrar +import org.jetbrains.kotlin.fir.extensions.FirSupertypeGenerationExtension +import org.jetbrains.kotlin.fir.extensions.predicate.LookupPredicate +import org.jetbrains.kotlin.fir.resolve.fqName +import org.jetbrains.kotlin.fir.types.ConeClassLikeType +import org.jetbrains.kotlin.fir.types.FirResolvedTypeRef + +internal class FirKopySupertypeGenerationExtension( + session: FirSession, +) : FirSupertypeGenerationExtension(session) { + + context(TypeResolveServiceContainer) + override fun computeAdditionalSupertypes( + classLikeDeclaration: FirClassLikeDeclaration, + resolvedSupertypes: List + ): List { + if (classLikeDeclaration.annotations.isEmpty()) return emptyList() + if (classLikeDeclaration.annotations.none { it.isKopy }) return emptyList() + val typeArgument: ConeClassLikeType = classLikeDeclaration.classId.toConeType() + val type: ConeClassLikeType = classId>().toConeType(typeArgument) + val supertype: FirResolvedTypeRef = createFirResolvedTypeRef(type) + return listOf(supertype) + } + + override fun needTransformSupertypes(declaration: FirClassLikeDeclaration): Boolean = true + + override fun FirDeclarationPredicateRegistrar.registerPredicates() { + register(LookupPredicate.create { annotated(fqName()) }) + } + + private val FirAnnotation.isKopy: Boolean + get() = fqName(session) == fqName() +} diff --git a/kopy-compiler/main/kotlin/com/javiersc/kotlin/kopy/compiler/ir/IrKopyGenerationExtension.kt b/kopy-compiler/main/kotlin/com/javiersc/kotlin/kopy/compiler/ir/IrKopyGenerationExtension.kt index 976347f..77cac9d 100644 --- a/kopy-compiler/main/kotlin/com/javiersc/kotlin/kopy/compiler/ir/IrKopyGenerationExtension.kt +++ b/kopy-compiler/main/kotlin/com/javiersc/kotlin/kopy/compiler/ir/IrKopyGenerationExtension.kt @@ -1,13 +1,31 @@ package com.javiersc.kotlin.kopy.compiler.ir +import com.javiersc.kotlin.compiler.extensions.ir.asIrOrNull +import com.javiersc.kotlin.compiler.extensions.ir.name +import com.javiersc.kotlin.kopy.compiler.ir._internal.InvokeCallTransformer +import com.javiersc.kotlin.kopy.compiler.ir._internal.UpdateCallTransformer +import com.javiersc.kotlin.stdlib.tree.TreeNode import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.ir.declarations.IrModuleFragment +import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression +import org.jetbrains.kotlin.name.Name internal class IrKopyGenerationExtension( private val configuration: CompilerConfiguration, ) : IrGenerationExtension { - override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) {} + override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) { + moduleFragment.transform(InvokeCallTransformer(moduleFragment, pluginContext), null) + moduleFragment.transform(UpdateCallTransformer(moduleFragment, pluginContext), null) + + println("IrKopyGenerationExtension") + } + + private fun TreeNode.addNestedReceivers(call: IrFunctionAccessExpression) { + val receiver = call.extensionReceiver?.asIrOrNull() ?: return + addChild(TreeNode(receiver.name)) + addNestedReceivers(receiver) + } } diff --git a/kopy-compiler/main/kotlin/com/javiersc/kotlin/kopy/compiler/ir/_internal/InvokeCallTransformer.kt b/kopy-compiler/main/kotlin/com/javiersc/kotlin/kopy/compiler/ir/_internal/InvokeCallTransformer.kt new file mode 100644 index 0000000..6c40ee4 --- /dev/null +++ b/kopy-compiler/main/kotlin/com/javiersc/kotlin/kopy/compiler/ir/_internal/InvokeCallTransformer.kt @@ -0,0 +1,173 @@ +package com.javiersc.kotlin.kopy.compiler.ir._internal + +import com.javiersc.kotlin.compiler.extensions.common.toCallableId +import com.javiersc.kotlin.compiler.extensions.common.toName +import com.javiersc.kotlin.compiler.extensions.ir.asIr +import com.javiersc.kotlin.compiler.extensions.ir.asIrOrNull +import com.javiersc.kotlin.compiler.extensions.ir.createIrFunctionExpression +import com.javiersc.kotlin.compiler.extensions.ir.declarationIrBuilder +import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext +import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext +import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder +import org.jetbrains.kotlin.descriptors.DescriptorVisibilities +import org.jetbrains.kotlin.ir.IrStatement +import org.jetbrains.kotlin.ir.builders.declarations.buildFun +import org.jetbrains.kotlin.ir.builders.declarations.buildValueParameter +import org.jetbrains.kotlin.ir.builders.irBlockBody +import org.jetbrains.kotlin.ir.builders.irCall +import org.jetbrains.kotlin.ir.builders.irGet +import org.jetbrains.kotlin.ir.builders.irReturn +import org.jetbrains.kotlin.ir.builders.irSet +import org.jetbrains.kotlin.ir.builders.irTemporary +import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin +import org.jetbrains.kotlin.ir.declarations.IrDeclarationParent +import org.jetbrains.kotlin.ir.declarations.IrModuleFragment +import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction +import org.jetbrains.kotlin.ir.declarations.IrVariable +import org.jetbrains.kotlin.ir.expressions.IrBlockBody +import org.jetbrains.kotlin.ir.expressions.IrCall +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression +import org.jetbrains.kotlin.ir.expressions.IrFunctionExpression +import org.jetbrains.kotlin.ir.expressions.IrGetValue +import org.jetbrains.kotlin.ir.expressions.IrSetValue +import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin +import org.jetbrains.kotlin.ir.expressions.IrTypeOperatorCall +import org.jetbrains.kotlin.ir.symbols.IrSymbol +import org.jetbrains.kotlin.ir.types.IrSimpleType +import org.jetbrains.kotlin.ir.types.typeWith +import org.jetbrains.kotlin.ir.util.statements +import org.jetbrains.kotlin.name.SpecialNames + +internal class InvokeCallTransformer( + private val moduleFragment: IrModuleFragment, + private val pluginContext: IrPluginContext, +) : IrElementTransformerVoidWithContext() { + + override fun visitCall(expression: IrCall): IrExpression { + fun originalCall(): IrExpression = super.visitCall(expression) + + if (!expression.isKopyInvoke) return originalCall() + val runCall: IrFunctionAccessExpression = createRunCall(expression) ?: return originalCall() + + return runCall + } + + private fun IrSymbol.declarationIrBuilder(): DeclarationIrBuilder = + pluginContext.declarationIrBuilder(this) + + private fun createRunCall(originalCall: IrCall): IrFunctionAccessExpression? { + val originalCallParent: IrDeclarationParent = + originalCall.findDeclarationParent(moduleFragment)?.asIrOrNull() + ?: return null + val type = originalCall.dispatchReceiver?.type ?: return null + + val runFunction: IrSimpleFunction = + pluginContext + .referenceFunctions("kotlin.run".toCallableId()) + .first { it.owner.typeParameters.count() == 2 } + .owner + + val runBlockFunction: IrSimpleFunction = + pluginContext.irFactory + .buildFun { + name = SpecialNames.ANONYMOUS + visibility = DescriptorVisibilities.LOCAL + returnType = type + origin = IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA + } + .apply { + parent = originalCallParent + extensionReceiverParameter = + buildValueParameter(this) { + this.name = "${'$'}this${'$'}run".toName() + this.type = type + } + } + + val originalStatements = + originalCall + .getValueArgument(0) + .asIrOrNull() + ?.function + ?.body + ?.statements ?: return null + + val runBlockFunctionBody: IrBlockBody = + runBlockFunction.symbol.declarationIrBuilder().irBlockBody { + val thisRunReceiver: IrGetValue = + runBlockFunction.extensionReceiverParameter?.let { valueParameter -> + runBlockFunction.symbol.declarationIrBuilder().irGet(valueParameter) + } ?: return@irBlockBody + + val tempVar: IrVariable = irTemporary(value = thisRunReceiver, isMutable = true) + for (statement in originalStatements) { + +statement.processStatement(originalCall, runBlockFunction, tempVar) + } + +irReturn(runBlockFunction.symbol.declarationIrBuilder().irGet(tempVar)) + } + + runBlockFunction.body = runBlockFunctionBody + + val runCall: IrFunctionAccessExpression = + runFunction.symbol.declarationIrBuilder().irCall(runFunction).apply { + extensionReceiver = originalCall.dispatchReceiver + putTypeArgument(index = 0, type = type) + putTypeArgument(index = 1, type = type) + val kFunction1Type: IrSimpleType = + runFunction.valueParameters + .first() + .type + .asIr() + .classifier + .typeWith(type, type) + putValueArgument( + index = 0, + valueArgument = + createIrFunctionExpression( + type = kFunction1Type, + function = runBlockFunction, + origin = IrStatementOrigin.LAMBDA, + ) + ) + this.type = type + } + + return runCall + } + + private fun IrStatement.processStatement( + originalCall: IrCall, + runBlockFunction: IrSimpleFunction, + tempVar: IrVariable, + ): IrStatement { + val operatorCall: IrFunctionAccessExpression = + asIrOrNull()?.argument?.asIrOrNull() ?: return this + + val original = originalCall.dispatchReceiver.asIrOrNull() ?: return this + val originalType = original.type + val originalName = original.symbol.descriptor.name + + val operator = operatorCall.dispatchReceiver.asIrOrNull() ?: return this + val operatorType = operator.type + val operatorName = operator.symbol.descriptor.name + + val hasSameReceiver = + originalType == operatorType && "${'$'}this${'$'}$originalName" == "$operatorName" + + if (!hasSameReceiver) return this + + operatorCall.apply { + val receiver: () -> IrGetValue = { + runBlockFunction.symbol.declarationIrBuilder().irGet(tempVar) + } + dispatchReceiver = receiver() + extensionReceiver.asIrOrNull()?.dispatchReceiver = + receiver() + } + val tempVarSet: IrSetValue = + runBlockFunction.symbol.declarationIrBuilder().irSet(tempVar, operatorCall) + + return tempVarSet + } +} diff --git a/kopy-compiler/main/kotlin/com/javiersc/kotlin/kopy/compiler/ir/_internal/UpdateCallTransformer.kt b/kopy-compiler/main/kotlin/com/javiersc/kotlin/kopy/compiler/ir/_internal/UpdateCallTransformer.kt new file mode 100644 index 0000000..5d0cc13 --- /dev/null +++ b/kopy-compiler/main/kotlin/com/javiersc/kotlin/kopy/compiler/ir/_internal/UpdateCallTransformer.kt @@ -0,0 +1,196 @@ +package com.javiersc.kotlin.kopy.compiler.ir._internal + +import com.javiersc.kotlin.compiler.extensions.common.toCallableId +import com.javiersc.kotlin.compiler.extensions.common.toName +import com.javiersc.kotlin.compiler.extensions.ir.asIr +import com.javiersc.kotlin.compiler.extensions.ir.asIrOrNull +import com.javiersc.kotlin.compiler.extensions.ir.createIrFunctionExpression +import com.javiersc.kotlin.compiler.extensions.ir.declarationIrBuilder +import com.javiersc.kotlin.compiler.extensions.ir.firstIrSimpleFunction +import com.javiersc.kotlin.compiler.extensions.ir.toIrExpression +import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext +import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext +import org.jetbrains.kotlin.descriptors.DescriptorVisibilities +import org.jetbrains.kotlin.ir.IrStatement +import org.jetbrains.kotlin.ir.builders.IrBlockBodyBuilder +import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter +import org.jetbrains.kotlin.ir.builders.declarations.buildFun +import org.jetbrains.kotlin.ir.builders.irBlockBody +import org.jetbrains.kotlin.ir.builders.irCall +import org.jetbrains.kotlin.ir.builders.irReturn +import org.jetbrains.kotlin.ir.declarations.IrClass +import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin +import org.jetbrains.kotlin.ir.declarations.IrDeclarationParent +import org.jetbrains.kotlin.ir.declarations.IrFunction +import org.jetbrains.kotlin.ir.declarations.IrModuleFragment +import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction +import org.jetbrains.kotlin.ir.declarations.IrValueParameter +import org.jetbrains.kotlin.ir.expressions.IrBlockBody +import org.jetbrains.kotlin.ir.expressions.IrCall +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.expressions.IrFunctionExpression +import org.jetbrains.kotlin.ir.expressions.IrGetField +import org.jetbrains.kotlin.ir.expressions.IrGetValue +import org.jetbrains.kotlin.ir.expressions.IrReturn +import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin +import org.jetbrains.kotlin.ir.symbols.IrPropertySymbol +import org.jetbrains.kotlin.ir.types.IrSimpleType +import org.jetbrains.kotlin.ir.types.IrType +import org.jetbrains.kotlin.ir.types.typeWith +import org.jetbrains.kotlin.ir.util.functions +import org.jetbrains.kotlin.ir.util.statements +import org.jetbrains.kotlin.name.SpecialNames + +internal class UpdateCallTransformer( + private val moduleFragment: IrModuleFragment, + private val pluginContext: IrPluginContext, +) : IrElementTransformerVoidWithContext() { + + override fun visitCall(expression: IrCall): IrExpression { + fun originalCall(): IrExpression = super.visitCall(expression) + if (!expression.isKopyUpdate) return originalCall() + + val letCall: IrCall = createLetCall(expression) ?: return originalCall() + + val copyCall: IrCall = createCopyCall(expression, letCall) + + return copyCall + } + + private fun createLetCall(updateCall: IrCall): IrCall { + val declarationParent: IrDeclarationParent = + updateCall.findDeclarationParent(moduleFragment).asIrOrNull() + ?: return updateCall + + val updateBlockStatements: List = + updateCall + .getValueArgument((updateCall.valueArgumentsCount - 1).takeIf { it >= 0 } ?: 0) + .asIrOrNull() + ?.function + ?.body + ?.statements + .orEmpty() + + val type: IrType = updateCall.type + + val letFunction: IrSimpleFunction = + pluginContext.firstIrSimpleFunction("kotlin.let".toCallableId()) + + val letBlockFunction: IrSimpleFunction = + pluginContext.irFactory + .buildFun { + name = SpecialNames.ANONYMOUS + visibility = DescriptorVisibilities.LOCAL + returnType = type + origin = IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA + } + .apply { + parent = declarationParent + addValueParameter { + this.name = "it".toName() + this.type = type + } + } + + val letBlockBody: IrBlockBody = + pluginContext.declarationIrBuilder(letBlockFunction).irBlockBody { + for (statement in updateBlockStatements) { + +processUpdateBlockStatement(statement) + } + } + letBlockFunction.body = letBlockBody + + val letCall: IrCall = + pluginContext + .declarationIrBuilder(letFunction.symbol) + .irCall(letFunction.symbol) + .apply { + this.extensionReceiver = updateCall.extensionReceiver + this.dispatchReceiver = null + putTypeArgument(index = 0, type = type) + putTypeArgument(index = 1, type = type) + val kFunction1Type: IrSimpleType = + letFunction.valueParameters + .first() + .type + .asIr() + .classifier + .typeWith(type, type) + putValueArgument( + index = 0, + valueArgument = + createIrFunctionExpression( + type = kFunction1Type, + function = letBlockFunction, + origin = IrStatementOrigin.LAMBDA, + ) + ) + this.type = type + } + + val letCall2: IrCall = + pluginContext.declarationIrBuilder(letFunction).irCall(letFunction.symbol).apply { + this.extensionReceiver = updateCall.extensionReceiver + putTypeArgument(0, type) + putTypeArgument(1, type) + this.dispatchReceiver = null + putValueArgument(0, letFunction.toIrExpression()) + this.type = type + } + + return letCall + } + + private fun IrBlockBodyBuilder.processUpdateBlockStatement( + statement: IrStatement + ): IrStatement = if (statement is IrReturn) irReturn(statement.value) else statement + + private fun createCopyCall(updateCall: IrCall, letCall: IrCall): IrCall { + val tempVarGet: IrGetValue = + updateCall.dispatchReceiver.asIrOrNull() ?: return letCall + val propertySymbol: IrPropertySymbol = + letCall.extensionReceiver + .asIrOrNull() + ?.symbol + ?.owner + ?.correspondingPropertySymbol ?: return letCall + val copyFunction: IrSimpleFunction = + tempVarGet.type + .asIrOrNull() + ?.classifier + ?.owner + ?.asIrOrNull() + ?.functions + ?.firstOrNull { it.name == "copy".toName() } ?: return letCall + + val copyCall: IrCall = + pluginContext.declarationIrBuilder(copyFunction).irCall(copyFunction.symbol).apply { + dispatchReceiver = tempVarGet + extensionReceiver = null + for (index in 0 ..< valueArgumentsCount) { + val isSameProperty: Boolean = + copyFunction.getIsSameProperty(index, propertySymbol) + if (!isSameProperty) putValueArgument(index, null) + else putValueArgument(index, letCall) + } + } + + return copyCall + } + + private fun IrFunction.getIsSameProperty( + index: Int, + propertySymbol: IrPropertySymbol + ): Boolean = + valueParameters + .getOrNull(index) + ?.asIrOrNull() + ?.symbol + ?.owner + ?.defaultValue + ?.expression + ?.asIrOrNull() + ?.symbol + ?.owner + ?.correspondingPropertySymbol == propertySymbol +} diff --git a/kopy-compiler/main/kotlin/com/javiersc/kotlin/kopy/compiler/ir/_internal/UpdateCallsForInvokeCallVisitor.kt b/kopy-compiler/main/kotlin/com/javiersc/kotlin/kopy/compiler/ir/_internal/UpdateCallsForInvokeCallVisitor.kt new file mode 100644 index 0000000..b070624 --- /dev/null +++ b/kopy-compiler/main/kotlin/com/javiersc/kotlin/kopy/compiler/ir/_internal/UpdateCallsForInvokeCallVisitor.kt @@ -0,0 +1,21 @@ +package com.javiersc.kotlin.kopy.compiler.ir._internal + +import com.javiersc.kotlin.compiler.extensions.ir.asIrOrNull +import org.jetbrains.kotlin.ir.expressions.IrCall +import org.jetbrains.kotlin.ir.expressions.IrGetValue +import org.jetbrains.kotlin.ir.symbols.IrValueParameterSymbol +import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid + +internal class UpdateCallsForInvokeCallVisitor( + private val symbol: IrValueParameterSymbol, +) : IrElementVisitorVoid { + + private val _calls: MutableList = mutableListOf() + val calls: List = _calls + + override fun visitCall(expression: IrCall) { + if (!expression.isKopyUpdate) return super.visitCall(expression) + val dispatchReceiverSymbol = expression.dispatchReceiver?.asIrOrNull()?.symbol + if (symbol == dispatchReceiverSymbol) _calls.add(expression) + } +} diff --git a/kopy-compiler/main/kotlin/com/javiersc/kotlin/kopy/compiler/ir/_internal/irTree.kt b/kopy-compiler/main/kotlin/com/javiersc/kotlin/kopy/compiler/ir/_internal/irTree.kt new file mode 100644 index 0000000..fcccff3 --- /dev/null +++ b/kopy-compiler/main/kotlin/com/javiersc/kotlin/kopy/compiler/ir/_internal/irTree.kt @@ -0,0 +1,21 @@ +package com.javiersc.kotlin.kopy.compiler.ir._internal + +import com.javiersc.kotlin.compiler.extensions.ir.treeNode +import com.javiersc.kotlin.stdlib.tree.TreeNode +import org.jetbrains.kotlin.ir.IrElement +import org.jetbrains.kotlin.ir.declarations.IrDeclarationParent +import org.jetbrains.kotlin.ir.declarations.IrFile +import org.jetbrains.kotlin.ir.declarations.IrModuleFragment + +internal val IrModuleFragment.irFilesTree: List> + get() = files.flatMap(IrFile::treeNode) + +internal fun IrElement.findDeclarationParent(moduleFragment: IrModuleFragment): IrElement? { + val element: TreeNode? = moduleFragment.irFilesTree.firstOrNull { it.value == this } + return element?.findDeclarationParent() +} + +internal fun TreeNode.findDeclarationParent(): IrElement? { + val parent: TreeNode = parent ?: return null + return if (parent.value is IrDeclarationParent) parent.value else parent.findDeclarationParent() +} diff --git a/kopy-compiler/main/kotlin/com/javiersc/kotlin/kopy/compiler/ir/_internal/isKopy.kt b/kopy-compiler/main/kotlin/com/javiersc/kotlin/kopy/compiler/ir/_internal/isKopy.kt new file mode 100644 index 0000000..eef5d21 --- /dev/null +++ b/kopy-compiler/main/kotlin/com/javiersc/kotlin/kopy/compiler/ir/_internal/isKopy.kt @@ -0,0 +1,13 @@ +package com.javiersc.kotlin.kopy.compiler.ir._internal + +import com.javiersc.kotlin.compiler.extensions.common.fqName +import com.javiersc.kotlin.compiler.extensions.ir.hasAnnotation +import com.javiersc.kotlin.kopy.KopyFunctionInvoke +import com.javiersc.kotlin.kopy.KopyFunctionUpdate +import org.jetbrains.kotlin.ir.expressions.IrCall + +internal val IrCall.isKopyInvoke: Boolean + get() = hasAnnotation(fqName()) + +internal val IrCall.isKopyUpdate: Boolean + get() = hasAnnotation(fqName()) diff --git a/kopy-compiler/test-data/box/.empty b/kopy-compiler/test-data/additional-files/SomeFile.kt similarity index 100% rename from kopy-compiler/test-data/box/.empty rename to kopy-compiler/test-data/additional-files/SomeFile.kt diff --git a/kopy-compiler/test-data/box/complex.kt.disabled b/kopy-compiler/test-data/box/complex.kt.disabled new file mode 100644 index 0000000..457c105 --- /dev/null +++ b/kopy-compiler/test-data/box/complex.kt.disabled @@ -0,0 +1,28 @@ +package com.javiersc.kotlin.kopy.playground + +import com.javiersc.kotlin.kopy.Kopy + +fun box(): String { + val house0 = + House( + kitchen = + Kitchen( + cat = + Cat( + name = "Garfield", + age = 5, + ), + squareMeters = 30, + ), + squareMeters = 100, + ) + val house1 = house0 { kitchen.cat.age.update { 2 } } + val house2 = house0.copy(kitchen = house0.kitchen.copy(cat = house0.kitchen.cat.copy(age = 2))) + return if (house1 == house2) "OK" else "ERROR" +} + +internal data class Cat(val name: String, val age: Int) + +internal data class Kitchen(val cat: Cat, val squareMeters: Int) + +@Kopy internal data class House(val kitchen: Kitchen, val squareMeters: Int) diff --git a/kopy-compiler/test-data/box/no-nest-update-no-it.fir.ir.txt b/kopy-compiler/test-data/box/no-nest-update-no-it.fir.ir.txt new file mode 100644 index 0000000..39086a8 --- /dev/null +++ b/kopy-compiler/test-data/box/no-nest-update-no-it.fir.ir.txt @@ -0,0 +1,246 @@ +FILE fqName:com.javiersc.kotlin.kopy.playground fileName:/no-nest-update-no-it.kt + FUN name:box visibility:public modality:FINAL <> () returnType:kotlin.String + BLOCK_BODY + VAR name:house0 type:com.javiersc.kotlin.kopy.playground.House [val] + CONSTRUCTOR_CALL 'public constructor (street: kotlin.String, squareMeters: kotlin.Int) declared in com.javiersc.kotlin.kopy.playground.House' type=com.javiersc.kotlin.kopy.playground.House origin=null + street: CONST String type=kotlin.String value="Street" + squareMeters: CONST Int type=kotlin.Int value=20 + VAR name:house1 type:com.javiersc.kotlin.kopy.playground.House [val] + CALL 'public final fun run (block: @[ExtensionFunctionType] kotlin.Function1): R of kotlin.run declared in kotlin' type=com.javiersc.kotlin.kopy.playground.House origin=null + : com.javiersc.kotlin.kopy.playground.House + : com.javiersc.kotlin.kopy.playground.House + $receiver: GET_VAR 'val house0: com.javiersc.kotlin.kopy.playground.House declared in com.javiersc.kotlin.kopy.playground.box' type=com.javiersc.kotlin.kopy.playground.House origin=VARIABLE_AS_FUNCTION + block: FUN_EXPR type=kotlin.Function1 origin=LAMBDA + FUN LOCAL_FUNCTION_FOR_LAMBDA name: visibility:local modality:FINAL <> ($receiver:com.javiersc.kotlin.kopy.playground.House) returnType:com.javiersc.kotlin.kopy.playground.House + $receiver: VALUE_PARAMETER name:$this$run type:com.javiersc.kotlin.kopy.playground.House + BLOCK_BODY + VAR IR_TEMPORARY_VARIABLE name:tmp_0 type:com.javiersc.kotlin.kopy.playground.House [var] + GET_VAR '$this$run: com.javiersc.kotlin.kopy.playground.House declared in com.javiersc.kotlin.kopy.playground.box.' type=com.javiersc.kotlin.kopy.playground.House origin=null + SET_VAR 'var tmp_0: com.javiersc.kotlin.kopy.playground.House declared in com.javiersc.kotlin.kopy.playground.box.' type=kotlin.Unit origin=EQ + CALL 'public final fun copy (street: kotlin.String, squareMeters: kotlin.Int): com.javiersc.kotlin.kopy.playground.House declared in com.javiersc.kotlin.kopy.playground.House' type=com.javiersc.kotlin.kopy.playground.House origin=null + $this: GET_VAR 'var tmp_0: com.javiersc.kotlin.kopy.playground.House declared in com.javiersc.kotlin.kopy.playground.box.' type=com.javiersc.kotlin.kopy.playground.House origin=null + squareMeters: CALL 'public final fun let (block: kotlin.Function1): R of kotlin.let declared in kotlin' type=kotlin.Int origin=null + : kotlin.Int + : kotlin.Int + $receiver: CALL 'public final fun (): kotlin.Int declared in com.javiersc.kotlin.kopy.playground.House' type=kotlin.Int origin=GET_PROPERTY + $this: GET_VAR 'var tmp_0: com.javiersc.kotlin.kopy.playground.House declared in com.javiersc.kotlin.kopy.playground.box.' type=com.javiersc.kotlin.kopy.playground.House origin=null + block: FUN_EXPR type=kotlin.Function1 origin=LAMBDA + FUN LOCAL_FUNCTION_FOR_LAMBDA name: visibility:local modality:FINAL <> (it:kotlin.Int) returnType:kotlin.Int + VALUE_PARAMETER name:it index:0 type:kotlin.Int + BLOCK_BODY + RETURN type=kotlin.Nothing from='local final fun (it: kotlin.Int): kotlin.Int declared in com.javiersc.kotlin.kopy.playground.box.' + CONST Int type=kotlin.Int value=40 + RETURN type=kotlin.Nothing from='local final fun (): com.javiersc.kotlin.kopy.playground.House declared in com.javiersc.kotlin.kopy.playground.box' + GET_VAR 'var tmp_0: com.javiersc.kotlin.kopy.playground.House declared in com.javiersc.kotlin.kopy.playground.box.' type=com.javiersc.kotlin.kopy.playground.House origin=null + VAR name:house2 type:com.javiersc.kotlin.kopy.playground.House [val] + CALL 'public final fun run (block: @[ExtensionFunctionType] kotlin.Function1): R of kotlin.run declared in kotlin' type=com.javiersc.kotlin.kopy.playground.House origin=null + : com.javiersc.kotlin.kopy.playground.House + : com.javiersc.kotlin.kopy.playground.House + $receiver: GET_VAR 'val house0: com.javiersc.kotlin.kopy.playground.House declared in com.javiersc.kotlin.kopy.playground.box' type=com.javiersc.kotlin.kopy.playground.House origin=null + block: FUN_EXPR type=@[ExtensionFunctionType] kotlin.Function1 origin=LAMBDA + FUN LOCAL_FUNCTION_FOR_LAMBDA name: visibility:local modality:FINAL <> ($receiver:com.javiersc.kotlin.kopy.playground.House) returnType:com.javiersc.kotlin.kopy.playground.House + $receiver: VALUE_PARAMETER name:$this$run type:com.javiersc.kotlin.kopy.playground.House + BLOCK_BODY + VAR name:tmp0 type:com.javiersc.kotlin.kopy.playground.House [var] + GET_VAR '$this$run: com.javiersc.kotlin.kopy.playground.House declared in com.javiersc.kotlin.kopy.playground.box.' type=com.javiersc.kotlin.kopy.playground.House origin=null + SET_VAR 'var tmp0: com.javiersc.kotlin.kopy.playground.House declared in com.javiersc.kotlin.kopy.playground.box.' type=kotlin.Unit origin=EQ + CALL 'public final fun copy (street: kotlin.String, squareMeters: kotlin.Int): com.javiersc.kotlin.kopy.playground.House declared in com.javiersc.kotlin.kopy.playground.House' type=com.javiersc.kotlin.kopy.playground.House origin=null + $this: GET_VAR 'var tmp0: com.javiersc.kotlin.kopy.playground.House declared in com.javiersc.kotlin.kopy.playground.box.' type=com.javiersc.kotlin.kopy.playground.House origin=null + squareMeters: CALL 'public final fun let (block: kotlin.Function1): R of kotlin.let declared in kotlin' type=kotlin.Int origin=null + : kotlin.Int + : kotlin.Int + $receiver: CALL 'public final fun (): kotlin.Int declared in com.javiersc.kotlin.kopy.playground.House' type=kotlin.Int origin=GET_PROPERTY + $this: GET_VAR 'var tmp0: com.javiersc.kotlin.kopy.playground.House declared in com.javiersc.kotlin.kopy.playground.box.' type=com.javiersc.kotlin.kopy.playground.House origin=null + block: FUN_EXPR type=kotlin.Function1 origin=LAMBDA + FUN LOCAL_FUNCTION_FOR_LAMBDA name: visibility:local modality:FINAL <> (it:kotlin.Int) returnType:kotlin.Int + VALUE_PARAMETER name:it index:0 type:kotlin.Int + BLOCK_BODY + RETURN type=kotlin.Nothing from='local final fun (it: kotlin.Int): kotlin.Int declared in com.javiersc.kotlin.kopy.playground.box.' + CONST Int type=kotlin.Int value=40 + RETURN type=kotlin.Nothing from='local final fun (): com.javiersc.kotlin.kopy.playground.House declared in com.javiersc.kotlin.kopy.playground.box' + GET_VAR 'var tmp0: com.javiersc.kotlin.kopy.playground.House declared in com.javiersc.kotlin.kopy.playground.box.' type=com.javiersc.kotlin.kopy.playground.House origin=null + RETURN type=kotlin.Nothing from='public final fun box (): kotlin.String declared in com.javiersc.kotlin.kopy.playground' + WHEN type=kotlin.String origin=IF + BRANCH + if: CALL 'public final fun EQEQ (arg0: kotlin.Any?, arg1: kotlin.Any?): kotlin.Boolean declared in kotlin.internal.ir' type=kotlin.Boolean origin=EQEQ + arg0: GET_VAR 'val house1: com.javiersc.kotlin.kopy.playground.House declared in com.javiersc.kotlin.kopy.playground.box' type=com.javiersc.kotlin.kopy.playground.House origin=null + arg1: GET_VAR 'val house2: com.javiersc.kotlin.kopy.playground.House declared in com.javiersc.kotlin.kopy.playground.box' type=com.javiersc.kotlin.kopy.playground.House origin=null + then: CONST String type=kotlin.String value="OK" + BRANCH + if: CONST Boolean type=kotlin.Boolean value=true + then: STRING_CONCATENATION type=kotlin.String + CONST String type=kotlin.String value="Fail: \nHouse1: " + GET_VAR 'val house1: com.javiersc.kotlin.kopy.playground.House declared in com.javiersc.kotlin.kopy.playground.box' type=com.javiersc.kotlin.kopy.playground.House origin=null + CONST String type=kotlin.String value=" \nHouse2: " + GET_VAR 'val house2: com.javiersc.kotlin.kopy.playground.House declared in com.javiersc.kotlin.kopy.playground.box' type=com.javiersc.kotlin.kopy.playground.House origin=null + CLASS CLASS name:House modality:FINAL visibility:internal [data] superTypes:[kotlin.Any; com.javiersc.kotlin.kopy.runtime.Kopyable] + annotations: + Kopy + $this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:com.javiersc.kotlin.kopy.playground.House + CONSTRUCTOR visibility:public <> (street:kotlin.String, squareMeters:kotlin.Int) returnType:com.javiersc.kotlin.kopy.playground.House [primary] + VALUE_PARAMETER name:street index:0 type:kotlin.String + VALUE_PARAMETER name:squareMeters index:1 type:kotlin.Int + BLOCK_BODY + DELEGATING_CONSTRUCTOR_CALL 'public constructor () declared in kotlin.Any' + INSTANCE_INITIALIZER_CALL classDescriptor='CLASS CLASS name:House modality:FINAL visibility:internal [data] superTypes:[kotlin.Any; com.javiersc.kotlin.kopy.runtime.Kopyable]' + PROPERTY name:street visibility:public modality:FINAL [val] + FIELD PROPERTY_BACKING_FIELD name:street type:kotlin.String visibility:private [final] + EXPRESSION_BODY + GET_VAR 'street: kotlin.String declared in com.javiersc.kotlin.kopy.playground.House.' type=kotlin.String origin=INITIALIZE_PROPERTY_FROM_PARAMETER + FUN DEFAULT_PROPERTY_ACCESSOR name: visibility:public modality:FINAL <> ($this:com.javiersc.kotlin.kopy.playground.House) returnType:kotlin.String + correspondingProperty: PROPERTY name:street visibility:public modality:FINAL [val] + $this: VALUE_PARAMETER name: type:com.javiersc.kotlin.kopy.playground.House + BLOCK_BODY + RETURN type=kotlin.Nothing from='public final fun (): kotlin.String declared in com.javiersc.kotlin.kopy.playground.House' + GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:street type:kotlin.String visibility:private [final]' type=kotlin.String origin=null + receiver: GET_VAR ': com.javiersc.kotlin.kopy.playground.House declared in com.javiersc.kotlin.kopy.playground.House.' type=com.javiersc.kotlin.kopy.playground.House origin=null + PROPERTY name:squareMeters visibility:public modality:FINAL [val] + FIELD PROPERTY_BACKING_FIELD name:squareMeters type:kotlin.Int visibility:private [final] + EXPRESSION_BODY + GET_VAR 'squareMeters: kotlin.Int declared in com.javiersc.kotlin.kopy.playground.House.' type=kotlin.Int origin=INITIALIZE_PROPERTY_FROM_PARAMETER + FUN DEFAULT_PROPERTY_ACCESSOR name: visibility:public modality:FINAL <> ($this:com.javiersc.kotlin.kopy.playground.House) returnType:kotlin.Int + correspondingProperty: PROPERTY name:squareMeters visibility:public modality:FINAL [val] + $this: VALUE_PARAMETER name: type:com.javiersc.kotlin.kopy.playground.House + BLOCK_BODY + RETURN type=kotlin.Nothing from='public final fun (): kotlin.Int declared in com.javiersc.kotlin.kopy.playground.House' + GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:squareMeters type:kotlin.Int visibility:private [final]' type=kotlin.Int origin=null + receiver: GET_VAR ': com.javiersc.kotlin.kopy.playground.House declared in com.javiersc.kotlin.kopy.playground.House.' type=com.javiersc.kotlin.kopy.playground.House origin=null + FUN GENERATED_DATA_CLASS_MEMBER name:component1 visibility:public modality:FINAL <> ($this:com.javiersc.kotlin.kopy.playground.House) returnType:kotlin.String [operator] + $this: VALUE_PARAMETER name: type:com.javiersc.kotlin.kopy.playground.House + BLOCK_BODY + RETURN type=kotlin.Nothing from='public final fun component1 (): kotlin.String declared in com.javiersc.kotlin.kopy.playground.House' + GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:street type:kotlin.String visibility:private [final]' type=kotlin.String origin=null + receiver: GET_VAR ': com.javiersc.kotlin.kopy.playground.House declared in com.javiersc.kotlin.kopy.playground.House.component1' type=com.javiersc.kotlin.kopy.playground.House origin=null + FUN GENERATED_DATA_CLASS_MEMBER name:component2 visibility:public modality:FINAL <> ($this:com.javiersc.kotlin.kopy.playground.House) returnType:kotlin.Int [operator] + $this: VALUE_PARAMETER name: type:com.javiersc.kotlin.kopy.playground.House + BLOCK_BODY + RETURN type=kotlin.Nothing from='public final fun component2 (): kotlin.Int declared in com.javiersc.kotlin.kopy.playground.House' + GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:squareMeters type:kotlin.Int visibility:private [final]' type=kotlin.Int origin=null + receiver: GET_VAR ': com.javiersc.kotlin.kopy.playground.House declared in com.javiersc.kotlin.kopy.playground.House.component2' type=com.javiersc.kotlin.kopy.playground.House origin=null + FUN GENERATED_DATA_CLASS_MEMBER name:copy visibility:public modality:FINAL <> ($this:com.javiersc.kotlin.kopy.playground.House, street:kotlin.String, squareMeters:kotlin.Int) returnType:com.javiersc.kotlin.kopy.playground.House + $this: VALUE_PARAMETER name: type:com.javiersc.kotlin.kopy.playground.House + VALUE_PARAMETER name:street index:0 type:kotlin.String + EXPRESSION_BODY + GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:street type:kotlin.String visibility:private [final]' type=kotlin.String origin=null + receiver: GET_VAR ': com.javiersc.kotlin.kopy.playground.House declared in com.javiersc.kotlin.kopy.playground.House.copy' type=com.javiersc.kotlin.kopy.playground.House origin=null + VALUE_PARAMETER name:squareMeters index:1 type:kotlin.Int + EXPRESSION_BODY + GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:squareMeters type:kotlin.Int visibility:private [final]' type=kotlin.Int origin=null + receiver: GET_VAR ': com.javiersc.kotlin.kopy.playground.House declared in com.javiersc.kotlin.kopy.playground.House.copy' type=com.javiersc.kotlin.kopy.playground.House origin=null + BLOCK_BODY + RETURN type=kotlin.Nothing from='public final fun copy (street: kotlin.String, squareMeters: kotlin.Int): com.javiersc.kotlin.kopy.playground.House declared in com.javiersc.kotlin.kopy.playground.House' + CONSTRUCTOR_CALL 'public constructor (street: kotlin.String, squareMeters: kotlin.Int) declared in com.javiersc.kotlin.kopy.playground.House' type=com.javiersc.kotlin.kopy.playground.House origin=null + street: GET_VAR 'street: kotlin.String declared in com.javiersc.kotlin.kopy.playground.House.copy' type=kotlin.String origin=null + squareMeters: GET_VAR 'squareMeters: kotlin.Int declared in com.javiersc.kotlin.kopy.playground.House.copy' type=kotlin.Int origin=null + FUN GENERATED_DATA_CLASS_MEMBER name:toString visibility:public modality:OPEN <> ($this:com.javiersc.kotlin.kopy.playground.House) returnType:kotlin.String + overridden: + public open fun toString (): kotlin.String declared in kotlin.Any + $this: VALUE_PARAMETER name: type:com.javiersc.kotlin.kopy.playground.House + BLOCK_BODY + RETURN type=kotlin.Nothing from='public open fun toString (): kotlin.String declared in com.javiersc.kotlin.kopy.playground.House' + STRING_CONCATENATION type=kotlin.String + CONST String type=kotlin.String value="House(" + CONST String type=kotlin.String value="street=" + GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:street type:kotlin.String visibility:private [final]' type=kotlin.String origin=null + receiver: GET_VAR ': com.javiersc.kotlin.kopy.playground.House declared in com.javiersc.kotlin.kopy.playground.House.toString' type=com.javiersc.kotlin.kopy.playground.House origin=null + CONST String type=kotlin.String value=", " + CONST String type=kotlin.String value="squareMeters=" + GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:squareMeters type:kotlin.Int visibility:private [final]' type=kotlin.Int origin=null + receiver: GET_VAR ': com.javiersc.kotlin.kopy.playground.House declared in com.javiersc.kotlin.kopy.playground.House.toString' type=com.javiersc.kotlin.kopy.playground.House origin=null + CONST String type=kotlin.String value=")" + FUN GENERATED_DATA_CLASS_MEMBER name:hashCode visibility:public modality:OPEN <> ($this:com.javiersc.kotlin.kopy.playground.House) returnType:kotlin.Int + overridden: + public open fun hashCode (): kotlin.Int declared in kotlin.Any + $this: VALUE_PARAMETER name: type:com.javiersc.kotlin.kopy.playground.House + BLOCK_BODY + VAR name:result type:kotlin.Int [var] + CALL 'public open fun hashCode (): kotlin.Int declared in kotlin.String' type=kotlin.Int origin=null + $this: GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:street type:kotlin.String visibility:private [final]' type=kotlin.String origin=null + receiver: GET_VAR ': com.javiersc.kotlin.kopy.playground.House declared in com.javiersc.kotlin.kopy.playground.House.hashCode' type=com.javiersc.kotlin.kopy.playground.House origin=null + SET_VAR 'var result: kotlin.Int declared in com.javiersc.kotlin.kopy.playground.House.hashCode' type=kotlin.Unit origin=EQ + CALL 'public final fun plus (other: kotlin.Int): kotlin.Int declared in kotlin.Int' type=kotlin.Int origin=null + $this: CALL 'public final fun times (other: kotlin.Int): kotlin.Int declared in kotlin.Int' type=kotlin.Int origin=null + $this: GET_VAR 'var result: kotlin.Int declared in com.javiersc.kotlin.kopy.playground.House.hashCode' type=kotlin.Int origin=null + other: CONST Int type=kotlin.Int value=31 + other: CALL 'public open fun hashCode (): kotlin.Int declared in kotlin.Int' type=kotlin.Int origin=null + $this: GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:squareMeters type:kotlin.Int visibility:private [final]' type=kotlin.Int origin=null + receiver: GET_VAR ': com.javiersc.kotlin.kopy.playground.House declared in com.javiersc.kotlin.kopy.playground.House.hashCode' type=com.javiersc.kotlin.kopy.playground.House origin=null + RETURN type=kotlin.Nothing from='public open fun hashCode (): kotlin.Int declared in com.javiersc.kotlin.kopy.playground.House' + GET_VAR 'var result: kotlin.Int declared in com.javiersc.kotlin.kopy.playground.House.hashCode' type=kotlin.Int origin=null + FUN GENERATED_DATA_CLASS_MEMBER name:equals visibility:public modality:OPEN <> ($this:com.javiersc.kotlin.kopy.playground.House, other:kotlin.Any?) returnType:kotlin.Boolean [operator] + overridden: + public open fun equals (other: kotlin.Any?): kotlin.Boolean declared in kotlin.Any + $this: VALUE_PARAMETER name: type:com.javiersc.kotlin.kopy.playground.House + VALUE_PARAMETER name:other index:0 type:kotlin.Any? + BLOCK_BODY + WHEN type=kotlin.Unit origin=null + BRANCH + if: CALL 'public final fun EQEQEQ (arg0: kotlin.Any?, arg1: kotlin.Any?): kotlin.Boolean declared in kotlin.internal.ir' type=kotlin.Boolean origin=EQEQEQ + arg0: GET_VAR ': com.javiersc.kotlin.kopy.playground.House declared in com.javiersc.kotlin.kopy.playground.House.equals' type=com.javiersc.kotlin.kopy.playground.House origin=null + arg1: GET_VAR 'other: kotlin.Any? declared in com.javiersc.kotlin.kopy.playground.House.equals' type=kotlin.Any? origin=null + then: RETURN type=kotlin.Nothing from='public open fun equals (other: kotlin.Any?): kotlin.Boolean declared in com.javiersc.kotlin.kopy.playground.House' + CONST Boolean type=kotlin.Boolean value=true + WHEN type=kotlin.Unit origin=null + BRANCH + if: TYPE_OP type=kotlin.Boolean origin=NOT_INSTANCEOF typeOperand=com.javiersc.kotlin.kopy.playground.House + GET_VAR 'other: kotlin.Any? declared in com.javiersc.kotlin.kopy.playground.House.equals' type=kotlin.Any? origin=null + then: RETURN type=kotlin.Nothing from='public open fun equals (other: kotlin.Any?): kotlin.Boolean declared in com.javiersc.kotlin.kopy.playground.House' + CONST Boolean type=kotlin.Boolean value=false + VAR IR_TEMPORARY_VARIABLE name:tmp_1 type:com.javiersc.kotlin.kopy.playground.House [val] + TYPE_OP type=com.javiersc.kotlin.kopy.playground.House origin=CAST typeOperand=com.javiersc.kotlin.kopy.playground.House + GET_VAR 'other: kotlin.Any? declared in com.javiersc.kotlin.kopy.playground.House.equals' type=kotlin.Any? origin=null + WHEN type=kotlin.Unit origin=null + BRANCH + if: CALL 'public final fun not (): kotlin.Boolean declared in kotlin.Boolean' type=kotlin.Boolean origin=EXCLEQ + $this: CALL 'public final fun EQEQ (arg0: kotlin.Any?, arg1: kotlin.Any?): kotlin.Boolean declared in kotlin.internal.ir' type=kotlin.Boolean origin=EXCLEQ + arg0: GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:street type:kotlin.String visibility:private [final]' type=kotlin.String origin=null + receiver: GET_VAR ': com.javiersc.kotlin.kopy.playground.House declared in com.javiersc.kotlin.kopy.playground.House.equals' type=com.javiersc.kotlin.kopy.playground.House origin=null + arg1: GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:street type:kotlin.String visibility:private [final]' type=kotlin.String origin=null + receiver: GET_VAR 'val tmp_1: com.javiersc.kotlin.kopy.playground.House declared in com.javiersc.kotlin.kopy.playground.House.equals' type=com.javiersc.kotlin.kopy.playground.House origin=null + then: RETURN type=kotlin.Nothing from='public open fun equals (other: kotlin.Any?): kotlin.Boolean declared in com.javiersc.kotlin.kopy.playground.House' + CONST Boolean type=kotlin.Boolean value=false + WHEN type=kotlin.Unit origin=null + BRANCH + if: CALL 'public final fun not (): kotlin.Boolean declared in kotlin.Boolean' type=kotlin.Boolean origin=EXCLEQ + $this: CALL 'public final fun EQEQ (arg0: kotlin.Any?, arg1: kotlin.Any?): kotlin.Boolean declared in kotlin.internal.ir' type=kotlin.Boolean origin=EXCLEQ + arg0: GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:squareMeters type:kotlin.Int visibility:private [final]' type=kotlin.Int origin=null + receiver: GET_VAR ': com.javiersc.kotlin.kopy.playground.House declared in com.javiersc.kotlin.kopy.playground.House.equals' type=com.javiersc.kotlin.kopy.playground.House origin=null + arg1: GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:squareMeters type:kotlin.Int visibility:private [final]' type=kotlin.Int origin=null + receiver: GET_VAR 'val tmp_1: com.javiersc.kotlin.kopy.playground.House declared in com.javiersc.kotlin.kopy.playground.House.equals' type=com.javiersc.kotlin.kopy.playground.House origin=null + then: RETURN type=kotlin.Nothing from='public open fun equals (other: kotlin.Any?): kotlin.Boolean declared in com.javiersc.kotlin.kopy.playground.House' + CONST Boolean type=kotlin.Boolean value=false + RETURN type=kotlin.Nothing from='public open fun equals (other: kotlin.Any?): kotlin.Boolean declared in com.javiersc.kotlin.kopy.playground.House' + CONST Boolean type=kotlin.Boolean value=true + FUN FAKE_OVERRIDE name:copy visibility:public modality:OPEN <> ($this:com.javiersc.kotlin.kopy.runtime.Kopyable, kopy:@[ExtensionFunctionType] kotlin.Function1) returnType:com.javiersc.kotlin.kopy.playground.House [fake_override,infix] + annotations: + KopyFunctionInvoke + overridden: + public open fun copy (kopy: @[ExtensionFunctionType] kotlin.Function1): T of com.javiersc.kotlin.kopy.runtime.Kopyable declared in com.javiersc.kotlin.kopy.runtime.Kopyable + $this: VALUE_PARAMETER name: type:com.javiersc.kotlin.kopy.runtime.Kopyable + VALUE_PARAMETER name:kopy index:0 type:@[ExtensionFunctionType] kotlin.Function1 + FUN FAKE_OVERRIDE name:invoke visibility:public modality:OPEN <> ($this:com.javiersc.kotlin.kopy.runtime.Kopyable, kopy:@[ExtensionFunctionType] kotlin.Function1) returnType:com.javiersc.kotlin.kopy.playground.House [fake_override,operator,infix] + annotations: + KopyFunctionInvoke + overridden: + public open fun invoke (kopy: @[ExtensionFunctionType] kotlin.Function1): T of com.javiersc.kotlin.kopy.runtime.Kopyable declared in com.javiersc.kotlin.kopy.runtime.Kopyable + $this: VALUE_PARAMETER name: type:com.javiersc.kotlin.kopy.runtime.Kopyable + VALUE_PARAMETER name:kopy index:0 type:@[ExtensionFunctionType] kotlin.Function1 + FUN FAKE_OVERRIDE name:set visibility:public modality:OPEN ($this:com.javiersc.kotlin.kopy.runtime.Kopyable, $receiver:R of com.javiersc.kotlin.kopy.playground.House.set, other:R of com.javiersc.kotlin.kopy.playground.House.set) returnType:R of com.javiersc.kotlin.kopy.playground.House.set [fake_override,infix] + annotations: + KopyFunctionSet + overridden: + public open fun set (other: R of com.javiersc.kotlin.kopy.runtime.Kopyable.set): R of com.javiersc.kotlin.kopy.runtime.Kopyable.set declared in com.javiersc.kotlin.kopy.runtime.Kopyable + TYPE_PARAMETER name:R index:0 variance: superTypes:[kotlin.Any?] reified:false + $this: VALUE_PARAMETER name: type:com.javiersc.kotlin.kopy.runtime.Kopyable + $receiver: VALUE_PARAMETER name: type:R of com.javiersc.kotlin.kopy.playground.House.set + VALUE_PARAMETER name:other index:0 type:R of com.javiersc.kotlin.kopy.playground.House.set + FUN FAKE_OVERRIDE name:update visibility:public modality:OPEN ($this:com.javiersc.kotlin.kopy.runtime.Kopyable, $receiver:R of com.javiersc.kotlin.kopy.playground.House.update, other:kotlin.Function1) returnType:R of com.javiersc.kotlin.kopy.playground.House.update [fake_override,infix] + annotations: + KopyFunctionUpdate + overridden: + public open fun update (other: kotlin.Function1): R of com.javiersc.kotlin.kopy.runtime.Kopyable.update declared in com.javiersc.kotlin.kopy.runtime.Kopyable + TYPE_PARAMETER name:R index:0 variance: superTypes:[kotlin.Any?] reified:false + $this: VALUE_PARAMETER name: type:com.javiersc.kotlin.kopy.runtime.Kopyable + $receiver: VALUE_PARAMETER name: type:R of com.javiersc.kotlin.kopy.playground.House.update + VALUE_PARAMETER name:other index:0 type:kotlin.Function1 +FILE fqName: fileName:/SomeFile.kt diff --git a/kopy-compiler/test-data/box/no-nest-update-no-it.fir.txt b/kopy-compiler/test-data/box/no-nest-update-no-it.fir.txt new file mode 100644 index 0000000..75193a9 --- /dev/null +++ b/kopy-compiler/test-data/box/no-nest-update-no-it.fir.txt @@ -0,0 +1,49 @@ +FILE: no-nest-update-no-it.kt + package com.javiersc.kotlin.kopy.playground + + public final fun box(): R|kotlin/String| { + lval house0: R|com/javiersc/kotlin/kopy/playground/House| = R|com/javiersc/kotlin/kopy/playground/House.House|(street = String(Street), squareMeters = Int(20)) + lval house1: R|com/javiersc/kotlin/kopy/playground/House| = R|/house0|.R|SubstitutionOverride|( = house0@fun R|com/javiersc/kotlin/kopy/playground/House|.(): R|kotlin/Unit| { + (this@R|special/anonymous|, this@R|special/anonymous|.R|com/javiersc/kotlin/kopy/playground/House.squareMeters|).R|SubstitutionOverride|( = update@fun (it: R|kotlin/Int|): R|kotlin/Int| { + ^ Int(40) + } + ) + } + ) + lval house2: R|com/javiersc/kotlin/kopy/playground/House| = R|/house0|.R|kotlin/run|( = run@fun R|com/javiersc/kotlin/kopy/playground/House|.(): R|com/javiersc/kotlin/kopy/playground/House| { + lvar tmp0: R|com/javiersc/kotlin/kopy/playground/House| = this@R|special/anonymous| + R|/tmp0| = R|/tmp0|.R|com/javiersc/kotlin/kopy/playground/House.copy|(squareMeters = R|/tmp0|.R|com/javiersc/kotlin/kopy/playground/House.squareMeters|.R|kotlin/let|( = let@fun (it: R|kotlin/Int|): R|kotlin/Int| { + ^ Int(40) + } + )) + ^ R|/tmp0| + } + ) + ^box when () { + ==(R|/house1|, R|/house2|) -> { + String(OK) + } + else -> { + (String(Fail: ), Char(10), String(House1: ), R|/house1|, String( ), Char(10), String(House2: ), R|/house2|) + } + } + + } + @R|com/javiersc/kotlin/kopy/Kopy|() internal final data class House : R|kotlin/Any|, R|com/javiersc/kotlin/kopy/runtime/Kopyable| { + public constructor(street: R|kotlin/String|, squareMeters: R|kotlin/Int|): R|com/javiersc/kotlin/kopy/playground/House| { + super() + } + + public final val street: R|kotlin/String| = R|/street| + public get(): R|kotlin/String| + + public final val squareMeters: R|kotlin/Int| = R|/squareMeters| + public get(): R|kotlin/Int| + + public final operator fun component1(): R|kotlin/String| + + public final operator fun component2(): R|kotlin/Int| + + public final fun copy(street: R|kotlin/String| = this@R|com/javiersc/kotlin/kopy/playground/House|.R|com/javiersc/kotlin/kopy/playground/House.street|, squareMeters: R|kotlin/Int| = this@R|com/javiersc/kotlin/kopy/playground/House|.R|com/javiersc/kotlin/kopy/playground/House.squareMeters|): R|com/javiersc/kotlin/kopy/playground/House| + + } diff --git a/kopy-compiler/test-data/box/no-nest-update-no-it.kt b/kopy-compiler/test-data/box/no-nest-update-no-it.kt new file mode 100644 index 0000000..1d213c3 --- /dev/null +++ b/kopy-compiler/test-data/box/no-nest-update-no-it.kt @@ -0,0 +1,17 @@ +package com.javiersc.kotlin.kopy.playground + +import com.javiersc.kotlin.kopy.Kopy + +fun box(): String { + val house0 = House(street = "Street", squareMeters = 20) + val house1 = house0 { squareMeters.update { 40 } } + val house2 = + house0.run { + var tmp0 = this + tmp0 = tmp0.copy(squareMeters = tmp0.squareMeters.let { 40 }) + tmp0 + } + return if (house1 == house2) "OK" else "Fail: \nHouse1: $house1 \nHouse2: $house2" +} + +@Kopy internal data class House(val street: String, val squareMeters: Int) diff --git a/kopy-compiler/test-gen/java/com/javiersc/kotlin/kopy/compiler/BoxTestGenerated.java b/kopy-compiler/test-gen/java/com/javiersc/kotlin/kopy/compiler/BoxTestGenerated.java index ad091fe..5e99b6e 100644 --- a/kopy-compiler/test-gen/java/com/javiersc/kotlin/kopy/compiler/BoxTestGenerated.java +++ b/kopy-compiler/test-gen/java/com/javiersc/kotlin/kopy/compiler/BoxTestGenerated.java @@ -21,4 +21,10 @@ public class BoxTestGenerated extends AbstractBoxTest { public void testAllFilesPresentInBox() throws Exception { KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("test-data/box"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true); } + + @Test + @TestMetadata("no-nest-update-no-it.kt") + public void testNo_nest_update_no_it() throws Exception { + runTest("test-data/box/no-nest-update-no-it.kt"); + } } diff --git a/kopy-gradle-plugin/main/kotlin/com/javiersc/kotlin/kopy/gradle/plugin/KopyGradlePlugin.kt b/kopy-gradle-plugin/main/kotlin/com/javiersc/kotlin/kopy/gradle/plugin/KopyGradlePlugin.kt index 21f8b4e..f393fed 100644 --- a/kopy-gradle-plugin/main/kotlin/com/javiersc/kotlin/kopy/gradle/plugin/KopyGradlePlugin.kt +++ b/kopy-gradle-plugin/main/kotlin/com/javiersc/kotlin/kopy/gradle/plugin/KopyGradlePlugin.kt @@ -1,6 +1,6 @@ package com.javiersc.kotlin.kopy.gradle.plugin -import com.javiersc.kotlin.kopy.kopy.compiler.KopyCompilerProjectData +import com.javiersc.kotlin.kopy.compiler.KopyCompilerProjectData import javax.inject.Inject import org.gradle.api.Project import org.gradle.api.provider.Provider diff --git a/kopy-runtime/api/library.api b/kopy-runtime/api/library.api deleted file mode 100644 index c211e1e..0000000 --- a/kopy-runtime/api/library.api +++ /dev/null @@ -1,4 +0,0 @@ -public final class Empty { - public fun ()V -} - diff --git a/kopy-runtime/build.gradle.kts b/kopy-runtime/build.gradle.kts index c35896b..125faab 100644 --- a/kopy-runtime/build.gradle.kts +++ b/kopy-runtime/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + hubdle { config { analysis() @@ -9,8 +11,10 @@ hubdle { publishing() } kotlin { - multiplatform { // - jvm() + jvm { + features { + contextReceivers() + } } } } diff --git a/kopy-runtime/main/kotlin/com/javiersc/kotlin/kopy/Kopy.kt b/kopy-runtime/main/kotlin/com/javiersc/kotlin/kopy/Kopy.kt index 1dedce0..ad641d9 100644 --- a/kopy-runtime/main/kotlin/com/javiersc/kotlin/kopy/Kopy.kt +++ b/kopy-runtime/main/kotlin/com/javiersc/kotlin/kopy/Kopy.kt @@ -6,3 +6,24 @@ public annotation class Kopy { public companion object } + +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.BINARY) +public annotation class KopyFunctionInvoke { + + public companion object +} + +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.BINARY) +public annotation class KopyFunctionSet { + + public companion object +} + +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.BINARY) +public annotation class KopyFunctionUpdate { + + public companion object +} diff --git a/kopy-runtime/main/kotlin/com/javiersc/kotlin/kopy/runtime/Kopy.kt b/kopy-runtime/main/kotlin/com/javiersc/kotlin/kopy/runtime/Kopy.kt deleted file mode 100644 index a03564c..0000000 --- a/kopy-runtime/main/kotlin/com/javiersc/kotlin/kopy/runtime/Kopy.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.javiersc.kotlin.kopy.runtime - -public interface Kopy { - - public infix operator fun invoke(kopy: T.() -> Unit): T = TODO(error) - - public infix fun R.set(other: R): R = TODO(error) - - public infix fun R.set(other: (R) -> R): R = TODO(error) -} - -private const val error = "Kopy plugin is not applied" diff --git a/kopy-runtime/main/kotlin/com/javiersc/kotlin/kopy/runtime/Kopyable.kt b/kopy-runtime/main/kotlin/com/javiersc/kotlin/kopy/runtime/Kopyable.kt new file mode 100644 index 0000000..de0a270 --- /dev/null +++ b/kopy-runtime/main/kotlin/com/javiersc/kotlin/kopy/runtime/Kopyable.kt @@ -0,0 +1,27 @@ +@file:Suppress("DEPRECATION_ERROR") + +package com.javiersc.kotlin.kopy.runtime + +import com.javiersc.kotlin.kopy.KopyFunctionInvoke +import com.javiersc.kotlin.kopy.KopyFunctionSet +import com.javiersc.kotlin.kopy.KopyFunctionUpdate + +public interface Kopyable { + + @KopyFunctionInvoke + public infix operator fun invoke(kopy: /*context(KopyableScope)*/ T.() -> Unit): T = TODO(error) + + @KopyFunctionInvoke + public infix fun copy(kopy: /*context(KopyableScope)*/ T.() -> Unit): T = TODO(error) + + // context(KopyableScope) + @KopyFunctionSet public infix fun R.set(other: R): R = TODO(error) + + // context(KopyableScope) + @KopyFunctionUpdate public infix fun R.update(other: (R) -> R): R = TODO(error) + + @Deprecated("This is deprecated", level = DeprecationLevel.ERROR) + public interface KopyableScope +} + +private const val error = "Kopy plugin is not applied"