Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion arrow-libs/optics/arrow-optics-compiler-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,35 @@ plugins {
kotlin {
explicitApi = null
compilerOptions {
optIn.add("org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi")
// optIn.add("org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi")
// optIn.add("org.jetbrains.kotlin.fir.extensions.ExperimentalTopLevelDeclarationsGenerationApi")
// optIn.add("org.jetbrains.kotlin.ir.symbols.UnsafeDuringIrConstructionAPI")
freeCompilerArgs.add("-Xcontext-parameters")
}
}

dependencies {
compileOnly(kotlin("compiler"))

testImplementation(kotlin("test"))
testImplementation(libs.kotest.assertionsCore)
testImplementation(libs.classgraph)
testImplementation(libs.kotlinCompileTesting) {
exclude(
group = libs.classgraph.get().module.group,
module = libs.classgraph.get().module.name
)
exclude(
group = "org.jetbrains.kotlin",
module = "kotlin-stdlib"
)
}
testRuntimeOnly(projects.arrowAnnotations)
testRuntimeOnly(projects.arrowCore)
testRuntimeOnly(projects.arrowOptics)
}

tasks.withType<Test>().configureEach {
maxParallelForks = 1
useJUnitPlatform()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package arrow.optics.plugin

import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.descriptors.Visibility
import org.jetbrains.kotlin.name.Name

/** The kind of an `@optics`-annotated source class. */
enum class OpticsClassKind { DATA, VALUE, SEALED, INELIGIBLE }

/** A user-facing generation target, mirroring `arrow.optics.OpticsTarget` plus COPY. */
enum class OpticsTargetKind { ISO, LENS, PRISM, DSL, COPY }

/** The base optic actually produced for a single focus. */
enum class OpticKind { ISO, LENS, PRISM }

/** The optic kind of the *outer* optic in a DSL composition extension. */
enum class DslKind { ISO, LENS, PRISM, OPTIONAL, TRAVERSAL }

/**
* Which outer-optic variants are produced for a base optic of [kind] (algo §8.2):
* exactly the kinds `X` for which `X` composed with the base kind is still an `X`.
*/
fun dslVariantsFor(kind: OpticKind): List<DslKind> = when (kind) {
OpticKind.LENS -> listOf(DslKind.LENS, DslKind.OPTIONAL, DslKind.TRAVERSAL)
OpticKind.PRISM -> listOf(DslKind.OPTIONAL, DslKind.PRISM, DslKind.TRAVERSAL)
OpticKind.ISO -> listOf(DslKind.ISO, DslKind.LENS, DslKind.OPTIONAL, DslKind.PRISM, DslKind.TRAVERSAL)
}

/**
* Compute the effective target set for a class, per algo §2.3:
* read the annotation's targets (OPTIONAL dropped), default to everything when empty,
* then intersect with what the class kind supports, and add COPY when requested.
*
* @param annotationTargets the names found in `@optics(targets = [...])`, or `null`/empty for the no-arg case.
*/
fun computeTargets(
kind: OpticsClassKind,
annotationTargets: Set<OpticsTargetKind>,
hasCopy: Boolean,
): Set<OpticsTargetKind> {
val requested =
annotationTargets.ifEmpty { setOf(OpticsTargetKind.ISO, OpticsTargetKind.LENS, OpticsTargetKind.PRISM, OpticsTargetKind.DSL) }
val allowed = when (kind) {
OpticsClassKind.SEALED -> setOf(OpticsTargetKind.PRISM, OpticsTargetKind.LENS, OpticsTargetKind.DSL)
OpticsClassKind.VALUE -> setOf(OpticsTargetKind.ISO, OpticsTargetKind.DSL)
OpticsClassKind.DATA -> setOf(OpticsTargetKind.LENS, OpticsTargetKind.DSL)
OpticsClassKind.INELIGIBLE -> emptySet()
}
return buildSet {
addAll(requested intersect allowed)
if (hasCopy && kind != OpticsClassKind.INELIGIBLE) add(OpticsTargetKind.COPY)
}
}

/** The optic name for a PRISM focus: subclass simple name with the first letter lowercased (algo §3.2). */
fun lowercaseFirst(name: Name): Name {
val s = name.identifierOrNullIfSpecial ?: return name
if (s.isEmpty() || !s[0].isUpperCase()) return name
return Name.identifier(s.replaceFirstChar { it.lowercaseChar() })
}

/**
* Most-restrictive combination of two visibilities (algo §3.3).
* `public` is the identity; `private` dominates; `internal` and `protected` collapse to `private`.
*/
fun mostRestrictive(a: Visibility, b: Visibility): Visibility = when {
a == Visibilities.Public -> b

b == Visibilities.Public -> a

a == Visibilities.Private || b == Visibilities.Private -> Visibilities.Private

a == b -> a

// mixing internal and protected
else -> Visibilities.Private
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package arrow.optics.plugin

import org.jetbrains.kotlin.name.CallableId
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name

/**
* Central registry of the fully-qualified names of the `arrow.optics` API that the
* generated optics refer to. FIR uses these to build cone types; IR uses them to
* resolve external symbols.
*/
object OpticsNames {
val ARROW_OPTICS_PACKAGE = FqName("arrow.optics")

val OPTICS_ANNOTATION = ClassId(ARROW_OPTICS_PACKAGE, Name.identifier("optics"))
val OPTICS_ANNOTATION_FQNAME: FqName = OPTICS_ANNOTATION.asSingleFqName()
val OPTICS_COPY_ANNOTATION =
OPTICS_ANNOTATION.createNestedClassId(Name.identifier("copy"))

// Underlying poly interfaces (these carry the companion objects with the factories, and the
// generated optic types use them directly — the `Lens`/`Iso`/… type-aliases are never referenced).
val PLENS = ClassId(ARROW_OPTICS_PACKAGE, Name.identifier("PLens"))
val PISO = ClassId(ARROW_OPTICS_PACKAGE, Name.identifier("PIso"))
val PPRISM = ClassId(ARROW_OPTICS_PACKAGE, Name.identifier("PPrism"))
val POPTIONAL = ClassId(ARROW_OPTICS_PACKAGE, Name.identifier("POptional"))
val PTRAVERSAL = ClassId(ARROW_OPTICS_PACKAGE, Name.identifier("PTraversal"))

private val INVOKE = Name.identifier("invoke")
private val PLUS = Name.identifier("plus")

val PLENS_COMPANION = PLENS.createNestedClassId(Name.identifier("Companion"))
val PISO_COMPANION = PISO.createNestedClassId(Name.identifier("Companion"))
val PPRISM_COMPANION = PPRISM.createNestedClassId(Name.identifier("Companion"))

val LENS_INVOKE = CallableId(PLENS_COMPANION, INVOKE)
val ISO_INVOKE = CallableId(PISO_COMPANION, INVOKE)
val PRISM_INSTANCE_OF = CallableId(PPRISM_COMPANION, Name.identifier("instanceOf"))

val ISO_PLUS = CallableId(PISO, PLUS)
val LENS_PLUS = CallableId(PLENS, PLUS)
val PRISM_PLUS = CallableId(PPRISM, PLUS)
val OPTIONAL_PLUS = CallableId(POPTIONAL, PLUS)
val TRAVERSAL_PLUS = CallableId(PTRAVERSAL, PLUS)

val ARROW_OPTICS_COPY = CallableId(ARROW_OPTICS_PACKAGE, Name.identifier("copy"))
val COPY = ClassId(ARROW_OPTICS_PACKAGE, Name.identifier("Copy"))

/** Poly-interface ClassId for a DSL outer-optic kind. */
fun polyClassFor(kind: DslKind): ClassId = when (kind) {
DslKind.ISO -> PISO
DslKind.LENS -> PLENS
DslKind.PRISM -> PPRISM
DslKind.OPTIONAL -> POPTIONAL
DslKind.TRAVERSAL -> PTRAVERSAL
}

/** `plus` CallableId for a DSL outer-optic kind. */
fun plusFor(kind: DslKind): CallableId = when (kind) {
DslKind.ISO -> ISO_PLUS
DslKind.LENS -> LENS_PLUS
DslKind.PRISM -> PRISM_PLUS
DslKind.OPTIONAL -> OPTIONAL_PLUS
DslKind.TRAVERSAL -> TRAVERSAL_PLUS
}
}
Loading
Loading