[JS FIR] Implement JSCODE_ARGUMENT_NON_CONST_EXPRESSION FIR checker

The meaning of this check is the same as K1
JSCODE_ARGUMENT_SHOULD_BE_CONSTANT and JSCODE_ARGUMENT_NON_CONST_EXPRESSION
diagnostics.

The main difference is that K2 JSCODE_ARGUMENT_NON_CONST_EXPRESSION
diagnostic checks the js() argument in the same way as
const val initializers or annotation arguments are checked.

This means that, the K2 diagnostic is stricter than original
K1 JSCODE_ARGUMENT_SHOULD_BE_CONSTANT diagnostic,
which allows the use of non-constant vals.

^KT-59435 Fixed
This commit is contained in:
Alexander Korepanov
2023-10-17 14:58:10 +02:00
committed by Space Team
parent 2d934f6357
commit 71eaf651e8
6 changed files with 69 additions and 3 deletions
@@ -22,6 +22,7 @@ object JsExpressionCheckers : ExpressionCheckers() {
override val functionCallCheckers: Set<FirFunctionCallChecker>
get() = setOf(
FirJsCodeConstantArgumentChecker,
FirJsReifiedExternalChecker
)
@@ -0,0 +1,58 @@
/*
* 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.analysis.js.checkers.expression
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.analysis.checkers.canBeEvaluatedAtCompileTime
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirFunctionCallChecker
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors
import org.jetbrains.kotlin.fir.declarations.utils.isConst
import org.jetbrains.kotlin.fir.expressions.FirExpression
import org.jetbrains.kotlin.fir.expressions.FirFunctionCall
import org.jetbrains.kotlin.fir.expressions.FirPropertyAccessExpression
import org.jetbrains.kotlin.fir.expressions.arguments
import org.jetbrains.kotlin.fir.references.toResolvedCallableSymbol
import org.jetbrains.kotlin.fir.types.isString
import org.jetbrains.kotlin.fir.types.resolvedType
import org.jetbrains.kotlin.fir.visitors.FirVisitorVoid
import org.jetbrains.kotlin.name.JsStandardClassIds
object FirJsCodeConstantArgumentChecker : FirFunctionCallChecker() {
override fun check(expression: FirFunctionCall, context: CheckerContext, reporter: DiagnosticReporter) {
if (expression.calleeReference.toResolvedCallableSymbol()?.callableId != JsStandardClassIds.Callables.JsCode) {
return
}
val jsCodeExpression = expression.arguments.firstOrNull()
if (jsCodeExpression == null || !jsCodeExpression.resolvedType.isString) {
reporter.reportOn(jsCodeExpression?.source ?: expression.source, FirJsErrors.JSCODE_ARGUMENT_NON_CONST_EXPRESSION, context)
return
}
jsCodeExpression.accept(object : FirVisitorVoid() {
var lastReportedElement: FirElement? = null
override fun visitElement(element: FirElement) {
val lastReported = lastReportedElement
element.acceptChildren(this)
if (lastReported == lastReportedElement && !canBeEvaluatedAtCompileTime(element as? FirExpression, context.session)) {
lastReportedElement = element
val source = element.source ?: jsCodeExpression.source
reporter.reportOn(source, FirJsErrors.JSCODE_ARGUMENT_NON_CONST_EXPRESSION, context)
}
}
override fun visitPropertyAccessExpression(propertyAccessExpression: FirPropertyAccessExpression) {
if (propertyAccessExpression.calleeReference.toResolvedCallableSymbol()?.isConst != true) {
super.visitPropertyAccessExpression(propertyAccessExpression)
}
}
})
}
}
@@ -31,6 +31,10 @@ import org.jetbrains.kotlin.util.OperatorNameConventions
fun ConeKotlinType.canBeUsedForConstVal(): Boolean = with(lowerBoundIfFlexible()) { isPrimitive || isString || isUnsignedType }
fun canBeEvaluatedAtCompileTime(expression: FirExpression?, session: FirSession): Boolean {
return checkConstantArguments(expression, session) == null
}
internal fun checkConstantArguments(
expression: FirExpression?,
session: FirSession,
@@ -67,7 +67,7 @@ object FirAnnotationClassDeclarationChecker : FirRegularClassChecker() {
reporter.reportOn(source, FirErrors.VAR_ANNOTATION_PARAMETER, context)
}
val defaultValue = parameter.defaultValue
if (defaultValue != null && checkConstantArguments(defaultValue, context.session) != null) {
if (defaultValue != null && !canBeEvaluatedAtCompileTime(defaultValue, context.session)) {
reporter.reportOn(defaultValue.source, FirErrors.ANNOTATION_PARAMETER_DEFAULT_VALUE_MUST_BE_CONSTANT, context)
}
@@ -8,12 +8,12 @@ package org.jetbrains.kotlin.fir.analysis.checkers.declaration
import org.jetbrains.kotlin.KtFakeSourceElementKind
import org.jetbrains.kotlin.descriptors.ClassKind
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.checkers.getModifier
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.analysis.checkers.canBeEvaluatedAtCompileTime
import org.jetbrains.kotlin.fir.declarations.FirProperty
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
import org.jetbrains.kotlin.fir.declarations.utils.isConst
@@ -62,7 +62,7 @@ object FirConstPropertyChecker : FirPropertyChecker() {
return
}
if (checkConstantArguments(initializer, context.session) != null) {
if (!canBeEvaluatedAtCompileTime(initializer, context.session)) {
reporter.reportOn(initializer.source, FirErrors.CONST_VAL_WITH_NON_CONST_INITIALIZER, context)
}
}
@@ -58,6 +58,9 @@ object JsStandardClassIds {
}
object Callables {
@JvmField
val JsCode = "js".callableId(BASE_JS_PACKAGE)
@JvmField
val JsDefinedExternally = "definedExternally".callableId(BASE_JS_PACKAGE)