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 52474c73077..44095be80d5 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 @@ -5684,6 +5684,12 @@ public class DiagnosisCompilerTestFE10TestdataTestGenerated extends AbstractDiag runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/assignmentInLocalsInConstructor.kt"); } + @Test + @TestMetadata("assignmentOfSyntheticVarWithInconsistentNullability.kt") + public void testAssignmentOfSyntheticVarWithInconsistentNullability() throws Exception { + runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/assignmentOfSyntheticVarWithInconsistentNullability.kt"); + } + @Test @TestMetadata("backingFieldInsideGetter_after.kt") public void testBackingFieldInsideGetter_after() throws Exception { diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirLightTreeOldFrontendDiagnosticsTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirLightTreeOldFrontendDiagnosticsTestGenerated.java index cff0cc69053..8b7b090148b 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirLightTreeOldFrontendDiagnosticsTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirLightTreeOldFrontendDiagnosticsTestGenerated.java @@ -5684,6 +5684,12 @@ public class FirLightTreeOldFrontendDiagnosticsTestGenerated extends AbstractFir runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/assignmentInLocalsInConstructor.kt"); } + @Test + @TestMetadata("assignmentOfSyntheticVarWithInconsistentNullability.kt") + public void testAssignmentOfSyntheticVarWithInconsistentNullability() throws Exception { + runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/assignmentOfSyntheticVarWithInconsistentNullability.kt"); + } + @Test @TestMetadata("backingFieldInsideGetter_after.kt") public void testBackingFieldInsideGetter_after() throws Exception { diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirPsiOldFrontendDiagnosticsTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirPsiOldFrontendDiagnosticsTestGenerated.java index 7a23d927546..dd6a98ce7e9 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirPsiOldFrontendDiagnosticsTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirPsiOldFrontendDiagnosticsTestGenerated.java @@ -5690,6 +5690,12 @@ public class FirPsiOldFrontendDiagnosticsTestGenerated extends AbstractFirPsiDia runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/assignmentInLocalsInConstructor.kt"); } + @Test + @TestMetadata("assignmentOfSyntheticVarWithInconsistentNullability.kt") + public void testAssignmentOfSyntheticVarWithInconsistentNullability() throws Exception { + runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/assignmentOfSyntheticVarWithInconsistentNullability.kt"); + } + @Test @TestMetadata("backingFieldInsideGetter_after.kt") public void testBackingFieldInsideGetter_after() throws Exception { diff --git a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/resolve/calls/Synthetics.kt b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/resolve/calls/Synthetics.kt index 3d3fb81357e..d2740ef2705 100644 --- a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/resolve/calls/Synthetics.kt +++ b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/resolve/calls/Synthetics.kt @@ -119,8 +119,7 @@ class FirSyntheticPropertiesScope private constructor( val parameter = setter.valueParameters.singleOrNull() ?: return if (setter.typeParameters.isNotEmpty() || setter.isStatic) return val parameterType = (parameter.returnTypeRef as? FirResolvedTypeRef)?.type ?: return - - if (!setterTypeIsConsistentWithGetterType(getterSymbol, parameterType, baseScope)) return + if (!setterTypeIsConsistentWithGetterType(propertyName, getterSymbol, setterSymbol, parameterType)) return matchingSetter = setterSymbol.fir }) } @@ -161,21 +160,59 @@ class FirSyntheticPropertiesScope private constructor( } private fun setterTypeIsConsistentWithGetterType( + propertyName: Name, getterSymbol: FirNamedFunctionSymbol, - parameterType: ConeKotlinType, - scopeOfGetter: FirTypeScope + setterSymbol: FirNamedFunctionSymbol, + setterParameterType: ConeKotlinType ): Boolean { val getterReturnType = getterSymbol.resolvedReturnTypeRef.type - if (AbstractTypeChecker.equalTypes(session.typeContext, getterReturnType, parameterType)) return true - if (!AbstractTypeChecker.isSubtypeOf(session.typeContext, getterReturnType, parameterType)) return false + if (AbstractTypeChecker.equalTypes(session.typeContext, getterReturnType, setterParameterType)) return true + if (!AbstractTypeChecker.isSubtypeOf(session.typeContext, getterReturnType, setterParameterType)) return false + /* - * Here we search for some getter in overrides hierarchy which type is consistent with type of setter + * If type of setter parameter is subtype of getter return type, we need to check corresponding "overridden" synthetic + * properties from parent classes. If some of them has this setter or its overridden as base for setter, then current + * setterSymbol can be used as setter for corresponding getterSymbol + * + * See corresponding code in FE 1.0 in `SyntheticJavaPropertyDescriptor.isGoodSetMethod` + * Note that FE 1.0 looks through overrides just ones (by setter hierarchy), but FIR does twice (for setter and getter) + * This is needed because FIR does not create fake overrides for all inherited methods of class, so there may be a + * situation, when in inheritor class only getter is overridden, and setter does not have overriddens at all + * + * class Base { + * public Object getX() {...} + * public Object setX(Object x) {...} // setterSymbol + * } + * + * class Derived extends Base { + * public String getX() {...} // getterSymbol + * // public fake-override Object setX(Object x) {...} // exist in FE 1.0 but not in FIR + * } */ - for ((overriddenGetter, scope) in scopeOfGetter.getDirectOverriddenFunctionsWithBaseScope(getterSymbol)) { - val foundGoodOverride = setterTypeIsConsistentWithGetterType(overriddenGetter, parameterType, scope) - if (foundGoodOverride) return true + + fun processOverrides(symbolToStart: FirNamedFunctionSymbol, setterSymbolToCompare: FirNamedFunctionSymbol?): Boolean { + var hasMatchingSetter = false + baseScope.processDirectOverriddenFunctionsWithBaseScope(symbolToStart) l@{ symbol, scope -> + if (hasMatchingSetter) return@l ProcessorAction.STOP + val baseDispatchReceiverType = symbol.dispatchReceiverType ?: return@l ProcessorAction.NEXT + val syntheticScope = FirSyntheticPropertiesScope(session, scope, baseDispatchReceiverType, syntheticNamesProvider) + val baseProperties = syntheticScope.getProperties(propertyName) + val propertyFound = baseProperties.any { + val baseProperty = it.fir + baseProperty is FirSyntheticProperty && baseProperty.setter?.delegate?.symbol == (setterSymbolToCompare ?: symbol) + } + if (propertyFound) { + hasMatchingSetter = true + ProcessorAction.STOP + } else { + ProcessorAction.NEXT + } + } + return hasMatchingSetter } - return false + + return processOverrides(setterSymbol, setterSymbolToCompare = null) + || processOverrides(getterSymbol, setterSymbolToCompare = setterSymbol) } private fun FirNamedFunctionSymbol.hasJavaOverridden(): Boolean { diff --git a/compiler/testData/diagnostics/tests/controlFlowAnalysis/assignmentOfSyntheticVarWithInconsistentNullability.fir.kt b/compiler/testData/diagnostics/tests/controlFlowAnalysis/assignmentOfSyntheticVarWithInconsistentNullability.fir.kt new file mode 100644 index 00000000000..13933a141a6 --- /dev/null +++ b/compiler/testData/diagnostics/tests/controlFlowAnalysis/assignmentOfSyntheticVarWithInconsistentNullability.fir.kt @@ -0,0 +1,96 @@ +// ISSUE: KT-56506 +// FILE: JavaBase.java + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public interface JavaBase { + @NotNull String getFoo(); + + // Important: no parameter nullability! + void setFoo(String arg); +} + +// FILE: JavaOverride.java +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class JavaOverride implements JavaBase { + @Override + @NotNull + public String getFoo() { + return ""; + } + + // Important: parameter is nullable! + @Override + public void setFoo(@Nullable String arg) {} +} + +// FILE: NoOverride.java +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class NoOverride { + @NotNull + public String getFoo() { + return ""; + } + + // Important: parameter is nullable! + public void setFoo(@Nullable String arg) {} +} + +// FILE: KotlinBase.kt +open class KotlinBase { + val foo: String + get() = "" +} + +// FILE: KotlinOverrideBase.java +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public interface KotlinOverrideBase extends KotlinBase { + @Override + @NotNull String getFoo(); + + void setFoo(String arg); +} + +// FILE: KotlinOverride.java +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class KotlinOverride implements KotlinOverrideBase { + @Override + @NotNull + public String getFoo() { + return ""; + } + + // Important: parameter is nullable! + @Override + public void setFoo(@Nullable String arg) {} +} + +// FILE: main.kt +fun test_1(d: NoOverride, s: String) { + d.foo = s +} + +fun test_2(d: JavaBase, s: String) { + d.foo = s +} + +fun test_3(d: JavaOverride, s: String) { + d.foo = s +} + +fun test_4(d: KotlinOverrideBase, s: String) { + d.foo = s +} + +fun test_5(d: KotlinOverride, s: String) { + d.foo = s +} diff --git a/compiler/testData/diagnostics/tests/controlFlowAnalysis/assignmentOfSyntheticVarWithInconsistentNullability.kt b/compiler/testData/diagnostics/tests/controlFlowAnalysis/assignmentOfSyntheticVarWithInconsistentNullability.kt new file mode 100644 index 00000000000..5e91a34a6e9 --- /dev/null +++ b/compiler/testData/diagnostics/tests/controlFlowAnalysis/assignmentOfSyntheticVarWithInconsistentNullability.kt @@ -0,0 +1,96 @@ +// ISSUE: KT-56506 +// FILE: JavaBase.java + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public interface JavaBase { + @NotNull String getFoo(); + + // Important: no parameter nullability! + void setFoo(String arg); +} + +// FILE: JavaOverride.java +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class JavaOverride implements JavaBase { + @Override + @NotNull + public String getFoo() { + return ""; + } + + // Important: parameter is nullable! + @Override + public void setFoo(@Nullable String arg) {} +} + +// FILE: NoOverride.java +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class NoOverride { + @NotNull + public String getFoo() { + return ""; + } + + // Important: parameter is nullable! + public void setFoo(@Nullable String arg) {} +} + +// FILE: KotlinBase.kt +open class KotlinBase { + val foo: String + get() = "" +} + +// FILE: KotlinOverrideBase.java +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public interface KotlinOverrideBase extends KotlinBase { + @Override + @NotNull String getFoo(); + + void setFoo(String arg); +} + +// FILE: KotlinOverride.java +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class KotlinOverride implements KotlinOverrideBase { + @Override + @NotNull + public String getFoo() { + return ""; + } + + // Important: parameter is nullable! + @Override + public void setFoo(@Nullable String arg) {} +} + +// FILE: main.kt +fun test_1(d: NoOverride, s: String) { + d.foo = s +} + +fun test_2(d: JavaBase, s: String) { + d.foo = s +} + +fun test_3(d: JavaOverride, s: String) { + d.foo = s +} + +fun test_4(d: KotlinOverrideBase, s: String) { + d.foo = s +} + +fun test_5(d: KotlinOverride, s: String) { + d.foo = s +} 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 a0df90d3759..313b0157b85 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 @@ -5690,6 +5690,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest { runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/assignmentInLocalsInConstructor.kt"); } + @Test + @TestMetadata("assignmentOfSyntheticVarWithInconsistentNullability.kt") + public void testAssignmentOfSyntheticVarWithInconsistentNullability() throws Exception { + runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/assignmentOfSyntheticVarWithInconsistentNullability.kt"); + } + @Test @TestMetadata("backingFieldInsideGetter_after.kt") public void testBackingFieldInsideGetter_after() throws Exception {