Add deprecation warning for false-negative TYPE_MISMATCH

See KT-49404 for details

In K1, within SubstitutingScope we approximate almost all the types
containing captured types are being approximated to either a lower or
an upper bound.

While mostly, it's being done correctly there are some problems with
approximations for flexible types

So, the parameter's type of A<in Any>::foo is approximated to Inv<in Any!>,
thus allowing to use Inv<*>, while it's obviously unsound.

NB: For the similar example, in B, there are regular TYPE_MISMATCH
because parameter for B<in Any>::foo is approximated to Nothing

Also, it's important to say that
- in K2 everything works because we don't use type approximation there
- approximation algorithm that works incorrectly is only being used in K1

^KT-54332 Fixed
This commit is contained in:
Denis.Zharkov
2022-10-06 13:08:37 +02:00
committed by Space Team
parent 4b455c0e51
commit 31ba1f1534
13 changed files with 384 additions and 1 deletions
@@ -33981,6 +33981,44 @@ public class DiagnosisCompilerTestFE10TestdataTestGenerated extends AbstractDiag
}
}
@Nested
@TestMetadata("compiler/testData/diagnostics/tests/warningsForBreakingChanges")
@TestDataPath("$PROJECT_ROOT")
public class WarningsForBreakingChanges {
@Test
public void testAllFilesPresentInWarningsForBreakingChanges() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/warningsForBreakingChanges"), Pattern.compile("^(.+)\\.kt$"), Pattern.compile("^(.+)\\.fir\\.kts?$"), true);
}
@Nested
@TestMetadata("compiler/testData/diagnostics/tests/warningsForBreakingChanges/capturedTypes")
@TestDataPath("$PROJECT_ROOT")
public class CapturedTypes {
@Test
public void testAllFilesPresentInCapturedTypes() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/warningsForBreakingChanges/capturedTypes"), Pattern.compile("^(.+)\\.kt$"), Pattern.compile("^(.+)\\.fir\\.kts?$"), true);
}
@Test
@TestMetadata("incorrectCapturedApproximationForValueParameters.kt")
public void testIncorrectCapturedApproximationForValueParameters() throws Exception {
runTest("compiler/testData/diagnostics/tests/warningsForBreakingChanges/capturedTypes/incorrectCapturedApproximationForValueParameters.kt");
}
@Test
@TestMetadata("noWarningAfterSmartcast.kt")
public void testNoWarningAfterSmartcast() throws Exception {
runTest("compiler/testData/diagnostics/tests/warningsForBreakingChanges/capturedTypes/noWarningAfterSmartcast.kt");
}
@Test
@TestMetadata("noWarningOnSAMAdaption.kt")
public void testNoWarningOnSAMAdaption() throws Exception {
runTest("compiler/testData/diagnostics/tests/warningsForBreakingChanges/capturedTypes/noWarningOnSAMAdaption.kt");
}
}
}
@Nested
@TestMetadata("compiler/testData/diagnostics/tests/when")
@TestDataPath("$PROJECT_ROOT")
@@ -33981,6 +33981,44 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirDiagnosti
}
}
@Nested
@TestMetadata("compiler/testData/diagnostics/tests/warningsForBreakingChanges")
@TestDataPath("$PROJECT_ROOT")
public class WarningsForBreakingChanges {
@Test
public void testAllFilesPresentInWarningsForBreakingChanges() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/warningsForBreakingChanges"), Pattern.compile("^(.+)\\.kt$"), Pattern.compile("^(.+)\\.fir\\.kts?$"), true);
}
@Nested
@TestMetadata("compiler/testData/diagnostics/tests/warningsForBreakingChanges/capturedTypes")
@TestDataPath("$PROJECT_ROOT")
public class CapturedTypes {
@Test
public void testAllFilesPresentInCapturedTypes() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/warningsForBreakingChanges/capturedTypes"), Pattern.compile("^(.+)\\.kt$"), Pattern.compile("^(.+)\\.fir\\.kts?$"), true);
}
@Test
@TestMetadata("incorrectCapturedApproximationForValueParameters.kt")
public void testIncorrectCapturedApproximationForValueParameters() throws Exception {
runTest("compiler/testData/diagnostics/tests/warningsForBreakingChanges/capturedTypes/incorrectCapturedApproximationForValueParameters.kt");
}
@Test
@TestMetadata("noWarningAfterSmartcast.kt")
public void testNoWarningAfterSmartcast() throws Exception {
runTest("compiler/testData/diagnostics/tests/warningsForBreakingChanges/capturedTypes/noWarningAfterSmartcast.kt");
}
@Test
@TestMetadata("noWarningOnSAMAdaption.kt")
public void testNoWarningOnSAMAdaption() throws Exception {
runTest("compiler/testData/diagnostics/tests/warningsForBreakingChanges/capturedTypes/noWarningOnSAMAdaption.kt");
}
}
}
@Nested
@TestMetadata("compiler/testData/diagnostics/tests/when")
@TestDataPath("$PROJECT_ROOT")
@@ -33981,6 +33981,44 @@ public class FirOldFrontendDiagnosticsWithLightTreeTestGenerated extends Abstrac
}
}
@Nested
@TestMetadata("compiler/testData/diagnostics/tests/warningsForBreakingChanges")
@TestDataPath("$PROJECT_ROOT")
public class WarningsForBreakingChanges {
@Test
public void testAllFilesPresentInWarningsForBreakingChanges() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/warningsForBreakingChanges"), Pattern.compile("^(.+)\\.kt$"), Pattern.compile("^(.+)\\.fir\\.kts?$"), true);
}
@Nested
@TestMetadata("compiler/testData/diagnostics/tests/warningsForBreakingChanges/capturedTypes")
@TestDataPath("$PROJECT_ROOT")
public class CapturedTypes {
@Test
public void testAllFilesPresentInCapturedTypes() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/warningsForBreakingChanges/capturedTypes"), Pattern.compile("^(.+)\\.kt$"), Pattern.compile("^(.+)\\.fir\\.kts?$"), true);
}
@Test
@TestMetadata("incorrectCapturedApproximationForValueParameters.kt")
public void testIncorrectCapturedApproximationForValueParameters() throws Exception {
runTest("compiler/testData/diagnostics/tests/warningsForBreakingChanges/capturedTypes/incorrectCapturedApproximationForValueParameters.kt");
}
@Test
@TestMetadata("noWarningAfterSmartcast.kt")
public void testNoWarningAfterSmartcast() throws Exception {
runTest("compiler/testData/diagnostics/tests/warningsForBreakingChanges/capturedTypes/noWarningAfterSmartcast.kt");
}
@Test
@TestMetadata("noWarningOnSAMAdaption.kt")
public void testNoWarningOnSAMAdaption() throws Exception {
runTest("compiler/testData/diagnostics/tests/warningsForBreakingChanges/capturedTypes/noWarningOnSAMAdaption.kt");
}
}
}
@Nested
@TestMetadata("compiler/testData/diagnostics/tests/when")
@TestDataPath("$PROJECT_ROOT")
@@ -894,6 +894,9 @@ public interface Errors {
DiagnosticFactory0<PsiElement> RESOLUTION_TO_PRIVATE_CONSTRUCTOR_OF_SEALED_CLASS = DiagnosticFactory0.create(WARNING);
DiagnosticFactory2<PsiElement, KotlinType, KotlinType> TYPE_MISMATCH_WARNING_FOR_INCORRECT_CAPTURE_APPROXIMATION = DiagnosticFactory2.create(WARNING);
DiagnosticFactory2<PsiElement, KotlinType, KotlinType> RECEIVER_TYPE_MISMATCH_WARNING_FOR_INCORRECT_CAPTURE_APPROXIMATION = DiagnosticFactory2.create(WARNING);
// Type inference
DiagnosticFactory0<KtParameter> CANNOT_INFER_PARAMETER_TYPE = DiagnosticFactory0.create(ERROR);
@@ -462,6 +462,16 @@ public class DefaultErrorMessages {
MAP.put(RESOLUTION_TO_PRIVATE_CONSTRUCTOR_OF_SEALED_CLASS, "The private constructor of a sealed class will become inaccessible here in future. See https://youtrack.jetbrains.com/issue/KT-44866 for details");
MAP.put(
TYPE_MISMATCH_WARNING_FOR_INCORRECT_CAPTURE_APPROXIMATION,
"Type mismatch: inferred type is {1} but {0} was expected. This warning will be an error soon. See https://youtrack.jetbrains.com/issue/KT-49404 for details",
RENDER_TYPE, RENDER_TYPE
);
MAP.put(
RECEIVER_TYPE_MISMATCH_WARNING_FOR_INCORRECT_CAPTURE_APPROXIMATION,
"Extension receiver type mismatch: inferred type is {1} but {0} was expected. This warning will be an error soon. See https://youtrack.jetbrains.com/issue/KT-49404 for details",
RENDER_TYPE, RENDER_TYPE
);
MAP.put(LOCAL_EXTENSION_PROPERTY, "Local extension properties are not allowed");
MAP.put(LOCAL_VARIABLE_WITH_GETTER, "Local variables are not allowed to have getters");
MAP.put(LOCAL_VARIABLE_WITH_SETTER, "Local variables are not allowed to have setters");
@@ -70,7 +70,7 @@ private val DEFAULT_CALL_CHECKERS = listOf(
ReferencingToUnderscoreNamedParameterOfCatchBlockChecker, VarargWrongExecutionOrderChecker, SelfCallInNestedObjectConstructorChecker,
NewSchemeOfIntegerOperatorResolutionChecker, EnumEntryVsCompanionPriorityCallChecker, CompanionInParenthesesLHSCallChecker,
ResolutionToPrivateConstructorOfSealedClassChecker, EqualityCallChecker, UnsupportedUntilOperatorChecker,
BuilderInferenceAssignmentChecker,
BuilderInferenceAssignmentChecker, IncorrectCapturedApproximationCallChecker,
)
private val DEFAULT_TYPE_CHECKERS = emptyList<AdditionalTypeChecker>()
private val DEFAULT_CLASSIFIER_USAGE_CHECKERS = listOf(
@@ -0,0 +1,97 @@
/*
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.resolve.calls.checkers
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.builtins.isFunctionOrSuspendFunctionType
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.resolve.calls.inference.isCaptured
import org.jetbrains.kotlin.resolve.calls.inference.wrapWithCapturingSubstitution
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.isReallySuccess
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValue
import org.jetbrains.kotlin.resolve.calls.util.getEffectiveExpectedType
import org.jetbrains.kotlin.resolve.calls.util.getType
import org.jetbrains.kotlin.resolve.sam.getFunctionTypeForSamType
import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.typeUtil.contains
import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
object IncorrectCapturedApproximationCallChecker : CallChecker {
override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) {
if (!resolvedCall.isReallySuccess()) return
val dispatchReceiverType = resolvedCall.smartCastDispatchReceiverType ?: resolvedCall.dispatchReceiver?.type ?: return
if (dispatchReceiverType.arguments.all { it.projectionKind == Variance.INVARIANT && !it.type.isCaptured() }) return
val substitutor =
TypeSubstitutor.create(dispatchReceiverType)
.substitution.wrapWithCapturingSubstitution(needApproximation = false).buildSubstitutor()
val functionDescriptor = resolvedCall.resultingDescriptor.original as? FunctionDescriptor ?: return
val capturedSubstituted = functionDescriptor.substitute(substitutor) ?: return
val indexedArguments = resolvedCall.valueArgumentsByIndex ?: return
for ((index, parameter) in capturedSubstituted.valueParameters.withIndex()) {
for (argument in indexedArguments[index].arguments) {
val expectedType =
getEffectiveExpectedType(parameter, argument, context.resolutionContext)
val argumentExpression = argument.getArgumentExpression() ?: continue
val expressionType = argumentExpression.getType(context.trace.bindingContext) ?: continue
val dataFlowValue =
context.dataFlowValueFactory.createDataFlowValue(argumentExpression, expressionType, context.resolutionContext)
if (shouldWarningBeReported(expressionType, expectedType, dataFlowValue, context)) {
context.trace.report(
Errors.TYPE_MISMATCH_WARNING_FOR_INCORRECT_CAPTURE_APPROXIMATION.on(
argumentExpression, expectedType, expressionType
)
)
}
}
}
capturedSubstituted.extensionReceiverParameter?.let { extensionReceiverParameter ->
val extensionReceiver = resolvedCall.extensionReceiver ?: return@let
val dataFlowValue =
context.dataFlowValueFactory.createDataFlowValue(extensionReceiver, context.resolutionContext)
if (shouldWarningBeReported(extensionReceiver.type, extensionReceiverParameter.type, dataFlowValue, context)) {
val expression = (extensionReceiver as? ExpressionReceiver)?.expression ?: reportOn
context.trace.report(
Errors.RECEIVER_TYPE_MISMATCH_WARNING_FOR_INCORRECT_CAPTURE_APPROXIMATION.on(
expression, extensionReceiverParameter.type, extensionReceiver.type
)
)
}
}
}
private fun shouldWarningBeReported(
expressionType: KotlinType,
expectedType: KotlinType,
dataFlowValue: DataFlowValue,
context: CallCheckerContext,
): Boolean {
if (expectedType.arguments.none { arg -> !arg.isStarProjection && arg.type.contains(UnwrappedType::isCaptured) }) return false
if (expressionType.isSubtypeOf(expectedType)) return false
val samExpectedType = getFunctionTypeForSamType(
expectedType, context.callComponents.samConversionResolver, context.callComponents.samConversionOracle,
)
if (expectedType.isFunctionOrSuspendFunctionType || samExpectedType?.isFunctionOrSuspendFunctionType == true) return false
return context.dataFlowInfo.getCollectedTypes(
dataFlowValue,
context.languageVersionSettings,
).none { it.isSubtypeOf(expectedType) }
}
}
@@ -0,0 +1,43 @@
// ISSUE: KT-49404
// SKIP_TXT
// FILE: A.java
public class A<T> implements WithExtension<T> {
public void foo(Inv<T> w) {}
public void ext(Inv<T> t) {}
}
// FILE: main.kt
class Inv<T>(var t: T)
interface WithExtension<F> {
fun Inv<F>.ext() {}
}
class B<E> : WithExtension<E> {
fun foo(w: Inv<E>) {}
}
fun withInvStar(i: Inv<*>.() -> Unit) {}
fun bar1(a: A<in Any>, i: Inv<*>) {
a.foo(<!ARGUMENT_TYPE_MISMATCH!>i<!>)
}
fun bar2(b: B<in Any>, i: Inv<*>) {
b.foo(<!ARGUMENT_TYPE_MISMATCH!>i<!>)
}
fun A<in Any>.bar3(i: Inv<*>) {
i.<!UNRESOLVED_REFERENCE_WRONG_RECEIVER!>ext<!>()
withInvStar {
<!UNRESOLVED_REFERENCE_WRONG_RECEIVER!>ext<!>()
}
}
fun B<in Any>.bar4(i: Inv<*>) {
i.<!UNRESOLVED_REFERENCE_WRONG_RECEIVER!>ext<!>()
withInvStar {
<!UNRESOLVED_REFERENCE_WRONG_RECEIVER!>ext<!>()
}
}
@@ -0,0 +1,43 @@
// ISSUE: KT-49404
// SKIP_TXT
// FILE: A.java
public class A<T> implements WithExtension<T> {
public void foo(Inv<T> w) {}
public void ext(Inv<T> t) {}
}
// FILE: main.kt
class Inv<T>(var t: T)
interface WithExtension<F> {
fun Inv<F>.ext() {}
}
class B<E> : WithExtension<E> {
fun foo(w: Inv<E>) {}
}
fun withInvStar(i: Inv<*>.() -> Unit) {}
fun bar1(a: A<in Any>, i: Inv<*>) {
a.foo(<!TYPE_MISMATCH_WARNING_FOR_INCORRECT_CAPTURE_APPROXIMATION!>i<!>)
}
fun bar2(b: B<in Any>, i: Inv<*>) {
b.foo(<!TYPE_MISMATCH!>i<!>)
}
fun A<in Any>.bar3(i: Inv<*>) {
<!RECEIVER_TYPE_MISMATCH_WARNING_FOR_INCORRECT_CAPTURE_APPROXIMATION!>i<!>.ext()
withInvStar {
<!RECEIVER_TYPE_MISMATCH_WARNING_FOR_INCORRECT_CAPTURE_APPROXIMATION!>ext<!>()
}
}
fun B<in Any>.bar4(i: Inv<*>) {
i.<!UNRESOLVED_REFERENCE_WRONG_RECEIVER!>ext<!>()
withInvStar {
<!UNRESOLVED_REFERENCE_WRONG_RECEIVER!>ext<!>()
}
}
@@ -0,0 +1,9 @@
// SKIP_TXT
interface Comp<T> {
fun foo(t: T)
}
fun <E : Any> foo(c: Comp<in E>, e: E?) {
if (e == null) return
c.foo(e)
}
@@ -0,0 +1,9 @@
// SKIP_TXT
interface Comp<T> {
fun foo(t: T)
}
fun <E : Any> foo(c: Comp<in E>, e: E?) {
if (e == null) return
c.foo(<!DEBUG_INFO_SMARTCAST!>e<!>)
}
@@ -0,0 +1,17 @@
// FIR_IDENTICAL
// SKIP_TXT
// FILE: TaskProvider.java
public interface TaskProvider<T extends CharSequence> {
void configure(Action<? super T> a);
}
// FILE: main.kt
fun interface Action<E> {
fun E.exec()
}
fun foo(tp: TaskProvider<out CharSequence>) {
tp.configure {
length
}
}
@@ -34071,6 +34071,44 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest {
}
}
@Nested
@TestMetadata("compiler/testData/diagnostics/tests/warningsForBreakingChanges")
@TestDataPath("$PROJECT_ROOT")
public class WarningsForBreakingChanges {
@Test
public void testAllFilesPresentInWarningsForBreakingChanges() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/warningsForBreakingChanges"), Pattern.compile("^(.*)\\.kts?$"), Pattern.compile("^(.+)\\.fir\\.kts?$"), true);
}
@Nested
@TestMetadata("compiler/testData/diagnostics/tests/warningsForBreakingChanges/capturedTypes")
@TestDataPath("$PROJECT_ROOT")
public class CapturedTypes {
@Test
public void testAllFilesPresentInCapturedTypes() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/warningsForBreakingChanges/capturedTypes"), Pattern.compile("^(.*)\\.kts?$"), Pattern.compile("^(.+)\\.fir\\.kts?$"), true);
}
@Test
@TestMetadata("incorrectCapturedApproximationForValueParameters.kt")
public void testIncorrectCapturedApproximationForValueParameters() throws Exception {
runTest("compiler/testData/diagnostics/tests/warningsForBreakingChanges/capturedTypes/incorrectCapturedApproximationForValueParameters.kt");
}
@Test
@TestMetadata("noWarningAfterSmartcast.kt")
public void testNoWarningAfterSmartcast() throws Exception {
runTest("compiler/testData/diagnostics/tests/warningsForBreakingChanges/capturedTypes/noWarningAfterSmartcast.kt");
}
@Test
@TestMetadata("noWarningOnSAMAdaption.kt")
public void testNoWarningOnSAMAdaption() throws Exception {
runTest("compiler/testData/diagnostics/tests/warningsForBreakingChanges/capturedTypes/noWarningOnSAMAdaption.kt");
}
}
}
@Nested
@TestMetadata("compiler/testData/diagnostics/tests/when")
@TestDataPath("$PROJECT_ROOT")