All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jetbrains.kotlin.fir.resolve.calls.FirArgumentsToParametersMapper.kt Maven / Gradle / Ivy

There is a newer version: 2.1.0-RC
Show newest version
/*
 * 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.FirSession
import org.jetbrains.kotlin.fir.declarations.FirDeclarationOrigin
import org.jetbrains.kotlin.fir.declarations.FirFunction
import org.jetbrains.kotlin.fir.declarations.FirSimpleFunction
import org.jetbrains.kotlin.fir.declarations.FirValueParameter
import org.jetbrains.kotlin.fir.declarations.isJavaOrEnhancement
import org.jetbrains.kotlin.fir.declarations.utils.isOperator
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.isSubstitutionOrIntersectionOverride
import org.jetbrains.kotlin.fir.resolve.BodyResolveComponents
import org.jetbrains.kotlin.fir.resolve.areNamedArgumentsForbiddenIgnoringOverridden
import org.jetbrains.kotlin.fir.resolve.defaultParameterResolver
import org.jetbrains.kotlin.fir.resolve.forbiddenNamedArgumentsTargetOrNull
import org.jetbrains.kotlin.fir.scopes.FirScope
import org.jetbrains.kotlin.fir.scopes.FirTypeScope
import org.jetbrains.kotlin.fir.scopes.ProcessorAction
import org.jetbrains.kotlin.fir.scopes.processOverriddenFunctions
import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirValueParameterSymbol
import org.jetbrains.kotlin.fir.utils.exceptions.withFirEntry
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.ForbiddenNamedArgumentsTarget
import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlin.utils.exceptions.errorWithAttachment
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: LinkedHashMap,
    val diagnostics: List,
) {
    fun toArgumentToParameterMapping(): LinkedHashMap {
        val argumentToParameterMapping = linkedMapOf()
        parameterToCallArgumentMap.forEach { (valueParameter, resolvedArgument) ->
            when (resolvedArgument) {
                is ResolvedCallArgument.SimpleArgument -> argumentToParameterMapping[resolvedArgument.callArgument] = valueParameter
                is ResolvedCallArgument.VarargArgument -> resolvedArgument.arguments.forEach {
                    argumentToParameterMapping[it] = valueParameter
                }
                ResolvedCallArgument.DefaultArgument -> {
                }
            }
        }
        return argumentToParameterMapping
    }

    fun numDefaults(): Int {
        return parameterToCallArgumentMap.values.count { it == ResolvedCallArgument.DefaultArgument }
    }
}

private val EmptyArgumentMapping = ArgumentMapping(linkedMapOf(), emptyList())

fun BodyResolveComponents.mapArguments(
    arguments: List,
    function: FirFunction,
    originScope: FirScope?,
    callSiteIsOperatorCall: Boolean,
): ArgumentMapping {
    if (arguments.isEmpty() && function.valueParameters.isEmpty()) {
        return EmptyArgumentMapping
    }

    val nonLambdaArguments: MutableList = mutableListOf()
    val excessLambdaArguments: MutableList = mutableListOf()
    var externalArgument: FirExpression? = null
    for (argument in arguments) {
        if (argument is FirAnonymousFunctionExpression && argument.isTrailingLambda) {
            if (externalArgument == null) {
                externalArgument = argument
            } else {
                excessLambdaArguments.add(argument)
            }
        } else {
            nonLambdaArguments.add(argument)
        }
    }

    // If this is an indexed access set operator, it could have default values or a vararg parameter in the middle.
    // For proper argument mapping, wrap the last one, which is supposed to be the updated value, as a named argument.
    val isIndexedSetOperator = callSiteIsOperatorCall
            && function is FirSimpleFunction
            && function.isOperator
            && function.name == OperatorNameConventions.SET
            && function.origin !is FirDeclarationOrigin.DynamicScope

    val processor = FirCallArgumentsProcessor(session, function, this, originScope, isIndexedSetOperator)
    processor.processNonLambdaArguments(nonLambdaArguments)
    if (externalArgument != null) {
        processor.processExternalArgument(externalArgument)
    }
    processor.processExcessLambdaArguments(excessLambdaArguments)
    processor.processDefaultsAndRunChecks()

    return ArgumentMapping(processor.result, processor.diagnostics ?: emptyList())
}

private class FirCallArgumentsProcessor(
    private val useSiteSession: FirSession,
    private val function: FirFunction,
    private val bodyResolveComponents: BodyResolveComponents,
    private val originScope: FirScope?,
    private val isIndexedSetOperator: Boolean,
) {
    private var state = State.POSITION_ARGUMENTS
    private var currentPositionedParameterIndex = 0
    private var varargArguments: MutableList? = null
    private var nameToParameter: Map? = null
    private var namedDynamicArgumentsNamesImpl: MutableSet? = null
    private val namedDynamicArgumentsNames: MutableSet
        get() = namedDynamicArgumentsNamesImpl ?: mutableSetOf().also { namedDynamicArgumentsNamesImpl = it }
    var diagnostics: MutableList? = null
        private set
    val result: LinkedHashMap = LinkedHashMap(function.valueParameters.size)

    val forbiddenNamedArgumentsTarget: ForbiddenNamedArgumentsTarget? by lazy {
        function.forbiddenNamedArgumentsTargetOrNull(originScope as? FirTypeScope)
    }

    private enum class State {
        POSITION_ARGUMENTS,
        VARARG_POSITION,
        NAMED_ONLY_ARGUMENTS
    }

    fun processNonLambdaArguments(arguments: List) {
        for ((argumentIndex, argument) in arguments.withIndex()) {
            processNonLambdaArgument(argument, isLastArgument = argumentIndex == arguments.lastIndex)
        }
        if (state == State.VARARG_POSITION) {
            completeVarargPositionArguments()
        }
    }

    private fun processNonLambdaArgument(argument: FirExpression, isLastArgument: Boolean) {
        when {
            // process position argument
            argument !is FirNamedArgumentExpression -> {
                if (state == State.VARARG_POSITION && isIndexedSetOperator && isLastArgument) {
                    // The last argument of an indexed set operator should be reserved for the last argument (the assigned value).
                    // That's why if vararg presented, they should be completed
                    completeVarargPositionArguments()
                }
                processPositionArgument(argument, isLastArgument)
            }
            // process named argument
            function.origin == FirDeclarationOrigin.DynamicScope -> {
                processPositionArgument(argument.expression, isLastArgument)
                if (!namedDynamicArgumentsNames.add(argument.name)) {
                    addDiagnostic(ArgumentPassedTwice(argument))
                }
            }
            else -> {
                if (state == State.VARARG_POSITION) {
                    completeVarargPositionArguments()
                }
                processNamedArgument(argument)
            }
        }
    }

    private fun processPositionArgument(argument: FirExpression, isLastArgument: Boolean) {
        if (state == State.NAMED_ONLY_ARGUMENTS) {
            addDiagnostic(MixingNamedAndPositionArguments(argument))
            return
        }

        // The last parameter of an indexed set operator should be reserved for the last argument (the assigned value).
        // We don't want the assigned value mapped to an index parameter if some of the index arguments are absent.
        val assignedParameterIndex = if (isIndexedSetOperator) {
            val lastParameterIndex = parameters.lastIndex
            when {
                isLastArgument -> lastParameterIndex
                currentPositionedParameterIndex >= lastParameterIndex -> {
                    // This is an extra index argument that should NOT be mapped to the parameter for the assigned value.
                    -1
                }
                else -> {
                    // This is an index argument that can be properly mapped.
                    currentPositionedParameterIndex
                }
            }
        } else {
            currentPositionedParameterIndex
        }
        val parameter = parameters.getOrNull(assignedParameterIndex)
        if (parameter == null) {
            addDiagnostic(TooManyArguments(argument, function))
            return
        }

        return if (!parameter.isVararg) {
            currentPositionedParameterIndex++

            result[parameter] = ResolvedCallArgument.SimpleArgument(argument)
            state = State.POSITION_ARGUMENTS
        }
        // all position arguments will be mapped to current vararg parameter
        else {
            addVarargArgument(argument)
            state = State.VARARG_POSITION
        }
    }

    private fun processNamedArgument(argument: FirNamedArgumentExpression) {
        forbiddenNamedArgumentsTarget?.let {
            addDiagnostic(NamedArgumentNotAllowed(argument, function, it))
        }

        val stateAllowsMixedNamedAndPositionArguments = state != State.NAMED_ONLY_ARGUMENTS
        state = State.NAMED_ONLY_ARGUMENTS
        val parameter = findParameterByName(argument) ?: return

        result[parameter]?.let {
            addDiagnostic(ArgumentPassedTwice(argument))
            return
        }

        result[parameter] = ResolvedCallArgument.SimpleArgument(argument)

        if (stateAllowsMixedNamedAndPositionArguments && 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 (function.origin != FirDeclarationOrigin.DynamicScope) {
            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)
        } else {
            val existing = result[lastParameter]
            if (existing == null) {
                result[lastParameter] = ResolvedCallArgument.SimpleArgument(externalArgument)
            } else {
                result[lastParameter] = ResolvedCallArgument.VarargArgument(existing.arguments + externalArgument)
            }
        }
    }

    fun processExcessLambdaArguments(excessLambdaArguments: List) {
        excessLambdaArguments.forEach { arg -> addDiagnostic(ManyLambdaExpressionArguments(arg)) }
    }

    fun processDefaultsAndRunChecks() {
        for ((parameter, resolvedArgument) in result) {
            if (!parameter.isVararg) {
                if (resolvedArgument !is ResolvedCallArgument.SimpleArgument) {
                    errorWithAttachment("Incorrect resolved argument for parameter ${parameter::class.java}: ${resolvedArgument::class.java}") {
                        withFirEntry("parameter", parameter)
                        withEntryGroup("arguments") {
                            for ((index, argument) in resolvedArgument.arguments.withIndex()) {
                                withFirEntry("argument$index", argument)
                            }
                        }
                    }
                } else if (resolvedArgument.callArgument.isSpread) {
                    addDiagnostic(NonVarargSpread(resolvedArgument.callArgument))
                }
            }
        }

        for ((index, parameter) in parameters.withIndex()) {
            if (!result.containsKey(parameter)) {
                when {
                    bodyResolveComponents.session.defaultParameterResolver.declaresDefaultValue(
                        useSiteSession, bodyResolveComponents.scopeSession, function, originScope, index
                    ) ->
                        result[parameter] = ResolvedCallArgument.DefaultArgument
                    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[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) {
            // Situation where the immediate function doesn't allow named arguments, but overrides a function that allows them,
            // e.g., Java function override of Kotlin function.
            if (function.areNamedArgumentsForbiddenIgnoringOverridden() && forbiddenNamedArgumentsTarget == null) {
                val symbol = function.symbol as? FirNamedFunctionSymbol
                if (symbol != null) {
                    (originScope as? FirTypeScope)?.processOverriddenFunctions(symbol) { overrideSymbol ->
                        if (overrideSymbol.fir.areNamedArgumentsForbiddenIgnoringOverridden()) {
                            return@processOverriddenFunctions ProcessorAction.NEXT
                        }
                        // Get the parameter names from the first applicable override and associate original parameters with them.
                        // If there are multiple overrides with ambiguous parameter names,
                        // a diagnostic will be reported in findParameterByName.
                        nameToParameter = parameters.withIndex().associateTo(LinkedHashMap()) { (i, p) ->
                            overrideSymbol.fir.valueParameters[i].name to p
                        }
                        ProcessorAction.STOP
                    }
                }
                if (nameToParameter == null) {
                    nameToParameter = emptyMap()
                }
            } else {
                nameToParameter = parameters.associateTo(LinkedHashMap()) { it.name to it }
            }
        }
        return nameToParameter!![name]
    }

    private fun findParameterByName(argument: FirNamedArgumentExpression): FirValueParameter? {
        var parameter = getParameterByName(argument.name)

        val symbol = function.symbol as? FirNamedFunctionSymbol
        var matchedIndex = -1

        // Note: should be called when parameter != null && matchedIndex != -1
        fun List.findAndReportValueParameterWithDifferentName(): ProcessorAction {
            val someParameter = getOrNull(matchedIndex)?.fir
            val someName = someParameter?.name
            if (someName != null && someName != argument.name) {
                addDiagnostic(
                    NameForAmbiguousParameter(argument)
                )
                return ProcessorAction.STOP
            }
            return ProcessorAction.NEXT
        }

        if (parameter == null) {
            if (symbol != null && (function.isSubstitutionOrIntersectionOverride || function.isJavaOrEnhancement)) {
                var allowedParameters: List? = null
                (originScope as? FirTypeScope)?.processOverriddenFunctions(symbol) {
                    if (it.fir.areNamedArgumentsForbiddenIgnoringOverridden()) {
                        return@processOverriddenFunctions ProcessorAction.NEXT
                    }
                    val someParameterSymbols = it.valueParameterSymbols
                    if (matchedIndex != -1) {
                        someParameterSymbols.findAndReportValueParameterWithDifferentName()
                    } else {
                        matchedIndex = someParameterSymbols.indexOfFirst { originalParameter ->
                            originalParameter.name == argument.name
                        }
                        if (matchedIndex != -1) {
                            parameter = parameters[matchedIndex]
                            val someParameter = allowedParameters?.getOrNull(matchedIndex)?.fir
                            if (someParameter != null) {
                                addDiagnostic(
                                    NameForAmbiguousParameter(argument)
                                )
                                ProcessorAction.STOP
                            } else {
                                ProcessorAction.NEXT
                            }
                        } else {
                            allowedParameters = someParameterSymbols
                            ProcessorAction.NEXT
                        }
                    }
                }
            }
            if (parameter == null) {
                addDiagnostic(NameNotFound(argument, function))
            }
        } else {
            if (symbol != null && (function.isSubstitutionOrIntersectionOverride || function.isJavaOrEnhancement)) {
                // We know the map is initialized, and it preserves insertion order.
                matchedIndex = nameToParameter!!.entries.indexOfFirst { it.key == argument.name }
                if (matchedIndex != -1) {
                    (originScope as? FirTypeScope)?.processOverriddenFunctions(symbol) {
                        if (it.fir.areNamedArgumentsForbiddenIgnoringOverridden()) {
                            return@processOverriddenFunctions ProcessorAction.NEXT
                        }
                        it.valueParameterSymbols.findAndReportValueParameterWithDifferentName()
                    }
                }
            }
        }

        return parameter
    }

    private fun addDiagnostic(diagnostic: ResolutionDiagnostic) {
        if (diagnostics == null) {
            diagnostics = mutableListOf()
        }
        diagnostics!!.add(diagnostic)
    }

    private val FirExpression.isSpread: Boolean
        get() = this is FirWrappedArgumentExpression && isSpread

    private val parameters: List
        get() = function.valueParameters
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy