diff --git a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java index fe14cd537da..e78606b86df 100644 --- a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java +++ b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java @@ -807,6 +807,12 @@ public class DiagnosisCompilerTestFE10TestdataTestGenerated extends AbstractDiag runTest("compiler/testData/diagnostics/tests/syntheticSet.kt"); } + @Test + @TestMetadata("syntheticSetFalsePositive.kt") + public void testSyntheticSetFalsePositive() throws Exception { + runTest("compiler/testData/diagnostics/tests/syntheticSetFalsePositive.kt"); + } + @Test @TestMetadata("tailRecBasic.kt") public void testTailRecBasic() throws Exception { diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java index cff60a89c8e..0e9a85f9bae 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java @@ -807,6 +807,12 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirDiagnosti runTest("compiler/testData/diagnostics/tests/syntheticSet.kt"); } + @Test + @TestMetadata("syntheticSetFalsePositive.kt") + public void testSyntheticSetFalsePositive() throws Exception { + runTest("compiler/testData/diagnostics/tests/syntheticSetFalsePositive.kt"); + } + @Test @TestMetadata("tailRecBasic.kt") public void testTailRecBasic() throws Exception { diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java index 32d59052859..bf2daafe5a7 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java @@ -807,6 +807,12 @@ public class FirOldFrontendDiagnosticsWithLightTreeTestGenerated extends Abstrac runTest("compiler/testData/diagnostics/tests/syntheticSet.kt"); } + @Test + @TestMetadata("syntheticSetFalsePositive.kt") + public void testSyntheticSetFalsePositive() throws Exception { + runTest("compiler/testData/diagnostics/tests/syntheticSetFalsePositive.kt"); + } + @Test @TestMetadata("tailRecBasic.kt") public void testTailRecBasic() throws Exception { diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/JvmSyntheticAssignmentChecker.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/JvmSyntheticAssignmentChecker.kt new file mode 100644 index 00000000000..3db3d68b218 --- /dev/null +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/JvmSyntheticAssignmentChecker.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.jvm.checkers + +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor +import org.jetbrains.kotlin.psi.KtBinaryExpression +import org.jetbrains.kotlin.psi.KtNameReferenceExpression +import org.jetbrains.kotlin.psi.psiUtil.getParentOfType +import org.jetbrains.kotlin.resolve.calls.checkers.CallChecker +import org.jetbrains.kotlin.resolve.calls.checkers.CallCheckerContext +import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall +import org.jetbrains.kotlin.resolve.calls.tower.isSynthesized +import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm +import org.jetbrains.kotlin.synthetic.SyntheticJavaPropertyDescriptor +import org.jetbrains.kotlin.types.IndexedParametersSubstitution +import org.jetbrains.kotlin.types.TypeProjection +import org.jetbrains.kotlin.types.TypeSubstitutor +import org.jetbrains.kotlin.types.Variance +import org.jetbrains.kotlin.types.expressions.BasicExpressionTypingVisitor +import org.jetbrains.kotlin.types.typeUtil.isNothing + +object JvmSyntheticAssignmentChecker : CallChecker { + override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) { + val resultingDescriptor = resolvedCall.resultingDescriptor + if (!resultingDescriptor.isSynthesized) return + if (resultingDescriptor !is SyntheticJavaPropertyDescriptor) return + if (reportOn !is KtNameReferenceExpression) return + val binaryExpression = reportOn.getParentOfType(strict = true) ?: return + if (!BasicExpressionTypingVisitor.isLValue(reportOn, binaryExpression)) return + val receiverType = resolvedCall.extensionReceiver?.type ?: return + val unsubstitutedReceiverType = resolvedCall.candidateDescriptor.extensionReceiverParameter?.type ?: return + if (receiverType.constructor !== unsubstitutedReceiverType.constructor) return + val propertyType = resolvedCall.candidateDescriptor.returnType ?: return + + val substitutionParameters = mutableListOf() + val substitutionArguments = mutableListOf() + for ((unsubstitutedArgument, substitutedArgument) in unsubstitutedReceiverType.arguments.zip(receiverType.arguments)) { + val typeParameter = unsubstitutedArgument.type.constructor.declarationDescriptor as? TypeParameterDescriptor ?: continue + substitutionParameters += typeParameter + substitutionArguments += substitutedArgument + } + val substitutor = TypeSubstitutor.create( + IndexedParametersSubstitution( + substitutionParameters.toTypedArray(), substitutionArguments.toTypedArray(), approximateContravariantCapturedTypes = true + ) + ) + val substitutedPropertyType = substitutor.substitute(propertyType.unwrap(), Variance.IN_VARIANCE) + if (substitutedPropertyType == null || !substitutedPropertyType.isNothing()) return + context.trace.report(ErrorsJvm.SYNTHETIC_SETTER_PROJECTED_OUT.on(binaryExpression.left ?: reportOn, resultingDescriptor)) + } +} \ No newline at end of file diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/DefaultErrorMessagesJvm.java b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/DefaultErrorMessagesJvm.java index 314ec7f724d..8d63430c8fb 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/DefaultErrorMessagesJvm.java +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/DefaultErrorMessagesJvm.java @@ -143,6 +143,8 @@ public class DefaultErrorMessagesJvm implements DefaultErrorMessages.Extension { MAP.put(INAPPLICABLE_JVM_FIELD_WARNING, "{0}. This warning will become an error in further releases", STRING); MAP.put(JVM_SYNTHETIC_ON_DELEGATE, "'@JvmSynthetic' annotation cannot be used on delegated properties"); + MAP.put(SYNTHETIC_SETTER_PROJECTED_OUT, "Use of setter for ''{0}'' is unsafe due to the type projection in receiver." + + " Workaround: use explicit cast on receiver. See https://youtrack.jetbrains.com/issue/KT-54309", NAME); MAP.put(STRICTFP_ON_CLASS, "'@Strictfp' annotation on classes is unsupported yet"); diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/ErrorsJvm.java b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/ErrorsJvm.java index edd7eff6845..7d3e4893765 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/ErrorsJvm.java +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/ErrorsJvm.java @@ -42,6 +42,8 @@ public interface ErrorsJvm { DiagnosticFactory0 JVM_SYNTHETIC_ON_DELEGATE = DiagnosticFactory0.create(ERROR); + DiagnosticFactory1 SYNTHETIC_SETTER_PROJECTED_OUT = DiagnosticFactory1.create(WARNING); + DiagnosticFactory0 STRICTFP_ON_CLASS = DiagnosticFactory0.create(WARNING); DiagnosticFactory0 VOLATILE_ON_VALUE = DiagnosticFactory0.create(ERROR); diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/platform/JvmPlatformConfigurator.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/platform/JvmPlatformConfigurator.kt index da5adefd6ea..3681f6851a8 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/platform/JvmPlatformConfigurator.kt +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/platform/JvmPlatformConfigurator.kt @@ -62,6 +62,7 @@ object JvmPlatformConfigurator : PlatformConfiguratorBase( SamInterfaceConstructorReferenceCallChecker, EnumDeclaringClassDeprecationChecker, UpperBoundViolatedInTypealiasConstructorChecker, + JvmSyntheticAssignmentChecker, ), additionalTypeCheckers = listOf( diff --git a/compiler/testData/diagnostics/tests/syntheticSet.kt b/compiler/testData/diagnostics/tests/syntheticSet.kt index 3eb07ccd921..e849419708e 100644 --- a/compiler/testData/diagnostics/tests/syntheticSet.kt +++ b/compiler/testData/diagnostics/tests/syntheticSet.kt @@ -20,7 +20,7 @@ public class Wrapper { fun foo(container: Container<*>, wrapper: Wrapper) { container.w = wrapper - container.wrapper = wrapper + container.wrapper = wrapper container.setWrapper(wrapper) } @@ -42,16 +42,16 @@ fun dif(container: Container, wrapper: Wrapper) { } fun out(container: Container, wrapper: Wrapper) { - container.wrapper = wrapper + container.wrapper = wrapper container.setWrapper(wrapper) } fun inn(container: Container, wrapper: Wrapper) { - container.wrapper = wrapper + container.wrapper = wrapper container.setWrapper(wrapper) } fun generic(container: Container, wrapper: Wrapper) { - container.wrapper = wrapper + container.wrapper = wrapper container.setWrapper(wrapper) } diff --git a/compiler/testData/diagnostics/tests/syntheticSetFalsePositive.kt b/compiler/testData/diagnostics/tests/syntheticSetFalsePositive.kt new file mode 100644 index 00000000000..6cc329dbb7b --- /dev/null +++ b/compiler/testData/diagnostics/tests/syntheticSetFalsePositive.kt @@ -0,0 +1,15 @@ +// FIR_IDENTICAL +// FILE: JavaClass.java +import java.util.List; + +public interface JavaClass { + List getFoo(); + void setFoo(List l); +} + +// FILE: main.kt + +fun foo(x: JavaClass, l: MutableList) { + x.setFoo(l) // OK + x.foo = l // Should be OK, too +} diff --git a/compiler/testData/diagnostics/tests/syntheticSetFalsePositive.txt b/compiler/testData/diagnostics/tests/syntheticSetFalsePositive.txt new file mode 100644 index 00000000000..f3e2f3d03d6 --- /dev/null +++ b/compiler/testData/diagnostics/tests/syntheticSetFalsePositive.txt @@ -0,0 +1,11 @@ +package + +public fun foo(/*0*/ x: JavaClass, /*1*/ l: kotlin.collections.MutableList): kotlin.Unit + +public interface JavaClass { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public abstract fun getFoo(): (kotlin.collections.MutableList..kotlin.collections.List?) + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public abstract fun setFoo(/*0*/ l: (kotlin.collections.MutableList..kotlin.collections.List?)): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java index 198708d46f9..1335d7e92f4 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java @@ -807,6 +807,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest { runTest("compiler/testData/diagnostics/tests/syntheticSet.kt"); } + @Test + @TestMetadata("syntheticSetFalsePositive.kt") + public void testSyntheticSetFalsePositive() throws Exception { + runTest("compiler/testData/diagnostics/tests/syntheticSetFalsePositive.kt"); + } + @Test @TestMetadata("tailRecBasic.kt") public void testTailRecBasic() throws Exception {