Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify optics to Traversal/Optional/Lens/Prism #2873

Merged
merged 14 commits into from
Dec 21, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -31,99 +31,48 @@ val KSClassDeclaration.nameWithParentClass: String
}

enum class OpticsTarget {
ISO,
LENS,
PRISM,
OPTIONAL,
DSL
}

typealias IsoTarget = Target.Iso

typealias PrismTarget = Target.Prism

typealias LensTarget = Target.Lens

typealias OptionalTarget = Target.Optional

typealias SealedClassDsl = Target.SealedClassDsl

typealias DataClassDsl = Target.DataClassDsl

sealed class Target {
abstract val foci: List<Focus>

data class Iso(override val foci: List<Focus>) : Target()
data class Prism(override val foci: List<Focus>) : Target()
data class Lens(override val foci: List<Focus>) : Target()
data class Optional(override val foci: List<Focus>) : Target()
data class SealedClassDsl(override val foci: List<Focus>) : Target()
data class DataClassDsl(override val foci: List<Focus>) : Target()
}

typealias NonNullFocus = Focus.NonNull

typealias OptionFocus = Focus.Option

typealias NullableFocus = Focus.Nullable

sealed class Focus {

companion object {
operator fun invoke(fullName: String, paramName: String, refinedType: KSType? = null): Focus =
when {
fullName.endsWith("?") -> Nullable(fullName, paramName, refinedType)
fullName.startsWith("`arrow`.`core`.`Option`") -> Option(fullName, paramName, refinedType)
else -> NonNull(fullName, paramName, refinedType)
}
}

abstract val className: String
abstract val paramName: String
// only used for type-refining prisms
abstract val refinedType: KSType?

data class Focus(
val className: String,
val paramName: String,
val refinedType: KSType?
) {
val refinedArguments: List<String>
get() = refinedType?.arguments?.filter {
it.type?.resolve()?.declaration is KSTypeParameter
}?.map { it.qualifiedString() }.orEmpty()

data class Nullable(
override val className: String,
override val paramName: String,
override val refinedType: KSType?
) : Focus() {
val nonNullClassName = className.dropLast(1)
}

data class Option(
override val className: String,
override val paramName: String,
override val refinedType: KSType?
) : Focus() {
val nestedClassName =
Regex("`arrow`.`core`.`Option`<(.*)>$").matchEntire(className)!!.groupValues[1]
companion object {
operator fun invoke(fullName: String, paramName: String): Focus =
Focus(fullName, paramName, null)
}

data class NonNull(
override val className: String,
override val paramName: String,
override val refinedType: KSType?
) : Focus()
}

const val Lens = "arrow.optics.Lens"
const val Iso = "arrow.optics.Iso"
const val Optional = "arrow.optics.Optional"
const val Prism = "arrow.optics.Prism"
const val Getter = "arrow.optics.Getter"
const val Setter = "arrow.optics.Setter"
const val Traversal = "arrow.optics.Traversal"
const val Fold = "arrow.optics.Fold"
const val Every = "arrow.optics.Every"
const val Tuple = "arrow.core.Tuple"
const val Pair = "kotlin.Pair"
const val Triple = "kotlin.Triple"

data class Snippet(
val `package`: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,6 @@ fun generateLensDsl(ele: ADT, optic: DataClassDsl): Snippet =
content = processLensSyntax(ele, optic.foci)
)

fun generateOptionalDsl(ele: ADT, optic: DataClassDsl): Snippet =
Snippet(
`package` = ele.packageName,
name = ele.simpleName,
content = processOptionalSyntax(ele, optic)
)

fun generatePrismDsl(ele: ADT, isoOptic: SealedClassDsl): Snippet =
Snippet(
`package` = ele.packageName,
Expand All @@ -25,83 +18,30 @@ private fun processLensSyntax(ele: ADT, foci: List<Focus>): String =
if (ele.typeParameters.isEmpty()) {
foci.joinToString(separator = "\n") { focus ->
"""
|${ele.visibilityModifierName} inline val <S> $Iso<S, ${ele.sourceClassName}>.${focus.lensParamName()}: $Lens<S, ${focus.className}> inline get() = this + ${ele.sourceClassName}.${focus.lensParamName()}
|${ele.visibilityModifierName} inline val <S> $Lens<S, ${ele.sourceClassName}>.${focus.lensParamName()}: $Lens<S, ${focus.className}> inline get() = this + ${ele.sourceClassName}.${focus.lensParamName()}
|${ele.visibilityModifierName} inline val <S> $Optional<S, ${ele.sourceClassName}>.${focus.lensParamName()}: $Optional<S, ${focus.className}> inline get() = this + ${ele.sourceClassName}.${focus.lensParamName()}
|${ele.visibilityModifierName} inline val <S> $Prism<S, ${ele.sourceClassName}>.${focus.lensParamName()}: $Optional<S, ${focus.className}> inline get() = this + ${ele.sourceClassName}.${focus.lensParamName()}
|${ele.visibilityModifierName} inline val <S> $Getter<S, ${ele.sourceClassName}>.${focus.lensParamName()}: $Getter<S, ${focus.className}> inline get() = this + ${ele.sourceClassName}.${focus.lensParamName()}
|${ele.visibilityModifierName} inline val <S> $Setter<S, ${ele.sourceClassName}>.${focus.lensParamName()}: $Setter<S, ${focus.className}> inline get() = this + ${ele.sourceClassName}.${focus.lensParamName()}
|${ele.visibilityModifierName} inline val <S> $Traversal<S, ${ele.sourceClassName}>.${focus.lensParamName()}: $Traversal<S, ${focus.className}> inline get() = this + ${ele.sourceClassName}.${focus.lensParamName()}
|${ele.visibilityModifierName} inline val <S> $Fold<S, ${ele.sourceClassName}>.${focus.lensParamName()}: $Fold<S, ${focus.className}> inline get() = this + ${ele.sourceClassName}.${focus.lensParamName()}
|${ele.visibilityModifierName} inline val <S> $Every<S, ${ele.sourceClassName}>.${focus.lensParamName()}: $Every<S, ${focus.className}> inline get() = this + ${ele.sourceClassName}.${focus.lensParamName()}
|""".trimMargin()
}
} else {
val sourceClassNameWithParams = "${ele.sourceClassName}${ele.angledTypeParameters}"
val joinedTypeParams = ele.typeParameters.joinToString(separator=",")
foci.joinToString(separator = "\n") { focus ->
"""
|${ele.visibilityModifierName} inline fun <S,$joinedTypeParams> $Iso<S, $sourceClassNameWithParams>.${focus.lensParamName()}(): $Lens<S, ${focus.className}> = this + ${ele.sourceClassName}.${focus.lensParamName()}()
|${ele.visibilityModifierName} inline fun <S,$joinedTypeParams> $Lens<S, $sourceClassNameWithParams>.${focus.lensParamName()}(): $Lens<S, ${focus.className}> = this + ${ele.sourceClassName}.${focus.lensParamName()}()
|${ele.visibilityModifierName} inline fun <S,$joinedTypeParams> $Optional<S, $sourceClassNameWithParams>.${focus.lensParamName()}(): $Optional<S, ${focus.className}> = this + ${ele.sourceClassName}.${focus.lensParamName()}()
|${ele.visibilityModifierName} inline fun <S,$joinedTypeParams> $Prism<S, $sourceClassNameWithParams>.${focus.lensParamName()}(): $Optional<S, ${focus.className}> = this + ${ele.sourceClassName}.${focus.lensParamName()}()
|${ele.visibilityModifierName} inline fun <S,$joinedTypeParams> $Getter<S, $sourceClassNameWithParams>.${focus.lensParamName()}(): $Getter<S, ${focus.className}> = this + ${ele.sourceClassName}.${focus.lensParamName()}()
|${ele.visibilityModifierName} inline fun <S,$joinedTypeParams> $Setter<S, $sourceClassNameWithParams>.${focus.lensParamName()}(): $Setter<S, ${focus.className}> = this + ${ele.sourceClassName}.${focus.lensParamName()}()
|${ele.visibilityModifierName} inline fun <S,$joinedTypeParams> $Traversal<S, $sourceClassNameWithParams>.${focus.lensParamName()}(): $Traversal<S, ${focus.className}> = this + ${ele.sourceClassName}.${focus.lensParamName()}()
|${ele.visibilityModifierName} inline fun <S,$joinedTypeParams> $Fold<S, $sourceClassNameWithParams>.${focus.lensParamName()}(): $Fold<S, ${focus.className}> = this + ${ele.sourceClassName}.${focus.lensParamName()}()
|${ele.visibilityModifierName} inline fun <S,$joinedTypeParams> $Every<S, $sourceClassNameWithParams>.${focus.lensParamName()}(): $Every<S, ${focus.className}> = this + ${ele.sourceClassName}.${focus.lensParamName()}()
|""".trimMargin()
}
}

private fun processOptionalSyntax(ele: ADT, optic: DataClassDsl): String {
val sourceClassNameWithParams = "${ele.sourceClassName}${ele.angledTypeParameters}"
val joinedTypeParams = ele.typeParameters.joinToString(separator=",")
return optic.foci.filterNot { it is NonNullFocus }.joinToString(separator = "\n") { focus ->
val targetClassName =
when (focus) {
is Focus.Nullable -> focus.nonNullClassName
is Focus.Option -> focus.nestedClassName
is Focus.NonNull -> ""
}
if (ele.typeParameters.isEmpty()) {
"""
|${ele.visibilityModifierName} inline val <S> $Iso<S, ${ele.sourceClassName}>.${focus.paramName}: $Optional<S, $targetClassName> inline get() = this + ${ele.sourceClassName}.${focus.paramName}
|${ele.visibilityModifierName} inline val <S> $Lens<S, ${ele.sourceClassName}>.${focus.paramName}: $Optional<S, $targetClassName> inline get() = this + ${ele.sourceClassName}.${focus.paramName}
|${ele.visibilityModifierName} inline val <S> $Optional<S, ${ele.sourceClassName}>.${focus.paramName}: $Optional<S, $targetClassName> inline get() = this + ${ele.sourceClassName}.${focus.paramName}
|${ele.visibilityModifierName} inline val <S> $Prism<S, ${ele.sourceClassName}>.${focus.paramName}: $Optional<S, $targetClassName> inline get() = this + ${ele.sourceClassName}.${focus.paramName}
|${ele.visibilityModifierName} inline val <S> $Setter<S, ${ele.sourceClassName}>.${focus.paramName}: $Setter<S, $targetClassName> inline get() = this + ${ele.sourceClassName}.${focus.paramName}
|${ele.visibilityModifierName} inline val <S> $Traversal<S, ${ele.sourceClassName}>.${focus.paramName}: $Traversal<S, $targetClassName> inline get() = this + ${ele.sourceClassName}.${focus.paramName}
|${ele.visibilityModifierName} inline val <S> $Fold<S, ${ele.sourceClassName}>.${focus.paramName}: $Fold<S, $targetClassName> inline get() = this + ${ele.sourceClassName}.${focus.paramName}
|${ele.visibilityModifierName} inline val <S> $Every<S, ${ele.sourceClassName}>.${focus.paramName}: $Every<S, $targetClassName> inline get() = this + ${ele.sourceClassName}.${focus.paramName}
|""".trimMargin()
} else {
"""
|${ele.visibilityModifierName} inline fun <S,$joinedTypeParams> $Iso<S, $sourceClassNameWithParams>.${focus.paramName}(): $Optional<S, $targetClassName> = this + ${ele.sourceClassName}.${focus.paramName}()
|${ele.visibilityModifierName} inline fun <S,$joinedTypeParams> $Lens<S, $sourceClassNameWithParams>.${focus.paramName}(): $Optional<S, $targetClassName> = this + ${ele.sourceClassName}.${focus.paramName}()
|${ele.visibilityModifierName} inline fun <S,$joinedTypeParams> $Optional<S, $sourceClassNameWithParams>.${focus.paramName}(): $Optional<S, $targetClassName> = this + ${ele.sourceClassName}.${focus.paramName}()
|${ele.visibilityModifierName} inline fun <S,$joinedTypeParams> $Prism<S, $sourceClassNameWithParams>.${focus.paramName}(): $Optional<S, $targetClassName> = this + ${ele.sourceClassName}.${focus.paramName}()
|${ele.visibilityModifierName} inline fun <S,$joinedTypeParams> $Setter<S, $sourceClassNameWithParams>.${focus.paramName}(): $Setter<S, $targetClassName> = this + ${ele.sourceClassName}.${focus.paramName}()
|${ele.visibilityModifierName} inline fun <S,$joinedTypeParams> $Traversal<S, $sourceClassNameWithParams>.${focus.paramName}(): $Traversal<S, $targetClassName> = this + ${ele.sourceClassName}.${focus.paramName}()
|${ele.visibilityModifierName} inline fun <S,$joinedTypeParams> $Fold<S, $sourceClassNameWithParams>.${focus.paramName}(): $Fold<S, $targetClassName> = this + ${ele.sourceClassName}.${focus.paramName}()
|${ele.visibilityModifierName} inline fun <S,$joinedTypeParams> $Every<S, $sourceClassNameWithParams>.${focus.paramName}(): $Every<S, $targetClassName> = this + ${ele.sourceClassName}.${focus.paramName}()
|""".trimMargin()
}
}
}

private fun processPrismSyntax(ele: ADT, dsl: SealedClassDsl): String =
if (ele.typeParameters.isEmpty()) {
dsl.foci.joinToString(separator = "\n\n") { focus ->
"""
|${ele.visibilityModifierName} inline val <S> $Iso<S, ${ele.sourceClassName}>.${focus.paramName}: $Prism<S, ${focus.className}> inline get() = this + ${ele.sourceClassName}.${focus.paramName}
|${ele.visibilityModifierName} inline val <S> $Lens<S, ${ele.sourceClassName}>.${focus.paramName}: $Optional<S, ${focus.className}> inline get() = this + ${ele.sourceClassName}.${focus.paramName}
|${ele.visibilityModifierName} inline val <S> $Optional<S, ${ele.sourceClassName}>.${focus.paramName}: $Optional<S, ${focus.className}> inline get() = this + ${ele.sourceClassName}.${focus.paramName}
|${ele.visibilityModifierName} inline val <S> $Prism<S, ${ele.sourceClassName}>.${focus.paramName}: $Prism<S, ${focus.className}> inline get() = this + ${ele.sourceClassName}.${focus.paramName}
|${ele.visibilityModifierName} inline val <S> $Setter<S, ${ele.sourceClassName}>.${focus.paramName}: $Setter<S, ${focus.className}> inline get() = this + ${ele.sourceClassName}.${focus.paramName}
|${ele.visibilityModifierName} inline val <S> $Traversal<S, ${ele.sourceClassName}>.${focus.paramName}: $Traversal<S, ${focus.className}> inline get() = this + ${ele.sourceClassName}.${focus.paramName}
|${ele.visibilityModifierName} inline val <S> $Fold<S, ${ele.sourceClassName}>.${focus.paramName}: $Fold<S, ${focus.className}> inline get() = this + ${ele.sourceClassName}.${focus.paramName}
|${ele.visibilityModifierName} inline val <S> $Every<S, ${ele.sourceClassName}>.${focus.paramName}: $Every<S, ${focus.className}> inline get() = this + ${ele.sourceClassName}.${focus.paramName}
|""".trimMargin()
}
} else {
Expand All @@ -112,14 +52,9 @@ private fun processPrismSyntax(ele: ADT, dsl: SealedClassDsl): String =
else -> focus.refinedArguments.joinToString(separator=",")
}
"""
|${ele.visibilityModifierName} inline fun <S,$joinedTypeParams> $Iso<S, $sourceClassNameWithParams>.${focus.paramName}(): $Prism<S, ${focus.className}> = this + ${ele.sourceClassName}.${focus.paramName}()
|${ele.visibilityModifierName} inline fun <S,$joinedTypeParams> $Lens<S, $sourceClassNameWithParams>.${focus.paramName}(): $Optional<S, ${focus.className}> = this + ${ele.sourceClassName}.${focus.paramName}()
|${ele.visibilityModifierName} inline fun <S,$joinedTypeParams> $Optional<S, $sourceClassNameWithParams>.${focus.paramName}(): $Optional<S, ${focus.className}> = this + ${ele.sourceClassName}.${focus.paramName}()
|${ele.visibilityModifierName} inline fun <S,$joinedTypeParams> $Prism<S, $sourceClassNameWithParams>.${focus.paramName}(): $Prism<S, ${focus.className}> = this + ${ele.sourceClassName}.${focus.paramName}()
|${ele.visibilityModifierName} inline fun <S,$joinedTypeParams> $Setter<S, $sourceClassNameWithParams>.${focus.paramName}(): $Setter<S, ${focus.className}> = this + ${ele.sourceClassName}.${focus.paramName}()
|${ele.visibilityModifierName} inline fun <S,$joinedTypeParams> $Traversal<S, $sourceClassNameWithParams>.${focus.paramName}(): $Traversal<S, ${focus.className}> = this + ${ele.sourceClassName}.${focus.paramName}()
|${ele.visibilityModifierName} inline fun <S,$joinedTypeParams> $Fold<S, $sourceClassNameWithParams>.${focus.paramName}(): $Fold<S, ${focus.className}> = this + ${ele.sourceClassName}.${focus.paramName}()
|${ele.visibilityModifierName} inline fun <S,$joinedTypeParams> $Every<S, $sourceClassNameWithParams>.${focus.paramName}(): $Every<S, ${focus.className}> = this + ${ele.sourceClassName}.${focus.paramName}()
|""".trimMargin()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,6 @@ val String.otherClassTypeErrorMessage
| ^
|Only data and sealed classes can be annotated with @optics""".trimMargin()

val String.typeParametersErrorMessage
get() =
"""
|$this cannot be annotated with @optics
| ^
|Only classes with no type parameters can be annotated with @optics""".trimMargin()

val String.lensErrorMessage
get() =
"""
Expand All @@ -23,15 +16,6 @@ val String.lensErrorMessage
|It is only valid for data classes.
""".trimMargin()

val String.optionalErrorMessage
get() =
"""
|Cannot generate arrow.optics.Optional for $this
| ^
|arrow.optics.OpticsTarget.OPTIONAL is an invalid @optics argument for $this.
|It is only valid for data classes.
""".trimMargin()

val String.prismErrorMessage
get() =
"""
Expand All @@ -41,32 +25,6 @@ val String.prismErrorMessage
|It is only valid for sealed classes.
""".trimMargin()

val String.isoErrorMessage
get() =
"""
|Cannot generate arrow.optics.Iso for $this
| ^
|arrow.optics.OpticsTarget.ISO is an invalid @optics argument for $this.
|It is only valid for data classes.
""".trimMargin()

val String.isoTooBigErrorMessage
get() =
"""
|Cannot generate arrow.optics.Iso for $this
| ^
|Iso generation is supported for data classes with up to 22 constructor parameters.
""".trimMargin()

val String.dslErrorMessage
get() =
"""
|Cannot generate DSL (arrow.optics.BoundSetter) for $this
| ^
|arrow.optics.OpticsTarget.DSL is an invalid @optics argument for $this.
|It is only valid for data classes and sealed classes.
""".trimMargin()

val String.noCompanion
get() =
"""
Expand Down
Loading