Pluggable Synthetic Objects

This commit is contained in:
Roman Elizarov
2016-11-03 12:54:25 +03:00
parent 7a4b6a1462
commit 8affb2726f
50 changed files with 731 additions and 222 deletions
@@ -20,6 +20,7 @@ import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.backend.common.bridges.findInterfaceImplementation
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorUtils
@@ -162,4 +163,23 @@ object CodegenUtil {
val document = file.viewProvider.document
return document?.getLineNumber(if (markEndOffset) statement.textRange.endOffset else statement.textOffset)?.plus(1)
}
// Returns the descriptor for a function (whose parameters match the given predicate) which should be generated in the class.
// Note that we always generate equals/hashCode/toString in data classes, unless that would lead to a JVM signature clash with
// another method, which can only happen if the method is declared in the data class (manually or via delegation).
// Also there are no hard asserts or assumptions because such methods are generated for erroneous code as well (in light classes mode).
fun getMemberToGenerate(
classDescriptor: ClassDescriptor,
name: String,
isReturnTypeOk: (KotlinType) -> Boolean,
areParametersOk: (List<ValueParameterDescriptor>) -> Boolean
): FunctionDescriptor? =
classDescriptor.unsubstitutedMemberScope.getContributedFunctions(Name.identifier(name), NoLookupLocation.FROM_BACKEND)
.singleOrNull { function ->
function.kind.let { kind -> kind == CallableMemberDescriptor.Kind.SYNTHESIZED || kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE } &&
function.modality != Modality.FINAL &&
areParametersOk(function.valueParameters) &&
function.returnType != null &&
isReturnTypeOk(function.returnType!!)
}
}
@@ -16,18 +16,17 @@
package org.jetbrains.kotlin.backend.common
import org.jetbrains.kotlin.backend.common.CodegenUtil.getMemberToGenerate
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.FAKE_OVERRIDE
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.SYNTHESIZED
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtParameter
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.BindingContextUtils
import org.jetbrains.kotlin.types.KotlinType
/**
* A platform-independent logic for generating data class synthetic methods.
@@ -79,17 +78,20 @@ abstract class DataClassMethodGenerator(private val declaration: KtClassOrObject
}
private fun generateDataClassToStringIfNeeded(properties: List<PropertyDescriptor>) {
val function = getMemberToGenerate("toString", KotlinBuiltIns::isString, List<ValueParameterDescriptor>::isEmpty) ?: return
val function = getMemberToGenerate(classDescriptor, "toString",
KotlinBuiltIns::isString, List<ValueParameterDescriptor>::isEmpty) ?: return
generateToStringMethod(function, properties)
}
private fun generateDataClassHashCodeIfNeeded(properties: List<PropertyDescriptor>) {
val function = getMemberToGenerate("hashCode", KotlinBuiltIns::isInt, List<ValueParameterDescriptor>::isEmpty) ?: return
val function = getMemberToGenerate(classDescriptor, "hashCode",
KotlinBuiltIns::isInt, List<ValueParameterDescriptor>::isEmpty) ?: return
generateHashCodeMethod(function, properties)
}
private fun generateDataClassEqualsIfNeeded(properties: List<PropertyDescriptor>) {
val function = getMemberToGenerate("equals", KotlinBuiltIns::isBoolean) { parameters ->
val function = getMemberToGenerate(classDescriptor, "equals",
KotlinBuiltIns::isBoolean) { parameters ->
parameters.size == 1 && KotlinBuiltIns.isNullableAny(parameters.first().type)
} ?: return
generateEqualsMethod(function, properties)
@@ -102,22 +104,4 @@ abstract class DataClassMethodGenerator(private val declaration: KtClassOrObject
private val primaryConstructorParameters: List<KtParameter>
get() = (declaration as? KtClass)?.getPrimaryConstructorParameters().orEmpty()
// Returns the descriptor for a function (whose parameters match the given predicate) which should be generated in the data class.
// Note that we always generate equals/hashCode/toString in data classes, unless that would lead to a JVM signature clash with
// another method, which can only happen if the method is declared in the data class (manually or via delegation).
// Also there are no hard asserts or assumptions because such methods are generated for erroneous code as well (in light classes mode).
private fun getMemberToGenerate(
name: String,
isReturnTypeOk: (KotlinType) -> Boolean,
areParametersOk: (List<ValueParameterDescriptor>) -> Boolean
): FunctionDescriptor? =
classDescriptor.unsubstitutedMemberScope.getContributedFunctions(Name.identifier(name), NoLookupLocation.FROM_BACKEND)
.singleOrNull { function ->
function.kind.let { kind -> kind == SYNTHESIZED || kind == FAKE_OVERRIDE } &&
function.modality != Modality.FINAL &&
areParametersOk(function.valueParameters) &&
function.returnType != null &&
isReturnTypeOk(function.returnType!!)
}
}