[FIR] Properly set isOperator flag for java functions

^KT-56875 Fixed
This commit is contained in:
Dmitriy Novozhilov
2023-02-22 16:34:39 +02:00
committed by Space Team
parent df47581c5a
commit 244dbb37cf
21 changed files with 368 additions and 256 deletions
@@ -0,0 +1,226 @@
/*
* Copyright 2010-2023 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.fir.declarations
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.containingClassLookupTag
import org.jetbrains.kotlin.fir.declarations.utils.isInline
import org.jetbrains.kotlin.fir.languageVersionSettings
import org.jetbrains.kotlin.fir.resolve.ScopeSession
import org.jetbrains.kotlin.fir.resolve.defaultType
import org.jetbrains.kotlin.fir.resolve.toFirRegularClassSymbol
import org.jetbrains.kotlin.fir.scopes.overriddenFunctions
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.fir.types.impl.ConeClassLikeTypeImpl
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.name.StandardClassIds
import org.jetbrains.kotlin.util.OperatorNameConventions
sealed class CheckResult(val isSuccess: Boolean) {
class IllegalSignature(val error: String) : CheckResult(false)
object IllegalFunctionName : CheckResult(false)
object SuccessCheck : CheckResult(true)
}
object OperatorFunctionChecks {
fun isOperator(function: FirSimpleFunction, session: FirSession, scopeSession: ScopeSession?): CheckResult {
val checks = checksByName.getOrElse(function.name) {
regexChecks.find { it.first.matches(function.name.asString()) }?.second
} ?: return CheckResult.IllegalFunctionName
for (check in checks) {
check.check(function, session, scopeSession)?.let { return CheckResult.IllegalSignature(it) }
}
return CheckResult.SuccessCheck
}
//reimplementation of org.jetbrains.kotlin.util.OperatorChecks for FIR
private val checksByName: Map<Name, List<Check>> = buildMap {
checkFor(OperatorNameConventions.GET, Checks.memberOrExtension, Checks.ValueParametersCount.atLeast(1))
checkFor(
OperatorNameConventions.SET,
Checks.memberOrExtension, Checks.ValueParametersCount.atLeast(2),
Checks.simple("last parameter should not have a default value or be a vararg") {
it.valueParameters.lastOrNull()?.let { param ->
param.defaultValue == null && !param.isVararg
} == true
}
)
checkFor(
OperatorNameConventions.GET_VALUE,
Checks.memberOrExtension,
Checks.noDefaultAndVarargs, Checks.ValueParametersCount.atLeast(2),
Checks.isKProperty
)
checkFor(
OperatorNameConventions.SET_VALUE,
Checks.memberOrExtension,
Checks.noDefaultAndVarargs, Checks.ValueParametersCount.atLeast(3),
Checks.isKProperty
)
checkFor(
OperatorNameConventions.PROVIDE_DELEGATE,
Checks.memberOrExtension,
Checks.noDefaultAndVarargs, Checks.ValueParametersCount.exactly(2),
Checks.isKProperty
)
checkFor(OperatorNameConventions.INVOKE, Checks.memberOrExtension)
checkFor(
OperatorNameConventions.CONTAINS,
Checks.memberOrExtension, Checks.ValueParametersCount.single,
Checks.noDefaultAndVarargs, Checks.Returns.boolean
)
checkFor(OperatorNameConventions.ITERATOR, Checks.memberOrExtension, Checks.ValueParametersCount.none)
checkFor(OperatorNameConventions.NEXT, Checks.memberOrExtension, Checks.ValueParametersCount.none)
checkFor(OperatorNameConventions.HAS_NEXT, Checks.memberOrExtension, Checks.ValueParametersCount.none, Checks.Returns.boolean)
checkFor(OperatorNameConventions.RANGE_TO, Checks.memberOrExtension, Checks.ValueParametersCount.single, Checks.noDefaultAndVarargs)
checkFor(
OperatorNameConventions.RANGE_UNTIL,
Checks.memberOrExtension, Checks.ValueParametersCount.single,
Checks.noDefaultAndVarargs
)
checkFor(
OperatorNameConventions.EQUALS,
Checks.member,
object : Check() {
override fun check(function: FirSimpleFunction, session: FirSession, scopeSession: ScopeSession?): String? {
if (scopeSession == null) return null
val containingClassSymbol = function.containingClassLookupTag()?.toFirRegularClassSymbol(session) ?: return null
val customEqualsSupported = session.languageVersionSettings.supportsFeature(LanguageFeature.CustomEqualsInValueClasses)
if (function.symbol.overriddenFunctions(containingClassSymbol, session, scopeSession)
.any { it.containingClassLookupTag()?.classId == StandardClassIds.Any }
|| (customEqualsSupported && function.isTypedEqualsInValueClass(session))
) {
return null
}
return buildString {
append("must override ''equals()'' in Any")
if (customEqualsSupported && containingClassSymbol.isInline) {
val expectedParameterTypeRendered =
containingClassSymbol.defaultType().replaceArgumentsWithStarProjections().renderReadable()
append(" or define ''equals(other: ${expectedParameterTypeRendered}): Boolean''")
}
}
}
}
)
checkFor(
OperatorNameConventions.COMPARE_TO,
Checks.memberOrExtension, Checks.Returns.int, Checks.ValueParametersCount.single,
Checks.noDefaultAndVarargs
)
checkFor(
OperatorNameConventions.BINARY_OPERATION_NAMES,
Checks.memberOrExtension, Checks.ValueParametersCount.single,
Checks.noDefaultAndVarargs
)
checkFor(OperatorNameConventions.SIMPLE_UNARY_OPERATION_NAMES, Checks.memberOrExtension, Checks.ValueParametersCount.none)
checkFor(
setOf(OperatorNameConventions.INC, OperatorNameConventions.DEC),
Checks.memberOrExtension,
Checks.full("receiver must be a supertype of the return type") { session, function ->
val receiver = function.dispatchReceiverType ?: function.receiverParameter?.typeRef?.coneType ?: return@full false
function.returnTypeRef.coneType.isSubtypeOf(session.typeContext, receiver)
}
)
checkFor(
OperatorNameConventions.ASSIGNMENT_OPERATIONS,
Checks.memberOrExtension, Checks.Returns.unit, Checks.ValueParametersCount.single,
Checks.noDefaultAndVarargs
)
}
private val regexChecks: List<Pair<Regex, List<Check>>> = buildList {
checkFor(OperatorNameConventions.COMPONENT_REGEX, Checks.memberOrExtension, Checks.ValueParametersCount.none)
}
private fun MutableMap<Name, List<Check>>.checkFor(name: Name, vararg checks: Check) {
put(name, checks.asList())
}
private fun MutableMap<Name, List<Check>>.checkFor(names: Set<Name>, vararg checks: Check) {
names.forEach { put(it, checks.asList()) }
}
private fun MutableList<Pair<Regex, List<Check>>>.checkFor(regex: Regex, vararg checks: Check) {
add(regex to checks.asList())
}
}
private abstract class Check {
abstract fun check(function: FirSimpleFunction, session: FirSession, scopeSession: ScopeSession?): String?
}
private object Checks {
fun simple(message: String, predicate: (FirSimpleFunction) -> Boolean) = object : Check() {
override fun check(function: FirSimpleFunction, session: FirSession, scopeSession: ScopeSession?): String? =
message.takeIf { !predicate(function) }
}
fun full(message: String, predicate: (FirSession, FirSimpleFunction) -> Boolean) = object : Check() {
override fun check(function: FirSimpleFunction, session: FirSession, scopeSession: ScopeSession?): String? =
message.takeIf { !predicate(session, function) }
}
val memberOrExtension = simple("must be a member or an extension function") {
it.dispatchReceiverType != null || it.receiverParameter != null
}
val member = simple("must be a member function") {
it.dispatchReceiverType != null
}
object ValueParametersCount {
fun atLeast(n: Int) = simple("must have at least $n value parameter" + (if (n > 1) "s" else "")) {
it.valueParameters.size >= n
}
fun exactly(n: Int) = simple("must have exactly $n value parameters") {
it.valueParameters.size == n
}
val single = simple("must have a single value parameter") {
it.valueParameters.size == 1
}
val none = simple("must have no value parameters") {
it.valueParameters.isEmpty()
}
}
object Returns {
val boolean = simple("must return Boolean") {
it.returnTypeRef.isBoolean
}
val int = simple("must return Int") {
it.returnTypeRef.isInt
}
val unit = simple("must return Unit") {
it.returnTypeRef.isUnit
}
}
val noDefaultAndVarargs = simple("should not have varargs or parameters with default values") {
it.valueParameters.all { param ->
param.defaultValue == null && !param.isVararg
}
}
private val kPropertyType = ConeClassLikeTypeImpl(
StandardClassIds.KProperty.toLookupTag(),
arrayOf(ConeStarProjection),
isNullable = false
)
val isKProperty = full("second parameter must be of type KProperty<*> or its supertype") { session, function ->
val paramType = function.valueParameters.getOrNull(1)?.returnTypeRef?.coneType ?: return@full false
kPropertyType.isSubtypeOf(paramType, session, errorTypesEqualToAnything = true)
}
}
@@ -9,16 +9,19 @@ import org.jetbrains.kotlin.descriptors.InlineClassRepresentation
import org.jetbrains.kotlin.descriptors.ValueClassRepresentation
import org.jetbrains.kotlin.descriptors.createValueClassRepresentation
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.containingClassLookupTag
import org.jetbrains.kotlin.fir.declarations.utils.isInline
import org.jetbrains.kotlin.fir.resolve.defaultType
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
import org.jetbrains.kotlin.fir.resolve.substitution.createTypeSubstitutorByTypeConstructor
import org.jetbrains.kotlin.fir.resolve.toFirRegularClassSymbol
import org.jetbrains.kotlin.fir.resolve.toSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirConstructorSymbol
import org.jetbrains.kotlin.fir.symbols.lazyResolveToPhase
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.types.model.typeConstructor
import org.jetbrains.kotlin.util.OperatorNameConventions
internal fun ConeKotlinType.substitutedUnderlyingTypeForInlineClass(session: FirSession, context: ConeTypeContext): ConeKotlinType? {
val unsubstitutedType = unsubstitutedUnderlyingTypeForInlineClass(session) ?: return null
@@ -77,3 +80,15 @@ private fun ConeSimpleKotlinType.valueClassRepresentationTypeMarkersList(session
.onEach { it.lazyResolveToPhase(FirResolvePhase.TYPES) }
.map { it.name to it.resolvedReturnType as ConeSimpleKotlinType }
}
fun FirSimpleFunction.isTypedEqualsInValueClass(session: FirSession): Boolean =
containingClassLookupTag()?.toFirRegularClassSymbol(session)?.run {
val valueClassStarProjection = this@run.defaultType().replaceArgumentsWithStarProjections()
with(this@isTypedEqualsInValueClass) {
contextReceivers.isEmpty() && receiverParameter == null
&& name == OperatorNameConventions.EQUALS
&& this@run.isInline && valueParameters.size == 1
&& (returnTypeRef.isBoolean || returnTypeRef.isNothing)
&& valueParameters[0].returnTypeRef.coneType.let { it is ConeClassLikeType && it.replaceArgumentsWithStarProjections() == valueClassStarProjection }
}
} ?: false
@@ -13,9 +13,7 @@ import org.jetbrains.kotlin.fir.resolve.ScopeSession
import org.jetbrains.kotlin.fir.resolve.scope
import org.jetbrains.kotlin.fir.resolve.toSymbol
import org.jetbrains.kotlin.fir.symbols.ConeClassLikeLookupTag
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
import org.jetbrains.kotlin.fir.symbols.impl.*
fun ConeClassLikeLookupTag.getNestedClassifierScope(session: FirSession, scopeSession: ScopeSession): FirContainingNamesAwareScope? {
val klass = toSymbol(session)?.fir as? FirRegularClass ?: return null
@@ -56,3 +54,24 @@ fun debugCollectOverrides(symbol: FirCallableSymbol<*>, scope: FirTypeScope): Ma
}
return process(scope, symbol)
}
fun FirNamedFunctionSymbol.overriddenFunctions(
containingClass: FirClassSymbol<*>,
session: FirSession,
scopeSession: ScopeSession
): List<FirFunctionSymbol<*>> {
val firTypeScope = containingClass.unsubstitutedScope(
session,
scopeSession,
withForcedTypeCalculator = true
)
val overriddenFunctions = mutableListOf<FirFunctionSymbol<*>>()
firTypeScope.processFunctionsByName(callableId.callableName) { }
firTypeScope.processOverriddenFunctions(this) {
overriddenFunctions.add(it)
ProcessorAction.NEXT
}
return overriddenFunctions
}
@@ -516,9 +516,9 @@ private class CapturedArguments(val capturedArguments: Array<out ConeTypeProject
}
}
fun ConeKotlinType.isSubtypeOf(superType: ConeKotlinType, session: FirSession): Boolean =
fun ConeKotlinType.isSubtypeOf(superType: ConeKotlinType, session: FirSession, errorTypesEqualToAnything: Boolean = false): Boolean =
AbstractTypeChecker.isSubtypeOf(
session.typeContext.newTypeCheckerState(errorTypesEqualToAnything = false, stubTypesEqualToAnything = false),
session.typeContext.newTypeCheckerState(errorTypesEqualToAnything, stubTypesEqualToAnything = false),
this, superType,
)