FIR resolve: introduce argument mapper

This commit is contained in:
Mikhail Glukhikh
2019-04-10 17:37:10 +03:00
parent c98b820968
commit e05dfb6541
13 changed files with 436 additions and 7 deletions
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.fir.resolve.calls
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.declarations.FirCallableDeclaration
import org.jetbrains.kotlin.fir.declarations.FirValueParameter
import org.jetbrains.kotlin.fir.expressions.FirExpression
import org.jetbrains.kotlin.fir.renderWithType
import org.jetbrains.kotlin.fir.resolve.FirSymbolProvider
@@ -41,7 +42,7 @@ class CallInfo(
val typeProvider: (FirExpression) -> FirTypeRef?
) {
val argumentCount get() = arguments.size
}
interface CheckerSink {
@@ -69,6 +70,8 @@ class Candidate(
system
}
lateinit var substitutor: ConeSubstitutor
var argumentMapping: Map<FirExpression, FirValueParameter>? = null
}
sealed class CallKind {
@@ -0,0 +1,120 @@
/*
* 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.builtins.KotlinBuiltIns
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
import org.jetbrains.kotlin.fir.types.ConeClassType
import org.jetbrains.kotlin.fir.types.FirResolvedTypeRef
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 FirWrappedArgumentExpression) {
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)
fun nextParameter() {
val currentParameter = currentParameter ?: return
if (currentParameter.isVararg) return
usedParameters += currentParameter
currentParameterIndex++
}
override fun processArgument(argument: FirExpression): MappingStatus {
require(argument !is FirNamedArgumentExpression) {
"Positional-only argument processor state should not receiver ${argument.render()}"
}
val currentParameter = currentParameter ?: return MappingStatus.ERROR
argumentMap[argument] = currentParameter
nextParameter()
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
}
}
}
}
}
}
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.fir.resolve.calls
import org.jetbrains.kotlin.fir.declarations.FirFunction
import org.jetbrains.kotlin.fir.resolve.transformers.firUnsafe
import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol
import java.lang.IllegalStateException
abstract class ResolutionStage {
@@ -19,7 +20,11 @@ abstract class CheckerStage : ResolutionStage()
internal object MapArguments : ResolutionStage() {
override fun check(candidate: Candidate, sink: CheckerSink, callInfo: CallInfo) {
val symbol = candidate.symbol as? FirFunctionSymbol ?: return sink.reportApplicability(CandidateApplicability.HIDDEN)
if (symbol.firUnsafe<FirFunction>().valueParameters.size != callInfo.arguments.size) {
val function = symbol.firUnsafe<FirFunction>()
val processor = FirCallArgumentsProcessor(function, callInfo.arguments)
val mappingResult = processor.process()
candidate.argumentMapping = mappingResult.argumentMapping
if (!mappingResult.isSuccess) {
return sink.reportApplicability(CandidateApplicability.PARAMETER_MAPPING_ERROR)
}
}
@@ -28,10 +33,9 @@ internal object MapArguments : ResolutionStage() {
internal object CheckArguments : CheckerStage() {
override fun check(candidate: Candidate, sink: CheckerSink, callInfo: CallInfo) {
val symbol = candidate.symbol as? FirFunctionSymbol ?: error("Can't check arguments for non function")
val declaration = symbol.fir as FirFunction
for ((parameter, argument) in declaration.valueParameters.zip(callInfo.arguments)) {
val argumentMapping =
candidate.argumentMapping ?: throw IllegalStateException("Argument should be already mapped while checking arguments!")
for ((argument, parameter) in argumentMapping) {
candidate.resolveArgument(argument, parameter, isReceiver = false, typeProvider = callInfo.typeProvider, sink = sink)
}
@@ -0,0 +1,26 @@
fun foo(first: Int, second: Double = 3.14, third: Boolean = false) {}
fun bar(first: Int, second: Double = 2.71, third: Boolean, fourth: String = "") {}
fun baz(x: Int, vararg y: String, z: Boolean = false) {}
fun test() {
foo(1)
foo(1, 2.0)
foo(1, 2.0, true)
foo(1, third = true)
foo()
foo(0, 0.0, false, "")
bar(1, third = true)
bar(1, 2.0, true)
bar(1, 2.0, true, "my")
bar(1, true)
baz(1)
baz(1, "my", "yours")
baz(1, z = true)
baz(0, "", false)
}
@@ -0,0 +1,23 @@
FILE: default.kt
public final fun foo(first: R|kotlin/Int|, second: R|kotlin/Double| = Double(3.14), third: R|kotlin/Boolean| = Boolean(false)): R|kotlin/Unit| {
}
public final fun bar(first: R|kotlin/Int|, second: R|kotlin/Double| = Double(2.71), third: R|kotlin/Boolean|, fourth: R|kotlin/String| = String()): R|kotlin/Unit| {
}
public final fun baz(x: R|kotlin/Int|, vararg y: R|kotlin/String|, z: R|kotlin/Boolean| = Boolean(false)): R|kotlin/Unit| {
}
public final fun test(): R|kotlin/Unit| {
R|/foo|(Int(1))
R|/foo|(Int(1), Double(2.0))
R|/foo|(Int(1), Double(2.0), Boolean(true))
R|/foo|(Int(1), third = Boolean(true))
<Inapplicable(PARAMETER_MAPPING_ERROR): [/foo]>#()
<Inapplicable(PARAMETER_MAPPING_ERROR): [/foo]>#(Int(0), Double(0.0), Boolean(false), String())
R|/bar|(Int(1), third = Boolean(true))
R|/bar|(Int(1), Double(2.0), Boolean(true))
R|/bar|(Int(1), Double(2.0), Boolean(true), String(my))
<Inapplicable(PARAMETER_MAPPING_ERROR): [/bar]>#(Int(1), Boolean(true))
R|/baz|(Int(1))
R|/baz|(Int(1), String(my), String(yours))
R|/baz|(Int(1), z = Boolean(true))
<Inapplicable(INAPPLICABLE): [/baz]>#(Int(0), String(), Boolean(false))
}
@@ -0,0 +1,28 @@
fun foo(f: () -> Unit) {}
fun bar(x: Int, f: () -> Unit) {}
fun baz(f: () -> Unit, other: Boolean = true) {}
fun test() {
foo {}
foo() {}
foo({})
foo(1) {}
foo(f = {}) {}
bar(1) {}
bar(x = 1) {}
bar(1, {})
bar(x = 1, f = {})
bar {}
bar({})
baz(other = false, f = {})
baz({}, false)
baz {}
baz() {}
baz(other = false) {}
}
@@ -0,0 +1,127 @@
FILE: lambda.kt
public final fun foo(f: R|kotlin/Function0|): R|kotlin/Unit| {
}
public final fun bar(x: R|kotlin/Int|, f: R|kotlin/Function0|): R|kotlin/Unit| {
}
public final fun baz(f: R|kotlin/Function0|, other: R|kotlin/Boolean| = Boolean(true)): R|kotlin/Unit| {
}
public final fun test(): R|kotlin/Unit| {
R|/foo|(<L> = foo@fun <implicit>.<anonymous>(): <implicit> {
^ {
Unit
}
}
)
R|/foo|(<L> = foo@fun <implicit>.<anonymous>(): <implicit> {
^ {
Unit
}
}
)
R|/foo|(foo@fun <implicit>.<anonymous>(): <implicit> {
^ {
Unit
}
}
)
<Inapplicable(PARAMETER_MAPPING_ERROR): [/foo]>#(Int(1), <L> = foo@fun <implicit>.<anonymous>(): <implicit> {
^ {
Unit
}
}
)
<Inapplicable(PARAMETER_MAPPING_ERROR): [/foo]>#(f = foo@fun <implicit>.<anonymous>(): <implicit> {
^ {
Unit
}
}
, <L> = foo@fun <implicit>.<anonymous>(): <implicit> {
^ {
Unit
}
}
)
R|/bar|(Int(1), <L> = bar@fun <implicit>.<anonymous>(): <implicit> {
^ {
Unit
}
}
)
R|/bar|(x = Int(1), <L> = bar@fun <implicit>.<anonymous>(): <implicit> {
^ {
Unit
}
}
)
R|/bar|(Int(1), bar@fun <implicit>.<anonymous>(): <implicit> {
^ {
Unit
}
}
)
R|/bar|(x = Int(1), f = bar@fun <implicit>.<anonymous>(): <implicit> {
^ {
Unit
}
}
)
<Inapplicable(PARAMETER_MAPPING_ERROR): [/bar]>#(<L> = bar@fun <implicit>.<anonymous>(): <implicit> {
^ {
Unit
}
}
)
<Inapplicable(PARAMETER_MAPPING_ERROR): [/bar]>#(bar@fun <implicit>.<anonymous>(): <implicit> {
^ {
Unit
}
}
)
R|/baz|(other = Boolean(false), f = baz@fun <implicit>.<anonymous>(): <implicit> {
^ {
Unit
}
}
)
R|/baz|(baz@fun <implicit>.<anonymous>(): <implicit> {
^ {
Unit
}
}
, Boolean(false))
<Inapplicable(PARAMETER_MAPPING_ERROR): [/baz]>#(<L> = baz@fun <implicit>.<anonymous>(): <implicit> {
^ {
Unit
}
}
)
<Inapplicable(PARAMETER_MAPPING_ERROR): [/baz]>#(<L> = baz@fun <implicit>.<anonymous>(): <implicit> {
^ {
Unit
}
}
)
<Inapplicable(PARAMETER_MAPPING_ERROR): [/baz]>#(other = Boolean(false), <L> = baz@fun <implicit>.<anonymous>(): <implicit> {
^ {
Unit
}
}
)
}
@@ -0,0 +1,17 @@
fun foo(first: Int, second: Double, third: Boolean, fourth: String) {}
fun test() {
foo(1, 2.0, true, "")
foo(1, 2.0, true, fourth = "!")
foo(1, 2.0, fourth = "???", third = false)
foo(1, second = 3.14, third = false, fourth = "!?")
foo(third = false, second = 2.71, fourth = "?!", first = 0)
foo()
foo(0.0, false, 0, "")
foo(1, 2.0, third = true, "")
foo(second = 0.0, first = 0, fourth = "")
foo(first = 0.0, second = 0, third = "", fourth = false)
foo(first = 0, second = 0.0, third = false, fourth = "", first = 1)
foo(0, 0.0, false, foth = "")
}
@@ -0,0 +1,17 @@
FILE: simple.kt
public final fun foo(first: R|kotlin/Int|, second: R|kotlin/Double|, third: R|kotlin/Boolean|, fourth: R|kotlin/String|): R|kotlin/Unit| {
}
public final fun test(): R|kotlin/Unit| {
R|/foo|(Int(1), Double(2.0), Boolean(true), String())
R|/foo|(Int(1), Double(2.0), Boolean(true), fourth = String(!))
R|/foo|(Int(1), Double(2.0), fourth = String(???), third = Boolean(false))
R|/foo|(Int(1), second = Double(3.14), third = Boolean(false), fourth = String(!?))
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())
<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(PARAMETER_MAPPING_ERROR): [/foo]>#(Int(0), Double(0.0), Boolean(false), foth = String())
}
@@ -0,0 +1,16 @@
fun foo(x: Int, vararg y: String) {}
fun bar(x: Int, vararg y: String, z: Boolean) {}
fun test() {
foo(1)
foo(1, "")
foo(1, "my", "yours")
foo("")
foo(1, 2)
bar(1, z = true, y = *arrayOf("my", "yours"))
bar(0, z = false, y = "", y = "other")
bar(0, "", true)
}
@@ -0,0 +1,15 @@
FILE: vararg.kt
public final fun foo(x: R|kotlin/Int|, vararg y: R|kotlin/String|): R|kotlin/Unit| {
}
public final fun bar(x: R|kotlin/Int|, vararg y: R|kotlin/String|, z: R|kotlin/Boolean|): R|kotlin/Unit| {
}
public final fun test(): R|kotlin/Unit| {
R|/foo|(Int(1))
R|/foo|(Int(1), String())
R|/foo|(Int(1), String(my), String(yours))
<Inapplicable(INAPPLICABLE): [/foo]>#(String())
<Inapplicable(INAPPLICABLE): [/foo]>#(Int(1), Int(2))
R|/bar|(Int(1), z = Boolean(true), y = <Inapplicable(INAPPLICABLE): [kotlin/arrayOf]>#(String(my), String(yours)))
<Inapplicable(PARAMETER_MAPPING_ERROR): [/bar]>#(Int(0), z = Boolean(false), y = String(), y = String(other))
<Inapplicable(PARAMETER_MAPPING_ERROR): [/bar]>#(Int(0), String(), Boolean(true))
}
+1 -1
View File
@@ -37,7 +37,7 @@ FILE: simple.kt
public final fun test(): R|kotlin/Unit| {
R|/B.foo|()
R|/B.bar|()
<Ambiguity: buz, [/B.buz, /A.buz]>#()
<Inapplicable(PARAMETER_MAPPING_ERROR): [/B.buz, /A.buz]>#()
}
}
@@ -124,6 +124,39 @@ public class FirResolveTestCaseGenerated extends AbstractFirResolveTestCase {
runTest("compiler/fir/resolve/testData/resolve/typeParameterVsNested.kt");
}
@TestMetadata("compiler/fir/resolve/testData/resolve/arguments")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class Arguments extends AbstractFirResolveTestCase {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
}
public void testAllFilesPresentInArguments() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/fir/resolve/testData/resolve/arguments"), Pattern.compile("^([^.]+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("default.kt")
public void testDefault() throws Exception {
runTest("compiler/fir/resolve/testData/resolve/arguments/default.kt");
}
@TestMetadata("lambda.kt")
public void testLambda() throws Exception {
runTest("compiler/fir/resolve/testData/resolve/arguments/lambda.kt");
}
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
runTest("compiler/fir/resolve/testData/resolve/arguments/simple.kt");
}
@TestMetadata("vararg.kt")
public void testVararg() throws Exception {
runTest("compiler/fir/resolve/testData/resolve/arguments/vararg.kt");
}
}
@TestMetadata("compiler/fir/resolve/testData/resolve/builtins")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)