Skip to content

Commit d0ce3b1

Browse files
committed
WIP: Validated -> Raise 2
1 parent e46f59f commit d0ce3b1

File tree

6 files changed

+56
-168
lines changed

6 files changed

+56
-168
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.intuit.hooks.plugin
2+
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
9+
import kotlin.experimental.ExperimentalTypeInference
10+
11+
// Collection of [Raise] helpers for accumulating errors from a single error context
12+
13+
/** Helper for accumulating errors from single-error validators */
14+
@RaiseDSL
15+
@OptIn(ExperimentalTypeInference::class)
16+
internal fun <Error, A> Raise<Nel<Error>>.ensure(@BuilderInference block: Raise<Error>.() -> A): A =
17+
recover(block) { e: Error -> raise(e.nel()) }
18+
19+
/** Helper for accumulating errors from single-error validators */
20+
@RaiseDSL
21+
public inline fun <Error> Raise<Nel<Error>>.ensure(condition: Boolean, raise: () -> Error) {
22+
recover({ ensure(condition, raise) }) { e: Error -> raise(e.nel()) }
23+
}
24+
25+
/** Raise a _logical failure_ of type [Error] */
26+
@RaiseDSL
27+
public inline fun <Error> Raise<Nel<Error>>.raise(r: Error): Nothing {
28+
raise(r.nel())
29+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@ import com.google.devtools.ksp.symbol.*
99
import com.google.devtools.ksp.validate
1010
import com.google.devtools.ksp.visitor.KSDefaultVisitor
1111
import com.intuit.hooks.plugin.codegen.*
12+
import com.intuit.hooks.plugin.ensure
1213
import com.intuit.hooks.plugin.ksp.validation.*
1314
import com.intuit.hooks.plugin.ksp.validation.EdgeCase
1415
import com.intuit.hooks.plugin.ksp.validation.HookValidationError
1516
import com.intuit.hooks.plugin.ksp.validation.error
1617
import com.intuit.hooks.plugin.ksp.validation.validateProperty
18+
import com.intuit.hooks.plugin.raise
1719
import com.squareup.kotlinpoet.*
1820
import com.squareup.kotlinpoet.ksp.*
1921

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

Lines changed: 2 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import com.intuit.hooks.plugin.codegen.HookParameter
1212
import com.intuit.hooks.plugin.codegen.HookSignature
1313
import com.intuit.hooks.plugin.codegen.HookType
1414
import com.intuit.hooks.plugin.codegen.HookType.Companion.annotationDslMarkers
15+
import com.intuit.hooks.plugin.ensure
1516
import com.intuit.hooks.plugin.ksp.HooksProcessor
1617
import com.intuit.hooks.plugin.ksp.text
1718
import com.squareup.kotlinpoet.KModifier
@@ -43,6 +44,7 @@ import com.squareup.kotlinpoet.ksp.toTypeName
4344
override fun toString() = "${symbol.shortName.asString()}Hook"
4445
}
4546

47+
/** Build [HookInfo] from the validated [HookAnnotation] found on the [property] */
4648
context(Raise<Nel<HookValidationError>>)
4749
internal fun KSPropertyDeclaration.validateHookAnnotation(parentResolver: TypeParameterResolver): HookInfo {
4850
val annotation = ensure { onlyHasASingleDslAnnotation() }
@@ -57,24 +59,6 @@ internal fun KSPropertyDeclaration.validateHookAnnotation(parentResolver: TypePa
5759
)
5860
}
5961

60-
/** Build [HookInfo] from the validated [HookAnnotation] found on the [property] */
61-
internal fun KSPropertyDeclaration.validateHookAnnotation(parentResolver: TypeParameterResolver): ValidatedNel<HookValidationError, HookInfo> =
62-
onlyHasASingleDslAnnotation().andThen { annotation ->
63-
64-
val hasCodeGenerator = hasCodeGenerator(annotation)
65-
val mustBeHookType = mustBeHookType(annotation, parentResolver)
66-
val validateParameters = validateParameters(annotation, parentResolver)
67-
val hookMember = simpleName.asString()
68-
val propertyVisibility = this.getVisibility().toKModifier() ?: KModifier.PUBLIC
69-
70-
hasCodeGenerator.zip(
71-
mustBeHookType,
72-
validateParameters
73-
) { hookType: HookType, hookSignature: HookSignature, hookParameters: List<HookParameter> ->
74-
HookInfo(hookMember, hookType, hookSignature, hookParameters, propertyVisibility)
75-
}
76-
}
77-
7862
// TODO: This'd be a good smart constructor use case
7963
context(Raise<HookValidationError>) private fun KSPropertyDeclaration.onlyHasASingleDslAnnotation(): HookAnnotation {
8064
val annotations = annotations.filter { it.shortName.asString() in annotationDslMarkers }.toList()
@@ -85,15 +69,6 @@ context(Raise<HookValidationError>) private fun KSPropertyDeclaration.onlyHasASi
8569
}.let(::HookAnnotation)
8670
}
8771

