K2: Change resolution rules of when/if expressions with expected type

When expected type is known, use it as expected type for branch bodies.
While it indeed becomes different from the usual select call resolution,
where expected type is applied only after completion starts,
it helps to support, e.g. callable references resolution just as powerful
as it was in K1.

Also, in some cases where diagnostics have been changed, they become
a bit more helpful since they are reported closer
to the problematic places

cannotCastToFunction.kt test has been removed because it relied
on the case erroneously supported by the hack removed from
the FirCallResolver in this commit.

^KT-45989 Fixed
^KT-55936 Fixed
^KT-56445 Fixed
^KT-54709 Related
^KT-55931 Related
This commit is contained in:
Denis.Zharkov
2022-11-29 13:54:07 +01:00
committed by Space Team
parent a38040680c
commit 33dcbaac16
26 changed files with 156 additions and 81 deletions
@@ -3106,6 +3106,18 @@ public class DiagnosisCompilerTestFE10TestdataTestGenerated extends AbstractDiag
@TestMetadata("compiler/testData/diagnostics/tests/callableReference/adapted")
@TestDataPath("$PROJECT_ROOT")
public class Adapted {
@Test
@TestMetadata("adaptationByExpectTypeOutsideCall.kt")
public void testAdaptationByExpectTypeOutsideCall() throws Exception {
runTest("compiler/testData/diagnostics/tests/callableReference/adapted/adaptationByExpectTypeOutsideCall.kt");
}
@Test
@TestMetadata("adaptationInWhenWithMapOf.kt")
public void testAdaptationInWhenWithMapOf() throws Exception {
runTest("compiler/testData/diagnostics/tests/callableReference/adapted/adaptationInWhenWithMapOf.kt");
}
@Test
public void testAllFilesPresentInAdapted() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/callableReference/adapted"), Pattern.compile("^(.+)\\.kt$"), Pattern.compile("^(.+)\\.fir\\.kts?$"), true);
@@ -3112,6 +3112,18 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirDiagnosti
@TestMetadata("compiler/testData/diagnostics/tests/callableReference/adapted")
@TestDataPath("$PROJECT_ROOT")
public class Adapted {
@Test
@TestMetadata("adaptationByExpectTypeOutsideCall.kt")
public void testAdaptationByExpectTypeOutsideCall() throws Exception {
runTest("compiler/testData/diagnostics/tests/callableReference/adapted/adaptationByExpectTypeOutsideCall.kt");
}
@Test
@TestMetadata("adaptationInWhenWithMapOf.kt")
public void testAdaptationInWhenWithMapOf() throws Exception {
runTest("compiler/testData/diagnostics/tests/callableReference/adapted/adaptationInWhenWithMapOf.kt");
}
@Test
public void testAllFilesPresentInAdapted() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/callableReference/adapted"), Pattern.compile("^(.*)\\.kts?$"), Pattern.compile("^(.+)\\.fir\\.kts?$"), true);
@@ -3106,6 +3106,18 @@ public class FirOldFrontendDiagnosticsWithLightTreeTestGenerated extends Abstrac
@TestMetadata("compiler/testData/diagnostics/tests/callableReference/adapted")
@TestDataPath("$PROJECT_ROOT")
public class Adapted {
@Test
@TestMetadata("adaptationByExpectTypeOutsideCall.kt")
public void testAdaptationByExpectTypeOutsideCall() throws Exception {
runTest("compiler/testData/diagnostics/tests/callableReference/adapted/adaptationByExpectTypeOutsideCall.kt");
}
@Test
@TestMetadata("adaptationInWhenWithMapOf.kt")
public void testAdaptationInWhenWithMapOf() throws Exception {
runTest("compiler/testData/diagnostics/tests/callableReference/adapted/adaptationInWhenWithMapOf.kt");
}
@Test
public void testAllFilesPresentInAdapted() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/callableReference/adapted"), Pattern.compile("^(.+)\\.kt$"), Pattern.compile("^(.+)\\.fir\\.kts?$"), true);
@@ -18337,12 +18337,6 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT
runTest("compiler/testData/codegen/box/fir/callableReferenceToStaticFunction.kt");
}
@Test
@TestMetadata("cannotCastToFunction.kt")
public void testCannotCastToFunction() throws Exception {
runTest("compiler/testData/codegen/box/fir/cannotCastToFunction.kt");
}
@Test
@TestMetadata("cannotCastToFunctionInIf.kt")
public void testCannotCastToFunctionInIf() throws Exception {
@@ -407,18 +407,6 @@ class FirCallResolver(
}
val chosenCandidate = reducedCandidates.single()
if (!resolvedCallableReferenceAtom.hasBeenPostponed &&
expectedType is ConeTypeVariableType &&
chosenCandidate.symbol.fir.let { func ->
func is FirSimpleFunction && func.valueParameters.any { param ->
param.defaultValue != null
}
}
) {
resolvedCallableReferenceAtom.hasBeenPostponed = true
return applicability to true
}
constraintSystemBuilder.runTransaction {
chosenCandidate.outerConstraintBuilderEffect!!(this)
@@ -74,7 +74,13 @@ class FirControlFlowStatementsResolveTransformer(transformer: FirAbstractBodyRes
// when with one branch cannot be completed if it's not already complete in the first place
}
else -> {
whenExpression = whenExpression.transformBranches(transformer, ResolutionMode.ContextDependent)
whenExpression = whenExpression.transformBranches(
transformer,
data.takeIf {
val expectedType = it.expectedType
expectedType != null && expectedType !is FirImplicitTypeRef
} ?: ResolutionMode.ContextDependent,
)
whenExpression = syntheticCallGenerator.generateCalleeForWhenExpression(whenExpression, resolutionContext) ?: run {
whenExpression = whenExpression.transformSingle(whenExhaustivenessTransformer, null)
@@ -1,22 +0,0 @@
// TARGET_BACKEND: JVM_IR
// IGNORE_BACKEND_K1: JVM_IR
open class IrElement
fun IrElement.dumpKotlinLike(options: String = ""): String = "O"
fun IrElement.dump(normalizeNames: Boolean = false, stableOrder: Boolean = false): String = "K"
fun <T> select(flag: Boolean, a: T, b: T): T =
if (flag) a else b
fun dump(data: IrElement, dumpStrategy: String): String {
// Note: K1 reports TYPE_MISMATCH here (see also cannotCastToFunctionInIf.kt working both in K1/K2)
val dump: IrElement.() -> String = select(dumpStrategy == "KotlinLike", IrElement::dumpKotlinLike, IrElement::dump)
return data.dump()
}
fun box(): String {
val element = IrElement()
return dump(element, "KotlinLike") + dump(element, "OtherStrategy")
}
@@ -0,0 +1,30 @@
// SKIP_TXT
fun baz(options: String = ""): String = ""
fun bar(normalizeNames: Boolean = false): String = ""
fun <E> select(x: E, y: E) = x
fun <E> id(e: E): E = e
fun runForString(x: () -> String) {}
val cs: CharSequence = ""
fun foo(dumpStrategy: String) {
val dump0: () -> String = ::baz // TYPE_MISMATCH
val dump1: () -> String = <!INITIALIZER_TYPE_MISMATCH, TYPE_MISMATCH!>id(::baz)<!> // TYPE_MISMATCH
// OK, TYPE_MISMATCH IN K2
val dump2: () -> String = if (dumpStrategy == "KotlinLike") ::baz else ::bar
val dump3: () -> String = <!INITIALIZER_TYPE_MISMATCH, TYPE_MISMATCH, TYPE_MISMATCH!>select(::baz, ::bar)<!> // TYPE_MISMATCH
var dump4: () -> String = if (dumpStrategy == "KotlinLike") ::baz else ::bar
dump4.invoke()
dump4 = if (dumpStrategy == "KotlinLike") ::baz else ::bar
var dump5: () -> CharSequence = { cs }
expectString(<!ARGUMENT_TYPE_MISMATCH!>dump5.invoke()<!>)
dump5 = if (dumpStrategy == "KotlinLike") ::baz else ::bar
// `dump5` should have smart cast to () -> String
expectString(dump5.invoke())
}
fun expectString(x: String) {}
@@ -0,0 +1,30 @@
// SKIP_TXT
fun baz(options: String = ""): String = ""
fun bar(normalizeNames: Boolean = false): String = ""
fun <E> select(x: E, y: E) = x
fun <E> id(e: E): E = e
fun runForString(x: () -> String) {}
val cs: CharSequence = ""
fun foo(dumpStrategy: String) {
val dump0: () -> String = <!TYPE_MISMATCH!>::<!TYPE_MISMATCH!>baz<!><!> // TYPE_MISMATCH
val dump1: () -> String = <!TYPE_MISMATCH, TYPE_MISMATCH!>id(::baz)<!> // TYPE_MISMATCH
// OK, TYPE_MISMATCH IN K2
val dump2: () -> String = if (dumpStrategy == "KotlinLike") ::baz else ::bar
val dump3: () -> String = <!TYPE_MISMATCH, TYPE_MISMATCH, TYPE_MISMATCH!>select(::baz, ::bar)<!> // TYPE_MISMATCH
var dump4: () -> String = if (dumpStrategy == "KotlinLike") ::baz else ::bar
dump4.invoke()
dump4 = if (dumpStrategy == "KotlinLike") ::baz else ::bar
var dump5: () -> CharSequence = { cs }
expectString(<!TYPE_MISMATCH!>dump5.invoke()<!>)
dump5 = if (dumpStrategy == "KotlinLike") ::baz else ::bar
// `dump5` should have smart cast to () -> String
expectString(<!DEBUG_INFO_SMARTCAST!>dump5<!>.invoke())
}
fun expectString(x: String) {}
@@ -0,0 +1,14 @@
// FIR_IDENTICAL
// SKIP_TXT
// WITH_STDLIB
// ISSUE: KT-56445
fun foo(x: Int?, y: Int = 1) {}
fun bar() {}
fun test() {
mapOf( // String, KFunction<Unit>
"a" to ::foo,
"b" to ::bar,
)
}
@@ -10,7 +10,7 @@ fun dump(dumpStrategy: String) {
val f0: Function0<String> = <!INAPPLICABLE_CANDIDATE!>returnAdapter<!>(::<!UNRESOLVED_REFERENCE!>foo<!>)
val f1: Function0<String> = ::foo
val f2: Function0<String> = if (dumpStrategy == "KotlinLike") ::<!UNRESOLVED_REFERENCE!>foo<!> else ::<!UNRESOLVED_REFERENCE!>bar<!>
val f2: Function0<String> = if (dumpStrategy == "KotlinLike") ::foo else ::bar
}
fun returnAdapter(a: kotlin.reflect.KFunction0<String>) = a
@@ -26,7 +26,7 @@ fun <T> bind2(r: Option<T>): Option<T> {
fun <T, R> bind3(r: Option<T>): Option<T> {
return <!RETURN_TYPE_MISMATCH!>if (r is Some) {
// Diagnoses an error correctly
if (true) None<R>() else r
<!TYPE_MISMATCH!>if (true) <!TYPE_MISMATCH, TYPE_MISMATCH, TYPE_MISMATCH!>None<R>()<!> else r<!>
}
else r<!>
}
@@ -29,11 +29,11 @@ val testSafeCall4: String? = J.m[""]?.let { it.toString() }
val testIf1: String = if (true) J.s else J.s
val testIf2: String? = if (true) J.s else J.s
val testIf3: String = <!INITIALIZER_TYPE_MISMATCH, TYPE_MISMATCH!>if (true) J.m[""] else J.m[""]<!>
val testIf3: String = <!INITIALIZER_TYPE_MISMATCH, TYPE_MISMATCH!>if (true) <!TYPE_MISMATCH!>J.m[""]<!> else <!TYPE_MISMATCH!>J.m[""]<!><!>
val testIf4: String? = if (true) J.m[""] else J.m[""]
val testWhen1: String = when { else -> J.s }
val testWhen2: String? = when { else -> J.s }
val testWhen3: String = <!INITIALIZER_TYPE_MISMATCH, TYPE_MISMATCH!>when { else -> J.m[""] }<!>
val testWhen3: String = <!INITIALIZER_TYPE_MISMATCH, TYPE_MISMATCH!>when { else -> <!TYPE_MISMATCH!>J.m[""]<!> }<!>
val testWhen4: String? = when { else -> J.m[""] }
@@ -30,8 +30,8 @@ fun test_3(b: Boolean) {
}
val x3: String.() -> String = if (b) {
{ <!CANNOT_INFER_PARAMETER_TYPE!>x<!> -> "x" }
<!ARGUMENT_TYPE_MISMATCH!>{ <!CANNOT_INFER_PARAMETER_TYPE!>x<!> -> "x" }<!>
} else {
{ <!CANNOT_INFER_PARAMETER_TYPE!>x<!> -> "x" }
<!ARGUMENT_TYPE_MISMATCH!>{ <!CANNOT_INFER_PARAMETER_TYPE!>x<!> -> "x" }<!>
}
}
@@ -5,7 +5,7 @@ interface Entity<T>
abstract class SecuredEntity<out E>(val entity: E) where E : Entity<Int>, E : SecurityCodeAware<*,*>
interface SecurityCodeAware<out E, R : SecuredEntity<E>> where E : Entity<Int>, E : SecurityCodeAware<E, R>
fun <E, R : SecuredEntity<E>> SecurityCodeAware<E, R>.secured() : R where E : Entity<Int>, E : SecurityCodeAware<E, R> = <!RETURN_TYPE_MISMATCH!>when(this) {
is Order -> SecuredOrder(this)
is Order -> <!TYPE_MISMATCH!>SecuredOrder(this)<!>
else -> null!!
}<!>
class Order : Entity<Int>
@@ -1,5 +1,5 @@
// SKIP_TXT
// ISSUE: KT-55729
// ISSUE: KT-55729, KT-55931, KT-55936
fun main(b: Boolean) {
callWithLambda {
@@ -9,16 +9,16 @@ fun main(b: Boolean) {
callWithLambda {
// Unit conversion should work (for K2 see KT-55936)
<!ARGUMENT_TYPE_MISMATCH!>if (b) ::test1 else ::test2<!>
if (b) ::test1 else ::test2
}
callWithLambda {
// That hasn't been working ever in K1 nor K2
<!ARGUMENT_TYPE_MISMATCH!>if (b) {
// Doesn't work in K1, but does in K2 (see KT-55931)
if (b) {
::test1
} else {
::test2
}<!>
}
}
callWithLambda {
@@ -1,5 +1,5 @@
// SKIP_TXT
// ISSUE: KT-55729
// ISSUE: KT-55729, KT-55931, KT-55936
fun main(b: Boolean) {
callWithLambda {
@@ -13,7 +13,7 @@ fun main(b: Boolean) {
}
callWithLambda {
// That hasn't been working ever in K1 nor K2
// Doesn't work in K1, but does in K2 (see KT-55931)
if (b) <!TYPE_MISMATCH!>{
<!TYPE_MISMATCH!>::<!TYPE_MISMATCH!>test1<!><!>
}<!> else <!TYPE_MISMATCH!>{
@@ -48,7 +48,7 @@ fun bar(block: () -> String) {}
fun test_5(b: Boolean) {
bar {
<!ARGUMENT_TYPE_MISMATCH!>if (b) {
println("meh")
<!TYPE_MISMATCH!>println("meh")<!>
}<!>
}
}
@@ -8,7 +8,7 @@ fun main() {
val x3: () -> String = if (true) {{ -> "this" }} else {{ -> "this" }}
val x4: String.() -> String = if (true) {{ str: String -> "this" }} else {{ str: String -> "this" }}
val x41: String.(String) -> String = if (true) {{ str: String, str2: String -> "this" }} else {{ str: String, str2: String -> "this" }}
val x42: String.(String) -> String = if (true) {<!ARGUMENT_TYPE_MISMATCH!>{ str, <!CANNOT_INFER_PARAMETER_TYPE!>str2<!> -> "this" }<!>} else {<!ARGUMENT_TYPE_MISMATCH!>{ str, <!CANNOT_INFER_PARAMETER_TYPE!>str2<!> -> "this" }<!>}
val x42: String.(String) -> String = if (true) {{ str, <!CANNOT_INFER_PARAMETER_TYPE!>str2<!> -> "this" }} else {{ str, <!CANNOT_INFER_PARAMETER_TYPE!>str2<!> -> "this" }}
val x5: String.() -> String = if (true) {<!ARGUMENT_TYPE_MISMATCH!>{ <!CANNOT_INFER_PARAMETER_TYPE!>str<!> -> "this" }<!>} else {<!ARGUMENT_TYPE_MISMATCH!>{ <!CANNOT_INFER_PARAMETER_TYPE!>str<!> -> "this" }<!>}
val x6: String.() -> String = if (true) {<!ARGUMENT_TYPE_MISMATCH!>{ <!CANNOT_INFER_PARAMETER_TYPE!>str<!> -> "this" }<!>} else {{ "this" }}
val x7: String.() -> String = select({ -> this }, { -> this })
@@ -1,7 +0,0 @@
// !DIAGNOSTICS: -UNUSED_VARIABLE
fun foo() {
val s: String? = if (true) <!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Nothing?")!>materialize()<!> else null
}
fun <K> materialize(): K = TODO()
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// !DIAGNOSTICS: -UNUSED_VARIABLE
fun foo() {
+4 -4
View File
@@ -1,9 +1,9 @@
val test: Int = <!INITIALIZER_TYPE_MISMATCH, TYPE_MISMATCH, TYPE_MISMATCH!>if (true) {
when (2) {
val test: Int = if (true) {
<!TYPE_MISMATCH!>when (2) {
1 -> 1
else -> null
}
}<!>
}
else {
2
}<!>
}
+4 -4
View File
@@ -11,17 +11,17 @@
fun test1(): Int {
val x: String = <!TYPE_MISMATCH!>if (true) {
when {
true -> Any()
<!TYPE_MISMATCH, TYPE_MISMATCH!>when {
true -> <!TYPE_MISMATCH!>Any()<!>
else -> null
}
}<!>
} else ""<!>
return x.hashCode()
}
fun test2(): Int {
val x: String = <!INITIALIZER_TYPE_MISMATCH, TYPE_MISMATCH, TYPE_MISMATCH!><!TYPE_MISMATCH!>when {
true -> Any()
true -> <!TYPE_MISMATCH!>Any()<!>
else -> null
}<!> ?: return 0<!>
return x.hashCode()
@@ -28,8 +28,7 @@ val test3: (String) -> Boolean =
}
val test4: (String) -> Boolean =
when {
<!INITIALIZER_TYPE_MISMATCH!>when {
true -> <!ARGUMENT_TYPE_MISMATCH!>{ s1, <!CANNOT_INFER_PARAMETER_TYPE!>s2<!> -> true }<!>
else -> null!!
}
}<!>
@@ -3112,6 +3112,18 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest {
@TestMetadata("compiler/testData/diagnostics/tests/callableReference/adapted")
@TestDataPath("$PROJECT_ROOT")
public class Adapted {
@Test
@TestMetadata("adaptationByExpectTypeOutsideCall.kt")
public void testAdaptationByExpectTypeOutsideCall() throws Exception {
runTest("compiler/testData/diagnostics/tests/callableReference/adapted/adaptationByExpectTypeOutsideCall.kt");
}
@Test
@TestMetadata("adaptationInWhenWithMapOf.kt")
public void testAdaptationInWhenWithMapOf() throws Exception {
runTest("compiler/testData/diagnostics/tests/callableReference/adapted/adaptationInWhenWithMapOf.kt");
}
@Test
public void testAllFilesPresentInAdapted() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/callableReference/adapted"), Pattern.compile("^(.*)\\.kts?$"), Pattern.compile("^(.+)\\.fir\\.kts?$"), true);
@@ -18337,12 +18337,6 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
runTest("compiler/testData/codegen/box/fir/callableReferenceToStaticFunction.kt");
}
@Test
@TestMetadata("cannotCastToFunction.kt")
public void testCannotCastToFunction() throws Exception {
runTest("compiler/testData/codegen/box/fir/cannotCastToFunction.kt");
}
@Test
@TestMetadata("cannotCastToFunctionInIf.kt")
public void testCannotCastToFunctionInIf() throws Exception {