From 7bb81ef157155e887663ee3d335165a8b695df12 Mon Sep 17 00:00:00 2001 From: Tianyu Geng Date: Fri, 2 Apr 2021 20:49:50 -0700 Subject: [PATCH] FIR: add equality call checker Added checker for FirEqualityOperatorCall. It's surfaced as one of the following diagnostics depending on the PSI structure and types under comparison: * INCOMPATIBLE_TYPES(_WARNING) * EQUALITY_NOT_APPLICABLE(_WARNING) * INCOMPATIBLE_ENUM_COMPARISON_ERROR Comparing with FE1.0, the current implementation is more conservative and only highlights error if the types are known to follow certain contracts with `equals` method. Otherwise, the checker reports warnings instead. However, the current checker is more strict in the following situations: 1. it now rejects incompatible enum types like `Enum` and `Enum`, which was previously accepted 2. it now rejects incompatible class types like `Class` and `Class`, which was previously accepted 3. the check now takes smart cast into consideration, so `if (x is String) x == 3` is now rejected --- .../resolve/lambdaPropertyTypeInference.kt | 2 +- .../testData/resolve/whenElse.kt | 8 +- ...irOldFrontendDiagnosticsTestGenerated.java | 6 + ...DiagnosticsWithLightTreeTestGenerated.java | 6 + .../diagnostics/FirDiagnosticsList.kt | 25 + .../fir/analysis/diagnostics/FirErrors.kt | 5 + .../checkers/ConeTypeCompatibilityChecker.kt | 454 ++++++++++++++++++ .../fir/analysis/checkers/FirHelpers.kt | 13 +- .../FirEqualityOperatorCallChecker.kt | 88 ++++ .../diagnostics/FirDefaultErrorMessages.kt | 27 ++ .../fir/checkers/CommonExpressionCheckers.kt | 5 + .../converter/ExpressionsConverter.kt | 2 +- .../org/jetbrains/kotlin/fir/Primitives.kt | 22 +- .../fir/declarations/FirDeclarationUtil.kt | 4 +- .../tests/BinaryCallsOnNullableValues.fir.kt | 6 +- .../IdentityComparisonWithPrimitives.fir.kt | 2 +- .../tests/classLiteral/smartCast.fir.kt | 2 +- .../controlFlowAnalysis/kt1185enums.fir.kt | 47 -- .../tests/controlFlowAnalysis/kt1185enums.kt | 3 +- .../enum/compareTwoDifferentEnums.fir.kt | 6 +- .../tests/enum/enumSubjectTypeCheck.fir.kt | 4 +- .../enum/incompatibleEnumEntryClasses.fir.kt | 30 +- .../tests/enum/incompatibleEnums.fir.kt | 68 +-- .../tests/enum/incompatibleEnums_1_4.fir.kt | 68 +-- .../tests/enum/typeCompatibility.fir.kt | 97 ++++ .../tests/enum/typeCompatibility.kt | 97 ++++ .../tests/enum/typeCompatibility.txt | 109 +++++ ...identityComparisonWithInlineClasses.fir.kt | 6 +- .../nullabilityAndSmartCasts/kt1778.fir.kt | 6 +- .../sealed/TreeWhenFunctionalNoIs.fir.kt | 2 +- .../forbiddenEqualsOnUnsignedTypes.fir.kt | 4 +- .../identityComparisonWithValueClasses.fir.kt | 4 +- .../diagnostics/tests/when/When.fir.kt | 6 +- .../test/runners/DiagnosticTestGenerated.java | 6 + .../when-expression/p-6/neg/3.1.fir.kt | 4 +- .../diagnostics/notLinked/dfa/neg/1.fir.kt | 26 +- .../diagnostics/notLinked/dfa/pos/6.fir.kt | 2 +- .../kotlin/config/LanguageVersionSettings.kt | 1 + .../diagnostics/KtFirDataClassConverters.kt | 42 ++ .../api/fir/diagnostics/KtFirDiagnostics.kt | 32 ++ .../fir/diagnostics/KtFirDiagnosticsImpl.kt | 47 ++ .../BinaryCallsOnNullableValues.fir.kt | 6 +- idea/testData/checker/When.fir.kt | 6 +- 43 files changed, 1218 insertions(+), 188 deletions(-) create mode 100644 compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/ConeTypeCompatibilityChecker.kt create mode 100644 compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirEqualityOperatorCallChecker.kt delete mode 100644 compiler/testData/diagnostics/tests/controlFlowAnalysis/kt1185enums.fir.kt create mode 100644 compiler/testData/diagnostics/tests/enum/typeCompatibility.fir.kt create mode 100644 compiler/testData/diagnostics/tests/enum/typeCompatibility.kt create mode 100644 compiler/testData/diagnostics/tests/enum/typeCompatibility.txt diff --git a/compiler/fir/analysis-tests/testData/resolve/lambdaPropertyTypeInference.kt b/compiler/fir/analysis-tests/testData/resolve/lambdaPropertyTypeInference.kt index 8bae96c5e36..ae9f0e41837 100644 --- a/compiler/fir/analysis-tests/testData/resolve/lambdaPropertyTypeInference.kt +++ b/compiler/fir/analysis-tests/testData/resolve/lambdaPropertyTypeInference.kt @@ -32,7 +32,7 @@ fun case1(javaClass: JavaClass?) { } class Case1(val javaClass: JavaClass?) { - val x = if (javaClass != null) { it -> it == javaClass } else BooCase2.FILTER + val x = if (javaClass != null) { it -> it == javaClass } else BooCase2.FILTER } class BooCase1() { diff --git a/compiler/fir/analysis-tests/testData/resolve/whenElse.kt b/compiler/fir/analysis-tests/testData/resolve/whenElse.kt index b0c7497d497..1e1b5cacf5d 100644 --- a/compiler/fir/analysis-tests/testData/resolve/whenElse.kt +++ b/compiler/fir/analysis-tests/testData/resolve/whenElse.kt @@ -45,12 +45,12 @@ fun case3() { val flag = "" //A val l1 = when (flag!!) {// should be NO_ELSE_IN_WHEN - A.A1 -> B() //should be INCOMPATIBLE_TYPES - A.A2 -> B() //should be INCOMPATIBLE_TYPES + A.A1 -> B() //should be INCOMPATIBLE_TYPES + A.A2 -> B() //should be INCOMPATIBLE_TYPES } val l2 = when (flag) {// should be NO_ELSE_IN_WHEN - A.A1 -> B() //should be INCOMPATIBLE_TYPES - A.A2 -> B() //should be INCOMPATIBLE_TYPES + A.A1 -> B() //should be INCOMPATIBLE_TYPES + A.A2 -> B() //should be INCOMPATIBLE_TYPES } } 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 55c5d230b46..09de7e75117 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 @@ -9071,6 +9071,12 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirDiagnosti runTest("compiler/testData/diagnostics/tests/enum/starImportNestedClassAndEntries.kt"); } + @Test + @TestMetadata("typeCompatibility.kt") + public void testTypeCompatibility() throws Exception { + runTest("compiler/testData/diagnostics/tests/enum/typeCompatibility.kt"); + } + @Test @TestMetadata("typeParametersInEnum.kt") public void testTypeParametersInEnum() 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 c7e12a3c167..438f08b5971 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 @@ -9071,6 +9071,12 @@ public class FirOldFrontendDiagnosticsWithLightTreeTestGenerated extends Abstrac runTest("compiler/testData/diagnostics/tests/enum/starImportNestedClassAndEntries.kt"); } + @Test + @TestMetadata("typeCompatibility.kt") + public void testTypeCompatibility() throws Exception { + runTest("compiler/testData/diagnostics/tests/enum/typeCompatibility.kt"); + } + @Test @TestMetadata("typeParametersInEnum.kt") public void testTypeParametersInEnum() throws Exception { diff --git a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt index b8918c20cae..30f84b407a5 100644 --- a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt +++ b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt @@ -360,6 +360,16 @@ object DIAGNOSTICS_LIST : DiagnosticList() { val MISPLACED_TYPE_PARAMETER_CONSTRAINTS by warning() val DYNAMIC_UPPER_BOUND by error() + + val INCOMPATIBLE_TYPES by error { + parameter("typeA") + parameter("typeB") + } + + val INCOMPATIBLE_TYPES_WARNING by warning { + parameter("typeA") + parameter("typeB") + } } val REFLECTION by object : DiagnosticGroup("Reflection") { @@ -721,6 +731,21 @@ object DIAGNOSTICS_LIST : DiagnosticList() { val UNDERSCORE_IS_RESERVED by error(PositioningStrategy.RESERVED_UNDERSCORE) val UNDERSCORE_USAGE_WITHOUT_BACKTICKS by error(PositioningStrategy.RESERVED_UNDERSCORE) + + val EQUALITY_NOT_APPLICABLE by error { + parameter("operator") + parameter("leftType") + parameter("rightType") + } + val EQUALITY_NOT_APPLICABLE_WARNING by warning { + parameter("operator") + parameter("leftType") + parameter("rightType") + } + val INCOMPATIBLE_ENUM_COMPARISON_ERROR by error { + parameter("leftType") + parameter("rightType") + } } val TYPE_ALIAS by object : DiagnosticGroup("Type alias") { diff --git a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt index 19802569ed8..0e98d6ba236 100644 --- a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt +++ b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt @@ -259,6 +259,8 @@ object FirErrors { val DEPRECATED_TYPE_PARAMETER_SYNTAX by error0(SourceElementPositioningStrategies.TYPE_PARAMETERS_LIST) val MISPLACED_TYPE_PARAMETER_CONSTRAINTS by warning0() val DYNAMIC_UPPER_BOUND by error0() + val INCOMPATIBLE_TYPES by error2() + val INCOMPATIBLE_TYPES_WARNING by warning2() // Reflection val EXTENSION_IN_CLASS_REFERENCE_NOT_ALLOWED by error1>(SourceElementPositioningStrategies.REFERENCE_BY_QUALIFIED) @@ -423,6 +425,9 @@ object FirErrors { val DELEGATE_SPECIAL_FUNCTION_RETURN_TYPE_MISMATCH by error3() val UNDERSCORE_IS_RESERVED by error0(SourceElementPositioningStrategies.RESERVED_UNDERSCORE) val UNDERSCORE_USAGE_WITHOUT_BACKTICKS by error0(SourceElementPositioningStrategies.RESERVED_UNDERSCORE) + val EQUALITY_NOT_APPLICABLE by error3() + val EQUALITY_NOT_APPLICABLE_WARNING by warning3() + val INCOMPATIBLE_ENUM_COMPARISON_ERROR by error2() // Type alias val TOPLEVEL_TYPEALIASES_ONLY by error0() diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/ConeTypeCompatibilityChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/ConeTypeCompatibilityChecker.kt new file mode 100644 index 00000000000..16a259edff0 --- /dev/null +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/ConeTypeCompatibilityChecker.kt @@ -0,0 +1,454 @@ +/* + * Copyright 2010-2021 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.fir.analysis.checkers + +import org.jetbrains.kotlin.config.LanguageFeature +import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.fir.declarations.* +import org.jetbrains.kotlin.fir.isPrimitiveType +import org.jetbrains.kotlin.fir.languageVersionSettings +import org.jetbrains.kotlin.fir.resolve.calls.fullyExpandedClass +import org.jetbrains.kotlin.fir.resolve.getSymbolByLookupTag +import org.jetbrains.kotlin.fir.symbols.ConeClassLikeLookupTag +import org.jetbrains.kotlin.fir.symbols.ConeTypeParameterLookupTag +import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol +import org.jetbrains.kotlin.fir.types.* +import org.jetbrains.kotlin.name.StandardClassIds +import org.jetbrains.kotlin.types.Variance + +/** + * Checks if a given collection of [ConeKotlinType] are compatible. In other words, the types are compatible if it's possible at all to + * define a type that's a subtype of all of the given types. The compatibility of a given set of types concept is closely related to whether + * the intersection of these types is inhabited. But it's not identical because 1) one can manually control visibility of constructors and + * 2) there can be unused type parameters. + * + * The compatibility check is done recursively on the given types and all type arguments passed to each corresponding type parameters. For + * example, consider the following two types: + * + * ``` + * - ArrayList> + * - List> + * ``` + * + * The checker first checks the base types `ArrayList` and `List`, and it sees no issue since `ArrayList <: List`. Next it checks the type + * parameters bound to these base types: `T1` in `ArrayList` and `T2` in `List`. For `T1`, there is only one bound type argument + * `Set`, so it's good. For `T2`, there are two bound type arguments: `Set` and `HashSet`. Now the checker recursively + * checks whether these two types are compatible. Again, it first checks the base type `Set` and `HashSet`, and it finds no problem since + * `HashSet <: Set`. Finally, checks the type arguments `String` and `Int` that are bound to `T` in `Set`. They are incompatible since + * `String` and `Int` are unrelated classes. + * + * The above example only goes over covariant type arguments. For contravariant types, the checker simply checks whether the range formed by + * covariant and contravariant bounds is empty. For example, a range like `[Collection, List]` is empty and hence invalid because `List` is + * not a super class/interface of `Collection` + */ +internal object ConeTypeCompatibilityChecker { + + /** + * The result returned by [ConeTypeCompatibilityChecker]. Note the order of enum entries matters. + */ + enum class Compatibility : Comparable { + /** The given types are fully compatible. */ + COMPATIBLE, + + /** The given types may not be compatible. But the compiler would allow such comparisons. */ + SOFT_INCOMPATIBLE, + + /** + * The given types are definitely incompatible. If the established contracts of Kotlin code are respected, values of the given + * types can never be considered equal. + */ + HARD_INCOMPATIBLE, + } + + fun Collection.areCompatible(ctx: ConeInferenceContext): Compatibility { + // If all types are nullable, then `null` makes the given types compatible. + if (all { with(ctx) { it.isNullableType() } }) return Compatibility.COMPATIBLE + + // Next can simply focus on the type hierarchy and don't need to worry about nullability. + val compatibilityUpperBound = if (this.all { it.isConcreteType() }) { + Compatibility.HARD_INCOMPATIBLE + } else { + // If any type is not concrete, for example, type parameter, we only report warning for incompatible types. This is to stay + // compatible with FE1.0. + Compatibility.SOFT_INCOMPATIBLE + } + return ctx.areCompatible(flatMap { it.collectUpperBounds() }.toSet(), emptySet(), compatibilityUpperBound) + } + + private fun ConeKotlinType.isConcreteType(): Boolean { + return when (this) { + is ConeClassLikeType -> true + is ConeDefinitelyNotNullType -> original.isConcreteType() + is ConeIntersectionType -> intersectedTypes.all { it.isConcreteType() } + else -> false + } + } + + /** + * @param compatibilityUpperBound the max compatibility result that can be returned by this method. For example, if this is set to + * [Compatibility.SOFT_INCOMPATIBLE], then even if the given bounds don't match the hard way (for example, incompatible primitives) the + * method should still return [Compatibility.SOFT_INCOMPATIBLE]. This is useful for checking type parameters since we don't want to + * dictate what semantics a type parameter may have in user code. In other words, if user wants to compare `MyCustom` with + * `MyCustom`, we let them do so since we do not know what class `MyCustom` uses the type parameter for. Empty containers are + * another example: `emptyList() == emptyList()`. + */ + private fun ConeInferenceContext.areCompatible( + upperBounds: Set, + lowerBounds: Set, + compatibilityUpperBound: Compatibility + ): Compatibility { + val upperBoundClasses: Set = upperBounds.mapNotNull { it.toFirClassWithSuperClasses(this) }.toSet() + + // Following if condition is an optimization: if we ignore the subtyping relation and treat all upper bounds as unrelated + // classes/interfaces, yet the types are deemed compatible for sure, then we just bail out early. + if (lowerBounds.isEmpty() && + (upperBounds.size < 2 || + this.areClassesOrInterfacesCompatible(upperBoundClasses, compatibilityUpperBound) == Compatibility.COMPATIBLE) + ) { + return Compatibility.COMPATIBLE + } + + val leafClassesOrInterfaces = computeLeafClassesOrInterfaces(upperBoundClasses) + this.areClassesOrInterfacesCompatible(leafClassesOrInterfaces, compatibilityUpperBound)?.let { return it } + + // Check if the range formed by upper bounds and lower bounds is empty. + if (!lowerBounds.all { lowerBoundType -> + val classesSatisfyingLowerBounds = + lowerBoundType.toFirClassWithSuperClasses(this)?.thisAndAllSuperClasses ?: emptySet() + leafClassesOrInterfaces.all { it in classesSatisfyingLowerBounds } + } + ) { + return compatibilityUpperBound + } + + if (upperBounds.size < 2) return Compatibility.COMPATIBLE + + // Base types are compatible. Now we check type parameters. + + val typeArgumentMapping = mutableMapOf().apply { + for (type in upperBounds) { + collectTypeArgumentMapping(type, this@areCompatible, compatibilityUpperBound) + } + } + var result = Compatibility.COMPATIBLE + val typeArgsCompatibility = typeArgumentMapping.values.asSequence() + .map { (upper, lower, compatibilityUpperBound) -> areCompatible(upper, lower, compatibilityUpperBound) } + for (compatibility in typeArgsCompatibility) { + if (compatibility == compatibilityUpperBound) return compatibility + if (compatibility > result) { + result = compatibility + } + } + return result + } + + /** + * Puts the upper bound classes into the class hierarchy and count hows many subclasses are there for each encountered class. Then + * output a list of leaf classes or interfaces in the class hierarchy. + */ + private fun computeLeafClassesOrInterfaces(upperBoundClasses: Set): Set { + val isLeaf = mutableMapOf() + upperBoundClasses.associateWithTo(isLeaf) { true } // implementation of keysToMap actually ends up creating 2 maps so this is better + val queue = ArrayDeque(upperBoundClasses) + while (queue.isNotEmpty()) { + for (superClass in queue.removeFirst().superClasses) { + when (isLeaf[superClass]) { + true -> isLeaf[superClass] = false + false -> { + // nothing to be done since this super class has already been handled. + } + else -> { + isLeaf[superClass] = false + queue.addLast(superClass) + } + } + } + } + return isLeaf.filterValues { it }.keys + } + + /** + * Checks whether the given classes are compatible. In other words, check if it's possible for objects of the given classes to be + * considered equal by [Any.equals]. + * + * @return null if this check is inconclusive + */ + private fun ConeInferenceContext.areClassesOrInterfacesCompatible( + classesOrInterfaces: Collection, + compatibilityUpperBound: Compatibility + ): Compatibility? { + val classes = classesOrInterfaces.filter { !it.isInterface } + // Java force single inheritance, so any pair of unrelated classes are incompatible. + if (classes.size >= 2) { + return if (classes.any { it.getHasPredefinedEqualityContract(this) }) { + compatibilityUpperBound + } else { + Compatibility.SOFT_INCOMPATIBLE + } + } + val finalClass = classes.firstOrNull { it.isFinal } ?: return null + // One final class and some other unrelated interface are not compatible + if (classesOrInterfaces.size > classes.size) { + return if (finalClass.getHasPredefinedEqualityContract(this)) { + compatibilityUpperBound + } else { + Compatibility.SOFT_INCOMPATIBLE + } + } + return null + } + + /** + * Collects the upper bounds as [ConeClassLikeType]. + */ + private fun ConeKotlinType?.collectUpperBounds(): Set { + if (this == null) return emptySet() + return when (this) { + is ConeClassErrorType -> emptySet() // Ignore error types + is ConeLookupTagBasedType -> when (this) { + is ConeClassLikeType -> setOf(this) + is ConeTypeVariableType -> when (val tag = lookupTag) { + is ConeTypeVariableTypeConstructor -> (tag.originalTypeParameter as? ConeTypeParameterLookupTag)?.typeParameterSymbol.collectUpperBounds() + else -> throw IllegalStateException("missing branch for ${lookupTag.javaClass.name}") + } + is ConeTypeParameterType -> lookupTag.typeParameterSymbol.collectUpperBounds() + else -> throw IllegalStateException("missing branch for ${javaClass.name}") + } + is ConeDefinitelyNotNullType -> original.collectUpperBounds() + is ConeIntersectionType -> intersectedTypes.flatMap { it.collectUpperBounds() }.toSet() + is ConeFlexibleType -> upperBound.collectUpperBounds() + is ConeCapturedType, is ConeStubType, is ConeIntegerLiteralType -> throw IllegalStateException("$this should not reach here") + } + } + + private fun FirTypeParameterSymbol?.collectUpperBounds(): Set { + if (this == null) return emptySet() + return fir.bounds.flatMap { it.coneTypeSafe().collectUpperBounds() }.toSet() + } + + private fun ConeKotlinType?.collectLowerBounds(): Set { + if (this == null) return emptySet() + return when (this) { + is ConeClassErrorType -> emptySet() // Ignore error types + is ConeLookupTagBasedType -> when (this) { + is ConeClassLikeType -> setOf(this) + is ConeTypeVariableType -> emptySet() + is ConeTypeParameterType -> emptySet() + else -> throw IllegalStateException("missing branch for ${javaClass.name}") + } + is ConeDefinitelyNotNullType -> original.collectLowerBounds() + is ConeIntersectionType -> intersectedTypes.flatMap { it.collectLowerBounds() }.toSet() + is ConeFlexibleType -> lowerBound.collectLowerBounds() + is ConeCapturedType, is ConeStubType, is ConeIntegerLiteralType -> throw IllegalStateException("$this should not reach here") + } + } + + /** + * For each type parameters appeared in the class hierarchy, collect all type arguments that eventually mapped to it. For example, + * given type `List`, the returned map contains + * + * - type parameter of `List` -> upper:[`String`], lower:[] + * - type parameter of `Collection` -> upper:[`String`], lower:[] + * - type parameter of `Iterable` -> upper:[`String`], lower:[] + * + * If later `Collection` is passed to this method with the same receiver map, the receiver map would become: + * + * - type parameter of `List` -> upper:[`String`], lower:[] + * - type parameter of `Collection` -> upper:[`String`, `Int`], lower:[] + * - type parameter of `Iterable` -> upper:[`String`, `Int`], lower:[] + */ + private fun MutableMap.collectTypeArgumentMapping( + coneType: ConeClassLikeType, + ctx: ConeInferenceContext, + compatibilityUpperBound: Compatibility + ) { + val queue = ArrayDeque() + queue.addLast(coneType.toTypeArgumentMapping(ctx) ?: return) + while (queue.isNotEmpty()) { + val (typeParameterOwner, mapping) = queue.removeFirst() + val superTypes = typeParameterOwner.getSuperTypes() + for (superType in superTypes) { + queue.addLast(superType.toTypeArgumentMapping(ctx, mapping) ?: continue) + } + for ((firTypeParameterRef, boundTypeArgument) in mapping) { + this.collect(ctx, typeParameterOwner, firTypeParameterRef, boundTypeArgument, compatibilityUpperBound) + } + } + } + + /** Converts type arguments in a [ConeClassLikeType] to a [TypeArgumentMapping]. */ + @OptIn(ExperimentalStdlibApi::class) + private fun ConeClassLikeType.toTypeArgumentMapping( + ctx: ConeInferenceContext, + envMapping: Map = emptyMap(), + ): TypeArgumentMapping? { + val typeParameterOwner = getClassLikeElement(ctx) ?: return null + val mapping = buildMap { + typeArguments.forEachIndexed { index, coneTypeProjection -> + val typeParameter: FirTypeParameterRef = typeParameterOwner.getTypeParameter(index) ?: return@forEachIndexed + var boundTypeArgument: BoundTypeArgument = when (coneTypeProjection) { + // Ignore star since it doesn't provide any constraints. + ConeStarProjection -> return@forEachIndexed + // Ignore contravariant projection because they induces union types. Hence, whatever type argument should always be + // considered compatible. + is ConeKotlinTypeProjectionIn -> BoundTypeArgument(coneTypeProjection.type, Variance.IN_VARIANCE) + is ConeKotlinTypeProjectionOut -> BoundTypeArgument(coneTypeProjection.type, Variance.OUT_VARIANCE) + is ConeKotlinType -> + when ((typeParameter as? FirTypeParameter)?.variance) { + Variance.IN_VARIANCE -> BoundTypeArgument(coneTypeProjection.type, Variance.IN_VARIANCE) + Variance.OUT_VARIANCE -> BoundTypeArgument(coneTypeProjection.type, Variance.OUT_VARIANCE) + else -> BoundTypeArgument(coneTypeProjection.type, Variance.INVARIANT) + } + } + val coneKotlinType = boundTypeArgument.type + if (coneKotlinType is ConeTypeParameterType) { + val envTypeParameter = coneKotlinType.lookupTag.typeParameterSymbol.fir + val envTypeArgument = envMapping[envTypeParameter] + if (envTypeArgument != null) { + boundTypeArgument = envTypeArgument + } + } + put(typeParameter, boundTypeArgument) + } + } + return TypeArgumentMapping(typeParameterOwner, mapping) + } + + private fun MutableMap.collect( + ctx: ConeInferenceContext, + typeParameterOwner: FirClassLikeDeclaration<*>, + parameter: FirTypeParameterRef, + boundTypeArgument: BoundTypeArgument, + compatibilityUpperBound: Compatibility, + ) { + computeIfAbsent(parameter) { + // the semantic of type parameter in Enum and KClass are fixed: values of types with incompatible type parameters are always + // incompatible. + val compatibilityUpperBoundForTypeArg = + if ((ctx.prohibitComparisonOfIncompatibleEnums && typeParameterOwner.symbol.classId == StandardClassIds.Enum) || + (ctx.prohibitComparisonOfIncompatibleClasses && typeParameterOwner.symbol.classId == StandardClassIds.KClass) + ) { + compatibilityUpperBound + } else { + Compatibility.SOFT_INCOMPATIBLE + } + BoundTypeArguments(mutableSetOf(), mutableSetOf(), compatibilityUpperBoundForTypeArg) + }.let { + val type = boundTypeArgument.type + if (boundTypeArgument.variance.allowsInPosition) { + it.lower += type.collectLowerBounds() + } + if (boundTypeArgument.variance.allowsOutPosition) { + it.upper += type.collectUpperBounds() + } + } + } + + private fun FirClassLikeDeclaration<*>.getSuperTypes(): List { + return when (this) { + is FirTypeAlias -> listOfNotNull(expandedTypeRef.coneTypeSafe()) + is FirClass<*> -> superTypeRefs.mapNotNull { it.coneTypeSafe() } + else -> emptyList() + } + } + + private fun ConeClassLikeType.getClassLikeElement(ctx: ConeInferenceContext): FirClassLikeDeclaration<*>? = + ctx.symbolProvider.getSymbolByLookupTag(lookupTag)?.fir + + private fun FirClassLikeDeclaration<*>.getTypeParameter(index: Int): FirTypeParameterRef? { + return when (this) { + is FirTypeAlias -> typeParameters[index] + is FirClass<*> -> typeParameters[index] + else -> return null + } + } + + /** A class declaration and the arguments bound to the declared type parameters. */ + private data class TypeArgumentMapping( + val typeParameterOwner: FirClassLikeDeclaration<*>, + val mapping: Map + ) + + /** A single bound type argument to a type parameter declared in a class. */ + private data class BoundTypeArgument(val type: ConeKotlinType, val variance: Variance) + + /** Accumulated type arguments bound to a type parameter declared in a class. */ + private data class BoundTypeArguments( + val upper: MutableSet, + val lower: MutableSet, + val compatibilityUpperBound: Compatibility + ) + + private fun ConeClassLikeType.toFirClassWithSuperClasses(ctx: ConeInferenceContext): FirClassWithSuperClasses? { + return lookupTag.toFirClassWithSuperClasses(ctx) + } + + private fun ConeClassLikeLookupTag.toFirClassWithSuperClasses( + ctx: ConeInferenceContext + ): FirClassWithSuperClasses? = when (val klass = ctx.symbolProvider.getSymbolByLookupTag(this)?.fir) { + is FirTypeAlias -> klass.fullyExpandedClass(ctx.session)?.let { FirClassWithSuperClasses(it, ctx) } + is FirClass<*> -> FirClassWithSuperClasses(klass, ctx) + else -> null + } + + private data class FirClassWithSuperClasses(val firClass: FirClass<*>, val ctx: ConeInferenceContext) { + val isInterface: Boolean get() = firClass.isInterface + + val superClasses: Set by lazy { + firClass.superConeTypes.mapNotNull { it.lookupTag.toFirClassWithSuperClasses(ctx) }.toSet() + } + + @OptIn(ExperimentalStdlibApi::class) + val thisAndAllSuperClasses: Set by lazy { + val queue = ArrayDeque() + queue.addLast(this) + buildSet { + add(this@FirClassWithSuperClasses) + while (queue.isNotEmpty()) { + val current = queue.removeFirst() + val superTypes = current.superClasses + superTypes.filterNotTo(queue) { it in this@buildSet } + addAll(superTypes) + } + } + } + + val isFinal: Boolean get() = firClass.isFinal + + /** + * The following are considered to have a predefined equality contract: + * - enums + * - primitives (including unsigned integer types) + * - classes + * - strings + * - objects of data classes + * - objects of inline classes + * - kotlin.Unit + */ + fun getHasPredefinedEqualityContract(ctx: ConeInferenceContext): Boolean { + return (ctx.prohibitComparisonOfIncompatibleEnums && (firClass.isEnumClass || firClass.classId == StandardClassIds.Enum)) || + firClass.isPrimitiveType() || + (ctx.prohibitComparisonOfIncompatibleClasses && firClass.classId == StandardClassIds.KClass) || + firClass.classId == StandardClassIds.String || firClass.classId == StandardClassIds.Unit || + (firClass is FirRegularClass && (firClass.isData || firClass.isInline)) + } + + private val FirClass<*>.isFinal: Boolean + get() { + return when (this) { + is FirAnonymousObject -> true + is FirRegularClass -> status.modality == Modality.FINAL + else -> throw java.lang.IllegalStateException("unknown type of FirClass $this") + } + } + } + + private val ConeInferenceContext.prohibitComparisonOfIncompatibleEnums: Boolean + get() = session.languageVersionSettings.supportsFeature(LanguageFeature.ProhibitComparisonOfIncompatibleEnums) + + private val ConeInferenceContext.prohibitComparisonOfIncompatibleClasses: Boolean + get() = session.languageVersionSettings.supportsFeature(LanguageFeature.ProhibitComparisonOfIncompatibleClasses) +} diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirHelpers.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirHelpers.kt index 33bf2f80150..7ec23ed675d 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirHelpers.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirHelpers.kt @@ -9,12 +9,14 @@ import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.descriptors.Visibilities import org.jetbrains.kotlin.descriptors.Visibility -import org.jetbrains.kotlin.fir.* +import org.jetbrains.kotlin.fir.FirSession +import org.jetbrains.kotlin.fir.FirSymbolOwner import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext import org.jetbrains.kotlin.fir.analysis.diagnostics.modalityModifier import org.jetbrains.kotlin.fir.analysis.diagnostics.overrideModifier import org.jetbrains.kotlin.fir.analysis.diagnostics.visibilityModifier import org.jetbrains.kotlin.fir.analysis.getChild +import org.jetbrains.kotlin.fir.containingClass import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.expressions.FirComponentCall import org.jetbrains.kotlin.fir.expressions.FirExpression @@ -34,6 +36,7 @@ import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol +import org.jetbrains.kotlin.fir.typeContext import org.jetbrains.kotlin.fir.types.* import org.jetbrains.kotlin.lexer.KtModifierKeywordToken import org.jetbrains.kotlin.lexer.KtTokens @@ -88,6 +91,14 @@ fun FirClass<*>.isSupertypeOf(other: FirClass<*>, session: FirSession): Boolean return isSupertypeOf(other, mutableSetOf()) } +/** + * Returns the FirClass associated with this + * or null of something goes wrong. + */ +fun ConeClassLikeType.toClass(session: FirSession): FirClass<*>? { + return lookupTag.toSymbol(session).safeAs>()?.fir +} + /** * Returns the FirRegularClass associated with this * or null of something goes wrong. diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirEqualityOperatorCallChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirEqualityOperatorCallChecker.kt new file mode 100644 index 00000000000..a4103e3226e --- /dev/null +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirEqualityOperatorCallChecker.kt @@ -0,0 +1,88 @@ +/* + * Copyright 2010-2021 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.fir.analysis.checkers.expression + +import org.jetbrains.kotlin.fir.FirRealSourceElementKind +import org.jetbrains.kotlin.fir.analysis.checkers.ConeTypeCompatibilityChecker +import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext +import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors +import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn +import org.jetbrains.kotlin.fir.declarations.isEnumClass +import org.jetbrains.kotlin.fir.expressions.FirEqualityOperatorCall +import org.jetbrains.kotlin.fir.resolve.inference.inferenceComponents +import org.jetbrains.kotlin.fir.resolve.toFirRegularClass +import org.jetbrains.kotlin.fir.types.* +import org.jetbrains.kotlin.fir.analysis.checkers.ConeTypeCompatibilityChecker.areCompatible + +object FirEqualityCompatibilityChecker : FirEqualityOperatorCallChecker() { + override fun check(expression: FirEqualityOperatorCall, context: CheckerContext, reporter: DiagnosticReporter) { + val arguments = expression.argumentList.arguments + if (arguments.size != 2) return + val lType = arguments[0].typeRef.coneType + val rType = arguments[1].typeRef.coneType + // If one of the type is already `Nothing?`, we skip reporting further comparison. This is to allow comparing with `null`, which has + // type `Nothing?` + if (lType.isNullableNothing || rType.isNullableNothing) return + val inferenceContext = context.session.inferenceComponents.ctx + val intersectionType = inferenceContext.intersectTypesOrNull(listOf(lType, rType)) as? ConeIntersectionType ?: return + + val compatibility = intersectionType.intersectedTypes.areCompatible(inferenceContext) + if (compatibility != ConeTypeCompatibilityChecker.Compatibility.COMPATIBLE) { + when (expression.source?.kind) { + FirRealSourceElementKind -> { + // Note: FE1.0 reports INCOMPATIBLE_ENUM_COMPARISON_ERROR only when TypeIntersector.isIntersectionEmpty() thinks the + // given types are compatible. Exactly mimicking the behavior of FE1.0 is difficult and does not seem to provide any + // value. So instead, we deterministically output INCOMPATIBLE_ENUM_COMPARISON_ERROR if at least one of the value is an + // enum. + if (compatibility == ConeTypeCompatibilityChecker.Compatibility.HARD_INCOMPATIBLE && + (lType.isEnumType(context) || rType.isEnumType(context)) + ) { + reporter.reportOn( + expression.source, + FirErrors.INCOMPATIBLE_ENUM_COMPARISON_ERROR, + lType, + rType, + context + ) + } else { + reporter.reportOn( + expression.source, + if (compatibility == ConeTypeCompatibilityChecker.Compatibility.HARD_INCOMPATIBLE) { + FirErrors.EQUALITY_NOT_APPLICABLE + } else { + FirErrors.EQUALITY_NOT_APPLICABLE_WARNING + }, + expression.operation.operator, + lType, + rType, + context + ) + } + } + else -> reporter.reportOn( + expression.source, + if (compatibility == ConeTypeCompatibilityChecker.Compatibility.HARD_INCOMPATIBLE) { + FirErrors.INCOMPATIBLE_TYPES + } else { + FirErrors.INCOMPATIBLE_TYPES_WARNING + }, + lType, + rType, + context + ) + } + } + } + + private fun ConeKotlinType.isEnumType( + context: CheckerContext + ): Boolean { + if (isEnum) return true + val firRegularClass = (this as? ConeClassLikeType)?.lookupTag?.toFirRegularClass(context.session) ?: return false + return firRegularClass.isEnumClass + } +} \ No newline at end of file diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt index 61b3c6affed..1550b33aefa 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt @@ -91,6 +91,8 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.DESERIALIZATION_E import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.DYNAMIC_UPPER_BOUND import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EMPTY_RANGE import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ENUM_AS_SUPERTYPE +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EQUALITY_NOT_APPLICABLE +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EQUALITY_NOT_APPLICABLE_WARNING import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ERROR_FROM_JAVA_RESOLUTION import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ERROR_IN_CONTRACT_DESCRIPTION import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXPECTED_DECLARATION_WITH_BODY @@ -129,7 +131,10 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ILLEGAL_UNDERSCOR import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INAPPLICABLE_CANDIDATE import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INAPPLICABLE_INFIX_MODIFIER import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INAPPLICABLE_LATEINIT_MODIFIER +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INCOMPATIBLE_ENUM_COMPARISON_ERROR import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INCOMPATIBLE_MODIFIERS +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INCOMPATIBLE_TYPES +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INCOMPATIBLE_TYPES_WARNING import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INFERENCE_ERROR import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INITIALIZER_REQUIRED_FOR_DESTRUCTURING_DECLARATION import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INITIALIZER_TYPE_MISMATCH @@ -521,6 +526,8 @@ class FirDefaultErrorMessages : DefaultErrorMessages.Extension { RENDER_TYPE ) map.put(UPPER_BOUND_IS_EXTENSION_FUNCTION_TYPE, "Extension function type can not be used as an upper bound") + map.put(INCOMPATIBLE_TYPES, "Incompatible types: {0} and {1}", RENDER_TYPE, RENDER_TYPE) + map.put(INCOMPATIBLE_TYPES_WARNING, "Potentially incompatible types: {0} and {1}", RENDER_TYPE, RENDER_TYPE) map.put( BOUNDS_NOT_ALLOWED_IF_BOUNDED_BY_TYPE_PARAMETER, @@ -927,6 +934,26 @@ class FirDefaultErrorMessages : DefaultErrorMessages.Extension { RENDER_TYPE, RENDER_TYPE, ) + map.put( + EQUALITY_NOT_APPLICABLE, + "Operator ''{0}'' cannot be applied to ''{1}'' and ''{2}''", + TO_STRING, + RENDER_TYPE, + RENDER_TYPE + ) + map.put( + EQUALITY_NOT_APPLICABLE_WARNING, + "Comparing with ''{0}'' may not be intended because ''{1}'' and ''{2}'' are incompatible types", + TO_STRING, + RENDER_TYPE, + RENDER_TYPE + ) + map.put( + INCOMPATIBLE_ENUM_COMPARISON_ERROR, + "Comparison of incompatible enums ''{0}'' and ''{1}'' is always unsuccessful", + RENDER_TYPE, + RENDER_TYPE + ) // Type alias map.put(TOPLEVEL_TYPEALIASES_ONLY, "Nested and local type aliases are not supported") diff --git a/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/checkers/CommonExpressionCheckers.kt b/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/checkers/CommonExpressionCheckers.kt index f785222e120..581bfb529f0 100644 --- a/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/checkers/CommonExpressionCheckers.kt +++ b/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/checkers/CommonExpressionCheckers.kt @@ -105,4 +105,9 @@ object CommonExpressionCheckers : ExpressionCheckers() { get() = setOf( FirStandaloneQualifierChecker, ) + + override val equalityOperatorCallCheckers: Set + get() = setOf( + FirEqualityCompatibilityChecker, + ) } diff --git a/compiler/fir/raw-fir/light-tree2fir/src/org/jetbrains/kotlin/fir/lightTree/converter/ExpressionsConverter.kt b/compiler/fir/raw-fir/light-tree2fir/src/org/jetbrains/kotlin/fir/lightTree/converter/ExpressionsConverter.kt index 0ead6a01fcd..1e817828606 100644 --- a/compiler/fir/raw-fir/light-tree2fir/src/org/jetbrains/kotlin/fir/lightTree/converter/ExpressionsConverter.kt +++ b/compiler/fir/raw-fir/light-tree2fir/src/org/jetbrains/kotlin/fir/lightTree/converter/ExpressionsConverter.kt @@ -735,7 +735,7 @@ class ExpressionsConverter( } return if (whenRefWithSubject != null) { buildEqualityOperatorCall { - source = whenCondition.toFirSourceElement() + source = whenCondition.toFirSourceElement(FirFakeSourceElementKind.WhenCondition) operation = FirOperation.EQ argumentList = buildBinaryArgumentList( buildWhenSubjectExpression { diff --git a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/Primitives.kt b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/Primitives.kt index 72d7ec097d3..6d8f586b3ce 100644 --- a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/Primitives.kt +++ b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/Primitives.kt @@ -5,11 +5,13 @@ package org.jetbrains.kotlin.fir -import org.jetbrains.kotlin.name.StandardClassIds +import org.jetbrains.kotlin.fir.declarations.FirClass +import org.jetbrains.kotlin.fir.declarations.classId import org.jetbrains.kotlin.fir.symbols.impl.ConeClassLikeLookupTagImpl import org.jetbrains.kotlin.fir.types.ConeClassLikeType import org.jetbrains.kotlin.fir.types.impl.ConeClassLikeTypeImpl import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.StandardClassIds object PrimitiveTypes { val Boolean: ConeClassLikeType = StandardClassIds.Boolean.createType() @@ -34,11 +36,27 @@ fun ConeClassLikeType.isByte(): Boolean = lookupTag.classId == StandardClassIds. fun ConeClassLikeType.isBoolean(): Boolean = lookupTag.classId == StandardClassIds.Boolean fun ConeClassLikeType.isChar(): Boolean = lookupTag.classId == StandardClassIds.Char -fun ConeClassLikeType.isPrimitiveType(): Boolean = isPrimitiveNumberOrUnsignedNumberType() || isBoolean() || isByte() || isShort() || isChar() +fun ConeClassLikeType.isPrimitiveType(): Boolean = + isPrimitiveNumberOrUnsignedNumberType() || isBoolean() || isByte() || isShort() || isChar() + fun ConeClassLikeType.isPrimitiveNumberType(): Boolean = lookupTag.classId in PRIMITIVE_NUMBER_CLASS_IDS fun ConeClassLikeType.isPrimitiveUnsignedNumberType(): Boolean = lookupTag.classId in PRIMITIVE_UNSIGNED_NUMBER_CLASS_IDS fun ConeClassLikeType.isPrimitiveNumberOrUnsignedNumberType(): Boolean = isPrimitiveNumberType() || isPrimitiveUnsignedNumberType() +fun FirClass<*>.isDouble(): Boolean = classId == StandardClassIds.Double +fun FirClass<*>.isFloat(): Boolean = classId == StandardClassIds.Float +fun FirClass<*>.isLong(): Boolean = classId == StandardClassIds.Long +fun FirClass<*>.isInt(): Boolean = classId == StandardClassIds.Int +fun FirClass<*>.isShort(): Boolean = classId == StandardClassIds.Short +fun FirClass<*>.isByte(): Boolean = classId == StandardClassIds.Byte +fun FirClass<*>.isBoolean(): Boolean = classId == StandardClassIds.Boolean +fun FirClass<*>.isChar(): Boolean = classId == StandardClassIds.Char + +fun FirClass<*>.isPrimitiveType(): Boolean = isPrimitiveNumberOrUnsignedNumberType() || isBoolean() || isByte() || isShort() || isChar() +fun FirClass<*>.isPrimitiveNumberType(): Boolean = classId in PRIMITIVE_NUMBER_CLASS_IDS +fun FirClass<*>.isPrimitiveUnsignedNumberType(): Boolean = classId in PRIMITIVE_UNSIGNED_NUMBER_CLASS_IDS +fun FirClass<*>.isPrimitiveNumberOrUnsignedNumberType(): Boolean = isPrimitiveNumberType() || isPrimitiveUnsignedNumberType() + private val PRIMITIVE_NUMBER_CLASS_IDS: Set = setOf( StandardClassIds.Double, StandardClassIds.Float, StandardClassIds.Long, StandardClassIds.Int, StandardClassIds.Short, StandardClassIds.Byte diff --git a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/declarations/FirDeclarationUtil.kt b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/declarations/FirDeclarationUtil.kt index 4177f928b59..dff25a7d0d6 100644 --- a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/declarations/FirDeclarationUtil.kt +++ b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/declarations/FirDeclarationUtil.kt @@ -35,10 +35,10 @@ fun FirTypeParameterBuilder.addDefaultBoundIfNecessary(isFlexible: Boolean = fal } } -inline val FirRegularClass.isInterface: Boolean +inline val FirClass<*>.isInterface: Boolean get() = classKind == ClassKind.INTERFACE -inline val FirRegularClass.isEnumClass: Boolean +inline val FirClass<*>.isEnumClass: Boolean get() = classKind == ClassKind.ENUM_CLASS inline val FirRegularClass.modality get() = status.modality diff --git a/compiler/testData/diagnostics/tests/BinaryCallsOnNullableValues.fir.kt b/compiler/testData/diagnostics/tests/BinaryCallsOnNullableValues.fir.kt index 530b46562ba..687dae6a3e3 100644 --- a/compiler/testData/diagnostics/tests/BinaryCallsOnNullableValues.fir.kt +++ b/compiler/testData/diagnostics/tests/BinaryCallsOnNullableValues.fir.kt @@ -14,10 +14,10 @@ fun f(): Unit { x == 1 x != 1 - A() == 1 + A() == 1 - x === "1" - x !== "1" + x === "1" + x !== "1" x === 1 x !== 1 diff --git a/compiler/testData/diagnostics/tests/IdentityComparisonWithPrimitives.fir.kt b/compiler/testData/diagnostics/tests/IdentityComparisonWithPrimitives.fir.kt index 6b1c6f1338a..aa5360abff5 100644 --- a/compiler/testData/diagnostics/tests/IdentityComparisonWithPrimitives.fir.kt +++ b/compiler/testData/diagnostics/tests/IdentityComparisonWithPrimitives.fir.kt @@ -32,7 +32,7 @@ val test_dd = d === d || d !== d val test_cc = c === c || c !== c // Identity for primitive values of different types (no extra error) -val test_zb = z === b || z !== b +val test_zb = z === b || z !== b // Primitive vs nullable val test_znz = z === nz || nz === z || z !== nz || nz !== z diff --git a/compiler/testData/diagnostics/tests/classLiteral/smartCast.fir.kt b/compiler/testData/diagnostics/tests/classLiteral/smartCast.fir.kt index 7ab9924cefa..817b380c0b6 100644 --- a/compiler/testData/diagnostics/tests/classLiteral/smartCast.fir.kt +++ b/compiler/testData/diagnostics/tests/classLiteral/smartCast.fir.kt @@ -5,7 +5,7 @@ import kotlin.reflect.KClass class Foo { override fun equals(other: Any?): Boolean { if (this === other) return true - if (other === null || other::class != this::class) return false + if (other === null || other::class != this::class) return false return true } diff --git a/compiler/testData/diagnostics/tests/controlFlowAnalysis/kt1185enums.fir.kt b/compiler/testData/diagnostics/tests/controlFlowAnalysis/kt1185enums.fir.kt deleted file mode 100644 index 48a32d9ed13..00000000000 --- a/compiler/testData/diagnostics/tests/controlFlowAnalysis/kt1185enums.fir.kt +++ /dev/null @@ -1,47 +0,0 @@ -//KT-1185 Support full enumeration check for 'when' - -package kt1185 - -enum class Direction { - NORTH, - SOUTH, - WEST, - EAST -} - -class A { - companion object { - - } -} - -enum class Color(val rgb : Int) { - RED(0xFF0000), - GREEN(0x00FF00), - BLUE(0x0000FF) -} - -fun foo(d: Direction) = when(d) { //no 'else' should be requested - Direction.NORTH -> 1 - Direction.SOUTH -> 2 - A -> 1 - Direction.WEST -> 3 - Direction.EAST -> 4 -} - -fun foo1(d: Direction) = when(d) { - Direction.NORTH -> 1 - Direction.SOUTH -> 2 - Direction.WEST -> 3 -} - -fun bar(c: Color) = when (c) { - Color.RED -> 1 - Color.GREEN -> 2 - Color.BLUE -> 3 -} - -fun bar1(c: Color) = when (c) { - Color.RED -> 1 - Color.GREEN -> 2 -} diff --git a/compiler/testData/diagnostics/tests/controlFlowAnalysis/kt1185enums.kt b/compiler/testData/diagnostics/tests/controlFlowAnalysis/kt1185enums.kt index f8c297b8643..d19ddbc10ec 100644 --- a/compiler/testData/diagnostics/tests/controlFlowAnalysis/kt1185enums.kt +++ b/compiler/testData/diagnostics/tests/controlFlowAnalysis/kt1185enums.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL //KT-1185 Support full enumeration check for 'when' package kt1185 @@ -44,4 +45,4 @@ fun bar(c: Color) = when (c) { fun bar1(c: Color) = when (c) { Color.RED -> 1 Color.GREEN -> 2 -} \ No newline at end of file +} diff --git a/compiler/testData/diagnostics/tests/enum/compareTwoDifferentEnums.fir.kt b/compiler/testData/diagnostics/tests/enum/compareTwoDifferentEnums.fir.kt index fa44fd0b0ae..5d512b33421 100644 --- a/compiler/testData/diagnostics/tests/enum/compareTwoDifferentEnums.fir.kt +++ b/compiler/testData/diagnostics/tests/enum/compareTwoDifferentEnums.fir.kt @@ -12,6 +12,6 @@ public enum JavaEnumB {} enum class KotlinEnumA enum class KotlinEnumB -fun jj(a: JavaEnumA, b: JavaEnumB) = a == b -fun jk(a: JavaEnumA, b: KotlinEnumB) = a == b -fun kk(a: KotlinEnumA, b: KotlinEnumB) = a == b +fun jj(a: JavaEnumA, b: JavaEnumB) = a == b +fun jk(a: JavaEnumA, b: KotlinEnumB) = a == b +fun kk(a: KotlinEnumA, b: KotlinEnumB) = a == b diff --git a/compiler/testData/diagnostics/tests/enum/enumSubjectTypeCheck.fir.kt b/compiler/testData/diagnostics/tests/enum/enumSubjectTypeCheck.fir.kt index de45682c62d..750c3bdf0e6 100644 --- a/compiler/testData/diagnostics/tests/enum/enumSubjectTypeCheck.fir.kt +++ b/compiler/testData/diagnostics/tests/enum/enumSubjectTypeCheck.fir.kt @@ -32,8 +32,8 @@ fun useEn2(x: En2) = x fun bar(x: Any) { if (x is En && x is En2) { when (x) { - En.A -> useEn(x) - En2.D -> useEn2(x) + En.A -> useEn(x) + En2.D -> useEn2(x) else -> {} } } diff --git a/compiler/testData/diagnostics/tests/enum/incompatibleEnumEntryClasses.fir.kt b/compiler/testData/diagnostics/tests/enum/incompatibleEnumEntryClasses.fir.kt index b6ca0f8a11c..5fef25a6942 100644 --- a/compiler/testData/diagnostics/tests/enum/incompatibleEnumEntryClasses.fir.kt +++ b/compiler/testData/diagnostics/tests/enum/incompatibleEnumEntryClasses.fir.kt @@ -7,15 +7,15 @@ interface I { enum class E1 : I { A { override fun foo() { - this == E2.A + this == E2.A val q = this when (q) { this -> {} E1.A -> {} E1.B -> {} - E2.A -> {} - E2.B -> {} + E2.A -> {} + E2.B -> {} else -> {} } } @@ -41,13 +41,13 @@ enum class E2 : I { } fun foo1(e1: E1, e2: E2) { - e1 == e2 - e1 != e2 + e1 == e2 + e1 != e2 - e1 == E2.A - E1.B == e2 + e1 == E2.A + E1.B == e2 - E1.A == E2.B + E1.A == E2.B e1 == E1.A E1.A == e1 @@ -58,27 +58,27 @@ fun foo1(e1: E1, e2: E2) { fun foo2(e1: E1, e2: E2) { when (e1) { E1.A -> {} - E2.A -> {} - E2.B -> {} + E2.A -> {} + E2.B -> {} e1 -> {} - e2 -> {} + e2 -> {} else -> {} } } fun foo3(e1: Enum, e2: Enum, e: Enum<*>) { e1 == e - e1 == e2 + e1 == e2 e1 == E1.A - e1 == E2.A + e1 == E2.A when (e1) { e1 -> {} - e2 -> {} + e2 -> {} e -> {} E1.A -> {} - E2.A -> {} + E2.A -> {} else -> {} } diff --git a/compiler/testData/diagnostics/tests/enum/incompatibleEnums.fir.kt b/compiler/testData/diagnostics/tests/enum/incompatibleEnums.fir.kt index b871cd0fe2a..a9c36a0b2ab 100644 --- a/compiler/testData/diagnostics/tests/enum/incompatibleEnums.fir.kt +++ b/compiler/testData/diagnostics/tests/enum/incompatibleEnums.fir.kt @@ -9,13 +9,13 @@ enum class E2 { } fun foo1(e1: E1, e2: E2) { - e1 == e2 - e1 != e2 + e1 == e2 + e1 != e2 - e1 == E2.A - E1.B == e2 + e1 == E2.A + E1.B == e2 - E1.A == E2.B + E1.A == E2.B e1 == E1.A E1.A == e1 @@ -26,27 +26,27 @@ fun foo1(e1: E1, e2: E2) { fun foo2(e1: E1, e2: E2) { when (e1) { E1.A -> {} - E2.A -> {} - E2.B -> {} + E2.A -> {} + E2.B -> {} e1 -> {} - e2 -> {} + e2 -> {} else -> {} } } fun foo3(e1: Enum, e2: Enum, e: Enum<*>) { e1 == e - e1 == e2 + e1 == e2 e1 == E1.A - e1 == E2.A + e1 == E2.A when (e1) { e1 -> {} - e2 -> {} + e2 -> {} e -> {} E1.A -> {} - E2.A -> {} + E2.A -> {} else -> {} } @@ -63,15 +63,15 @@ interface MyInterface open class MyOpenClass fun foo4(e1: E1, i: MyInterface, c: MyOpenClass) { - e1 == i - i == e1 + e1 == i + i == e1 - e1 == c - c == e1 + e1 == c + c == e1 when (e1) { - i -> {} - c -> {} + i -> {} + c -> {} else -> {} } } @@ -90,10 +90,10 @@ fun foo6(e1: E1?, e2: E2) { e1 == null null == e1 - e1 == E2.A - E2.A == e1 - e1 == e2 - e2 == e1 + e1 == E2.A + E2.A == e1 + e1 == e2 + e2 == e1 e2 == null null == e2 @@ -117,16 +117,16 @@ fun foo8(e1: E1?, e2: E2, t: T) { } fun foo9(e1: E1?, e2: E2, t: T, k: K) where T : MyInterface, T : MyOpenClass, K : MyInterface { - e1 == t - t == e1 + e1 == t + t == e1 - e2 == t - t == e2 + e2 == t + t == e2 - E1.A == t - t == E1.A + E1.A == t + t == E1.A - E3.X == t + E3.X == t E3.X == k k == E3.X @@ -137,9 +137,9 @@ interface Inv enum class E4 : Inv { A } fun foo10(e4: E4, invString: Inv) { - e4 == invString - invString == e4 + e4 == invString + invString == e4 - E4.A == invString - invString == E4.A -} \ No newline at end of file + E4.A == invString + invString == E4.A +} diff --git a/compiler/testData/diagnostics/tests/enum/incompatibleEnums_1_4.fir.kt b/compiler/testData/diagnostics/tests/enum/incompatibleEnums_1_4.fir.kt index a6e997afaa4..9cea7b10a93 100644 --- a/compiler/testData/diagnostics/tests/enum/incompatibleEnums_1_4.fir.kt +++ b/compiler/testData/diagnostics/tests/enum/incompatibleEnums_1_4.fir.kt @@ -9,13 +9,13 @@ enum class E2 { } fun foo1(e1: E1, e2: E2) { - e1 == e2 - e1 != e2 + e1 == e2 + e1 != e2 - e1 == E2.A - E1.B == e2 + e1 == E2.A + E1.B == e2 - E1.A == E2.B + E1.A == E2.B e1 == E1.A E1.A == e1 @@ -26,27 +26,27 @@ fun foo1(e1: E1, e2: E2) { fun foo2(e1: E1, e2: E2) { when (e1) { E1.A -> {} - E2.A -> {} - E2.B -> {} + E2.A -> {} + E2.B -> {} e1 -> {} - e2 -> {} + e2 -> {} else -> {} } } fun foo3(e1: Enum, e2: Enum, e: Enum<*>) { e1 == e - e1 == e2 + e1 == e2 e1 == E1.A - e1 == E2.A + e1 == E2.A when (e1) { e1 -> {} - e2 -> {} + e2 -> {} e -> {} E1.A -> {} - E2.A -> {} + E2.A -> {} else -> {} } @@ -63,15 +63,15 @@ interface MyInterface open class MyOpenClass fun foo4(e1: E1, i: MyInterface, c: MyOpenClass) { - e1 == i - i == e1 + e1 == i + i == e1 - e1 == c - c == e1 + e1 == c + c == e1 when (e1) { - i -> {} - c -> {} + i -> {} + c -> {} else -> {} } } @@ -90,10 +90,10 @@ fun foo6(e1: E1?, e2: E2) { e1 == null null == e1 - e1 == E2.A - E2.A == e1 - e1 == e2 - e2 == e1 + e1 == E2.A + E2.A == e1 + e1 == e2 + e2 == e1 e2 == null null == e2 @@ -117,16 +117,16 @@ fun foo8(e1: E1?, e2: E2, t: T) { } fun foo9(e1: E1?, e2: E2, t: T, k: K) where T : MyInterface, T : MyOpenClass, K : MyInterface { - e1 == t - t == e1 + e1 == t + t == e1 - e2 == t - t == e2 + e2 == t + t == e2 - E1.A == t - t == E1.A + E1.A == t + t == E1.A - E3.X == t + E3.X == t E3.X == k k == E3.X @@ -137,9 +137,9 @@ interface Inv enum class E4 : Inv { A } fun foo10(e4: E4, invString: Inv) { - e4 == invString - invString == e4 + e4 == invString + invString == e4 - E4.A == invString - invString == E4.A -} \ No newline at end of file + E4.A == invString + invString == E4.A +} diff --git a/compiler/testData/diagnostics/tests/enum/typeCompatibility.fir.kt b/compiler/testData/diagnostics/tests/enum/typeCompatibility.fir.kt new file mode 100644 index 00000000000..0189311b6df --- /dev/null +++ b/compiler/testData/diagnostics/tests/enum/typeCompatibility.fir.kt @@ -0,0 +1,97 @@ +class A +class AIn +class AOut +class A2 + +open class B +open class BIn +open class BOut + +open class C +open class D + +enum class E +interface I + +interface T + +open class TSub1 : T +open class TSub2 : T + +fun foo( + string: String, int: Int, + strings: List, ints: List, + + aString: A, aInt: A, + aOutString: A, aOutInt: A, + aOutString2: AOut, aOutInt2: AOut, + aInString: A, aInInt: A, + aInString2: AIn, aInInt2: AIn, + + bString: B, bInt: B, + bOutString: B, bOutInt: B, + bOutString2: BOut, bOutInt2: BOut, + bInString: B, bInInt: B, + bInString2: BIn, bInInt2: BIn, + + a2: A2, + + e: E, + i: I, + + ac: A, ad: A, + + tSub1: TSub1, + tSub2: TSub2, + + aListInt: A>, + aSetInt: A>, + aListString: A>, +) { + "a" == "b" + 1 == 2 + "" == 2 + + string == int + strings == ints + + aString == aInt + aOutString == aOutInt + aInString == aInInt + aOutString == aInInt + aInString == aOutInt + aOutString == aInt + aInString == aInt + aOutString2 == aOutInt2 + aInString2 == aInInt2 + aOutString2 == aInInt2 + aInString2 == aOutInt2 + aString == a2 + + bString == bInt + bOutString == bOutInt + bInString == bInInt + bOutString == bInInt + bInString == bOutInt + bOutString == bInt + bInString == bInt + bOutString2 == bOutInt2 + bInString2 == bInInt2 + bOutString2 == bInInt2 + bInString2 == bOutInt2 + + e == i + "" == i + ac == ad + + tSub1 == tSub2 + + aString == bString + + aListInt == aSetInt + aSetInt == aListString + aListString == aListInt + + aString == aListString + bString == aListString +} diff --git a/compiler/testData/diagnostics/tests/enum/typeCompatibility.kt b/compiler/testData/diagnostics/tests/enum/typeCompatibility.kt new file mode 100644 index 00000000000..bf68f636d1e --- /dev/null +++ b/compiler/testData/diagnostics/tests/enum/typeCompatibility.kt @@ -0,0 +1,97 @@ +class A +class AIn +class AOut +class A2 + +open class B +open class BIn +open class BOut + +open class C +open class D + +enum class E +interface I + +interface T + +open class TSub1 : T +open class TSub2 : T + +fun foo( + string: String, int: Int, + strings: List, ints: List, + + aString: A, aInt: A, + aOutString: A, aOutInt: A, + aOutString2: AOut, aOutInt2: AOut, + aInString: A, aInInt: A, + aInString2: AIn, aInInt2: AIn, + + bString: B, bInt: B, + bOutString: B, bOutInt: B, + bOutString2: BOut, bOutInt2: BOut, + bInString: B, bInInt: B, + bInString2: BIn, bInInt2: BIn, + + a2: A2, + + e: E, + i: I, + + ac: A, ad: A, + + tSub1: TSub1, + tSub2: TSub2, + + aListInt: A>, + aSetInt: A>, + aListString: A>, +) { + "a" == "b" + 1 == 2 + "" == 2 + + string == int + strings == ints + + aString == aInt + aOutString == aOutInt + aInString == aInInt + aOutString == aInInt + aInString == aOutInt + aOutString == aInt + aInString == aInt + aOutString2 == aOutInt2 + aInString2 == aInInt2 + aOutString2 == aInInt2 + aInString2 == aOutInt2 + aString == a2 + + bString == bInt + bOutString == bOutInt + bInString == bInInt + bOutString == bInInt + bInString == bOutInt + bOutString == bInt + bInString == bInt + bOutString2 == bOutInt2 + bInString2 == bInInt2 + bOutString2 == bInInt2 + bInString2 == bOutInt2 + + e == i + "" == i + ac == ad + + tSub1 == tSub2 + + aString == bString + + aListInt == aSetInt + aSetInt == aListString + aListString == aListInt + + aString == aListString + bString == aListString +} diff --git a/compiler/testData/diagnostics/tests/enum/typeCompatibility.txt b/compiler/testData/diagnostics/tests/enum/typeCompatibility.txt new file mode 100644 index 00000000000..ca7d8e98604 --- /dev/null +++ b/compiler/testData/diagnostics/tests/enum/typeCompatibility.txt @@ -0,0 +1,109 @@ +package + +public fun foo(/*0*/ string: kotlin.String, /*1*/ int: kotlin.Int, /*2*/ strings: kotlin.collections.List, /*3*/ ints: kotlin.collections.List, /*4*/ aString: A, /*5*/ aInt: A, /*6*/ aOutString: A, /*7*/ aOutInt: A, /*8*/ aOutString2: AOut, /*9*/ aOutInt2: AOut, /*10*/ aInString: A, /*11*/ aInInt: A, /*12*/ aInString2: AIn, /*13*/ aInInt2: AIn, /*14*/ bString: B, /*15*/ bInt: B, /*16*/ bOutString: B, /*17*/ bOutInt: B, /*18*/ bOutString2: BOut, /*19*/ bOutInt2: BOut, /*20*/ bInString: B, /*21*/ bInInt: B, /*22*/ bInString2: BIn, /*23*/ bInInt2: BIn, /*24*/ a2: A2, /*25*/ e: E, /*26*/ i: I, /*27*/ ac: A, /*28*/ ad: A, /*29*/ tSub1: TSub1, /*30*/ tSub2: TSub2, /*31*/ aListInt: A>, /*32*/ aSetInt: A>, /*33*/ aListString: A>, /*34*/ mutableListAny: kotlin.collections.MutableList, /*35*/ listString: kotlin.collections.List): kotlin.Unit + +public final class A { + public constructor A() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public final class A2 { + public constructor A2() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public final class AIn { + public constructor AIn() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public final class AOut { + public constructor AOut() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public open class B { + public constructor B() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public open class BIn { + public constructor BIn() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public open class BOut { + public constructor BOut() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public open class C { + public constructor C() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public open class D { + public constructor D() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public final enum class E : kotlin.Enum { + private constructor E() + public final override /*1*/ /*fake_override*/ val name: kotlin.String + public final override /*1*/ /*fake_override*/ val ordinal: kotlin.Int + protected final override /*1*/ /*fake_override*/ fun clone(): kotlin.Any + public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: E): kotlin.Int + public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + protected/*protected and package*/ final override /*1*/ /*fake_override*/ /*isHiddenForResolutionEverywhereBesideSupercalls*/ fun finalize(): kotlin.Unit + public final override /*1*/ /*fake_override*/ /*isHiddenForResolutionEverywhereBesideSupercalls*/ fun getDeclaringClass(): java.lang.Class! + public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + // Static members + public final /*synthesized*/ fun valueOf(/*0*/ value: kotlin.String): E + public final /*synthesized*/ fun values(): kotlin.Array +} + +public interface I { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface T { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public open class TSub1 : T { + public constructor TSub1() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public open class TSub2 : T { + public constructor TSub2() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/testData/diagnostics/tests/inlineClasses/identityComparisonWithInlineClasses.fir.kt b/compiler/testData/diagnostics/tests/inlineClasses/identityComparisonWithInlineClasses.fir.kt index a51dfec0cf6..bcb23a88bc6 100644 --- a/compiler/testData/diagnostics/tests/inlineClasses/identityComparisonWithInlineClasses.fir.kt +++ b/compiler/testData/diagnostics/tests/inlineClasses/identityComparisonWithInlineClasses.fir.kt @@ -7,11 +7,11 @@ inline class Bar(val y: String) fun test(f1: Foo, f2: Foo, b1: Bar, fn1: Foo?, fn2: Foo?) { val a1 = f1 === f2 || f1 !== f2 val a2 = f1 === f1 - val a3 = f1 === b1 || f1 !== b1 + val a3 = f1 === b1 || f1 !== b1 val c1 = fn1 === fn2 || fn1 !== fn2 val c2 = f1 === fn1 || f1 !== fn1 - val c3 = b1 === fn1 || b1 !== fn1 + val c3 = b1 === fn1 || b1 !== fn1 val any = Any() @@ -19,4 +19,4 @@ fun test(f1: Foo, f2: Foo, b1: Bar, fn1: Foo?, fn2: Foo?) { val d2 = f1 === any || f1 !== any val d3 = any === fn1 || any !== fn1 val d4 = fn1 === any || fn1 !== any -} \ No newline at end of file +} diff --git a/compiler/testData/diagnostics/tests/nullabilityAndSmartCasts/kt1778.fir.kt b/compiler/testData/diagnostics/tests/nullabilityAndSmartCasts/kt1778.fir.kt index e0bfec66a76..1234da06ded 100644 --- a/compiler/testData/diagnostics/tests/nullabilityAndSmartCasts/kt1778.fir.kt +++ b/compiler/testData/diagnostics/tests/nullabilityAndSmartCasts/kt1778.fir.kt @@ -7,8 +7,8 @@ import checkSubtype fun main(args : Array) { val x = checkSubtype(args[0]) if(x is java.lang.CharSequence) { - if ("a" == x) x.length else x.length() // OK - if ("a" == x || "b" == x) x.length else x.length() // <– THEN ERROR - if ("a" == x && "a" == x) x.length else x.length() // <– ELSE ERROR + if ("a" == x) x.length else x.length() // OK + if ("a" == x || "b" == x) x.length else x.length() // <– THEN ERROR + if ("a" == x && "a" == x) x.length else x.length() // <– ELSE ERROR } } diff --git a/compiler/testData/diagnostics/tests/sealed/TreeWhenFunctionalNoIs.fir.kt b/compiler/testData/diagnostics/tests/sealed/TreeWhenFunctionalNoIs.fir.kt index 3e2555f93fd..dbe0f0cdf10 100644 --- a/compiler/testData/diagnostics/tests/sealed/TreeWhenFunctionalNoIs.fir.kt +++ b/compiler/testData/diagnostics/tests/sealed/TreeWhenFunctionalNoIs.fir.kt @@ -11,7 +11,7 @@ sealed class Tree { fun maxIsClass(): Int = when(this) { Empty -> -1 - Leaf -> 0 + Leaf -> 0 is Node -> this.left.max() } diff --git a/compiler/testData/diagnostics/tests/unsignedTypes/forbiddenEqualsOnUnsignedTypes.fir.kt b/compiler/testData/diagnostics/tests/unsignedTypes/forbiddenEqualsOnUnsignedTypes.fir.kt index f46d32673a7..67380a436ea 100644 --- a/compiler/testData/diagnostics/tests/unsignedTypes/forbiddenEqualsOnUnsignedTypes.fir.kt +++ b/compiler/testData/diagnostics/tests/unsignedTypes/forbiddenEqualsOnUnsignedTypes.fir.kt @@ -11,7 +11,7 @@ fun test( val ui = ui1 === ui2 || ui1 !== ui2 val ul = ul1 === ul2 || ul1 !== ul2 - val u = ub1 === ul1 + val u = ub1 === ul1 val a1 = 1u === 2u || 1u !== 2u val a2 = 0xFFFF_FFFF_FFFF_FFFFu === 0xFFFF_FFFF_FFFF_FFFFu @@ -20,4 +20,4 @@ fun test( val bu2 = 1u val c1 = bu1 === bu2 || bu1 !== bu2 -} \ No newline at end of file +} diff --git a/compiler/testData/diagnostics/tests/valueClasses/identityComparisonWithValueClasses.fir.kt b/compiler/testData/diagnostics/tests/valueClasses/identityComparisonWithValueClasses.fir.kt index 0fff9c556ff..d01623a1a38 100644 --- a/compiler/testData/diagnostics/tests/valueClasses/identityComparisonWithValueClasses.fir.kt +++ b/compiler/testData/diagnostics/tests/valueClasses/identityComparisonWithValueClasses.fir.kt @@ -14,11 +14,11 @@ value class Bar(val y: String) fun test(f1: Foo, f2: Foo, b1: Bar, fn1: Foo?, fn2: Foo?) { val a1 = f1 === f2 || f1 !== f2 val a2 = f1 === f1 - val a3 = f1 === b1 || f1 !== b1 + val a3 = f1 === b1 || f1 !== b1 val c1 = fn1 === fn2 || fn1 !== fn2 val c2 = f1 === fn1 || f1 !== fn1 - val c3 = b1 === fn1 || b1 !== fn1 + val c3 = b1 === fn1 || b1 !== fn1 val any = Any() diff --git a/compiler/testData/diagnostics/tests/when/When.fir.kt b/compiler/testData/diagnostics/tests/when/When.fir.kt index 6f885487511..3340e3c95ea 100644 --- a/compiler/testData/diagnostics/tests/when/When.fir.kt +++ b/compiler/testData/diagnostics/tests/when/When.fir.kt @@ -23,7 +23,7 @@ fun foo() : Int { !is Int -> 1 is Any? -> 1 is Any -> 1 - s -> 1 + s -> 1 1 -> 1 1 + a -> 1 in 1..a -> 1 @@ -41,8 +41,8 @@ fun test() { val s = ""; when (x) { - s -> 1 - "" -> 1 + s -> 1 + "" -> 1 x -> 1 1 -> 1 } 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 56f58775d33..a88e743ac83 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 @@ -9077,6 +9077,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest { runTest("compiler/testData/diagnostics/tests/enum/starImportNestedClassAndEntries.kt"); } + @Test + @TestMetadata("typeCompatibility.kt") + public void testTypeCompatibility() throws Exception { + runTest("compiler/testData/diagnostics/tests/enum/typeCompatibility.kt"); + } + @Test @TestMetadata("typeParametersInEnum.kt") public void testTypeParametersInEnum() throws Exception { diff --git a/compiler/tests-spec/testData/diagnostics/linked/expressions/when-expression/p-6/neg/3.1.fir.kt b/compiler/tests-spec/testData/diagnostics/linked/expressions/when-expression/p-6/neg/3.1.fir.kt index 521fa81217e..8ceb2af57ba 100644 --- a/compiler/tests-spec/testData/diagnostics/linked/expressions/when-expression/p-6/neg/3.1.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/linked/expressions/when-expression/p-6/neg/3.1.fir.kt @@ -3,8 +3,8 @@ // TESTCASE NUMBER: 1 fun case_1(value_1: Int, value_2: TypesProvider): String { when (value_1) { - -1000L..100 -> return "" - value_2.getInt()..getLong() -> return "" + -1000L..100 -> return "" + value_2.getInt()..getLong() -> return "" } return "" diff --git a/compiler/tests-spec/testData/diagnostics/notLinked/dfa/neg/1.fir.kt b/compiler/tests-spec/testData/diagnostics/notLinked/dfa/neg/1.fir.kt index 6a11265b90e..9d77fca1e04 100644 --- a/compiler/tests-spec/testData/diagnostics/notLinked/dfa/neg/1.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/notLinked/dfa/neg/1.fir.kt @@ -55,7 +55,7 @@ fun case_4(x: Char?) { fun case_5() { val x: Unit? = null - if (x !== null is Boolean?) x + if (x !== null is Boolean?) x if (x !== null == null) x.equals(null) if (x !== null == null) x.propT if (x !== null == null) x.propAny @@ -87,7 +87,7 @@ fun case_6(x: EmptyClass?) { // TESTCASE NUMBER: 7 fun case_7() { - if (nullableNumberProperty != null || nullableNumberProperty != null is Boolean) { + if (nullableNumberProperty != null || nullableNumberProperty != null is Boolean) { nullableNumberProperty nullableNumberProperty.equals(null) nullableNumberProperty.propT @@ -141,12 +141,12 @@ fun case_10() { fun case_11(x: TypealiasNullableStringIndirect?, y: TypealiasNullableStringIndirect) { val t: TypealiasNullableStringIndirect = null - if (x == null is Boolean) { + if (x == null is Boolean) { } else { - if (y != null is Boolean == true) { + if (y != null is Boolean == true) { if ((nullableStringProperty == null) !is Boolean) { - if (t != null is Boolean) { + if (t != null is Boolean) { x x.equals(null) x.propT @@ -236,7 +236,7 @@ fun case_14() { // TESTCASE NUMBER: 15 fun case_15(x: EmptyObject) { - val t = if (x === null is Boolean is Boolean is Boolean) "" else { + val t = if (x === null is Boolean is Boolean is Boolean) "" else { x x.equals(null) x.propT @@ -254,7 +254,7 @@ fun case_15(x: EmptyObject) { fun case_16() { val x: TypealiasNullableNothing = null - if (x != null !is Boolean !is Boolean !is Boolean !is Boolean !is Boolean) { + if (x != null !is Boolean !is Boolean !is Boolean !is Boolean !is Boolean) { x x.java } @@ -302,7 +302,7 @@ fun case_19(b: Boolean) { } } else null - if (a != null !is Boolean && a.B19 != null is Boolean && a.B19.C19 != null is Boolean && a.B19.C19.D19 != null == null && a.B19.C19.D19.x != null !== null) { + if (a != null !is Boolean && a.B19 != null is Boolean && a.B19.C19 != null is Boolean && a.B19.C19.D19 != null == null && a.B19.C19.D19.x != null !== null) { a.B19.C19.D19.x a.B19.C19.D19.x.equals(null) a.B19.C19.D19.x.propT @@ -328,7 +328,7 @@ fun case_20(b: Boolean) { } } - if (a.B19.C19.D19 !== null !is Boolean) { + if (a.B19.C19.D19 !== null !is Boolean) { a.B19.C19.D19 a.B19.C19.D19.equals(null) a.B19.C19.D19.propT @@ -344,7 +344,7 @@ fun case_20(b: Boolean) { // TESTCASE NUMBER: 21 fun case_21() { - if (EnumClassWithNullableProperty.B.prop_1 !== null is Boolean == true !is Boolean != true) { + if (EnumClassWithNullableProperty.B.prop_1 !== null is Boolean == true !is Boolean != true) { EnumClassWithNullableProperty.B.prop_1 EnumClassWithNullableProperty.B.prop_1.equals(null) EnumClassWithNullableProperty.B.prop_1.propT @@ -360,7 +360,7 @@ fun case_21() { // TESTCASE NUMBER: 22 fun case_22(a: (() -> Unit)?) { - if (a != null !is Boolean) { + if (a != null !is Boolean) { a() a().equals(null) a().propT @@ -376,7 +376,7 @@ fun case_22(a: (() -> Unit)?) { // TESTCASE NUMBER: 23 fun case_23(a: ((Float) -> Int?)?, b: Float?) { - if (a != null !is Boolean && b !== null is Boolean) { + if (a != null !is Boolean && b !== null is Boolean) { val x = a(b) if (x != null) { x @@ -395,7 +395,7 @@ fun case_23(a: ((Float) -> Int?)?, b: Float?) { // TESTCASE NUMBER: 24 fun case_24(a: ((() -> Unit) -> Unit)?, b: (() -> Unit)?) = - if (a !== null is Boolean && b !== null !is Boolean) { + if (a !== null is Boolean && b !== null !is Boolean) { a(?")!>b) a(b) ?")!>b.equals(null) diff --git a/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/6.fir.kt b/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/6.fir.kt index 5802bfa7f61..4d0ec43070a 100644 --- a/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/6.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/6.fir.kt @@ -577,7 +577,7 @@ fun case_29(x: Boolean) { if (false || false || false || false || y !== v) { val t = ?")!>y() - if (z !== t || false) { + if (z !== t || false) { ?")!>t.a ?")!>t.equals(null) ?")!>t.propT diff --git a/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt b/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt index 71e788880da..ada29dc8560 100644 --- a/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt +++ b/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt @@ -221,6 +221,7 @@ enum class LanguageFeature( ), MultiPlatformProjects(sinceVersion = null, defaultState = State.DISABLED), InlineClasses(KOTLIN_1_3, defaultState = State.ENABLED_WITH_WARNING, kind = UNSTABLE_FEATURE), + ProhibitComparisonOfIncompatibleClasses(sinceVersion = null, kind = BUG_FIX, defaultState = State.DISABLED), ; diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDataClassConverters.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDataClassConverters.kt index 5bb40066b42..49e267510b8 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDataClassConverters.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDataClassConverters.kt @@ -1112,6 +1112,22 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert token, ) } + add(FirErrors.INCOMPATIBLE_TYPES) { firDiagnostic -> + IncompatibleTypesImpl( + firSymbolBuilder.typeBuilder.buildKtType(firDiagnostic.a), + firSymbolBuilder.typeBuilder.buildKtType(firDiagnostic.b), + firDiagnostic as FirPsiDiagnostic<*>, + token, + ) + } + add(FirErrors.INCOMPATIBLE_TYPES_WARNING) { firDiagnostic -> + IncompatibleTypesWarningImpl( + firSymbolBuilder.typeBuilder.buildKtType(firDiagnostic.a), + firSymbolBuilder.typeBuilder.buildKtType(firDiagnostic.b), + firDiagnostic as FirPsiDiagnostic<*>, + token, + ) + } add(FirErrors.EXTENSION_IN_CLASS_REFERENCE_NOT_ALLOWED) { firDiagnostic -> ExtensionInClassReferenceNotAllowedImpl( firSymbolBuilder.callableBuilder.buildCallableSymbol(firDiagnostic.a as FirCallableDeclaration), @@ -2020,6 +2036,32 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert token, ) } + add(FirErrors.EQUALITY_NOT_APPLICABLE) { firDiagnostic -> + EqualityNotApplicableImpl( + firDiagnostic.a, + firSymbolBuilder.typeBuilder.buildKtType(firDiagnostic.b), + firSymbolBuilder.typeBuilder.buildKtType(firDiagnostic.c), + firDiagnostic as FirPsiDiagnostic<*>, + token, + ) + } + add(FirErrors.EQUALITY_NOT_APPLICABLE_WARNING) { firDiagnostic -> + EqualityNotApplicableWarningImpl( + firDiagnostic.a, + firSymbolBuilder.typeBuilder.buildKtType(firDiagnostic.b), + firSymbolBuilder.typeBuilder.buildKtType(firDiagnostic.c), + firDiagnostic as FirPsiDiagnostic<*>, + token, + ) + } + add(FirErrors.INCOMPATIBLE_ENUM_COMPARISON_ERROR) { firDiagnostic -> + IncompatibleEnumComparisonErrorImpl( + firSymbolBuilder.typeBuilder.buildKtType(firDiagnostic.a), + firSymbolBuilder.typeBuilder.buildKtType(firDiagnostic.b), + firDiagnostic as FirPsiDiagnostic<*>, + token, + ) + } add(FirErrors.TOPLEVEL_TYPEALIASES_ONLY) { firDiagnostic -> ToplevelTypealiasesOnlyImpl( firDiagnostic as FirPsiDiagnostic<*>, diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnostics.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnostics.kt index 0901463252e..7cdf10947af 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnostics.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnostics.kt @@ -785,6 +785,18 @@ sealed class KtFirDiagnostic : KtDiagnosticWithPsi { override val diagnosticClass get() = DynamicUpperBound::class } + abstract class IncompatibleTypes : KtFirDiagnostic() { + override val diagnosticClass get() = IncompatibleTypes::class + abstract val typeA: KtType + abstract val typeB: KtType + } + + abstract class IncompatibleTypesWarning : KtFirDiagnostic() { + override val diagnosticClass get() = IncompatibleTypesWarning::class + abstract val typeA: KtType + abstract val typeB: KtType + } + abstract class ExtensionInClassReferenceNotAllowed : KtFirDiagnostic() { override val diagnosticClass get() = ExtensionInClassReferenceNotAllowed::class abstract val referencedDeclaration: KtCallableSymbol @@ -1413,6 +1425,26 @@ sealed class KtFirDiagnostic : KtDiagnosticWithPsi { override val diagnosticClass get() = UnderscoreUsageWithoutBackticks::class } + abstract class EqualityNotApplicable : KtFirDiagnostic() { + override val diagnosticClass get() = EqualityNotApplicable::class + abstract val operator: String + abstract val leftType: KtType + abstract val rightType: KtType + } + + abstract class EqualityNotApplicableWarning : KtFirDiagnostic() { + override val diagnosticClass get() = EqualityNotApplicableWarning::class + abstract val operator: String + abstract val leftType: KtType + abstract val rightType: KtType + } + + abstract class IncompatibleEnumComparisonError : KtFirDiagnostic() { + override val diagnosticClass get() = IncompatibleEnumComparisonError::class + abstract val leftType: KtType + abstract val rightType: KtType + } + abstract class ToplevelTypealiasesOnly : KtFirDiagnostic() { override val diagnosticClass get() = ToplevelTypealiasesOnly::class } diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnosticsImpl.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnosticsImpl.kt index 08095de306a..20e4403bb49 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnosticsImpl.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnosticsImpl.kt @@ -1269,6 +1269,24 @@ internal class DynamicUpperBoundImpl( override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic) } +internal class IncompatibleTypesImpl( + override val typeA: KtType, + override val typeB: KtType, + firDiagnostic: FirPsiDiagnostic<*>, + override val token: ValidityToken, +) : KtFirDiagnostic.IncompatibleTypes(), KtAbstractFirDiagnostic { + override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic) +} + +internal class IncompatibleTypesWarningImpl( + override val typeA: KtType, + override val typeB: KtType, + firDiagnostic: FirPsiDiagnostic<*>, + override val token: ValidityToken, +) : KtFirDiagnostic.IncompatibleTypesWarning(), KtAbstractFirDiagnostic { + override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic) +} + internal class ExtensionInClassReferenceNotAllowedImpl( override val referencedDeclaration: KtCallableSymbol, firDiagnostic: FirPsiDiagnostic<*>, @@ -2293,6 +2311,35 @@ internal class UnderscoreUsageWithoutBackticksImpl( override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic) } +internal class EqualityNotApplicableImpl( + override val operator: String, + override val leftType: KtType, + override val rightType: KtType, + firDiagnostic: FirPsiDiagnostic<*>, + override val token: ValidityToken, +) : KtFirDiagnostic.EqualityNotApplicable(), KtAbstractFirDiagnostic { + override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic) +} + +internal class EqualityNotApplicableWarningImpl( + override val operator: String, + override val leftType: KtType, + override val rightType: KtType, + firDiagnostic: FirPsiDiagnostic<*>, + override val token: ValidityToken, +) : KtFirDiagnostic.EqualityNotApplicableWarning(), KtAbstractFirDiagnostic { + override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic) +} + +internal class IncompatibleEnumComparisonErrorImpl( + override val leftType: KtType, + override val rightType: KtType, + firDiagnostic: FirPsiDiagnostic<*>, + override val token: ValidityToken, +) : KtFirDiagnostic.IncompatibleEnumComparisonError(), KtAbstractFirDiagnostic { + override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic) +} + internal class ToplevelTypealiasesOnlyImpl( firDiagnostic: FirPsiDiagnostic<*>, override val token: ValidityToken, diff --git a/idea/testData/checker/BinaryCallsOnNullableValues.fir.kt b/idea/testData/checker/BinaryCallsOnNullableValues.fir.kt index d21cde0b76d..ae7234bca72 100644 --- a/idea/testData/checker/BinaryCallsOnNullableValues.fir.kt +++ b/idea/testData/checker/BinaryCallsOnNullableValues.fir.kt @@ -13,10 +13,10 @@ fun f(): Unit { x == 1 x != 1 - A() == 1 + A() == 1 - x === "1" - x !== "1" + x === "1" + x !== "1" x === 1 x !== 1 diff --git a/idea/testData/checker/When.fir.kt b/idea/testData/checker/When.fir.kt index fcc1075621c..f75adb98b58 100644 --- a/idea/testData/checker/When.fir.kt +++ b/idea/testData/checker/When.fir.kt @@ -7,7 +7,7 @@ fun foo() : Int { is String -> 1 !is Int -> 1 is Any? -> 1 - s -> 1 + s -> 1 1 -> 1 1 + a -> 1 in 1..a -> 1 @@ -25,8 +25,8 @@ fun test() { val s = ""; when (x) { - s -> 1 - "" -> 1 + s -> 1 + "" -> 1 x -> 1 1 -> 1 else -> 1