Be able to infer a type variable based on several builder inference lambdas

^KT-48329 Fixed
This commit is contained in:
Victor Petukhov
2021-08-19 11:59:35 +03:00
parent b8c3b4957a
commit 9aaf002954
7 changed files with 101 additions and 13 deletions

View File

@@ -339,6 +339,8 @@ class BuilderInferenceSession(
val callSubstitutor = storage.buildResultingSubstitutor(commonSystem, transformTypeVariablesToErrorTypes = false)
for (initialConstraint in storage.initialConstraints) {
if (initialConstraint.position is BuilderInferencePosition) continue
val substitutedConstraint = initialConstraint.substitute(callSubstitutor)
val (lower, upper) = substituteNotFixedVariables(
substitutedConstraint.a as KotlinType,

View File

@@ -205,7 +205,9 @@ class PostponedArgumentsAnalyzer(
val variable = variableWithConstraints.typeVariable
c.getBuilder().unmarkPostponedVariable(variable)
c.getBuilder().addEqualityConstraint(variable.defaultType(c), resultType, BuilderInferencePosition)
// We add <inferred type> <: TypeVariable(T) to be able to contribute type info from several builder inference lambdas
c.getBuilder().addSubtypeConstraint(resultType, variable.defaultType(c), BuilderInferencePosition)
}
}
}

View File

@@ -13,6 +13,7 @@ import org.jetbrains.kotlin.resolve.calls.model.*
import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.model.TypeConstructorMarker
import org.jetbrains.kotlin.types.model.TypeVariableMarker
import org.jetbrains.kotlin.types.model.TypeVariableTypeConstructorMarker
import org.jetbrains.kotlin.types.model.safeSubstitute
import org.jetbrains.kotlin.types.typeUtil.asTypeProjection
import org.jetbrains.kotlin.utils.addIfNotNull
@@ -178,31 +179,34 @@ class KotlinConstraintSystemCompleter(
val lambdaArguments = postponedArguments.filterIsInstance<ResolvedLambdaAtom>().takeIf { it.isNotEmpty() } ?: return false
val useBuilderInferenceWithoutAnnotation =
languageVersionSettings.supportsFeature(LanguageFeature.UseBuilderInferenceWithoutAnnotation)
val allNotFixedInputTypeVariables = mutableSetOf<TypeVariableTypeConstructorMarker>()
return lambdaArguments.any { argument ->
for (argument in lambdaArguments) {
if (!argument.atom.hasBuilderInferenceAnnotation && !useBuilderInferenceWithoutAnnotation)
return@any false
continue
val notFixedInputTypeVariables = argument.inputTypes
.map { it.extractTypeVariables() }.flatten().filter { it !in fixedTypeVariables }
if (notFixedInputTypeVariables.isEmpty()) return@any false
if (notFixedInputTypeVariables.isEmpty()) continue
allNotFixedInputTypeVariables.addAll(notFixedInputTypeVariables)
for (variable in notFixedInputTypeVariables) {
getBuilder().markPostponedVariable(notFixedTypeVariables.getValue(variable).typeVariable)
}
analyze(argument)
val variableForFixation = variableFixationFinder.findFirstVariableForFixation(
this, notFixedInputTypeVariables, postponedArguments, completionMode, topLevelType
)
// continue completion (rerun stages) only if ready for fixation variables with proper constraints have appeared
// (after analysing a lambda with the builder inference)
// otherwise we report "not enough type information" error
return variableForFixation?.hasProperConstraint == true
}
val variableForFixation = variableFixationFinder.findFirstVariableForFixation(
this, allNotFixedInputTypeVariables.toList(), postponedArguments, completionMode, topLevelType
)
// continue completion (rerun stages) only if ready for fixation variables with proper constraints have appeared
// (after analysing a lambda with the builder inference)
// otherwise we don't continue and report "not enough type information" error
return variableForFixation?.hasProperConstraint == true
}
private fun transformToAtomWithNewFunctionalExpectedType(

View File

@@ -0,0 +1,63 @@
// WITH_RUNTIME
// DONT_TARGET_EXACT_BACKEND: WASM
// IGNORE_BACKEND_FIR: JVM_IR
// TARGET_BACKEND: JVM
import kotlin.experimental.ExperimentalTypeInference
class In<in K> {
fun contribute(x: K) {}
}
class Out<out K> {
fun get(): K = null as K
}
class Inv<K> {
fun get(): K = null as K
}
interface A
class B: A
class C: A
@OptIn(ExperimentalTypeInference::class)
fun <K> build1(@BuilderInference builderAction1: In<K>.() -> Unit, @BuilderInference builderAction2: In<K>.() -> Unit): K = 1 as K
@OptIn(ExperimentalTypeInference::class)
fun <K> build2(@BuilderInference builderAction1: In<K>.() -> Unit, @BuilderInference builderAction2: In<K>.() -> Unit): K = B() as K
@OptIn(ExperimentalTypeInference::class)
fun <K> build3(@BuilderInference builderAction1: Out<K>.() -> Unit, @BuilderInference builderAction2: Out<K>.() -> Unit): K = 1 as K
@OptIn(ExperimentalTypeInference::class)
fun <K> build4(@BuilderInference builderAction1: Out<K>.() -> Unit, @BuilderInference builderAction2: Out<K>.() -> Unit): K = B() as K
@OptIn(ExperimentalTypeInference::class)
fun <K> build5(@BuilderInference builderAction1: Inv<K>.() -> Unit, @BuilderInference builderAction2: Inv<K>.() -> Unit): K = 1 as K
@OptIn(ExperimentalTypeInference::class)
fun <K> build6(@BuilderInference builderAction1: Inv<K>.() -> Unit, @BuilderInference builderAction2: Inv<K>.() -> Unit): K = B() as K
@OptIn(ExperimentalStdlibApi::class)
fun box(): String {
val x1 = build1({ contribute(1f) }, { contribute(1.0) })
<!DEBUG_INFO_EXPRESSION_TYPE("{Comparable<*> & Number}")!>x1<!>
val y1 = build2({ contribute(B()) }, { contribute(C()) })
<!DEBUG_INFO_EXPRESSION_TYPE("A")!>y1<!>
val x2 = build3({ val x: Float = get() }, { val x: Double = get() })
<!DEBUG_INFO_EXPRESSION_TYPE("{Comparable<*> & Number}")!>x2<!>
val y2 = build4({ val x: B = get() }, { val x: C = get() })
<!DEBUG_INFO_EXPRESSION_TYPE("A")!>y2<!>
val x3 = build3({ val x: Float = get() }, { val x: Double = get() })
<!DEBUG_INFO_EXPRESSION_TYPE("{Comparable<*> & Number}")!>x2<!>
val y3 = build4({ val x: B = get() }, { val x: C = get() })
<!DEBUG_INFO_EXPRESSION_TYPE("A")!>y2<!>
return "OK"
}

View File

@@ -17917,6 +17917,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
runTest("compiler/testData/codegen/box/inference/builderInference/constraintsBetweenTwoStubVariables.kt");
}
@Test
@TestMetadata("cstBasedOnTwoBuilderInferenceLambda.kt")
public void testCstBasedOnTwoBuilderInferenceLambda() throws Exception {
runTest("compiler/testData/codegen/box/inference/builderInference/cstBasedOnTwoBuilderInferenceLambda.kt");
}
@Test
@TestMetadata("intersect.kt")
public void testIntersect() throws Exception {

View File

@@ -18055,6 +18055,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
runTest("compiler/testData/codegen/box/inference/builderInference/constraintsBetweenTwoStubVariables.kt");
}
@Test
@TestMetadata("cstBasedOnTwoBuilderInferenceLambda.kt")
public void testCstBasedOnTwoBuilderInferenceLambda() throws Exception {
runTest("compiler/testData/codegen/box/inference/builderInference/cstBasedOnTwoBuilderInferenceLambda.kt");
}
@Test
@TestMetadata("inferFromExpectedType.kt")
public void testInferFromExpectedType() throws Exception {

View File

@@ -14845,6 +14845,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
runTest("compiler/testData/codegen/box/inference/builderInference/constraintsBetweenTwoStubVariables.kt");
}
@TestMetadata("cstBasedOnTwoBuilderInferenceLambda.kt")
public void testCstBasedOnTwoBuilderInferenceLambda() throws Exception {
runTest("compiler/testData/codegen/box/inference/builderInference/cstBasedOnTwoBuilderInferenceLambda.kt");
}
@TestMetadata("intersect.kt")
public void testIntersect() throws Exception {
runTest("compiler/testData/codegen/box/inference/builderInference/intersect.kt");