Implement IrType.equals/hashCode via isEqualTo/toHashCode

This commit is contained in:
Alexander Udalov
2019-04-01 19:55:18 +02:00
parent 8cce5dc8de
commit dd2c7aff6e
10 changed files with 103 additions and 76 deletions
@@ -53,8 +53,8 @@ class CheckIrElementVisitor(
if (!config.checkTypes)
return
if (!expectedType.isEqualTo(type)) {
reportError(this, "unexpected expression.type: expected $expectedType, got ${type.render()}")
if (type != expectedType) {
reportError(this, "unexpected expression.type: expected ${expectedType.render()}, got ${type.render()}")
}
}
@@ -20,21 +20,24 @@ import org.jetbrains.kotlin.backend.common.CommonBackendContext
import org.jetbrains.kotlin.backend.common.FileLoweringPass
import org.jetbrains.kotlin.backend.common.atMostOne
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope
import org.jetbrains.kotlin.ir.builders.createTmpVariable
import org.jetbrains.kotlin.ir.builders.irCall
import org.jetbrains.kotlin.ir.builders.irGet
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.declarations.IrSymbolDeclaration
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrStringConcatenation
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.isNullableAny
import org.jetbrains.kotlin.ir.util.constructors
import org.jetbrains.kotlin.ir.util.functions
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.name.Name
/**
* This lowering pass replaces [IrStringConcatenation]s with StringBuilder appends.
*/
@@ -65,19 +68,17 @@ private class StringConcatenationTransformer(val lower: StringConcatenationLower
private val toStringFunction = stringBuilder.functions.single {
it.valueParameters.size == 0 && it.name == nameToString
}
private val defaultAppendFunction = stringBuilder.functions.single {
it.name == nameAppend &&
it.valueParameters.size == 1 &&
it.valueParameters.single().type.isNullableAny()
}
private val appendFunctions: Map<IrType, IrSimpleFunction?> =
typesWithSpecialAppendFunction.map { type ->
type to stringBuilder.functions.toList().atMostOne {
it.name == nameAppend &&
it.valueParameters.size == 1 &&
it.valueParameters.single().type.isEqualTo(type)
it.name == nameAppend && it.valueParameters.singleOrNull()?.type == type
}
}.toMap()
@@ -29,7 +29,6 @@ import org.jetbrains.kotlin.ir.types.IrSimpleType
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.IrTypeProjection
import org.jetbrains.kotlin.ir.types.impl.IrSimpleTypeImpl
import org.jetbrains.kotlin.ir.types.isEqualTo
import org.jetbrains.kotlin.ir.util.isInlined
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
@@ -546,7 +545,7 @@ class CallableReferenceLowering(val context: JsIrBackendContext) : FileLoweringP
val value = JsIrBuilder.buildGetValue(unboundParamSymbols[i])
val parameter = callTarget.valueParameters[j]
val argument =
if (parameter.varargElementType?.let { closureParam.type.isEqualTo(it) } == true) {
if (parameter.varargElementType == closureParam.type) {
// fun foo(x: X, vararg y: Y): Z
// val r: (X, Y) -> Z = ::foo
val tailValues = unboundParamSymbols.drop(i)
@@ -5,12 +5,12 @@
package org.jetbrains.kotlin.ir.backend.js.lower.calls
import org.jetbrains.kotlin.ir.util.irCall
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.irCall
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.types.SimpleType
@@ -58,21 +58,7 @@ internal fun MemberToTransformer.add(type: IrType, name: Name, v: (IrCall) -> Ir
put(SimpleMemberKey(type, name), v)
}
internal class SimpleMemberKey(val klass: IrType, val name: Name) {
// TODO drop custom equals and hashCode when IrTypes will have right equals
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as SimpleMemberKey
if (name != other.name) return false
return klass.isEqualTo(other.klass)
}
override fun hashCode() = 31 * klass.toHashCode() + name.hashCode()
}
internal data class SimpleMemberKey(val klass: IrType, val name: Name)
enum class PrimitiveType {
FLOATING_POINT_NUMBER,
@@ -57,6 +57,12 @@ import org.jetbrains.org.objectweb.asm.Type
//Hack implementation to support CR java types in lower
class CrIrType(val type: Type) : IrType {
override val annotations = emptyList()
override fun equals(other: Any?): Boolean =
other is CrIrType && type == other.type
override fun hashCode(): Int =
type.hashCode()
}
internal val callableReferencePhase = makeIrFilePhase(
@@ -5,13 +5,18 @@
package org.jetbrains.kotlin.ir.symbols
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassifierDescriptor
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
interface IrClassifierEqualityChecker {
fun areEqual(left: IrClassifierSymbol, right: IrClassifierSymbol): Boolean
fun getHashCode(symbol: IrClassifierSymbol): Int
}
object FqNameEqualityChecker : IrClassifierEqualityChecker {
@@ -23,8 +28,24 @@ object FqNameEqualityChecker : IrClassifierEqualityChecker {
return checkViaDeclarations(left.owner, right.owner)
}
private val IrDeclarationWithName.fqName
get(): FqName? {
override fun getHashCode(symbol: IrClassifierSymbol): Int {
if (symbol.isBound) {
val owner = symbol.owner
if (owner is IrClass && !isLocalClass(owner)) {
return owner.fqName.hashCode()
}
return owner.hashCode()
}
val descriptor = symbol.descriptor
if (descriptor is ClassDescriptor && !DescriptorUtils.isLocal(descriptor)) {
return descriptor.fqNameSafe.hashCode()
}
return descriptor.hashCode()
}
private val IrDeclarationWithName.fqName: FqName?
get() {
val parentFqName = when (val parent = parent) {
is IrPackageFragment -> parent.fqName
is IrDeclarationWithName -> parent.fqName
@@ -11,6 +11,18 @@ import org.jetbrains.kotlin.types.Variance
interface IrType {
val annotations: List<IrCall>
/**
* @return true if this type is equal to [other] symbolically. Note that this is NOT EQUIVALENT to the full type checking algorithm
* used in the compiler frontend. For example, this method will return `false` on the types `List<*>` and `List<Any?>`,
* whereas the real type checker from the compiler frontend would return `true`.
*
* Classes are compared by FQ names, which means that even if two types refer to different symbols of the class with the same FQ name,
* such types will be considered equal. Type annotations do not have any effect on the behavior of this method.
*/
override fun equals(other: Any?): Boolean
override fun hashCode(): Int
}
interface IrErrorType : IrType
@@ -23,11 +35,15 @@ interface IrSimpleType : IrType {
val arguments: List<IrTypeArgument>
}
interface IrTypeArgument
interface IrTypeArgument {
override fun equals(other: Any?): Boolean
override fun hashCode(): Int
}
interface IrStarProjection : IrTypeArgument
interface IrTypeProjection : IrTypeArgument {
val variance: Variance
val type: IrType
}
}
@@ -5,11 +5,13 @@
package org.jetbrains.kotlin.ir.types
import org.jetbrains.kotlin.ir.symbols.*
import org.jetbrains.kotlin.ir.symbols.FqNameEqualityChecker
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrClassifierSymbol
import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol
import org.jetbrains.kotlin.ir.util.render
import org.jetbrains.kotlin.utils.DFS
fun IrClassifierSymbol.superTypes() = when (this) {
is IrClassSymbol -> owner.superTypes
is IrTypeParameterSymbol -> owner.superTypes
@@ -26,41 +28,6 @@ fun IrType.isSubtypeOfClass(superClass: IrClassSymbol): Boolean {
return classifier.isSubtypeOfClass(superClass)
}
fun IrType.isEqualTo(that: IrType): Boolean {
if (this is IrDynamicType && that is IrDynamicType) return true
if (this is IrErrorType || that is IrErrorType) return false
if (this === that) return true
if (this is IrSimpleType && that is IrSimpleType) return FqNameEqualityChecker.areEqual(this.classifier, that.classifier) &&
this.arguments.zip(that.arguments).all { (ths, tht) ->
when (ths) {
is IrStarProjection -> tht is IrStarProjection
is IrTypeProjection -> tht is IrTypeProjection
&& ths.variance == tht.variance
&& ths.type.isEqualTo(tht.type)
else -> error("Unsupported Type Argument")
}
}
return false
}
fun IrTypeArgument.toHashCode(): Int = when (this) {
is IrTypeProjection -> 31 * type.toHashCode() + variance.hashCode()
is IrStarProjection -> hashCode()
else -> 0
}
fun IrType.toHashCode(): Int {
if (this is IrDynamicType) return -1
if (this is IrErrorType) return 0
require(this is IrSimpleType)
var result = classifier.hashCode()
result = 31 * result + arguments.fold(0) { a, t -> 31 * a + t.toHashCode() }
return 31 * result + if (hasQuestionMark) 1 else 0
}
fun Collection<IrClassifierSymbol>.commonSuperclass(): IrClassifierSymbol {
var superClassifiers: MutableSet<IrClassifierSymbol>? = null
@@ -91,4 +58,4 @@ fun Collection<IrClassifierSymbol>.commonSuperclass(): IrClassifierSymbol {
postfix = "]"
) { it.owner.render() }}"
)
}
}
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.ir.types.impl
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.symbols.FqNameEqualityChecker
import org.jetbrains.kotlin.ir.symbols.IrClassifierSymbol
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.types.KotlinType
@@ -45,12 +46,28 @@ class IrSimpleTypeImpl(
other.classifier, other.hasQuestionMark, other.arguments, other.annotations, variance
)
override fun equals(other: Any?): Boolean =
other is IrSimpleTypeImpl &&
FqNameEqualityChecker.areEqual(classifier, other.classifier) &&
hasQuestionMark == other.hasQuestionMark &&
arguments == other.arguments &&
variance == other.variance
override fun hashCode(): Int =
((FqNameEqualityChecker.getHashCode(classifier) * 31 + hasQuestionMark.hashCode()) * 31 +
arguments.hashCode()) * 31 + variance.hashCode()
}
class IrTypeProjectionImpl internal constructor(
override val type: IrType,
override val variance: Variance
) : IrTypeProjection
) : IrTypeProjection {
override fun equals(other: Any?): Boolean =
other is IrTypeProjectionImpl && type == other.type && variance == other.variance
override fun hashCode(): Int =
type.hashCode() * 31 + variance.hashCode()
}
fun makeTypeProjection(type: IrType, variance: Variance): IrTypeProjection =
when {
@@ -59,4 +76,4 @@ fun makeTypeProjection(type: IrType, variance: Variance): IrTypeProjection =
type is IrDynamicType -> IrDynamicTypeImpl(null, type.annotations, variance)
type is IrErrorType -> IrErrorTypeImpl(null, type.annotations, variance)
else -> IrTypeProjectionImpl(type, variance)
}
}
@@ -16,31 +16,45 @@ abstract class IrTypeBase(
override val annotations: List<IrCall>,
override val variance: Variance
) : IrType, IrTypeProjection {
override val type: IrType get() = this
}
class IrErrorTypeImpl(
kotlinType: KotlinType?,
annotations: List<IrCall>,
variance: Variance
) : IrTypeBase(kotlinType, annotations, variance), IrErrorType
) : IrTypeBase(kotlinType, annotations, variance), IrErrorType {
override fun equals(other: Any?): Boolean = other is IrErrorTypeImpl
override fun hashCode(): Int = IrErrorTypeImpl::class.java.name.hashCode()
}
class IrDynamicTypeImpl(
kotlinType: KotlinType?,
annotations: List<IrCall>,
variance: Variance
) : IrTypeBase(kotlinType, annotations, variance), IrDynamicType, IrTypeProjection
) : IrTypeBase(kotlinType, annotations, variance), IrDynamicType, IrTypeProjection {
override fun equals(other: Any?): Boolean = other is IrDynamicTypeImpl
override fun hashCode(): Int = IrDynamicTypeImpl::class.java.name.hashCode()
}
val IrType.originalKotlinType: KotlinType?
get() = safeAs<IrTypeBase>()?.kotlinType
object IrStarProjectionImpl : IrStarProjection
object IrStarProjectionImpl : IrStarProjection {
override fun equals(other: Any?): Boolean = this === other
override fun hashCode(): Int = System.identityHashCode(this)
}
@Deprecated("Hack to temporary cover late type initialization")
object IrUninitializedType : IrType {
override val annotations: List<IrCall> = emptyList()
override fun equals(other: Any?): Boolean = this === other
override fun hashCode(): Int = System.identityHashCode(this)
}