From b6ea2d4fd48e3f2f0e91d070ce3ff1cd22a4e112 Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Wed, 20 May 2015 16:14:21 +0300 Subject: [PATCH] Control-Flow: Compute type predicates for arguments of unresolved calls --- .../kotlin/cfg/JetControlFlowProcessor.java | 92 ++++++++++++++- .../ValueArgumentsToParametersMapper.java | 2 +- .../expressions/unresolvedCalls.instructions | 87 +++++++++++++++ .../cfg/expressions/unresolvedCalls.kt | 8 ++ .../cfg/expressions/unresolvedCalls.values | 31 ++++++ .../unresolvedCallsWithReceiver.instructions | 105 ++++++++++++++++++ .../unresolvedCallsWithReceiver.kt | 11 ++ .../unresolvedCallsWithReceiver.values | 39 +++++++ .../kotlin/cfg/ControlFlowTestGenerated.java | 12 ++ .../kotlin/cfg/PseudoValueTestGenerated.java | 12 ++ 10 files changed, 397 insertions(+), 2 deletions(-) create mode 100644 compiler/testData/cfg/expressions/unresolvedCalls.instructions create mode 100644 compiler/testData/cfg/expressions/unresolvedCalls.kt create mode 100644 compiler/testData/cfg/expressions/unresolvedCalls.values create mode 100644 compiler/testData/cfg/expressions/unresolvedCallsWithReceiver.instructions create mode 100644 compiler/testData/cfg/expressions/unresolvedCallsWithReceiver.kt create mode 100644 compiler/testData/cfg/expressions/unresolvedCallsWithReceiver.values diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/JetControlFlowProcessor.java b/compiler/frontend/src/org/jetbrains/kotlin/cfg/JetControlFlowProcessor.java index 35f28fa2a26..7c1daeef09a 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/JetControlFlowProcessor.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/JetControlFlowProcessor.java @@ -17,11 +17,13 @@ package org.jetbrains.kotlin.cfg; import com.google.common.collect.Lists; +import com.google.common.collect.Sets; import com.intellij.psi.PsiElement; import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.SmartFMap; import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.MultiMap; import kotlin.KotlinPackage; import kotlin.jvm.functions.Function0; import kotlin.jvm.functions.Function1; @@ -43,8 +45,15 @@ import org.jetbrains.kotlin.resolve.BindingContext; import org.jetbrains.kotlin.resolve.BindingContextUtils; import org.jetbrains.kotlin.resolve.BindingTrace; import org.jetbrains.kotlin.resolve.CompileTimeConstantUtils; +import org.jetbrains.kotlin.renderer.DescriptorRenderer; +import org.jetbrains.kotlin.resolve.*; +import org.jetbrains.kotlin.resolve.bindingContextUtil.BindingContextUtilPackage; +import org.jetbrains.kotlin.resolve.calls.ValueArgumentsToParametersMapper; +import org.jetbrains.kotlin.resolve.calls.callUtil.CallUtilPackage; import org.jetbrains.kotlin.resolve.calls.model.*; import org.jetbrains.kotlin.resolve.calls.tasks.ExplicitReceiverKind; +import org.jetbrains.kotlin.resolve.calls.tasks.ResolutionCandidate; +import org.jetbrains.kotlin.resolve.calls.tasks.TracingStrategy; import org.jetbrains.kotlin.resolve.constants.CompileTimeConstant; import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator; import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver; @@ -220,9 +229,90 @@ public class JetControlFlowProcessor { return createNonSyntheticValue(to, Arrays.asList(from), kind); } + @Nullable + private Map getTypeMapForUnresolvedCall(@NotNull JetElement to, List arguments) { + Call call = CallUtilPackage.getCall(to, trace.getBindingContext()); + if (call == null) return null; + + JetExpression callee = call.getCalleeExpression(); + if (callee == null) return null; + + Collection candidates = KotlinPackage.sortBy( + KotlinPackage.filterIsInstance( + BindingContextUtilPackage.getReferenceTargets(callee, trace.getBindingContext()), + FunctionDescriptor.class + ), + new Function1() { + @Override + public Comparable invoke(FunctionDescriptor descriptor) { + return DescriptorRenderer.DEBUG_TEXT.render(descriptor); + } + } + ); + if (candidates.isEmpty()) return null; + + ReceiverValue explicitReceiver = call.getExplicitReceiver(); + int argValueOffset = explicitReceiver.exists() ? 1 : 0; + + MultiMap valuesToPredicates = new MultiMap(arguments.size(), 1); + + candidateLoop: + for (FunctionDescriptor candidate : candidates) { + ResolvedCallImpl candidateCall = ResolvedCallImpl.create( + ResolutionCandidate.create(call, + candidate, + call.getDispatchReceiver(), + explicitReceiver, + ExplicitReceiverKind.NO_EXPLICIT_RECEIVER, + null), + new DelegatingBindingTrace(trace.getBindingContext(), "Compute type predicates for unresolved call arguments"), + TracingStrategy.EMPTY, + new DataFlowInfoForArgumentsImpl(call) + ); + ValueArgumentsToParametersMapper.Status status = ValueArgumentsToParametersMapper.mapValueArgumentsToParameters( + call, + TracingStrategy.EMPTY, + candidateCall, + Sets.newLinkedHashSet() + ); + if (!status.isSuccess()) continue; + + Map candidateArgumentMap = candidateCall.getValueArguments(); + List callArguments = call.getValueArguments(); + for (int i = 0; i < callArguments.size(); i++) { + int valueIndex = i + argValueOffset; + if (valueIndex >= arguments.size()) continue candidateLoop; + PseudoValue argumentValue = arguments.get(valueIndex); + + ArgumentMapping mapping = candidateCall.getArgumentMapping(callArguments.get(i)); + if (!(mapping instanceof ArgumentMatch)) continue candidateLoop; + + ValueParameterDescriptor candidateParameter = ((ArgumentMatch) mapping).getValueParameter(); + ResolvedValueArgument resolvedArgument = candidateArgumentMap.get(candidateParameter); + JetType expectedType = resolvedArgument instanceof VarargValueArgument + ? candidateParameter.getVarargElementType() + : candidateParameter.getType(); + + valuesToPredicates.putValue(argumentValue, expectedType != null ? new AllSubtypes(expectedType) : AllTypes.INSTANCE$); + } + } + + SmartFMap result = SmartFMap.emptyMap(); + for (Map.Entry> entry : valuesToPredicates.entrySet()) { + result = result.plus(entry.getKey(), PseudocodePackage.or(entry.getValue())); + } + + return result; + } + @NotNull private PseudoValue createUnresolvedCallByValues(@NotNull JetElement to, @Nullable JetElement valueElement, List arguments) { - return builder.magic(to, valueElement, arguments, defaultTypeMap(arguments), MagicKind.UNRESOLVED_CALL).getOutputValue(); + Map typeMap = getTypeMapForUnresolvedCall(to, arguments); + if (typeMap == null) { + typeMap = defaultTypeMap(arguments); + } + + return builder.magic(to, valueElement, arguments, typeMap, MagicKind.UNRESOLVED_CALL).getOutputValue(); } @NotNull diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/ValueArgumentsToParametersMapper.java b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/ValueArgumentsToParametersMapper.java index 645c6025e0f..560d75706a7 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/ValueArgumentsToParametersMapper.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/ValueArgumentsToParametersMapper.java @@ -40,7 +40,7 @@ import static org.jetbrains.kotlin.diagnostics.Errors.*; import static org.jetbrains.kotlin.resolve.BindingContext.REFERENCE_TARGET; import static org.jetbrains.kotlin.resolve.calls.ValueArgumentsToParametersMapper.Status.*; -/*package*/ class ValueArgumentsToParametersMapper { +public class ValueArgumentsToParametersMapper { public enum Status { STRONG_ERROR(false), diff --git a/compiler/testData/cfg/expressions/unresolvedCalls.instructions b/compiler/testData/cfg/expressions/unresolvedCalls.instructions new file mode 100644 index 00000000000..da3a42b02db --- /dev/null +++ b/compiler/testData/cfg/expressions/unresolvedCalls.instructions @@ -0,0 +1,87 @@ +== foo == +fun foo(i: Int) {} +--------------------- +L0: + 1 + v(i: Int) + magic[FAKE_INITIALIZER](i: Int) -> + w(i|) + 2 mark({}) + read (Unit) +L1: + 1 NEXT:[] +error: + PREV:[] +sink: + PREV:[, ] +===================== +== foo == +fun foo(a: IntArray) {} +--------------------- +L0: + 1 + v(a: IntArray) + magic[FAKE_INITIALIZER](a: IntArray) -> + w(a|) + 2 mark({}) + read (Unit) +L1: + 1 NEXT:[] +error: + PREV:[] +sink: + PREV:[, ] +===================== +== foo == +fun foo(a: String, b: Int) {} +--------------------- +L0: + 1 + v(a: String) + magic[FAKE_INITIALIZER](a: String) -> + w(a|) + v(b: Int) + magic[FAKE_INITIALIZER](b: Int) -> + w(b|) + 2 mark({}) + read (Unit) +L1: + 1 NEXT:[] +error: + PREV:[] +sink: + PREV:[, ] +===================== +== foo == +fun foo() {} +--------------------- +L0: + 1 + 2 mark({}) + read (Unit) +L1: + 1 NEXT:[] +error: + PREV:[] +sink: + PREV:[, ] +===================== +== test == +fun test() { + foo(bar()) +} +--------------------- +L0: + 1 + 2 mark({ foo(bar()) }) + mark(bar()) + magic[UNRESOLVED_CALL](bar()|!) -> + mark(foo(bar())) + magic[UNRESOLVED_CALL](foo(bar())|, !) -> +L1: + 1 NEXT:[] +error: + PREV:[] +sink: + PREV:[, ] +===================== diff --git a/compiler/testData/cfg/expressions/unresolvedCalls.kt b/compiler/testData/cfg/expressions/unresolvedCalls.kt new file mode 100644 index 00000000000..20646a7c00e --- /dev/null +++ b/compiler/testData/cfg/expressions/unresolvedCalls.kt @@ -0,0 +1,8 @@ +fun foo(i: Int) {} +fun foo(a: IntArray) {} +fun foo(a: String, b: Int) {} +fun foo() {} + +fun test() { + foo(bar()) +} \ No newline at end of file diff --git a/compiler/testData/cfg/expressions/unresolvedCalls.values b/compiler/testData/cfg/expressions/unresolvedCalls.values new file mode 100644 index 00000000000..e64e39c9428 --- /dev/null +++ b/compiler/testData/cfg/expressions/unresolvedCalls.values @@ -0,0 +1,31 @@ +== foo == +fun foo(i: Int) {} +--------------------- + : Int NEW: magic[FAKE_INITIALIZER](i: Int) -> +===================== +== foo == +fun foo(a: IntArray) {} +--------------------- + : IntArray NEW: magic[FAKE_INITIALIZER](a: IntArray) -> +===================== +== foo == +fun foo(a: String, b: Int) {} +--------------------- + : String NEW: magic[FAKE_INITIALIZER](a: String) -> + : Int NEW: magic[FAKE_INITIALIZER](b: Int) -> +===================== +== foo == +fun foo() {} +--------------------- +===================== +== test == +fun test() { + foo(bar()) +} +--------------------- +foo !: * +bar !: * +bar() : OR{{<: IntArray}, {<: Int}} NEW: magic[UNRESOLVED_CALL](bar()|!) -> +foo(bar()) : * NEW: magic[UNRESOLVED_CALL](foo(bar())|, !) -> +{ foo(bar()) } : * COPY +===================== diff --git a/compiler/testData/cfg/expressions/unresolvedCallsWithReceiver.instructions b/compiler/testData/cfg/expressions/unresolvedCallsWithReceiver.instructions new file mode 100644 index 00000000000..44fea2ad623 --- /dev/null +++ b/compiler/testData/cfg/expressions/unresolvedCallsWithReceiver.instructions @@ -0,0 +1,105 @@ +== Foo == +open class Foo { + fun foo(a: IntArray) {} + fun foo(a: Int, b: Int) {} +} +--------------------- +L0: + 1 +L1: + NEXT:[] +error: + PREV:[] +sink: + PREV:[, ] +===================== +== foo == +fun foo(a: IntArray) {} +--------------------- +L0: + 1 + v(a: IntArray) + magic[FAKE_INITIALIZER](a: IntArray) -> + w(a|) + 2 mark({}) + read (Unit) +L1: + 1 NEXT:[] +error: + PREV:[] +sink: + PREV:[, ] +===================== +== foo == +fun foo(a: Int, b: Int) {} +--------------------- +L0: + 1 + v(a: Int) + magic[FAKE_INITIALIZER](a: Int) -> + w(a|) + v(b: Int) + magic[FAKE_INITIALIZER](b: Int) -> + w(b|) + 2 mark({}) + read (Unit) +L1: + 1 NEXT:[] +error: + PREV:[] +sink: + PREV:[, ] +===================== +== foo == +fun Foo.foo(i: Int) {} +--------------------- +L0: + 1 + v(i: Int) + magic[FAKE_INITIALIZER](i: Int) -> + w(i|) + 2 mark({}) + read (Unit) +L1: + 1 NEXT:[] +error: + PREV:[] +sink: + PREV:[, ] +===================== +== foo == +fun Foo.foo() {} +--------------------- +L0: + 1 + 2 mark({}) + read (Unit) +L1: + 1 NEXT:[] +error: + PREV:[] +sink: + PREV:[, ] +===================== +== test == +fun test() { + Foo().foo(bar()) +} +--------------------- +L0: + 1 + 2 mark({ Foo().foo(bar()) }) + mark(Foo().foo(bar())) + mark(Foo()) + call(Foo(), ) -> + mark(bar()) + magic[UNRESOLVED_CALL](bar()|!) -> + mark(foo(bar())) + call(foo(bar()), foo|, ) -> +L1: + 1 NEXT:[] +error: + PREV:[] +sink: + PREV:[, ] +===================== diff --git a/compiler/testData/cfg/expressions/unresolvedCallsWithReceiver.kt b/compiler/testData/cfg/expressions/unresolvedCallsWithReceiver.kt new file mode 100644 index 00000000000..8c1655d0624 --- /dev/null +++ b/compiler/testData/cfg/expressions/unresolvedCallsWithReceiver.kt @@ -0,0 +1,11 @@ +open class Foo { + fun foo(a: IntArray) {} + fun foo(a: Int, b: Int) {} +} + +fun Foo.foo(i: Int) {} +fun Foo.foo() {} + +fun test() { + Foo().foo(bar()) +} \ No newline at end of file diff --git a/compiler/testData/cfg/expressions/unresolvedCallsWithReceiver.values b/compiler/testData/cfg/expressions/unresolvedCallsWithReceiver.values new file mode 100644 index 00000000000..8b8ba2f84f9 --- /dev/null +++ b/compiler/testData/cfg/expressions/unresolvedCallsWithReceiver.values @@ -0,0 +1,39 @@ +== Foo == +open class Foo { + fun foo(a: IntArray) {} + fun foo(a: Int, b: Int) {} +} +--------------------- +===================== +== foo == +fun foo(a: IntArray) {} +--------------------- + : IntArray NEW: magic[FAKE_INITIALIZER](a: IntArray) -> +===================== +== foo == +fun foo(a: Int, b: Int) {} +--------------------- + : Int NEW: magic[FAKE_INITIALIZER](a: Int) -> + : Int NEW: magic[FAKE_INITIALIZER](b: Int) -> +===================== +== foo == +fun Foo.foo(i: Int) {} +--------------------- + : Int NEW: magic[FAKE_INITIALIZER](i: Int) -> +===================== +== foo == +fun Foo.foo() {} +--------------------- +===================== +== test == +fun test() { + Foo().foo(bar()) +} +--------------------- +Foo() : {<: Foo} NEW: call(Foo(), ) -> +bar !: * +bar() : IntArray NEW: magic[UNRESOLVED_CALL](bar()|!) -> +foo(bar()) : * NEW: call(foo(bar()), foo|, ) -> +Foo().foo(bar()) : * COPY +{ Foo().foo(bar()) } : * COPY +===================== diff --git a/compiler/tests/org/jetbrains/kotlin/cfg/ControlFlowTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/cfg/ControlFlowTestGenerated.java index 54bb798da90..519412dd5ff 100644 --- a/compiler/tests/org/jetbrains/kotlin/cfg/ControlFlowTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/cfg/ControlFlowTestGenerated.java @@ -616,6 +616,18 @@ public class ControlFlowTestGenerated extends AbstractControlFlowTest { doTest(fileName); } + @TestMetadata("unresolvedCalls.kt") + public void testUnresolvedCalls() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/cfg/expressions/unresolvedCalls.kt"); + doTest(fileName); + } + + @TestMetadata("unresolvedCallsWithReceiver.kt") + public void testUnresolvedCallsWithReceiver() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/cfg/expressions/unresolvedCallsWithReceiver.kt"); + doTest(fileName); + } + @TestMetadata("unresolvedProperty.kt") public void testUnresolvedProperty() throws Exception { String fileName = JetTestUtils.navigationMetadata("compiler/testData/cfg/expressions/unresolvedProperty.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/cfg/PseudoValueTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/cfg/PseudoValueTestGenerated.java index 7017601bb43..620c7f836d6 100644 --- a/compiler/tests/org/jetbrains/kotlin/cfg/PseudoValueTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/cfg/PseudoValueTestGenerated.java @@ -618,6 +618,18 @@ public class PseudoValueTestGenerated extends AbstractPseudoValueTest { doTest(fileName); } + @TestMetadata("unresolvedCalls.kt") + public void testUnresolvedCalls() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/cfg/expressions/unresolvedCalls.kt"); + doTest(fileName); + } + + @TestMetadata("unresolvedCallsWithReceiver.kt") + public void testUnresolvedCallsWithReceiver() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/cfg/expressions/unresolvedCallsWithReceiver.kt"); + doTest(fileName); + } + @TestMetadata("unresolvedProperty.kt") public void testUnresolvedProperty() throws Exception { String fileName = JetTestUtils.navigationMetadata("compiler/testData/cfg/expressions/unresolvedProperty.kt");