Skip to content

Commit b7d6e47

Browse files
committed
working raise implementation
1 parent d75c179 commit b7d6e47

File tree

13 files changed

+404
-89
lines changed

13 files changed

+404
-89
lines changed

hooks/src/main/kotlin/com/intuit/hooks/AsyncSeriesBailHook.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ public abstract class AsyncSeriesBailHook<F : Function<BailResult<R>>, R> : Asyn
77
taps.forEach { tapInfo ->
88
when (val result = invokeWithContext(tapInfo.f, context)) {
99
is BailResult.Bail<R> -> return@call result.value
10+
is BailResult.Continue<*> -> Unit
1011
}
1112
}
1213

hooks/src/main/kotlin/com/intuit/hooks/SyncBailHook.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public abstract class SyncBailHook<F : Function<BailResult<R>>, R> : SyncBaseHoo
1313
taps.forEach { tapInfo ->
1414
when (val result = invokeWithContext(tapInfo.f, context)) {
1515
is BailResult.Bail<R> -> return@call result.value
16+
is BailResult.Continue<*> -> Unit
1617
}
1718
}
1819

processor/api/processor.api

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
public final class com/intuit/hooks/plugin/RaiseKt {
2+
public static final fun accumulate (Larrow/core/raise/Raise;Ljava/lang/Iterable;Lkotlin/jvm/functions/Function2;)V
3+
public static final fun ensure (Larrow/core/raise/Raise;ZLkotlin/jvm/functions/Function0;)V
4+
public static final fun mapOrAccumulate (Larrow/core/raise/Raise;Larrow/core/NonEmptyList;Lkotlin/jvm/functions/Function2;)Larrow/core/NonEmptyList;
5+
public static final fun mapOrAccumulate (Larrow/core/raise/Raise;Ljava/lang/Iterable;Lkotlin/jvm/functions/Function2;)Ljava/util/List;
6+
public static final fun mapOrAccumulate (Larrow/core/raise/Raise;Lkotlin/sequences/Sequence;Lkotlin/jvm/functions/Function2;)Ljava/util/List;
7+
public static final fun mapOrAccumulate-jkbboic (Larrow/core/raise/Raise;Ljava/util/Set;Lkotlin/jvm/functions/Function2;)Ljava/util/Set;
8+
public static final fun raise (Larrow/core/raise/Raise;Ljava/lang/Object;)Ljava/lang/Void;
9+
public static final fun raiseAll (Larrow/core/raise/Raise;Ljava/lang/Iterable;Lkotlin/jvm/functions/Function2;)Ljava/util/List;
10+
}
11+
112
public final class com/intuit/hooks/plugin/ksp/HooksProcessor : com/google/devtools/ksp/processing/SymbolProcessor {
213
public fun <init> (Lcom/google/devtools/ksp/processing/CodeGenerator;Lcom/google/devtools/ksp/processing/KSPLogger;)V
314
public fun process (Lcom/google/devtools/ksp/processing/Resolver;)Ljava/util/List;
Lines changed: 63 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,15 @@
1+
@file:OptIn(ExperimentalTypeInference::class)
2+
13
package com.intuit.hooks.plugin
24

3-
import arrow.core.Nel
4-
import arrow.core.nel
5-
import arrow.core.raise.Raise
6-
import arrow.core.raise.RaiseDSL
7-
import arrow.core.raise.ensure
8-
import arrow.core.raise.recover
5+
import arrow.core.*
6+
import arrow.core.raise.*
97
import kotlin.experimental.ExperimentalTypeInference
108

119
// Collection of [Raise] helpers for accumulating errors from a single error context
1210

1311
/** Helper for accumulating errors from single-error validators */
1412
@RaiseDSL
15-
@OptIn(ExperimentalTypeInference::class)
1613
internal fun <Error, A> Raise<Nel<Error>>.ensure(@BuilderInference block: Raise<Error>.() -> A): A =
1714
recover(block) { e: Error -> raise(e.nel()) }
1815

@@ -22,8 +19,65 @@ public inline fun <Error> Raise<Nel<Error>>.ensure(condition: Boolean, raise: ()
2219
recover({ ensure(condition, raise) }) { e: Error -> raise(e.nel()) }
2320
}
2421

25-
/** Raise a _logical failure_ of type [Error] */
22+
/** Raise a _logical failure_ of type [Error] in a multi-[Error] accumulator */
2623
@RaiseDSL
2724
public inline fun <Error> Raise<Nel<Error>>.raise(r: Error): Nothing {
2825
raise(r.nel())
29-
}
26+
}
27+
28+
@RaiseDSL
29+
public inline fun <Error, A> Raise<NonEmptyList<Error>>.raiseAll(
30+
iterable: Iterable<A>,
31+
@BuilderInference transform: Raise<NonEmptyList<Error>>.(A) -> Unit
32+
): List<Unit> = mapOrAccumulate(iterable) { arg ->
33+
recover<NonEmptyList<Error>, Unit>({ transform(arg) }) { errors ->
34+
this@raiseAll.raise(errors)
35+
}
36+
}
37+
38+
/** Explicitly accumulate errors that may have been raised while processing each element */
39+
context(Raise<NonEmptyList<Error>>)
40+
@RaiseDSL
41+
public inline fun <Error, A> Iterable<A>.accumulate(
42+
@BuilderInference operation: Raise<Nel<Error>>.(A) -> Unit
43+
) {
44+
flatMap {
45+
recover({
46+
operation(it); emptyList()
47+
}) { it }
48+
}.toNonEmptyListOrNull()?.let { raise(it) }
49+
}
50+
51+
/** [mapOrAccumulate] variant that accumulates errors from a validator that may raise multiple errors */
52+
context(Raise<NonEmptyList<Error>>)
53+
@RaiseDSL
54+
public inline fun <Error, A, B> Sequence<A>.mapOrAccumulate( // TODO: Consider renaming
55+
@BuilderInference operation: Raise<Nel<Error>>.(A) -> B
56+
): List<B> = toList().mapOrAccumulate(operation)
57+
58+
/** [mapOrAccumulate] variant that accumulates errors from a validator that may raise multiple errors */
59+
context(Raise<NonEmptyList<Error>>)
60+
@RaiseDSL
61+
public inline fun <Error, A, B> Iterable<A>.mapOrAccumulate( // TODO: Consider renaming
62+
@BuilderInference operation: Raise<Nel<Error>>.(A) -> B
63+
): List<B> = recover({
64+
mapOrAccumulate(this@mapOrAccumulate) { operation(it) }
65+
}) { errors -> raise(errors.flatMap { it }) }
66+
67+
/** [mapOrAccumulate] variant that accumulates errors from a validator that may raise multiple errors */
68+
context(Raise<NonEmptyList<Error>>)
69+
@RaiseDSL
70+
public inline fun <Error, A, B> NonEmptyList<A>.mapOrAccumulate( // TODO: Consider renaming
71+
@BuilderInference operation: Raise<Nel<Error>>.(A) -> B
72+
): NonEmptyList<B> = recover({
73+
mapOrAccumulate(this@mapOrAccumulate) { operation(it) }
74+
}) { errors -> raise(errors.flatMap { it }) }
75+
76+
/** [mapOrAccumulate] variant that accumulates errors from a validator that may raise multiple errors */
77+
context(Raise<NonEmptyList<Error>>)
78+
@RaiseDSL
79+
public inline fun <Error, A, B> NonEmptySet<A>.mapOrAccumulate( // TODO: Consider renaming
80+
@BuilderInference operation: Raise<Nel<Error>>.(A) -> B
81+
): NonEmptySet<B> = recover({
82+
mapOrAccumulate(this@mapOrAccumulate) { operation(it) }
83+
}) { errors -> raise(errors.flatMap { it }) }

