diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/CallResolver.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/CallResolver.kt index b770ed6e560..fe9d0ffb168 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/CallResolver.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/CallResolver.kt @@ -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? = null } sealed class CallKind { diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/FirCallArgumentsProcessor.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/FirCallArgumentsProcessor.kt new file mode 100644 index 00000000000..c5b7c14894c --- /dev/null +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/FirCallArgumentsProcessor.kt @@ -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 +) { + class Result(val argumentMapping: Map, 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, + val argumentMap: MutableMap = mutableMapOf(), + val usedParameters: MutableSet = mutableSetOf() + ) { + abstract fun processArgument(argument: FirExpression): MappingStatus + + class PositionalOnly(valueParameters: List) : 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, + argumentMap: MutableMap, + usedParameters: MutableSet + ) : 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 + } + } + } + } + } +} \ No newline at end of file diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/ResolverParts.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/ResolverParts.kt index 070538487a8..b5dc3800f1e 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/ResolverParts.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/ResolverParts.kt @@ -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().valueParameters.size != callInfo.arguments.size) { + val function = symbol.firUnsafe() + 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) } diff --git a/compiler/fir/resolve/testData/resolve/arguments/default.kt b/compiler/fir/resolve/testData/resolve/arguments/default.kt new file mode 100644 index 00000000000..762f7da88ac --- /dev/null +++ b/compiler/fir/resolve/testData/resolve/arguments/default.kt @@ -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) +} + diff --git a/compiler/fir/resolve/testData/resolve/arguments/default.txt b/compiler/fir/resolve/testData/resolve/arguments/default.txt new file mode 100644 index 00000000000..a9f81abb907 --- /dev/null +++ b/compiler/fir/resolve/testData/resolve/arguments/default.txt @@ -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)) + #() + #(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)) + #(Int(1), Boolean(true)) + R|/baz|(Int(1)) + R|/baz|(Int(1), String(my), String(yours)) + R|/baz|(Int(1), z = Boolean(true)) + #(Int(0), String(), Boolean(false)) + } diff --git a/compiler/fir/resolve/testData/resolve/arguments/lambda.kt b/compiler/fir/resolve/testData/resolve/arguments/lambda.kt new file mode 100644 index 00000000000..acc2e223bf2 --- /dev/null +++ b/compiler/fir/resolve/testData/resolve/arguments/lambda.kt @@ -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) {} +} \ No newline at end of file diff --git a/compiler/fir/resolve/testData/resolve/arguments/lambda.txt b/compiler/fir/resolve/testData/resolve/arguments/lambda.txt new file mode 100644 index 00000000000..11c847f0520 --- /dev/null +++ b/compiler/fir/resolve/testData/resolve/arguments/lambda.txt @@ -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|( = foo@fun .(): { + ^ { + Unit + } + + } + ) + R|/foo|( = foo@fun .(): { + ^ { + Unit + } + + } + ) + R|/foo|(foo@fun .(): { + ^ { + Unit + } + + } + ) + #(Int(1), = foo@fun .(): { + ^ { + Unit + } + + } + ) + #(f = foo@fun .(): { + ^ { + Unit + } + + } + , = foo@fun .(): { + ^ { + Unit + } + + } + ) + R|/bar|(Int(1), = bar@fun .(): { + ^ { + Unit + } + + } + ) + R|/bar|(x = Int(1), = bar@fun .(): { + ^ { + Unit + } + + } + ) + R|/bar|(Int(1), bar@fun .(): { + ^ { + Unit + } + + } + ) + R|/bar|(x = Int(1), f = bar@fun .(): { + ^ { + Unit + } + + } + ) + #( = bar@fun .(): { + ^ { + Unit + } + + } + ) + #(bar@fun .(): { + ^ { + Unit + } + + } + ) + R|/baz|(other = Boolean(false), f = baz@fun .(): { + ^ { + Unit + } + + } + ) + R|/baz|(baz@fun .(): { + ^ { + Unit + } + + } + , Boolean(false)) + #( = baz@fun .(): { + ^ { + Unit + } + + } + ) + #( = baz@fun .(): { + ^ { + Unit + } + + } + ) + #(other = Boolean(false), = baz@fun .(): { + ^ { + Unit + } + + } + ) + } diff --git a/compiler/fir/resolve/testData/resolve/arguments/simple.kt b/compiler/fir/resolve/testData/resolve/arguments/simple.kt new file mode 100644 index 00000000000..feaefbc3214 --- /dev/null +++ b/compiler/fir/resolve/testData/resolve/arguments/simple.kt @@ -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 = "") +} \ No newline at end of file diff --git a/compiler/fir/resolve/testData/resolve/arguments/simple.txt b/compiler/fir/resolve/testData/resolve/arguments/simple.txt new file mode 100644 index 00000000000..51808274162 --- /dev/null +++ b/compiler/fir/resolve/testData/resolve/arguments/simple.txt @@ -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)) + #() + #(Double(0.0), Boolean(false), Int(0), String()) + #(Int(1), Double(2.0), third = Boolean(true), String()) + #(second = Double(0.0), first = Int(0), fourth = String()) + #(first = Double(0.0), second = Int(0), third = String(), fourth = Boolean(false)) + #(first = Int(0), second = Double(0.0), third = Boolean(false), fourth = String(), first = Int(1)) + #(Int(0), Double(0.0), Boolean(false), foth = String()) + } diff --git a/compiler/fir/resolve/testData/resolve/arguments/vararg.kt b/compiler/fir/resolve/testData/resolve/arguments/vararg.kt new file mode 100644 index 00000000000..35fdf26963a --- /dev/null +++ b/compiler/fir/resolve/testData/resolve/arguments/vararg.kt @@ -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) +} diff --git a/compiler/fir/resolve/testData/resolve/arguments/vararg.txt b/compiler/fir/resolve/testData/resolve/arguments/vararg.txt new file mode 100644 index 00000000000..d86173b312a --- /dev/null +++ b/compiler/fir/resolve/testData/resolve/arguments/vararg.txt @@ -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)) + #(String()) + #(Int(1), Int(2)) + R|/bar|(Int(1), z = Boolean(true), y = #(String(my), String(yours))) + #(Int(0), z = Boolean(false), y = String(), y = String(other)) + #(Int(0), String(), Boolean(true)) + } diff --git a/compiler/fir/resolve/testData/resolve/overrides/simple.txt b/compiler/fir/resolve/testData/resolve/overrides/simple.txt index 86595838233..98f1502ce1e 100644 --- a/compiler/fir/resolve/testData/resolve/overrides/simple.txt +++ b/compiler/fir/resolve/testData/resolve/overrides/simple.txt @@ -37,7 +37,7 @@ FILE: simple.kt public final fun test(): R|kotlin/Unit| { R|/B.foo|() R|/B.bar|() - #() + #() } } diff --git a/compiler/fir/resolve/tests/org/jetbrains/kotlin/fir/FirResolveTestCaseGenerated.java b/compiler/fir/resolve/tests/org/jetbrains/kotlin/fir/FirResolveTestCaseGenerated.java index 1621d7382b1..f5629e59903 100644 --- a/compiler/fir/resolve/tests/org/jetbrains/kotlin/fir/FirResolveTestCaseGenerated.java +++ b/compiler/fir/resolve/tests/org/jetbrains/kotlin/fir/FirResolveTestCaseGenerated.java @@ -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)