[Assign plugin] Add a compiler plugin for overloading assign ('=') operator
This commit is contained in:
committed by
Dmitriy Novozhilov
parent
6052962f50
commit
09d6dfc8bf
@@ -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()
|
||||
+101
@@ -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<String>
|
||||
) : AbstractAssignPluginResolutionAltererExtension() {
|
||||
override fun getAnnotationFqNames(modifierListOwner: KtModifierListOwner?): List<String> = 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<FunctionDescriptor> =
|
||||
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<FunctionDescriptor> {
|
||||
val call = makeCallWithExpressions(
|
||||
binaryExpression, receiver, null, binaryExpression.operationReference, Collections.singletonList(binaryExpression.right)
|
||||
)
|
||||
return resolveCallWithGivenName(context, call, binaryExpression.operationReference, ASSIGN_METHOD)
|
||||
}
|
||||
}
|
||||
+66
@@ -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<String>) : 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<String>) : AnnotationBasedExtension {
|
||||
override fun getAnnotationFqNames(modifierListOwner: KtModifierListOwner?): List<String> = annotations
|
||||
|
||||
fun isAnnotated(descriptor: ClassDescriptor): Boolean = descriptor.hasSpecialAnnotation(null)
|
||||
}
|
||||
}
|
||||
+34
@@ -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"
|
||||
)
|
||||
}
|
||||
}
|
||||
+36
@@ -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<KtDeclaration> DECLARATION_ERROR_ASSIGN_METHOD_SHOULD_RETURN_UNIT =
|
||||
DiagnosticFactory0.create(Severity.ERROR, DECLARATION_RETURN_TYPE);
|
||||
DiagnosticFactory0<KtSimpleNameExpression> CALL_ERROR_ASSIGN_METHOD_SHOULD_RETURN_UNIT =
|
||||
DiagnosticFactory0.create(ERROR, PositioningStrategies.CALL_EXPRESSION);
|
||||
DiagnosticFactory0<KtSimpleNameExpression> NO_APPLICABLE_ASSIGN_METHOD =
|
||||
DiagnosticFactory0.create(ERROR, PositioningStrategies.CALL_EXPRESSION);
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
Object _initializer = new Object() {
|
||||
{
|
||||
Errors.Initializer.initializeFactoryNamesAndDefaultErrorMessages(
|
||||
ErrorsAssignmentPlugin.class,
|
||||
DefaultErrorMessagesAssignmentPlugin.INSTANCE
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user