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