Skip to content

Commit

Permalink
Implement SkieVisibility propagation.
Browse files Browse the repository at this point in the history
  • Loading branch information
FilipDolnik committed May 16, 2024
1 parent 011cd2f commit f0b0cf0
Show file tree
Hide file tree
Showing 19 changed files with 288 additions and 9 deletions.
2 changes: 1 addition & 1 deletion SKIE/acceptance-tests
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,15 @@ annotation class SkieVisibility {
annotation class InternalAndReplaced

/**
* The declaration will either be Public or Internal.
* The callable declaration will either be Public or Internal.
* Which one is chosen depends on whether the declaration is automatically wrapped by SKIE or not.
*
* For example, a top-level function originally exposed as `FileKt.functionName` will be internal, if SKIE generated the global function wrapper for it.
*
* Note that this setting will only affect callable declarations (functions, properties, constructors) - not classes.
*/
@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY, AnnotationTarget.CONSTRUCTOR)
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY, AnnotationTarget.CONSTRUCTOR)
annotation class InternalIfWrapped

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,12 @@ object SkieVisibility : ConfigurationKey.Enum<co.touchlab.skie.configuration.Ski
InternalAndReplaced(SkieVisibility.InternalAndReplaced::class),

/**
* The declaration will either be Public or Internal.
* The callable declaration will either be Public or Internal.
* Which one is chosen depends on whether the declaration is automatically wrapped by SKIE or not.
*
* For example, a top-level function originally exposed as `FileKt.functionName` will be internal, if SKIE generated the global function wrapper for it.
*
* Note that this setting will only affect callable declarations (functions, properties, constructors) - not classes.
*/
InternalIfWrapped(SkieVisibility.InternalIfWrapped::class),

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import co.touchlab.skie.sir.element.SirTypeDeclaration
* This phase ensures that SKIE can use isReplaced internally without having to worry about the declarations already being replaced.
* (For example, isReplaced is used by CreateSirMembersPhase to implement isRefinedInSwift and user-configurable SkieVisibility.)
*/
object CommitSirIsReplacedPhase : SirPhase {
object CommitSirIsReplacedPropertyPhase : SirPhase {

context(SirPhase.Context)
override suspend fun execute() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package co.touchlab.skie.phases.sir.member

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.SirProperty
import co.touchlab.skie.sir.element.SirSimpleFunction
import co.touchlab.skie.sir.element.minimumVisibility
import co.touchlab.skie.sir.type.visibilityConstraint

object PropagateSirVisibilityToMembersPhase : SirPhase {

context(SirPhase.Context)
override suspend fun execute() {
sirProvider.allLocalDeclarations
.filterIsInstance<SirCallableDeclaration>()
.forEach {
updateVisibility(it)
}
}

private fun updateVisibility(sirCallableDeclaration: SirCallableDeclaration) {
when (sirCallableDeclaration) {
is SirConstructor -> updateVisibility(sirCallableDeclaration)
is SirSimpleFunction -> updateVisibility(sirCallableDeclaration)
is SirProperty -> updateVisibility(sirCallableDeclaration)
}
}

private fun updateVisibility(sirConstructor: SirConstructor) {
val allConstraints = listOfNotNull(
sirConstructor.visibility,
sirConstructor.parent.classDeclaration.visibility,
) +
sirConstructor.valueParameters.map { it.type.visibilityConstraint }

sirConstructor.visibility = allConstraints.minimumVisibility()
}

private fun updateVisibility(sirFunction: SirSimpleFunction) {
val allConstraints = listOfNotNull(
sirFunction.visibility,
sirFunction.memberOwner?.visibility,
sirFunction.returnType.visibilityConstraint,
) +
sirFunction.valueParameters.map { it.type.visibilityConstraint } +
sirFunction.typeParameters.flatMap { typeParameter -> typeParameter.bounds.map { it.visibilityConstraint } }

sirFunction.visibility = allConstraints.minimumVisibility()
}

private fun updateVisibility(sirProperty: SirProperty) {
val allConstraints = listOfNotNull(
sirProperty.visibility,
sirProperty.memberOwner?.visibility,
sirProperty.type.visibilityConstraint,
)

sirProperty.visibility = allConstraints.minimumVisibility()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package co.touchlab.skie.phases.sir.type

import co.touchlab.skie.kir.element.KirClass
import co.touchlab.skie.phases.SirPhase
import co.touchlab.skie.sir.element.SirClass
import co.touchlab.skie.sir.element.SirModule
import co.touchlab.skie.sir.element.SirVisibility
import co.touchlab.skie.sir.element.coerceAtMostInSwift
import co.touchlab.skie.sir.element.kirClassOrNull
import co.touchlab.skie.sir.element.superClassType
import co.touchlab.skie.sir.element.superProtocolTypes
import co.touchlab.skie.sir.type.SirType

object PropagateSirVisibilityToClassesPhase : SirPhase {

context(SirPhase.Context)
override suspend fun execute() {
val updaterProvider = TypeVisibilityUpdaterProvider(sirProvider.allLocalClasses)

updaterProvider.allTypeVisibilityUpdaters.forEach {
it.propagateVisibility()
}
}

private class TypeVisibilityUpdaterProvider(
sirClasses: List<SirClass>,
) {

private val cache = sirClasses.associateWith { ClassVisibilityUpdater(it) }

init {
cache.values.forEach {
it.initialize(this)
}
}

val allTypeVisibilityUpdaters: Collection<ClassVisibilityUpdater> = cache.values

operator fun get(sirClass: SirClass): ClassVisibilityUpdater =
cache.getValue(sirClass)
}

private class ClassVisibilityUpdater(private val sirClass: SirClass) {

private val directDependents = mutableListOf<ClassVisibilityUpdater>()

fun initialize(typeVisibilityUpdaterProvider: TypeVisibilityUpdaterProvider) {
getDependencies()
.filter { it.module is SirModule.Skie || it.module is SirModule.Kotlin }
.forEach {
typeVisibilityUpdaterProvider[it].registerDirectDependentDeclaration(this)
}
}

private fun registerDirectDependentDeclaration(typeVisibilityUpdater: ClassVisibilityUpdater) {
directDependents.add(typeVisibilityUpdater)
}

fun propagateVisibility() {
directDependents.forEach {
it.coerceVisibilityAtMost(sirClass.visibility)
}
}

private fun coerceVisibilityAtMost(limit: SirVisibility) {
val newVisibility = sirClass.visibility.coerceAtMostInSwift(limit)

if (sirClass.visibility == newVisibility) {
return
}

sirClass.visibility = newVisibility

propagateVisibility()
}

private fun getDependencies(): Set<SirClass> =
setOfNotNull(
sirClass.namespace?.classDeclaration,
(sirClass.kirClassOrNull?.parent as? KirClass)?.originalSirClass,
) +
sirClass.typeParameters.flatMap { typeParameter -> typeParameter.bounds.flatMap { it.referencedClasses } }.toSet() +
getSuperTypesDependencies()

private fun getSuperTypesDependencies(): List<SirClass> =
if (sirClass.kind != SirClass.Kind.Protocol) {
val classSuperType = listOfNotNull(sirClass.superClassType)
val protocolSuperTypes = sirClass.superProtocolTypes

val consideredTypes = protocolSuperTypes.flatMap { it.typeArguments } + classSuperType

consideredTypes.flatMap { it.referencedClasses }
} else {
sirClass.superTypes.flatMap { it.referencedClasses }
}

private val SirType.referencedClasses: List<SirClass>
get() = this.normalizedEvaluatedType().referencedTypeDeclarations
.map { it as? SirClass ?: error("Normalized type should only reference SirClasses: $it") }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package co.touchlab.skie.phases.sir.type

import co.touchlab.skie.phases.SirPhase
import co.touchlab.skie.sir.element.SirTypeAlias
import co.touchlab.skie.sir.element.minimumVisibility
import co.touchlab.skie.sir.type.visibilityConstraint

object PropagateSirVisibilityToTypeAliasesPhase : SirPhase {

context(SirPhase.Context)
override suspend fun execute() {
sirProvider.allLocalTypeAliases.forEach {
updateVisibility(it)
}
}

private fun updateVisibility(sirTypeAlias: SirTypeAlias) {
val allConstraints = listOfNotNull(
sirTypeAlias.visibility,
sirTypeAlias.namespace?.classDeclaration?.visibility,
) +
sirTypeAlias.type.visibilityConstraint +
sirTypeAlias.typeParameters.flatMap { typeParameter -> typeParameter.bounds.map { it.visibilityConstraint } }

sirTypeAlias.visibility = allConstraints.minimumVisibility()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import co.touchlab.skie.sir.element.SirFile
import co.touchlab.skie.sir.element.SirModule
import co.touchlab.skie.sir.element.SirSimpleFunction
import co.touchlab.skie.sir.element.SirTopLevelDeclarationParent
import co.touchlab.skie.sir.element.SirTypeAlias
import co.touchlab.skie.sir.element.SirTypeDeclaration
import co.touchlab.skie.sir.element.SirVisibility
import co.touchlab.skie.sir.element.getAllDeclarationsRecursively
import co.touchlab.skie.util.directory.FrameworkLayout
import co.touchlab.skie.util.directory.SkieBuildDirectory
Expand Down Expand Up @@ -61,6 +61,9 @@ class SirProvider(
val allLocalClasses: List<SirClass>
get() = allLocalTypeDeclarations.filterIsInstance<SirClass>()

val allLocalTypeAliases: List<SirTypeAlias>
get() = allLocalTypeDeclarations.filterIsInstance<SirTypeAlias>()

val allLocalEnums: List<SirClass>
get() = allLocalClasses.filter { it.kind == SirClass.Kind.Enum }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@ val SirClass.superClassType: SirDeclaredSirType?
get() = superTypes.map { it.resolveAsSirClassType() }
.firstOrNull { (it?.declaration as? SirClass)?.kind == SirClass.Kind.Class }

val SirClass.superProtocolTypes: List<SirDeclaredSirType>
get() = superTypes.mapNotNull { it.resolveAsSirClassType() }
.filter { (it.declaration as? SirClass)?.kind == SirClass.Kind.Protocol }

val SirClass.superClass: SirClass?
get() = superClassType?.declaration as? SirClass

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,27 @@ package co.touchlab.skie.sir.element
enum class SirVisibility {

Public,

/** Applicable only to Obj-C code, Swift code will use Public instead. */
PublicButHidden,
Internal,
Private,

/** Applicable only to generated code, existing code cannot be removed and will be marked as private instead. */
Removed,
}

fun SirVisibility.coerceAtMostInSwift(limit: SirVisibility): SirVisibility =
when (limit) {
SirVisibility.Public, SirVisibility.PublicButHidden -> this
SirVisibility.Internal -> if (this.toSwiftVisibility() == SirVisibility.Public) SirVisibility.Internal else this
SirVisibility.Private -> if (this == SirVisibility.Removed) SirVisibility.Removed else SirVisibility.Private
SirVisibility.Removed -> SirVisibility.Removed
}

fun List<SirVisibility>.minimumVisibility(): SirVisibility =
this.fold(SirVisibility.Public) { acc, visibility -> acc.coerceAtMostInSwift(visibility) }

val SirVisibility.isAccessibleFromOtherModules: Boolean
get() = when (toSwiftVisibility()) {
SirVisibility.Public -> true
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package co.touchlab.skie.sir.type

import co.touchlab.skie.sir.element.SirTypeDeclaration
import co.touchlab.skie.sir.element.SirVisibility
import io.outfoxx.swiftpoet.TypeName

sealed interface EvaluatedSirType {
Expand All @@ -12,22 +14,34 @@ sealed interface EvaluatedSirType {
// Uses either FqName or internal name depending on context. Used for generating code.
val swiftPoetTypeName: TypeName

val visibilityConstraint: SirVisibility

val referencedTypeDeclarations: Set<SirTypeDeclaration>

class Eager(
override val type: SirType,
override val canonicalName: String,
override val swiftPoetTypeName: TypeName,
override val visibilityConstraint: SirVisibility,
override val referencedTypeDeclarations: Set<SirTypeDeclaration>,
) : EvaluatedSirType

class Lazy(
typeProvider: kotlin.Lazy<SirType>,
canonicalNameProvider: kotlin.Lazy<String>,
swiftPoetTypeNameProvider: kotlin.Lazy<TypeName>,
lowestVisibility: kotlin.Lazy<SirVisibility>,
referencedTypeDeclarationsProvider: kotlin.Lazy<Set<SirTypeDeclaration>>,
) : EvaluatedSirType {

override val type: SirType by typeProvider

override val canonicalName: String by canonicalNameProvider

override val swiftPoetTypeName: TypeName by swiftPoetTypeNameProvider

override val visibilityConstraint: SirVisibility by lowestVisibility

override val referencedTypeDeclarations: Set<SirTypeDeclaration> by referencedTypeDeclarationsProvider
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package co.touchlab.skie.sir.type

import co.touchlab.skie.sir.element.SirTypeParameter
import co.touchlab.skie.sir.element.minimumVisibility
import io.outfoxx.swiftpoet.AttributeSpec
import io.outfoxx.swiftpoet.FunctionTypeName
import io.outfoxx.swiftpoet.ParameterSpec
Expand Down Expand Up @@ -37,6 +38,12 @@ data class LambdaSirType(
},
)
},
lowestVisibility = lazy {
(evaluatedValueParameterTypes.value.map { it.visibilityConstraint } + evaluatedReturnType.value.visibilityConstraint).minimumVisibility()
},
referencedTypeDeclarationsProvider = lazy {
(evaluatedValueParameterTypes.value.flatMap { it.referencedTypeDeclarations } + evaluatedReturnType.value.referencedTypeDeclarations).toSet()
},
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ data class NullableSirType(
typeProvider = evaluatedType.map { copy(type = it.type) },
canonicalNameProvider = evaluatedType.map { it.canonicalName + "?" },
swiftPoetTypeNameProvider = evaluatedType.map { it.swiftPoetTypeName.makeOptional() },
lowestVisibility = evaluatedType.map { it.visibilityConstraint },
referencedTypeDeclarationsProvider = evaluatedType.map { it.referencedTypeDeclarations },
)
}

Expand Down
Loading

0 comments on commit f0b0cf0

Please sign in to comment.