Skip to content

Commit

Permalink
Add KopyVisibility to set the visibility of the generated functions
Browse files Browse the repository at this point in the history
  • Loading branch information
JavierSegoviaCordoba committed Aug 25, 2024
1 parent 7570011 commit 9a3951c
Show file tree
Hide file tree
Showing 73 changed files with 2,453 additions and 25 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

### Added

- `KopyVisibility` to set the visibility of the generated functions

### Changed

### Deprecated
Expand Down
44 changes: 43 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,49 @@ Apply the plugin in the `build.gradle.kts` or `build.gradle`:

```kotlin
plugins {
id("com.javiersc.kotlin.kopy") version $version
id("com.javiersc.kotlin.kopy") version "$version"
}
```

### Plugin configurations

The extension `kopy` is available to configure the plugin:

#### Visibility

The `visibility` option allows changing the visibility of the `copy` and `invoke` functions. The
default value is `Auto`, which uses the same visibility the primary constructor has, as the original
`copy` function does after Kotlin 2.0.20.

Possible values:

- `KopyVisibility.Auto`
- `KopyVisibility.Public`
- `KopyVisibility.Internal`
- `KopyVisibility.Protected`
- `KopyVisibility.Private`

It is possible to have a more restrictive Kopy `copy` and `invoke` functions than the original one,
for example by providing the `KopyVisiblity.Private` and the primary constructor being `public` or
`internal`. The original `copy` function would be `public` or `internal` respectively, and the Kopy
functions would be `private`.

> **Note**
>
> If the primary constructor visibility is more restrictive than the specified visibility, the
> primary constructor one is used.
##### Example

```kotlin
import com.javiersc.kotlin.kopy.args.KopyVisibility

plugins {
id("com.javiersc.kotlin.kopy") version "$version"
}

kopy {
visibility = KopyVisibility.Private
}
```

