FIR: introduce three diagnostics for const val

This commit is contained in:
zadorotskas
2021-03-05 12:51:26 +03:00
committed by Mikhail Glukhikh
parent cc106085e2
commit 334d0a8b5a
23 changed files with 451 additions and 200 deletions
@@ -946,6 +946,11 @@ public class LazyBodyIsNotTouchedTilContractsPhaseTestGenerated extends Abstract
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/fir/analysis-tests/testData/resolve/constVal"), Pattern.compile("^([^.]+)\\.kt$"), null, true);
}
@TestMetadata("const.kt")
public void testConst() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/constVal/const.kt");
}
@TestMetadata("constValNotTopLevelOrObject.kt")
public void testConstValNotTopLevelOrObject() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/constVal/constValNotTopLevelOrObject.kt");
@@ -0,0 +1,60 @@
FILE: const.kt
public final const val a: R|kotlin/String| = String(something)
public get(): R|kotlin/String|
public final const val b: <ERROR TYPE REF: Cannot infer variable type without initializer / getter / delegate>
public get(): <ERROR TYPE REF: Cannot infer variable type without initializer / getter / delegate>
public final const val c: R|kotlin/Nothing?| = Null(null)
public get(): R|kotlin/Nothing?|
public final const val d: R|ForConst.Companion| = Q|ForConst|
public get(): R|ForConst.Companion|
public final const val e: R|kotlin/String| = Q|ForConst|.R|/ForConst.Companion.one|()
public get(): R|kotlin/String|
public final const val f: R|kotlin/Int| = Int(1).R|kotlin/Int.plus|(Int(2)).R|kotlin/Int.times|(Int(3)).R|kotlin/Int.div|(Int(4)).R|kotlin/Int.rem|(Int(5)).R|kotlin/Int.minus|(Int(1))
public get(): R|kotlin/Int|
public final const val g: R|kotlin/String| = <strcat>(String(string ), R|/f|.R|kotlin/Any.toString|())
public get(): R|kotlin/String|
public final const val h: R|kotlin/String| = String(string).R|kotlin/String.plus|(R|/g|)
public get(): R|kotlin/String|
public final const val i: R|kotlin/String| = Q|ForConst|.R|/ForConst.Companion.one|().R|kotlin/String.plus|(String(one))
public get(): R|kotlin/String|
public final const val j: R|kotlin/Int| = Int(4).R|kotlin/Int.times|(Q|ForConst|.R|/ForConst.Companion.two|())
public get(): R|kotlin/Int|
public final val k: R|kotlin/Int| = Int(3).R|kotlin/Int.minus|(Q|ForConst|.R|/ForConst.Companion.two|())
public get(): R|kotlin/Int|
public final const val l: R|kotlin/Int| = R|/k|
public get(): R|kotlin/Int|
public final class ForConst : R|kotlin/Any| {
public constructor(): R|ForConst| {
super<R|kotlin/Any|>()
}
public final companion object Companion : R|kotlin/Any| {
private constructor(): R|ForConst.Companion| {
super<R|kotlin/Any|>()
}
public final fun one(): R|kotlin/String| {
^one String(1)
}
public final fun two(): R|kotlin/Int| {
^two Int(2)
}
}
}
private final const val MAJOR_BITS: R|kotlin/Int| = Int(3)
private get(): R|kotlin/Int|
private final const val MINOR_BITS: R|kotlin/Int| = Int(4)
private get(): R|kotlin/Int|
private final const val PATCH_BITS: R|kotlin/Int| = Int(7)
private get(): R|kotlin/Int|
private final const val MAJOR_MASK: R|kotlin/Int| = Int(1).R|kotlin/Int.shl|(R|/MAJOR_BITS|).R|kotlin/Int.minus|(Int(1))
private get(): R|kotlin/Int|
private final const val MINOR_MASK: R|kotlin/Int| = Int(1).R|kotlin/Int.shl|(R|/MINOR_BITS|).R|kotlin/Int.minus|(Int(1))
private get(): R|kotlin/Int|
private final const val PATCH_MASK: R|kotlin/Int| = Int(1).R|kotlin/Int.shl|(R|/PATCH_BITS|).R|kotlin/Int.minus|(Int(1))
private get(): R|kotlin/Int|
private final const val stringFromJava: R|kotlin/String| = Q|Constants|.R|/Constants.FIRST|.R|kotlin/String.plus|(String(+)).R|kotlin/String.plus|(Q|Constants|.R|/Constants.SECOND|)
private get(): R|kotlin/String|
@@ -0,0 +1,36 @@
// FILE: Constants.java
public class Constants {
public static final String FIRST = "1st";
public static final String SECOND = "2nd";
}
// FILE: const.kt
const val a = "something"
<!MUST_BE_INITIALIZED!><!CONST_VAL_WITHOUT_INITIALIZER!>const<!> val b<!>
<!TYPE_CANT_BE_USED_FOR_CONST_VAL!>const<!> val c = null
<!TYPE_CANT_BE_USED_FOR_CONST_VAL!>const<!> val d = ForConst
const val e = <!CONST_VAL_WITH_NON_CONST_INITIALIZER!>ForConst.one()<!>
const val f = ((1 + 2) * 3) / 4 % 5 - 1
const val g = "string $f"
const val h = "string" + g
const val i = <!CONST_VAL_WITH_NON_CONST_INITIALIZER!>ForConst.one() + "one"<!>
const val j = <!CONST_VAL_WITH_NON_CONST_INITIALIZER!>4 * ForConst.two()<!>
val k = 3 - ForConst.two()
const val l = <!CONST_VAL_WITH_NON_CONST_INITIALIZER!>k<!>
class ForConst{
companion object {
fun one(): String = "1"
fun two(): Int = 2
}
}
private const val MAJOR_BITS = 3
private const val MINOR_BITS = 4
private const val PATCH_BITS = 7
private const val MAJOR_MASK = (1 shl MAJOR_BITS) - 1 // False positive error
private const val MINOR_MASK = (1 shl MINOR_BITS) - 1 // False positive error
private const val PATCH_MASK = (1 shl PATCH_BITS) - 1 // False positive error
private const val stringFromJava = Constants.FIRST + "+" + Constants.SECOND
@@ -1093,6 +1093,12 @@ public class FirDiagnosticTestGenerated extends AbstractFirDiagnosticTest {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/fir/analysis-tests/testData/resolve/constVal"), Pattern.compile("^([^.]+)\\.kt$"), null, true);
}
@Test
@TestMetadata("const.kt")
public void testConst() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/constVal/const.kt");
}
@Test
@TestMetadata("constValNotTopLevelOrObject.kt")
public void testConstValNotTopLevelOrObject() throws Exception {
@@ -1102,6 +1102,12 @@ public class FirDiagnosticsWithLightTreeTestGenerated extends AbstractFirDiagnos
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/fir/analysis-tests/testData/resolve/constVal"), Pattern.compile("^([^.]+)\\.kt$"), null, true);
}
@Test
@TestMetadata("const.kt")
public void testConst() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/constVal/const.kt");
}
@Test
@TestMetadata("constValNotTopLevelOrObject.kt")
public void testConstValNotTopLevelOrObject() throws Exception {
@@ -496,6 +496,11 @@ object DIAGNOSTICS_LIST : DiagnosticList() {
val CONST_VAL_NOT_TOP_LEVEL_OR_OBJECT by error<FirSourceElement, KtProperty>(PositioningStrategy.CONST_MODIFIER)
val CONST_VAL_WITH_GETTER by error<FirSourceElement, KtProperty>()
val CONST_VAL_WITH_DELEGATE by error<FirSourceElement, KtPropertyDelegate>()
val TYPE_CANT_BE_USED_FOR_CONST_VAL by error<FirSourceElement, KtProperty>(PositioningStrategy.CONST_MODIFIER) {
parameter<ConeKotlinType>("constValType")
}
val CONST_VAL_WITHOUT_INITIALIZER by error<FirSourceElement, KtProperty>(PositioningStrategy.CONST_MODIFIER)
val CONST_VAL_WITH_NON_CONST_INITIALIZER by error<FirSourceElement, KtExpression>()
val WRONG_SETTER_PARAMETER_TYPE by error<FirSourceElement, KtTypeReference> {
parameter<ConeKotlinType>("expectedType")
parameter<ConeKotlinType>("actualType")
@@ -312,6 +312,9 @@ object FirErrors {
val CONST_VAL_NOT_TOP_LEVEL_OR_OBJECT by error0<FirSourceElement, KtProperty>(SourceElementPositioningStrategies.CONST_MODIFIER)
val CONST_VAL_WITH_GETTER by error0<FirSourceElement, KtProperty>()
val CONST_VAL_WITH_DELEGATE by error0<FirSourceElement, KtPropertyDelegate>()
val TYPE_CANT_BE_USED_FOR_CONST_VAL by error1<FirSourceElement, KtProperty, ConeKotlinType>(SourceElementPositioningStrategies.CONST_MODIFIER)
val CONST_VAL_WITHOUT_INITIALIZER by error0<FirSourceElement, KtProperty>(SourceElementPositioningStrategies.CONST_MODIFIER)
val CONST_VAL_WITH_NON_CONST_INITIALIZER by error0<FirSourceElement, KtExpression>()
val WRONG_SETTER_PARAMETER_TYPE by error2<FirSourceElement, KtTypeReference, ConeKotlinType, ConeKotlinType>()
val INITIALIZER_TYPE_MISMATCH by error2<FirSourceElement, KtProperty, ConeKotlinType, ConeKotlinType>(SourceElementPositioningStrategies.ASSIGNMENT_VALUE)
@@ -0,0 +1,207 @@
/*
* Copyright 2010-2021 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.analysis.checkers
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.FirSymbolOwner
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.references.FirErrorNamedReference
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
import org.jetbrains.kotlin.fir.resolve.toSymbol
import org.jetbrains.kotlin.fir.scopes.impl.FirIntegerOperatorCall
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.name.StandardClassIds
import org.jetbrains.kotlin.util.OperatorNameConventions
fun ConeKotlinType.canBeUsedForConstVal(): Boolean {
val classId = if (this is ConeFlexibleType) {
val lb = this.lowerBoundIfFlexible()
if (lb is ConeClassLikeType) lb.lookupTag.classId
else this.classId
} else this.classId
return (classId in StandardClassIds.primitiveTypes || classId in StandardClassIds.unsignedTypes) && !this.isNullable ||
classId == StandardClassIds.String
}
internal fun checkConstantArguments(
expression: FirExpression,
session: FirSession,
): ConstantArgumentKind? {
val expressionSymbol = expression.toResolvedCallableSymbol()
?.fir
val classKindOfParent = (expressionSymbol
?.getReferencedClass(session) as? FirRegularClass)
?.classKind
when {
expression is FirTypeOperatorCall -> {
if (expression.operation == FirOperation.AS) return ConstantArgumentKind.NOT_CONST
}
expression is FirConstExpression<*>
|| expressionSymbol is FirEnumEntry
|| (expressionSymbol as? FirMemberDeclaration)?.isConst == true
|| expressionSymbol is FirConstructor && classKindOfParent == ClassKind.ANNOTATION_CLASS -> {
//DO NOTHING
}
classKindOfParent == ClassKind.ENUM_CLASS -> {
return ConstantArgumentKind.ENUM_NOT_CONST
}
expression is FirComparisonExpression -> {
return checkConstantArguments(expression.compareToCall, session)
}
expression is FirIntegerOperatorCall -> {
for (exp in (expression as FirCall).arguments.plus(expression.dispatchReceiver))
checkConstantArguments(exp, session).let { return it }
}
expression is FirStringConcatenationCall || expression is FirEqualityOperatorCall -> {
for (exp in (expression as FirCall).arguments)
checkConstantArguments(exp, session).let { return it }
}
(expression is FirGetClassCall) -> {
var coneType = (expression as? FirCall)
?.argument
?.typeRef
?.coneType
if (coneType is ConeClassErrorType)
return ConstantArgumentKind.NOT_CONST
while (coneType?.classId == StandardClassIds.Array)
coneType = (coneType.lowerBoundIfFlexible().typeArguments.first() as? ConeKotlinTypeProjection)?.type ?: break
return when {
coneType is ConeTypeParameterType ->
ConstantArgumentKind.KCLASS_LITERAL_OF_TYPE_PARAMETER_ERROR
(expression as FirCall).argument !is FirResolvedQualifier ->
ConstantArgumentKind.NOT_KCLASS_LITERAL
else ->
null
}
}
expressionSymbol == null -> {
//DO NOTHING
}
expressionSymbol is FirField -> {
//TODO: fix checking of Java fields initializer
if (
!(expressionSymbol as FirMemberDeclaration).status.isStatic
|| (expressionSymbol as FirMemberDeclaration).status.modality != Modality.FINAL
)
return ConstantArgumentKind.NOT_CONST
}
expression is FirFunctionCall -> {
val calleeReference = expression.calleeReference
if (calleeReference is FirErrorNamedReference) {
return null
}
if (expression.typeRef.coneType.classId == StandardClassIds.KClass) {
return ConstantArgumentKind.NOT_KCLASS_LITERAL
}
//TODO: UNRESOLVED REFERENCE
if (expression.dispatchReceiver is FirThisReceiverExpression) {
return null
}
when (calleeReference.name) {
in OperatorNameConventions.BINARY_OPERATION_NAMES, in OperatorNameConventions.UNARY_OPERATION_NAMES,
OperatorNameConventions.SHL, OperatorNameConventions.SHR, OperatorNameConventions.USHR,
OperatorNameConventions.OR, OperatorNameConventions.AND -> {
val coneType =
expression.dispatchReceiver.typeRef.coneTypeSafe<ConeKotlinType>() ?: return ConstantArgumentKind.NOT_CONST
val receiverClassId = coneType.lowerBoundIfFlexible().classId
if ((calleeReference.name == OperatorNameConventions.DIV || calleeReference.name == OperatorNameConventions.REM)
&& expression.typeRef.coneType.classId == StandardClassIds.Int
) {
val value = expression.arguments.first() as? FirConstExpression<*>
if (value?.value == 0L) {
return ConstantArgumentKind.NOT_CONST
}
}
for (exp in (expression as FirCall).arguments.plus(expression.dispatchReceiver)) {
val expClassId = exp.typeRef.coneType.lowerBoundIfFlexible().classId
if (calleeReference.name == OperatorNameConventions.PLUS
&& expClassId != receiverClassId
&& (expClassId !in StandardClassIds.primitiveTypesAndString || receiverClassId !in StandardClassIds.primitiveTypesAndString)
) {
return ConstantArgumentKind.NOT_CONST
}
checkConstantArguments(exp, session)?.let { return it }
}
}
else -> {
if (expression.arguments.isNotEmpty() || calleeReference !is FirResolvedNamedReference) {
return ConstantArgumentKind.NOT_CONST
}
val symbol = calleeReference.resolvedSymbol as? FirCallableSymbol
if (calleeReference.name == OperatorNameConventions.TO_STRING ||
calleeReference.name in CONVERSION_NAMES && symbol?.callableId?.packageName?.asString() == "kotlin"
) {
return checkConstantArguments(expression.dispatchReceiver, session)
}
return ConstantArgumentKind.NOT_CONST
}
}
}
expression is FirQualifiedAccessExpression -> {
when {
(expressionSymbol as FirProperty).isLocal || expressionSymbol.symbol.callableId.className?.isRoot == false ->
return ConstantArgumentKind.NOT_CONST
expression.typeRef.coneType.classId == StandardClassIds.KClass ->
return ConstantArgumentKind.NOT_KCLASS_LITERAL
//TODO: UNRESOLVED REFERENCE
expression.dispatchReceiver is FirThisReceiverExpression ->
return null
}
return when ((expressionSymbol as? FirProperty)?.initializer) {
is FirConstExpression<*> -> {
if ((expressionSymbol as? FirVariable)?.isVal == true)
ConstantArgumentKind.NOT_CONST_VAL_IN_CONST_EXPRESSION
else
ConstantArgumentKind.NOT_CONST
}
is FirGetClassCall ->
ConstantArgumentKind.NOT_KCLASS_LITERAL
else ->
ConstantArgumentKind.NOT_CONST
}
}
else ->
return ConstantArgumentKind.NOT_CONST
}
return null
}
private fun FirTypedDeclaration?.getReferencedClass(session: FirSession): FirSymbolOwner<*>? =
this?.returnTypeRef
?.coneTypeSafe<ConeLookupTagBasedType>()
?.lookupTag
?.toSymbol(session)
?.fir
private val CONVERSION_NAMES = listOf(
"toInt", "toLong", "toShort", "toByte", "toFloat", "toDouble", "toChar", "toBoolean"
).mapTo(hashSetOf()) { Name.identifier(it) }
internal enum class ConstantArgumentKind {
NOT_CONST,
ENUM_NOT_CONST,
NOT_KCLASS_LITERAL,
NOT_CONST_VAL_IN_CONST_EXPRESSION,
KCLASS_LITERAL_OF_TYPE_PARAMETER_ERROR
}
@@ -11,6 +11,8 @@ import org.jetbrains.kotlin.fir.FirAnnotationContainer
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.FirSourceElement
import org.jetbrains.kotlin.fir.FirSymbolOwner
import org.jetbrains.kotlin.fir.analysis.checkers.ConstantArgumentKind
import org.jetbrains.kotlin.fir.analysis.checkers.checkConstantArguments
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticFactory0
@@ -78,158 +80,15 @@ object FirAnnotationArgumentChecker : FirBasicDeclarationChecker() {
?.let { reporter.reportOn(arg.source, it, context) }
}
else ->
return checkAnnotationArgument(expression, session)
return when (checkConstantArguments(expression, session)) {
ConstantArgumentKind.NOT_CONST -> FirErrors.ANNOTATION_ARGUMENT_MUST_BE_CONST
ConstantArgumentKind.ENUM_NOT_CONST -> FirErrors.ANNOTATION_ARGUMENT_MUST_BE_ENUM_CONST
ConstantArgumentKind.NOT_KCLASS_LITERAL -> FirErrors.ANNOTATION_ARGUMENT_MUST_BE_KCLASS_LITERAL
ConstantArgumentKind.KCLASS_LITERAL_OF_TYPE_PARAMETER_ERROR -> FirErrors.ANNOTATION_ARGUMENT_KCLASS_LITERAL_OF_TYPE_PARAMETER_ERROR
ConstantArgumentKind.NOT_CONST_VAL_IN_CONST_EXPRESSION -> FirErrors.NON_CONST_VAL_USED_IN_CONSTANT_EXPRESSION
null -> null
}
}
return null
}
private fun checkAnnotationArgument(
expression: FirExpression,
session: FirSession,
): FirDiagnosticFactory0<FirSourceElement, KtExpression>? {
val expressionSymbol = expression.toResolvedCallableSymbol()
?.fir
val classKindOfParent = (expressionSymbol
?.getReferencedClass(session) as? FirRegularClass)
?.classKind
when {
expression is FirConstExpression<*>
|| expressionSymbol is FirEnumEntry
|| (expressionSymbol as? FirMemberDeclaration)?.isConst == true
|| expressionSymbol is FirConstructor && classKindOfParent == ClassKind.ANNOTATION_CLASS -> {
//DO NOTHING
}
classKindOfParent == ClassKind.ENUM_CLASS -> {
return FirErrors.ANNOTATION_ARGUMENT_MUST_BE_ENUM_CONST
}
expression is FirComparisonExpression -> {
return checkAnnotationArgument(expression.compareToCall, session)
}
expression is FirIntegerOperatorCall -> {
for (exp in (expression as FirCall).arguments.plus(expression.dispatchReceiver))
checkAnnotationArgument(exp, session).let { return it }
}
expression is FirStringConcatenationCall || expression is FirEqualityOperatorCall -> {
for (exp in (expression as FirCall).arguments)
checkAnnotationArgument(exp, session).let { return it }
}
(expression is FirGetClassCall) -> {
var coneType = (expression as? FirCall)
?.argument
?.typeRef
?.coneType
if (coneType is ConeClassErrorType)
return FirErrors.ANNOTATION_ARGUMENT_MUST_BE_CONST
while (coneType?.classId == StandardClassIds.Array)
coneType = (coneType.lowerBoundIfFlexible().typeArguments.first() as? ConeKotlinTypeProjection)?.type ?: break
return when {
coneType is ConeTypeParameterType ->
FirErrors.ANNOTATION_ARGUMENT_KCLASS_LITERAL_OF_TYPE_PARAMETER_ERROR
(expression as FirCall).argument !is FirResolvedQualifier ->
FirErrors.ANNOTATION_ARGUMENT_MUST_BE_KCLASS_LITERAL
else ->
null
}
}
expressionSymbol == null -> {
//DO NOTHING
}
expressionSymbol is FirField -> {
//TODO: fix checking of Java fields initializer
if (
!(expressionSymbol as FirMemberDeclaration).status.isStatic
|| (expressionSymbol as FirMemberDeclaration).status.modality != Modality.FINAL
)
return FirErrors.ANNOTATION_ARGUMENT_MUST_BE_CONST
}
expression is FirFunctionCall -> {
val calleeReference = expression.calleeReference
if (calleeReference is FirErrorNamedReference) {
return null
}
if (expression.typeRef.coneType.classId == StandardClassIds.KClass) {
return FirErrors.ANNOTATION_ARGUMENT_MUST_BE_KCLASS_LITERAL
}
//TODO: UNRESOLVED REFERENCE
if (expression.dispatchReceiver is FirThisReceiverExpression) {
return null
}
when (calleeReference.name) {
in BINARY_OPERATION_NAMES, in UNARY_OPERATION_NAMES -> {
val receiverClassId = expression.dispatchReceiver.typeRef.coneType.classId
for (exp in (expression as FirCall).arguments.plus(expression.dispatchReceiver)) {
val expClassId = exp.typeRef.coneType.classId
if (calleeReference.name == PLUS
&& expClassId != receiverClassId
&& (expClassId !in primitiveTypesAndString || receiverClassId !in primitiveTypesAndString)
)
return FirErrors.ANNOTATION_ARGUMENT_MUST_BE_CONST
checkAnnotationArgument(exp, session)?.let { return it }
}
}
else -> {
if (expression.arguments.isNotEmpty() || calleeReference !is FirResolvedNamedReference) {
return FirErrors.ANNOTATION_ARGUMENT_MUST_BE_CONST
}
val symbol = calleeReference.resolvedSymbol as? FirCallableSymbol
if (calleeReference.name == TO_STRING ||
calleeReference.name in CONVERSION_NAMES && symbol?.callableId?.packageName?.asString() == "kotlin"
) {
return checkAnnotationArgument(expression.dispatchReceiver, session)
}
return FirErrors.ANNOTATION_ARGUMENT_MUST_BE_CONST
}
}
}
expression is FirQualifiedAccessExpression -> {
when {
(expressionSymbol as FirProperty).isLocal || expressionSymbol.symbol.callableId.className?.isRoot == false ->
return FirErrors.ANNOTATION_ARGUMENT_MUST_BE_CONST
expression.typeRef.coneType.classId == StandardClassIds.KClass ->
return FirErrors.ANNOTATION_ARGUMENT_MUST_BE_KCLASS_LITERAL
//TODO: UNRESOLVED REFERENCE
expression.dispatchReceiver is FirThisReceiverExpression ->
return null
}
return when ((expressionSymbol as? FirProperty)?.initializer) {
is FirConstExpression<*> -> {
if ((expressionSymbol as? FirVariable)?.isVal == true)
FirErrors.NON_CONST_VAL_USED_IN_CONSTANT_EXPRESSION
else
FirErrors.ANNOTATION_ARGUMENT_MUST_BE_CONST
}
is FirGetClassCall ->
FirErrors.ANNOTATION_ARGUMENT_MUST_BE_KCLASS_LITERAL
else ->
FirErrors.ANNOTATION_ARGUMENT_MUST_BE_CONST
}
}
else ->
return FirErrors.ANNOTATION_ARGUMENT_MUST_BE_CONST
}
return null
}
private fun FirTypedDeclaration?.getReferencedClass(session: FirSession): FirSymbolOwner<*>? =
this?.returnTypeRef
?.coneTypeSafe<ConeLookupTagBasedType>()
?.lookupTag
?.toSymbol(session)
?.fir
private val CONVERSION_NAMES = listOf(
"toInt", "toLong", "toShort", "toByte", "toFloat", "toDouble", "toChar", "toBoolean"
).mapTo(hashSetOf()) { Name.identifier(it) }
}
@@ -7,6 +7,8 @@ package org.jetbrains.kotlin.fir.analysis.checkers.declaration
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.fir.FirFakeSourceElementKind
import org.jetbrains.kotlin.fir.analysis.checkers.canBeUsedForConstVal
import org.jetbrains.kotlin.fir.analysis.checkers.checkConstantArguments
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
@@ -14,6 +16,8 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn
import org.jetbrains.kotlin.fir.declarations.FirProperty
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
import org.jetbrains.kotlin.fir.declarations.isConst
import org.jetbrains.kotlin.fir.types.ConeClassErrorType
import org.jetbrains.kotlin.fir.types.coneType
import org.jetbrains.kotlin.lexer.KtTokens
object FirConstPropertyChecker : FirPropertyChecker() {
@@ -30,19 +34,34 @@ object FirConstPropertyChecker : FirPropertyChecker() {
val classKind = (context.containingDeclarations.lastOrNull() as? FirRegularClass)?.classKind
if (classKind != ClassKind.OBJECT && context.containingDeclarations.size > 1) {
reporter.reportOn(declaration.source, FirErrors.CONST_VAL_NOT_TOP_LEVEL_OR_OBJECT, context)
return
}
if (declaration.getter?.source?.kind !is FirFakeSourceElementKind) {
reporter.reportOn(declaration.getter?.source, FirErrors.CONST_VAL_WITH_GETTER, context)
val source = declaration.getter?.source
if (source != null && source.kind !is FirFakeSourceElementKind) {
reporter.reportOn(source, FirErrors.CONST_VAL_WITH_GETTER, context)
return
}
if (declaration.delegate != null) {
reporter.reportOn(declaration.delegate?.source, FirErrors.CONST_VAL_WITH_DELEGATE, context)
return
}
// TODO: Implement checkers for these errors (see ConstModifierChecker in FE1.0):
// - TYPE_CANT_BE_USED_FOR_CONST_VAL
// - CONST_VAL_WITHOUT_INITIALIZER
// - CONST_VAL_WITH_NON_CONST_INITIALIZER
val initializer = declaration.initializer
if (initializer == null) {
reporter.reportOn(declaration.source, FirErrors.CONST_VAL_WITHOUT_INITIALIZER, context)
return
}
val type = declaration.returnTypeRef.coneType
if ((type !is ConeClassErrorType) && !type.canBeUsedForConstVal()) {
reporter.reportOn(declaration.source, FirErrors.TYPE_CANT_BE_USED_FOR_CONST_VAL, declaration.returnTypeRef.coneType, context)
return
}
if (checkConstantArguments(initializer, context.session) != null) {
reporter.reportOn(initializer.source, FirErrors.CONST_VAL_WITH_NON_CONST_INITIALIZER, context)
}
}
}
@@ -63,6 +63,11 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CONFLICTING_PROJE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CONFLICTING_UPPER_BOUNDS
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CONSTRUCTOR_IN_INTERFACE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CONSTRUCTOR_IN_OBJECT
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CONST_VAL_NOT_TOP_LEVEL_OR_OBJECT
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CONST_VAL_WITHOUT_INITIALIZER
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CONST_VAL_WITH_DELEGATE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CONST_VAL_WITH_GETTER
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CONST_VAL_WITH_NON_CONST_INITIALIZER
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CYCLIC_CONSTRUCTOR_DELEGATION_CALL
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.DATA_CLASS_NOT_PROPERTY_PARAMETER
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.DATA_CLASS_VARARG_PARAMETER
@@ -214,6 +219,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.SYNTAX
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.TOO_MANY_ARGUMENTS
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.TOPLEVEL_TYPEALIASES_ONLY
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.TYPE_ARGUMENTS_NOT_ALLOWED
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.TYPE_CANT_BE_USED_FOR_CONST_VAL
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.TYPE_MISMATCH
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.TYPE_PARAMETERS_IN_ENUM
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.TYPE_PARAMETERS_IN_OBJECT
@@ -693,6 +699,13 @@ class FirDefaultErrorMessages : DefaultErrorMessages.Extension {
)
map.put(INITIALIZER_TYPE_MISMATCH, "Initializer type mismatch: expected {0}, actual {1}", RENDER_TYPE, RENDER_TYPE)
map.put(CONST_VAL_NOT_TOP_LEVEL_OR_OBJECT, "Const 'val' are only allowed on top level or in objects")
map.put(CONST_VAL_WITH_GETTER, "Const 'val' should not have a getter")
map.put(CONST_VAL_WITH_DELEGATE, "Const 'val' should not have a delegate")
map.put(TYPE_CANT_BE_USED_FOR_CONST_VAL, "Const ''val'' has type ''{0}''. Only primitives and String are allowed", RENDER_TYPE)
map.put(CONST_VAL_WITHOUT_INITIALIZER, "Const 'val' should have an initializer")
map.put(CONST_VAL_WITH_NON_CONST_INITIALIZER, "Const 'val' initializer should be a constant value")
// Multi-platform projects
map.put(EXPECTED_DECLARATION_WITH_BODY, "Expected declaration must not have a body")
map.put(EXPECTED_PROPERTY_INITIALIZER, "Expected property cannot have an initializer")
@@ -1,7 +0,0 @@
// !DIAGNOSTICS: -UNUSED_VARIABLE, -UNUSED_PARAMETER
fun Int.mod(other: Int) = 10
fun Int.floorDiv(other: Int): Int = 20
const val a1 = (-5).mod(2)
const val b1 = 5.floorDiv(3)
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// !DIAGNOSTICS: -UNUSED_VARIABLE, -UNUSED_PARAMETER
fun Int.mod(other: Int) = 10
@@ -17,6 +17,6 @@ val a11 = <!DIVISION_BY_ZERO!>1 / 0.0<!>
val a12 = <!DIVISION_BY_ZERO!>1L / 0<!>
val b1: Byte = <!DIVISION_BY_ZERO!>1 / 0<!>
@Ann(<!DIVISION_BY_ZERO!>1 / 0<!>) val b2 = 1
@Ann(<!ANNOTATION_ARGUMENT_MUST_BE_CONST, DIVISION_BY_ZERO!>1 / 0<!>) val b2 = 1
annotation class Ann(val i : Int)
@@ -31,7 +31,7 @@ abstract class C {
final const val final = 11
const val withoutInitializer: Int
<!CONST_VAL_WITHOUT_INITIALIZER!>const<!> val withoutInitializer: Int
init {
withoutInitializer = 12
@@ -70,20 +70,20 @@ class Outer {
const val defaultGetter = 19
<!CONST_VAL_WITH_GETTER!>get<!>
const val nonConstInitializer1 = foo()
const val nonConstInitializer2 = 1 as String
const val nonConstInitializer3 = 1.0 as String
const val nonConstInitializer4 = 1 as Double
const val nonConstInitializer5 = "2" as Int
const val nonConstInitializer6 = 1/0
const val nonConstInitializer7 = -1/0
const val nonConstInitializer8 = 1/0 - 1/0
const val nonConstInitializer9 = 1.0/0.0 - 1/0
const val nonConstInitializer10 = 0/0
const val nonConstInitializer11 = 1 % 0
const val nonConstInitializer12 = 0 % 0
const val nonConstInitializer14 = 0.rem(0)
const val nonConstInitializer15 = 0.div(0)
const val nonConstInitializer1 = <!CONST_VAL_WITH_NON_CONST_INITIALIZER!>foo()<!>
const val nonConstInitializer2 = <!CONST_VAL_WITH_NON_CONST_INITIALIZER!>1 as String<!>
const val nonConstInitializer3 = <!CONST_VAL_WITH_NON_CONST_INITIALIZER!>1.0 as String<!>
const val nonConstInitializer4 = <!CONST_VAL_WITH_NON_CONST_INITIALIZER!>1 as Double<!>
const val nonConstInitializer5 = <!CONST_VAL_WITH_NON_CONST_INITIALIZER!>"2" as Int<!>
const val nonConstInitializer6 = <!CONST_VAL_WITH_NON_CONST_INITIALIZER!>1/0<!>
const val nonConstInitializer7 = <!CONST_VAL_WITH_NON_CONST_INITIALIZER!>-1/0<!>
const val nonConstInitializer8 = <!CONST_VAL_WITH_NON_CONST_INITIALIZER!>1/0 - 1/0<!>
const val nonConstInitializer9 = <!CONST_VAL_WITH_NON_CONST_INITIALIZER!>1.0/0.0 - 1/0<!>
const val nonConstInitializer10 = <!CONST_VAL_WITH_NON_CONST_INITIALIZER!>0/0<!>
const val nonConstInitializer11 = <!CONST_VAL_WITH_NON_CONST_INITIALIZER!>1 % 0<!>
const val nonConstInitializer12 = <!CONST_VAL_WITH_NON_CONST_INITIALIZER!>0 % 0<!>
const val nonConstInitializer14 = <!CONST_VAL_WITH_NON_CONST_INITIALIZER!>0.rem(0)<!>
const val nonConstInitializer15 = <!CONST_VAL_WITH_NON_CONST_INITIALIZER!>0.div(0)<!>
const val constInitializer1 = 1.0/0
const val constInitializer2 = 1/0.0
@@ -9,7 +9,7 @@ const val e = 0.0 % 0
const val g = 0.0.rem(0)
const val h = 0.0.div(0)
const val i = 1 / 0
const val i = <!CONST_VAL_WITH_NON_CONST_INITIALIZER!>1 / 0<!>
val nonConst1 = 1.0 / 0
val nonConst2 = 1 / 0
@@ -1,17 +0,0 @@
// !DIAGNOSTICS: -UNUSED_PARAMETER
const val intConst = 1
const val longConst: Long = 1
const val boolConst = true
const val stringConst = "empty"
enum class MyEnum { A }
const val enumConst: MyEnum = MyEnum.A
const val arrayConst: Array<String> = arrayOf("1")
const val intArrayConst: IntArray = intArrayOf()
const val unresolvedConst1 = <!UNRESOLVED_REFERENCE!>Unresolved<!>
<!WRONG_MODIFIER_TARGET!>const<!> var unresolvedConst2 = <!UNRESOLVED_REFERENCE!>Unresolved<!>
const val unresolvedConst3 = <!PROPERTY_INITIALIZER_NO_BACKING_FIELD, UNRESOLVED_REFERENCE!>Unresolved<!>
<!CONST_VAL_WITH_GETTER!>get() = 10<!>
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// !DIAGNOSTICS: -UNUSED_PARAMETER
const val intConst = 1
@@ -26,7 +26,7 @@ expect var customAccessorVar: String
get() = "no"
set(value) {}
expect const val constVal: Int
expect <!CONST_VAL_WITHOUT_INITIALIZER!>const<!> val constVal: Int
expect <!EXPECTED_LATEINIT_PROPERTY!>lateinit<!> var lateinitVar: String
@@ -1,6 +1,6 @@
val nonConst = 1
const val constConst = nonConst * nonConst + 2
const val constConst = <!CONST_VAL_WITH_NON_CONST_INITIALIZER!>nonConst * nonConst + 2<!>
annotation class Ann(val x: Int, val y: String)
@@ -1430,6 +1430,25 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
token,
)
}
add(FirErrors.TYPE_CANT_BE_USED_FOR_CONST_VAL) { firDiagnostic ->
TypeCantBeUsedForConstValImpl(
firSymbolBuilder.typeBuilder.buildKtType(firDiagnostic.a),
firDiagnostic as FirPsiDiagnostic<*>,
token,
)
}
add(FirErrors.CONST_VAL_WITHOUT_INITIALIZER) { firDiagnostic ->
ConstValWithoutInitializerImpl(
firDiagnostic as FirPsiDiagnostic<*>,
token,
)
}
add(FirErrors.CONST_VAL_WITH_NON_CONST_INITIALIZER) { firDiagnostic ->
ConstValWithNonConstInitializerImpl(
firDiagnostic as FirPsiDiagnostic<*>,
token,
)
}
add(FirErrors.WRONG_SETTER_PARAMETER_TYPE) { firDiagnostic ->
WrongSetterParameterTypeImpl(
firSymbolBuilder.typeBuilder.buildKtType(firDiagnostic.a),
@@ -1011,6 +1011,19 @@ sealed class KtFirDiagnostic<PSI: PsiElement> : KtDiagnosticWithPsi<PSI> {
override val diagnosticClass get() = ConstValWithDelegate::class
}
abstract class TypeCantBeUsedForConstVal : KtFirDiagnostic<KtProperty>() {
override val diagnosticClass get() = TypeCantBeUsedForConstVal::class
abstract val constValType: KtType
}
abstract class ConstValWithoutInitializer : KtFirDiagnostic<KtProperty>() {
override val diagnosticClass get() = ConstValWithoutInitializer::class
}
abstract class ConstValWithNonConstInitializer : KtFirDiagnostic<KtExpression>() {
override val diagnosticClass get() = ConstValWithNonConstInitializer::class
}
abstract class WrongSetterParameterType : KtFirDiagnostic<KtTypeReference>() {
override val diagnosticClass get() = WrongSetterParameterType::class
abstract val expectedType: KtType
@@ -1636,6 +1636,28 @@ internal class ConstValWithDelegateImpl(
override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic)
}
internal class TypeCantBeUsedForConstValImpl(
override val constValType: KtType,
firDiagnostic: FirPsiDiagnostic<*>,
override val token: ValidityToken,
) : KtFirDiagnostic.TypeCantBeUsedForConstVal(), KtAbstractFirDiagnostic<KtProperty> {
override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic)
}
internal class ConstValWithoutInitializerImpl(
firDiagnostic: FirPsiDiagnostic<*>,
override val token: ValidityToken,
) : KtFirDiagnostic.ConstValWithoutInitializer(), KtAbstractFirDiagnostic<KtProperty> {
override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic)
}
internal class ConstValWithNonConstInitializerImpl(
firDiagnostic: FirPsiDiagnostic<*>,
override val token: ValidityToken,
) : KtFirDiagnostic.ConstValWithNonConstInitializer(), KtAbstractFirDiagnostic<KtExpression> {
override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic)
}
internal class WrongSetterParameterTypeImpl(
override val expectedType: KtType,
override val actualType: KtType,