Repository: cashapp/redwood
Pull request #591 (2022-12-05 20:56:35)

Migrate event tag to value class with signed storage
Refs #418 


Patch:
diff --git a/redwood-cli/src/main/kotlin/app/cash/redwood/generator/diffConsumingGeneration.kt b/redwood-cli/src/main/kotlin/app/cash/redwood/generator/diffConsumingGeneration.kt
index a21b98600..84f6dece2 100644
--- a/redwood-cli/src/main/kotlin/app/cash/redwood/generator/diffConsumingGeneration.kt
+++ b/redwood-cli/src/main/kotlin/app/cash/redwood/generator/diffConsumingGeneration.kt
@@ -202,7 +202,7 @@ internal class DiffConsumingSunspotButton<W : Any>(
       2 -> delegate.enabled(json.decodeFromJsonElement(serializer_1, diff.value))
       3 -> {
         val onClick: (() -> Unit)? = if (diff.value.jsonPrimitive.boolean) {
-          { eventSink.sendEvent(Event(diff.id, 3)) }
+          { eventSink.sendEvent(Event(diff.id, EventTag(3))) }
         } else {
           null
         }
@@ -292,15 +292,17 @@ internal fun generateDiffConsumingWidget(schema: Schema, widget: Widget, host: S
                           nextSerializerId++
                         }
                         addStatement(
-                          "{ eventSink.sendEvent(%T(diff.id, %LU, json.encodeToJsonElement(serializer_%L, it))) }",
+                          "{ eventSink.sendEvent(%T(diff.id, %T(%L), json.encodeToJsonElement(serializer_%L, it))) }",
                           Protocol.Event,
+                          Protocol.EventTag,
                           trait.tag,
                           serializerId,
                         )
                       } else {
                         addStatement(
-                          "{ eventSink.sendEvent(%T(diff.id, %LU)) }",
+                          "{ eventSink.sendEvent(%T(diff.id, %T(%L))) }",
                           Protocol.Event,
+                          Protocol.EventTag,
                           trait.tag,
                         )
                       }
diff --git a/redwood-cli/src/main/kotlin/app/cash/redwood/generator/diffProducingGeneration.kt b/redwood-cli/src/main/kotlin/app/cash/redwood/generator/diffProducingGeneration.kt
index 18c6a8ba3..6843478f0 100644
--- a/redwood-cli/src/main/kotlin/app/cash/redwood/generator/diffProducingGeneration.kt
+++ b/redwood-cli/src/main/kotlin/app/cash/redwood/generator/diffProducingGeneration.kt
@@ -153,9 +153,9 @@ internal class DiffProducingSunspotButton(
   }
 
   override fun sendEvent(event: Event) {
-    when (val tag = event.tag) {
+    when (event.tag.value) {
       3 -> onClick?.invoke()
-      else -> mismatchHandler.onUnknownEvent(12, tag)
+      else -> mismatchHandler.onUnknownEvent(12, event.tag)
     }
   }
 }
@@ -275,7 +275,7 @@ internal fun generateDiffProducingWidget(schema: Schema, widget: Widget, host: S
             FunSpec.builder("sendEvent")
               .addModifiers(OVERRIDE)
               .addParameter("event", Protocol.Event)
-              .beginControlFlow("when (val tag = event.tag)")
+              .beginControlFlow("when (event.tag.value)")
               .apply {
                 for (event in widget.traits.filterIsInstance<Event>()) {
                   val parameterType = event.parameterType?.asTypeName()
@@ -284,17 +284,17 @@ internal fun generateDiffProducingWidget(schema: Schema, widget: Widget, host: S
                       nextSerializerId++
                     }
                     addStatement(
-                      "%LU -> %N?.invoke(json.decodeFromJsonElement(serializer_%L, event.value))",
+                      "%L -> %N?.invoke(json.decodeFromJsonElement(serializer_%L, event.value))",
                       event.tag,
                       event.name,
                       serializerId,
                     )
                   } else {
-                    addStatement("%LU -> %N?.invoke()", event.tag, event.name)
+                    addStatement("%L -> %N?.invoke()", event.tag, event.name)
                   }
                 }
               }
-              .addStatement("else -> mismatchHandler.onUnknownEvent(%L, tag)", widget.tag)
+              .addStatement("else -> mismatchHandler.onUnknownEvent(%L, event.tag)", widget.tag)
               .endControlFlow()
               .build(),
           )
