[FIR] Improve mapping arguments to parameters

This commit is contained in:
Dmitriy Novozhilov
2020-02-14 11:10:46 +03:00
parent 2e597a3a32
commit e696d1d06a
43 changed files with 498 additions and 241 deletions
@@ -324,16 +324,19 @@ private fun Candidate.getExpectedTypeWithSAMConversion(
internal fun FirExpression.getExpectedType(
session: FirSession,
parameter: FirValueParameter/*, languageVersionSettings: LanguageVersionSettings*/
) =
// if (this.isSpread || this.isArrayAssignedAsNamedArgumentInAnnotation(parameter, languageVersionSettings)) {
// parameter.type.unwrap()
// } else {
if (parameter.isVararg && (this !is FirWrappedArgumentExpression || !isSpread)) {
): ConeKotlinType {
val shouldUnwrapVarargType = when (this) {
is FirSpreadArgumentExpression -> !isSpread
is FirNamedArgumentExpression -> false
else -> true
}
return if (parameter.isVararg && shouldUnwrapVarargType) {
parameter.returnTypeRef.coneTypeUnsafe<ConeKotlinType>().varargElementType(session)
} else {
parameter.returnTypeRef.coneTypeUnsafe()
}//?.varargElementType?.unwrap() ?: parameter.type.unwrap()
// }
}
}
private fun ConeKotlinType.varargElementType(session: FirSession): ConeKotlinType {
@@ -354,4 +357,4 @@ fun FirTypeRef.isExtensionFunctionType(session: FirSession): Boolean {
if (typeAlias.expandedTypeRef.annotations.any(FirAnnotationCall::isExtensionFunctionAnnotationCall)) return true
return false
}
}
@@ -109,6 +109,8 @@ class Candidate(
var argumentMapping: Map<FirExpression, FirValueParameter>? = null
val postponedAtoms = mutableListOf<PostponedResolvedAtomMarker>()
val diagnostics: MutableList<ResolutionDiagnostic> = mutableListOf()
fun dispatchReceiverExpression(): FirExpression = when (explicitReceiverKind) {
ExplicitReceiverKind.DISPATCH_RECEIVER, ExplicitReceiverKind.BOTH_RECEIVERS -> callInfo.explicitReceiver!!
else -> dispatchReceiverValue?.receiverExpression ?: FirNoReceiverExpression
@@ -0,0 +1,257 @@
/*
* Copyright 2010-2020 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.resolve.calls
import org.jetbrains.kotlin.fir.declarations.FirFunction
import org.jetbrains.kotlin.fir.declarations.FirValueParameter
import org.jetbrains.kotlin.fir.expressions.FirExpression
import org.jetbrains.kotlin.fir.expressions.FirLambdaArgumentExpression
import org.jetbrains.kotlin.fir.expressions.FirNamedArgumentExpression
import org.jetbrains.kotlin.fir.expressions.FirSpreadArgumentExpression
import org.jetbrains.kotlin.name.Name
import java.util.*
import kotlin.collections.ArrayList
import kotlin.collections.component1
import kotlin.collections.component2
import kotlin.collections.set
data class ArgumentMapping(
// This map should be ordered by arguments as written, e.g.:
// fun foo(a: Int, b: Int) {}
// foo(b = bar(), a = qux())
// parameterToCallArgumentMap.values() should be [ 'bar()', 'foo()' ]
val parameterToCallArgumentMap: Map<FirValueParameter, ResolvedCallArgument>,
val diagnostics: List<ResolutionDiagnostic>
)
private val EmptyArgumentMapping = ArgumentMapping(emptyMap(), emptyList())
fun mapArguments(
arguments: List<FirExpression>,
function: FirFunction<*>
): ArgumentMapping {
if (arguments.isEmpty() && function.valueParameters.isEmpty()) {
return EmptyArgumentMapping
}
val externalArgument: FirExpression? = arguments.lastOrNull { it is FirLambdaArgumentExpression }
val argumentsInParenthesis: List<FirExpression> = if (externalArgument == null) {
arguments
} else {
arguments.subList(0, arguments.size - 1)
}
val processor = FirCallArgumentsProcessor(function)
processor.processArgumentsInParenthesis(argumentsInParenthesis)
if (externalArgument != null) {
processor.processExternalArgument(externalArgument)
}
processor.processDefaultsAndRunChecks()
return ArgumentMapping(processor.result, processor.diagnostics ?: emptyList())
}
private class FirCallArgumentsProcessor(private val function: FirFunction<*>) {
private var state = State.POSITION_ARGUMENTS
private var currentPositionedParameterIndex = 0
private var varargArguments: MutableList<FirExpression>? = null
private var nameToParameter: Map<Name, FirValueParameter>? = null
var diagnostics: MutableList<ResolutionDiagnostic>? = null
private set
val result: MutableMap<FirValueParameter, ResolvedCallArgument> = LinkedHashMap()
private enum class State {
POSITION_ARGUMENTS,
VARARG_POSITION,
NAMED_ONLY_ARGUMENTS
}
fun processArgumentsInParenthesis(arguments: List<FirExpression>) {
for (argument in arguments) {
val argumentName = argument.argumentName
// process position argument
if (argumentName == null) {
if (processPositionArgument(argument)) {
state = State.VARARG_POSITION
}
}
// process named argument
else {
if (state == State.VARARG_POSITION) {
completeVarargPositionArguments()
}
state = State.NAMED_ONLY_ARGUMENTS
processNamedArgument(argument, argumentName)
}
}
if (state == State.VARARG_POSITION) {
completeVarargPositionArguments()
}
}
// return true, if it was mapped to vararg parameter
private fun processPositionArgument(argument: FirExpression): Boolean {
if (state == State.NAMED_ONLY_ARGUMENTS) {
addDiagnostic(MixingNamedAndPositionArguments(argument))
return false
}
val parameter = parameters.getOrNull(currentPositionedParameterIndex)
if (parameter == null) {
addDiagnostic(TooManyArguments(argument, function))
return false
}
if (!parameter.isVararg) {
currentPositionedParameterIndex++
result[parameter] = ResolvedCallArgument.SimpleArgument(argument)
return false
}
// all position arguments will be mapped to current vararg parameter
else {
addVarargArgument(argument)
return true
}
}
private fun processNamedArgument(argument: FirExpression, name: Name) {
if (!function.hasStableParameterNames) {
addDiagnostic(NamedArgumentNotAllowed(argument, function))
}
val parameter = findParameterByName(argument, name) ?: return
result[parameter]?.let {
addDiagnostic(ArgumentPassedTwice(argument, parameter, it))
return
}
result[parameter] = ResolvedCallArgument.SimpleArgument(argument)
if (parameters.getOrNull(currentPositionedParameterIndex) == parameter) {
state = State.POSITION_ARGUMENTS
currentPositionedParameterIndex++
}
}
fun processExternalArgument(externalArgument: FirExpression) {
val lastParameter = parameters.lastOrNull()
if (lastParameter == null) {
addDiagnostic(TooManyArguments(externalArgument, function))
return
}
if (lastParameter.isVararg) {
addDiagnostic(VarargArgumentOutsideParentheses(externalArgument, lastParameter))
return
}
val previousOccurrence = result[lastParameter]
if (previousOccurrence != null) {
addDiagnostic(TooManyArguments(externalArgument, function))
return
}
result[lastParameter] = ResolvedCallArgument.SimpleArgument(externalArgument)
}
fun processDefaultsAndRunChecks() {
for ((parameter, resolvedArgument) in result) {
if (!parameter.isVararg) {
if (resolvedArgument !is ResolvedCallArgument.SimpleArgument) {
error("Incorrect resolved argument for parameter $parameter :$resolvedArgument")
} else if (resolvedArgument.callArgument.isSpread) {
addDiagnostic(NonVarargSpread(resolvedArgument.callArgument))
}
}
}
for (parameter in parameters) {
if (!result.containsKey(parameter)) {
if (parameter.defaultValue != null) {
result[parameter] = ResolvedCallArgument.DefaultArgument
} else if (parameter.isVararg) {
result[parameter] = ResolvedCallArgument.VarargArgument(emptyList())
} else {
addDiagnostic(NoValueForParameter(parameter, function))
}
}
}
}
private fun completeVarargPositionArguments() {
assert(state == State.VARARG_POSITION) { "Incorrect state: $state" }
val parameter = parameters[currentPositionedParameterIndex]
result.put(parameter, ResolvedCallArgument.VarargArgument(varargArguments!!))
}
private fun addVarargArgument(argument: FirExpression) {
if (varargArguments == null) {
varargArguments = ArrayList()
}
varargArguments!!.add(argument)
}
private fun getParameterByName(name: Name): FirValueParameter? {
if (nameToParameter == null) {
nameToParameter = parameters.associateBy { it.name }
}
return nameToParameter!![name]
}
private fun findParameterByName(argument: FirExpression, name: Name): FirValueParameter? {
val parameter = getParameterByName(name)
// TODO
// if (descriptor is CallableMemberDescriptor && descriptor.kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE) {
// if (parameter == null) {
// for (valueParameter in descriptor.valueParameters) {
// val matchedParameter = valueParameter.overriddenDescriptors.firstOrNull {
// it.containingDeclaration.hasStableParameterNames() && it.name == name
// }
// if (matchedParameter != null) {
// addDiagnostic(NamedArgumentReference(argument, valueParameter))
// addDiagnostic(NameForAmbiguousParameter(argument, valueParameter, matchedParameter))
// return valueParameter
// }
// }
// } else {
// parameter.getOverriddenParameterWithOtherName()?.let {
// addDiagnostic(NameForAmbiguousParameter(argument, parameter, it))
// }
// }
// }
//
if (parameter == null) addDiagnostic(NameNotFound(argument, function))
return parameter
}
private fun addDiagnostic(diagnostic: ResolutionDiagnostic) {
if (diagnostics == null) {
diagnostics = mutableListOf()
}
diagnostics!!.add(diagnostic)
}
private val FirExpression.isSpread: Boolean
get() = this is FirSpreadArgumentExpression && isSpread
private val parameters: List<FirValueParameter>
get() = function.valueParameters
private val FirExpression.argumentName: Name?
get() = (this as? FirNamedArgumentExpression)?.name
// TODO: handle java functions
private val FirFunction<*>.hasStableParameterNames: Boolean
get() = true
}
@@ -1,119 +0,0 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. 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.resolve.calls
import org.jetbrains.kotlin.fir.declarations.FirFunction
import org.jetbrains.kotlin.fir.declarations.FirValueParameter
import org.jetbrains.kotlin.fir.expressions.FirExpression
import org.jetbrains.kotlin.fir.expressions.FirLambdaArgumentExpression
import org.jetbrains.kotlin.fir.expressions.FirNamedArgumentExpression
import org.jetbrains.kotlin.fir.expressions.FirWrappedArgumentExpression
import org.jetbrains.kotlin.fir.render
class FirCallArgumentsProcessor(
private val function: FirFunction<*>,
private val arguments: List<FirExpression>
) {
class Result(val argumentMapping: Map<FirExpression, FirValueParameter>, val isSuccess: Boolean)
fun process(): Result {
var currentState: State = State.PositionalOnly(function.valueParameters)
for (argument in arguments) {
if (argument is FirNamedArgumentExpression || argument is FirLambdaArgumentExpression) {
currentState = State.PositionalThenNamed(
function.valueParameters,
currentState.argumentMap,
currentState.usedParameters
)
}
val status = currentState.processArgument(argument)
if (status != MappingStatus.SUCCESS) {
// unmapped argument
return Result(currentState.argumentMap, isSuccess = false)
}
}
for (valueParameter in function.valueParameters) {
if (valueParameter !in currentState.usedParameters && !valueParameter.isVararg && valueParameter.defaultValue == null) {
// unmapped parameter
return Result(currentState.argumentMap, isSuccess = false)
}
}
return Result(currentState.argumentMap, isSuccess = currentState.argumentMap.size == arguments.size)
}
private enum class MappingStatus {
SUCCESS,
ERROR
}
private sealed class State(
val valueParameters: List<FirValueParameter>,
val argumentMap: MutableMap<FirExpression, FirValueParameter> = mutableMapOf(),
val usedParameters: MutableSet<FirValueParameter> = mutableSetOf()
) {
abstract fun processArgument(argument: FirExpression): MappingStatus
class PositionalOnly(valueParameters: List<FirValueParameter>) : State(valueParameters) {
var currentParameterIndex: Int = 0
val currentParameter get() = valueParameters.getOrNull(currentParameterIndex)
override fun processArgument(argument: FirExpression): MappingStatus {
require(argument !is FirNamedArgumentExpression) {
"Positional-only argument processor state should not receive ${argument.render()}"
}
val currentParameter = currentParameter ?: return MappingStatus.ERROR
argumentMap[argument] = currentParameter
val isSpread = argument is FirWrappedArgumentExpression && argument.isSpread
if (!currentParameter.isVararg || isSpread) {
usedParameters += currentParameter
currentParameterIndex++
}
if (!currentParameter.isVararg && isSpread) {
return MappingStatus.ERROR
}
return MappingStatus.SUCCESS
}
}
class PositionalThenNamed(
valueParameters: List<FirValueParameter>,
argumentMap: MutableMap<FirExpression, FirValueParameter>,
usedParameters: MutableSet<FirValueParameter>
) : State(valueParameters, argumentMap, usedParameters) {
val nameToParameter = valueParameters.associateBy { it.name }
private fun map(parameter: FirValueParameter, argument: FirExpression): MappingStatus {
if (parameter in usedParameters) return MappingStatus.ERROR
argumentMap[argument] = parameter
usedParameters += parameter
return MappingStatus.SUCCESS
}
override fun processArgument(argument: FirExpression): MappingStatus {
when (argument) {
is FirNamedArgumentExpression -> {
val name = argument.name
val parameter = nameToParameter[name] ?: return MappingStatus.ERROR
return map(parameter, argument)
}
is FirLambdaArgumentExpression -> {
val lastParameter = valueParameters.lastOrNull() ?: return MappingStatus.ERROR
return map(lastParameter, argument)
}
else -> {
return MappingStatus.ERROR
}
}
}
}
}
}
@@ -0,0 +1,51 @@
/*
* Copyright 2010-2020 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.resolve.calls
import org.jetbrains.kotlin.fir.declarations.FirFunction
import org.jetbrains.kotlin.fir.declarations.FirValueParameter
import org.jetbrains.kotlin.fir.expressions.FirExpression
import org.jetbrains.kotlin.resolve.calls.tower.ResolutionCandidateApplicability
abstract class ResolutionDiagnostic(val applicability: CandidateApplicability)
abstract class InapplicableArgumentDiagnostic : ResolutionDiagnostic(CandidateApplicability.INAPPLICABLE) {
abstract val argument: FirExpression
}
class MixingNamedAndPositionArguments(override val argument: FirExpression) : InapplicableArgumentDiagnostic()
class TooManyArguments(
val argument: FirExpression,
val function: FirFunction<*>
) : ResolutionDiagnostic(CandidateApplicability.PARAMETER_MAPPING_ERROR)
class NamedArgumentNotAllowed(
override val argument: FirExpression,
val function: FirFunction<*>
) : InapplicableArgumentDiagnostic()
class ArgumentPassedTwice(
override val argument: FirExpression,
val valueParameter: FirValueParameter,
val firstOccurrence: ResolvedCallArgument
) : InapplicableArgumentDiagnostic()
class VarargArgumentOutsideParentheses(
override val argument: FirExpression,
val valueParameter: FirValueParameter
) : InapplicableArgumentDiagnostic()
class NonVarargSpread(override val argument: FirExpression) : InapplicableArgumentDiagnostic()
class NoValueForParameter(
val valueParameter: FirValueParameter,
val function: FirFunction<*>
) : ResolutionDiagnostic(CandidateApplicability.PARAMETER_MAPPING_ERROR)
class NameNotFound(
override val argument: FirExpression,
val function: FirFunction<*>
) : InapplicableArgumentDiagnostic()
@@ -0,0 +1,26 @@
/*
* Copyright 2010-2020 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.resolve.calls
import org.jetbrains.kotlin.fir.expressions.FirExpression
sealed class ResolvedCallArgument {
abstract val arguments: List<FirExpression>
object DefaultArgument : ResolvedCallArgument() {
override val arguments: List<FirExpression>
get() = emptyList()
}
class SimpleArgument(val callArgument: FirExpression) : ResolvedCallArgument() {
override val arguments: List<FirExpression>
get() = listOf(callArgument)
}
class VarargArgument(override val arguments: List<FirExpression>) : ResolvedCallArgument()
}
@@ -28,6 +28,7 @@ import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystemOperation
import org.jetbrains.kotlin.resolve.calls.inference.model.SimpleConstraintSystemConstraintPosition
import org.jetbrains.kotlin.resolve.calls.tasks.ExplicitReceiverKind
import org.jetbrains.kotlin.resolve.calls.tasks.ExplicitReceiverKind.*
import org.jetbrains.kotlin.utils.addToStdlib.min
abstract class ResolutionStage {
@@ -154,11 +155,26 @@ internal object MapArguments : ResolutionStage() {
override suspend fun check(candidate: Candidate, sink: CheckerSink, callInfo: CallInfo) {
val symbol = candidate.symbol as? FirFunctionSymbol<*> ?: return sink.reportApplicability(CandidateApplicability.HIDDEN)
val function = symbol.fir
val processor = FirCallArgumentsProcessor(function, callInfo.arguments)
val mappingResult = processor.process()
candidate.argumentMapping = mappingResult.argumentMapping
if (!mappingResult.isSuccess) {
return sink.yieldApplicability(CandidateApplicability.PARAMETER_MAPPING_ERROR)
val mapping = mapArguments(callInfo.arguments, function)
val argumentToParameterMapping = mutableMapOf<FirExpression, FirValueParameter>()
mapping.parameterToCallArgumentMap.forEach { (valueParameter, resolvedArgument) ->
when (resolvedArgument) {
is ResolvedCallArgument.SimpleArgument -> argumentToParameterMapping[resolvedArgument.callArgument] = valueParameter
is ResolvedCallArgument.VarargArgument -> resolvedArgument.arguments.forEach {
argumentToParameterMapping[it] = valueParameter
}
}
}
candidate.argumentMapping = argumentToParameterMapping
var applicability = CandidateApplicability.RESOLVED
mapping.diagnostics.forEach {
candidate.diagnostics += it
applicability = min(applicability, it.applicability)
}
if (applicability < CandidateApplicability.RESOLVED) {
return sink.yieldApplicability(applicability)
}
}
}
@@ -4,24 +4,30 @@ fun baz(f: () -> Unit, other: Boolean = true) {}
fun test() {
// OK
foo {}
foo() {}
foo({})
// Bad
<!INAPPLICABLE_CANDIDATE!>foo<!>(1) {}
<!INAPPLICABLE_CANDIDATE!>foo<!>(f = {}) {}
// OK
bar(1) {}
bar(x = 1) {}
bar(1, {})
bar(x = 1, f = {})
// Bad
<!INAPPLICABLE_CANDIDATE!>bar<!> {}
<!INAPPLICABLE_CANDIDATE!>bar<!>({})
// OK
baz(other = false, f = {})
baz({}, false)
// Bad
<!INAPPLICABLE_CANDIDATE!>baz<!> {}
<!INAPPLICABLE_CANDIDATE!>baz<!>() {}
<!INAPPLICABLE_CANDIDATE!>baz<!>(other = false) {}
+1 -1
View File
@@ -9,7 +9,7 @@ fun test() {
<!INAPPLICABLE_CANDIDATE!>foo<!>()
<!INAPPLICABLE_CANDIDATE!>foo<!>(0.0, false, 0, "")
<!INAPPLICABLE_CANDIDATE!>foo<!>(1, 2.0, third = true, "")
foo(1, 2.0, third = true, "")
<!INAPPLICABLE_CANDIDATE!>foo<!>(second = 0.0, first = 0, fourth = "")
<!INAPPLICABLE_CANDIDATE!>foo<!>(first = 0.0, second = 0, third = "", fourth = false)
<!INAPPLICABLE_CANDIDATE!>foo<!>(first = 0, second = 0.0, third = false, fourth = "", first = 1)
+2 -2
View File
@@ -9,9 +9,9 @@ FILE: simple.kt
R|/foo|(third = Boolean(false), second = Double(2.71), fourth = String(?!), first = Int(0))
<Inapplicable(PARAMETER_MAPPING_ERROR): [/foo]>#()
<Inapplicable(INAPPLICABLE): [/foo]>#(Double(0.0), Boolean(false), Int(0), String())
<Inapplicable(PARAMETER_MAPPING_ERROR): [/foo]>#(Int(1), Double(2.0), third = Boolean(true), String())
R|/foo|(Int(1), Double(2.0), third = Boolean(true), String())
<Inapplicable(PARAMETER_MAPPING_ERROR): [/foo]>#(second = Double(0.0), first = Int(0), fourth = String())
<Inapplicable(INAPPLICABLE): [/foo]>#(first = Double(0.0), second = Int(0), third = String(), fourth = Boolean(false))
<Inapplicable(PARAMETER_MAPPING_ERROR): [/foo]>#(first = Int(0), second = Double(0.0), third = Boolean(false), fourth = String(), first = Int(1))
<Inapplicable(INAPPLICABLE): [/foo]>#(first = Int(0), second = Double(0.0), third = Boolean(false), fourth = String(), first = Int(1))
<Inapplicable(PARAMETER_MAPPING_ERROR): [/foo]>#(Int(0), Double(0.0), Boolean(false), foth = String())
}
+1 -1
View File
@@ -11,6 +11,6 @@ FILE: vararg.kt
<Inapplicable(INAPPLICABLE): [/foo]>#(String())
<Inapplicable(INAPPLICABLE): [/foo]>#(Int(1), Int(2))
R|/bar|(Int(1), z = Boolean(true), vararg(y = *R|kotlin/arrayOf|<R|kotlin/String|>(vararg(String(my), String(yours)))))
<Inapplicable(PARAMETER_MAPPING_ERROR): [/bar]>#(Int(0), z = Boolean(false), y = String(), y = String(other))
<Inapplicable(INAPPLICABLE): [/bar]>#(Int(0), z = Boolean(false), y = String(), y = String(other))
<Inapplicable(PARAMETER_MAPPING_ERROR): [/bar]>#(Int(0), String(), Boolean(true))
}