From aa4ad90f3d810767bd0187a56cb6df113f0b7011 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Doln=C3=ADk?= Date: Mon, 20 Nov 2023 10:21:19 +0100 Subject: [PATCH] Add warnings when SKIE renames a class or function. --- SKIE/acceptance-tests | 2 +- ...onsConflictingWithTypeDeclarationsPhase.kt | 15 +- ...ameConflictingCallableDeclarationsPhase.kt | 2 + .../skie/phases/memberconflicts/Signature.kt | 109 ++++++++++--- .../memberconflicts/UniqueSignatureSet.kt | 82 ++++++---- ...RenameTypesConflictingWithKeywordsPhase.kt | 8 +- ...meTypesConflictingWithKotlinModulePhase.kt | 20 +-- ...RenameTypesConflictsWithOtherTypesPhase.kt | 9 +- .../skie/sir/element/SirConstructor.kt | 2 - .../touchlab/skie/sir/element/SirEnumCase.kt | 3 + .../touchlab/skie/sir/element/SirFunction.kt | 3 + .../touchlab/skie/sir/element/SirProperty.kt | 3 +- .../skie/sir/element/SirSimpleFunction.kt | 3 +- .../util/SirDeclaration+resolveCollision.kt | 146 ++++++++++++++++++ 14 files changed, 312 insertions(+), 95 deletions(-) create mode 100644 SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/util/SirDeclaration+resolveCollision.kt diff --git a/SKIE/acceptance-tests b/SKIE/acceptance-tests index c151a83ce..e6d0d3bf9 160000 --- a/SKIE/acceptance-tests +++ b/SKIE/acceptance-tests @@ -1 +1 @@ -Subproject commit c151a83ce2c86883ed25c179a618e395bec01303 +Subproject commit e6d0d3bf948c050ef4e73bb0bcff130964b9a1d3 diff --git a/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/phases/memberconflicts/RenameCallableDeclarationsConflictingWithTypeDeclarationsPhase.kt b/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/phases/memberconflicts/RenameCallableDeclarationsConflictingWithTypeDeclarationsPhase.kt index 88f6a1943..95823d638 100644 --- a/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/phases/memberconflicts/RenameCallableDeclarationsConflictingWithTypeDeclarationsPhase.kt +++ b/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/phases/memberconflicts/RenameCallableDeclarationsConflictingWithTypeDeclarationsPhase.kt @@ -3,9 +3,7 @@ package co.touchlab.skie.phases.memberconflicts import co.touchlab.skie.phases.SirPhase import co.touchlab.skie.sir.element.SirCallableDeclaration import co.touchlab.skie.sir.element.SirClass -import co.touchlab.skie.sir.element.SirConstructor -import co.touchlab.skie.sir.element.SirProperty -import co.touchlab.skie.sir.element.SirSimpleFunction +import co.touchlab.skie.util.resolveCollisionWithWarning // TODO This does not work for nested classes object RenameCallableDeclarationsConflictingWithTypeDeclarationsPhase : SirPhase { @@ -24,15 +22,10 @@ object RenameCallableDeclarationsConflictingWithTypeDeclarationsPhase : SirPhase } } + context(SirPhase.Context) private fun SirCallableDeclaration.renameIfConflictsWith(reservedNames: Set) { - if (this.identifier !in reservedNames) { - return - } - - when (this) { - is SirConstructor -> error("Constructors cannot be in a global scope: $this") - is SirSimpleFunction -> this.identifier += "_" - is SirProperty -> this.identifier += "_" + this.resolveCollisionWithWarning { + if (identifierAfterVisibilityChanges in reservedNames) "a type name '$identifierAfterVisibilityChanges'" else null } } } diff --git a/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/phases/memberconflicts/RenameConflictingCallableDeclarationsPhase.kt b/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/phases/memberconflicts/RenameConflictingCallableDeclarationsPhase.kt index f7a562362..cb5863658 100644 --- a/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/phases/memberconflicts/RenameConflictingCallableDeclarationsPhase.kt +++ b/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/phases/memberconflicts/RenameConflictingCallableDeclarationsPhase.kt @@ -141,12 +141,14 @@ object RenameConflictingCallableDeclarationsPhase : SirPhase { private val SirDeclarationParent.containerFqName: String get() = (this.parent?.containerFqName ?: "") + this.toString() + context(SirPhase.Context) private fun UniqueSignatureSet.addEnumCases(enumCases: List) { enumCases.forEach { this.add(it) } } + context(SirPhase.Context) private fun UniqueSignatureSet.addCallableDeclarations(callableDeclarations: List) { callableDeclarations.forEach { this.add(it) diff --git a/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/phases/memberconflicts/Signature.kt b/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/phases/memberconflicts/Signature.kt index 87084ee1b..e52634fce 100644 --- a/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/phases/memberconflicts/Signature.kt +++ b/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/phases/memberconflicts/Signature.kt @@ -25,13 +25,42 @@ sealed class Signature { abstract val returnType: ReturnType abstract val scope: Scope - class Function( + sealed class Function : Signature() + + class SimpleFunction( override val receiver: Receiver, override val identifier: String, override val valueParameters: List, override val returnType: ReturnType, override val scope: Scope, - ) : Signature() + ) : Function() { + + override fun toString(): String = + ("static ".takeIf { scope == Scope.Static } ?: "") + + "func " + + "$receiver.".takeIf { receiver !is Receiver.None } + + identifier + + "(${valueParameters.joinToString()})" + + " -> $returnType" + } + + class Constructor( + override val receiver: Receiver.Constructor, + override val valueParameters: List, + ) : Function() { + + override val identifier: String = "init" + + override val returnType: ReturnType = ReturnType.Specific(receiver.sirClass.defaultType.signatureType) + + override val scope: Scope = Scope.Static + + override fun toString(): String = + "func " + + "$receiver." + + identifier + + "(${valueParameters.joinToString()})" + } class Property( override val receiver: Receiver, @@ -42,10 +71,16 @@ sealed class Signature { override val valueParameters: List = emptyList() override val returnType: ReturnType = ReturnType.Any + + override fun toString(): String = + ("static ".takeIf { scope == Scope.Static } ?: "") + + "var " + + "$receiver.".takeIf { receiver !is Receiver.None } + + identifier } class EnumCase( - override val receiver: Receiver, + override val receiver: Receiver.Simple, override val identifier: String, ) : Signature() { @@ -54,6 +89,9 @@ sealed class Signature { override val returnType: ReturnType = ReturnType.Any override val scope: Scope = Scope.Static + + override fun toString(): String = + "case $receiver.$identifier" } override fun equals(other: Any?): Boolean { @@ -80,22 +118,32 @@ sealed class Signature { return result } - override fun toString(): String = - "Signature.${this::class.simpleName}(" + - "receiver=$receiver, " + - "identifier='$identifier', " + - "valueParameters=$valueParameters, " + - "returnType=$returnType, " + - "scope=$scope" + - ")" - sealed interface Receiver { - data class Simple(val type: Type.Class, val constraints: Set) : Receiver + data class Simple(val type: Type.Class, val constraints: Set) : Receiver { - data class Constructor(val sirClass: SirClass, val constraints: Set) : Receiver + override fun toString(): String = + if (constraints.isEmpty()) { + type.toString() + } else { + "($type" + "where ${constraints.joinToString(", ")})" + } + } - object None : Receiver + data class Constructor(val sirClass: SirClass, val constraints: Set) : Receiver { + + override fun toString(): String = + if (constraints.isEmpty()) { + sirClass.fqName.toString() + } else { + "(${sirClass.fqName}" + "where ${constraints.joinToString(", ")})" + } + } + + object None : Receiver { + + override fun toString(): String = "None" + } data class Constraint(val typeParameterName: String, val bounds: Set) { @@ -103,10 +151,16 @@ sealed class Signature { typeParameterName = conditionalConstraint.typeParameter.name, bounds = conditionalConstraint.bounds.map { it.signatureType }.toSet(), ) + + override fun toString(): String = + "$typeParameterName: ${bounds.joinToString(" & ")}" } } - data class ValueParameter(val argumentLabel: String, val type: String) + data class ValueParameter(val argumentLabel: String, val type: String) { + + override fun toString(): String = "$argumentLabel: $type" + } sealed interface ReturnType { @@ -123,6 +177,8 @@ sealed class Signature { } override fun hashCode(): Int = 0 + + override fun toString(): String = type.toString() } object Any : ReturnType { @@ -134,6 +190,8 @@ sealed class Signature { } override fun hashCode(): Int = 0 + + override fun toString(): String = "AnyType" } } @@ -153,9 +211,11 @@ sealed class Signature { } override fun hashCode(): Int = 0 + + override fun toString(): String = sirClass.fqName.toString() + ("<${typeArguments.joinToString()}>".takeIf { typeArguments.isNotEmpty() } ?: "") } - class Optional(val nested: Type) : Type { + data class Optional(val nested: Type) : Type { override fun equals(other: Any?): Boolean { if (this === other) return true @@ -167,9 +227,11 @@ sealed class Signature { } override fun hashCode(): Int = 1 + + override fun toString(): String = "$nested?" } - class Special(val name: String) : Type { + data class Special(val name: String) : Type { override fun equals(other: Any?): Boolean { if (this === other) return true @@ -181,6 +243,8 @@ sealed class Signature { } override fun hashCode(): Int = name.hashCode() + + override fun toString(): String = name } class TypeArgument(val type: SirType) { @@ -197,6 +261,8 @@ sealed class Signature { } override fun hashCode(): Int = canonicalName.hashCode() + + override fun toString(): String = canonicalName } } @@ -222,7 +288,7 @@ sealed class Signature { } operator fun invoke(function: SirSimpleFunction): Signature = - Function( + SimpleFunction( receiver = function.receiver, identifier = function.identifierAfterVisibilityChanges, valueParameters = function.signatureValueParameters, @@ -237,12 +303,9 @@ sealed class Signature { "Constructors should always have a constructor receiver. Was: $receiver" } - return Function( + return Constructor( receiver = receiver, - identifier = constructor.identifierAfterVisibilityChanges, valueParameters = constructor.signatureValueParameters, - returnType = ReturnType.Specific(receiver.sirClass.defaultType.signatureType), - scope = constructor.signatureScope, ) } diff --git a/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/phases/memberconflicts/UniqueSignatureSet.kt b/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/phases/memberconflicts/UniqueSignatureSet.kt index f7463e89e..8e160bbbd 100644 --- a/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/phases/memberconflicts/UniqueSignatureSet.kt +++ b/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/phases/memberconflicts/UniqueSignatureSet.kt @@ -1,18 +1,22 @@ package co.touchlab.skie.phases.memberconflicts +import co.touchlab.skie.phases.SirPhase import co.touchlab.skie.sir.element.SirCallableDeclaration import co.touchlab.skie.sir.element.SirConstructor import co.touchlab.skie.sir.element.SirEnumCase import co.touchlab.skie.sir.element.SirProperty import co.touchlab.skie.sir.element.SirSimpleFunction import co.touchlab.skie.sir.element.getEntireOverrideHierarchy +import co.touchlab.skie.util.resolveCollisionWithWarning class UniqueSignatureSet { private val alreadyAddedDeclarations = mutableSetOf() private val alreadyAddedEnumCase = mutableSetOf() - private val existingSignatures = mutableSetOf() + // Map so that we can get the signatures for conflicts + private val existingSignaturesMap = mutableMapOf() + context(SirPhase.Context) fun add(callableDeclaration: SirCallableDeclaration) { if (callableDeclaration in alreadyAddedDeclarations) { return @@ -20,27 +24,37 @@ class UniqueSignatureSet { val group = Group(callableDeclaration) - while (group.createsConflict) { - group.mangle() + group.resolveCollisionWithWarning { + val signature = signature + + if (signature in existingSignaturesMap) { + "an another declaration '${existingSignaturesMap[signature]}'" + } else { + null + } } - group.addToAlreadyAdded() + group.addToCaches() } + context(SirPhase.Context) fun add(enumCase: SirEnumCase) { if (enumCase in alreadyAddedEnumCase) { return } - var signature = enumCase.signature - - while (signature in existingSignatures) { - enumCase.simpleName += "_" + enumCase.resolveCollisionWithWarning { + val signature = signature - signature = enumCase.signature + if (signature in existingSignaturesMap) { + "an another declaration '${existingSignaturesMap[signature]}'" + } else { + null + } } - existingSignatures.add(signature) + val signature = enumCase.signature + existingSignaturesMap[signature] = signature alreadyAddedEnumCase.add(enumCase) } @@ -50,39 +64,39 @@ class UniqueSignatureSet { private val callableDeclarations = representative.getEntireOverrideHierarchy() - private var signatures = callableDeclarations.map { it.signature } - - val createsConflict: Boolean - get() = signatures.any { it in existingSignatures } + context(SirPhase.Context) + fun resolveCollisionWithWarning(collisionReasonProvider: SirCallableDeclaration.() -> String?) { + do { + var changed = false - fun mangle() { - callableDeclarations.forEach { - it.mangle() - } + callableDeclarations.forEach { + // Avoid short-circuiting + changed = it.resolveCollisionWithWarning(collisionReasonProvider) || changed - signatures = callableDeclarations.map { it.signature } + unifyNames(it) + } + } while (changed) } - private fun SirCallableDeclaration.mangle() { - when (this) { - is SirSimpleFunction -> this.identifier += "_" - is SirProperty -> this.identifier += "_" - is SirConstructor -> { - val lastValueParameter = this.valueParameters.lastOrNull() - ?: error( - "Cannot mangle $this because it does not have any value parameters. " + - "This should never happen because constructors without value parameters " + - "shouldn't create conflicts (as they are processed first).", - ) - - lastValueParameter.label = lastValueParameter.labelOrName + "_" + private fun unifyNames(basedOn: SirCallableDeclaration) { + callableDeclarations.forEach { + when (it) { + is SirSimpleFunction -> it.identifier = basedOn.identifier + is SirProperty -> it.identifier = basedOn.identifier + is SirConstructor -> { + it.valueParameters.lastOrNull()?.label = (basedOn as SirConstructor).valueParameters.lastOrNull()?.label + } } } } - fun addToAlreadyAdded() { + fun addToCaches() { alreadyAddedDeclarations.addAll(callableDeclarations) - existingSignatures.addAll(signatures) + callableDeclarations.forEach { + val signature = it.signature + + existingSignaturesMap[signature] = signature + } } } } diff --git a/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/phases/typeconflicts/RenameTypesConflictingWithKeywordsPhase.kt b/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/phases/typeconflicts/RenameTypesConflictingWithKeywordsPhase.kt index 9f4386f04..0e9671ec7 100644 --- a/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/phases/typeconflicts/RenameTypesConflictingWithKeywordsPhase.kt +++ b/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/phases/typeconflicts/RenameTypesConflictingWithKeywordsPhase.kt @@ -1,6 +1,7 @@ package co.touchlab.skie.phases.typeconflicts import co.touchlab.skie.phases.SirPhase +import co.touchlab.skie.util.resolveCollisionWithWarning object RenameTypesConflictingWithKeywordsPhase : SirPhase { @@ -13,9 +14,10 @@ object RenameTypesConflictingWithKeywordsPhase : SirPhase { context(SirPhase.Context) override fun execute() { sirProvider.allLocalTypeDeclarations - .filter { it.simpleName in problematicKeywords } - .forEach { - it.baseName += "_" + .forEach { declaration -> + declaration.resolveCollisionWithWarning { + if (declaration.simpleName in problematicKeywords) "a reserved Swift keyword '${declaration.simpleName}'" else null + } } } } diff --git a/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/phases/typeconflicts/RenameTypesConflictingWithKotlinModulePhase.kt b/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/phases/typeconflicts/RenameTypesConflictingWithKotlinModulePhase.kt index 7bb58045a..83decc6e1 100644 --- a/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/phases/typeconflicts/RenameTypesConflictingWithKotlinModulePhase.kt +++ b/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/phases/typeconflicts/RenameTypesConflictingWithKotlinModulePhase.kt @@ -1,6 +1,7 @@ package co.touchlab.skie.phases.typeconflicts import co.touchlab.skie.phases.SirPhase +import co.touchlab.skie.util.resolveCollisionWithWarning object RenameTypesConflictingWithKotlinModulePhase : SirPhase { @@ -8,25 +9,10 @@ object RenameTypesConflictingWithKotlinModulePhase : SirPhase { override fun execute() { val moduleName = sirProvider.kotlinModule.name - var collisionExists = false - sirProvider.allLocalTypeDeclarations.forEach { type -> - if (type.simpleName == moduleName) { - type.baseName += "_" - collisionExists = true + type.resolveCollisionWithWarning { + if (type.simpleName == moduleName) "the framework name '$moduleName'" else null } } - - if (collisionExists) { - logModuleNameCollisionWarning(moduleName) - } - } - - context(SirPhase.Context) - private fun logModuleNameCollisionWarning(moduleName: String) { - reporter.warning( - "Type '$moduleName' was renamed to '${moduleName}_' " + - "because it has the same name as the produced framework which is forbidden.", - ) } } diff --git a/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/phases/typeconflicts/RenameTypesConflictsWithOtherTypesPhase.kt b/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/phases/typeconflicts/RenameTypesConflictsWithOtherTypesPhase.kt index 7aa15d055..568a802fb 100644 --- a/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/phases/typeconflicts/RenameTypesConflictsWithOtherTypesPhase.kt +++ b/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/phases/typeconflicts/RenameTypesConflictsWithOtherTypesPhase.kt @@ -8,6 +8,7 @@ import co.touchlab.skie.sir.element.SirClass import co.touchlab.skie.sir.element.SirTypeDeclaration import co.touchlab.skie.sir.element.SirVisibility import co.touchlab.skie.sir.element.isRemoved +import co.touchlab.skie.util.resolveCollisionWithWarning import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe @@ -85,8 +86,12 @@ object RenameTypesConflictsWithOtherTypesPhase : SirPhase { val existingFqNames = mutableSetOf() typeDeclarations.forEach { typeDeclaration -> - while (typeDeclaration.fqName.toString() in existingFqNames) { - typeDeclaration.baseName += "_" + typeDeclaration.resolveCollisionWithWarning { + if (typeDeclaration.fqName.toString() in existingFqNames) { + "an another type named '${typeDeclaration.fqName}'" + } else { + null + } } existingFqNames.add(typeDeclaration.fqName.toString()) diff --git a/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/sir/element/SirConstructor.kt b/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/sir/element/SirConstructor.kt index 0f1e7782f..7daf90ba8 100644 --- a/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/sir/element/SirConstructor.kt +++ b/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/sir/element/SirConstructor.kt @@ -28,8 +28,6 @@ class SirConstructor( override val valueParameters: MutableList = mutableListOf() - override fun toString(): String = "${this::class.simpleName}: $name" - companion object { context(SirDeclarationNamespace) diff --git a/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/sir/element/SirEnumCase.kt b/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/sir/element/SirEnumCase.kt index 2a18cf3c9..6f4690208 100644 --- a/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/sir/element/SirEnumCase.kt +++ b/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/sir/element/SirEnumCase.kt @@ -11,6 +11,9 @@ class SirEnumCase( val associatedValues: MutableList = mutableListOf() + val index: Int + get() = parent.enumCases.indexOf(this) + override fun toString(): String = "${this::class.simpleName}: $simpleName" companion object { diff --git a/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/sir/element/SirFunction.kt b/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/sir/element/SirFunction.kt index e267d0e69..c82ad6c7b 100644 --- a/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/sir/element/SirFunction.kt +++ b/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/sir/element/SirFunction.kt @@ -1,5 +1,6 @@ package co.touchlab.skie.sir.element +import co.touchlab.skie.phases.memberconflicts.signature import io.outfoxx.swiftpoet.CodeBlock import io.outfoxx.swiftpoet.FunctionSpec import io.outfoxx.swiftpoet.Modifier @@ -49,6 +50,8 @@ sealed class SirFunction( } protected abstract val identifierForReference: String + + override fun toString(): String = this.signature.toString() } fun SirFunction.copyValueParametersFrom(other: SirFunction) { diff --git a/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/sir/element/SirProperty.kt b/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/sir/element/SirProperty.kt index cb158b8d3..67ad32678 100644 --- a/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/sir/element/SirProperty.kt +++ b/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/sir/element/SirProperty.kt @@ -1,6 +1,7 @@ package co.touchlab.skie.sir.element import co.touchlab.skie.kir.element.DeprecationLevel +import co.touchlab.skie.phases.memberconflicts.signature import co.touchlab.skie.sir.element.util.sirDeclarationParent import co.touchlab.skie.sir.type.SirType import io.outfoxx.swiftpoet.CodeBlock @@ -76,7 +77,7 @@ class SirProperty( this.setter = setter } - override fun toString(): String = "${this::class.simpleName}: $name" + override fun toString(): String = this.signature.toString() companion object { diff --git a/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/sir/element/SirSimpleFunction.kt b/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/sir/element/SirSimpleFunction.kt index 9779ba1fb..c1ec85cac 100644 --- a/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/sir/element/SirSimpleFunction.kt +++ b/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/sir/element/SirSimpleFunction.kt @@ -4,6 +4,7 @@ import co.touchlab.skie.sir.element.util.sirDeclarationParent import co.touchlab.skie.sir.type.SirType import io.outfoxx.swiftpoet.Modifier import co.touchlab.skie.kir.element.DeprecationLevel +import co.touchlab.skie.phases.memberconflicts.signature import io.outfoxx.swiftpoet.CodeBlock class SirSimpleFunction( @@ -61,7 +62,7 @@ class SirSimpleFunction( overridableDeclarationDelegate.removeOverriddenBy(declaration) } - override fun toString(): String = "${this::class.simpleName}: $name" + override fun toString(): String = this.signature.toString() companion object { diff --git a/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/util/SirDeclaration+resolveCollision.kt b/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/util/SirDeclaration+resolveCollision.kt new file mode 100644 index 000000000..360a1c4ab --- /dev/null +++ b/SKIE/compiler/kotlin-plugin/src/kgp_common/kotlin/co/touchlab/skie/util/SirDeclaration+resolveCollision.kt @@ -0,0 +1,146 @@ +@file:Suppress("UNCHECKED_CAST") + +package co.touchlab.skie.util + +import co.touchlab.skie.kir.element.KirCallableDeclaration +import co.touchlab.skie.kir.element.KirElement +import co.touchlab.skie.kir.element.KirEnumEntry +import co.touchlab.skie.kir.element.classDescriptorOrNull +import co.touchlab.skie.phases.SirPhase +import co.touchlab.skie.sir.element.SirCallableDeclaration +import co.touchlab.skie.sir.element.SirClass +import co.touchlab.skie.sir.element.SirConstructor +import co.touchlab.skie.sir.element.SirEnumCase +import co.touchlab.skie.sir.element.SirProperty +import co.touchlab.skie.sir.element.SirSimpleFunction +import co.touchlab.skie.sir.element.SirTypeDeclaration +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor + +context(SirPhase.Context) +fun T.resolveCollisionWithWarning(collisionReasonProvider: T.() -> String?): Boolean = + resolveCollisionWithWarning(collisionReasonProvider) { + baseName += "_" + } + +context(SirPhase.Context) +fun T.resolveCollisionWithWarning(collisionReasonProvider: T.() -> String?): Boolean = + when (val declaration = this as SirCallableDeclaration) { + is SirConstructor -> declaration.resolveCollisionWithWarning(collisionReasonProvider as SirConstructor.() -> String?) + is SirSimpleFunction -> declaration.resolveCollisionWithWarning(collisionReasonProvider as SirSimpleFunction.() -> String?) + is SirProperty -> declaration.resolveCollisionWithWarning(collisionReasonProvider as SirProperty.() -> String?) + } + +context(SirPhase.Context) +fun SirConstructor.resolveCollisionWithWarning(collisionReasonProvider: SirConstructor.() -> String?): Boolean = + resolveCollisionWithWarning(collisionReasonProvider) { + val lastValueParameter = valueParameters.lastOrNull() + ?: error("Cannot resolve collision for $this because it does not have any value parameters.") + + lastValueParameter.label = lastValueParameter.labelOrName + "_" + } + +context(SirPhase.Context) +fun SirProperty.resolveCollisionWithWarning(collisionReasonProvider: SirProperty.() -> String?): Boolean = + resolveCollisionWithWarning(collisionReasonProvider) { + identifier += "_" + } + +context(SirPhase.Context) +fun SirSimpleFunction.resolveCollisionWithWarning(collisionReasonProvider: SirSimpleFunction.() -> String?): Boolean = + resolveCollisionWithWarning(collisionReasonProvider) { + identifier += "_" + } + +context(SirPhase.Context) +fun SirEnumCase.resolveCollisionWithWarning(collisionReasonProvider: SirEnumCase.() -> String?): Boolean = + resolveCollisionWithWarning( + collisionReasonProvider = collisionReasonProvider, + rename = { simpleName += "_" }, + getName = { parent.fqName.toLocalString() + "." + simpleName }, + findKirElement = { kirProvider.findClass(parent)?.enumEntries?.get(index) }, + getDescriptor = { descriptor }, + ) + +context(SirPhase.Context) +private inline fun T.resolveCollisionWithWarning( + collisionReasonProvider: T.() -> String?, + rename: () -> Unit, +): Boolean = + resolveCollisionWithWarning( + collisionReasonProvider = collisionReasonProvider, + rename = rename, + getName = { name }, + findKirElement = { + kirProvider.findCallableDeclaration(this) + ?: if (this is SirProperty) kirProvider.findEnumEntry(this) else null + }, + getDescriptor = { + when (this) { + is KirCallableDeclaration<*> -> descriptor + is KirEnumEntry -> descriptor + else -> null + } + }, + ) + +context(SirPhase.Context) +private inline fun T.resolveCollisionWithWarning( + collisionReasonProvider: T.() -> String?, + rename: () -> Unit, +): Boolean = + resolveCollisionWithWarning( + collisionReasonProvider = collisionReasonProvider, + rename = rename, + getName = { fqName.toLocalString() }, + findKirElement = { (this as? SirClass)?.let { kirProvider.findClass(it) } }, + getDescriptor = { classDescriptorOrNull }, + ) + +context(SirPhase.Context) +private inline fun T.resolveCollisionWithWarning( + collisionReasonProvider: T.() -> String?, + rename: () -> Unit, + getName: T.() -> String, + findKirElement: T.() -> K?, + getDescriptor: K.() -> DeclarationDescriptor?, +): Boolean { + val originalName = getName() + + val collisionReason = resolveCollision(collisionReasonProvider, rename) ?: return false + + val newName = getName() + + val kirElement = findKirElement() + if (kirElement != null) { + reportCollision(originalName, newName, collisionReason, kirElement.getDescriptor()) + } + + return true +} + +private inline fun T.resolveCollision(collisionReasonProvider: T.() -> String?, rename: () -> Unit): String? { + val firstCollisionReason = collisionReasonProvider() + + var nextCollisionReason: String? = firstCollisionReason + while (nextCollisionReason != null) { + rename() + + nextCollisionReason = collisionReasonProvider() + } + + return firstCollisionReason +} + +private fun SirPhase.Context.reportCollision( + originalName: String, + newName: String, + collisionReason: String, + declarationDescriptor: DeclarationDescriptor?, +) { + reporter.warning( + message = "'$originalName' was renamed to '$newName' because of a name collision with $collisionReason. " + + "Consider resolving the conflict either by changing the name in Kotlin, or via the @ObjCName annotation. " + + "Using renamed declarations from Swift is not recommended because their name will change if the conflict is resolved.", + declaration = declarationDescriptor, + ) +}