88-
private fun KSPropertyDeclaration.onlyHasASingleDslAnnotation(): ValidatedNel<HookValidationError, HookAnnotation> {
89-
val annotations = annotations.filter { it.shortName.asString() in annotationDslMarkers }.toList()
90-
return when (annotations.size) {
91-
0 -> HookValidationError.NoHookDslAnnotations(this).invalidNel()
92-
1 -> annotations.single().let(::HookAnnotation).valid()
93-
else -> HookValidationError.TooManyHookDslAnnotations(annotations, this).invalidNel()
94-
}
95-
}
96-
9772
context(Raise<HookValidationError>) private fun HookAnnotation.validateParameters(parentResolver: TypeParameterResolver): List<HookParameter> = try {
9873
hookFunctionSignatureReference.functionParameters.mapIndexed { index: Int, parameter: KSValueParameter ->
9974
val name = parameter.name?.asString()
@@ -104,25 +79,9 @@ context(Raise<HookValidationError>) private fun HookAnnotation.validateParameter
10479
raise(HookValidationError.MustBeHookTypeSignature(this))
10580
}
10681

107-
private fun validateParameters(annotation: HookAnnotation, parentResolver: TypeParameterResolver): ValidatedNel<HookValidationError, List<HookParameter>> = try {
108-
annotation.hookFunctionSignatureReference.functionParameters.mapIndexed { index: Int, parameter: KSValueParameter ->
109-
val name = parameter.name?.asString()
110-
val type = parameter.type.toTypeName(parentResolver)
111-
HookParameter(name, type, index)
112-
}.valid()
113-
} catch (exception: Exception) {
114-
HookValidationError.MustBeHookTypeSignature(annotation).invalidNel()
115-
}
116-
11782
// TODO: This would be obsolete with smart constructor
11883
context(Raise<HookValidationError.NoCodeGenerator>) private fun HookAnnotation.hasCodeGenerator(): HookType = type
11984

120-
private fun hasCodeGenerator(annotation: HookAnnotation): ValidatedNel<HookValidationError, HookType> = try {
121-
annotation.type!!.valid()
122-
} catch (e: Exception) {
123-
HookValidationError.NoCodeGenerator(annotation).invalidNel()
124-
}
125-
12685
/** TODO: Another good smart constructor example */
12786
context(Raise<HookValidationError>)
12887
private fun HookAnnotation.mustBeHookType(parentResolver: TypeParameterResolver): HookSignature = try {
@@ -143,21 +102,3 @@ private fun HookAnnotation.mustBeHookType(parentResolver: TypeParameterResolver)
143102
} catch (exception: Exception) {
144103
raise(HookValidationError.MustBeHookTypeSignature(this))
145104
}
146-
private fun mustBeHookType(annotation: HookAnnotation, parentResolver: TypeParameterResolver): ValidatedNel<HookValidationError, HookSignature> = try {
147-
val isSuspend: Boolean = annotation.hookFunctionSignatureType.modifiers.contains(Modifier.SUSPEND)
148-
// I'm leaving this here because KSP knows that it's (String) -> Int, whereas once it gets to Poet, it's just kotlin.Function1<kotlin.Int, kotlin.String>
149-
val text = annotation.hookFunctionSignatureType.text
150-
val hookFunctionSignatureType = annotation.hookFunctionSignatureType.toTypeName(parentResolver)
151-
val returnType = annotation.hookFunctionSignatureReference.returnType.toTypeName(parentResolver)
152-
val returnTypeType = annotation.hookFunctionSignatureReference.returnType.element?.typeArguments?.firstOrNull()?.toTypeName(parentResolver)
153-
154-
HookSignature(
155-
text,
156-
isSuspend,
157-
returnType,
158-
returnTypeType,
159-
hookFunctionSignatureType
160-
).valid()
161-
} catch (exception: Exception) {
162-
HookValidationError.MustBeHookTypeSignature(annotation).invalidNel()
163-
}

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

Lines changed: 2 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,13 @@ import arrow.core.raise.*
55
import com.google.devtools.ksp.symbol.KSPropertyDeclaration
66
import com.intuit.hooks.plugin.codegen.HookInfo
77
import com.intuit.hooks.plugin.codegen.HookProperty
8+
import com.intuit.hooks.plugin.ensure
89
import kotlin.contracts.CallsInPlace
910
import kotlin.contracts.ExperimentalContracts
1011
import kotlin.contracts.InvocationKind.AT_MOST_ONCE
1112
import kotlin.contracts.contract
1213
import kotlin.experimental.ExperimentalTypeInference
1314

14-
internal fun HookProperty.validate(
15-
info: HookInfo,
16-
property: KSPropertyDeclaration,
17-
): ValidatedNel<HookValidationError, HookProperty> = when (this) {
18-
is HookProperty.Bail -> valid()
19-
is HookProperty.Loop -> valid()
20-
is HookProperty.Async -> validate(info, property)
21-
is HookProperty.Waterfall -> validate(info, property)
22-
}
2315

2416
context(Raise<Nel<HookValidationError>>)
2517
internal fun HookProperty.validate(
@@ -29,7 +21,7 @@ internal fun HookProperty.validate(
2921
when (this) {
3022
is HookProperty.Bail -> Unit
3123
is HookProperty.Loop -> Unit
32-
is HookProperty.Async -> raiseSingle {
24+
is HookProperty.Async -> ensure {
3325
info.validateAsync(property)
3426
}
3527
is HookProperty.Waterfall -> validate(info, property)
@@ -41,22 +33,6 @@ private fun HookInfo.validateAsync(property: KSPropertyDeclaration) {
4133
ensure(hookSignature.isSuspend) { HookValidationError.AsyncHookWithoutSuspend(property) }
4234
}
4335

44-
private fun HookProperty.Async.validate(
45-
info: HookInfo,
46-
property: KSPropertyDeclaration,
47-
): ValidatedNel<HookValidationError, HookProperty> =
48-
if (info.hookSignature.isSuspend) valid()
49-
else HookValidationError.AsyncHookWithoutSuspend(property).invalidNel()
50-
51-
private fun HookProperty.Waterfall.validate(
52-
info: HookInfo,
53-
property: KSPropertyDeclaration,
54-
): ValidatedNel<HookValidationError, HookProperty> =
55-
Either.zipOrAccumulate(
56-
arity(info, property).toEither(),
57-
parameters(info, property).toEither()
58-
) { _, _ -> this }.toValidated()
59-
6036
context(Raise<Nel<HookValidationError>>)
6137
private fun HookProperty.Waterfall.validate(
6238
info: HookInfo,
@@ -68,14 +44,6 @@ private fun HookProperty.Waterfall.validate(
6844
) { _, _ -> }
6945
}
7046

71-
private fun HookProperty.Waterfall.arity(
72-
info: HookInfo,
73-
property: KSPropertyDeclaration,
74-
): ValidatedNel<HookValidationError, HookProperty> {
75-
return if (!info.zeroArity) valid()
76-
else HookValidationError.WaterfallMustHaveParameters(property).invalidNel()
77-
}
78-
7947
context(Raise<HookValidationError.WaterfallMustHaveParameters>)
8048
private fun HookProperty.Waterfall.arity(
8149
info: HookInfo,
@@ -84,15 +52,6 @@ private fun HookProperty.Waterfall.arity(
8452
ensure(!info.zeroArity) { HookValidationError.WaterfallMustHaveParameters(property) }
8553
}
8654

87-
88-
private fun HookProperty.Waterfall.parameters(
89-
info: HookInfo,
90-
property: KSPropertyDeclaration,
91-
): ValidatedNel<HookValidationError, HookProperty> {
92-
return if (info.hookSignature.returnType == info.params.firstOrNull()?.type) valid()
93-
else HookValidationError.WaterfallParameterTypeMustMatch(property).invalidNel()
94-
}
95-
9655
context(Raise<HookValidationError.WaterfallParameterTypeMustMatch>)
9756
private fun HookProperty.Waterfall.parameters(
9857
info: HookInfo,

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

Lines changed: 5 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -6,28 +6,13 @@ import arrow.core.raise.ensure
66
import com.google.devtools.ksp.processing.KSPLogger
77
import com.google.devtools.ksp.symbol.*
88
import com.intuit.hooks.plugin.codegen.HookInfo
9+
import com.intuit.hooks.plugin.ensure
910
import com.intuit.hooks.plugin.ksp.text
10-
import com.intuit.hooks.plugin.ksp.validation.ensure
1111
import com.squareup.kotlinpoet.ksp.TypeParameterResolver
12-
import kotlin.contracts.InvocationKind
13-
import kotlin.contracts.contract
14-
import kotlin.experimental.ExperimentalTypeInference
1512

16-
//context(HookValidationError)
17-
internal fun KSPLogger.error(validationError: HookValidationError) {
18-
error(validationError.message, validationError.symbol)
19-
}
20-
21-
internal sealed interface LogicalFailure
22-
23-
/** Logical failure that can be ignored */
24-
internal sealed interface EdgeCase : LogicalFailure {
25-
class NoHooksDefined(val file: KSFile) : EdgeCase
26-
}
27-
28-
/** Logical failure that should probably be reported */
29-
internal sealed interface ErrorCase : LogicalFailure {
30-
val message: String
13+
context(HookValidationError)
14+
internal fun KSPLogger.error() {
15+
error(message, symbol)
3116
}
3217

3318
// TODO: It'd be nice if the validations were codegen framework agnostic
@@ -47,6 +32,7 @@ internal sealed class HookValidationError(override val message: String, val symb
4732
operator fun component2(): KSNode = symbol
4833
}
4934

35+
/** main entrypoint for validating [KSPropertyDeclaration]s as valid annotated hook members */
5036
context(Raise<Nel<HookValidationError>>)
5137
internal fun KSPropertyDeclaration.validateProperty(parentResolver: TypeParameterResolver): HookInfo {
5238
// 1. validate types
@@ -71,60 +57,15 @@ internal fun KSPropertyDeclaration.validateProperty(parentResolver: TypeParamete
7157
}
7258
}
7359

74-
/** main entrypoint for validating [KSPropertyDeclaration]s as valid annotated hook members */
75-
//internal fun validateProperty(property: KSPropertyDeclaration, parentResolver: TypeParameterResolver): ValidatedNel<HookValidationError, HookInfo> = with(property) {
76-
// // validate property has the correct type
77-
// validateHookType()
78-
// .andThen { validateHookAnnotation(parentResolver) }
79-
// // validate property against hook info with specific hook type validations
80-
// .andThen { info -> validateHookProperties(info) }
81-
//
82-
// recover { validateHookType() }
83-
// .map { validateHookProperties(parentResolver) }
84-
// .map { info -> validateHookProperties(info) }
85-
//
86-
// fold(
87-
// { validateHookType() },
88-
// )
89-
//}
90-
9160
context(Raise<HookValidationError.UnsupportedAbstractPropertyType>)
9261
private fun KSPropertyDeclaration.validateHookType() {
9362
ensure(type.text == "Hook") {
9463
HookValidationError.UnsupportedAbstractPropertyType(this)
9564
}
9665
}
9766

98-
private fun KSPropertyDeclaration.validateHookType(): ValidatedNel<HookValidationError, KSTypeReference> =
99-
if (type.text == "Hook") type.valid()
100-
else HookValidationError.UnsupportedAbstractPropertyType(this).invalidNel()
101-
102-
10367
context(Raise<Nel<HookValidationError>>) private fun KSPropertyDeclaration.validateHookProperties(info: HookInfo) {
10468
info.hookType.properties.map {
10569
it.validate(info, this)
10670
}
10771
}
108-
109-
//private fun KSPropertyDeclaration.validateHookProperties(hookInfo: HookInfo): Validated<NonEmptyList<HookValidationError>, HookInfo> =
110-
// hookInfo.hookType.properties.map { it.validate(hookInfo, this) }
111-
// .sequence()
112-
// .map { hookInfo }
113-
114-
/** Helper for accumulating errors from single-error validators */
115-
@RaiseDSL
116-
@OptIn(ExperimentalTypeInference::class)
117-
internal fun <Error, A> Raise<Nel<Error>>.ensure(@BuilderInference block: Raise<Error>.() -> A): A =
118-
recover(block) { e: Error -> raise(e.nel()) }
119-
120-
/** Helper for accumulating errors from single-error validators */
121-
@RaiseDSL
122-
public inline fun <Error> Raise<Nel<Error>>.ensure(condition: Boolean, raise: () -> Error) {
123-
recover({ ensure(condition, raise) }) { e: Error -> raise(e.nel()) }
124-
}
125-
126-
/** Raise a _logical failure_ of type [Error] */
127-
@RaiseDSL
128-
public inline fun <Error> Raise<Nel<Error>>.raise(r: Error): Nothing {
129-
raise(r.nel())
130-
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.intuit.hooks.plugin.ksp.validation
2+
3+
import com.google.devtools.ksp.symbol.KSFile
4+
5+
/** Base construct to represent a reason to not execute happy-path logic */
6+
internal sealed interface LogicalFailure
7+
8+
/** Logical failure that can be ignored, valid edge case */
9+
internal sealed interface EdgeCase : LogicalFailure {
10+
class NoHooksDefined(val file: KSFile) : EdgeCase
11+
}
12+
13+
/** Logical failure that should probably be reported, something bad happened */
14+
internal sealed interface ErrorCase : LogicalFailure {
15+
val message: String
16+
}

0 commit comments

Comments
 (0)