[NI] Fix unit coercion
Consider following case:
fun foo(): Unit = run { "hello" }
Previously, NI would analyze lambda body without expected type, because
it is a type variable 'R' from 'run', which hasn't been fixed yet. This
leads to treating "hello" as lambda-return argument and adding bogus
'String' constraint on 'R', and, consequently, type mismatch.
Now, we peek into current constraint system and check if return-type of
lambda is type variable with upper-Unit constraint (which is exactly
condition for its body to be Unit-coerced). If so, then we provide
expected Unit-type for body explicitly, and the rest will be done
automatically (in particular, in aforementioned example "hello" wouldn't
be treated as lambda return argument).
This commit is contained in:
+19
-3
@@ -34,6 +34,8 @@ class PostponedArgumentsAnalyzer(
|
||||
// type can be proper if it not contains not fixed type variables
|
||||
fun canBeProper(type: UnwrappedType): Boolean
|
||||
|
||||
fun hasUpperUnitConstraint(type: UnwrappedType): Boolean
|
||||
|
||||
// mutable operations
|
||||
fun addOtherSystem(otherSystem: ConstraintStorage)
|
||||
|
||||
@@ -73,10 +75,24 @@ class PostponedArgumentsAnalyzer(
|
||||
|
||||
val receiver = lambda.receiver?.let(::substitute)
|
||||
val parameters = lambda.parameters.map(::substitute)
|
||||
val expectedType = lambda.returnType.takeIf { c.canBeProper(it) }?.let(::substitute)
|
||||
val rawReturnType = lambda.returnType
|
||||
|
||||
val returnArguments =
|
||||
resolutionCallbacks.analyzeAndGetLambdaReturnArguments(lambda.atom, lambda.isSuspend, receiver, parameters, expectedType)
|
||||
val expectedTypeForReturnArguments = when {
|
||||
c.canBeProper(rawReturnType) -> substitute(rawReturnType)
|
||||
|
||||
// For Unit-coercion
|
||||
c.hasUpperUnitConstraint(rawReturnType) -> lambda.returnType.builtIns.unitType
|
||||
|
||||
else -> null
|
||||
}
|
||||
|
||||
val returnArguments = resolutionCallbacks.analyzeAndGetLambdaReturnArguments(
|
||||
lambda.atom,
|
||||
lambda.isSuspend,
|
||||
receiver,
|
||||
parameters,
|
||||
expectedTypeForReturnArguments
|
||||
)
|
||||
|
||||
returnArguments.forEach { c.addSubsystemFromArgument(it) }
|
||||
|
||||
|
||||
+11
@@ -27,7 +27,9 @@ import org.jetbrains.kotlin.resolve.calls.model.KotlinCallDiagnostic
|
||||
import org.jetbrains.kotlin.resolve.calls.tower.isSuccess
|
||||
import org.jetbrains.kotlin.types.TypeConstructor
|
||||
import org.jetbrains.kotlin.types.UnwrappedType
|
||||
import org.jetbrains.kotlin.types.checker.NewTypeVariableConstructor
|
||||
import org.jetbrains.kotlin.types.typeUtil.contains
|
||||
import org.jetbrains.kotlin.types.typeUtil.isUnit
|
||||
import org.jetbrains.kotlin.utils.SmartList
|
||||
|
||||
class NewConstraintSystemImpl(
|
||||
@@ -248,4 +250,13 @@ class NewConstraintSystemImpl(
|
||||
checkState(State.BUILDING, State.COMPLETION)
|
||||
return storage.buildCurrentSubstitutor()
|
||||
}
|
||||
|
||||
// PostponedArgumentsAnalyzer.Context
|
||||
override fun hasUpperUnitConstraint(type: UnwrappedType): Boolean {
|
||||
checkState(State.BUILDING, State.COMPLETION, State.FREEZED)
|
||||
|
||||
val constraints = storage.notFixedTypeVariables[type.constructor]?.constraints ?: return false
|
||||
|
||||
return constraints.any { it.kind == ConstraintKind.UPPER && it.type.isUnit() }
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
// !WITH_NEW_INFERENCE
|
||||
|
||||
fun <T> materialize(): T = TODO()
|
||||
|
||||
val a: () -> Unit = l@{
|
||||
// Expected type 'Unit' is used here for inference
|
||||
if (true) return@l materialize()
|
||||
|
||||
// Expected type here is Unit, but it also implies coercion,
|
||||
// so we can end lambda body with statement
|
||||
if (true) <!UNUSED_EXPRESSION!>42<!>
|
||||
}
|
||||
|
||||
val b: () -> Unit = l@{
|
||||
// Error, coercion can't be applied at this position!
|
||||
if (true) return@l <!TYPE_MISMATCH!>"hello"<!>
|
||||
|
||||
// However, this is OK, because here coercion is applied
|
||||
<!UNUSED_EXPRESSION!>"hello"<!>
|
||||
}
|
||||
|
||||
val c: () -> Unit = {
|
||||
// Interesting enough, for such expessions we use expected type Unit
|
||||
// (compare that with the previous case, where we didn't used expected type Unit for "hello")
|
||||
materialize()
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package
|
||||
|
||||
public val a: () -> kotlin.Unit
|
||||
public val b: () -> kotlin.Unit
|
||||
public val c: () -> kotlin.Unit
|
||||
public fun </*0*/ T> materialize(): T
|
||||
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
// !WITH_NEW_INFERENCE
|
||||
|
||||
fun <T : Number> materializeNumber(): T = TODO()
|
||||
|
||||
fun a(): Unit = run {
|
||||
<!OI;TYPE_INFERENCE_NO_INFORMATION_FOR_PARAMETER!>materializeNumber<!>()
|
||||
}
|
||||
|
||||
fun b(): Unit = run {
|
||||
run {
|
||||
<!OI;TYPE_INFERENCE_NO_INFORMATION_FOR_PARAMETER!>materializeNumber<!>()
|
||||
}
|
||||
}
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
package
|
||||
|
||||
public fun a(): kotlin.Unit
|
||||
public fun b(): kotlin.Unit
|
||||
public fun </*0*/ T : kotlin.Number> materializeNumber(): T
|
||||
Vendored
+19
@@ -0,0 +1,19 @@
|
||||
// !WITH_NEW_INFERENCE
|
||||
|
||||
fun <T> materialize(): T = TODO()
|
||||
|
||||
fun implicitCoercion() {
|
||||
val <!UNUSED_VARIABLE!>a<!> = {
|
||||
// Block is implicitly Unit-coerced, so it is allowed to place statement at the end of lambda
|
||||
if (true) <!UNUSED_EXPRESSION!>42<!>
|
||||
}
|
||||
|
||||
val <!UNUSED_VARIABLE!>b<!> = l@{
|
||||
return@l
|
||||
}
|
||||
|
||||
val <!UNUSED_VARIABLE!>c<!> = l@{
|
||||
// Error: block doesn't have an expected type, so call can't be inferred!
|
||||
<!NI;UNREACHABLE_CODE!>return@l<!> <!OI;TYPE_INFERENCE_NO_INFORMATION_FOR_PARAMETER!>materialize<!>()
|
||||
}
|
||||
}
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
package
|
||||
|
||||
public fun implicitCoercion(): kotlin.Unit
|
||||
public fun </*0*/ T> materialize(): T
|
||||
Vendored
+36
@@ -0,0 +1,36 @@
|
||||
// !WITH_NEW_INFERENCE
|
||||
|
||||
fun <T> materialize(): T = TODO()
|
||||
|
||||
fun a(): Unit = run {
|
||||
run {
|
||||
// Ok, block is coerced, because it has (indirectly) Unit-expected type
|
||||
"hello"
|
||||
}
|
||||
}
|
||||
|
||||
fun b(): Unit = run {
|
||||
// Ok, expected type is applied
|
||||
materialize()
|
||||
}
|
||||
|
||||
fun c(): Unit = run {
|
||||
run {
|
||||
// Attention!
|
||||
// In OI expected type 'Unit' isn't applied here because of implementation quirks (note that OI still applies Unit in case 'e')
|
||||
// In NI, it is applied and call is correctly inferred, which is consistent with the previous case
|
||||
<!OI;TYPE_INFERENCE_NO_INFORMATION_FOR_PARAMETER!>materialize<!>()
|
||||
}
|
||||
}
|
||||
|
||||
fun d(): Unit = run outer@{
|
||||
run inner@{
|
||||
<!NI;UNREACHABLE_CODE!>return@inner<!> <!OI;TYPE_INFERENCE_NO_INFORMATION_FOR_PARAMETER!>materialize<!>()
|
||||
}
|
||||
}
|
||||
|
||||
fun e(): Unit = run outer@{
|
||||
run inner@{
|
||||
return@outer materialize()
|
||||
}
|
||||
}
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
package
|
||||
|
||||
public fun a(): kotlin.Unit
|
||||
public fun b(): kotlin.Unit
|
||||
public fun c(): kotlin.Unit
|
||||
public fun d(): kotlin.Unit
|
||||
public fun e(): kotlin.Unit
|
||||
public fun </*0*/ T> materialize(): T
|
||||
@@ -0,0 +1,26 @@
|
||||
// !WITH_NEW_INFERENCE
|
||||
// !CHECK_TYPE
|
||||
|
||||
fun noCoercionLastExpressionUsedAsReturnArgument() {
|
||||
val a = {
|
||||
42
|
||||
}
|
||||
|
||||
a checkType { _<() -> Int>() }
|
||||
}
|
||||
|
||||
fun noCoercionBlockHasExplicitType() {
|
||||
val <!UNUSED_VARIABLE!>b<!>: () -> Int = {
|
||||
<!TYPE_MISMATCH!>if (true) <!UNUSED_EXPRESSION!>42<!><!>
|
||||
}
|
||||
}
|
||||
|
||||
fun noCoercionBlockHasExplicitReturn() {
|
||||
val <!UNUSED_VARIABLE!>c<!> = l@{
|
||||
if (true) return@l 42
|
||||
|
||||
<!INVALID_IF_AS_EXPRESSION!>if<!> (true) 239
|
||||
}
|
||||
}
|
||||
|
||||
fun noCoercionInExpressionBody(): Unit = <!TYPE_MISMATCH!>"hello"<!>
|
||||
@@ -0,0 +1,6 @@
|
||||
package
|
||||
|
||||
public fun noCoercionBlockHasExplicitReturn(): kotlin.Unit
|
||||
public fun noCoercionBlockHasExplicitType(): kotlin.Unit
|
||||
public fun noCoercionInExpressionBody(): kotlin.Unit
|
||||
public fun noCoercionLastExpressionUsedAsReturnArgument(): kotlin.Unit
|
||||
@@ -10703,6 +10703,45 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("compiler/testData/diagnostics/tests/inference/coercionToUnit")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class CoercionToUnit extends AbstractDiagnosticsTest {
|
||||
public void testAllFilesPresentInCoercionToUnit() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/tests/inference/coercionToUnit"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("coercionWithExpectedType.kt")
|
||||
public void testCoercionWithExpectedType() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inference/coercionToUnit/coercionWithExpectedType.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("coercionWithExpectedTypeAndBound.kt")
|
||||
public void testCoercionWithExpectedTypeAndBound() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inference/coercionToUnit/coercionWithExpectedTypeAndBound.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("coercionWithoutExpectedType.kt")
|
||||
public void testCoercionWithoutExpectedType() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inference/coercionToUnit/coercionWithoutExpectedType.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("indirectCoercionWithExpectedType.kt")
|
||||
public void testIndirectCoercionWithExpectedType() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inference/coercionToUnit/indirectCoercionWithExpectedType.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("noCoercion.kt")
|
||||
public void testNoCoercion() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inference/coercionToUnit/noCoercion.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("compiler/testData/diagnostics/tests/inference/commonSystem")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
Generated
+39
@@ -10703,6 +10703,45 @@ public class DiagnosticsUsingJavacTestGenerated extends AbstractDiagnosticsUsing
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("compiler/testData/diagnostics/tests/inference/coercionToUnit")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class CoercionToUnit extends AbstractDiagnosticsUsingJavacTest {
|
||||
public void testAllFilesPresentInCoercionToUnit() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/tests/inference/coercionToUnit"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("coercionWithExpectedType.kt")
|
||||
public void testCoercionWithExpectedType() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inference/coercionToUnit/coercionWithExpectedType.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("coercionWithExpectedTypeAndBound.kt")
|
||||
public void testCoercionWithExpectedTypeAndBound() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inference/coercionToUnit/coercionWithExpectedTypeAndBound.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("coercionWithoutExpectedType.kt")
|
||||
public void testCoercionWithoutExpectedType() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inference/coercionToUnit/coercionWithoutExpectedType.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("indirectCoercionWithExpectedType.kt")
|
||||
public void testIndirectCoercionWithExpectedType() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inference/coercionToUnit/indirectCoercionWithExpectedType.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("noCoercion.kt")
|
||||
public void testNoCoercion() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inference/coercionToUnit/noCoercion.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("compiler/testData/diagnostics/tests/inference/commonSystem")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
Reference in New Issue
Block a user