diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirConstChecks.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirConstChecks.kt index d5cb9be4a23..75186784fea 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirConstChecks.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirConstChecks.kt @@ -5,10 +5,12 @@ package org.jetbrains.kotlin.fir.analysis.checkers +import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.Modality -import org.jetbrains.kotlin.fir.FirSession -import org.jetbrains.kotlin.fir.declarations.getAnnotationByClassId +import org.jetbrains.kotlin.fir.* +import org.jetbrains.kotlin.fir.declarations.FirProperty +import org.jetbrains.kotlin.fir.declarations.hasAnnotation import org.jetbrains.kotlin.fir.declarations.utils.isConst import org.jetbrains.kotlin.fir.declarations.utils.isStatic import org.jetbrains.kotlin.fir.declarations.utils.modality @@ -22,8 +24,9 @@ import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol import org.jetbrains.kotlin.fir.symbols.SymbolInternals import org.jetbrains.kotlin.fir.symbols.impl.* import org.jetbrains.kotlin.fir.types.* -import org.jetbrains.kotlin.fir.unwrapFakeOverrides +import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.StandardClassIds +import org.jetbrains.kotlin.util.OperatorNameConventions fun ConeKotlinType.canBeUsedForConstVal(): Boolean = with(lowerBoundIfFlexible()) { isPrimitive || isString || isUnsignedType } @@ -33,6 +36,11 @@ internal fun checkConstantArguments( ): ConstantArgumentKind? { val expressionSymbol = expression.toReference()?.toResolvedCallableSymbol(discardErrorReference = true) val classKindOfParent = (expressionSymbol?.getReferencedClassSymbol(session) as? FirRegularClassSymbol)?.classKind + val intrinsicConstEvaluation = session.languageVersionSettings.supportsFeature(LanguageFeature.IntrinsicConstEvaluation) + + fun FirBasedSymbol<*>.canBeEvaluated(): Boolean { + return intrinsicConstEvaluation && this.hasAnnotation(INTRINSIC_CONST_EVALUATION_ANNOTATION, session) + } when { expression is FirNamedArgumentExpression -> { @@ -40,7 +48,7 @@ internal fun checkConstantArguments( } expression is FirTypeOperatorCall -> if (expression.operation == FirOperation.AS) return ConstantArgumentKind.NOT_CONST expression is FirWhenExpression -> { - if (!expression.isProperlyExhaustive) { + if (!expression.isProperlyExhaustive || !intrinsicConstEvaluation) { return ConstantArgumentKind.NOT_CONST } @@ -125,8 +133,7 @@ internal fun checkConstantArguments( if (calleeReference !is FirResolvedNamedReference) return ConstantArgumentKind.NOT_CONST val symbol = calleeReference.resolvedSymbol as? FirNamedFunctionSymbol ?: return ConstantArgumentKind.NOT_CONST - @OptIn(SymbolInternals::class) - if (symbol.fir.getAnnotationByClassId(INTRINSIC_CONST_EVALUATION_ANNOTATION, session) == null) { + if (!symbol.canBeEvaluated() && !expression.isCompileTimeBuiltinCall()) { return ConstantArgumentKind.NOT_CONST } @@ -154,8 +161,9 @@ internal fun checkConstantArguments( @OptIn(SymbolInternals::class) val property = propertySymbol.fir when { - property.unwrapFakeOverrides().getAnnotationByClassId(INTRINSIC_CONST_EVALUATION_ANNOTATION, session) != null -> { - return checkConstantArguments(expression.dispatchReceiver, session) + property.unwrapFakeOverrides().symbol.canBeEvaluated() || property.isCompileTimeBuiltinProperty() -> { + val receiver = listOf(expression.dispatchReceiver, expression.extensionReceiver).single { it != FirNoReceiverExpression } + return checkConstantArguments(receiver, session) } propertySymbol.isLocal || propertySymbol.callableId.className?.isRoot == false -> return ConstantArgumentKind.NOT_CONST expressionType.classId == StandardClassIds.KClass -> return ConstantArgumentKind.NOT_KCLASS_LITERAL @@ -177,6 +185,58 @@ internal fun checkConstantArguments( return null } +private val compileTimeFunctions = setOf( + *OperatorNameConventions.BINARY_OPERATION_NAMES.toTypedArray(), *OperatorNameConventions.UNARY_OPERATION_NAMES.toTypedArray(), + OperatorNameConventions.SHL, OperatorNameConventions.SHR, OperatorNameConventions.USHR, + OperatorNameConventions.OR, OperatorNameConventions.AND, OperatorNameConventions.XOR, + OperatorNameConventions.COMPARE_TO +) + +private val compileTimeExtensionFunctions = listOf("floorDiv", "mod", "code").mapTo(hashSetOf()) { Name.identifier(it) } + +private val compileTimeConversionFunctions = listOf( + "toInt", "toLong", "toShort", "toByte", "toFloat", "toDouble", "toChar", "toBoolean" +).mapTo(hashSetOf()) { Name.identifier(it) } + +private fun FirFunctionCall.isCompileTimeBuiltinCall(): Boolean { + val calleeReference = this.calleeReference + if (calleeReference !is FirResolvedNamedReference) return false + + val name = calleeReference.name + val symbol = calleeReference.resolvedSymbol as? FirCallableSymbol + if (!symbol.fromKotlin()) return false + + val coneType = this.dispatchReceiver.typeRef.coneTypeSafe() + val receiverClassId = coneType?.lowerBoundIfFlexible()?.classId + + if (receiverClassId in StandardClassIds.unsignedTypes) return false + + if ( + name in compileTimeFunctions || + name in compileTimeExtensionFunctions || + name == OperatorNameConventions.TO_STRING || + name in compileTimeConversionFunctions + ) return true + + if (calleeReference.name == OperatorNameConventions.GET && receiverClassId == StandardClassIds.String) return true + + return false +} + +private fun FirProperty.isCompileTimeBuiltinProperty(): Boolean { + val receiverType = dispatchReceiverType ?: receiverParameter?.typeRef?.coneTypeSafe() ?: return false + val receiverClassId = receiverType.lowerBoundIfFlexible().classId ?: return false + return when (name.asString()) { + "length" -> receiverClassId == StandardClassIds.String + "code" -> receiverClassId == StandardClassIds.Char + else -> false + } +} + +private fun FirCallableSymbol<*>?.fromKotlin(): Boolean { + return this?.callableId?.packageName?.asString() == "kotlin" +} + private fun FirCallableSymbol<*>?.getReferencedClassSymbol(session: FirSession): FirBasedSymbol<*>? = this?.resolvedReturnTypeRef ?.coneTypeSafe() diff --git a/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt b/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt index c23a2b44292..a88729695ca 100644 --- a/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt +++ b/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt @@ -333,6 +333,7 @@ enum class LanguageFeature( ContractSyntaxV2(sinceVersion = null, kind = UNSTABLE_FEATURE), // KT-56127 ImplicitSignedToUnsignedIntegerConversion(sinceVersion = null), // KT-56583 ForbidInferringTypeVariablesIntoEmptyIntersection(sinceVersion = null, kind = BUG_FIX), // KT-51221 + IntrinsicConstEvaluation(sinceVersion = null, kind = UNSTABLE_FEATURE), // KT-49303 ; init {