processor/src/main/kotlin/com/intuit/hooks/plugin/codegen/HookType.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ internal sealed class HookProperty {
77
object Waterfall : HookProperty()
88
}
99

10-
internal enum class HookType(vararg val properties: HookProperty) {
10+
internal enum class HookType(val properties: Set<HookProperty>) {
1111
SyncHook,
1212
SyncBailHook(HookProperty.Bail),
1313
SyncWaterfallHook(HookProperty.Waterfall),
@@ -19,6 +19,8 @@ internal enum class HookType(vararg val properties: HookProperty) {
1919
AsyncSeriesWaterfallHook(HookProperty.Async, HookProperty.Waterfall),
2020
AsyncSeriesLoopHook(HookProperty.Async, HookProperty.Loop);
2121

22+
constructor(vararg properties: HookProperty) : this(properties.toSet())
23+
2224
companion object {
2325
val supportedHookTypes = values().map(HookType::name)
2426

processor/src/main/kotlin/com/intuit/hooks/plugin/ksp/HooksProcessor.kt

Lines changed: 21 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,20 @@ import com.google.devtools.ksp.getVisibility
77
import com.google.devtools.ksp.processing.*
88
import com.google.devtools.ksp.symbol.*
99
import com.google.devtools.ksp.validate
10-
import com.google.devtools.ksp.visitor.KSDefaultVisitor
1110
import com.intuit.hooks.plugin.codegen.*
12-
import com.intuit.hooks.plugin.ensure
1311
import com.intuit.hooks.plugin.ksp.validation.*
1412
import com.intuit.hooks.plugin.ksp.validation.EdgeCase
1513
import com.intuit.hooks.plugin.ksp.validation.HookValidationError
1614
import com.intuit.hooks.plugin.ksp.validation.error
1715
import com.intuit.hooks.plugin.ksp.validation.validateProperty
16+
import com.intuit.hooks.plugin.mapOrAccumulate
1817
import com.intuit.hooks.plugin.raise
1918
import com.squareup.kotlinpoet.*
2019
import com.squareup.kotlinpoet.ksp.*
2120

2221
public class HooksProcessor(
2322
private val codeGenerator: CodeGenerator,
24-
private val logger: KSPLogger,
23+
private val logger: KSPLogger
2524
) : SymbolProcessor {
2625

2726
override fun process(resolver: Resolver): List<KSAnnotated> {
@@ -32,26 +31,18 @@ public class HooksProcessor(
3231
return emptyList()
3332
}
3433

35-
private inner class HookPropertyVisitor : KSDefaultVisitor<TypeParameterResolver, HookInfo>() {
36-
34+
private inner class HookPropertyVisitor : KSRaiseVisitor<TypeParameterResolver, HookInfo, HookValidationError>() {
3735
context(Raise<Nel<HookValidationError>>)
38-
override fun visitPropertyDeclaration(property: KSPropertyDeclaration, parentResolver: TypeParameterResolver): HookInfo {
39-
ensure(property.modifiers.contains(Modifier.ABSTRACT)) {
40-
HookValidationError.NotAnAbstractProperty(property)
41-
}
42-
43-
return property.validateProperty(parentResolver)
44-
}
45-
46-
override fun defaultHandler(node: KSNode, data: TypeParameterResolver) = error("Should not happen.")
36+
override fun visitPropertyDeclaration(property: KSPropertyDeclaration, data: TypeParameterResolver): HookInfo =
37+
property.validateProperty(data)
4738
}
4839

4940
private inner class HookFileVisitor : KSVisitorVoid() {
5041
override fun visitFile(file: KSFile, data: Unit) {
5142
recover({
5243
val containers = file.declarations
5344
.filterIsInstance<KSClassDeclaration>()
54-
.flatMap { it.accept(HookContainerVisitor(), this) }
45+
.flatMap { it.accept(HookContainerVisitor(), Unit) }
5546
.ifEmpty { raise(EdgeCase.NoHooksDefined(file)) }
5647

5748
val packageName = file.packageName.asString()
@@ -69,45 +60,46 @@ public class HooksProcessor(
6960
}
7061
}
7162

72-
private inner class HookContainerVisitor : KSDefaultVisitor<Raise<Nel<HookValidationError>>, Sequence<HooksContainer>>() {
73-
// TODO: Try with context receiver
74-
override fun visitClassDeclaration(
75-
classDeclaration: KSClassDeclaration,
76-
raise: Raise<Nel<HookValidationError>>
77-
): Sequence<HooksContainer> = with(raise) {
63+
private inner class HookContainerVisitor : KSRaiseVisitor<Unit, Sequence<HooksContainer>, HookValidationError>() {
64+
65+
context(Raise<Nel<HookValidationError>>)
66+
override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: Unit): Sequence<HooksContainer> {
7867
val superTypeNames = classDeclaration.superTypes
7968
.filter { it.toString().contains("Hooks") }
8069
.toList()
8170

8271
return if (superTypeNames.isEmpty()) {
8372
classDeclaration.declarations
8473
.filter { it is KSClassDeclaration && it.validate() /* TODO: Tie in validations to KSP */ }
85-
.flatMap { it.accept(this@HookContainerVisitor, raise) }
74+
.flatMap { it.accept(this@HookContainerVisitor, Unit) }
8675
} else if (superTypeNames.any { it.resolve().declaration.qualifiedName?.getQualifier() == "com.intuit.hooks.dsl" }) {
8776
val parentResolver = classDeclaration.typeParameters.toTypeParameterResolver()
8877

8978
classDeclaration.getAllProperties()
90-
.map { it.accept(HookPropertyVisitor(), parentResolver) }
91-
// TODO: Maybe curry class declaration
92-
.run { createHooksContainer(classDeclaration, toList()) }
79+
.mapOrAccumulate { it.accept(HookPropertyVisitor(), parentResolver) }
80+
.let { createHooksContainer(classDeclaration, it) }
9381
.let { sequenceOf(it) }
94-
} else emptySequence()
82+
} else {
83+
emptySequence()
84+
}
9585
}
9686

97-
fun ClassKind.toTypeSpecKind(): TypeSpec.Kind = when (this) {
87+
context(Raise<Nel<HookValidationError>>)
88+
fun KSClassDeclaration.toTypeSpecKind(): TypeSpec.Kind = when (classKind) {
9889
ClassKind.CLASS -> TypeSpec.Kind.CLASS
9990
ClassKind.INTERFACE -> TypeSpec.Kind.INTERFACE
10091
ClassKind.OBJECT -> TypeSpec.Kind.OBJECT
101-
else -> throw NotImplementedError("Hooks in constructs other than class, interface, and object aren't supported")
92+
else -> raise(HookValidationError.UnsupportedContainer(this))
10293
}
10394

95+
context(Raise<Nel<HookValidationError>>)
10496
fun createHooksContainer(classDeclaration: KSClassDeclaration, hooks: List<HookInfo>): HooksContainer {
10597
val name =
10698
"${classDeclaration.parentDeclaration?.simpleName?.asString() ?: ""}${classDeclaration.simpleName.asString()}Impl"
10799
val visibilityModifier = classDeclaration.getVisibility().toKModifier() ?: KModifier.PUBLIC
108100
val typeArguments = classDeclaration.typeParameters.map { it.toTypeVariableName() }
109101
val className = classDeclaration.toClassName()
110-
val typeSpecKind = classDeclaration.classKind.toTypeSpecKind()
102+
val typeSpecKind = classDeclaration.toTypeSpecKind()
111103

112104
return HooksContainer(
113105
name,
@@ -118,8 +110,6 @@ public class HooksProcessor(
118110
hooks
119111
)
120112
}
121-
122-
override fun defaultHandler(node: KSNode, data: Raise<Nel<HookValidationError>>) = TODO("Not yet implemented")
123113
}
124114

125115
public class Provider : SymbolProcessorProvider {

0 commit comments

Comments
 (0)