diff --git a/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/enhancement/EnhancedTypeForWarningAttribute.kt b/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/enhancement/EnhancedTypeForWarningAttribute.kt index 0edcf4a51bd..20d6844ae72 100644 --- a/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/enhancement/EnhancedTypeForWarningAttribute.kt +++ b/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/enhancement/EnhancedTypeForWarningAttribute.kt @@ -8,6 +8,7 @@ package org.jetbrains.kotlin.fir.java.enhancement import org.jetbrains.kotlin.builtins.jvm.JavaToKotlinClassMap import org.jetbrains.kotlin.fir.resolve.substitution.AbstractConeSubstitutor import org.jetbrains.kotlin.fir.types.* +import org.jetbrains.kotlin.name.StandardClassIds import kotlin.reflect.KClass data class EnhancedTypeForWarningAttribute( @@ -45,8 +46,10 @@ val ConeKotlinType.isEnhancedTypeForWarningDeprecation: Boolean class EnhancedForWarningConeSubstitutor(typeContext: ConeTypeContext) : AbstractConeSubstitutor(typeContext) { override fun substituteType(type: ConeKotlinType): ConeKotlinType? { // The attribute is usually taken from the lower bound of flexible types. - // However, if the type has flexible mutability, we don't want to accidentally enhance the mutability to the lower bound. - if (type is ConeFlexibleType && type.hasFlexibleMutability()) { + // However, if the type has flexible mutability or type argument variance, + // we can't just use the lower bound, otherwise we can get false positive mismatches + // because of the mutability or the type argument variance. + if (type is ConeFlexibleType && (type.hasFlexibleMutability() || type.isArrayWithFlexibleVariance())) { val lowerSubstituted = substituteOrNull(type.lowerBound) return if (lowerSubstituted is ConeSimpleKotlinType) { @@ -67,7 +70,17 @@ class EnhancedForWarningConeSubstitutor(typeContext: ConeTypeContext) : Abstract return enhancedTopLevel?.let(::substituteOrSelf) } + /** + * `List` is represented as `MutableList..List?`. + */ private fun ConeFlexibleType.hasFlexibleMutability(): Boolean { return JavaToKotlinClassMap.isMutable(lowerBound.classId) && JavaToKotlinClassMap.isReadOnly(upperBound.classId) } + + /** + * `Object[]` is represented as `Array..Array?`. + */ + private fun ConeFlexibleType.isArrayWithFlexibleVariance(): Boolean { + return lowerBound.classId == StandardClassIds.Array && lowerBound.typeArguments.firstOrNull()?.kind != upperBound.typeArguments.firstOrNull()?.kind + } } \ No newline at end of file diff --git a/compiler/testData/diagnostics/foreignAnnotationsTests/java8Tests/jspecify/warnMode/OverrideOfAnnotated.fir.kt b/compiler/testData/diagnostics/foreignAnnotationsTests/java8Tests/jspecify/warnMode/OverrideOfAnnotated.fir.kt index 2f5176ba3c4..045c922020a 100644 --- a/compiler/testData/diagnostics/foreignAnnotationsTests/java8Tests/jspecify/warnMode/OverrideOfAnnotated.fir.kt +++ b/compiler/testData/diagnostics/foreignAnnotationsTests/java8Tests/jspecify/warnMode/OverrideOfAnnotated.fir.kt @@ -18,6 +18,8 @@ public class BaseClass { public Foo explicitlyNullnessUnspecified(@NullnessUnspecified Foo x) { return null; } + public void withVararg(Object... p) { } + public static Foo foo() { return null; } } @@ -54,6 +56,8 @@ class Correct : IntermediateClass() { override fun intermediateNotNull(): Foo { return FOO } + + override fun withVararg(vararg p: Any) {} } class WrongReturnTypes : IntermediateClass() { diff --git a/compiler/testData/diagnostics/foreignAnnotationsTests/java8Tests/jspecify/warnMode/OverrideOfAnnotated.kt b/compiler/testData/diagnostics/foreignAnnotationsTests/java8Tests/jspecify/warnMode/OverrideOfAnnotated.kt index 763b600bd71..30147b6cad6 100644 --- a/compiler/testData/diagnostics/foreignAnnotationsTests/java8Tests/jspecify/warnMode/OverrideOfAnnotated.kt +++ b/compiler/testData/diagnostics/foreignAnnotationsTests/java8Tests/jspecify/warnMode/OverrideOfAnnotated.kt @@ -18,6 +18,8 @@ public class BaseClass { public Foo explicitlyNullnessUnspecified(@NullnessUnspecified Foo x) { return null; } + public void withVararg(Object... p) { } + public static Foo foo() { return null; } } @@ -54,6 +56,8 @@ class Correct : IntermediateClass() { override fun intermediateNotNull(): Foo { return FOO } + + override fun withVararg(vararg p: Any) {} } class WrongReturnTypes : IntermediateClass() { diff --git a/compiler/testData/diagnostics/foreignAnnotationsTests/java8Tests/jspecify/warnMode/OverrideOfAnnotated.txt b/compiler/testData/diagnostics/foreignAnnotationsTests/java8Tests/jspecify/warnMode/OverrideOfAnnotated.txt index ea98baa1665..847a602f14e 100644 --- a/compiler/testData/diagnostics/foreignAnnotationsTests/java8Tests/jspecify/warnMode/OverrideOfAnnotated.txt +++ b/compiler/testData/diagnostics/foreignAnnotationsTests/java8Tests/jspecify/warnMode/OverrideOfAnnotated.txt @@ -12,6 +12,7 @@ private val FOO: FOO.`` public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int @org.jspecify.annotations.Nullable public open fun mixed(/*0*/ x: Foo!): @org.jspecify.annotations.Nullable Foo! public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + public open fun withVararg(/*0*/ vararg p: kotlin.Any! /*kotlin.Array<(out) kotlin.Any!>!*/): kotlin.Unit // Static members public open fun foo(): Foo! @@ -28,6 +29,7 @@ public final class Correct : IntermediateClass { public open override /*1*/ fun intermediateNotNull(): Foo public open override /*1*/ fun mixed(/*0*/ x: Foo): Foo? public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + public open override /*1*/ fun withVararg(/*0*/ vararg p: kotlin.Any /*kotlin.Array*/): kotlin.Unit } public interface Foo { @@ -47,6 +49,7 @@ public open class IntermediateClass : BaseClass { public open fun intermediateNotNull(): Foo! @org.jspecify.annotations.Nullable public open override /*1*/ /*fake_override*/ fun mixed(/*0*/ x: Foo!): @org.jspecify.annotations.Nullable Foo! public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + public open override /*1*/ /*fake_override*/ fun withVararg(/*0*/ vararg p: kotlin.Any! /*kotlin.Array<(out) kotlin.Any!>!*/): kotlin.Unit } public final class WrongParameter : IntermediateClass { @@ -60,6 +63,7 @@ public final class WrongParameter : IntermediateClass { public open override /*1*/ /*fake_override*/ fun intermediateNotNull(): Foo! public open override /*1*/ fun mixed(/*0*/ x: Foo?): Foo? public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + public open override /*1*/ /*fake_override*/ fun withVararg(/*0*/ vararg p: kotlin.Any! /*kotlin.Array<(out) kotlin.Any!>!*/): kotlin.Unit } public final class WrongReturnTypes : IntermediateClass { @@ -73,5 +77,6 @@ public final class WrongReturnTypes : IntermediateClass { public open override /*1*/ fun intermediateNotNull(): Foo? @org.jspecify.annotations.Nullable public open override /*1*/ /*fake_override*/ fun mixed(/*0*/ x: Foo!): @org.jspecify.annotations.Nullable Foo! public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + public open override /*1*/ /*fake_override*/ fun withVararg(/*0*/ vararg p: kotlin.Any! /*kotlin.Array<(out) kotlin.Any!>!*/): kotlin.Unit }