diff --git a/redwood-cli/src/main/kotlin/app/cash/redwood/generator/types.kt b/redwood-cli/src/main/kotlin/app/cash/redwood/generator/types.kt
index 6586bda86..4a5b97209 100644
--- a/redwood-cli/src/main/kotlin/app/cash/redwood/generator/types.kt
+++ b/redwood-cli/src/main/kotlin/app/cash/redwood/generator/types.kt
@@ -27,6 +27,7 @@ import com.squareup.kotlinpoet.UNIT
 
 internal object Protocol {
   val Event = ClassName("app.cash.redwood.protocol", "Event")
+  val EventTag = ClassName("app.cash.redwood.protocol", "EventTag")
   val EventSink = ClassName("app.cash.redwood.protocol", "EventSink")
   val Id = ClassName("app.cash.redwood.protocol", "Id")
   val PropertyDiff = ClassName("app.cash.redwood.protocol", "PropertyDiff")
diff --git a/redwood-protocol-compose/src/commonMain/kotlin/app/cash/redwood/protocol/compose/ProtocolMismatchHandler.kt b/redwood-protocol-compose/src/commonMain/kotlin/app/cash/redwood/protocol/compose/ProtocolMismatchHandler.kt
index 901eeacee..e0f635faa 100644
--- a/redwood-protocol-compose/src/commonMain/kotlin/app/cash/redwood/protocol/compose/ProtocolMismatchHandler.kt
+++ b/redwood-protocol-compose/src/commonMain/kotlin/app/cash/redwood/protocol/compose/ProtocolMismatchHandler.kt
@@ -15,6 +15,7 @@
  */
 package app.cash.redwood.protocol.compose
 
+import app.cash.redwood.protocol.EventTag
 import kotlin.jvm.JvmField
 
 /**
@@ -24,14 +25,14 @@ import kotlin.jvm.JvmField
  */
 public interface ProtocolMismatchHandler {
   /** Handle a request to process an unknown event [tag] for the specified widget [kind]. */
-  public fun onUnknownEvent(kind: Int, tag: UInt)
+  public fun onUnknownEvent(kind: Int, tag: EventTag)
 
   public companion object {
     /** A [ProtocolMismatchHandler] which throws [IllegalArgumentException] for all callbacks. */
     @JvmField
     public val Throwing: ProtocolMismatchHandler = object : ProtocolMismatchHandler {
-      override fun onUnknownEvent(kind: Int, tag: UInt) {
-        throw IllegalArgumentException("Unknown event tag $tag for widget kind $kind")
+      override fun onUnknownEvent(kind: Int, tag: EventTag) {
+        throw IllegalArgumentException("Unknown event tag ${tag.value} for widget kind $kind")
       }
     }
   }
diff --git a/redwood-protocol/src/commonMain/kotlin/app/cash/redwood/protocol/protocol.kt b/redwood-protocol/src/commonMain/kotlin/app/cash/redwood/protocol/protocol.kt
index c5e332448..ab71105fb 100644
--- a/redwood-protocol/src/commonMain/kotlin/app/cash/redwood/protocol/protocol.kt
+++ b/redwood-protocol/src/commonMain/kotlin/app/cash/redwood/protocol/protocol.kt
@@ -15,7 +15,6 @@
  */
 package app.cash.redwood.protocol
 
-import kotlin.jvm.JvmInline
 import kotlinx.serialization.SerialName
 import kotlinx.serialization.Serializable
 import kotlinx.serialization.json.JsonArray
@@ -30,21 +29,12 @@ public fun interface DiffSink {
   public fun sendDiff(diff: Diff)
 }
 
-/** Identifies a widget instance. */
-@JvmInline
-@Serializable
-public value class Id(public val value: Long) {
-  public companion object {
-    public val Root: Id = Id(0L)
-  }
-}
-
 @Serializable
 public data class Event(
   /** Identifier for the widget from which this event originated. */
   val id: Id,
   /** Identifies which event occurred on the widget with [id]. */
-  val tag: UInt,
+  val tag: EventTag,
   val value: JsonElement = JsonNull,
 )
 
diff --git a/redwood-protocol/src/commonMain/kotlin/app/cash/redwood/protocol/values.kt b/redwood-protocol/src/commonMain/kotlin/app/cash/redwood/protocol/values.kt
new file mode 100644
index 000000000..6272150d1
--- /dev/null
+++ b/redwood-protocol/src/commonMain/kotlin/app/cash/redwood/protocol/values.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2022 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package app.cash.redwood.protocol
+
+import kotlin.jvm.JvmInline
+import kotlinx.serialization.Serializable
+
+/** Identifies a widget instance. */
+@JvmInline
+@Serializable
+public value class Id(public val value: Long) {
+  public companion object {
+    public val Root: Id = Id(0L)
+  }
+}
+
+@JvmInline
+@Serializable
+public value class EventTag(public val value: Int)


Test patch:
diff --git a/redwood-protocol-compose/src/commonTest/kotlin/app/cash/redwood/protocol/compose/DiffProducingWidgetFactoryTest.kt b/redwood-protocol-compose/src/commonTest/kotlin/app/cash/redwood/protocol/compose/DiffProducingWidgetFactoryTest.kt
index 842c4de22..ac4204a3d 100644
--- a/redwood-protocol-compose/src/commonTest/kotlin/app/cash/redwood/protocol/compose/DiffProducingWidgetFactoryTest.kt
+++ b/redwood-protocol-compose/src/commonTest/kotlin/app/cash/redwood/protocol/compose/DiffProducingWidgetFactoryTest.kt
@@ -18,6 +18,7 @@ package app.cash.redwood.protocol.compose
 import app.cash.redwood.LayoutModifier
 import app.cash.redwood.protocol.Diff
 import app.cash.redwood.protocol.Event
+import app.cash.redwood.protocol.EventTag
 import app.cash.redwood.protocol.Id
 import app.cash.redwood.protocol.LayoutModifiers
 import app.cash.redwood.protocol.PropertyDiff
@@ -143,7 +144,7 @@ class DiffProducingWidgetFactoryTest {
       argument = it
     }
 
-    diffProducingWidget.sendEvent(Event(Id(1), 4U, JsonPrimitive("PT10S")))
+    diffProducingWidget.sendEvent(Event(Id(1), EventTag(4), JsonPrimitive("PT10S")))
 
     assertEquals(10.seconds, argument)
   }
@@ -152,7 +153,7 @@ class DiffProducingWidgetFactoryTest {
     val factory = DiffProducingExampleSchemaWidgetFactory(ProtocolBridge())
     val button = factory.Button() as DiffProducingWidget
 
-    val event = Event(Id(1), 3456543U)
+    val event = Event(Id(1), EventTag(3456543))
     val t = assertFailsWith<IllegalArgumentException> {
       button.sendEvent(event)
     }
@@ -165,7 +166,7 @@ class DiffProducingWidgetFactoryTest {
     val factory = DiffProducingExampleSchemaWidgetFactory(ProtocolBridge(), mismatchHandler = handler)
     val button = factory.Button() as DiffProducingWidget
 
-    button.sendEvent(Event(Id(1), 3456543U))
+    button.sendEvent(Event(Id(1), EventTag(3456543)))
 
     assertEquals("Unknown event 3456543 for 4", handler.events.single())
   }
diff --git a/redwood-protocol-compose/src/commonTest/kotlin/app/cash/redwood/protocol/compose/ProtocolMismatchHandlerTest.kt b/redwood-protocol-compose/src/commonTest/kotlin/app/cash/redwood/protocol/compose/ProtocolMismatchHandlerTest.kt
index 929d3fdec..62d39253a 100644
--- a/redwood-protocol-compose/src/commonTest/kotlin/app/cash/redwood/protocol/compose/ProtocolMismatchHandlerTest.kt
+++ b/redwood-protocol-compose/src/commonTest/kotlin/app/cash/redwood/protocol/compose/ProtocolMismatchHandlerTest.kt
@@ -15,6 +15,7 @@
  */
 package app.cash.redwood.protocol.compose
 
+import app.cash.redwood.protocol.EventTag
 import kotlin.test.Test
 import kotlin.test.assertEquals
 import kotlin.test.assertFailsWith
@@ -22,7 +23,7 @@ import kotlin.test.assertFailsWith
 class ProtocolMismatchHandlerTest {
   @Test fun throwingUnknownEvent() {
     val t = assertFailsWith<IllegalArgumentException> {
-      ProtocolMismatchHandler.Throwing.onUnknownEvent(1, 2U)
+      ProtocolMismatchHandler.Throwing.onUnknownEvent(1, EventTag(2))
     }
     assertEquals("Unknown event tag 2 for widget kind 1", t.message)
   }
diff --git a/redwood-protocol-compose/src/commonTest/kotlin/app/cash/redwood/protocol/compose/ProtocolTest.kt b/redwood-protocol-compose/src/commonTest/kotlin/app/cash/redwood/protocol/compose/ProtocolTest.kt
index e377bc0d1..ac5a94f2d 100644
--- a/redwood-protocol-compose/src/commonTest/kotlin/app/cash/redwood/protocol/compose/ProtocolTest.kt
+++ b/redwood-protocol-compose/src/commonTest/kotlin/app/cash/redwood/protocol/compose/ProtocolTest.kt
@@ -24,6 +24,7 @@ import app.cash.redwood.protocol.ChildrenDiff
 import app.cash.redwood.protocol.ChildrenDiff.Companion.RootChildrenTag
 import app.cash.redwood.protocol.Diff
 import app.cash.redwood.protocol.Event
+import app.cash.redwood.protocol.EventTag
 import app.cash.redwood.protocol.Id
 import app.cash.redwood.protocol.LayoutModifiers
 import app.cash.redwood.protocol.PropertyDiff
@@ -149,7 +150,7 @@ class ProtocolTest {
     )
 
     // Invoke the onClick lambda to move the state from 0 to 1.
-    bridge.sendEvent(Event(Id(1), 2U))
+    bridge.sendEvent(Event(Id(1), EventTag(2)))
     yield() // Allow state change to be handled.
 
     clock.awaitFrame()
@@ -163,7 +164,7 @@ class ProtocolTest {
     )
 
     // Invoke the onClick lambda to move the state from 1 to 2.
-    bridge.sendEvent(Event(Id(1), 2U))
+    bridge.sendEvent(Event(Id(1), EventTag(2)))
     yield() // Allow state change to be handled.
 
     clock.awaitFrame()
diff --git a/redwood-protocol-compose/src/commonTest/kotlin/app/cash/redwood/protocol/compose/RecordingProtocolMismatchHandler.kt b/redwood-protocol-compose/src/commonTest/kotlin/app/cash/redwood/protocol/compose/RecordingProtocolMismatchHandler.kt
index 8740c5ecc..a5eaef5ab 100644
--- a/redwood-protocol-compose/src/commonTest/kotlin/app/cash/redwood/protocol/compose/RecordingProtocolMismatchHandler.kt
+++ b/redwood-protocol-compose/src/commonTest/kotlin/app/cash/redwood/protocol/compose/RecordingProtocolMismatchHandler.kt
@@ -15,10 +15,12 @@
  */
 package app.cash.redwood.protocol.compose
 
+import app.cash.redwood.protocol.EventTag
+
 class RecordingProtocolMismatchHandler : ProtocolMismatchHandler {
   val events = mutableListOf<String>()
 
-  override fun onUnknownEvent(kind: Int, tag: UInt) {
-    events += "Unknown event $tag for $kind"
+  override fun onUnknownEvent(kind: Int, tag: EventTag) {
+    events += "Unknown event ${tag.value} for $kind"
   }
 }
diff --git a/redwood-protocol-widget/src/commonTest/kotlin/app/cash/redwood/protocol/widget/DiffConsumingWidgetFactoryTest.kt b/redwood-protocol-widget/src/commonTest/kotlin/app/cash/redwood/protocol/widget/DiffConsumingWidgetFactoryTest.kt
index 0d3ac43a0..f8ed21817 100644
--- a/redwood-protocol-widget/src/commonTest/kotlin/app/cash/redwood/protocol/widget/DiffConsumingWidgetFactoryTest.kt
+++ b/redwood-protocol-widget/src/commonTest/kotlin/app/cash/redwood/protocol/widget/DiffConsumingWidgetFactoryTest.kt
@@ -18,6 +18,7 @@ package app.cash.redwood.protocol.widget
 import app.cash.redwood.LayoutModifier
 import app.cash.redwood.protocol.Event
 import app.cash.redwood.protocol.EventSink
+import app.cash.redwood.protocol.EventTag
 import app.cash.redwood.protocol.Id
 import app.cash.redwood.protocol.PropertyDiff
 import example.redwood.compose.TestScope
@@ -279,7 +280,7 @@ class DiffConsumingWidgetFactoryTest {
 
     recordingTextInput.onChangeCustomType!!.invoke(10.seconds)
 
-    assertEquals(Event(Id(1), 4U, JsonPrimitive("PT10S")), eventSink.events.single())
+    assertEquals(Event(Id(1), EventTag(4), JsonPrimitive("PT10S")), eventSink.events.single())
   }
 
   class RecordingTextInput : TextInput<Nothing> {
diff --git a/redwood-protocol/src/commonTest/kotlin/app/cash/redwood/protocol/ProtocolTest.kt b/redwood-protocol/src/commonTest/kotlin/app/cash/redwood/protocol/ProtocolTest.kt
index d21f8ff74..ad49b15c0 100644
--- a/redwood-protocol/src/commonTest/kotlin/app/cash/redwood/protocol/ProtocolTest.kt
+++ b/redwood-protocol/src/commonTest/kotlin/app/cash/redwood/protocol/ProtocolTest.kt
@@ -30,13 +30,13 @@ class ProtocolTest {
   }
 
   @Test fun eventNonNullValue() {
-    val model = Event(Id(1), 2U, JsonPrimitive("Hello"))
+    val model = Event(Id(1), EventTag(2), JsonPrimitive("Hello"))
     val json = """{"id":1,"tag":2,"value":"Hello"}"""
     assertJsonRoundtrip(Event.serializer(), model, json)
   }
 
   @Test fun eventNullValue() {
-    val model = Event(Id(1), 2U)
+    val model = Event(Id(1), EventTag(2))
     val json = """{"id":1,"tag":2}"""
     assertJsonRoundtrip(Event.serializer(), model, json)
   }