[FIR] Remove FirNamedArgumentExpressions during completion

They are mostly necessary for argument mapping during resolution.
To support a couple checkers, we transform named args for varargs
into "fake" spread expressions.

Other than that, named arguments aren't needed for anything and often
lead to bugs where we forget to unwrap them for something, so it's
better to get rid of them.

#KT-66124
This commit is contained in:
Kirill Rakhman
2024-03-01 16:00:11 +01:00
committed by Space Team
parent 03fc0fd381
commit 8443daf78d
54 changed files with 249 additions and 117 deletions
@@ -48,8 +48,9 @@ object FirJavaAnnotationsChecker : FirAnnotationChecker(MppCheckerKind.Common) {
if (expression is FirAnnotationCall) {
val argumentList = expression.argumentList
if (argumentList is FirResolvedArgumentList) {
for ((key, value) in argumentList.mapping) {
if (value.name != Annotations.ParameterNames.value && key !is FirWrappedArgumentExpression) {
val arguments = argumentList.originalArgumentList?.arguments ?: return
for (key in arguments) {
if (key !is FirWrappedArgumentExpression && argumentList.mapping[key]?.name.let { it != null && it != Annotations.ParameterNames.value}) {
reporter.reportOn(key.source, FirJvmErrors.POSITIONED_VALUE_ARGUMENT_FOR_JAVA_ANNOTATION, context)
}
}
@@ -92,6 +92,10 @@ private class FirConstCheckVisitor(private val session: FirSession) : FirVisitor
return namedArgumentExpression.expression.accept(this, data)
}
override fun visitSpreadArgumentExpression(spreadArgumentExpression: FirSpreadArgumentExpression, data: Nothing?): ConstantArgumentKind {
return spreadArgumentExpression.expression.accept(this, data)
}
override fun visitTypeOperatorCall(typeOperatorCall: FirTypeOperatorCall, data: Nothing?): ConstantArgumentKind {
return if (typeOperatorCall.operation == FirOperation.AS) ConstantArgumentKind.NOT_CONST else ConstantArgumentKind.VALID_CONST
}
@@ -50,7 +50,7 @@ object FirAnnotationExpressionChecker : FirAnnotationCallChecker(MppCheckerKind.
val annotationClassId = expression.toAnnotationClassId(context.session)
val fqName = annotationClassId?.asSingleFqName()
for (arg in argumentMapping.values) {
val argExpression = (arg as? FirNamedArgumentExpression)?.expression ?: (arg as? FirErrorExpression)?.expression ?: arg
val argExpression = (arg as? FirErrorExpression)?.expression ?: arg
checkAnnotationArgumentWithSubElements(argExpression, context.session, reporter, context)
?.let { reporter.reportOn(argExpression.source, it, context) }
}
@@ -123,8 +123,7 @@ object FirAnnotationExpressionChecker : FirAnnotationCallChecker(MppCheckerKind.
context: CheckerContext,
reporter: DiagnosticReporter
): ApiVersion? {
val constantExpression = (expression as? FirLiteralExpression<*>)
?: ((expression as? FirNamedArgumentExpression)?.expression as? FirLiteralExpression<*>) ?: return null
val constantExpression = (expression as? FirLiteralExpression<*>) ?: return null
val stringValue = constantExpression.value as? String ?: return null
if (!stringValue.matches(RequireKotlinConstants.VERSION_REGEX)) {
reporter.reportOn(expression.source, FirErrors.ILLEGAL_KOTLIN_VERSION_STRING_VALUE, context)
@@ -20,6 +20,8 @@ import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.languageVersionSettings
import org.jetbrains.kotlin.fir.references.FirErrorNamedReference
import org.jetbrains.kotlin.fir.types.*
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
object FirNamedVarargChecker : FirCallChecker(MppCheckerKind.Common) {
override fun check(expression: FirCall, context: CheckerContext, reporter: DiagnosticReporter) {
@@ -38,8 +40,8 @@ object FirNamedVarargChecker : FirCallChecker(MppCheckerKind.Common) {
)
fun checkArgument(argument: FirExpression, isVararg: Boolean, expectedArrayType: ConeKotlinType) {
if (argument !is FirNamedArgumentExpression) return
if (argument.isSpread) {
if (!isNamedSpread(argument)) return
if (!argument.isFakeSpread && argument.isNamed) {
if (isVararg && (expression as? FirResolvable)?.calleeReference !is FirErrorNamedReference) {
reporter.reportOn(argument.expression.source, redundantSpreadWarningFactory, context)
}
@@ -69,17 +71,25 @@ object FirNamedVarargChecker : FirCallChecker(MppCheckerKind.Common) {
if (expression is FirArrayLiteral) {
// FirArrayLiteral has the `vararg` argument expression pre-flattened and doesn't have an argument mapping.
expression.arguments.forEach { checkArgument(it, it is FirNamedArgumentExpression, expression.resolvedType) }
expression.arguments.forEach { checkArgument(it, isVararg = isNamedSpread(it), expression.resolvedType) }
} else {
val argumentMap = expression.resolvedArgumentMapping ?: return
for ((argument, parameter) in argumentMap) {
if (!parameter.isVararg) continue
if (argument is FirVarargArgumentsExpression) {
argument.arguments.forEach { checkArgument(it, true, parameter.returnTypeRef.coneType) }
argument.arguments.forEach { checkArgument(it, isVararg = true, parameter.returnTypeRef.coneType) }
} else {
checkArgument(argument, false, parameter.returnTypeRef.coneType)
checkArgument(argument, isVararg = false, parameter.returnTypeRef.coneType)
}
}
}
}
@OptIn(ExperimentalContracts::class)
private fun isNamedSpread(expression: FirExpression): Boolean {
contract {
returns(true) implies (expression is FirSpreadArgumentExpression)
}
return expression is FirSpreadArgumentExpression && expression.isNamed
}
}
@@ -23,7 +23,7 @@ object FirSpreadOfNullableChecker : FirFunctionCallChecker(MppCheckerKind.Common
override fun check(expression: FirFunctionCall, context: CheckerContext, reporter: DiagnosticReporter) {
fun checkAndReport(argument: FirExpression, source: KtSourceElement?) {
val coneType = argument.resolvedType
if (argument is FirSpreadArgumentExpression && coneType !is ConeFlexibleType && coneType.canBeNull(context.session)) {
if (argument is FirSpreadArgumentExpression && !argument.isFakeSpread && coneType !is ConeFlexibleType && coneType.canBeNull(context.session)) {
reporter.reportOn(source, FirErrors.SPREAD_OF_NULLABLE, context)
}
}