diff --git a/kotlin/workflow-core/src/main/java/com/squareup/workflow/Worker.kt b/kotlin/workflow-core/src/main/java/com/squareup/workflow/Worker.kt index 3726d6758..3240d34e4 100644 --- a/kotlin/workflow-core/src/main/java/com/squareup/workflow/Worker.kt +++ b/kotlin/workflow-core/src/main/java/com/squareup/workflow/Worker.kt @@ -31,7 +31,8 @@ import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flow import kotlin.experimental.ExperimentalTypeInference -import kotlin.reflect.KClass +import kotlin.reflect.KType +import kotlin.reflect.typeOf /** * Represents a unit of asynchronous work that can have zero, one, or multiple outputs. @@ -179,6 +180,17 @@ interface Worker { companion object { + /** + * Use this value instead of calling `typeOf()` directly, which isn't allowed because + * [Nothing] isn't allowed as a reified type parameter. + * + * The KType of Nothing on the JVM is actually java.lang.Void if you do + * Nothing::class.createType(). However createType() lives in the reflection library, so we just + * reference Void directly so we don't have to add a dependency on kotlin-reflect. + */ + @UseExperimental(ExperimentalStdlibApi::class) + private val TYPE_OF_NOTHING = typeOf() + /** * Shorthand for `flow { block() }.asWorker()`. * @@ -208,7 +220,7 @@ interface Worker { */ fun createSideEffect( block: suspend () -> Unit - ): Worker = TypedWorker(Nothing::class, flow { block() }) + ): Worker = TypedWorker(TYPE_OF_NOTHING, flow { block() }) /** * Returns a [Worker] that finishes immediately without emitting anything. @@ -259,8 +271,9 @@ interface Worker { /** * Returns a [Worker] that will, when performed, emit whatever this [Flow] receives. */ +@UseExperimental(ExperimentalStdlibApi::class) inline fun Flow.asWorker(): Worker = - TypedWorker(OutputT::class, this) + TypedWorker(typeOf(), this) /** * Returns a [Worker] that will await this [Deferred] and then emit it. @@ -361,8 +374,7 @@ fun Worker.transform( */ @PublishedApi internal class TypedWorker( - /** Can't be `KClass` because `OutputT` doesn't have upper bound `Any`. */ - private val type: KClass<*>, + private val type: KType, private val work: Flow ) : Worker { diff --git a/kotlin/workflow-core/src/test/java/com/squareup/workflow/WorkerTest.kt b/kotlin/workflow-core/src/test/java/com/squareup/workflow/WorkerTest.kt index 1464c4c6c..3e1e24840 100644 --- a/kotlin/workflow-core/src/test/java/com/squareup/workflow/WorkerTest.kt +++ b/kotlin/workflow-core/src/test/java/com/squareup/workflow/WorkerTest.kt @@ -20,4 +20,22 @@ class WorkerTest { assertTrue(Worker1().doesSameWorkAs(Worker1())) assertFalse(Worker1().doesSameWorkAs(Worker2())) } + + @Test fun `createSideEffect workers are equivalent`() { + val worker1 = Worker.createSideEffect {} + val worker2 = Worker.createSideEffect {} + assertTrue(worker1.doesSameWorkAs(worker2)) + } + + @Test fun `TypedWorkers are compared by higher types`() { + val worker1 = Worker.create> { } + val worker2 = Worker.create> { } + assertFalse(worker1.doesSameWorkAs(worker2)) + } + + @Test fun `TypedWorkers are equivalent with higher types`() { + val worker1 = Worker.create> { } + val worker2 = Worker.create> { } + assertTrue(worker1.doesSameWorkAs(worker2)) + } } diff --git a/kotlin/workflow-tracing/src/test/resources/com/squareup/workflow/diagnostic/tracing/expected_trace_file.txt b/kotlin/workflow-tracing/src/test/resources/com/squareup/workflow/diagnostic/tracing/expected_trace_file.txt index 16b2c5192..c53cd43e7 100644 --- a/kotlin/workflow-tracing/src/test/resources/com/squareup/workflow/diagnostic/tracing/expected_trace_file.txt +++ b/kotlin/workflow-tracing/src/test/resources/com/squareup/workflow/diagnostic/tracing/expected_trace_file.txt @@ -36,7 +36,7 @@ {"name":"TracingDiagnosticListenerTest$TestWorkflow (0)","cat":"rendering","ph":"B","ts":0,"pid":0,"tid":0,"args":{"workflowId":"0","props":"2","state":"changed state"}}, {"name":"TracingDiagnosticListenerTest$TestWorkflow (1)","cat":"rendering","ph":"B","ts":0,"pid":0,"tid":0,"args":{"workflowId":"1","props":"0","state":"initial"}}, {"name":"TracingDiagnosticListenerTest$TestWorkflow (1)","cat":"rendering","ph":"E","ts":0,"pid":0,"tid":0,"args":{"rendering":"initial"}}, -{"name":"Worker: 2","cat":"workflow","ph":"b","ts":0,"pid":0,"tid":0,"id":"workflow","args":{"parent":"TracingDiagnosticListenerTest$TestWorkflow (0)","key":"","description":"TypedWorker(class kotlin.String)"}}, +{"name":"Worker: 2","cat":"workflow","ph":"b","ts":0,"pid":0,"tid":0,"id":"workflow","args":{"parent":"TracingDiagnosticListenerTest$TestWorkflow (0)","key":"","description":"TypedWorker(kotlin.String)"}}, {"name":"TracingDiagnosticListenerTest$TestWorkflow (0)","cat":"rendering","ph":"E","ts":0,"pid":0,"tid":0,"args":{"rendering":"rendering"}}, {"name":"Render Pass","cat":"rendering","ph":"E","ts":0,"pid":0,"tid":0,"args":{"rendering":"rendering"}}, {"name":"used/free memory","ph":"C","ts":0,"pid":0,"tid":0,"args":{"usedMemory":1,"freeMemory":42}},