Support structural equals/hashCode for type constructors of type parameters
Use the same logic as for type constructors of classes, based on the fully-qualified name of the classifier, with special cases for error types and local declarations, with an additional check that the type constructors' declaration descriptors are structurally equal via `DescriptorEquivalenceForOverrides`. The latter is required because type parameters of overloaded functions must be different, even though their full FQ name is the same. This (hopefully) has no effect for the compiler, but is useful for kotlin-reflect where `KType.equals` runs the type checker on the underlying `KotlinType` instances, which eventually ends up comparing type constructors. Descriptors and types in kotlin-reflect are cached on soft references, so they may be suddenly garbage-collected and recomputed, and we want copies of the same type parameter to be equal to each other. This fixes flaky codegen tests which started to fail after migration to the new test infrastructure, where tests are now run in parallel in the same process, thus with higher memory pressure and more soft references being GC'd: * `codegen/box/reflection/types/createType/typeParameter.kt` * `codegen/box/reflection/supertypes/genericSubstitution.kt` Also, add a new test to check that we do the instanceof check in overrides of `AbstractTypeConstructor.isSameClassifier`. #KT-44850 Fixed
This commit is contained in:
+6
@@ -35387,6 +35387,12 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT
|
||||
runTest("compiler/testData/codegen/box/reflection/types/classifiersOfBuiltInTypes.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("equalsForClassAndTypeParameterWithSameFqName.kt")
|
||||
public void testEqualsForClassAndTypeParameterWithSameFqName() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/reflection/types/equalsForClassAndTypeParameterWithSameFqName.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("innerGenericArguments.kt")
|
||||
public void testInnerGenericArguments() throws Exception {
|
||||
|
||||
@@ -266,6 +266,8 @@ open class IrBasedTypeParameterDescriptor(owner: IrTypeParameter) : TypeParamete
|
||||
override fun getDeclarationDescriptor() = this@IrBasedTypeParameterDescriptor
|
||||
|
||||
override fun getBuiltIns() = module.builtIns
|
||||
|
||||
override fun isSameClassifier(classifier: ClassifierDescriptor): Boolean = declarationDescriptor === classifier
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Vendored
+27
@@ -0,0 +1,27 @@
|
||||
// TARGET_BACKEND: JVM
|
||||
// WITH_REFLECT
|
||||
|
||||
// FIR incorrectly resolves typeParameterType's return type to the nested class `A.T`.
|
||||
// IGNORE_BACKEND_FIR: JVM_IR
|
||||
|
||||
package test
|
||||
|
||||
class A<T> {
|
||||
class T
|
||||
|
||||
fun typeParameterType(): T? = null
|
||||
fun nestedClassType(): A.T? = null
|
||||
}
|
||||
|
||||
fun box(): String {
|
||||
val typeParameterType = A<*>::typeParameterType.returnType
|
||||
val classType = A<*>::nestedClassType.returnType
|
||||
|
||||
if (typeParameterType == classType)
|
||||
return "Fail 1: type parameter's type constructor shouldn't be equal to the class with the same FQ name"
|
||||
|
||||
if (classType == typeParameterType)
|
||||
return "Fail 2: class' type constructor shouldn't be equal to the type parameter with the same FQ name"
|
||||
|
||||
return "OK"
|
||||
}
|
||||
+6
@@ -35387,6 +35387,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
||||
runTest("compiler/testData/codegen/box/reflection/types/classifiersOfBuiltInTypes.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("equalsForClassAndTypeParameterWithSameFqName.kt")
|
||||
public void testEqualsForClassAndTypeParameterWithSameFqName() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/reflection/types/equalsForClassAndTypeParameterWithSameFqName.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("innerGenericArguments.kt")
|
||||
public void testInnerGenericArguments() throws Exception {
|
||||
|
||||
+6
@@ -35387,6 +35387,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
|
||||
runTest("compiler/testData/codegen/box/reflection/types/classifiersOfBuiltInTypes.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("equalsForClassAndTypeParameterWithSameFqName.kt")
|
||||
public void testEqualsForClassAndTypeParameterWithSameFqName() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/reflection/types/equalsForClassAndTypeParameterWithSameFqName.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("innerGenericArguments.kt")
|
||||
public void testInnerGenericArguments() throws Exception {
|
||||
|
||||
+5
@@ -28135,6 +28135,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
|
||||
runTest("compiler/testData/codegen/box/reflection/types/classifiersOfBuiltInTypes.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("equalsForClassAndTypeParameterWithSameFqName.kt")
|
||||
public void testEqualsForClassAndTypeParameterWithSameFqName() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/reflection/types/equalsForClassAndTypeParameterWithSameFqName.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("innerGenericArguments.kt")
|
||||
public void testInnerGenericArguments() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/reflection/types/innerGenericArguments.kt");
|
||||
|
||||
+11
@@ -23,6 +23,7 @@ import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
|
||||
import org.jetbrains.kotlin.descriptors.*;
|
||||
import org.jetbrains.kotlin.descriptors.annotations.Annotations;
|
||||
import org.jetbrains.kotlin.name.Name;
|
||||
import org.jetbrains.kotlin.resolve.DescriptorEquivalenceForOverrides;
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt;
|
||||
import org.jetbrains.kotlin.resolve.scopes.LazyScopeAdapter;
|
||||
import org.jetbrains.kotlin.resolve.scopes.MemberScope;
|
||||
@@ -222,5 +223,15 @@ public abstract class AbstractTypeParameterDescriptor extends DeclarationDescrip
|
||||
protected KotlinType defaultSupertypeIfEmpty() {
|
||||
return ErrorUtils.createErrorType("Cyclic upper bounds");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isSameClassifier(@NotNull ClassifierDescriptor classifier) {
|
||||
return classifier instanceof TypeParameterDescriptor &&
|
||||
DescriptorEquivalenceForOverrides.INSTANCE.areTypeParametersEquivalent(
|
||||
AbstractTypeParameterDescriptor.this,
|
||||
(TypeParameterDescriptor) classifier,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -56,7 +56,8 @@ object DescriptorEquivalenceForOverrides {
|
||||
return a.typeConstructor == b.typeConstructor
|
||||
}
|
||||
|
||||
private fun areTypeParametersEquivalent(
|
||||
@JvmOverloads
|
||||
fun areTypeParametersEquivalent(
|
||||
a: TypeParameterDescriptor,
|
||||
b: TypeParameterDescriptor,
|
||||
allowCopiesFromTheSameDeclaration: Boolean,
|
||||
|
||||
@@ -19,8 +19,10 @@ package org.jetbrains.kotlin.types;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
|
||||
import org.jetbrains.kotlin.descriptors.*;
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils;
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor;
|
||||
import org.jetbrains.kotlin.descriptors.ClassifierDescriptor;
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor;
|
||||
import org.jetbrains.kotlin.descriptors.ModalityUtilsKt;
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt;
|
||||
import org.jetbrains.kotlin.storage.StorageManager;
|
||||
import org.jetbrains.kotlin.utils.SmartList;
|
||||
@@ -29,28 +31,10 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
public abstract class AbstractClassTypeConstructor extends AbstractTypeConstructor implements TypeConstructor {
|
||||
private int hashCode = 0;
|
||||
|
||||
public AbstractClassTypeConstructor(@NotNull StorageManager storageManager) {
|
||||
super(storageManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int hashCode() {
|
||||
int currentHashCode = hashCode;
|
||||
if (currentHashCode != 0) return currentHashCode;
|
||||
|
||||
ClassifierDescriptor descriptor = getDeclarationDescriptor();
|
||||
if (hasMeaningfulFqName(descriptor)) {
|
||||
currentHashCode = DescriptorUtils.getFqName(descriptor).hashCode();
|
||||
}
|
||||
else {
|
||||
currentHashCode = System.identityHashCode(this);
|
||||
}
|
||||
hashCode = currentHashCode;
|
||||
return currentHashCode;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public abstract ClassDescriptor getDeclarationDescriptor();
|
||||
@@ -68,60 +52,8 @@ public abstract class AbstractClassTypeConstructor extends AbstractTypeConstruct
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean equals(Object other) {
|
||||
if (this == other) return true;
|
||||
if (!(other instanceof TypeConstructor)) return false;
|
||||
|
||||
// performance optimization: getFqName is slow method
|
||||
if (other.hashCode() != hashCode()) return false;
|
||||
|
||||
// Sometimes we can get two classes from different modules with different counts of type parameters.
|
||||
// To avoid problems in type checker we suppose that it is different type constructors.
|
||||
if (((TypeConstructor) other).getParameters().size() != getParameters().size()) return false;
|
||||
|
||||
ClassifierDescriptor myDescriptor = getDeclarationDescriptor();
|
||||
ClassifierDescriptor otherDescriptor = ((TypeConstructor) other).getDeclarationDescriptor();
|
||||
|
||||
if (!hasMeaningfulFqName(myDescriptor) ||
|
||||
otherDescriptor != null && !hasMeaningfulFqName(otherDescriptor)) {
|
||||
// All error types and local classes have the same descriptor,
|
||||
// but we've already checked identity equality in the beginning of the method
|
||||
return false;
|
||||
}
|
||||
|
||||
if (otherDescriptor instanceof ClassDescriptor) {
|
||||
return areFqNamesEqual(((ClassDescriptor) myDescriptor), ((ClassDescriptor) otherDescriptor));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean areFqNamesEqual(ClassDescriptor first, ClassDescriptor second) {
|
||||
if (!first.getName().equals(second.getName())) return false;
|
||||
|
||||
DeclarationDescriptor a = first.getContainingDeclaration();
|
||||
DeclarationDescriptor b = second.getContainingDeclaration();
|
||||
while (a != null && b != null) {
|
||||
if (a instanceof ModuleDescriptor) return b instanceof ModuleDescriptor;
|
||||
if (b instanceof ModuleDescriptor) return false;
|
||||
|
||||
if (a instanceof PackageFragmentDescriptor) {
|
||||
return b instanceof PackageFragmentDescriptor &&
|
||||
((PackageFragmentDescriptor) a).getFqName().equals(((PackageFragmentDescriptor) b).getFqName());
|
||||
}
|
||||
if (b instanceof PackageFragmentDescriptor) return false;
|
||||
|
||||
if (!a.getName().equals(b.getName())) return false;
|
||||
|
||||
a = a.getContainingDeclaration();
|
||||
b = b.getContainingDeclaration();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean hasMeaningfulFqName(@NotNull ClassifierDescriptor descriptor) {
|
||||
return !ErrorUtils.isError(descriptor) &&
|
||||
!DescriptorUtils.isLocal(descriptor);
|
||||
protected boolean isSameClassifier(@NotNull ClassifierDescriptor classifier) {
|
||||
return classifier instanceof ClassDescriptor && areFqNamesEqual(getDeclarationDescriptor(), classifier);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
@@ -17,15 +17,16 @@
|
||||
package org.jetbrains.kotlin.types
|
||||
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.descriptors.ClassifierDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.SupertypeLoopChecker
|
||||
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||
import org.jetbrains.kotlin.storage.StorageManager
|
||||
import org.jetbrains.kotlin.types.checker.KotlinTypeRefiner
|
||||
import org.jetbrains.kotlin.types.checker.refineTypes
|
||||
import org.jetbrains.kotlin.types.refinement.TypeRefinement
|
||||
|
||||
abstract class AbstractTypeConstructor(storageManager: StorageManager) : TypeConstructor {
|
||||
private var hashCode = 0
|
||||
|
||||
override fun getSupertypes() = supertypes().supertypesWithoutCycles
|
||||
|
||||
abstract override fun getDeclarationDescriptor(): ClassifierDescriptor
|
||||
@@ -132,4 +133,65 @@ abstract class AbstractTypeConstructor(storageManager: StorageManager) : TypeCon
|
||||
|
||||
// Only for debugging
|
||||
fun renderAdditionalDebugInformation(): String = "supertypes=${supertypes.renderDebugInformation()}"
|
||||
|
||||
override fun hashCode(): Int {
|
||||
val cachedHashCode = hashCode
|
||||
if (cachedHashCode != 0) return cachedHashCode
|
||||
|
||||
val descriptor = declarationDescriptor
|
||||
val computedHashCode = if (hasMeaningfulFqName(descriptor)) {
|
||||
DescriptorUtils.getFqName(descriptor).hashCode()
|
||||
} else {
|
||||
System.identityHashCode(this)
|
||||
}
|
||||
|
||||
return computedHashCode.also { hashCode = it }
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is TypeConstructor) return false
|
||||
|
||||
// performance optimization: getFqName is slow method
|
||||
if (other.hashCode() != hashCode()) return false
|
||||
|
||||
// Sometimes we can get two classes from different modules with different counts of type parameters.
|
||||
// To avoid problems in type checker we suppose that it is different type constructors.
|
||||
if (other.parameters.size != parameters.size) return false
|
||||
|
||||
val myDescriptor = declarationDescriptor
|
||||
val otherDescriptor = other.declarationDescriptor ?: return false
|
||||
if (!hasMeaningfulFqName(myDescriptor) || !hasMeaningfulFqName(otherDescriptor)) {
|
||||
// All error types and local classes have the same descriptor,
|
||||
// but we've already checked identity equality in the beginning of the method
|
||||
return false
|
||||
}
|
||||
|
||||
return isSameClassifier(otherDescriptor)
|
||||
}
|
||||
|
||||
protected abstract fun isSameClassifier(classifier: ClassifierDescriptor): Boolean
|
||||
|
||||
protected fun areFqNamesEqual(first: ClassifierDescriptor, second: ClassifierDescriptor): Boolean {
|
||||
if (first.name != second.name) return false
|
||||
var a: DeclarationDescriptor? = first.containingDeclaration
|
||||
var b: DeclarationDescriptor? = second.containingDeclaration
|
||||
while (a != null && b != null) {
|
||||
when {
|
||||
a is ModuleDescriptor -> return b is ModuleDescriptor
|
||||
b is ModuleDescriptor -> return false
|
||||
a is PackageFragmentDescriptor -> return b is PackageFragmentDescriptor && a.fqName == b.fqName
|
||||
b is PackageFragmentDescriptor -> return false
|
||||
a.name != b.name -> return false
|
||||
else -> {
|
||||
a = a.containingDeclaration
|
||||
b = b.containingDeclaration
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun hasMeaningfulFqName(descriptor: ClassifierDescriptor): Boolean =
|
||||
!ErrorUtils.isError(descriptor) && !DescriptorUtils.isLocal(descriptor)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user