[FIR] Resolve rhs of += in dependent context

This commit is contained in:
Dmitriy Novozhilov
2020-07-06 12:51:47 +03:00
parent 943b59b5d8
commit 29849b1330
15 changed files with 303 additions and 53 deletions
@@ -0,0 +1,107 @@
digraph plusAssignWithLambdaInRhs_kt {
graph [nodesep=3]
node [shape=box penwidth=2]
edge [penwidth=2]
subgraph cluster_0 {
color=red
0 [label="Enter function plusAssign" style="filled" fillcolor=red];
subgraph cluster_1 {
color=blue
1 [label="Enter block"];
2 [label="Exit block"];
}
3 [label="Exit function plusAssign" style="filled" fillcolor=red];
}
0 -> {1};
1 -> {2};
2 -> {3};
subgraph cluster_2 {
color=red
4 [label="Enter function plus" style="filled" fillcolor=red];
subgraph cluster_3 {
color=blue
5 [label="Enter block"];
6 [label="Access variable this@R|/plus|"];
7 [label="Jump: ^plus this@R|/plus|"];
8 [label="Stub" style="filled" fillcolor=gray];
9 [label="Exit block" style="filled" fillcolor=gray];
}
10 [label="Exit function plus" style="filled" fillcolor=red];
}
4 -> {5};
5 -> {6};
6 -> {7};
7 -> {10};
7 -> {8} [style=dotted];
8 -> {9} [style=dotted];
9 -> {10} [style=dotted];
subgraph cluster_4 {
color=red
11 [label="Enter function test" style="filled" fillcolor=red];
subgraph cluster_5 {
color=blue
12 [label="Enter block"];
13 [label="Const: Null(null)"];
14 [label="Check not null: Null(null)!!"];
15 [label="Variable declaration: lval list: R|kotlin/collections/MutableList<kotlin/Function1<kotlin/String, kotlin/String>>|"];
16 [label="Access variable R|<local>/list|"];
17 [label="Postponed enter to lambda"];
subgraph cluster_6 {
color=blue
22 [label="Enter function anonymousFunction" style="filled" fillcolor=red];
subgraph cluster_7 {
color=blue
23 [label="Enter block"];
24 [label="Access variable R|<local>/it|"];
25 [label="Exit block"];
}
26 [label="Exit function anonymousFunction" style="filled" fillcolor=red];
}
18 [label="Postponed exit from lambda"];
19 [label="Function call: R|<local>/list|.R|/plusAssign|<R|(kotlin/String) -> kotlin/String|>(...)"];
20 [label="Exit block"];
}
21 [label="Exit function test" style="filled" fillcolor=red];
}
11 -> {12};
12 -> {13};
13 -> {14};
14 -> {15};
15 -> {16};
16 -> {17};
17 -> {18 22};
18 -> {19};
19 -> {20};
20 -> {21};
22 -> {23};
23 -> {24};
24 -> {25};
25 -> {26};
subgraph cluster_8 {
color=red
27 [label="Enter function listOf" style="filled" fillcolor=red];
subgraph cluster_9 {
color=blue
28 [label="Enter block"];
29 [label="Const: Null(null)"];
30 [label="Check not null: Null(null)!!"];
31 [label="Jump: ^listOf Null(null)!!"];
32 [label="Stub" style="filled" fillcolor=gray];
33 [label="Exit block" style="filled" fillcolor=gray];
}
34 [label="Exit function listOf" style="filled" fillcolor=red];
}
27 -> {28};
28 -> {29};
29 -> {30};
30 -> {31};
31 -> {34};
31 -> {32} [style=dotted];
32 -> {33} [style=dotted];
33 -> {34} [style=dotted];
}
@@ -0,0 +1,19 @@
// ISSUE: KT-39005
// !DUMP_CFG
fun test() {
val list: MutableList<(String) -> String> = null!!
list += { it }
}
//class A<T>(private val executor: ((T) -> Unit) -> Unit)
//
//fun <T> postpone(computation: () -> T): A<T> {
// val queue = mutableListOf<() -> Unit>()
//
// return A { resolve ->
// queue += {
// resolve(computation())
// }
// }
//}
@@ -0,0 +1,16 @@
FILE: plusAssignWithLambdaInRhs.kt
public final operator fun <T> R|kotlin/collections/MutableList<T>|.plusAssign(x: R|T|): R|kotlin/Unit| {
}
public final operator fun <T> R|kotlin/collections/List<T>|.plus(x: R|T|): R|kotlin/collections/List<T>| {
^plus this@R|/plus|
}
public final fun test(): R|kotlin/Unit| {
lval list: R|kotlin/collections/MutableList<kotlin/Function1<kotlin/String, kotlin/String>>| = Null(null)!!
R|<local>/list|.R|/plusAssign|<R|(kotlin/String) -> kotlin/String|>(fun <anonymous>(it: R|kotlin/String|): R|kotlin/String| {
^ R|<local>/it|
}
)
}
public final fun <T> listOf(x: R|T|): R|kotlin/collections/List<T>| {
^listOf Null(null)!!
}
@@ -716,6 +716,11 @@ public class FirDiagnosticsWithStdlibTestGenerated extends AbstractFirDiagnostic
public void testIfElvisReturn() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolveWithStdlib/inference/ifElvisReturn.kt");
}
@TestMetadata("plusAssignWithLambdaInRhs.kt")
public void testPlusAssignWithLambdaInRhs() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolveWithStdlib/inference/plusAssignWithLambdaInRhs.kt");
}
}
@TestMetadata("compiler/fir/analysis-tests/testData/resolveWithStdlib/j+k")
@@ -58,14 +58,22 @@ class FirCallResolver(
inferenceComponents.session.callConflictResolverFactory
.create(TypeSpecificityComparator.NONE, inferenceComponents)
@PrivateForInline
var needTransformArguments: Boolean = true
@OptIn(PrivateForInline::class)
fun resolveCallAndSelectCandidate(functionCall: FirFunctionCall): FirFunctionCall {
qualifiedResolver.reset()
@Suppress("NAME_SHADOWING")
val functionCall = functionCall.transformExplicitReceiver(transformer, ResolutionMode.ContextIndependent)
.also {
dataFlowAnalyzer.enterQualifiedAccessExpression()
functionCall.argumentList.transformArguments(transformer, ResolutionMode.ContextDependent)
}
val functionCall = if (needTransformArguments) {
functionCall.transformExplicitReceiver(transformer, ResolutionMode.ContextIndependent)
.also {
dataFlowAnalyzer.enterQualifiedAccessExpression()
functionCall.argumentList.transformArguments(transformer, ResolutionMode.ContextDependent)
}
} else {
functionCall
}
val name = functionCall.calleeReference.name
val result = collectCandidates(functionCall, name)
@@ -289,6 +297,17 @@ class FirCallResolver(
return callResolver.selectDelegatingConstructorCall(delegatedConstructorCall, name, result, callInfo)
}
@OptIn(PrivateForInline::class)
inline fun <T> withNoArgumentsTransform(block: () -> T): T {
val oldValue = needTransformArguments
needTransformArguments = false
return try {
block()
} finally {
needTransformArguments = oldValue
}
}
private fun selectDelegatingConstructorCall(
call: FirDelegatedConstructorCall, name: Name, result: CandidateCollector, callInfo: CallInfo
): FirDelegatedConstructorCall {
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.fir.resolve.dfa
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.PrivateForInline
import org.jetbrains.kotlin.fir.contracts.FirResolvedContractDescription
import org.jetbrains.kotlin.fir.contracts.description.ConeBooleanConstantReference
import org.jetbrains.kotlin.fir.contracts.description.ConeConditionalEffectDeclaration
@@ -106,6 +107,9 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
protected val any = components.session.builtinTypes.anyType.type
private val nullableNothing = components.session.builtinTypes.nullableNothingType.type
@PrivateForInline
var ignoreFunctionCalls: Boolean = false
// ----------------------------------- Requests -----------------------------------
fun getTypeUsingSmartcastInfo(qualifiedAccessExpression: FirQualifiedAccessExpression): MutableList<ConeKotlinType>? {
@@ -133,6 +137,17 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
graphBuilder.dropSubgraphFromCall(call)
}
@OptIn(PrivateForInline::class)
inline fun <T> withIgnoreFunctionCalls(block: () -> T): T {
val oldValue = ignoreFunctionCalls
ignoreFunctionCalls = true
return try {
block()
} finally {
ignoreFunctionCalls = oldValue
}
}
// ----------------------------------- Named function -----------------------------------
fun enterFunction(function: FirFunction<*>) {
@@ -683,7 +698,12 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
graphBuilder.enterCall()
}
@OptIn(PrivateForInline::class)
fun exitFunctionCall(functionCall: FirFunctionCall, callCompleted: Boolean) {
if (ignoreFunctionCalls) {
graphBuilder.exitIgnoredCall(functionCall)
return
}
val (functionCallNode, unionNode) = graphBuilder.exitFunctionCall(functionCall, callCompleted)
unionNode?.let { unionFlowFromArguments(it) }
functionCallNode.mergeIncomingFlow()
@@ -93,6 +93,25 @@ class ControlFlowGraphBuilder {
private val exitSafeCallNodes: Stack<ExitSafeCallNode> = stackOf()
private val exitElvisExpressionNodes: Stack<ElvisExitNode> = stackOf()
/*
* ignoredFunctionCalls is needed for resolve of += operator:
* we have two different calls for resolve, but we left only one of them,
* so we twice call `enterCall` and twice increase `levelCounter`, but
* `exitFunctionCall` we call only once.
*
* So workflow looks like that:
* Calls:
* - a.plus(b) // (1)
* - a.plusAssign(b) // (2)
*
* enterCall(a.plus(b)), increase counter
* exitIgnoredCall(a.plus(b)) // decrease counter
* enterCall(a.plusAssign(b)) // increase counter
* exitIgnoredCall(a.plusAssign(b)) // decrease counter
* exitFunctionCall(a.plus(b) | a.plusAssign(b)) // don't touch counter
*/
private val ignoredFunctionCalls: MutableSet<FirFunctionCall> = mutableSetOf()
// ----------------------------------- API for node builders -----------------------------------
private var idCounter: Int = Random.nextInt()
@@ -818,8 +837,18 @@ class ControlFlowGraphBuilder {
levelCounter++
}
fun exitFunctionCall(functionCall: FirFunctionCall, callCompleted: Boolean): Pair<FunctionCallNode, UnionFunctionCallArgumentsNode?> {
fun exitIgnoredCall(functionCall: FirFunctionCall) {
levelCounter--
ignoredFunctionCalls += functionCall
}
fun exitFunctionCall(functionCall: FirFunctionCall, callCompleted: Boolean): Pair<FunctionCallNode, UnionFunctionCallArgumentsNode?> {
val callWasIgnored = ignoredFunctionCalls.remove(functionCall)
if (!callWasIgnored) {
levelCounter--
} else {
ignoredFunctionCalls.clear()
}
val returnsNothing = functionCall.resultType.isNothing
val node = createFunctionCallNode(functionCall)
val (kind, unionNode) = processUnionOfArguments(node, callCompleted)
@@ -14,25 +14,7 @@ import org.jetbrains.kotlin.resolve.calls.inference.model.ConstraintStorage
abstract class FirInferenceSession {
companion object {
val DEFAULT: FirInferenceSession = object : FirInferenceSession() {
override fun <T> shouldRunCompletion(call: T): Boolean where T : FirResolvable, T : FirStatement = true
override val currentConstraintSystem: ConstraintStorage
get() = ConstraintStorage.Empty
override fun <T> addPartiallyResolvedCall(call: T) where T : FirResolvable, T : FirStatement {}
override fun <T> addErrorCall(call: T) where T : FirResolvable, T : FirStatement {}
override fun <T> addCompetedCall(call: T, candidate: Candidate) where T : FirResolvable, T : FirStatement {}
override fun inferPostponedVariables(
lambda: ResolvedLambdaAtom,
initialStorage: ConstraintStorage
): Map<ConeTypeVariableTypeConstructor, ConeKotlinType>? = null
override fun <T> writeOnlyStubs(call: T): Boolean where T : FirResolvable, T : FirStatement = false
override fun <T> callCompleted(call: T): Boolean where T : FirResolvable, T : FirStatement = false
override fun <T> shouldCompleteResolvedSubAtomsOf(call: T): Boolean where T : FirResolvable, T : FirStatement = true
}
val DEFAULT: FirInferenceSession = object : FirStubInferenceSession() {}
}
abstract fun <T> shouldRunCompletion(call: T): Boolean where T : FirResolvable, T : FirStatement
@@ -51,4 +33,24 @@ abstract class FirInferenceSession {
abstract fun <T> writeOnlyStubs(call: T): Boolean where T : FirResolvable, T : FirStatement
abstract fun <T> callCompleted(call: T): Boolean where T : FirResolvable, T : FirStatement
abstract fun <T> shouldCompleteResolvedSubAtomsOf(call: T): Boolean where T : FirResolvable, T : FirStatement
}
}
abstract class FirStubInferenceSession : FirInferenceSession() {
override fun <T> shouldRunCompletion(call: T): Boolean where T : FirResolvable, T : FirStatement = true
override val currentConstraintSystem: ConstraintStorage
get() = ConstraintStorage.Empty
override fun <T> addPartiallyResolvedCall(call: T) where T : FirResolvable, T : FirStatement {}
override fun <T> addErrorCall(call: T) where T : FirResolvable, T : FirStatement {}
override fun <T> addCompetedCall(call: T, candidate: Candidate) where T : FirResolvable, T : FirStatement {}
override fun inferPostponedVariables(
lambda: ResolvedLambdaAtom,
initialStorage: ConstraintStorage
): Map<ConeTypeVariableTypeConstructor, ConeKotlinType>? = null
override fun <T> writeOnlyStubs(call: T): Boolean where T : FirResolvable, T : FirStatement = false
override fun <T> callCompleted(call: T): Boolean where T : FirResolvable, T : FirStatement = false
override fun <T> shouldCompleteResolvedSubAtomsOf(call: T): Boolean where T : FirResolvable, T : FirStatement = true
}
@@ -22,10 +22,10 @@ import org.jetbrains.kotlin.fir.references.builder.buildExplicitSuperReference
import org.jetbrains.kotlin.fir.references.builder.buildSimpleNamedReference
import org.jetbrains.kotlin.fir.references.impl.FirSimpleNamedReference
import org.jetbrains.kotlin.fir.resolve.*
import org.jetbrains.kotlin.fir.resolve.calls.ImplicitDispatchReceiverValue
import org.jetbrains.kotlin.fir.resolve.calls.*
import org.jetbrains.kotlin.fir.resolve.calls.candidate
import org.jetbrains.kotlin.fir.resolve.calls.mapArguments
import org.jetbrains.kotlin.fir.resolve.diagnostics.*
import org.jetbrains.kotlin.fir.resolve.inference.FirStubInferenceSession
import org.jetbrains.kotlin.fir.resolve.transformers.InvocationKindTransformer
import org.jetbrains.kotlin.fir.resolve.transformers.StoreReceiver
import org.jetbrains.kotlin.fir.resolve.transformers.firClassLike
@@ -199,6 +199,7 @@ open class FirExpressionsResolveTransformer(transformer: FirBodyResolveTransform
storeTypeFromCallee(functionCall)
}
if (functionCall.calleeReference !is FirSimpleNamedReference) return functionCall.compose()
if (functionCall.calleeReference is FirNamedReferenceWithCandidate) return functionCall.compose()
dataFlowAnalyzer.enterCall()
functionCall.annotations.forEach { it.accept(this, data) }
functionCall.transform<FirFunctionCall, Nothing?>(InvocationKindTransformer, null)
@@ -302,9 +303,10 @@ open class FirExpressionsResolveTransformer(transformer: FirBodyResolveTransform
require(operatorCall.operation != FirOperation.ASSIGN)
operatorCall.annotations.forEach { it.accept(this, data) }
var (leftArgument, rightArgument) = operatorCall.arguments
@Suppress("NAME_SHADOWING")
operatorCall.argumentList.transformArguments(this, ResolutionMode.ContextIndependent)
val (leftArgument, rightArgument) = operatorCall.arguments
leftArgument = leftArgument.transformSingle(transformer, ResolutionMode.ContextIndependent)
rightArgument = rightArgument.transformSingle(transformer, ResolutionMode.ContextDependent)
fun createFunctionCall(name: Name) = buildFunctionCall {
source = operatorCall.source?.withKind(FirFakeSourceElementKind.DesugaredCompoundAssignment)
@@ -317,24 +319,35 @@ open class FirExpressionsResolveTransformer(transformer: FirBodyResolveTransform
}
}
// TODO: disable DataFlowAnalyzer for resolving that two calls
// x.plusAssign(y)
val assignmentOperatorName = FirOperationNameConventions.ASSIGNMENTS.getValue(operatorCall.operation)
val assignOperatorCall = createFunctionCall(assignmentOperatorName)
val resolvedAssignCall = assignOperatorCall.transformSingle(this, ResolutionMode.ContextIndependent)
val assignCallReference = resolvedAssignCall.toResolvedCallableReference()
val assignIsError = resolvedAssignCall.typeRef is FirErrorTypeRef
val resolvedAssignCall = resolveCandidateForAssignmentOperatorCall {
assignOperatorCall.transformSingle(this, ResolutionMode.ContextDependent)
}
val assignCallReference = resolvedAssignCall.calleeReference as? FirNamedReferenceWithCandidate
val assignIsError = assignCallReference?.isError ?: true
// x = x + y
val simpleOperatorName = FirOperationNameConventions.ASSIGNMENTS_TO_SIMPLE_OPERATOR.getValue(operatorCall.operation)
val simpleOperatorCall = createFunctionCall(simpleOperatorName)
val resolvedOperatorCall = simpleOperatorCall.transformSingle(this, ResolutionMode.ContextIndependent)
val operatorCallReference = resolvedOperatorCall.toResolvedCallableReference()
val resolvedOperatorCall = resolveCandidateForAssignmentOperatorCall {
simpleOperatorCall.transformSingle(this, ResolutionMode.ContextDependent)
}
val operatorCallReference = resolvedOperatorCall.calleeReference as? FirNamedReferenceWithCandidate
val operatorIsError = operatorCallReference?.isError ?: true
val lhsReference = leftArgument.toResolvedCallableReference()
val lhsIsVar = (lhsReference?.resolvedSymbol as? FirVariableSymbol<*>)?.fir?.isVar == true
val lhsVariable = (lhsReference?.resolvedSymbol as? FirVariableSymbol<*>)?.fir
val lhsIsVar = lhsVariable?.isVar == true
return when {
operatorCallReference == null || (!lhsIsVar && !assignIsError) -> resolvedAssignCall.compose()
assignCallReference == null -> {
operatorIsError || (!lhsIsVar && !assignIsError) -> {
callCompleter.completeCall(resolvedAssignCall, noExpectedType)
dataFlowAnalyzer.exitFunctionCall(resolvedAssignCall, callCompleted = true)
resolvedAssignCall.compose()
}
assignIsError -> {
callCompleter.completeCall(resolvedOperatorCall, lhsVariable?.returnTypeRef ?: noExpectedType)
dataFlowAnalyzer.exitFunctionCall(resolvedOperatorCall, callCompleted = true)
val assignment =
buildVariableAssignment {
source = operatorCall.source
@@ -353,17 +366,39 @@ open class FirExpressionsResolveTransformer(transformer: FirBodyResolveTransform
}
assignment.transform(transformer, ResolutionMode.ContextIndependent)
}
else -> buildErrorExpression {
source = operatorCall.source
diagnostic =
ConeOperatorAmbiguityError(listOf(operatorCallReference.resolvedSymbol, assignCallReference.resolvedSymbol))
}.compose()
else -> {
val operatorCallSymbol = operatorCallReference?.candidateSymbol
val assignmentCallSymbol = assignCallReference?.candidateSymbol
requireNotNull(operatorCallSymbol)
requireNotNull(assignmentCallSymbol)
buildErrorExpression {
source = operatorCall.source
diagnostic =
ConeOperatorAmbiguityError(listOf(operatorCallSymbol, assignmentCallSymbol))
}.compose()
}
}
}
throw IllegalArgumentException(operatorCall.render())
}
private inline fun <T> resolveCandidateForAssignmentOperatorCall(block: () -> T): T {
return dataFlowAnalyzer.withIgnoreFunctionCalls {
callResolver.withNoArgumentsTransform {
inferenceComponents.withInferenceSession(InferenceSessionForAssignmentOperatorCall) {
block()
}
}
}
}
private object InferenceSessionForAssignmentOperatorCall : FirStubInferenceSession() {
override fun <T> shouldRunCompletion(call: T): Boolean where T : FirStatement, T : FirResolvable = false
}
private fun FirTypeRef.withTypeArgumentsForBareType(argument: FirExpression): FirTypeRef {
// TODO: Everything should also work for case of checked-type itself is a type alias
val baseTypeArguments =
@@ -1,4 +1,3 @@
// IGNORE_BACKEND_FIR: JVM_IR
// TARGET_BACKEND: JVM
// IGNORE_BACKEND: JVM_IR
@@ -1,4 +1,3 @@
// IGNORE_BACKEND_FIR: JVM_IR
class MyList<T>
operator fun <T> MyList<T>.plusAssign(element: T) {}
@@ -5,5 +5,5 @@ fun <T> f(): R<T> = R<T>()
operator fun Int.plusAssign(y: R<Int>) {}
fun box() {
<!INAPPLICABLE_CANDIDATE!>1 += f()<!>
1 += f()
}
@@ -29,7 +29,7 @@ class Test()
fun test()
{
val control = Control()
control.MouseMoved += { <!UNRESOLVED_REFERENCE!>it<!>.<!UNRESOLVED_REFERENCE!>X<!> } // here
control.MouseMoved += { it.X } // here
control.MouseMoved.plusAssign( { it.X } ) // ok
}
}
@@ -37,6 +37,6 @@ class B {
}
fun foo3(x: B) = {
<!VARIABLE_EXPECTED!>x<!> += { "" }
<!VARIABLE_EXPECTED!>x<!> += id { "" }
x += { "" }
x += id { "" }
}
@@ -13,8 +13,8 @@ fun <T> id(x: T) = x
fun main() {
var newValue = A()
<!INAPPLICABLE_CANDIDATE!>newValue += id { total -> A() }<!>
<!INAPPLICABLE_CANDIDATE!>newValue += id(fun(total) = A())<!>
<!INAPPLICABLE_CANDIDATE!>newValue += id(fun(total): A { return A() })<!>
<!ASSIGN_OPERATOR_AMBIGUITY!>newValue += id { total -> A() }<!>
<!ASSIGN_OPERATOR_AMBIGUITY!>newValue += id(fun(total) = A())<!>
<!ASSIGN_OPERATOR_AMBIGUITY!>newValue += id(fun(total): A { return A() })<!>
<!ASSIGN_OPERATOR_AMBIGUITY!>newValue += id(::foo)<!>
}