org.jetbrains.kotlin.resolve.calls.components.ArgumentsToParametersMapper.kt Maven / Gradle / Ivy
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.resolve.calls.components
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.calls.model.*
import org.jetbrains.kotlin.utils.addToStdlib.compactIfPossible
class ArgumentsToParametersMapper(
languageVersionSettings: LanguageVersionSettings
) {
private val allowMixedNamedAndPositionArguments =
languageVersionSettings.supportsFeature(LanguageFeature.MixedNamedArgumentsInTheirOwnPosition)
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,
val diagnostics: List
)
val EmptyArgumentMapping = ArgumentMapping(emptyMap(), emptyList())
fun mapArguments(call: KotlinCall, descriptor: CallableDescriptor): ArgumentMapping =
mapArguments(call.argumentsInParenthesis, call.externalArgument, descriptor)
fun mapArguments(
argumentsInParenthesis: List,
externalArgument: KotlinCallArgument?,
descriptor: CallableDescriptor
): ArgumentMapping {
// optimization for case of variable
if (argumentsInParenthesis.isEmpty() && externalArgument == null && descriptor.valueParameters.isEmpty()) {
return EmptyArgumentMapping
} else {
val processor = CallArgumentProcessor(descriptor, allowMixedNamedAndPositionArguments)
processor.processArgumentsInParenthesis(argumentsInParenthesis)
if (externalArgument != null) {
processor.processExternalArgument(externalArgument)
}
processor.processDefaultsAndRunChecks()
return ArgumentMapping(processor.result.compactIfPossible(), processor.getDiagnostics())
}
}
private class CallArgumentProcessor(
val descriptor: CallableDescriptor,
val languageSettingsAllowMixedNamedAndPositionArguments: Boolean
) {
val result: MutableMap = LinkedHashMap()
private var state = State.POSITION_ARGUMENTS
private val parameters: List get() = descriptor.valueParameters
private var diagnostics: MutableList? = null
private var nameToParameter: Map? = null
private var varargArguments: MutableList? = null
private var currentPositionedParameterIndex = 0
private fun addDiagnostic(diagnostic: KotlinCallDiagnostic) {
if (diagnostics == null) {
diagnostics = ArrayList()
}
diagnostics!!.add(diagnostic)
}
fun getDiagnostics() = diagnostics ?: emptyList()
private fun getParameterByName(name: Name): ValueParameterDescriptor? {
if (nameToParameter == null) {
nameToParameter = parameters.associateBy { it.name }
}
return nameToParameter!![name]
}
private fun addVarargArgument(argument: KotlinCallArgument) {
if (varargArguments == null) {
varargArguments = ArrayList()
}
varargArguments!!.add(argument)
}
private enum class State {
POSITION_ARGUMENTS,
VARARG_POSITION,
NAMED_ONLY_ARGUMENTS
}
private fun completeVarargPositionArguments() {
assert(state == State.VARARG_POSITION) { "Incorrect state: $state" }
val parameter = parameters[currentPositionedParameterIndex]
result.put(parameter.original, ResolvedCallArgument.VarargArgument(varargArguments!!))
}
// return true, if it was mapped to vararg parameter
private fun processPositionArgument(argument: KotlinCallArgument): Boolean {
if (state == State.NAMED_ONLY_ARGUMENTS) {
addDiagnostic(MixingNamedAndPositionArguments(argument))
return false
}
val parameter = parameters.getOrNull(currentPositionedParameterIndex)
if (parameter == null) {
addDiagnostic(TooManyArguments(argument, descriptor))
return false
}
if (!parameter.isVararg) {
currentPositionedParameterIndex++
result.put(parameter.original, ResolvedCallArgument.SimpleArgument(argument))
return false
}
// all position arguments will be mapped to current vararg parameter
else {
addVarargArgument(argument)
return true
}
}
private fun processNamedArgument(argument: KotlinCallArgument, name: Name) {
if (!descriptor.hasStableParameterNames()) {
addDiagnostic(NamedArgumentNotAllowed(argument, descriptor))
}
val stateAllowsMixedNamedAndPositionArguments = state != State.NAMED_ONLY_ARGUMENTS
state = State.NAMED_ONLY_ARGUMENTS
val parameter = findParameterByName(argument, name) ?: return
addDiagnostic(NamedArgumentReference(argument, parameter))
result[parameter.original]?.let {
addDiagnostic(ArgumentPassedTwice(argument, parameter, it))
return
}
result[parameter.original] = ResolvedCallArgument.SimpleArgument(argument)
if (stateAllowsMixedNamedAndPositionArguments && languageSettingsAllowMixedNamedAndPositionArguments &&
parameters.getOrNull(currentPositionedParameterIndex)?.original == parameter.original) {
state = State.POSITION_ARGUMENTS
currentPositionedParameterIndex++
}
}
private fun ValueParameterDescriptor.getOverriddenParameterWithOtherName() = overriddenDescriptors.firstOrNull {
it.containingDeclaration.hasStableParameterNames() && it.name != name
}
private fun findParameterByName(argument: KotlinCallArgument, name: Name): ValueParameterDescriptor? {
val parameter = getParameterByName(name)
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, descriptor))
return parameter
}
fun processArgumentsInParenthesis(arguments: List) {
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()
}
processNamedArgument(argument, argumentName)
}
}
if (state == State.VARARG_POSITION) {
completeVarargPositionArguments()
}
}
fun processExternalArgument(externalArgument: KotlinCallArgument) {
val lastParameter = parameters.lastOrNull()
if (lastParameter == null) {
addDiagnostic(TooManyArguments(externalArgument, descriptor))
return
}
if (lastParameter.isVararg) {
addDiagnostic(VarargArgumentOutsideParentheses(externalArgument, lastParameter))
return
}
val previousOccurrence = result[lastParameter.original]
if (previousOccurrence != null) {
addDiagnostic(TooManyArguments(externalArgument, descriptor))
return
}
result[lastParameter.original] = 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.original)) {
if (parameter.hasDefaultValue()) {
result[parameter.original] = ResolvedCallArgument.DefaultArgument
} else if (parameter.isVararg) {
result[parameter.original] = ResolvedCallArgument.VarargArgument(emptyList())
} else {
addDiagnostic(NoValueForParameter(parameter, descriptor))
}
}
}
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy