diff --git a/.idea/kotlinTestDataPluginTestDataPaths.xml b/.idea/kotlinTestDataPluginTestDataPaths.xml
index c38fdcaca2e..e53d4f9cf50 100644
--- a/.idea/kotlinTestDataPluginTestDataPaths.xml
+++ b/.idea/kotlinTestDataPluginTestDataPaths.xml
@@ -84,7 +84,8 @@
+
-
\ No newline at end of file
+
diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt
index 7ffa0f3abc2..b87ad9a4789 100644
--- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt
+++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt
@@ -27,7 +27,10 @@ import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.io.FileUtilRt
import com.intellij.openapi.util.text.StringUtil
-import com.intellij.openapi.vfs.*
+import com.intellij.openapi.vfs.PersistentFSConstants
+import com.intellij.openapi.vfs.VfsUtilCore
+import com.intellij.openapi.vfs.VirtualFile
+import com.intellij.openapi.vfs.VirtualFileSystem
import com.intellij.openapi.vfs.impl.ZipHandler
import com.intellij.psi.PsiElementFinder
import com.intellij.psi.PsiManager
@@ -44,7 +47,9 @@ import org.jetbrains.kotlin.asJava.KotlinAsJavaSupport
import org.jetbrains.kotlin.asJava.LightClassGenerationSupport
import org.jetbrains.kotlin.asJava.finder.JavaElementFinder
import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
-import org.jetbrains.kotlin.cli.common.*
+import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
+import org.jetbrains.kotlin.cli.common.CliModuleVisibilityManagerImpl
+import org.jetbrains.kotlin.cli.common.CompilerSystemProperties
import org.jetbrains.kotlin.cli.common.config.ContentRoot
import org.jetbrains.kotlin.cli.common.config.KotlinSourceRoot
import org.jetbrains.kotlin.cli.common.config.kotlinSourceRoots
@@ -54,6 +59,7 @@ import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.ERROR
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.STRONG_WARNING
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
+import org.jetbrains.kotlin.cli.common.toBooleanLenient
import org.jetbrains.kotlin.cli.jvm.compiler.jarfs.FastJarFileSystem
import org.jetbrains.kotlin.cli.jvm.config.*
import org.jetbrains.kotlin.cli.jvm.index.*
diff --git a/generators/build.gradle.kts b/generators/build.gradle.kts
index 5a7e7f38261..9f7f4189731 100644
--- a/generators/build.gradle.kts
+++ b/generators/build.gradle.kts
@@ -66,6 +66,7 @@ dependencies {
testApi(projectTests(":kotlin-noarg-compiler-plugin"))
testApi(projectTests(":kotlin-lombok-compiler-plugin"))
testApi(projectTests(":kotlin-sam-with-receiver-compiler-plugin"))
+ testApi(projectTests(":kotlin-assignment-compiler-plugin"))
testApi(projectTests(":kotlinx-serialization-compiler-plugin"))
testApi(projectTests(":kotlinx-atomicfu-compiler-plugin"))
testApi(projectTests(":plugins:fir-plugin-prototype"))
diff --git a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt
index db9de556f13..cd15cb9b75b 100644
--- a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt
+++ b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt
@@ -16,6 +16,10 @@ import org.jetbrains.kotlin.android.synthetic.test.AbstractAndroidBoxTest
import org.jetbrains.kotlin.android.synthetic.test.AbstractAndroidBytecodeShapeTest
import org.jetbrains.kotlin.android.synthetic.test.AbstractAndroidIrBoxTest
import org.jetbrains.kotlin.android.synthetic.test.AbstractAndroidSyntheticPropertyDescriptorTest
+import org.jetbrains.kotlin.assignment.plugin.AbstractFirBlackBoxCodegenTestForAssignmentPlugin
+import org.jetbrains.kotlin.assignment.plugin.AbstractFirAssignmentPluginDiagnosticTest
+import org.jetbrains.kotlin.assignment.plugin.AbstractIrBlackBoxCodegenTestAssignmentPlugin
+import org.jetbrains.kotlin.assignment.plugin.AbstractAssignmentPluginDiagnosticTest
import org.jetbrains.kotlin.fir.plugin.runners.AbstractFirPluginBlackBoxCodegenTest
import org.jetbrains.kotlin.fir.plugin.runners.AbstractFirPluginDiagnosticTest
import org.jetbrains.kotlin.generators.TestGroup
@@ -361,5 +365,19 @@ fun main(args: Array) {
}
}
+ testGroup("plugins/assign-plugin/tests-gen", "plugins/assign-plugin/testData") {
+ testClass {
+ model("diagnostics", excludedPattern = excludedFirTestdataPattern)
+ }
+ testClass {
+ model("diagnostics", excludedPattern = excludedFirTestdataPattern)
+ }
+ testClass {
+ model("codegen", excludedPattern = excludedFirTestdataPattern)
+ }
+ testClass {
+ model("codegen", excludedPattern = excludedFirTestdataPattern)
+ }
+ }
}
}
diff --git a/gradle/jps.gradle.kts b/gradle/jps.gradle.kts
index ad2bebb8391..a7d281710a8 100644
--- a/gradle/jps.gradle.kts
+++ b/gradle/jps.gradle.kts
@@ -223,7 +223,7 @@ if (kotlinBuildProperties.isInJpsBuildIdeaSync) {
inheritOutputDirs = true
}
}
-
+
if (this != rootProject) {
evaluationDependsOn(path)
}
@@ -418,7 +418,7 @@ fun NamedDomainObjectContainer.kotlinc() {
directory("license") {
directoryContent("$rootDir/license")
}
-
+
file("$rootDir/bootstrap/build.txt")
}
}
@@ -520,7 +520,7 @@ fun RecursiveArtifact.sourceJarsFromConfiguration(configuration: Configuration,
.resolvedArtifacts
jarsFromExternalModules(resolvedArtifacts, renamer)
-
+
resolvedArtifacts
.map { it.id.componentIdentifier }
.filterIsInstance()
diff --git a/js/js.translator/testData/typescript-export/js-name-in-exported-file/js-name.kt b/js/js.translator/testData/typescript-export/js-name-in-exported-file/js-name.kt
index 872f56c11fc..1caada9acbb 100644
--- a/js/js.translator/testData/typescript-export/js-name-in-exported-file/js-name.kt
+++ b/js/js.translator/testData/typescript-export/js-name-in-exported-file/js-name.kt
@@ -12,6 +12,7 @@
package foo
+
@JsName("Object")
external interface WeirdInterface {
val constructor: dynamic
diff --git a/plugins/assign-plugin/assign-plugin.cli/build.gradle.kts b/plugins/assign-plugin/assign-plugin.cli/build.gradle.kts
new file mode 100644
index 00000000000..434cfd03cf3
--- /dev/null
+++ b/plugins/assign-plugin/assign-plugin.cli/build.gradle.kts
@@ -0,0 +1,27 @@
+description = "Kotlin Assignment Compiler Plugin (CLI)"
+
+plugins {
+ kotlin("jvm")
+ id("jps-compatible")
+}
+
+dependencies {
+ api(project(":kotlin-assignment-compiler-plugin.common"))
+ api(project(":kotlin-assignment-compiler-plugin.k1"))
+ api(project(":kotlin-assignment-compiler-plugin.k2"))
+ compileOnly(project(":compiler:util"))
+ compileOnly(project(":compiler:plugin-api"))
+ compileOnly(project(":compiler:fir:entrypoint"))
+ compileOnly(intellijCore())
+}
+
+optInToExperimentalCompilerApi()
+
+sourceSets {
+ "main" { projectDefault() }
+ "test" { none() }
+}
+
+runtimeJar()
+sourcesJar()
+javadocJar()
diff --git a/plugins/assign-plugin/assign-plugin.cli/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor b/plugins/assign-plugin/assign-plugin.cli/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor
new file mode 100644
index 00000000000..3022018d2b6
--- /dev/null
+++ b/plugins/assign-plugin/assign-plugin.cli/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor
@@ -0,0 +1 @@
+org.jetbrains.kotlin.assignment.plugin.AssignmentCommandLineProcessor
diff --git a/plugins/assign-plugin/assign-plugin.cli/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar b/plugins/assign-plugin/assign-plugin.cli/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar
new file mode 100644
index 00000000000..75a63012120
--- /dev/null
+++ b/plugins/assign-plugin/assign-plugin.cli/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar
@@ -0,0 +1 @@
+org.jetbrains.kotlin.assignment.plugin.AssignmentComponentRegistrar
diff --git a/plugins/assign-plugin/assign-plugin.cli/src/org/jetbrains/kotlin/assignment/plugin/ValueContainerAssignmentPlugin.kt b/plugins/assign-plugin/assign-plugin.cli/src/org/jetbrains/kotlin/assignment/plugin/ValueContainerAssignmentPlugin.kt
new file mode 100644
index 00000000000..2e49cf220ab
--- /dev/null
+++ b/plugins/assign-plugin/assign-plugin.cli/src/org/jetbrains/kotlin/assignment/plugin/ValueContainerAssignmentPlugin.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+package org.jetbrains.kotlin.assignment.plugin
+
+import org.jetbrains.kotlin.compiler.plugin.*
+import org.jetbrains.kotlin.config.CompilerConfiguration
+import org.jetbrains.kotlin.config.CompilerConfigurationKey
+import org.jetbrains.kotlin.container.StorageComponentContainer
+import org.jetbrains.kotlin.assignment.plugin.AssignmentConfigurationKeys.ANNOTATION
+import org.jetbrains.kotlin.assignment.plugin.AssignmentPluginNames.ANNOTATION_OPTION_NAME
+import org.jetbrains.kotlin.assignment.plugin.AssignmentPluginNames.PLUGIN_ID
+import org.jetbrains.kotlin.assignment.plugin.diagnostics.AssignmentPluginDeclarationChecker
+import org.jetbrains.kotlin.assignment.plugin.k2.FirAssignmentPluginExtensionRegistrar
+import org.jetbrains.kotlin.container.useInstance
+import org.jetbrains.kotlin.descriptors.ModuleDescriptor
+import org.jetbrains.kotlin.extensions.StorageComponentContainerContributor
+import org.jetbrains.kotlin.extensions.internal.InternalNonStableExtensionPoints
+import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrarAdapter
+import org.jetbrains.kotlin.platform.TargetPlatform
+import org.jetbrains.kotlin.resolve.extensions.AssignResolutionAltererExtension
+
+object AssignmentConfigurationKeys {
+ val ANNOTATION: CompilerConfigurationKey> = CompilerConfigurationKey.create("annotation qualified name")
+}
+
+class AssignmentCommandLineProcessor : CommandLineProcessor {
+ companion object {
+ val ANNOTATION_OPTION = CliOption(
+ ANNOTATION_OPTION_NAME, "", "Annotation qualified names",
+ required = false, allowMultipleOccurrences = true
+ )
+ }
+
+ override val pluginId = PLUGIN_ID
+ override val pluginOptions = listOf(ANNOTATION_OPTION)
+
+ override fun processOption(option: AbstractCliOption, value: String, configuration: CompilerConfiguration) = when (option) {
+ ANNOTATION_OPTION -> configuration.appendList(ANNOTATION, value)
+ else -> throw CliOptionProcessingException("Unknown option: ${option.optionName}")
+ }
+}
+
+class AssignmentComponentRegistrar : CompilerPluginRegistrar() {
+ @OptIn(InternalNonStableExtensionPoints::class)
+ override fun ExtensionStorage.registerExtensions(configuration: CompilerConfiguration) {
+ val annotations = configuration.getList(ANNOTATION)
+ if (annotations.isNotEmpty()) {
+ AssignResolutionAltererExtension.Companion.registerExtension(CliAssignPluginResolutionAltererExtension(annotations))
+ StorageComponentContainerContributor.registerExtension(AssignmentComponentContainerContributor(annotations))
+ FirExtensionRegistrarAdapter.registerExtension(FirAssignmentPluginExtensionRegistrar(annotations))
+ }
+ }
+
+ override val supportsK2: Boolean
+ get() = true
+}
+
+class AssignmentComponentContainerContributor(private val annotations: List) : StorageComponentContainerContributor {
+ override fun registerModuleComponents(
+ container: StorageComponentContainer,
+ platform: TargetPlatform,
+ moduleDescriptor: ModuleDescriptor,
+ ) {
+ container.useInstance(AssignmentPluginDeclarationChecker(annotations))
+ }
+}
diff --git a/plugins/assign-plugin/assign-plugin.common/build.gradle.kts b/plugins/assign-plugin/assign-plugin.common/build.gradle.kts
new file mode 100644
index 00000000000..6b40d4dde1e
--- /dev/null
+++ b/plugins/assign-plugin/assign-plugin.common/build.gradle.kts
@@ -0,0 +1,20 @@
+description = "Kotlin Assignment Compiler Plugin (Common)"
+
+plugins {
+ kotlin("jvm")
+ id("jps-compatible")
+}
+
+dependencies {
+ compileOnly(project(":compiler:util"))
+ compileOnly(project(":core:compiler.common"))
+}
+
+sourceSets {
+ "main" { projectDefault() }
+ "test" { none() }
+}
+
+runtimeJar()
+javadocJar()
+sourcesJar()
diff --git a/plugins/assign-plugin/assign-plugin.common/src/org/jetbrains/kotlin/assignment/plugin/AssignmentPluginNames.kt b/plugins/assign-plugin/assign-plugin.common/src/org/jetbrains/kotlin/assignment/plugin/AssignmentPluginNames.kt
new file mode 100644
index 00000000000..4d3d0a2f719
--- /dev/null
+++ b/plugins/assign-plugin/assign-plugin.common/src/org/jetbrains/kotlin/assignment/plugin/AssignmentPluginNames.kt
@@ -0,0 +1,14 @@
+/*
+ * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+package org.jetbrains.kotlin.assignment.plugin
+
+import org.jetbrains.kotlin.name.Name
+
+object AssignmentPluginNames {
+ const val PLUGIN_ID = "org.jetbrains.kotlin.assignment"
+ const val ANNOTATION_OPTION_NAME = "annotation"
+ val ASSIGN_METHOD = Name.identifier("assign")
+}
diff --git a/plugins/assign-plugin/assign-plugin.k1/build.gradle.kts b/plugins/assign-plugin/assign-plugin.k1/build.gradle.kts
new file mode 100644
index 00000000000..ba5661c9bd2
--- /dev/null
+++ b/plugins/assign-plugin/assign-plugin.k1/build.gradle.kts
@@ -0,0 +1,23 @@
+description = "Kotlin Assignment Compiler Plugin (K1)"
+
+plugins {
+ kotlin("jvm")
+ id("jps-compatible")
+}
+
+dependencies {
+ implementation(project(":kotlin-assignment-compiler-plugin.common"))
+
+ compileOnly(project(":compiler:frontend"))
+ compileOnly(project(":compiler:frontend.java"))
+ compileOnly(intellijCore())
+}
+
+sourceSets {
+ "main" { projectDefault() }
+ "test" { projectDefault() }
+}
+
+runtimeJar()
+sourcesJar()
+javadocJar()
diff --git a/plugins/assign-plugin/assign-plugin.k1/src/org/jetbrains/kotlin/assignment/plugin/ValueContainerAssignResolutionAltererExtension.kt b/plugins/assign-plugin/assign-plugin.k1/src/org/jetbrains/kotlin/assignment/plugin/ValueContainerAssignResolutionAltererExtension.kt
new file mode 100644
index 00000000000..8b4a7209a45
--- /dev/null
+++ b/plugins/assign-plugin/assign-plugin.k1/src/org/jetbrains/kotlin/assignment/plugin/ValueContainerAssignResolutionAltererExtension.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+package org.jetbrains.kotlin.assignment.plugin
+
+import org.jetbrains.kotlin.cfg.getElementParentDeclaration
+import org.jetbrains.kotlin.assignment.plugin.AssignmentPluginNames.ASSIGN_METHOD
+import org.jetbrains.kotlin.assignment.plugin.diagnostics.ErrorsAssignmentPlugin.CALL_ERROR_ASSIGN_METHOD_SHOULD_RETURN_UNIT
+import org.jetbrains.kotlin.assignment.plugin.diagnostics.ErrorsAssignmentPlugin.NO_APPLICABLE_ASSIGN_METHOD
+import org.jetbrains.kotlin.descriptors.FunctionDescriptor
+import org.jetbrains.kotlin.descriptors.PropertyDescriptor
+import org.jetbrains.kotlin.descriptors.VariableDescriptor
+import org.jetbrains.kotlin.extensions.internal.InternalNonStableExtensionPoints
+import org.jetbrains.kotlin.psi.*
+import org.jetbrains.kotlin.resolve.BindingContext
+import org.jetbrains.kotlin.resolve.BindingContextUtils
+import org.jetbrains.kotlin.resolve.calls.CallResolver
+import org.jetbrains.kotlin.resolve.calls.context.TemporaryTraceAndCache
+import org.jetbrains.kotlin.resolve.calls.results.OverloadResolutionResults
+import org.jetbrains.kotlin.resolve.calls.results.OverloadResolutionResultsUtil
+import org.jetbrains.kotlin.resolve.calls.util.CallMaker.makeCallWithExpressions
+import org.jetbrains.kotlin.resolve.extensions.AssignResolutionAltererExtension
+import org.jetbrains.kotlin.resolve.scopes.LexicalWritableScope
+import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
+import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver.Companion.create
+import org.jetbrains.kotlin.types.KotlinType
+import org.jetbrains.kotlin.types.expressions.ExpressionTypingComponents
+import org.jetbrains.kotlin.types.expressions.ExpressionTypingContext
+import org.jetbrains.kotlin.types.expressions.KotlinTypeInfo
+import org.jetbrains.kotlin.types.typeUtil.isUnit
+import org.jetbrains.kotlin.types.typeUtil.makeNotNullable
+import java.util.*
+
+class CliAssignPluginResolutionAltererExtension(
+ private val annotations: List
+) : AbstractAssignPluginResolutionAltererExtension() {
+ override fun getAnnotationFqNames(modifierListOwner: KtModifierListOwner?): List = annotations
+}
+
+@OptIn(InternalNonStableExtensionPoints::class)
+abstract class AbstractAssignPluginResolutionAltererExtension : AssignResolutionAltererExtension {
+
+ override fun needOverloadAssign(expression: KtBinaryExpression, leftType: KotlinType?, bindingContext: BindingContext): Boolean {
+ return expression.isValPropertyAssignment(bindingContext) && leftType.hasSpecialAnnotation(expression)
+ }
+
+ private fun KtBinaryExpression.isValPropertyAssignment(bindingContext: BindingContext): Boolean {
+ val descriptor: VariableDescriptor? = BindingContextUtils.extractVariableFromResolvedCall(bindingContext, this.left)
+ return descriptor is PropertyDescriptor && !descriptor.isVar
+ }
+
+ private fun KotlinType?.hasSpecialAnnotation(expression: KtBinaryExpression): Boolean =
+ this?.constructor?.declarationDescriptor?.hasSpecialAnnotation(expression.getElementParentDeclaration()) ?: false
+
+ override fun resolveAssign(
+ bindingContext: BindingContext,
+ expression: KtBinaryExpression,
+ leftOperand: KtExpression,
+ left: KtExpression,
+ leftInfo: KotlinTypeInfo,
+ context: ExpressionTypingContext,
+ components: ExpressionTypingComponents,
+ scope: LexicalWritableScope
+ ): KotlinTypeInfo? {
+ var leftType: KotlinType = leftInfo.type!!
+ if (leftOperand is KtSafeQualifiedExpression) {
+ leftType = leftType.makeNotNullable()
+ }
+ val receiver = create(left, leftType, context.trace.bindingContext)
+ val operationSign: KtSimpleNameExpression = expression.operationReference
+ val temporaryForAssignmentOperation: TemporaryTraceAndCache =
+ TemporaryTraceAndCache.create(context, "trace to check assignment operation like '=' for", expression)
+ val temporaryContext = context.replaceTraceAndCache(temporaryForAssignmentOperation).replaceScope(scope)
+ val methodDescriptors: OverloadResolutionResults =
+ components.callResolver.resolveMethodCall(temporaryContext, receiver, expression)
+ val methodReturnType: KotlinType? = OverloadResolutionResultsUtil.getResultingType(methodDescriptors, context)
+
+ if (methodDescriptors.isSuccess && methodReturnType != null) {
+ temporaryForAssignmentOperation.commit()
+ return if (methodReturnType.isUnit()) {
+ leftInfo.replaceType(methodReturnType)
+ } else {
+ context.trace.report(CALL_ERROR_ASSIGN_METHOD_SHOULD_RETURN_UNIT.on(operationSign))
+ null
+ }
+ }
+ context.trace.report(NO_APPLICABLE_ASSIGN_METHOD.on(operationSign))
+ return null
+ }
+
+ private fun CallResolver.resolveMethodCall(
+ context: ExpressionTypingContext, receiver: ExpressionReceiver, binaryExpression: KtBinaryExpression
+ ): OverloadResolutionResults {
+ val call = makeCallWithExpressions(
+ binaryExpression, receiver, null, binaryExpression.operationReference, Collections.singletonList(binaryExpression.right)
+ )
+ return resolveCallWithGivenName(context, call, binaryExpression.operationReference, ASSIGN_METHOD)
+ }
+}
diff --git a/plugins/assign-plugin/assign-plugin.k1/src/org/jetbrains/kotlin/assignment/plugin/diagnostics/AssignmentPluginDeclarationChecker.kt b/plugins/assign-plugin/assign-plugin.k1/src/org/jetbrains/kotlin/assignment/plugin/diagnostics/AssignmentPluginDeclarationChecker.kt
new file mode 100644
index 00000000000..dfe85b0fa3d
--- /dev/null
+++ b/plugins/assign-plugin/assign-plugin.k1/src/org/jetbrains/kotlin/assignment/plugin/diagnostics/AssignmentPluginDeclarationChecker.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+package org.jetbrains.kotlin.assignment.plugin.diagnostics
+
+import org.jetbrains.kotlin.builtins.KotlinBuiltIns
+import org.jetbrains.kotlin.assignment.plugin.AssignmentPluginNames.ASSIGN_METHOD
+import org.jetbrains.kotlin.assignment.plugin.diagnostics.ErrorsAssignmentPlugin.DECLARATION_ERROR_ASSIGN_METHOD_SHOULD_RETURN_UNIT
+import org.jetbrains.kotlin.descriptors.ClassDescriptor
+import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
+import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
+import org.jetbrains.kotlin.diagnostics.DiagnosticSink
+import org.jetbrains.kotlin.extensions.AnnotationBasedExtension
+import org.jetbrains.kotlin.psi.KtDeclaration
+import org.jetbrains.kotlin.psi.KtFunction
+import org.jetbrains.kotlin.psi.KtModifierListOwner
+import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker
+import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext
+import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
+
+class AssignmentPluginDeclarationChecker(private val annotations: List) : DeclarationChecker {
+
+ private val annotationMatchingService = AnnotationMatchingService(annotations)
+
+ override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) {
+ if (descriptor is SimpleFunctionDescriptor) {
+ if (!descriptor.isAssignMethod()) return
+ val receiverClass = if (descriptor.isExtension) {
+ descriptor.extensionReceiverParameter?.type?.constructor?.declarationDescriptor as? ClassDescriptor
+ } else {
+ descriptor.containingDeclaration as? ClassDescriptor
+ }
+ val ktFunction = declaration as? KtFunction
+ if (receiverClass != null && ktFunction != null) {
+ checkAssignMethod(descriptor, receiverClass, ktFunction, context.trace)
+ }
+ }
+ }
+
+ private fun checkAssignMethod(
+ method: SimpleFunctionDescriptor,
+ receiverClass: ClassDescriptor,
+ declaration: KtFunction,
+ diagnosticHolder: DiagnosticSink
+ ) {
+ if (!annotationMatchingService.isAnnotated(receiverClass)) {
+ return
+ }
+
+ if (method.returnType?.let { KotlinBuiltIns.isUnit(it) } != true) {
+ diagnosticHolder.report(DECLARATION_ERROR_ASSIGN_METHOD_SHOULD_RETURN_UNIT.on(declaration))
+ }
+ }
+
+ private fun SimpleFunctionDescriptor.isAssignMethod(): Boolean {
+ return valueParameters.size == 1 && name == ASSIGN_METHOD
+ }
+
+ private class AnnotationMatchingService(val annotations: List) : AnnotationBasedExtension {
+ override fun getAnnotationFqNames(modifierListOwner: KtModifierListOwner?): List = annotations
+
+ fun isAnnotated(descriptor: ClassDescriptor): Boolean = descriptor.hasSpecialAnnotation(null)
+ }
+}
diff --git a/plugins/assign-plugin/assign-plugin.k1/src/org/jetbrains/kotlin/assignment/plugin/diagnostics/DefaultErrorMessagesAssignmentPlugin.kt b/plugins/assign-plugin/assign-plugin.k1/src/org/jetbrains/kotlin/assignment/plugin/diagnostics/DefaultErrorMessagesAssignmentPlugin.kt
new file mode 100644
index 00000000000..28682a6e1e4
--- /dev/null
+++ b/plugins/assign-plugin/assign-plugin.k1/src/org/jetbrains/kotlin/assignment/plugin/diagnostics/DefaultErrorMessagesAssignmentPlugin.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+package org.jetbrains.kotlin.assignment.plugin.diagnostics
+
+import org.jetbrains.kotlin.assignment.plugin.diagnostics.ErrorsAssignmentPlugin.*
+import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages
+import org.jetbrains.kotlin.diagnostics.rendering.DiagnosticFactoryToRendererMap
+
+object DefaultErrorMessagesAssignmentPlugin : DefaultErrorMessages.Extension {
+
+ private val MAP = DiagnosticFactoryToRendererMap("ValueContainerAssignment")
+
+ override fun getMap() = MAP
+
+ init {
+ MAP.put(
+ DECLARATION_ERROR_ASSIGN_METHOD_SHOULD_RETURN_UNIT,
+ "Function 'assign' used for '=' overload should return 'Unit'"
+ )
+
+ MAP.put(
+ CALL_ERROR_ASSIGN_METHOD_SHOULD_RETURN_UNIT,
+ "Function 'assign' used for '=' overload should return 'Unit'"
+ )
+
+ MAP.put(
+ NO_APPLICABLE_ASSIGN_METHOD,
+ "No applicable 'assign' function found for '=' overload"
+ )
+ }
+}
diff --git a/plugins/assign-plugin/assign-plugin.k1/src/org/jetbrains/kotlin/assignment/plugin/diagnostics/ErrorsAssignmentPlugin.java b/plugins/assign-plugin/assign-plugin.k1/src/org/jetbrains/kotlin/assignment/plugin/diagnostics/ErrorsAssignmentPlugin.java
new file mode 100644
index 00000000000..25e484683da
--- /dev/null
+++ b/plugins/assign-plugin/assign-plugin.k1/src/org/jetbrains/kotlin/assignment/plugin/diagnostics/ErrorsAssignmentPlugin.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+package org.jetbrains.kotlin.assignment.plugin.diagnostics;
+
+import org.jetbrains.kotlin.diagnostics.DiagnosticFactory0;
+import org.jetbrains.kotlin.diagnostics.Errors;
+import org.jetbrains.kotlin.diagnostics.PositioningStrategies;
+import org.jetbrains.kotlin.diagnostics.Severity;
+import org.jetbrains.kotlin.psi.KtDeclaration;
+import org.jetbrains.kotlin.psi.KtSimpleNameExpression;
+
+import static org.jetbrains.kotlin.diagnostics.PositioningStrategies.DECLARATION_RETURN_TYPE;
+import static org.jetbrains.kotlin.diagnostics.Severity.ERROR;
+
+public interface ErrorsAssignmentPlugin {
+
+ DiagnosticFactory0 DECLARATION_ERROR_ASSIGN_METHOD_SHOULD_RETURN_UNIT =
+ DiagnosticFactory0.create(Severity.ERROR, DECLARATION_RETURN_TYPE);
+ DiagnosticFactory0 CALL_ERROR_ASSIGN_METHOD_SHOULD_RETURN_UNIT =
+ DiagnosticFactory0.create(ERROR, PositioningStrategies.CALL_EXPRESSION);
+ DiagnosticFactory0 NO_APPLICABLE_ASSIGN_METHOD =
+ DiagnosticFactory0.create(ERROR, PositioningStrategies.CALL_EXPRESSION);
+
+ @SuppressWarnings("unused")
+ Object _initializer = new Object() {
+ {
+ Errors.Initializer.initializeFactoryNamesAndDefaultErrorMessages(
+ ErrorsAssignmentPlugin.class,
+ DefaultErrorMessagesAssignmentPlugin.INSTANCE
+ );
+ }
+ };
+}
diff --git a/plugins/assign-plugin/assign-plugin.k2/build.gradle.kts b/plugins/assign-plugin/assign-plugin.k2/build.gradle.kts
new file mode 100644
index 00000000000..85997ddbd02
--- /dev/null
+++ b/plugins/assign-plugin/assign-plugin.k2/build.gradle.kts
@@ -0,0 +1,29 @@
+description = "Kotlin Assignment Compiler Plugin (K2)"
+
+plugins {
+ kotlin("jvm")
+ id("jps-compatible")
+}
+
+dependencies {
+ implementation(project(":kotlin-assignment-compiler-plugin.common"))
+
+ compileOnly(project(":compiler:fir:cones"))
+ compileOnly(project(":compiler:fir:tree"))
+ compileOnly(project(":compiler:fir:resolve"))
+ compileOnly(project(":compiler:fir:checkers"))
+ compileOnly(project(":compiler:ir.backend.common"))
+ compileOnly(project(":compiler:fir:entrypoint"))
+
+ compileOnly(intellijCore())
+ runtimeOnly(kotlinStdlib())
+}
+
+sourceSets {
+ "main" { projectDefault() }
+ "test" { none() }
+}
+
+runtimeJar()
+sourcesJar()
+javadocJar()
diff --git a/plugins/assign-plugin/assign-plugin.k2/src/org/jetbrains/kotlin/assignment/plugin/k2/FirAssignAnnotationMatchingService.kt b/plugins/assign-plugin/assign-plugin.k2/src/org/jetbrains/kotlin/assignment/plugin/k2/FirAssignAnnotationMatchingService.kt
new file mode 100644
index 00000000000..4717203f7e1
--- /dev/null
+++ b/plugins/assign-plugin/assign-plugin.k2/src/org/jetbrains/kotlin/assignment/plugin/k2/FirAssignAnnotationMatchingService.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+package org.jetbrains.kotlin.assignment.plugin.k2
+
+import org.jetbrains.kotlin.fir.FirSession
+import org.jetbrains.kotlin.fir.caches.FirCache
+import org.jetbrains.kotlin.fir.caches.firCachesFactory
+import org.jetbrains.kotlin.fir.caches.getValue
+import org.jetbrains.kotlin.fir.expressions.classId
+import org.jetbrains.kotlin.fir.extensions.FirExtensionSessionComponent
+import org.jetbrains.kotlin.fir.extensions.FirExtensionSessionComponent.Factory
+import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
+import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
+import org.jetbrains.kotlin.fir.types.toRegularClassSymbol
+import org.jetbrains.kotlin.name.ClassId
+
+internal class FirAssignAnnotationMatchingService(
+ session: FirSession,
+ private val annotationClassIds: List
+) : FirExtensionSessionComponent(session) {
+
+ companion object {
+ fun getFactory(annotations: List): Factory {
+ return Factory { session -> FirAssignAnnotationMatchingService(session, annotations.map { ClassId.fromString(it) }) }
+ }
+ }
+
+ private val cache: FirCache = session.firCachesFactory.createCache { symbol, _ ->
+ symbol.annotated()
+ }
+
+ fun isAnnotated(symbol: FirRegularClassSymbol?): Boolean {
+ if (symbol == null) {
+ return false
+ }
+ return cache.getValue(symbol)
+ }
+
+ private fun FirRegularClassSymbol.annotated(): Boolean {
+ if (this.annotations.any { it.classId in annotationClassIds }) return true
+ return resolvedSuperTypeRefs.any {
+ val symbol = it.type.fullyExpandedType(session).toRegularClassSymbol(session) ?: return@any false
+ symbol.annotations.any { it.classId in annotationClassIds }
+ }
+ }
+}
+
+internal val FirSession.annotationMatchingService: FirAssignAnnotationMatchingService by FirSession.sessionComponentAccessor()
diff --git a/plugins/assign-plugin/assign-plugin.k2/src/org/jetbrains/kotlin/assignment/plugin/k2/FirAssignmentPluginAssignAltererExtension.kt b/plugins/assign-plugin/assign-plugin.k2/src/org/jetbrains/kotlin/assignment/plugin/k2/FirAssignmentPluginAssignAltererExtension.kt
new file mode 100644
index 00000000000..f7da1c8787f
--- /dev/null
+++ b/plugins/assign-plugin/assign-plugin.k2/src/org/jetbrains/kotlin/assignment/plugin/k2/FirAssignmentPluginAssignAltererExtension.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+package org.jetbrains.kotlin.assignment.plugin.k2
+
+import org.jetbrains.kotlin.KtFakeSourceElementKind
+import org.jetbrains.kotlin.assignment.plugin.AssignmentPluginNames.ASSIGN_METHOD
+import org.jetbrains.kotlin.fakeElement
+import org.jetbrains.kotlin.fir.FirSession
+import org.jetbrains.kotlin.fir.expressions.*
+import org.jetbrains.kotlin.fir.expressions.builder.buildFunctionCall
+import org.jetbrains.kotlin.fir.expressions.builder.buildPropertyAccessExpression
+import org.jetbrains.kotlin.fir.extensions.FirAssignExpressionAltererExtension
+import org.jetbrains.kotlin.fir.references.builder.buildSimpleNamedReference
+import org.jetbrains.kotlin.fir.resolvedSymbol
+import org.jetbrains.kotlin.fir.symbols.impl.FirBackingFieldSymbol
+import org.jetbrains.kotlin.fir.symbols.impl.FirFieldSymbol
+import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
+import org.jetbrains.kotlin.fir.symbols.impl.FirVariableSymbol
+import org.jetbrains.kotlin.fir.types.toRegularClassSymbol
+import org.jetbrains.kotlin.fir.types.upperBoundIfFlexible
+import org.jetbrains.kotlin.utils.addToStdlib.runIf
+
+class FirAssignmentPluginAssignAltererExtension(
+ session: FirSession
+) : FirAssignExpressionAltererExtension(session) {
+
+ override fun transformVariableAssignment(variableAssignment: FirVariableAssignment): FirStatement? {
+ return runIf(variableAssignment.supportsTransformVariableAssignment()) {
+ buildFunctionCall(variableAssignment)
+ }
+ }
+
+ private fun FirVariableAssignment.supportsTransformVariableAssignment(): Boolean {
+ return when (val lSymbol = lValue.resolvedSymbol as? FirVariableSymbol<*>) {
+ is FirPropertySymbol -> lSymbol.isVal && !lSymbol.isLocal && lSymbol.hasSpecialAnnotation()
+ is FirBackingFieldSymbol -> lSymbol.isVal && lSymbol.hasSpecialAnnotation()
+ is FirFieldSymbol -> lSymbol.isVal && lSymbol.hasSpecialAnnotation()
+ else -> false
+ }
+ }
+
+ private fun FirVariableSymbol<*>.hasSpecialAnnotation(): Boolean =
+ session.annotationMatchingService.isAnnotated(resolvedReturnType.upperBoundIfFlexible().toRegularClassSymbol(session))
+
+ private fun buildFunctionCall(variableAssignment: FirVariableAssignment): FirFunctionCall {
+ val leftArgument = variableAssignment.lValue
+ val leftSymbol = leftArgument.resolvedSymbol as FirVariableSymbol<*>
+ val leftResolvedType = leftSymbol.resolvedReturnTypeRef
+ val rightArgument = variableAssignment.rValue
+ return buildFunctionCall {
+ source = variableAssignment.source?.fakeElement(KtFakeSourceElementKind.DesugaredCompoundAssignment)
+ explicitReceiver = buildPropertyAccessExpression {
+ source = leftArgument.source
+ typeRef = leftResolvedType
+ calleeReference = variableAssignment.calleeReference
+ typeArguments += variableAssignment.typeArguments
+ annotations += variableAssignment.annotations
+ explicitReceiver = variableAssignment.explicitReceiver
+ dispatchReceiver = variableAssignment.dispatchReceiver
+ extensionReceiver = variableAssignment.extensionReceiver
+ contextReceiverArguments += variableAssignment.contextReceiverArguments
+ }
+ argumentList = buildUnaryArgumentList(rightArgument)
+ calleeReference = buildSimpleNamedReference {
+ source = variableAssignment.source
+ name = ASSIGN_METHOD
+ candidateSymbol = null
+ }
+ origin = FirFunctionCallOrigin.Regular
+ }
+ }
+}
diff --git a/plugins/assign-plugin/assign-plugin.k2/src/org/jetbrains/kotlin/assignment/plugin/k2/FirAssignmentPluginCheckersExtension.kt b/plugins/assign-plugin/assign-plugin.k2/src/org/jetbrains/kotlin/assignment/plugin/k2/FirAssignmentPluginCheckersExtension.kt
new file mode 100644
index 00000000000..30f0ab8948d
--- /dev/null
+++ b/plugins/assign-plugin/assign-plugin.k2/src/org/jetbrains/kotlin/assignment/plugin/k2/FirAssignmentPluginCheckersExtension.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+package org.jetbrains.kotlin.assignment.plugin.k2
+
+import org.jetbrains.kotlin.assignment.plugin.k2.diagnostics.FirAssignmentPluginFunctionCallChecker
+import org.jetbrains.kotlin.assignment.plugin.k2.diagnostics.FirAssignmentPluginFunctionChecker
+import org.jetbrains.kotlin.fir.FirSession
+import org.jetbrains.kotlin.fir.analysis.checkers.declaration.DeclarationCheckers
+import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirSimpleFunctionChecker
+import org.jetbrains.kotlin.fir.analysis.checkers.expression.ExpressionCheckers
+import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirFunctionCallChecker
+import org.jetbrains.kotlin.fir.analysis.extensions.FirAdditionalCheckersExtension
+
+class FirAssignmentPluginCheckersExtension(
+ session: FirSession
+) : FirAdditionalCheckersExtension(session) {
+
+ override val declarationCheckers: DeclarationCheckers = object : DeclarationCheckers() {
+ override val simpleFunctionCheckers: Set
+ get() = setOf(FirAssignmentPluginFunctionChecker)
+ }
+
+ override val expressionCheckers: ExpressionCheckers = object : ExpressionCheckers() {
+ override val functionCallCheckers: Set
+ get() = setOf(FirAssignmentPluginFunctionCallChecker)
+ }
+}
diff --git a/plugins/assign-plugin/assign-plugin.k2/src/org/jetbrains/kotlin/assignment/plugin/k2/FirAssignmentPluginExtensionRegistrar.kt b/plugins/assign-plugin/assign-plugin.k2/src/org/jetbrains/kotlin/assignment/plugin/k2/FirAssignmentPluginExtensionRegistrar.kt
new file mode 100644
index 00000000000..4a313d20c7c
--- /dev/null
+++ b/plugins/assign-plugin/assign-plugin.k2/src/org/jetbrains/kotlin/assignment/plugin/k2/FirAssignmentPluginExtensionRegistrar.kt
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+package org.jetbrains.kotlin.assignment.plugin.k2
+
+import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrar
+
+class FirAssignmentPluginExtensionRegistrar(private val annotations: List) : FirExtensionRegistrar() {
+ override fun ExtensionRegistrarContext.configurePlugin() {
+ +::FirAssignmentPluginAssignAltererExtension
+ +::FirAssignmentPluginCheckersExtension
+ +FirAssignAnnotationMatchingService.getFactory(annotations)
+ }
+}
diff --git a/plugins/assign-plugin/assign-plugin.k2/src/org/jetbrains/kotlin/assignment/plugin/k2/diagnostics/FirAssignmentPluginFunctionCallChecker.kt b/plugins/assign-plugin/assign-plugin.k2/src/org/jetbrains/kotlin/assignment/plugin/k2/diagnostics/FirAssignmentPluginFunctionCallChecker.kt
new file mode 100644
index 00000000000..c7f81234cb7
--- /dev/null
+++ b/plugins/assign-plugin/assign-plugin.k2/src/org/jetbrains/kotlin/assignment/plugin/k2/diagnostics/FirAssignmentPluginFunctionCallChecker.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+package org.jetbrains.kotlin.assignment.plugin.k2.diagnostics
+
+import org.jetbrains.kotlin.KtFakeSourceElementKind
+import org.jetbrains.kotlin.assignment.plugin.AssignmentPluginNames.ASSIGN_METHOD
+import org.jetbrains.kotlin.assignment.plugin.k2.annotationMatchingService
+import org.jetbrains.kotlin.assignment.plugin.k2.diagnostics.FirErrorsAssignmentPlugin.CALL_ERROR_ASSIGN_METHOD_SHOULD_RETURN_UNIT
+import org.jetbrains.kotlin.assignment.plugin.k2.diagnostics.FirErrorsAssignmentPlugin.NO_APPLICABLE_ASSIGN_METHOD
+import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
+import org.jetbrains.kotlin.diagnostics.reportOn
+import org.jetbrains.kotlin.fir.FirSession
+import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
+import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirFunctionCallChecker
+import org.jetbrains.kotlin.fir.analysis.checkers.toRegularClassSymbol
+import org.jetbrains.kotlin.fir.expressions.FirFunctionCall
+import org.jetbrains.kotlin.fir.expressions.arguments
+import org.jetbrains.kotlin.fir.expressions.toResolvedCallableSymbol
+import org.jetbrains.kotlin.fir.references.FirErrorNamedReference
+import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeAmbiguityError
+import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeDiagnosticWithSingleCandidate
+import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeUnresolvedNameError
+import org.jetbrains.kotlin.fir.types.isUnit
+
+object FirAssignmentPluginFunctionCallChecker : FirFunctionCallChecker() {
+
+ override fun check(expression: FirFunctionCall, context: CheckerContext, reporter: DiagnosticReporter) {
+ if (!expression.isOverloadAssignCallCandidate()) return
+
+ if (expression.isFunctionResolveError()) {
+ if (expression.isOverloadedAssignCallError(context.session)) {
+ reporter.reportOn(expression.source, NO_APPLICABLE_ASSIGN_METHOD, context)
+ }
+ } else if (expression.isOverloadedAssignCall(context.session) && !expression.isReturnTypeUnit()) {
+ reporter.reportOn(expression.source, CALL_ERROR_ASSIGN_METHOD_SHOULD_RETURN_UNIT, context)
+ }
+ }
+
+ private fun FirFunctionCall.isOverloadAssignCallCandidate() =
+ arguments.size == 1 && source?.kind == KtFakeSourceElementKind.DesugaredCompoundAssignment
+
+ private fun FirFunctionCall.isFunctionResolveError() = calleeReference is FirErrorNamedReference
+
+ private fun FirFunctionCall.isOverloadedAssignCallError(session: FirSession): Boolean {
+ val functionName = when (val diagnostic = (calleeReference as? FirErrorNamedReference)?.diagnostic) {
+ is ConeAmbiguityError -> diagnostic.name
+ is ConeDiagnosticWithSingleCandidate -> diagnostic.candidate.callInfo.name
+ is ConeUnresolvedNameError -> diagnostic.name
+ else -> calleeReference.name
+ }
+ return functionName == ASSIGN_METHOD && isAnnotated(session)
+ }
+
+ private fun FirFunctionCall.isOverloadedAssignCall(session: FirSession) =
+ calleeReference.name == ASSIGN_METHOD && isAnnotated(session)
+
+ private fun FirFunctionCall.isAnnotated(session: FirSession): Boolean =
+ session.annotationMatchingService.isAnnotated(explicitReceiver?.typeRef?.toRegularClassSymbol(session))
+
+ private fun FirFunctionCall.isReturnTypeUnit() = toResolvedCallableSymbol()?.resolvedReturnType?.isUnit ?: false
+}
diff --git a/plugins/assign-plugin/assign-plugin.k2/src/org/jetbrains/kotlin/assignment/plugin/k2/diagnostics/FirAssignmentPluginFunctionChecker.kt b/plugins/assign-plugin/assign-plugin.k2/src/org/jetbrains/kotlin/assignment/plugin/k2/diagnostics/FirAssignmentPluginFunctionChecker.kt
new file mode 100644
index 00000000000..a5769e0d84e
--- /dev/null
+++ b/plugins/assign-plugin/assign-plugin.k2/src/org/jetbrains/kotlin/assignment/plugin/k2/diagnostics/FirAssignmentPluginFunctionChecker.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+package org.jetbrains.kotlin.assignment.plugin.k2.diagnostics
+
+import org.jetbrains.kotlin.assignment.plugin.AssignmentPluginNames.ASSIGN_METHOD
+import org.jetbrains.kotlin.assignment.plugin.k2.annotationMatchingService
+import org.jetbrains.kotlin.assignment.plugin.k2.diagnostics.FirErrorsAssignmentPlugin.DECLARATION_ERROR_ASSIGN_METHOD_SHOULD_RETURN_UNIT
+import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
+import org.jetbrains.kotlin.diagnostics.reportOn
+import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
+import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirSimpleFunctionChecker
+import org.jetbrains.kotlin.fir.analysis.checkers.toRegularClassSymbol
+import org.jetbrains.kotlin.fir.declarations.FirDeclarationOrigin
+import org.jetbrains.kotlin.fir.declarations.FirSimpleFunction
+import org.jetbrains.kotlin.fir.symbols.impl.isExtension
+import org.jetbrains.kotlin.fir.types.coneType
+import org.jetbrains.kotlin.fir.types.isUnit
+import org.jetbrains.kotlin.fir.types.toRegularClassSymbol
+
+object FirAssignmentPluginFunctionChecker : FirSimpleFunctionChecker() {
+
+ override fun check(declaration: FirSimpleFunction, context: CheckerContext, reporter: DiagnosticReporter) {
+ if (declaration.origin != FirDeclarationOrigin.Source) return
+ if (!declaration.isAssignMethod()) return
+
+ val receiverClassSymbol = if (declaration.symbol.isExtension) {
+ declaration.symbol.resolvedReceiverTypeRef?.toRegularClassSymbol(context.session)
+ } else {
+ declaration.dispatchReceiverType?.toRegularClassSymbol(context.session)
+ }
+ if (!context.session.annotationMatchingService.isAnnotated(receiverClassSymbol)) return
+ if (!declaration.returnTypeRef.coneType.isUnit) {
+ reporter.reportOn(declaration.source, DECLARATION_ERROR_ASSIGN_METHOD_SHOULD_RETURN_UNIT, context)
+ }
+ }
+
+ private fun FirSimpleFunction.isAssignMethod(): Boolean {
+ return valueParameters.size == 1 && this.name == ASSIGN_METHOD
+ }
+}
diff --git a/plugins/assign-plugin/assign-plugin.k2/src/org/jetbrains/kotlin/assignment/plugin/k2/diagnostics/FirDefaultErrorMessagesAssignmentPlugin.kt b/plugins/assign-plugin/assign-plugin.k2/src/org/jetbrains/kotlin/assignment/plugin/k2/diagnostics/FirDefaultErrorMessagesAssignmentPlugin.kt
new file mode 100644
index 00000000000..e36999ae02f
--- /dev/null
+++ b/plugins/assign-plugin/assign-plugin.k2/src/org/jetbrains/kotlin/assignment/plugin/k2/diagnostics/FirDefaultErrorMessagesAssignmentPlugin.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+package org.jetbrains.kotlin.assignment.plugin.k2.diagnostics
+
+import org.jetbrains.kotlin.assignment.plugin.k2.diagnostics.FirErrorsAssignmentPlugin.CALL_ERROR_ASSIGN_METHOD_SHOULD_RETURN_UNIT
+import org.jetbrains.kotlin.assignment.plugin.k2.diagnostics.FirErrorsAssignmentPlugin.DECLARATION_ERROR_ASSIGN_METHOD_SHOULD_RETURN_UNIT
+import org.jetbrains.kotlin.assignment.plugin.k2.diagnostics.FirErrorsAssignmentPlugin.NO_APPLICABLE_ASSIGN_METHOD
+import org.jetbrains.kotlin.diagnostics.KtDiagnostic
+import org.jetbrains.kotlin.diagnostics.KtDiagnosticFactoryToRendererMap
+import org.jetbrains.kotlin.diagnostics.KtDiagnosticRenderer
+
+object FirDefaultErrorMessagesAssignmentPlugin {
+ fun getRendererForDiagnostic(diagnostic: KtDiagnostic): KtDiagnosticRenderer {
+ val factory = diagnostic.factory
+ return MAP[factory] ?: factory.ktRenderer
+ }
+
+ val MAP = KtDiagnosticFactoryToRendererMap("ValueContainerAssignment").also { map ->
+ map.put(
+ DECLARATION_ERROR_ASSIGN_METHOD_SHOULD_RETURN_UNIT,
+ "Function 'assign' used for '=' overload should return 'Unit'"
+ )
+
+ map.put(
+ CALL_ERROR_ASSIGN_METHOD_SHOULD_RETURN_UNIT,
+ "Function 'assign' used for '=' overload should return 'Unit'"
+ )
+
+ map.put(
+ NO_APPLICABLE_ASSIGN_METHOD,
+ "No applicable 'assign' function found for '=' overload"
+ )
+ }
+}
diff --git a/plugins/assign-plugin/assign-plugin.k2/src/org/jetbrains/kotlin/assignment/plugin/k2/diagnostics/FirErrorsAssignmentPlugin.kt b/plugins/assign-plugin/assign-plugin.k2/src/org/jetbrains/kotlin/assignment/plugin/k2/diagnostics/FirErrorsAssignmentPlugin.kt
new file mode 100644
index 00000000000..a443b31a845
--- /dev/null
+++ b/plugins/assign-plugin/assign-plugin.k2/src/org/jetbrains/kotlin/assignment/plugin/k2/diagnostics/FirErrorsAssignmentPlugin.kt
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+package org.jetbrains.kotlin.assignment.plugin.k2.diagnostics
+
+import com.intellij.psi.PsiElement
+import org.jetbrains.kotlin.diagnostics.SourceElementPositioningStrategies.DECLARATION_RETURN_TYPE
+import org.jetbrains.kotlin.diagnostics.SourceElementPositioningStrategies.OPERATOR
+import org.jetbrains.kotlin.diagnostics.error0
+
+object FirErrorsAssignmentPlugin {
+ val DECLARATION_ERROR_ASSIGN_METHOD_SHOULD_RETURN_UNIT by error0(DECLARATION_RETURN_TYPE)
+ val CALL_ERROR_ASSIGN_METHOD_SHOULD_RETURN_UNIT by error0(OPERATOR)
+ val NO_APPLICABLE_ASSIGN_METHOD by error0(OPERATOR)
+}
diff --git a/plugins/assign-plugin/build.gradle.kts b/plugins/assign-plugin/build.gradle.kts
new file mode 100644
index 00000000000..51d2f47f805
--- /dev/null
+++ b/plugins/assign-plugin/build.gradle.kts
@@ -0,0 +1,54 @@
+description = "Kotlin Assignment Compiler Plugin"
+
+plugins {
+ kotlin("jvm")
+ id("jps-compatible")
+}
+
+dependencies {
+ embedded(project(":kotlin-assignment-compiler-plugin.common"))
+ embedded(project(":kotlin-assignment-compiler-plugin.k1"))
+ embedded(project(":kotlin-assignment-compiler-plugin.k2"))
+ embedded(project(":kotlin-assignment-compiler-plugin.cli"))
+
+ testApi(project(":compiler:backend"))
+ testApi(project(":compiler:cli"))
+ testApi(project(":kotlin-assignment-compiler-plugin.cli"))
+ testCompileOnly(project(":kotlin-compiler"))
+ testImplementation(project(":kotlin-scripting-jvm-host-unshaded"))
+
+ testApi(projectTests(":compiler:tests-common-new"))
+
+ testImplementation(projectTests(":compiler:tests-common"))
+ testImplementation(commonDependency("junit:junit"))
+
+ testCompileOnly(project(":kotlin-reflect-api"))
+ testRuntimeOnly(project(":kotlin-reflect"))
+ testRuntimeOnly(project(":core:descriptors.runtime"))
+ testRuntimeOnly(project(":compiler:fir:fir-serialization"))
+
+ testApi(intellijCore())
+}
+
+optInToExperimentalCompilerApi()
+
+sourceSets {
+ "main" { none() }
+ "test" {
+ projectDefault()
+ generatedTestDir()
+ }
+}
+
+publish()
+
+runtimeJar()
+sourcesJar()
+javadocJar()
+testsJar()
+
+projectTest(parallel = true) {
+ dependsOn(":dist")
+ workingDir = rootDir
+ useJUnitPlatform()
+}
diff --git a/plugins/assign-plugin/test/org/jetbrains/kotlin/assignment/plugin/AssignmentPluginTests.kt b/plugins/assign-plugin/test/org/jetbrains/kotlin/assignment/plugin/AssignmentPluginTests.kt
new file mode 100644
index 00000000000..40fbc296b9f
--- /dev/null
+++ b/plugins/assign-plugin/test/org/jetbrains/kotlin/assignment/plugin/AssignmentPluginTests.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+package org.jetbrains.kotlin.assignment.plugin
+
+import org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar
+import org.jetbrains.kotlin.config.CompilerConfiguration
+import org.jetbrains.kotlin.assignment.plugin.k2.FirAssignmentPluginExtensionRegistrar
+import org.jetbrains.kotlin.extensions.StorageComponentContainerContributor
+import org.jetbrains.kotlin.extensions.internal.InternalNonStableExtensionPoints
+import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrarAdapter
+import org.jetbrains.kotlin.resolve.extensions.AssignResolutionAltererExtension
+import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder
+import org.jetbrains.kotlin.test.directives.DiagnosticsDirectives.RENDER_DIAGNOSTICS_FULL_TEXT
+import org.jetbrains.kotlin.test.model.TestModule
+import org.jetbrains.kotlin.test.runners.AbstractDiagnosticTest
+import org.jetbrains.kotlin.test.runners.AbstractFirDiagnosticTest
+import org.jetbrains.kotlin.test.runners.codegen.AbstractFirBlackBoxCodegenTest
+import org.jetbrains.kotlin.test.runners.codegen.AbstractIrBlackBoxCodegenTest
+import org.jetbrains.kotlin.test.runners.configurationForClassicAndFirTestsAlongside
+import org.jetbrains.kotlin.test.services.EnvironmentConfigurator
+import org.jetbrains.kotlin.test.services.TestServices
+
+// ------------------------ diagnostics ------------------------
+
+abstract class AbstractAssignmentPluginDiagnosticTest : AbstractDiagnosticTest() {
+ override fun configure(builder: TestConfigurationBuilder) {
+ super.configure(builder)
+ builder.configurePlugin()
+ builder.configureDiagnostics()
+ }
+}
+
+abstract class AbstractFirAssignmentPluginDiagnosticTest : AbstractFirDiagnosticTest() {
+ override fun configure(builder: TestConfigurationBuilder) {
+ super.configure(builder)
+ builder.configurePlugin()
+ builder.configureDiagnostics()
+ builder.configurationForClassicAndFirTestsAlongside()
+ }
+}
+
+// ------------------------ codegen ------------------------
+
+open class AbstractIrBlackBoxCodegenTestAssignmentPlugin : AbstractIrBlackBoxCodegenTest() {
+ override fun configure(builder: TestConfigurationBuilder) {
+ super.configure(builder)
+ builder.configurePlugin()
+ }
+}
+
+open class AbstractFirBlackBoxCodegenTestForAssignmentPlugin : AbstractFirBlackBoxCodegenTest() {
+ override fun configure(builder: TestConfigurationBuilder) {
+ super.configure(builder)
+ builder.configurePlugin()
+ }
+}
+
+// ------------------------ configuration ------------------------
+
+fun TestConfigurationBuilder.configurePlugin() {
+ useConfigurators(::AssignmentPluginEnvironmentConfigurator)
+}
+
+fun TestConfigurationBuilder.configureDiagnostics() {
+ defaultDirectives {
+ +RENDER_DIAGNOSTICS_FULL_TEXT
+ }
+}
+
+class AssignmentPluginEnvironmentConfigurator(testServices: TestServices) : EnvironmentConfigurator(testServices) {
+ companion object {
+ private val TEST_ANNOTATIONS = listOf("ValueContainer")
+ }
+
+ @OptIn(InternalNonStableExtensionPoints::class)
+ override fun CompilerPluginRegistrar.ExtensionStorage.registerCompilerExtensions(
+ module: TestModule,
+ configuration: CompilerConfiguration
+ ) {
+ AssignResolutionAltererExtension.Companion.registerExtension(CliAssignPluginResolutionAltererExtension(TEST_ANNOTATIONS))
+ StorageComponentContainerContributor.registerExtension(AssignmentComponentContainerContributor(TEST_ANNOTATIONS))
+ FirExtensionRegistrarAdapter.registerExtension(FirAssignmentPluginExtensionRegistrar(TEST_ANNOTATIONS))
+ }
+}
diff --git a/plugins/assign-plugin/testData/codegen/annotation.kt b/plugins/assign-plugin/testData/codegen/annotation.kt
new file mode 100644
index 00000000000..b6021c2a71f
--- /dev/null
+++ b/plugins/assign-plugin/testData/codegen/annotation.kt
@@ -0,0 +1,120 @@
+// FILE: JavaProperty.java
+@ValueContainer
+public interface JavaProperty {
+ void assign(T argument);
+ T get();
+}
+
+// FILE: JavaClassStringProperty.java
+@ValueContainer
+public class JavaClassStringProperty {
+ private String v;
+
+ public JavaClassStringProperty(String v) {
+ this.v = v;
+ }
+
+ public void assign(String v) {
+ this.v = v;
+ }
+ public String get() {
+ return v;
+ }
+}
+
+// FILE: test.kt
+annotation class ValueContainer
+
+data class JavaStringProperty(private var v: String): JavaProperty {
+ override fun assign(v: String) {
+ this.v = v
+ }
+ override fun get() = this.v
+}
+
+@ValueContainer
+interface KotlinProperty {
+ fun assign(argument: T);
+ fun get(): T;
+}
+
+data class KotlinStringProperty(private var v: String): KotlinProperty {
+ override fun assign(v: String) {
+ this.v = v
+ }
+ override fun get() = this.v
+}
+
+@ValueContainer
+data class KotlinClassStringProperty(private var v: String) {
+ fun assign(v: String) {
+ this.v = v
+ }
+ fun get(): String {
+ return v
+ }
+}
+
+fun `should work with annotation on Java interface`(): String {
+ data class Task(val input: JavaStringProperty)
+ val task = Task(JavaStringProperty("Fail"))
+ task.input = "OK"
+
+ return if (task.input.get() != "OK") {
+ "Fail: ${task.input.get()}"
+ } else {
+ "OK"
+ }
+}
+
+fun `should work with annotation on Java class`(): String {
+ data class Task(val input: JavaClassStringProperty)
+ val task = Task(JavaClassStringProperty("Fail"))
+ task.input = "OK"
+
+ return if (task.input.get() != "OK") {
+ "Fail: ${task.input.get()}"
+ } else {
+ "OK"
+ }
+}
+
+fun `should work with annotation on Kotlin interface`(): String {
+ data class Task(val input: KotlinStringProperty)
+ val task = Task(KotlinStringProperty("Fail"))
+ task.input = "OK"
+
+ return if (task.input.get() != "OK") {
+ "Fail: ${task.input.get()}"
+ } else {
+ "OK"
+ }
+}
+
+fun `should work with annotation on Kotlin class`(): String {
+ data class Task(val input: KotlinClassStringProperty)
+ val task = Task(KotlinClassStringProperty("Fail"))
+ task.input = "OK"
+
+ return if (task.input.get() != "OK") {
+ "Fail: ${task.input.get()}"
+ } else {
+ "OK"
+ }
+}
+
+fun box(): String {
+ var result = `should work with annotation on Java interface`()
+ if (result != "OK") return result
+
+ result = `should work with annotation on Java class`()
+ if (result != "OK") return result
+
+ result = `should work with annotation on Kotlin interface`()
+ if (result != "OK") return result
+
+ result = `should work with annotation on Kotlin class`()
+ if (result != "OK") return result
+
+ return "OK"
+}
diff --git a/plugins/assign-plugin/testData/codegen/otherOperators.kt b/plugins/assign-plugin/testData/codegen/otherOperators.kt
new file mode 100644
index 00000000000..6955b1764a5
--- /dev/null
+++ b/plugins/assign-plugin/testData/codegen/otherOperators.kt
@@ -0,0 +1,105 @@
+annotation class ValueContainer
+
+@ValueContainer
+class StringProperty(var v: String) {
+ fun assign(v: String) {
+ this.v = v
+ }
+ fun assign(v: StringProperty) {
+ this.v = v.get()
+ }
+ fun get(): String {
+ return v
+ }
+}
+
+data class Task(val input: StringProperty)
+
+var result = "Fail"
+operator fun StringProperty.plusAssign(v: String) {
+ result = v
+}
+operator fun StringProperty.plusAssign(v: StringProperty) {
+ result = v.get()
+}
+operator fun StringProperty.set(i: Int, v: String) {
+ result = v
+}
+operator fun StringProperty.set(i: Int, v: StringProperty) {
+ result = v.get()
+}
+operator fun StringProperty.set(i: Int, j: Int, v: String) {
+ result = v
+}
+operator fun StringProperty.set(i: Int, j: Int, v: StringProperty) {
+ result = v.get()
+}
+operator fun StringProperty.set(i: Int, j: Int, k: Int, v: String) {
+ result = v
+}
+operator fun StringProperty.set(i: Int, j: Int, k: Int, v: StringProperty) {
+ result = v.get()
+}
+operator fun StringProperty.compareTo(v: String): Int {
+ result = v
+ return 0
+}
+operator fun StringProperty.compareTo(v: StringProperty): Int {
+ result = v.get()
+ return 0
+}
+
+fun box(): String {
+ val task = Task(StringProperty("Fail"))
+
+ // Double check that assign is correctly setup
+ task.input = "OK"
+ if (task.input.get() != "OK") return task.input.get()
+
+ task?.input = "OK"
+ if (task.input.get() != "OK") return task.input.get()
+
+ result = "Fail"
+ task.input += "OK"
+ if (result != "OK") return result
+ result = "Fail"
+ task.input += StringProperty("OK")
+ if (result != "OK") return result
+
+ result = "Fail"
+ task.input >= "OK"
+ if (result != "OK") return result
+ result = "Fail"
+ task.input >= StringProperty("OK")
+ if (result != "OK") return result
+
+ result = "Fail"
+ task.input <= "OK"
+ if (result != "OK") return result
+ result = "Fail"
+ task.input <= StringProperty("OK")
+ if (result != "OK") return result
+
+ result = "Fail"
+ task.input[0] = "OK"
+ if (result != "OK") return result
+ result = "Fail"
+ task.input[0] = StringProperty("OK")
+ if (result != "OK") return result
+
+ result = "Fail"
+ task.input[0, 0] = "OK"
+ if (result != "OK") return result
+ result = "Fail"
+ task.input[0, 0] = StringProperty("OK")
+ if (result != "OK") return result
+
+ result = "Fail"
+ task.input[0, 0, 0] = "OK"
+ if (result != "OK") return result
+ result = "Fail"
+ task.input[0, 0, 0] = StringProperty("OK")
+ if (result != "OK") return result
+
+ return result
+}
diff --git a/plugins/assign-plugin/testData/codegen/plusAssignPrecedence.kt b/plugins/assign-plugin/testData/codegen/plusAssignPrecedence.kt
new file mode 100644
index 00000000000..c59da40bee0
--- /dev/null
+++ b/plugins/assign-plugin/testData/codegen/plusAssignPrecedence.kt
@@ -0,0 +1,108 @@
+annotation class ValueContainer
+
+abstract class AbstractStringProperty(protected var v: String) {
+ fun get(): String {
+ return v
+ }
+}
+
+@ValueContainer
+class StringProperty(v: String) : AbstractStringProperty(v) {
+ fun assign(v: String) {
+ this.v = v
+ }
+
+ fun assign(v: StringProperty) {
+ this.v = v.get()
+ }
+}
+
+@ValueContainer
+class StringPropertyWithPlus(v: String) : AbstractStringProperty(v) {
+ fun assign(v: String) {
+ this.v = v
+ }
+
+ fun assign(o: StringPropertyWithPlus) {
+ this.v = o.get()
+ }
+
+ operator fun plus(v: String) =
+ StringPropertyWithPlus(this.v + v)
+}
+
+@ValueContainer
+class StringPropertyWithPlusAssign(v: String) : AbstractStringProperty(v) {
+ fun assign(v: String) {
+ this.v = v
+ }
+
+ fun assign(o: StringPropertyWithPlusAssign) {
+ this.v = o.get()
+ }
+
+ operator fun plusAssign(v: String) {
+ this.v += v
+ }
+}
+
+@ValueContainer
+class StringPropertyWithPlusAndPlusAssign(v: String) : AbstractStringProperty(v) {
+ fun assign(v: String) {
+ this.v = v
+ }
+
+ fun assign(o: StringPropertyWithPlusAndPlusAssign) {
+ this.v = o.get()
+ }
+
+ operator fun plus(v: String) =
+ StringPropertyWithPlusAndPlusAssign(this.v + v)
+
+ operator fun plusAssign(v: String) {
+ this.v += v
+ }
+}
+
+data class Task(
+ val valInput: StringProperty,
+ var varInput: StringProperty,
+
+ val valInputWithPlus: StringPropertyWithPlus,
+ var varInputWithPlus: StringPropertyWithPlus,
+
+ val valInputWithPlusAssign: StringPropertyWithPlusAssign,
+ var varInputWithPlusAssign: StringPropertyWithPlusAssign,
+
+ val valInputWithPlusAndPlusAssign: StringPropertyWithPlusAndPlusAssign,
+ var varInputWithPlusAndPlusAssign: StringPropertyWithPlusAndPlusAssign,
+)
+
+fun box(): String {
+ val task = Task(
+ StringProperty("O"),
+ StringProperty("O"),
+
+ StringPropertyWithPlus("O"),
+ StringPropertyWithPlus("O"),
+
+ StringPropertyWithPlusAssign("O"),
+ StringPropertyWithPlusAssign("O"),
+
+ StringPropertyWithPlusAndPlusAssign("O"),
+ StringPropertyWithPlusAndPlusAssign("O")
+ )
+
+ task.varInputWithPlus += "K"
+ if (task.varInputWithPlus.get() != "OK") return task.varInputWithPlus.get()
+
+ task.valInputWithPlusAssign += "K"
+ if (task.valInputWithPlusAssign.get() != "OK") return task.valInputWithPlusAssign.get()
+ task.varInputWithPlusAssign += "K"
+ if (task.varInputWithPlusAssign.get() != "OK") return task.varInputWithPlusAssign.get()
+
+ task.valInputWithPlusAndPlusAssign += "K"
+ if (task.valInputWithPlusAndPlusAssign.get() != "OK") return task.valInputWithPlusAndPlusAssign.get()
+
+ return "OK"
+}
diff --git a/plugins/assign-plugin/testData/codegen/supportedUsage.kt b/plugins/assign-plugin/testData/codegen/supportedUsage.kt
new file mode 100644
index 00000000000..cdc478d2cf2
--- /dev/null
+++ b/plugins/assign-plugin/testData/codegen/supportedUsage.kt
@@ -0,0 +1,200 @@
+// FILE: Property.java
+@ValueContainer
+public interface Property {
+ void set(T v);
+ T get();
+}
+
+// FILE: JavaTaskWithField.java
+public class JavaTaskWithField {
+ public final StringProperty input;
+
+ public JavaTaskWithField(StringProperty input) {
+ this.input = input;
+ }
+}
+
+// FILE: JavaTaskWithProperty.java
+public class JavaTaskWithProperty {
+ private final StringProperty input;
+
+ public JavaTaskWithProperty(StringProperty input) {
+ this.input = input;
+ }
+
+ public StringProperty getInput() {
+ return input;
+ }
+}
+
+// FILE: test.kt
+annotation class ValueContainer
+
+data class StringProperty(var v: String): Property {
+ override fun set(v: String) {
+ this.v = v
+ }
+ fun assign(v: String) {
+ this.v = v
+ }
+ fun assign(v: Property) {
+ this.v = v.get()
+ }
+ override fun get(): String {
+ return v
+ }
+}
+
+data class Task(val input: StringProperty)
+
+fun `should work with assignment for raw type`(): String {
+ val task = Task(StringProperty("Fail"))
+ task.input = "OK"
+
+ return if (task.input.get() != "OK") {
+ "Fail: ${task.input.get()}"
+ } else {
+ "OK"
+ }
+}
+
+fun `should work with assignment for wrapped type`(): String {
+ val task = Task(StringProperty("Fail"))
+ task.input = StringProperty("OK")
+
+ return if (task.input.get() != "OK") {
+ "Fail: ${task.input.get()}"
+ } else {
+ "OK"
+ }
+}
+
+fun `should work with assignment with apply for raw type`(): String {
+ val task = Task(StringProperty("Fail"))
+ task.apply {
+ input = "OK"
+ }
+
+ return if (task.input.get() != "OK") {
+ "Fail: ${task.input.get()}"
+ } else {
+ "OK"
+ }
+}
+
+fun `should work with assignment with apply for wrapped type`(): String {
+ val task = Task(StringProperty("Fail"))
+ task.apply {
+ input = StringProperty("OK")
+ }
+
+ return if (task.input.get() != "OK") {
+ "Fail: ${task.input.get()}"
+ } else {
+ "OK"
+ }
+}
+
+fun `should work with extension function`(): String {
+ fun StringProperty.assign(v: Int) = this.assign("OK")
+ val task = Task(StringProperty("Fail"))
+ task.input = 42
+
+ return if (task.input.get() != "OK") {
+ "Fail: ${task.input.get()}"
+ } else {
+ "OK"
+ }
+}
+
+fun `should work with extension function for interface type`(): String {
+ fun Property.assign(v: Int) = this.set("OK")
+ val task = Task(StringProperty("Fail"))
+ task.input = 42
+
+ return if (task.input.get() != "OK") {
+ "Fail: ${task.input.get()}"
+ } else {
+ "OK"
+ }
+}
+
+fun `should work with generic extension function`(): String {
+ fun Property.assign(v: T) = this.set(v)
+ data class IntProperty(var v: Int): Property {
+ override fun set(v: Int) {
+ this.v = v
+ }
+ override fun get() = this.v
+ }
+ data class IntTask(val input: IntProperty)
+ val task = IntTask(IntProperty(0))
+ task.input = 42
+
+ return if (task.input.get() != 42) {
+ "Fail: ${task.input.get()}"
+ } else {
+ "OK"
+ }
+}
+
+fun `should work with callable receiver`(): String {
+ fun StringProperty.assign(r: StringProperty.() -> Unit) = r.invoke(this)
+ val task = Task(StringProperty("Fail"))
+ task.input = { this.set("OK") }
+
+ return if (task.input.get() != "OK") {
+ "Fail: ${task.input.get()}"
+ } else {
+ "OK"
+ }
+}
+
+fun `should work with Java classes`(): String {
+ var taskWithField = JavaTaskWithField(StringProperty("Fail"))
+ taskWithField.input = "OK"
+ if (taskWithField.input.get() != "OK") return "Fail for Java: ${taskWithField.input.get()}"
+ taskWithField = JavaTaskWithField(StringProperty("Fail"))
+ taskWithField.input = StringProperty("OK")
+ if (taskWithField.input.get() != "OK") return "Fail for Java: ${taskWithField.input.get()}"
+
+ var taskWithProperty = JavaTaskWithProperty(StringProperty("Fail"))
+ taskWithProperty.input = "OK"
+ if (taskWithProperty.input.get() != "OK") return "Fail for Java: ${taskWithProperty.input.get()}"
+ taskWithProperty = JavaTaskWithProperty(StringProperty("Fail"))
+ taskWithProperty.input = StringProperty("OK")
+ if (taskWithProperty.input.get() != "OK") return "Fail for Java: ${taskWithProperty.input.get()}"
+
+ return "OK"
+}
+
+fun box(): String {
+ var result = `should work with assignment for raw type`()
+ if (result != "OK") return result
+
+ result = `should work with assignment for wrapped type`()
+ if (result != "OK") return result
+
+ result = `should work with assignment with apply for raw type`()
+ if (result != "OK") return result
+
+ result = `should work with assignment with apply for wrapped type`()
+ if (result != "OK") return result
+
+ result = `should work with extension function`()
+ if (result != "OK") return result
+
+ result = `should work with extension function for interface type`()
+ if (result != "OK") return result
+
+ result = `should work with generic extension function`()
+ if (result != "OK") return result
+
+ result = `should work with callable receiver`()
+ if (result != "OK") return result
+
+ result = `should work with Java classes`()
+ if (result != "OK") return result
+
+ return "OK"
+}
diff --git a/plugins/assign-plugin/testData/codegen/varBehaviour.kt b/plugins/assign-plugin/testData/codegen/varBehaviour.kt
new file mode 100644
index 00000000000..48dd26905ac
--- /dev/null
+++ b/plugins/assign-plugin/testData/codegen/varBehaviour.kt
@@ -0,0 +1,65 @@
+annotation class ValueContainer
+
+@ValueContainer
+data class StringProperty(var v: String) {
+ fun assign(v: String) {
+ this.v = v
+ }
+ fun assign(v: StringProperty) {
+ this.v = v.get()
+ }
+ fun get() = v
+}
+
+data class Task(var input: StringProperty)
+
+fun `test local var reference and value`(): String {
+ var property = StringProperty("OK")
+ var originalProperty = property
+ property = StringProperty("Fail")
+
+ return when {
+ originalProperty.get() != "OK" -> "Fail: ${originalProperty.get()}"
+ originalProperty == property -> "Fail: originalProperty == property"
+ else -> "OK"
+ }
+}
+
+fun `test class property var reference and value`(): String {
+ val task = Task(StringProperty("OK"))
+ val originalProperty = task.input
+ task.input = StringProperty("Fail")
+
+ return when {
+ originalProperty.get() != "OK" -> "Fail: ${originalProperty.get()}"
+ originalProperty == task.input -> "Fail: originalProperty == task.input"
+ else -> "OK"
+ }
+}
+
+fun `test class property var reference and value with apply`(): String {
+ val task = Task(StringProperty("OK"))
+ val originalProperty = task.input
+ task.apply {
+ input = StringProperty("Fail")
+ }
+
+ return when {
+ originalProperty.get() != "OK" -> "Fail: ${originalProperty.get()}"
+ originalProperty == task.input -> "Fail: originalProperty == task.input"
+ else -> "OK"
+ }
+}
+
+fun box(): String {
+ var result = `test local var reference and value`()
+ if (result != "OK") return result
+
+ result = `test class property var reference and value`()
+ if (result != "OK") return result
+
+ result = `test class property var reference and value with apply`()
+ if (result != "OK") return result
+
+ return "OK"
+}
diff --git a/plugins/assign-plugin/testData/diagnostics/incorrectUsage.diag.txt b/plugins/assign-plugin/testData/diagnostics/incorrectUsage.diag.txt
new file mode 100644
index 00000000000..c36c5ec1dc1
--- /dev/null
+++ b/plugins/assign-plugin/testData/diagnostics/incorrectUsage.diag.txt
@@ -0,0 +1,19 @@
+/incorrectUsage.kt:18:5: error: val cannot be reassigned
+ task.input = 42
+ ^
+/incorrectUsage.kt:18:16: error: no applicable 'assign' function found for '=' overload
+ task.input = 42
+ ^
+/incorrectUsage.kt:18:18: error: the integer literal does not conform to the expected type StringProperty
+ task.input = 42
+ ^
+/incorrectUsage.kt:24:9: error: val cannot be reassigned
+ input = 42
+ ^
+/incorrectUsage.kt:24:15: error: no applicable 'assign' function found for '=' overload
+ input = 42
+ ^
+/incorrectUsage.kt:24:17: error: the integer literal does not conform to the expected type StringProperty
+ input = 42
+ ^
+
diff --git a/plugins/assign-plugin/testData/diagnostics/incorrectUsage.fir.kt b/plugins/assign-plugin/testData/diagnostics/incorrectUsage.fir.kt
new file mode 100644
index 00000000000..27a23293d5d
--- /dev/null
+++ b/plugins/assign-plugin/testData/diagnostics/incorrectUsage.fir.kt
@@ -0,0 +1,26 @@
+annotation class ValueContainer
+
+@ValueContainer
+data class StringProperty(var v: String) {
+ fun assign(v: String) {
+ this.v = v
+ }
+ fun assign(v: StringProperty) {
+ this.v = v.get()
+ }
+ fun get(): String = v
+}
+
+data class Task(val input: StringProperty)
+
+fun `should report error if type doesn't match`() {
+ val task = Task(StringProperty("Fail"))
+ task.input = 42
+}
+
+fun `should report error if type doesn't match with apply`() {
+ val task = Task(StringProperty("Fail"))
+ task.apply {
+ input = 42
+ }
+}
diff --git a/plugins/assign-plugin/testData/diagnostics/incorrectUsage.kt b/plugins/assign-plugin/testData/diagnostics/incorrectUsage.kt
new file mode 100644
index 00000000000..f78e00dd848
--- /dev/null
+++ b/plugins/assign-plugin/testData/diagnostics/incorrectUsage.kt
@@ -0,0 +1,27 @@
+annotation class ValueContainer
+
+@ValueContainer
+data class StringProperty(var v: String) {
+ fun assign(v: String) {
+ this.v = v
+ }
+ fun assign(v: StringProperty) {
+ this.v = v.get()
+ }
+ fun get(): String = v
+}
+
+data class Task(val input: StringProperty)
+
+fun `should report error if type doesn't match`() {
+ val task = Task(StringProperty("Fail"))
+ task.input = 42
+}
+
+fun `should report error if type doesn't match with apply`() {
+ val task = Task(StringProperty("Fail"))
+ task.apply {
+ input = 42
+ }
+}
+
diff --git a/plugins/assign-plugin/testData/diagnostics/incorrectUsage.txt b/plugins/assign-plugin/testData/diagnostics/incorrectUsage.txt
new file mode 100644
index 00000000000..505b97e4793
--- /dev/null
+++ b/plugins/assign-plugin/testData/diagnostics/incorrectUsage.txt
@@ -0,0 +1,35 @@
+package
+
+public fun `should report error if type doesn't match`(): kotlin.Unit
+public fun `should report error if type doesn't match with apply`(): kotlin.Unit
+
+@ValueContainer public final data class StringProperty {
+ public constructor StringProperty(/*0*/ v: kotlin.String)
+ public final var v: kotlin.String
+ public final fun assign(/*0*/ v: StringProperty): kotlin.Unit
+ public final fun assign(/*0*/ v: kotlin.String): kotlin.Unit
+ public final operator /*synthesized*/ fun component1(): kotlin.String
+ public final /*synthesized*/ fun copy(/*0*/ v: kotlin.String = ...): StringProperty
+ public open override /*1*/ /*synthesized*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
+ public final fun get(): kotlin.String
+ public open override /*1*/ /*synthesized*/ fun hashCode(): kotlin.Int
+ public open override /*1*/ /*synthesized*/ fun toString(): kotlin.String
+}
+
+public final data class Task {
+ public constructor Task(/*0*/ input: StringProperty)
+ public final val input: StringProperty
+ public final operator /*synthesized*/ fun component1(): StringProperty
+ public final /*synthesized*/ fun copy(/*0*/ input: StringProperty = ...): Task
+ public open override /*1*/ /*synthesized*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
+ public open override /*1*/ /*synthesized*/ fun hashCode(): kotlin.Int
+ public open override /*1*/ /*synthesized*/ fun toString(): kotlin.String
+}
+
+public final annotation class ValueContainer : kotlin.Annotation {
+ public constructor ValueContainer()
+ public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
+ public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
+ public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
+}
+
diff --git a/plugins/assign-plugin/testData/diagnostics/localVariables.diag.txt b/plugins/assign-plugin/testData/diagnostics/localVariables.diag.txt
new file mode 100644
index 00000000000..76b51ecdc1b
--- /dev/null
+++ b/plugins/assign-plugin/testData/diagnostics/localVariables.diag.txt
@@ -0,0 +1,22 @@
+/localVariables.kt:18:5: error: val cannot be reassigned
+ property = "Fail"
+ ^
+/localVariables.kt:18:16: error: type mismatch: inferred type is String but StringProperty was expected
+ property = "Fail"
+ ^
+/localVariables.kt:23:5: error: val cannot be reassigned
+ property = StringProperty("Fail")
+ ^
+/localVariables.kt:28:16: error: type mismatch: inferred type is String but StringProperty was expected
+ property = "Fail"
+ ^
+/localVariables.kt:38:9: error: val cannot be reassigned
+ property = "Fail"
+ ^
+/localVariables.kt:38:20: error: type mismatch: inferred type is String but StringProperty was expected
+ property = "Fail"
+ ^
+/localVariables.kt:42:9: error: val cannot be reassigned
+ property = StringProperty("Fail")
+ ^
+
diff --git a/plugins/assign-plugin/testData/diagnostics/localVariables.fir.kt b/plugins/assign-plugin/testData/diagnostics/localVariables.fir.kt
new file mode 100644
index 00000000000..37ebcae5cc2
--- /dev/null
+++ b/plugins/assign-plugin/testData/diagnostics/localVariables.fir.kt
@@ -0,0 +1,44 @@
+// !DIAGNOSTICS: -ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE
+
+annotation class ValueContainer
+
+@ValueContainer
+data class StringProperty(var v: String) {
+ fun assign(v: String) {
+ this.v = v
+ }
+ fun assign(v: StringProperty) {
+ this.v = v.get()
+ }
+ fun get(): String = v
+}
+
+fun `should not work with local val for different type`() {
+ val property = StringProperty("OK")
+ property = "Fail"
+}
+
+fun `should not work with local val for same type`() {
+ val property = StringProperty("OK")
+ property = StringProperty("Fail")
+}
+
+fun `should not work with local var for different type`() {
+ var property = StringProperty("OK")
+ property = "Fail"
+}
+
+fun `should work with local var for same type`() {
+ var property = StringProperty("OK")
+ property = StringProperty("Fail")
+}
+
+fun `should not work with method parameters`() {
+ fun m1(property: StringProperty): Unit {
+ property = "Fail"
+ }
+
+ fun m2(property: StringProperty): Unit {
+ property = StringProperty("Fail")
+ }
+}
diff --git a/plugins/assign-plugin/testData/diagnostics/localVariables.kt b/plugins/assign-plugin/testData/diagnostics/localVariables.kt
new file mode 100644
index 00000000000..d065fd13fc5
--- /dev/null
+++ b/plugins/assign-plugin/testData/diagnostics/localVariables.kt
@@ -0,0 +1,44 @@
+// !DIAGNOSTICS: -ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE
+
+annotation class ValueContainer
+
+@ValueContainer
+data class StringProperty(var v: String) {
+ fun assign(v: String) {
+ this.v = v
+ }
+ fun assign(v: StringProperty) {
+ this.v = v.get()
+ }
+ fun get(): String = v
+}
+
+fun `should not work with local val for different type`() {
+ val property = StringProperty("OK")
+ property = "Fail"
+}
+
+fun `should not work with local val for same type`() {
+ val property = StringProperty("OK")
+ property = StringProperty("Fail")
+}
+
+fun `should not work with local var for different type`() {
+ var property = StringProperty("OK")
+ property = "Fail"
+}
+
+fun `should work with local var for same type`() {
+ var property = StringProperty("OK")
+ property = StringProperty("Fail")
+}
+
+fun `should not work with method parameters`() {
+ fun m1(property: StringProperty): Unit {
+ property = "Fail"
+ }
+
+ fun m2(property: StringProperty): Unit {
+ property = StringProperty("Fail")
+ }
+}
diff --git a/plugins/assign-plugin/testData/diagnostics/localVariables.txt b/plugins/assign-plugin/testData/diagnostics/localVariables.txt
new file mode 100644
index 00000000000..26c3aafb67f
--- /dev/null
+++ b/plugins/assign-plugin/testData/diagnostics/localVariables.txt
@@ -0,0 +1,27 @@
+package
+
+public fun `should not work with local val for different type`(): kotlin.Unit
+public fun `should not work with local val for same type`(): kotlin.Unit
+public fun `should not work with local var for different type`(): kotlin.Unit
+public fun `should not work with method parameters`(): kotlin.Unit
+public fun `should work with local var for same type`(): kotlin.Unit
+
+@ValueContainer public final data class StringProperty {
+ public constructor StringProperty(/*0*/ v: kotlin.String)
+ public final var v: kotlin.String
+ public final fun assign(/*0*/ v: StringProperty): kotlin.Unit
+ public final fun assign(/*0*/ v: kotlin.String): kotlin.Unit
+ public final operator /*synthesized*/ fun component1(): kotlin.String
+ public final /*synthesized*/ fun copy(/*0*/ v: kotlin.String = ...): StringProperty
+ public open override /*1*/ /*synthesized*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
+ public final fun get(): kotlin.String
+ public open override /*1*/ /*synthesized*/ fun hashCode(): kotlin.Int
+ public open override /*1*/ /*synthesized*/ fun toString(): kotlin.String
+}
+
+public final annotation class ValueContainer : kotlin.Annotation {
+ public constructor ValueContainer()
+ public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
+ public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
+ public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
+}
diff --git a/plugins/assign-plugin/testData/diagnostics/methodDeclaration.diag.txt b/plugins/assign-plugin/testData/diagnostics/methodDeclaration.diag.txt
new file mode 100644
index 00000000000..6abfe2bd016
--- /dev/null
+++ b/plugins/assign-plugin/testData/diagnostics/methodDeclaration.diag.txt
@@ -0,0 +1,28 @@
+/methodDeclaration.kt:5:28: error: function 'assign' used for '=' overload should return 'Unit'
+ fun assign(v: String): String {
+ ^
+/methodDeclaration.kt:9:36: error: function 'assign' used for '=' overload should return 'Unit'
+ fun assign(v: StringProperty): String {
+ ^
+/methodDeclaration.kt:16:36: error: function 'assign' used for '=' overload should return 'Unit'
+fun StringProperty.assign(v: Int): String {
+ ^
+/methodDeclaration.kt:25:5: error: val cannot be reassigned
+ task.input = "42"
+ ^
+/methodDeclaration.kt:25:16: error: function 'assign' used for '=' overload should return 'Unit'
+ task.input = "42"
+ ^
+/methodDeclaration.kt:26:5: error: val cannot be reassigned
+ task.input = 42
+ ^
+/methodDeclaration.kt:26:16: error: function 'assign' used for '=' overload should return 'Unit'
+ task.input = 42
+ ^
+/methodDeclaration.kt:35:5: error: val cannot be reassigned
+ task.input = 42
+ ^
+/methodDeclaration.kt:35:18: error: the integer literal does not conform to the expected type IntProperty
+ task.input = 42
+ ^
+
diff --git a/plugins/assign-plugin/testData/diagnostics/methodDeclaration.fir.kt b/plugins/assign-plugin/testData/diagnostics/methodDeclaration.fir.kt
new file mode 100644
index 00000000000..d608dea6006
--- /dev/null
+++ b/plugins/assign-plugin/testData/diagnostics/methodDeclaration.fir.kt
@@ -0,0 +1,36 @@
+annotation class ValueContainer
+
+@ValueContainer
+data class StringProperty(var v: String) {
+ fun assign(v: String): String {
+ this.v = v
+ return ""
+ }
+ fun assign(v: StringProperty): String {
+ this.v = v.get()
+ return ""
+ }
+ fun get(): String = v
+}
+
+fun StringProperty.assign(v: Int): String {
+ this.v = "OK"
+ return ""
+}
+
+data class Task(val input: StringProperty)
+
+fun `should report an error for assign method return type on assignment for annotated class`() {
+ val task = Task(StringProperty("Fail"))
+ task.input = "42"
+ task.input = 42
+}
+
+fun `should not report an error for assign return type for unannotated class`() {
+ data class IntProperty(var v: Int)
+ fun IntProperty.assign(v: Int): String = "OK"
+ data class IntTask(val input: IntProperty)
+
+ val task = IntTask(IntProperty(42))
+ task.input = 42
+}
diff --git a/plugins/assign-plugin/testData/diagnostics/methodDeclaration.kt b/plugins/assign-plugin/testData/diagnostics/methodDeclaration.kt
new file mode 100644
index 00000000000..b218d9791b7
--- /dev/null
+++ b/plugins/assign-plugin/testData/diagnostics/methodDeclaration.kt
@@ -0,0 +1,36 @@
+annotation class ValueContainer
+
+@ValueContainer
+data class StringProperty(var v: String) {
+ fun assign(v: String): String {
+ this.v = v
+ return ""
+ }
+ fun assign(v: StringProperty): String {
+ this.v = v.get()
+ return ""
+ }
+ fun get(): String = v
+}
+
+fun StringProperty.assign(v: Int): String {
+ this.v = "OK"
+ return ""
+}
+
+data class Task(val input: StringProperty)
+
+fun `should report an error for assign method return type on assignment for annotated class`() {
+ val task = Task(StringProperty("Fail"))
+ task.input = "42"
+ task.input = 42
+}
+
+fun `should not report an error for assign return type for unannotated class`() {
+ data class IntProperty(var v: Int)
+ fun IntProperty.assign(v: Int): String = "OK"
+ data class IntTask(val input: IntProperty)
+
+ val task = IntTask(IntProperty(42))
+ task.input = 42
+}
diff --git a/plugins/assign-plugin/testData/diagnostics/methodDeclaration.txt b/plugins/assign-plugin/testData/diagnostics/methodDeclaration.txt
new file mode 100644
index 00000000000..bfd70ec61f8
--- /dev/null
+++ b/plugins/assign-plugin/testData/diagnostics/methodDeclaration.txt
@@ -0,0 +1,36 @@
+package
+
+public fun `should not report an error for assign return type for unannotated class`(): kotlin.Unit
+public fun `should report an error for assign method return type on assignment for annotated class`(): kotlin.Unit
+public fun StringProperty.assign(/*0*/ v: kotlin.Int): kotlin.String
+
+@ValueContainer public final data class StringProperty {
+ public constructor StringProperty(/*0*/ v: kotlin.String)
+ public final var v: kotlin.String
+ public final fun assign(/*0*/ v: StringProperty): kotlin.String
+ public final fun assign(/*0*/ v: kotlin.String): kotlin.String
+ public final operator /*synthesized*/ fun component1(): kotlin.String
+ public final /*synthesized*/ fun copy(/*0*/ v: kotlin.String = ...): StringProperty
+ public open override /*1*/ /*synthesized*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
+ public final fun get(): kotlin.String
+ public open override /*1*/ /*synthesized*/ fun hashCode(): kotlin.Int
+ public open override /*1*/ /*synthesized*/ fun toString(): kotlin.String
+}
+
+public final data class Task {
+ public constructor Task(/*0*/ input: StringProperty)
+ public final val input: StringProperty
+ public final operator /*synthesized*/ fun component1(): StringProperty
+ public final /*synthesized*/ fun copy(/*0*/ input: StringProperty = ...): Task
+ public open override /*1*/ /*synthesized*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
+ public open override /*1*/ /*synthesized*/ fun hashCode(): kotlin.Int
+ public open override /*1*/ /*synthesized*/ fun toString(): kotlin.String
+}
+
+public final annotation class ValueContainer : kotlin.Annotation {
+ public constructor ValueContainer()
+ public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
+ public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
+ public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
+}
+
diff --git a/plugins/assign-plugin/testData/diagnostics/noAnnotation.diag.txt b/plugins/assign-plugin/testData/diagnostics/noAnnotation.diag.txt
new file mode 100644
index 00000000000..519139ec454
--- /dev/null
+++ b/plugins/assign-plugin/testData/diagnostics/noAnnotation.diag.txt
@@ -0,0 +1,31 @@
+/noAnnotation.kt:17:5: error: val cannot be reassigned
+ task.input = "OK"
+ ^
+/noAnnotation.kt:17:18: error: type mismatch: inferred type is String but StringProperty was expected
+ task.input = "OK"
+ ^
+/noAnnotation.kt:18:5: error: val cannot be reassigned
+ task.input = StringProperty("OK")
+ ^
+/noAnnotation.kt:20:9: error: val cannot be reassigned
+ input = "OK"
+ ^
+/noAnnotation.kt:20:17: error: type mismatch: inferred type is String but StringProperty was expected
+ input = "OK"
+ ^
+/noAnnotation.kt:23:9: error: val cannot be reassigned
+ input = StringProperty("OK")
+ ^
+/noAnnotation.kt:25:5: error: val cannot be reassigned
+ task.input = 42
+ ^
+/noAnnotation.kt:25:18: error: the integer literal does not conform to the expected type StringProperty
+ task.input = 42
+ ^
+/noAnnotation.kt:27:9: error: val cannot be reassigned
+ input = 42
+ ^
+/noAnnotation.kt:27:17: error: the integer literal does not conform to the expected type StringProperty
+ input = 42
+ ^
+
diff --git a/plugins/assign-plugin/testData/diagnostics/noAnnotation.fir.kt b/plugins/assign-plugin/testData/diagnostics/noAnnotation.fir.kt
new file mode 100644
index 00000000000..2ad50135376
--- /dev/null
+++ b/plugins/assign-plugin/testData/diagnostics/noAnnotation.fir.kt
@@ -0,0 +1,29 @@
+data class StringProperty(var v: String) {
+ fun assign(v: String) {
+ this.v = v
+ }
+ fun assign(v: StringProperty) {
+ this.v = v.get()
+ }
+ fun get(): String = v
+}
+
+fun StringProperty.assign(v: Int) = this.assign("OK")
+
+data class Task(val input: StringProperty)
+
+fun `should not work with assignment when there is no annotation on a type`() {
+ val task = Task(StringProperty("Fail"))
+ task.input = "OK"
+ task.input = StringProperty("OK")
+ task.apply {
+ input = "OK"
+ }
+ task.apply {
+ input = StringProperty("OK")
+ }
+ task.input = 42
+ task.apply {
+ input = 42
+ }
+}
diff --git a/plugins/assign-plugin/testData/diagnostics/noAnnotation.kt b/plugins/assign-plugin/testData/diagnostics/noAnnotation.kt
new file mode 100644
index 00000000000..684c16caed2
--- /dev/null
+++ b/plugins/assign-plugin/testData/diagnostics/noAnnotation.kt
@@ -0,0 +1,29 @@
+data class StringProperty(var v: String) {
+ fun assign(v: String) {
+ this.v = v
+ }
+ fun assign(v: StringProperty) {
+ this.v = v.get()
+ }
+ fun get(): String = v
+}
+
+fun StringProperty.assign(v: Int) = this.assign("OK")
+
+data class Task(val input: StringProperty)
+
+fun `should not work with assignment when there is no annotation on a type`() {
+ val task = Task(StringProperty("Fail"))
+ task.input = "OK"
+ task.input = StringProperty("OK")
+ task.apply {
+ input = "OK"
+ }
+ task.apply {
+ input = StringProperty("OK")
+ }
+ task.input = 42
+ task.apply {
+ input = 42
+ }
+}
diff --git a/plugins/assign-plugin/testData/diagnostics/noAnnotation.txt b/plugins/assign-plugin/testData/diagnostics/noAnnotation.txt
new file mode 100644
index 00000000000..3c883bdf925
--- /dev/null
+++ b/plugins/assign-plugin/testData/diagnostics/noAnnotation.txt
@@ -0,0 +1,28 @@
+package
+
+public fun `should not work with assignment when there is no annotation on a type`(): kotlin.Unit
+public fun StringProperty.assign(/*0*/ v: kotlin.Int): kotlin.Unit
+
+public final data class StringProperty {
+ public constructor StringProperty(/*0*/ v: kotlin.String)
+ public final var v: kotlin.String
+ public final fun assign(/*0*/ v: StringProperty): kotlin.Unit
+ public final fun assign(/*0*/ v: kotlin.String): kotlin.Unit
+ public final operator /*synthesized*/ fun component1(): kotlin.String
+ public final /*synthesized*/ fun copy(/*0*/ v: kotlin.String = ...): StringProperty
+ public open override /*1*/ /*synthesized*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
+ public final fun get(): kotlin.String
+ public open override /*1*/ /*synthesized*/ fun hashCode(): kotlin.Int
+ public open override /*1*/ /*synthesized*/ fun toString(): kotlin.String
+}
+
+public final data class Task {
+ public constructor Task(/*0*/ input: StringProperty)
+ public final val input: StringProperty
+ public final operator /*synthesized*/ fun component1(): StringProperty
+ public final /*synthesized*/ fun copy(/*0*/ input: StringProperty = ...): Task
+ public open override /*1*/ /*synthesized*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
+ public open override /*1*/ /*synthesized*/ fun hashCode(): kotlin.Int
+ public open override /*1*/ /*synthesized*/ fun toString(): kotlin.String
+}
+
diff --git a/plugins/assign-plugin/testData/diagnostics/otherOperators.diag.txt b/plugins/assign-plugin/testData/diagnostics/otherOperators.diag.txt
new file mode 100644
index 00000000000..112db49b031
--- /dev/null
+++ b/plugins/assign-plugin/testData/diagnostics/otherOperators.diag.txt
@@ -0,0 +1,89 @@
+/otherOperators.kt:21:16: error: unresolved reference. None of the following candidates is applicable because of receiver type mismatch:
+public operator fun String?.plus(other: Any?): String defined in kotlin
+ task.input += StringProperty("Fail")
+ ^
+/otherOperators.kt:22:21: error: unresolved reference. None of the following candidates is applicable because of receiver type mismatch:
+public operator fun String?.plus(other: Any?): String defined in kotlin
+ nullTask?.input += StringProperty("Fail")
+ ^
+/otherOperators.kt:25:16: error: unresolved reference: <=
+ task.input <= StringProperty("Fail")
+ ^
+/otherOperators.kt:26:21: error: unresolved reference: <=
+ nullTask?.input <= StringProperty("Fail")
+ ^
+/otherOperators.kt:29:16: error: unresolved reference: >=
+ task.input >= StringProperty("Fail")
+ ^
+/otherOperators.kt:30:21: error: unresolved reference: >=
+ nullTask?.input >= StringProperty("Fail")
+ ^
+/otherOperators.kt:33:15: error: unresolved reference: task.input[0]
+ task.input[0] = StringProperty("Fail")
+ ^
+/otherOperators.kt:33:15: error: no set method providing array access
+ task.input[0] = StringProperty("Fail")
+ ^
+/otherOperators.kt:34:20: error: unresolved reference: nullTask?.input[0]
+ nullTask?.input[0] = StringProperty("Fail")
+ ^
+/otherOperators.kt:34:20: error: no set method providing array access
+ nullTask?.input[0] = StringProperty("Fail")
+ ^
+/otherOperators.kt:37:15: error: unresolved reference: task.input[0, 0]
+ task.input[0, 0] = StringProperty("Fail")
+ ^
+/otherOperators.kt:37:15: error: no set method providing array access
+ task.input[0, 0] = StringProperty("Fail")
+ ^
+/otherOperators.kt:38:20: error: unresolved reference: nullTask?.input[0, 0]
+ nullTask?.input[0, 0] = StringProperty("Fail")
+ ^
+/otherOperators.kt:38:20: error: no set method providing array access
+ nullTask?.input[0, 0] = StringProperty("Fail")
+ ^
+/otherOperators.kt:41:15: error: unresolved reference: task.input[0, 0, 0]
+ task.input[0, 0, 0] = StringProperty("Fail")
+ ^
+/otherOperators.kt:41:15: error: no set method providing array access
+ task.input[0, 0, 0] = StringProperty("Fail")
+ ^
+/otherOperators.kt:42:20: error: unresolved reference: nullTask?.input[0, 0, 0]
+ nullTask?.input[0, 0, 0] = StringProperty("Fail")
+ ^
+/otherOperators.kt:42:20: error: no set method providing array access
+ nullTask?.input[0, 0, 0] = StringProperty("Fail")
+ ^
+/otherOperators.kt:45:5: error: 'operator' modifier is required on 'get' in 'StringProperty'
+ task.input[0] += StringProperty("Fail")
+ ^
+/otherOperators.kt:45:15: error: no set method providing array access
+ task.input[0] += StringProperty("Fail")
+ ^
+/otherOperators.kt:45:16: error: too many arguments for public final fun get(): String defined in StringProperty
+ task.input[0] += StringProperty("Fail")
+ ^
+/otherOperators.kt:46:5: error: 'operator' modifier is required on 'get' in 'StringProperty'
+ nullTask?.input[0] += StringProperty("Fail")
+ ^
+/otherOperators.kt:46:5: error: only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type StringProperty?
+ nullTask?.input[0] += StringProperty("Fail")
+ ^
+/otherOperators.kt:46:20: error: no set method providing array access
+ nullTask?.input[0] += StringProperty("Fail")
+ ^
+/otherOperators.kt:46:21: error: too many arguments for public final fun get(): String defined in StringProperty
+ nullTask?.input[0] += StringProperty("Fail")
+ ^
+/otherOperators.kt:50:9: error: unresolved reference: task[0]
+ task[0] = StringProperty("Fail")
+ ^
+/otherOperators.kt:50:9: error: no set method providing array access
+ task[0] = StringProperty("Fail")
+ ^
+/otherOperators.kt:53:10: error: variable expected
+ task.get(0) = StringProperty("Fail")
+ ^
+/otherOperators.kt:54:15: error: variable expected
+ nullTask?.get(0) = StringProperty("Fail")
+ ^
diff --git a/plugins/assign-plugin/testData/diagnostics/otherOperators.fir.kt b/plugins/assign-plugin/testData/diagnostics/otherOperators.fir.kt
new file mode 100644
index 00000000000..90429adcce5
--- /dev/null
+++ b/plugins/assign-plugin/testData/diagnostics/otherOperators.fir.kt
@@ -0,0 +1,55 @@
+annotation class ValueContainer
+
+@ValueContainer
+data class StringProperty(var v: String) {
+ fun assign(v: String) {
+ this.v = v
+ }
+ fun assign(v: StringProperty) {
+ this.v = v.get()
+ }
+ fun get(): String = v
+}
+
+data class Task(val input: StringProperty)
+
+fun `should not effect error reporting for other operators`() {
+ val task = Task(StringProperty("Fail"))
+ val nullTask: Task? = null
+
+ // a.b += c
+ task.input += StringProperty("Fail")
+ nullTask?.input += StringProperty("Fail")
+
+ // a.b <= c
+ task.input <= StringProperty("Fail")
+ nullTask?.input <= StringProperty("Fail")
+
+ // a.b >= c
+ task.input >= StringProperty("Fail")
+ nullTask?.input >= StringProperty("Fail")
+
+ // a.b[c] = d
+ task.input[0] = StringProperty("Fail")
+ nullTask?.input[0] = StringProperty("Fail")
+
+ // a.b[c, d] = e
+ task.input[0, 0] = StringProperty("Fail")
+ nullTask?.input[0, 0] = StringProperty("Fail")
+
+ // a.b[c,..,d] = e
+ task.input[0, 0, 0] = StringProperty("Fail")
+ nullTask?.input[0, 0, 0] = StringProperty("Fail")
+
+ // a?.b[c] += d
+ task.input[0] += StringProperty("Fail")
+ nullTask?.input[0] += StringProperty("Fail")
+
+ // a[i] = b should not be translated to a.get(i).assign(b)
+ operator fun Task.get(i: Int) = this.input
+ task[0] = StringProperty("Fail")
+
+ // a.get(i) = b should not be translated to a.get(i).assign(b)
+ task.get(0) = StringProperty("Fail")
+ nullTask?.get(0) = StringProperty("Fail")
+}
diff --git a/plugins/assign-plugin/testData/diagnostics/otherOperators.kt b/plugins/assign-plugin/testData/diagnostics/otherOperators.kt
new file mode 100644
index 00000000000..2201d31f6b1
--- /dev/null
+++ b/plugins/assign-plugin/testData/diagnostics/otherOperators.kt
@@ -0,0 +1,55 @@
+annotation class ValueContainer
+
+@ValueContainer
+data class StringProperty(var v: String) {
+ fun assign(v: String) {
+ this.v = v
+ }
+ fun assign(v: StringProperty) {
+ this.v = v.get()
+ }
+ fun get(): String = v
+}
+
+data class Task(val input: StringProperty)
+
+fun `should not effect error reporting for other operators`() {
+ val task = Task(StringProperty("Fail"))
+ val nullTask: Task? = null
+
+ // a.b += c
+ task.input += StringProperty("Fail")
+ nullTask?.input += StringProperty("Fail")
+
+ // a.b <= c
+ task.input <= StringProperty("Fail")
+ nullTask?.input <= StringProperty("Fail")
+
+ // a.b >= c
+ task.input >= StringProperty("Fail")
+ nullTask?.input >= StringProperty("Fail")
+
+ // a.b[c] = d
+ task.input[0] = StringProperty("Fail")
+ nullTask?.input[0] = StringProperty("Fail")
+
+ // a.b[c, d] = e
+ task.input[0, 0] = StringProperty("Fail")
+ nullTask?.input[0, 0] = StringProperty("Fail")
+
+ // a.b[c,..,d] = e
+ task.input[0, 0, 0] = StringProperty("Fail")
+ nullTask?.input[0, 0, 0] = StringProperty("Fail")
+
+ // a?.b[c] += d
+ task.input[0] += StringProperty("Fail")
+ nullTask?.input[0] += StringProperty("Fail")
+
+ // a[i] = b should not be translated to a.get(i).assign(b)
+ operator fun Task.get(i: Int) = this.input
+ task[0] = StringProperty("Fail")
+
+ // a.get(i) = b should not be translated to a.get(i).assign(b)
+ task.get(0) = StringProperty("Fail")
+ nullTask?.get(0) = StringProperty("Fail")
+}
diff --git a/plugins/assign-plugin/testData/diagnostics/otherOperators.txt b/plugins/assign-plugin/testData/diagnostics/otherOperators.txt
new file mode 100644
index 00000000000..a861ec80ad6
--- /dev/null
+++ b/plugins/assign-plugin/testData/diagnostics/otherOperators.txt
@@ -0,0 +1,33 @@
+package
+
+public fun `should not effect error reporting for other operators`(): kotlin.Unit
+
+@ValueContainer public final data class StringProperty {
+ public constructor StringProperty(/*0*/ v: kotlin.String)
+ public final var v: kotlin.String
+ public final fun assign(/*0*/ v: StringProperty): kotlin.Unit
+ public final fun assign(/*0*/ v: kotlin.String): kotlin.Unit
+ public final operator /*synthesized*/ fun component1(): kotlin.String
+ public final /*synthesized*/ fun copy(/*0*/ v: kotlin.String = ...): StringProperty
+ public open override /*1*/ /*synthesized*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
+ public final fun get(): kotlin.String
+ public open override /*1*/ /*synthesized*/ fun hashCode(): kotlin.Int
+ public open override /*1*/ /*synthesized*/ fun toString(): kotlin.String
+}
+
+public final data class Task {
+ public constructor Task(/*0*/ input: StringProperty)
+ public final val input: StringProperty
+ public final operator /*synthesized*/ fun component1(): StringProperty
+ public final /*synthesized*/ fun copy(/*0*/ input: StringProperty = ...): Task
+ public open override /*1*/ /*synthesized*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
+ public open override /*1*/ /*synthesized*/ fun hashCode(): kotlin.Int
+ public open override /*1*/ /*synthesized*/ fun toString(): kotlin.String
+}
+
+public final annotation class ValueContainer : kotlin.Annotation {
+ public constructor ValueContainer()
+ public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
+ public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
+ public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
+}
diff --git a/plugins/assign-plugin/testData/diagnostics/plusAssignPrecedence.diag.txt b/plugins/assign-plugin/testData/diagnostics/plusAssignPrecedence.diag.txt
new file mode 100644
index 00000000000..e73904b0261
--- /dev/null
+++ b/plugins/assign-plugin/testData/diagnostics/plusAssignPrecedence.diag.txt
@@ -0,0 +1,17 @@
+/plusAssignPrecedence.kt:96:19: error: unresolved reference. None of the following candidates is applicable because of receiver type mismatch:
+public operator fun String?.plus(other: Any?): String defined in kotlin
+ task.valInput += "K"
+ ^
+/plusAssignPrecedence.kt:97:19: error: unresolved reference. None of the following candidates is applicable because of receiver type mismatch:
+public operator fun String?.plus(other: Any?): String defined in kotlin
+ task.varInput += "K"
+ ^
+/plusAssignPrecedence.kt:99:5: error: val cannot be reassigned
+ task.valInputWithPlus += "K"
+ ^
+/plusAssignPrecedence.kt:101:40: error: assignment operators ambiguity:
+public final operator fun plus(v: String): StringPropertyWithPlusAndPlusAssign defined in StringPropertyWithPlusAndPlusAssign
+public final operator fun plusAssign(v: String): Unit defined in StringPropertyWithPlusAndPlusAssign
+ task.varInputWithPlusAndPlusAssign += "K"
+ ^
+
diff --git a/plugins/assign-plugin/testData/diagnostics/plusAssignPrecedence.fir.kt b/plugins/assign-plugin/testData/diagnostics/plusAssignPrecedence.fir.kt
new file mode 100644
index 00000000000..bf436bfb97b
--- /dev/null
+++ b/plugins/assign-plugin/testData/diagnostics/plusAssignPrecedence.fir.kt
@@ -0,0 +1,104 @@
+annotation class ValueContainer
+
+abstract class AbstractStringProperty(protected var v: String) {
+ fun get(): String {
+ return v
+ }
+}
+
+@ValueContainer
+class StringProperty(v: String) : AbstractStringProperty(v) {
+ fun assign(v: String) {
+ this.v = v
+ }
+
+ fun assign(v: StringProperty) {
+ this.v = v.get()
+ }
+}
+
+@ValueContainer
+class StringPropertyWithPlus(v: String) : AbstractStringProperty(v) {
+ fun assign(v: String) {
+ this.v = v
+ }
+
+ fun assign(o: StringPropertyWithPlus) {
+ this.v = o.get()
+ }
+
+ operator fun plus(v: String) =
+ StringPropertyWithPlus(this.v + v)
+}
+
+@ValueContainer
+class StringPropertyWithPlusAssign(v: String) : AbstractStringProperty(v) {
+ fun assign(v: String) {
+ this.v = v
+ }
+
+ fun assign(o: StringPropertyWithPlusAssign) {
+ this.v = o.get()
+ }
+
+ operator fun plusAssign(v: String) {
+ this.v += v
+ }
+}
+
+@ValueContainer
+class StringPropertyWithPlusAndPlusAssign(v: String) : AbstractStringProperty(v) {
+ fun assign(v: String) {
+ this.v = v
+ }
+
+ fun assign(o: StringPropertyWithPlusAndPlusAssign) {
+ this.v = o.get()
+ }
+
+ operator fun plus(v: String) =
+ StringPropertyWithPlusAndPlusAssign(this.v + v)
+
+ operator fun plusAssign(v: String) {
+ this.v += v
+ }
+}
+
+data class Task(
+ val valInput: StringProperty,
+ var varInput: StringProperty,
+
+ val valInputWithPlus: StringPropertyWithPlus,
+ var varInputWithPlus: StringPropertyWithPlus,
+
+ val valInputWithPlusAssign: StringPropertyWithPlusAssign,
+ var varInputWithPlusAssign: StringPropertyWithPlusAssign,
+
+ val valInputWithPlusAndPlusAssign: StringPropertyWithPlusAndPlusAssign,
+ var varInputWithPlusAndPlusAssign: StringPropertyWithPlusAndPlusAssign,
+)
+
+fun box(): String {
+ val task = Task(
+ StringProperty("O"),
+ StringProperty("O"),
+
+ StringPropertyWithPlus("O"),
+ StringPropertyWithPlus("O"),
+
+ StringPropertyWithPlusAssign("O"),
+ StringPropertyWithPlusAssign("O"),
+
+ StringPropertyWithPlusAndPlusAssign("O"),
+ StringPropertyWithPlusAndPlusAssign("O")
+ )
+
+ task.valInput += "K"
+ task.varInput += "K"
+
+ task.valInputWithPlus += "K"
+
+ task.varInputWithPlusAndPlusAssign += "K"
+
+ return "OK"
+}
diff --git a/plugins/assign-plugin/testData/diagnostics/plusAssignPrecedence.kt b/plugins/assign-plugin/testData/diagnostics/plusAssignPrecedence.kt
new file mode 100644
index 00000000000..c70d798dac7
--- /dev/null
+++ b/plugins/assign-plugin/testData/diagnostics/plusAssignPrecedence.kt
@@ -0,0 +1,104 @@
+annotation class ValueContainer
+
+abstract class AbstractStringProperty(protected var v: String) {
+ fun get(): String {
+ return v
+ }
+}
+
+@ValueContainer
+class StringProperty(v: String) : AbstractStringProperty(v) {
+ fun assign(v: String) {
+ this.v = v
+ }
+
+ fun assign(v: StringProperty) {
+ this.v = v.get()
+ }
+}
+
+@ValueContainer
+class StringPropertyWithPlus(v: String) : AbstractStringProperty(v) {
+ fun assign(v: String) {
+ this.v = v
+ }
+
+ fun assign(o: StringPropertyWithPlus) {
+ this.v = o.get()
+ }
+
+ operator fun plus(v: String) =
+ StringPropertyWithPlus(this.v + v)
+}
+
+@ValueContainer
+class StringPropertyWithPlusAssign(v: String) : AbstractStringProperty(v) {
+ fun assign(v: String) {
+ this.v = v
+ }
+
+ fun assign(o: StringPropertyWithPlusAssign) {
+ this.v = o.get()
+ }
+
+ operator fun plusAssign(v: String) {
+ this.v += v
+ }
+}
+
+@ValueContainer
+class StringPropertyWithPlusAndPlusAssign(v: String) : AbstractStringProperty(v) {
+ fun assign(v: String) {
+ this.v = v
+ }
+
+ fun assign(o: StringPropertyWithPlusAndPlusAssign) {
+ this.v = o.get()
+ }
+
+ operator fun plus(v: String) =
+ StringPropertyWithPlusAndPlusAssign(this.v + v)
+
+ operator fun plusAssign(v: String) {
+ this.v += v
+ }
+}
+
+data class Task(
+ val valInput: StringProperty,
+ var varInput: StringProperty,
+
+ val valInputWithPlus: StringPropertyWithPlus,
+ var varInputWithPlus: StringPropertyWithPlus,
+
+ val valInputWithPlusAssign: StringPropertyWithPlusAssign,
+ var varInputWithPlusAssign: StringPropertyWithPlusAssign,
+
+ val valInputWithPlusAndPlusAssign: StringPropertyWithPlusAndPlusAssign,
+ var varInputWithPlusAndPlusAssign: StringPropertyWithPlusAndPlusAssign,
+)
+
+fun box(): String {
+ val task = Task(
+ StringProperty("O"),
+ StringProperty("O"),
+
+ StringPropertyWithPlus("O"),
+ StringPropertyWithPlus("O"),
+
+ StringPropertyWithPlusAssign("O"),
+ StringPropertyWithPlusAssign("O"),
+
+ StringPropertyWithPlusAndPlusAssign("O"),
+ StringPropertyWithPlusAndPlusAssign("O")
+ )
+
+ task.valInput += "K"
+ task.varInput += "K"
+
+ task.valInputWithPlus += "K"
+
+ task.varInputWithPlusAndPlusAssign += "K"
+
+ return "OK"
+}
diff --git a/plugins/assign-plugin/testData/diagnostics/plusAssignPrecedence.txt b/plugins/assign-plugin/testData/diagnostics/plusAssignPrecedence.txt
new file mode 100644
index 00000000000..dcc5797c85e
--- /dev/null
+++ b/plugins/assign-plugin/testData/diagnostics/plusAssignPrecedence.txt
@@ -0,0 +1,92 @@
+package
+
+public fun box(): kotlin.String
+
+public abstract class AbstractStringProperty {
+ public constructor AbstractStringProperty(/*0*/ v: kotlin.String)
+ protected final var v: kotlin.String
+ public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
+ public final fun get(): kotlin.String
+ public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
+ public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
+}
+
+@ValueContainer public final class StringProperty : AbstractStringProperty {
+ public constructor StringProperty(/*0*/ v: kotlin.String)
+ protected final override /*1*/ /*fake_override*/ var v: kotlin.String
+ public final fun assign(/*0*/ v: StringProperty): kotlin.Unit
+ public final fun assign(/*0*/ v: kotlin.String): kotlin.Unit
+ public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
+ public final override /*1*/ /*fake_override*/ fun get(): kotlin.String
+ public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
+ public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
+}
+
+@ValueContainer public final class StringPropertyWithPlus : AbstractStringProperty {
+ public constructor StringPropertyWithPlus(/*0*/ v: kotlin.String)
+ protected final override /*1*/ /*fake_override*/ var v: kotlin.String
+ public final fun assign(/*0*/ o: StringPropertyWithPlus): kotlin.Unit
+ public final fun assign(/*0*/ v: kotlin.String): kotlin.Unit
+ public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
+ public final override /*1*/ /*fake_override*/ fun get(): kotlin.String
+ public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
+ public final operator fun plus(/*0*/ v: kotlin.String): StringPropertyWithPlus
+ public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
+}
+
+@ValueContainer public final class StringPropertyWithPlusAndPlusAssign : AbstractStringProperty {
+ public constructor StringPropertyWithPlusAndPlusAssign(/*0*/ v: kotlin.String)
+ protected final override /*1*/ /*fake_override*/ var v: kotlin.String
+ public final fun assign(/*0*/ o: StringPropertyWithPlusAndPlusAssign): kotlin.Unit
+ public final fun assign(/*0*/ v: kotlin.String): kotlin.Unit
+ public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
+ public final override /*1*/ /*fake_override*/ fun get(): kotlin.String
+ public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
+ public final operator fun plus(/*0*/ v: kotlin.String): StringPropertyWithPlusAndPlusAssign
+ public final operator fun plusAssign(/*0*/ v: kotlin.String): kotlin.Unit
+ public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
+}
+
+@ValueContainer public final class StringPropertyWithPlusAssign : AbstractStringProperty {
+ public constructor StringPropertyWithPlusAssign(/*0*/ v: kotlin.String)
+ protected final override /*1*/ /*fake_override*/ var v: kotlin.String
+ public final fun assign(/*0*/ o: StringPropertyWithPlusAssign): kotlin.Unit
+ public final fun assign(/*0*/ v: kotlin.String): kotlin.Unit
+ public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
+ public final override /*1*/ /*fake_override*/ fun get(): kotlin.String
+ public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
+ public final operator fun plusAssign(/*0*/ v: kotlin.String): kotlin.Unit
+ public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
+}
+
+public final data class Task {
+ public constructor Task(/*0*/ valInput: StringProperty, /*1*/ varInput: StringProperty, /*2*/ valInputWithPlus: StringPropertyWithPlus, /*3*/ varInputWithPlus: StringPropertyWithPlus, /*4*/ valInputWithPlusAssign: StringPropertyWithPlusAssign, /*5*/ varInputWithPlusAssign: StringPropertyWithPlusAssign, /*6*/ valInputWithPlusAndPlusAssign: StringPropertyWithPlusAndPlusAssign, /*7*/ varInputWithPlusAndPlusAssign: StringPropertyWithPlusAndPlusAssign)
+ public final val valInput: StringProperty
+ public final val valInputWithPlus: StringPropertyWithPlus
+ public final val valInputWithPlusAndPlusAssign: StringPropertyWithPlusAndPlusAssign
+ public final val valInputWithPlusAssign: StringPropertyWithPlusAssign
+ public final var varInput: StringProperty
+ public final var varInputWithPlus: StringPropertyWithPlus
+ public final var varInputWithPlusAndPlusAssign: StringPropertyWithPlusAndPlusAssign
+ public final var varInputWithPlusAssign: StringPropertyWithPlusAssign
+ public final operator /*synthesized*/ fun component1(): StringProperty
+ public final operator /*synthesized*/ fun component2(): StringProperty
+ public final operator /*synthesized*/ fun component3(): StringPropertyWithPlus
+ public final operator /*synthesized*/ fun component4(): StringPropertyWithPlus
+ public final operator /*synthesized*/ fun component5(): StringPropertyWithPlusAssign
+ public final operator /*synthesized*/ fun component6(): StringPropertyWithPlusAssign
+ public final operator /*synthesized*/ fun component7(): StringPropertyWithPlusAndPlusAssign
+ public final operator /*synthesized*/ fun component8(): StringPropertyWithPlusAndPlusAssign
+ public final /*synthesized*/ fun copy(/*0*/ valInput: StringProperty = ..., /*1*/ varInput: StringProperty = ..., /*2*/ valInputWithPlus: StringPropertyWithPlus = ..., /*3*/ varInputWithPlus: StringPropertyWithPlus = ..., /*4*/ valInputWithPlusAssign: StringPropertyWithPlusAssign = ..., /*5*/ varInputWithPlusAssign: StringPropertyWithPlusAssign = ..., /*6*/ valInputWithPlusAndPlusAssign: StringPropertyWithPlusAndPlusAssign = ..., /*7*/ varInputWithPlusAndPlusAssign: StringPropertyWithPlusAndPlusAssign = ...): Task
+ public open override /*1*/ /*synthesized*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
+ public open override /*1*/ /*synthesized*/ fun hashCode(): kotlin.Int
+ public open override /*1*/ /*synthesized*/ fun toString(): kotlin.String
+}
+
+public final annotation class ValueContainer : kotlin.Annotation {
+ public constructor ValueContainer()
+ public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
+ public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
+ public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
+}
+
diff --git a/plugins/assign-plugin/tests-gen/org/jetbrains/kotlin/assignment/plugin/AssignmentPluginDiagnosticTestGenerated.java b/plugins/assign-plugin/tests-gen/org/jetbrains/kotlin/assignment/plugin/AssignmentPluginDiagnosticTestGenerated.java
new file mode 100644
index 00000000000..03a8335fe2f
--- /dev/null
+++ b/plugins/assign-plugin/tests-gen/org/jetbrains/kotlin/assignment/plugin/AssignmentPluginDiagnosticTestGenerated.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+package org.jetbrains.kotlin.assignment.plugin;
+
+import com.intellij.testFramework.TestDataPath;
+import org.jetbrains.kotlin.test.util.KtTestUtil;
+import org.jetbrains.kotlin.test.TestMetadata;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import java.io.File;
+import java.util.regex.Pattern;
+
+/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.GenerateTestsKt}. DO NOT MODIFY MANUALLY */
+@SuppressWarnings("all")
+@TestMetadata("plugins/assign-plugin/testData/diagnostics")
+@TestDataPath("$PROJECT_ROOT")
+public class AssignmentPluginDiagnosticTestGenerated extends AbstractAssignmentPluginDiagnosticTest {
+ @Test
+ public void testAllFilesPresentInDiagnostics() throws Exception {
+ KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("plugins/assign-plugin/testData/diagnostics"), Pattern.compile("^(.+)\\.kt$"), Pattern.compile("^(.+)\\.fir\\.kts?$"), true);
+ }
+
+ @Test
+ @TestMetadata("incorrectUsage.kt")
+ public void testIncorrectUsage() throws Exception {
+ runTest("plugins/assign-plugin/testData/diagnostics/incorrectUsage.kt");
+ }
+
+ @Test
+ @TestMetadata("localVariables.kt")
+ public void testLocalVariables() throws Exception {
+ runTest("plugins/assign-plugin/testData/diagnostics/localVariables.kt");
+ }
+
+ @Test
+ @TestMetadata("methodDeclaration.kt")
+ public void testMethodDeclaration() throws Exception {
+ runTest("plugins/assign-plugin/testData/diagnostics/methodDeclaration.kt");
+ }
+
+ @Test
+ @TestMetadata("noAnnotation.kt")
+ public void testNoAnnotation() throws Exception {
+ runTest("plugins/assign-plugin/testData/diagnostics/noAnnotation.kt");
+ }
+
+ @Test
+ @TestMetadata("otherOperators.kt")
+ public void testOtherOperators() throws Exception {
+ runTest("plugins/assign-plugin/testData/diagnostics/otherOperators.kt");
+ }
+
+ @Test
+ @TestMetadata("plusAssignPrecedence.kt")
+ public void testPlusAssignPrecedence() throws Exception {
+ runTest("plugins/assign-plugin/testData/diagnostics/plusAssignPrecedence.kt");
+ }
+}
diff --git a/plugins/assign-plugin/tests-gen/org/jetbrains/kotlin/assignment/plugin/FirAssignmentPluginDiagnosticTestGenerated.java b/plugins/assign-plugin/tests-gen/org/jetbrains/kotlin/assignment/plugin/FirAssignmentPluginDiagnosticTestGenerated.java
new file mode 100644
index 00000000000..e818967f339
--- /dev/null
+++ b/plugins/assign-plugin/tests-gen/org/jetbrains/kotlin/assignment/plugin/FirAssignmentPluginDiagnosticTestGenerated.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+package org.jetbrains.kotlin.assignment.plugin;
+
+import com.intellij.testFramework.TestDataPath;
+import org.jetbrains.kotlin.test.util.KtTestUtil;
+import org.jetbrains.kotlin.test.TestMetadata;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import java.io.File;
+import java.util.regex.Pattern;
+
+/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.GenerateTestsKt}. DO NOT MODIFY MANUALLY */
+@SuppressWarnings("all")
+@TestMetadata("plugins/assign-plugin/testData/diagnostics")
+@TestDataPath("$PROJECT_ROOT")
+public class FirAssignmentPluginDiagnosticTestGenerated extends AbstractFirAssignmentPluginDiagnosticTest {
+ @Test
+ public void testAllFilesPresentInDiagnostics() throws Exception {
+ KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("plugins/assign-plugin/testData/diagnostics"), Pattern.compile("^(.+)\\.kt$"), Pattern.compile("^(.+)\\.fir\\.kts?$"), true);
+ }
+
+ @Test
+ @TestMetadata("incorrectUsage.kt")
+ public void testIncorrectUsage() throws Exception {
+ runTest("plugins/assign-plugin/testData/diagnostics/incorrectUsage.kt");
+ }
+
+ @Test
+ @TestMetadata("localVariables.kt")
+ public void testLocalVariables() throws Exception {
+ runTest("plugins/assign-plugin/testData/diagnostics/localVariables.kt");
+ }
+
+ @Test
+ @TestMetadata("methodDeclaration.kt")
+ public void testMethodDeclaration() throws Exception {
+ runTest("plugins/assign-plugin/testData/diagnostics/methodDeclaration.kt");
+ }
+
+ @Test
+ @TestMetadata("noAnnotation.kt")
+ public void testNoAnnotation() throws Exception {
+ runTest("plugins/assign-plugin/testData/diagnostics/noAnnotation.kt");
+ }
+
+ @Test
+ @TestMetadata("otherOperators.kt")
+ public void testOtherOperators() throws Exception {
+ runTest("plugins/assign-plugin/testData/diagnostics/otherOperators.kt");
+ }
+
+ @Test
+ @TestMetadata("plusAssignPrecedence.kt")
+ public void testPlusAssignPrecedence() throws Exception {
+ runTest("plugins/assign-plugin/testData/diagnostics/plusAssignPrecedence.kt");
+ }
+}
diff --git a/plugins/assign-plugin/tests-gen/org/jetbrains/kotlin/assignment/plugin/FirBlackBoxCodegenTestForAssignmentPluginGenerated.java b/plugins/assign-plugin/tests-gen/org/jetbrains/kotlin/assignment/plugin/FirBlackBoxCodegenTestForAssignmentPluginGenerated.java
new file mode 100644
index 00000000000..f170e1daf46
--- /dev/null
+++ b/plugins/assign-plugin/tests-gen/org/jetbrains/kotlin/assignment/plugin/FirBlackBoxCodegenTestForAssignmentPluginGenerated.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+package org.jetbrains.kotlin.assignment.plugin;
+
+import com.intellij.testFramework.TestDataPath;
+import org.jetbrains.kotlin.test.util.KtTestUtil;
+import org.jetbrains.kotlin.test.TargetBackend;
+import org.jetbrains.kotlin.test.TestMetadata;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import java.io.File;
+import java.util.regex.Pattern;
+
+/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.GenerateTestsKt}. DO NOT MODIFY MANUALLY */
+@SuppressWarnings("all")
+@TestMetadata("plugins/assign-plugin/testData/codegen")
+@TestDataPath("$PROJECT_ROOT")
+public class FirBlackBoxCodegenTestForAssignmentPluginGenerated extends AbstractFirBlackBoxCodegenTestForAssignmentPlugin {
+ @Test
+ public void testAllFilesPresentInCodegen() throws Exception {
+ KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("plugins/assign-plugin/testData/codegen"), Pattern.compile("^(.+)\\.kt$"), Pattern.compile("^(.+)\\.fir\\.kts?$"), TargetBackend.JVM_IR, true);
+ }
+
+ @Test
+ @TestMetadata("annotation.kt")
+ public void testAnnotation() throws Exception {
+ runTest("plugins/assign-plugin/testData/codegen/annotation.kt");
+ }
+
+ @Test
+ @TestMetadata("otherOperators.kt")
+ public void testOtherOperators() throws Exception {
+ runTest("plugins/assign-plugin/testData/codegen/otherOperators.kt");
+ }
+
+ @Test
+ @TestMetadata("plusAssignPrecedence.kt")
+ public void testPlusAssignPrecedence() throws Exception {
+ runTest("plugins/assign-plugin/testData/codegen/plusAssignPrecedence.kt");
+ }
+
+ @Test
+ @TestMetadata("supportedUsage.kt")
+ public void testSupportedUsage() throws Exception {
+ runTest("plugins/assign-plugin/testData/codegen/supportedUsage.kt");
+ }
+
+ @Test
+ @TestMetadata("varBehaviour.kt")
+ public void testVarBehaviour() throws Exception {
+ runTest("plugins/assign-plugin/testData/codegen/varBehaviour.kt");
+ }
+}
diff --git a/plugins/assign-plugin/tests-gen/org/jetbrains/kotlin/assignment/plugin/IrBlackBoxCodegenTestAssignmentPluginGenerated.java b/plugins/assign-plugin/tests-gen/org/jetbrains/kotlin/assignment/plugin/IrBlackBoxCodegenTestAssignmentPluginGenerated.java
new file mode 100644
index 00000000000..3eb62a8fc5a
--- /dev/null
+++ b/plugins/assign-plugin/tests-gen/org/jetbrains/kotlin/assignment/plugin/IrBlackBoxCodegenTestAssignmentPluginGenerated.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+package org.jetbrains.kotlin.assignment.plugin;
+
+import com.intellij.testFramework.TestDataPath;
+import org.jetbrains.kotlin.test.util.KtTestUtil;
+import org.jetbrains.kotlin.test.TargetBackend;
+import org.jetbrains.kotlin.test.TestMetadata;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import java.io.File;
+import java.util.regex.Pattern;
+
+/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.GenerateTestsKt}. DO NOT MODIFY MANUALLY */
+@SuppressWarnings("all")
+@TestMetadata("plugins/assign-plugin/testData/codegen")
+@TestDataPath("$PROJECT_ROOT")
+public class IrBlackBoxCodegenTestAssignmentPluginGenerated extends AbstractIrBlackBoxCodegenTestAssignmentPlugin {
+ @Test
+ public void testAllFilesPresentInCodegen() throws Exception {
+ KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("plugins/assign-plugin/testData/codegen"), Pattern.compile("^(.+)\\.kt$"), Pattern.compile("^(.+)\\.fir\\.kts?$"), TargetBackend.JVM_IR, true);
+ }
+
+ @Test
+ @TestMetadata("annotation.kt")
+ public void testAnnotation() throws Exception {
+ runTest("plugins/assign-plugin/testData/codegen/annotation.kt");
+ }
+
+ @Test
+ @TestMetadata("otherOperators.kt")
+ public void testOtherOperators() throws Exception {
+ runTest("plugins/assign-plugin/testData/codegen/otherOperators.kt");
+ }
+
+ @Test
+ @TestMetadata("plusAssignPrecedence.kt")
+ public void testPlusAssignPrecedence() throws Exception {
+ runTest("plugins/assign-plugin/testData/codegen/plusAssignPrecedence.kt");
+ }
+
+ @Test
+ @TestMetadata("supportedUsage.kt")
+ public void testSupportedUsage() throws Exception {
+ runTest("plugins/assign-plugin/testData/codegen/supportedUsage.kt");
+ }
+
+ @Test
+ @TestMetadata("varBehaviour.kt")
+ public void testVarBehaviour() throws Exception {
+ runTest("plugins/assign-plugin/testData/codegen/varBehaviour.kt");
+ }
+}
diff --git a/settings.gradle b/settings.gradle
index e74e783d512..93a3d739564 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -210,6 +210,12 @@ include ":kotlin-sam-with-receiver-compiler-plugin",
":kotlin-sam-with-receiver-compiler-plugin.k2",
":kotlin-sam-with-receiver-compiler-plugin.cli"
+include ":kotlin-assignment-compiler-plugin",
+ ":kotlin-assignment-compiler-plugin.common",
+ ":kotlin-assignment-compiler-plugin.k1",
+ ":kotlin-assignment-compiler-plugin.k2",
+ ":kotlin-assignment-compiler-plugin.cli"
+
include ":kotlin-imports-dumper-compiler-plugin",
":kotlin-script-runtime",
":plugins:fir-plugin-prototype",
@@ -736,6 +742,12 @@ project(':kotlin-sam-with-receiver-compiler-plugin.k1').projectDir = "$rootDir/p
project(':kotlin-sam-with-receiver-compiler-plugin.k2').projectDir = "$rootDir/plugins/sam-with-receiver/sam-with-receiver.k2" as File
project(':kotlin-sam-with-receiver-compiler-plugin.cli').projectDir = "$rootDir/plugins/sam-with-receiver/sam-with-receiver.cli" as File
+project(':kotlin-assignment-compiler-plugin').projectDir = "$rootDir/plugins/assign-plugin" as File
+project(':kotlin-assignment-compiler-plugin.common').projectDir = "$rootDir/plugins/assign-plugin/assign-plugin.common" as File
+project(':kotlin-assignment-compiler-plugin.k1').projectDir = "$rootDir/plugins/assign-plugin/assign-plugin.k1" as File
+project(':kotlin-assignment-compiler-plugin.k2').projectDir = "$rootDir/plugins/assign-plugin/assign-plugin.k2" as File
+project(':kotlin-assignment-compiler-plugin.cli').projectDir = "$rootDir/plugins/assign-plugin/assign-plugin.cli" as File
+
project(':tools:kotlinp').projectDir = "$rootDir/libraries/tools/kotlinp" as File
project(':kotlin-project-model').projectDir = "$rootDir/libraries/tools/kotlin-project-model" as File
project(':kotlin-project-model-tests-generator').projectDir = "$rootDir/libraries/tools/kotlin-project-model-tests-generator" as File