Expand Down
30 changes: 30 additions & 0 deletions kopy-args/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
hubdle {
config {
analysis()
coverage()
documentation { //
api()
}
explicitApi()
format.isEnabled = false
publishing {
maven {
repositories { //
mavenLocalTest()
}
}
}
}
kotlin {
multiplatform {
common {
main {
dependencies { //
implementation(hubdle.jetbrains.kotlinx.atomicfu)
}
}
}
jvm()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.javiersc.kotlin.kopy.args

import java.io.Serializable

public enum class KopyVisibility(
public val value: String,
public val restrictive: Int,
) : Serializable {
Auto(value = "auto", restrictive = 0),
Public(value = "public", restrictive = 1),
Internal(value = "internal", restrictive = 2),
Protected(value = "protected", restrictive = 3),
Private(value = "private", restrictive = 4),
;

public companion object {

public const val NAME: String = "KopyVisibility"
public const val DESCRIPTION: String = "Visibility of the generated copy function"

public fun from(value: String): KopyVisibility =
when (value) {
Auto.name -> Auto
Auto.value -> Auto
Public.name -> Public
Public.value -> Public
Internal.name -> Internal
Internal.value -> Internal
Protected.name -> Protected
Protected.value -> Protected
Private.name -> Private
Private.value -> Private
else -> Auto
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.javiersc.kotlin.kopy.args

import io.kotest.matchers.shouldBe
import kotlin.test.Test

class KopyVisibilityTest {

@Test
fun `kopy visibility`() {
KopyVisibility.from("auto") shouldBe KopyVisibility.Auto
KopyVisibility.from("Auto") shouldBe KopyVisibility.Auto
KopyVisibility.from("public") shouldBe KopyVisibility.Public
KopyVisibility.from("Public") shouldBe KopyVisibility.Public
KopyVisibility.from("internal") shouldBe KopyVisibility.Internal
KopyVisibility.from("Internal") shouldBe KopyVisibility.Internal
KopyVisibility.from("protected") shouldBe KopyVisibility.Protected
KopyVisibility.from("Protected") shouldBe KopyVisibility.Protected
KopyVisibility.from("private") shouldBe KopyVisibility.Private
KopyVisibility.from("Private") shouldBe KopyVisibility.Private
KopyVisibility.from("random") shouldBe KopyVisibility.Auto
}
}
1 change: 1 addition & 0 deletions kopy-compiler/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ hubdle {
main { //
dependencies { //
implementation(hubdle.javiersc.kotlin.compiler.extensions)
implementation(projects.kopyArgs)
implementation(projects.kopyRuntime)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.javiersc.kotlin.kopy.compiler

import com.javiersc.kotlin.kopy.args.KopyVisibility
import com.javiersc.kotlin.kopy.compiler.KopyCompilerProjectData.Group
import com.javiersc.kotlin.kopy.compiler.KopyCompilerProjectData.Name
import org.jetbrains.kotlin.compiler.plugin.AbstractCliOption
import org.jetbrains.kotlin.compiler.plugin.CliOption
import org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor
import org.jetbrains.kotlin.config.CompilerConfiguration

Expand All @@ -12,26 +14,20 @@ public class KopyCommandLineProcessor : CommandLineProcessor {

override val pluginOptions: Collection<AbstractCliOption> =
listOf(
// CliOption(
// optionName = ...,
// valueDescription = ...,
// description = ...,
// required = true,
// ),
CliOption(
optionName = KopyVisibility.NAME,
valueDescription = KopyVisibility.DESCRIPTION,
description = KopyVisibility.DESCRIPTION,
required = true,
),
)

override fun processOption(
option: AbstractCliOption,
value: String,
configuration: CompilerConfiguration
) {
// when (option.optionName) {
// ... -> {
// configuration.put(..., value)
// }
// else -> {
// error("Unexpected config option: ${option.optionName}")
// }
// }
val kopyVisibility: KopyVisibility = KopyVisibility.from(value)
configuration.put(KopyKey.Visibility, kopyVisibility)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.javiersc.kotlin.kopy.compiler

import com.javiersc.kotlin.kopy.args.KopyVisibility
import org.jetbrains.kotlin.config.CompilerConfigurationKey

internal object KopyKey {
val Visibility = CompilerConfigurationKey<KopyVisibility>(KopyVisibility.NAME)
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.javiersc.kotlin.kopy.compiler.fir.generation.FirKopyAssignExpressionA
import com.javiersc.kotlin.kopy.compiler.fir.generation.FirKopyDeclarationGenerationExtension
import org.jetbrains.kotlin.GeneratedDeclarationKey
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrar

internal class FirKopyExtension(
Expand All @@ -22,7 +23,7 @@ internal class FirKopyExtension(

private fun ExtensionRegistrarContext.registerGenerators() {
+::FirKopyAssignExpressionAltererExtension
+::FirKopyDeclarationGenerationExtension
+{ session: FirSession -> FirKopyDeclarationGenerationExtension(session, configuration) }
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,20 @@ import com.javiersc.kotlin.kopy.KopyFunctionInvoke
import com.javiersc.kotlin.kopy.KopyFunctionSet
import com.javiersc.kotlin.kopy.KopyFunctionUpdate
import com.javiersc.kotlin.kopy.KopyFunctionUpdateEach
import com.javiersc.kotlin.kopy.args.KopyVisibility
import com.javiersc.kotlin.kopy.compiler.KopyKey
import com.javiersc.kotlin.kopy.compiler.fir.Key
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.descriptors.Visibility
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.primaryConstructorSymbol
import org.jetbrains.kotlin.fir.declarations.FirProperty
import org.jetbrains.kotlin.fir.declarations.FirSimpleFunction
import org.jetbrains.kotlin.fir.declarations.hasAnnotation
import org.jetbrains.kotlin.fir.declarations.utils.isData
import org.jetbrains.kotlin.fir.declarations.utils.visibility
import org.jetbrains.kotlin.fir.expressions.FirAnnotation
import org.jetbrains.kotlin.fir.expressions.builder.buildEmptyExpressionBlock
import org.jetbrains.kotlin.fir.extensions.FirDeclarationGenerationExtension
Expand All @@ -28,7 +35,6 @@ import org.jetbrains.kotlin.fir.extensions.predicate.DeclarationPredicate
import org.jetbrains.kotlin.fir.plugin.createMemberFunction
import org.jetbrains.kotlin.fir.plugin.createMemberProperty
import org.jetbrains.kotlin.fir.resolve.defaultType
import org.jetbrains.kotlin.fir.resolve.fqName
import org.jetbrains.kotlin.fir.resolve.providers.getRegularClassSymbolByClassId
import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutor
Expand All @@ -55,8 +61,12 @@ import org.jetbrains.kotlin.name.StandardClassIds

internal class FirKopyDeclarationGenerationExtension(
session: FirSession,
private val configuration: CompilerConfiguration,
) : FirDeclarationGenerationExtension(session) {

private val kopyVisibility: KopyVisibility
get() = configuration.get(KopyKey.Visibility, KopyVisibility.Auto)

private val kopyOptInClassId: ClassId = "com.javiersc.kotlin.kopy.KopyOptIn".toClassId()
private val kopyFunctionCopyClassId: ClassId = classId<KopyFunctionCopy>()
private val kopyFunctionInvokeClassId: ClassId = classId<KopyFunctionInvoke>()
Expand Down Expand Up @@ -120,6 +130,7 @@ internal class FirKopyDeclarationGenerationExtension(
config = {
status { isOverride = false }
modality = Modality.FINAL
visibility = calculateVisibility(owner)
},
)
return listOf(atomicProperty.symbol)
Expand Down Expand Up @@ -197,6 +208,7 @@ internal class FirKopyDeclarationGenerationExtension(
isInfix = true
}
modality = Modality.FINAL
visibility = calculateVisibility(owner)
this.extensionReceiverType { typeParameters ->
typeParameters.first().toConeType()
}
Expand Down Expand Up @@ -238,6 +250,7 @@ internal class FirKopyDeclarationGenerationExtension(
isInfix = true
}
modality = Modality.FINAL
visibility = calculateVisibility(owner)
this.extensionReceiverType { typeParameters ->
typeParameters.first().toConeType()
}
Expand Down Expand Up @@ -297,6 +310,7 @@ internal class FirKopyDeclarationGenerationExtension(
isInfix = true
}
modality = Modality.FINAL
visibility = calculateVisibility(owner)
this.extensionReceiverType { typeParameters ->
val typeParamsAsConeType: List<ConeTypeParameterType> =
typeParameters.map { it.toConeType() }
Expand Down Expand Up @@ -356,6 +370,7 @@ internal class FirKopyDeclarationGenerationExtension(
isOperator = callableId.callableName == invokeName
}
modality = Modality.FINAL
visibility = calculateVisibility(owner)
valueParameter(copyName, copyValueParameterType)
},
)
Expand Down Expand Up @@ -386,6 +401,35 @@ internal class FirKopyDeclarationGenerationExtension(
?.fir
?.symbol
?.toFirTypeRef()?.let(::createFirAnnotation)

private fun calculateVisibility(classSymbol: FirClassSymbol<*>): Visibility {
val visibility: Visibility =
classSymbol.primaryConstructorSymbol(session)?.visibility ?: return Visibilities.Public
val isMoreRestrictive: Boolean = kopyVisibility.isMoreRestrictedThan(visibility)
return if (isMoreRestrictive) kopyVisibility.toVisibility(visibility)
else visibility
}

private fun KopyVisibility.toVisibility(defaultVisibility: Visibility): Visibility =
when (this) {
KopyVisibility.Auto -> defaultVisibility
KopyVisibility.Public -> Visibilities.Public
KopyVisibility.Internal -> Visibilities.Internal
KopyVisibility.Protected -> Visibilities.Protected
KopyVisibility.Private -> Visibilities.Private
}

private fun KopyVisibility.isMoreRestrictedThan(visibility: Visibility): Boolean =
this.restrictive > visibility.restrictive

private val Visibility.restrictive: Int
get() = when (this) {
Visibilities.Public -> 1
Visibilities.Internal -> 2
Visibilities.Protected -> 3
Visibilities.Private -> 4
else -> 5
}
}

private fun FirSession.substitutor(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
FILE: internal.kt
package com.javiersc.kotlin.kopy.playground

public final fun diagnostics(): R|kotlin/Unit| {
lval foo1: R|com/javiersc/kotlin/kopy/playground/Foo| = R|com/javiersc/kotlin/kopy/playground/Foo.Foo|(Int(7), Char(W))
lval foo21: R|com/javiersc/kotlin/kopy/playground/Foo| = R|<local>/foo1|.R|com/javiersc/kotlin/kopy/playground/Foo.copy|(copy@fun R|com/javiersc/kotlin/kopy/playground/Foo|.<anonymous>(): R|kotlin/Unit| <inline=NoInline> {
(this@R|special/anonymous|, this@R|special/anonymous|.R|com/javiersc/kotlin/kopy/playground/Foo.number|).R|com/javiersc/kotlin/kopy/playground/Foo.set|<R|kotlin/Int|>(Int(42))
}
)
}
@R|com/javiersc/kotlin/kopy/Kopy|() public final data class Foo : R|kotlin/Any| {
internal constructor(number: R|kotlin/Int|, letter: R|kotlin/Char|): R|com/javiersc/kotlin/kopy/playground/Foo| {
super<R|kotlin/Any|>()
}

public final val number: R|kotlin/Int| = R|<local>/number|
public get(): R|kotlin/Int|

public final val letter: R|kotlin/Char| = R|<local>/letter|
public get(): R|kotlin/Char|

public final operator fun component1(): R|kotlin/Int|

public final operator fun component2(): R|kotlin/Char|

public final fun copy(number: R|kotlin/Int| = this@R|com/javiersc/kotlin/kopy/playground/Foo|.R|com/javiersc/kotlin/kopy/playground/Foo.number|, letter: R|kotlin/Char| = this@R|com/javiersc/kotlin/kopy/playground/Foo|.R|com/javiersc/kotlin/kopy/playground/Foo.letter|): R|com/javiersc/kotlin/kopy/playground/Foo|

@R|com/javiersc/kotlin/kopy/KopyOptIn|() @R|com/javiersc/kotlin/kopy/KopyFunctionUpdate|() internal final infix fun <U> R|U|.update(transform: R|(U) -> U|): R|kotlin/Unit| {
}

internal final val _atomic: R|kotlinx/atomicfu/AtomicRef<com/javiersc/kotlin/kopy/playground/Foo>|
internal get(): R|kotlinx/atomicfu/AtomicRef<com/javiersc/kotlin/kopy/playground/Foo>|

@R|com/javiersc/kotlin/kopy/KopyOptIn|() @R|com/javiersc/kotlin/kopy/KopyFunctionSet|() internal final infix fun <S> R|S|.set(other: R|S|): R|kotlin/Unit| {
}

@R|com/javiersc/kotlin/kopy/KopyOptIn|() @R|com/javiersc/kotlin/kopy/KopyFunctionCopy|() internal final infix fun copy(copy: R|com/javiersc/kotlin/kopy/playground/Foo.() -> kotlin/Unit|): R|com/javiersc/kotlin/kopy/playground/Foo| {
}

@R|com/javiersc/kotlin/kopy/KopyOptIn|() @R|com/javiersc/kotlin/kopy/KopyFunctionUpdateEach|() internal final infix fun <UE> R|kotlin/collections/Iterable<UE>|.updateEach(transform: R|(UE) -> UE|): R|kotlin/Unit| {
}

@R|com/javiersc/kotlin/kopy/KopyOptIn|() @R|com/javiersc/kotlin/kopy/KopyFunctionInvoke|() internal final operator infix fun invoke(copy: R|com/javiersc/kotlin/kopy/playground/Foo.() -> kotlin/Unit|): R|com/javiersc/kotlin/kopy/playground/Foo| {
}

}
Loading

0 comments on commit 9a3951c

Please sign in to comment.