From 0080f16cc2d10b310e337dbdb212294b41abb401 Mon Sep 17 00:00:00 2001 From: Mikhail Glukhikh Date: Fri, 7 Oct 2022 12:54:43 +0200 Subject: [PATCH] K1: report TYPE_MISMATCH for incorrect assignments in builder #KT-54004 Fixed --- .../resolve/PlatformConfiguratorBase.kt | 3 +- .../BuilderInferenceAssignmentChecker.kt | 55 +++++++++++++++++++ .../builderInference/unsafeAssignment.fir.kt | 9 ++- .../builderInference/unsafeAssignment.kt | 13 ++--- .../builderInference/unsafeAssignment.txt | 3 +- 5 files changed, 69 insertions(+), 14 deletions(-) create mode 100644 compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/BuilderInferenceAssignmentChecker.kt diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/PlatformConfiguratorBase.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/PlatformConfiguratorBase.kt index c9476042265..9643bb027a0 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/PlatformConfiguratorBase.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/PlatformConfiguratorBase.kt @@ -69,7 +69,8 @@ private val DEFAULT_CALL_CHECKERS = listOf( UnitConversionCallChecker, FunInterfaceConstructorReferenceChecker, NullableExtensionOperatorWithSafeCallChecker, ReferencingToUnderscoreNamedParameterOfCatchBlockChecker, VarargWrongExecutionOrderChecker, SelfCallInNestedObjectConstructorChecker, NewSchemeOfIntegerOperatorResolutionChecker, EnumEntryVsCompanionPriorityCallChecker, CompanionInParenthesesLHSCallChecker, - ResolutionToPrivateConstructorOfSealedClassChecker, EqualityCallChecker, UnsupportedUntilOperatorChecker + ResolutionToPrivateConstructorOfSealedClassChecker, EqualityCallChecker, UnsupportedUntilOperatorChecker, + BuilderInferenceAssignmentChecker, ) private val DEFAULT_TYPE_CHECKERS = emptyList() private val DEFAULT_CLASSIFIER_USAGE_CHECKERS = listOf( diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/BuilderInferenceAssignmentChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/BuilderInferenceAssignmentChecker.kt new file mode 100644 index 00000000000..511b4d4641c --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/BuilderInferenceAssignmentChecker.kt @@ -0,0 +1,55 @@ +/* + * 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.descriptors.PropertyDescriptor +import org.jetbrains.kotlin.diagnostics.Errors +import org.jetbrains.kotlin.psi.KtBinaryExpression +import org.jetbrains.kotlin.psi.KtNameReferenceExpression +import org.jetbrains.kotlin.psi.psiUtil.getParentOfType +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.bindingContextUtil.getDataFlowInfoBefore +import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall +import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValue +import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory +import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactoryImpl +import org.jetbrains.kotlin.resolve.calls.smartcasts.IdentifierInfo +import org.jetbrains.kotlin.resolve.calls.util.getType +import org.jetbrains.kotlin.types.StubTypeForBuilderInference +import org.jetbrains.kotlin.types.checker.KotlinTypeChecker +import org.jetbrains.kotlin.types.expressions.BasicExpressionTypingVisitor +import org.jetbrains.kotlin.types.isError + +object BuilderInferenceAssignmentChecker : CallChecker { + override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) { + val resultingDescriptor = resolvedCall.resultingDescriptor + if (resultingDescriptor !is PropertyDescriptor) return + if (resolvedCall.candidateDescriptor.returnType !is StubTypeForBuilderInference) return + if (reportOn !is KtNameReferenceExpression) return + val binaryExpression = reportOn.getParentOfType(strict = true) ?: return + if (!BasicExpressionTypingVisitor.isLValue(reportOn, binaryExpression)) return + + val leftType = resultingDescriptor.returnType?.takeIf { !it.isError } ?: return + val right = binaryExpression.right ?: return + val rightType = right.getType(context.trace.bindingContext) ?: return + + if (!KotlinTypeChecker.DEFAULT.isSubtypeOf(rightType, leftType)) { + val dfi = context.dataFlowInfo + val dfvFactory = context.dataFlowValueFactory + val stableTypesFromDataFlow = dfi.getStableTypes( + dfvFactory.createDataFlowValue(right, rightType, context.trace.bindingContext, context.moduleDescriptor), + context.languageVersionSettings + ) + if (stableTypesFromDataFlow.none { + KotlinTypeChecker.DEFAULT.isSubtypeOf(it, leftType) + } + ) { + context.trace.report(Errors.TYPE_MISMATCH.on(right, leftType, rightType)) + } + } + } +} diff --git a/compiler/testData/diagnostics/testsWithStdLib/builderInference/unsafeAssignment.fir.kt b/compiler/testData/diagnostics/testsWithStdLib/builderInference/unsafeAssignment.fir.kt index be88ad821b5..647aa3e5274 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/builderInference/unsafeAssignment.fir.kt +++ b/compiler/testData/diagnostics/testsWithStdLib/builderInference/unsafeAssignment.fir.kt @@ -5,16 +5,15 @@ class Foo { fun myBuilder(block: Foo.() -> Unit) : Foo = Foo().apply(block) -fun main() { - myBuilder { - a = "some string" - } - +fun main(arg: Any) { val x = 57 val value = myBuilder { doSmthng("one ") a = 57 a = x + if (arg is String) { + a = arg + } } println(value.a?.count { it in 'l' .. 'q' }) } diff --git a/compiler/testData/diagnostics/testsWithStdLib/builderInference/unsafeAssignment.kt b/compiler/testData/diagnostics/testsWithStdLib/builderInference/unsafeAssignment.kt index 8e43dbee609..270a8e34c09 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/builderInference/unsafeAssignment.kt +++ b/compiler/testData/diagnostics/testsWithStdLib/builderInference/unsafeAssignment.kt @@ -5,16 +5,15 @@ class Foo { fun myBuilder(block: Foo.() -> Unit) : Foo = Foo().apply(block) -fun main() { - myBuilder { - a = "some string" - } - +fun main(arg: Any) { val x = 57 val value = myBuilder { doSmthng("one ") - a = 57 - a = x + a = 57 + a = x + if (arg is String) { + a = arg + } } println(value.a?.count { it in 'l' .. 'q' }) } diff --git a/compiler/testData/diagnostics/testsWithStdLib/builderInference/unsafeAssignment.txt b/compiler/testData/diagnostics/testsWithStdLib/builderInference/unsafeAssignment.txt index be66b279ff2..15fed745689 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/builderInference/unsafeAssignment.txt +++ b/compiler/testData/diagnostics/testsWithStdLib/builderInference/unsafeAssignment.txt @@ -1,6 +1,6 @@ package -public fun main(): kotlin.Unit +public fun main(/*0*/ arg: kotlin.Any): kotlin.Unit public fun myBuilder(/*0*/ block: Foo.() -> kotlin.Unit): Foo public final class Foo { @@ -11,3 +11,4 @@ public final class Foo { public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String } +