[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
@@ -5,15 +5,14 @@
package org.jetbrains.kotlin.fir.resolve.calls
import org.jetbrains.kotlin.fir.FirCallResolver
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.declarations.FirFile
import org.jetbrains.kotlin.fir.expressions.FirArgumentList
import org.jetbrains.kotlin.fir.expressions.FirEmptyArgumentList
import org.jetbrains.kotlin.fir.expressions.FirExpression
import org.jetbrains.kotlin.fir.expressions.FirFunctionCallOrigin
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.expressions.builder.buildArgumentList
import org.jetbrains.kotlin.fir.expressions.impl.FirResolvedArgumentList
import org.jetbrains.kotlin.fir.resolve.DoubleColonLHS
import org.jetbrains.kotlin.fir.resolve.ResolutionMode
import org.jetbrains.kotlin.fir.types.ConeKotlinType
@@ -47,8 +46,18 @@ data class CallInfo(
val hasSyntheticOuterCall: Boolean = false,
val origin: FirFunctionCallOrigin = FirFunctionCallOrigin.Regular,
) : AbstractCallInfo() {
val arguments: List<FirExpression> get() = argumentList.arguments
/**
* If [argumentList] is a [FirResolvedArgumentList],
* returns the [FirArgumentList.arguments] of the [FirResolvedArgumentList.originalArgumentList].
* This means the result still contains [FirNamedArgumentExpression]s that are removed from the
* [FirResolvedArgumentList] during completion.
*
* This is important for Analysis API because it will trigger resolution on already resolved expressions,
* and we wouldn't otherwise have access to named arguments.
*
* @see FirCallResolver.collectAllCandidates
*/
val arguments: List<FirExpression> get() = (argumentList as? FirResolvedArgumentList)?.originalArgumentList?.arguments ?: argumentList.arguments
val argumentCount get() = arguments.size
fun replaceWithVariableAccess(): CallInfo =
@@ -17,6 +17,8 @@ import org.jetbrains.kotlin.fir.diagnostics.ConeSimpleDiagnostic
import org.jetbrains.kotlin.fir.diagnostics.DiagnosticKind
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.expressions.builder.buildSamConversionExpression
import org.jetbrains.kotlin.fir.expressions.builder.buildSpreadArgumentExpression
import org.jetbrains.kotlin.fir.expressions.impl.FirResolvedArgumentList
import org.jetbrains.kotlin.fir.references.FirNamedReference
import org.jetbrains.kotlin.fir.references.FirResolvedErrorReference
import org.jetbrains.kotlin.fir.references.builder.buildResolvedCallableReference
@@ -345,16 +347,17 @@ class FirCallCompletionResultsWriterTransformer(
val calleeReference = functionCall.calleeReference as? FirNamedReferenceWithCandidate
?: return functionCall
val result = prepareQualifiedTransform(functionCall, calleeReference)
val originalArgumentList = result.argumentList
val subCandidate = calleeReference.candidate
val resultType = result.resolvedType.substituteType(subCandidate)
if (calleeReference.isError) {
subCandidate.argumentMapping?.let {
result.replaceArgumentList(buildArgumentListForErrorCall(result.argumentList, it))
result.replaceArgumentList(buildArgumentListForErrorCall(originalArgumentList, it))
}
} else {
subCandidate.handleVarargs()
subCandidate.argumentMapping?.let {
val newArgumentList = buildResolvedArgumentList(it, source = functionCall.argumentList.source)
val newArgumentList = buildResolvedArgumentList(originalArgumentList, it)
val symbol = subCandidate.symbol
val functionIsInline =
(symbol as? FirNamedFunctionSymbol)?.fir?.isInline == true || symbol.isArrayConstructorWithLambda
@@ -375,7 +378,7 @@ class FirCallCompletionResultsWriterTransformer(
}
val expectedArgumentsTypeMapping = runIf(!calleeReference.isError) { subCandidate.createArgumentsMapping() }
result.transformWithExpectedTypes(expectedArgumentsTypeMapping)
result.transformArgumentList(expectedArgumentsTypeMapping)
result.replaceConeTypeOrNull(resultType)
session.lookupTracker?.recordTypeResolveAsLookup(resultType, functionCall.source, context.file.source)
@@ -398,8 +401,10 @@ class FirCallCompletionResultsWriterTransformer(
}
}
private fun FirCall.transformWithExpectedTypes(expectedArgumentsTypeMapping: ExpectedArgumentType.ArgumentsMap?) {
class SamConversionInsertionTransformer : FirTransformer<Nothing?>() {
private fun FirCall.transformArgumentList(expectedArgumentsTypeMapping: ExpectedArgumentType.ArgumentsMap?) {
val mapping = (argumentList as? FirResolvedArgumentList)?.mapping
class ArgumentTransformer : FirTransformer<Nothing?>() {
override fun <E : FirElement> transformElement(element: E, data: Nothing?): E {
// We want to handle only the most top-level "real" expressions
// We only recursively transform named, spread, lambda argument and vararg expressions.
@@ -422,9 +427,27 @@ class FirCallCompletionResultsWriterTransformer(
return transformed
}
override fun transformNamedArgumentExpression(
namedArgumentExpression: FirNamedArgumentExpression,
data: Nothing?
): FirStatement {
val expression = transformElement(namedArgumentExpression.expression, data)
val parameter = mapping?.get(namedArgumentExpression)
return if (namedArgumentExpression.isSpread || parameter?.isVararg == true) {
buildSpreadArgumentExpression {
this.source = namedArgumentExpression.source
this.expression = expression
this.isNamed = true
this.isFakeSpread = !namedArgumentExpression.isSpread
}
} else {
expression
}
}
}
argumentList.transformArguments(SamConversionInsertionTransformer(), null)
argumentList.transformArguments(ArgumentTransformer(), null)
}
private fun FirExpression.wrapInSamExpression(expectedArgumentType: ConeKotlinType): FirExpression {
@@ -468,9 +491,11 @@ class FirCallCompletionResultsWriterTransformer(
} else {
subCandidate.handleVarargs()
subCandidate.argumentMapping?.let {
annotationCall.replaceArgumentList(buildResolvedArgumentList(it, annotationCall.argumentList.source))
annotationCall.replaceArgumentList(buildResolvedArgumentList(annotationCall.argumentList, it))
}
}
annotationCall.transformArgumentList(expectedArgumentsTypeMapping = null)
return annotationCall
}
@@ -673,21 +698,22 @@ class FirCallCompletionResultsWriterTransformer(
delegatedConstructorCall.calleeReference as? FirNamedReferenceWithCandidate ?: return delegatedConstructorCall
val subCandidate = calleeReference.candidate
val originalArgumentList = delegatedConstructorCall.argumentList
if (calleeReference.isError) {
subCandidate.argumentMapping?.let {
delegatedConstructorCall.replaceArgumentList(buildArgumentListForErrorCall(delegatedConstructorCall.argumentList, it))
delegatedConstructorCall.replaceArgumentList(buildArgumentListForErrorCall(originalArgumentList, it))
}
} else {
subCandidate.handleVarargs()
subCandidate.argumentMapping?.let {
delegatedConstructorCall.replaceArgumentList(buildResolvedArgumentList(it, delegatedConstructorCall.argumentList.source))
delegatedConstructorCall.replaceArgumentList(buildResolvedArgumentList(originalArgumentList, it))
}
}
runPCLARelatedTasksForCandidate(subCandidate)
val argumentsMapping = runIf(!calleeReference.isError) { subCandidate.createArgumentsMapping() }
delegatedConstructorCall.transformWithExpectedTypes(argumentsMapping)
delegatedConstructorCall.transformArgumentList(argumentsMapping)
return delegatedConstructorCall.apply {
replaceCalleeReference(calleeReference.toResolvedReference())
@@ -17,6 +17,7 @@ import org.jetbrains.kotlin.fir.expressions.FirBlock
import org.jetbrains.kotlin.fir.expressions.FirExpression
import org.jetbrains.kotlin.fir.expressions.FirNamedArgumentExpression
import org.jetbrains.kotlin.fir.expressions.UnresolvedExpressionTypeAccess
import org.jetbrains.kotlin.fir.expressions.builder.buildSpreadArgumentExpression
import org.jetbrains.kotlin.fir.expressions.builder.buildVarargArgumentsExpression
import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider
import org.jetbrains.kotlin.fir.types.*
@@ -57,7 +58,16 @@ internal fun remapArgumentsWithVararg(
// NB: don't pull out of named arguments.
(valueParameter.isVararg && arg !is FirNamedArgumentExpression)
) {
arguments += arg
arguments += if (arg is FirNamedArgumentExpression) {
buildSpreadArgumentExpression {
this.source = arg.source
this.expression = arg.expression
this.isNamed = true
this.isFakeSpread = !arg.isSpread
}
} else {
arg
}
startOffset = minOf(startOffset, arg.source?.startOffset ?: Int.MAX_VALUE)
endOffset = maxOf(endOffset, arg.source?.endOffset ?: 0)
if (firstVarargElementSource == null) firstVarargElementSource = arg.source