[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
@@ -1083,6 +1083,12 @@ public class DiagnosisCompilerFirTestdataTestGenerated extends AbstractDiagnosis
runTest("compiler/fir/analysis-tests/testData/resolve/callResolution/extensionInvokeAfterSafeCall.kt");
}
@Test
@TestMetadata("incorrectPlusOperatorFromJava.kt")
public void testIncorrectPlusOperatorFromJava() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/callResolution/incorrectPlusOperatorFromJava.kt");
}
@Test
@TestMetadata("invokeAmbiguity.kt")
public void testInvokeAmbiguity() throws Exception {
@@ -933,6 +933,11 @@ public class LazyBodyIsNotTouchedTilContractsPhaseTestGenerated extends Abstract
runTest("compiler/fir/analysis-tests/testData/resolve/callResolution/extensionInvokeAfterSafeCall.kt");
}
@TestMetadata("incorrectPlusOperatorFromJava.kt")
public void testIncorrectPlusOperatorFromJava() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/callResolution/incorrectPlusOperatorFromJava.kt");
}
@TestMetadata("invokeAmbiguity.kt")
public void testInvokeAmbiguity() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/callResolution/invokeAmbiguity.kt");
@@ -0,0 +1,20 @@
FILE: main.kt
public final operator fun R|Base|.plus(s: R|kotlin/String|): R|kotlin/Int| {
^plus Int(0)
}
public final class Derived : R|Base| {
public constructor(): R|Derived| {
super<R|Base|>()
}
public final fun test_1(x: R|Base|): R|kotlin/Unit| {
lval y: R|kotlin/Int| = R|<local>/x|.R|/plus|(String())
R|<local>/y|.R|kotlin/Int.inc|()
}
public final fun test_2(x: R|Base|): R|kotlin/Unit| {
lval y: R|kotlin/String!| = R|<local>/x|.R|/Base.plus|(vararg(String()))
R|<local>/y|.R|kotlin/String.length|
}
}
@@ -0,0 +1,22 @@
// ISSUE: KT-56875
// FILE: Base.java
public class Base {
public String plus(String... strings) { // (1)
return "";
}
}
// FILE: main.kt
operator fun Base.plus(s: String): Int = 0 // (2)
class Derived : Base() {
fun test_1(x: Base) {
val y = x + "" // should resolve to (2)
y.inc() // should be ok
}
fun test_2(x: Base) {
val y = x.plus("") // should resolve to (1)
y.length // should be ok
}
}
@@ -1083,6 +1083,12 @@ public class FirLightTreeDiagnosticsTestGenerated extends AbstractFirLightTreeDi
runTest("compiler/fir/analysis-tests/testData/resolve/callResolution/extensionInvokeAfterSafeCall.kt");
}
@Test
@TestMetadata("incorrectPlusOperatorFromJava.kt")
public void testIncorrectPlusOperatorFromJava() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/callResolution/incorrectPlusOperatorFromJava.kt");
}
@Test
@TestMetadata("invokeAmbiguity.kt")
public void testInvokeAmbiguity() throws Exception {
@@ -1083,6 +1083,12 @@ public class FirPsiDiagnosticTestGenerated extends AbstractFirPsiDiagnosticTest
runTest("compiler/fir/analysis-tests/testData/resolve/callResolution/extensionInvokeAfterSafeCall.kt");
}
@Test
@TestMetadata("incorrectPlusOperatorFromJava.kt")
public void testIncorrectPlusOperatorFromJava() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/callResolution/incorrectPlusOperatorFromJava.kt");
}
@Test
@TestMetadata("invokeAmbiguity.kt")
public void testInvokeAmbiguity() throws Exception {
@@ -189,20 +189,7 @@ fun FirNamedFunctionSymbol.overriddenFunctions(
containingClass: FirClassSymbol<*>,
context: CheckerContext
): List<FirFunctionSymbol<*>> {
val firTypeScope = containingClass.unsubstitutedScope(
context.sessionHolder.session,
context.sessionHolder.scopeSession,
withForcedTypeCalculator = true
)
val overriddenFunctions = mutableListOf<FirFunctionSymbol<*>>()
firTypeScope.processFunctionsByName(callableId.callableName) { }
firTypeScope.processOverriddenFunctions(this) {
overriddenFunctions.add(it)
ProcessorAction.NEXT
}
return overriddenFunctions
return overriddenFunctions(containingClass, context.session, context.scopeSession)
}
fun FirClass.collectSupertypesWithDelegates(): Map<FirTypeRef, FirFieldSymbol?> {
@@ -18,6 +18,7 @@ import org.jetbrains.kotlin.fir.expressions.FirGetClassCall
import org.jetbrains.kotlin.fir.expressions.FirStatement
import org.jetbrains.kotlin.fir.languageVersionSettings
import org.jetbrains.kotlin.fir.resolve.ImplicitReceiverStack
import org.jetbrains.kotlin.fir.resolve.ScopeSession
import org.jetbrains.kotlin.fir.resolve.SessionHolder
import org.jetbrains.kotlin.fir.resolve.transformers.ReturnTypeCalculator
@@ -47,6 +48,9 @@ abstract class CheckerContext : DiagnosticContext {
val session: FirSession
get() = sessionHolder.session
val scopeSession: ScopeSession
get() = sessionHolder.scopeSession
override fun isDiagnosticSuppressed(diagnostic: KtDiagnostic): Boolean {
val factory = diagnostic.factory
val name = factory.name
@@ -9,49 +9,16 @@ import org.jetbrains.kotlin.config.LanguageFeature
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.Checks.Returns
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.Checks.ValueParametersCount
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.Checks.isKProperty
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.Checks.member
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.Checks.memberOrExtension
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.Checks.noDefaultAndVarargs
import org.jetbrains.kotlin.fir.analysis.checkers.hasModifier
import org.jetbrains.kotlin.fir.analysis.checkers.isSupertypeOf
import org.jetbrains.kotlin.fir.analysis.checkers.overriddenFunctions
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.containingClassLookupTag
import org.jetbrains.kotlin.fir.declarations.CheckResult
import org.jetbrains.kotlin.fir.declarations.FirSimpleFunction
import org.jetbrains.kotlin.fir.declarations.utils.isInline
import org.jetbrains.kotlin.fir.declarations.OperatorFunctionChecks
import org.jetbrains.kotlin.fir.declarations.utils.isOperator
import org.jetbrains.kotlin.fir.resolve.defaultType
import org.jetbrains.kotlin.fir.resolve.toFirRegularClassSymbol
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.fir.types.impl.ConeClassLikeTypeImpl
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.name.StandardClassIds
import org.jetbrains.kotlin.name.isSubpackageOf
import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlin.util.OperatorNameConventions.ASSIGNMENT_OPERATIONS
import org.jetbrains.kotlin.util.OperatorNameConventions.BINARY_OPERATION_NAMES
import org.jetbrains.kotlin.util.OperatorNameConventions.COMPARE_TO
import org.jetbrains.kotlin.util.OperatorNameConventions.COMPONENT_REGEX
import org.jetbrains.kotlin.util.OperatorNameConventions.CONTAINS
import org.jetbrains.kotlin.util.OperatorNameConventions.DEC
import org.jetbrains.kotlin.util.OperatorNameConventions.EQUALS
import org.jetbrains.kotlin.util.OperatorNameConventions.GET
import org.jetbrains.kotlin.util.OperatorNameConventions.GET_VALUE
import org.jetbrains.kotlin.util.OperatorNameConventions.HAS_NEXT
import org.jetbrains.kotlin.util.OperatorNameConventions.INC
import org.jetbrains.kotlin.util.OperatorNameConventions.INVOKE
import org.jetbrains.kotlin.util.OperatorNameConventions.ITERATOR
import org.jetbrains.kotlin.util.OperatorNameConventions.NEXT
import org.jetbrains.kotlin.util.OperatorNameConventions.PROVIDE_DELEGATE
import org.jetbrains.kotlin.util.OperatorNameConventions.RANGE_TO
import org.jetbrains.kotlin.util.OperatorNameConventions.RANGE_UNTIL
import org.jetbrains.kotlin.util.OperatorNameConventions.SET
import org.jetbrains.kotlin.util.OperatorNameConventions.SET_VALUE
import org.jetbrains.kotlin.util.OperatorNameConventions.SIMPLE_UNARY_OPERATION_NAMES
object FirOperatorModifierChecker : FirSimpleFunctionChecker() {
@@ -61,18 +28,14 @@ object FirOperatorModifierChecker : FirSimpleFunctionChecker() {
//we are not interested in implicit operators from override
if (!declaration.hasModifier(KtTokens.OPERATOR_KEYWORD)) return
val checks = OperatorFunctionChecks.checksByName.getOrElse(declaration.name) {
OperatorFunctionChecks.regexChecks.find { it.first.matches(declaration.name.asString()) }?.second
}
if (checks == null) {
reporter.reportOn(declaration.source, FirErrors.INAPPLICABLE_OPERATOR_MODIFIER, "illegal function name", context)
return
}
for (check in checks) {
check.check(context, declaration)?.let { error ->
reporter.reportOn(declaration.source, FirErrors.INAPPLICABLE_OPERATOR_MODIFIER, error, context)
when (val checkResult = OperatorFunctionChecks.isOperator(declaration, context.session, context.scopeSession)) {
CheckResult.SuccessCheck -> {}
CheckResult.IllegalFunctionName -> {
reporter.reportOn(declaration.source, FirErrors.INAPPLICABLE_OPERATOR_MODIFIER, "illegal function name", context)
return
}
is CheckResult.IllegalSignature -> {
reporter.reportOn(declaration.source, FirErrors.INAPPLICABLE_OPERATOR_MODIFIER, checkResult.error, context)
return
}
}
@@ -95,159 +58,3 @@ object FirOperatorModifierChecker : FirSimpleFunctionChecker() {
reporter.reportOn(declaration.source, diagnostic, declaration.symbol, replacement.asString(), context)
}
}
private interface Check : (CheckerContext, FirSimpleFunction) -> String? {
override fun invoke(p1: CheckerContext, p2: FirSimpleFunction): String? {
return check(p1, p2)
}
fun check(context: CheckerContext, function: FirSimpleFunction): String?
}
private object Checks {
fun simple(message: String, predicate: (FirSimpleFunction) -> Boolean) = object : Check {
override fun check(context: CheckerContext, function: FirSimpleFunction): String? = message.takeIf { !predicate(function) }
}
fun full(message: String, predicate: (CheckerContext, FirSimpleFunction) -> Boolean) = object : Check {
override fun check(context: CheckerContext, function: FirSimpleFunction): String? = message.takeIf { !predicate(context, 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
}
}
val isKProperty = full("second parameter must be of type KProperty<*> or its supertype") { ctx, function ->
val paramType = function.valueParameters[1].returnTypeRef.coneType
paramType.isSupertypeOf(
ctx.session.typeContext,
ConeClassLikeTypeImpl(
StandardClassIds.KProperty.toLookupTag(),
arrayOf(ConeStarProjection),
isNullable = false
)
)
}
}
private object OperatorFunctionChecks {
//reimplementation of org.jetbrains.kotlin.util.OperatorChecks for FIR
val checksByName: Map<Name, List<Check>> = buildMap {
checkFor(GET, memberOrExtension, ValueParametersCount.atLeast(1))
checkFor(
SET,
memberOrExtension, 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(GET_VALUE, memberOrExtension, noDefaultAndVarargs, ValueParametersCount.atLeast(2), isKProperty)
checkFor(SET_VALUE, memberOrExtension, noDefaultAndVarargs, ValueParametersCount.atLeast(3), isKProperty)
checkFor(PROVIDE_DELEGATE, memberOrExtension, noDefaultAndVarargs, ValueParametersCount.exactly(2), isKProperty)
checkFor(INVOKE, memberOrExtension)
checkFor(CONTAINS, memberOrExtension, ValueParametersCount.single, noDefaultAndVarargs, Returns.boolean)
checkFor(ITERATOR, memberOrExtension, ValueParametersCount.none)
checkFor(NEXT, memberOrExtension, ValueParametersCount.none)
checkFor(HAS_NEXT, memberOrExtension, ValueParametersCount.none, Returns.boolean)
checkFor(RANGE_TO, memberOrExtension, ValueParametersCount.single, noDefaultAndVarargs)
checkFor(RANGE_UNTIL, memberOrExtension, ValueParametersCount.single, noDefaultAndVarargs)
checkFor(
EQUALS,
member,
object : Check {
override fun check(context: CheckerContext, function: FirSimpleFunction): String? {
val containingClassSymbol = function.containingClassLookupTag()?.toFirRegularClassSymbol(context.session) ?: return null
val customEqualsSupported = context.languageVersionSettings.supportsFeature(LanguageFeature.CustomEqualsInValueClasses)
if (function.overriddenFunctions(containingClassSymbol, context)
.any { it.containingClassLookupTag()?.classId == StandardClassIds.Any }
|| (customEqualsSupported && function.isTypedEqualsInValueClass(context.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(COMPARE_TO, memberOrExtension, Returns.int, ValueParametersCount.single, noDefaultAndVarargs)
checkFor(BINARY_OPERATION_NAMES, memberOrExtension, ValueParametersCount.single, noDefaultAndVarargs)
checkFor(SIMPLE_UNARY_OPERATION_NAMES, memberOrExtension, ValueParametersCount.none)
checkFor(
setOf(INC, DEC),
memberOrExtension,
Checks.full("receiver must be a supertype of the return type") { ctx, function ->
val receiver = function.dispatchReceiverType ?: function.receiverParameter?.typeRef?.coneType ?: return@full false
function.returnTypeRef.coneType.isSubtypeOf(ctx.session.typeContext, receiver)
}
)
checkFor(ASSIGNMENT_OPERATIONS, memberOrExtension, Returns.unit, ValueParametersCount.single, noDefaultAndVarargs)
}
val regexChecks: List<Pair<Regex, List<Check>>> = buildList {
checkFor(COMPONENT_REGEX, memberOrExtension, 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())
}
}
@@ -10,17 +10,12 @@ import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.modality
import org.jetbrains.kotlin.fir.containingClassForStaticMemberAttr
import org.jetbrains.kotlin.fir.containingClassLookupTag
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.utils.*
import org.jetbrains.kotlin.fir.resolve.defaultType
import org.jetbrains.kotlin.fir.resolve.toFirRegularClassSymbol
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.*
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.fir.types.impl.FirImplicitUnitTypeRef
import org.jetbrains.kotlin.util.OperatorNameConventions
internal fun isInsideExpectClass(containingClass: FirClass, context: CheckerContext): Boolean {
return isInsideSpecificClass(containingClass, context) { klass -> klass is FirRegularClass && klass.isExpect }
@@ -110,18 +105,6 @@ fun FirClassSymbol<*>.primaryConstructorSymbol(): FirConstructorSymbol? {
return null
}
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
fun FirTypeRef.needsMultiFieldValueClassFlattening(session: FirSession) = with(session.typeContext) {
coneType.typeConstructor().isMultiFieldValueClass() && !coneType.isNullable
}
@@ -569,9 +569,6 @@ abstract class FirJavaFacade(
javaMethod.visibility.toEffectiveVisibility(dispatchReceiver.lookupTag)
).apply {
isStatic = javaMethod.isStatic
// Approximation: all Java methods with name that allows to use it in operator form are considered operators
// We need here more detailed checks (see modifierChecks.kt)
isOperator = name in ALL_JAVA_OPERATION_NAMES || OperatorNameConventions.COMPONENT_REGEX.matches(name.asString())
hasStableParameterNames = false
}
@@ -25,8 +25,8 @@ import org.jetbrains.kotlin.fir.declarations.utils.*
import org.jetbrains.kotlin.fir.expressions.FirAnnotation
import org.jetbrains.kotlin.fir.expressions.FirConstExpression
import org.jetbrains.kotlin.fir.expressions.unexpandedClassId
import org.jetbrains.kotlin.fir.java.*
import org.jetbrains.kotlin.fir.java.FirJavaTypeConversionMode
import org.jetbrains.kotlin.fir.java.JavaTypeParameterStack
import org.jetbrains.kotlin.fir.java.declarations.*
import org.jetbrains.kotlin.fir.java.resolveIfJavaType
import org.jetbrains.kotlin.fir.java.symbols.FirJavaOverriddenSyntheticPropertySymbol
@@ -339,11 +339,20 @@ class FirSignatureEnhancement(
if (isJavaRecordComponent) {
this.isJavaRecordComponent = true
}
updateIsOperatorFlagIfNeeded(this)
}
return function.symbol
}
private fun updateIsOperatorFlagIfNeeded(function: FirFunction) {
if (function !is FirSimpleFunction) return
val isOperator = OperatorFunctionChecks.isOperator(function, session, scopeSession = null).isSuccess
if (!isOperator) return
val newStatus = function.status.copy(isOperator = true)
function.replaceStatus(newStatus)
}
/**
* Perform first time initialization of bounds with FirResolvedTypeRef instances
* But after that bounds are still not enhanced and more over might have not totally correct raw types bounds
@@ -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,
)
+6 -6
View File
@@ -15,12 +15,12 @@ FILE fqName:<root> fileName:/kt43217.kt
BLOCK_BODY
DELEGATING_CONSTRUCTOR_CALL 'public constructor <init> () [primary] declared in <root>.DoubleExpression'
INSTANCE_INITIALIZER_CALL classDescriptor='CLASS CLASS name:<no name provided> modality:FINAL visibility:local superTypes:[<root>.DoubleExpression]'
FUN name:get visibility:public modality:OPEN <> ($this:<root>.A.b.<no name provided>) returnType:kotlin.Double [operator]
FUN name:get visibility:public modality:OPEN <> ($this:<root>.A.b.<no name provided>) returnType:kotlin.Double
overridden:
public abstract fun get (): @[EnhancedNullability] kotlin.Double [fake_override,operator] declared in <root>.DoubleExpression
public abstract fun get (): @[EnhancedNullability] kotlin.Double [fake_override] declared in <root>.DoubleExpression
$this: VALUE_PARAMETER name:<this> type:<root>.A.b.<no name provided>
BLOCK_BODY
RETURN type=kotlin.Nothing from='public open fun get (): kotlin.Double [operator] declared in <root>.A.b.<no name provided>'
RETURN type=kotlin.Nothing from='public open fun get (): kotlin.Double declared in <root>.A.b.<no name provided>'
CONST Double type=kotlin.Double value=0.0
FUN FAKE_OVERRIDE name:isEqualTo visibility:public modality:OPEN <> ($this:<root>.DoubleExpression, value:kotlin.Double) returnType:@[EnhancedNullability] kotlin.Any [fake_override]
annotations:
@@ -79,12 +79,12 @@ FILE fqName:<root> fileName:/kt43217.kt
BLOCK_BODY
DELEGATING_CONSTRUCTOR_CALL 'public constructor <init> () [primary] declared in <root>.DoubleExpression'
INSTANCE_INITIALIZER_CALL classDescriptor='CLASS CLASS name:C modality:FINAL visibility:public superTypes:[<root>.DoubleExpression]'
FUN name:get visibility:public modality:OPEN <> ($this:<root>.C) returnType:kotlin.Double [operator]
FUN name:get visibility:public modality:OPEN <> ($this:<root>.C) returnType:kotlin.Double
overridden:
public abstract fun get (): @[EnhancedNullability] kotlin.Double [fake_override,operator] declared in <root>.DoubleExpression
public abstract fun get (): @[EnhancedNullability] kotlin.Double [fake_override] declared in <root>.DoubleExpression
$this: VALUE_PARAMETER name:<this> type:<root>.C
BLOCK_BODY
RETURN type=kotlin.Nothing from='public open fun get (): kotlin.Double [operator] declared in <root>.C'
RETURN type=kotlin.Nothing from='public open fun get (): kotlin.Double declared in <root>.C'
CONST Double type=kotlin.Double value=0.0
FUN FAKE_OVERRIDE name:isEqualTo visibility:public modality:OPEN <> ($this:<root>.DoubleExpression, value:kotlin.Double) returnType:@[EnhancedNullability] kotlin.Any [fake_override]
annotations:
+2 -2
View File
@@ -14,7 +14,7 @@ class A {
}
override operator fun get(): Double {
override fun get(): Double {
return 0.0
}
@@ -33,7 +33,7 @@ class C : DoubleExpression {
}
override operator fun get(): Double {
override fun get(): Double {
return 0.0
}
@@ -7,7 +7,7 @@ FILE fqName:<root> fileName:/exclExclOnPlatformType.kt
CALL 'public final fun use (a: kotlin.Any): kotlin.Unit declared in <root>' type=kotlin.Unit origin=null
a: CALL 'public final fun CHECK_NOT_NULL <T0> (arg0: T0 of kotlin.internal.ir.CHECK_NOT_NULL?): {T0 of kotlin.internal.ir.CHECK_NOT_NULL & Any} declared in kotlin.internal.ir' type=kotlin.String origin=EXCLEXCL
<T0>: kotlin.String
arg0: CALL 'public open fun get (): T of java.lang.ref.WeakReference? [fake_override,operator] declared in java.lang.ref.WeakReference' type=kotlin.String? origin=null
arg0: CALL 'public open fun get (): T of java.lang.ref.WeakReference? [fake_override] declared in java.lang.ref.WeakReference' type=kotlin.String? origin=null
$this: CONSTRUCTOR_CALL 'public constructor <init> (p0: @[FlexibleNullability] T of java.lang.ref.WeakReference?) declared in java.lang.ref.WeakReference' type=java.lang.ref.WeakReference<@[FlexibleNullability] kotlin.String?> origin=null
<class: T>: @[FlexibleNullability] kotlin.String?
p0: CONST String type=kotlin.String value=""
@@ -1,4 +1,4 @@
public abstract interface SubclassOfMapEntry<K : R|kotlin/Any!|, V : R|kotlin/Any!|> : R|kotlin/collections/MutableMap.MutableEntry<ft<K & Any, K?>, ft<V & Any, V?>>| {
public abstract operator fun setValue(value: R|ft<V & Any, V?>|): R|ft<V & Any, V?>|
public abstract fun setValue(value: R|ft<V & Any, V?>|): R|ft<V & Any, V?>|
}
@@ -3,7 +3,7 @@ public abstract interface ReturnInnerSubclassOfSupersInner : R|kotlin/Any| {
public constructor<B : R|kotlin/Any!|>(): R|test/ReturnInnerSubclassOfSupersInner.Sub<B>|
public/*package*/ open inner class Inner<B : R|kotlin/Any!|> : R|test/ReturnInnerSubclassOfSupersInner.Super.Inner<ft<B & Any, B?>>| {
public/*package*/ open operator fun get(): R|ft<test/ReturnInnerSubclassOfSupersInner.Sub<ft<B & Any, B?>>, test/ReturnInnerSubclassOfSupersInner.Sub<ft<B & Any, B?>>?>|
public/*package*/ open fun get(): R|ft<test/ReturnInnerSubclassOfSupersInner.Sub<ft<B & Any, B?>>, test/ReturnInnerSubclassOfSupersInner.Sub<ft<B & Any, B?>>?>|
public/*package*/ test/ReturnInnerSubclassOfSupersInner.Sub<B>.constructor(): R|test/ReturnInnerSubclassOfSupersInner.Sub.Inner<B>|
@@ -13,7 +13,7 @@ public abstract interface ReturnInnerSubclassOfSupersInner : R|kotlin/Any| {
public constructor<A : R|kotlin/Any!|>(): R|test/ReturnInnerSubclassOfSupersInner.Super<A>|
public/*package*/ open inner class Inner<A : R|kotlin/Any!|> : R|kotlin/Any| {
public/*package*/ open operator fun get(): R|ft<test/ReturnInnerSubclassOfSupersInner.Super<ft<A & Any, A?>>, test/ReturnInnerSubclassOfSupersInner.Super<ft<A & Any, A?>>?>|
public/*package*/ open fun get(): R|ft<test/ReturnInnerSubclassOfSupersInner.Super<ft<A & Any, A?>>, test/ReturnInnerSubclassOfSupersInner.Super<ft<A & Any, A?>>?>|
public/*package*/ test/ReturnInnerSubclassOfSupersInner.Super<A>.constructor(): R|test/ReturnInnerSubclassOfSupersInner.Super.Inner<A>|