Add typed Kotlin string predicate helpers

ober

62a7cf90cbe4540e8b480ce08e16ef4b8c06e475

diff --git a/docs/typed-kotlin.md b/docs/typed-kotlin.md
index 8e6f652..77cde94 100644
--- a/docs/typed-kotlin.md
+++ b/docs/typed-kotlin.md
@@ -32,6 +32,8 @@ JVM-sized numeric and array helpers are explicit typed forms:
 - `(int32->string value)` converts a Kotlin `Int` interop value to `String`.
 - `(string-take text count)` lowers to Kotlin `String.take`.
 - `(string-pad-start text width char)` lowers to Kotlin `String.padStart`.
+- `(string-lowercase text)` lowers to Kotlin `String.lowercase`.
+- `(string-contains? text needle)` lowers to Kotlin `String.contains`.
 - `(float-array value ...)` produces a Kotlin `FloatArray`; each value must be
   `Float32`.
 - `(float-array-ref array index)` reads a `Float32` from a `FloatArray` at an
diff --git a/lib/jerboa/typed/checker.ss b/lib/jerboa/typed/checker.ss
index 0a163a6..8e59913 100644
--- a/lib/jerboa/typed/checker.ss
+++ b/lib/jerboa/typed/checker.ss
@@ -406,6 +406,12 @@
       (cons 'string-pad-start
             (make-typed-call-sig (list 'String 'Int32 'Char) 'String '()
               'jvm-string-pad-start '()))
+      (cons 'string-lowercase
+            (make-typed-call-sig (list 'String) 'String '()
+              'jvm-string-lowercase '()))
+      (cons 'string-contains?
+            (make-typed-call-sig (list 'String 'String) 'Bool '()
+              'jvm-string-contains '()))
       ;; crypto primitives — FFI to vetted RustCrypto crates, never reimplemented.
       ;; Each carries kind 'crypto-prim and an info `prim` the emitter dispatches
       ;; on; the Rust block they emit references the crate (sha2/hmac/hkdf) that
diff --git a/lib/jerboa/typed/kotlin/lower.ss b/lib/jerboa/typed/kotlin/lower.ss
index fb13e7e..1b88098 100644
--- a/lib/jerboa/typed/kotlin/lower.ss
+++ b/lib/jerboa/typed/kotlin/lower.ss
@@ -226,6 +226,10 @@
          (make-kt-member-call (car args) 'take (cdr args))]
         [(jvm-string-pad-start)
          (make-kt-member-call (car args) 'padStart (cdr args))]
+        [(jvm-string-lowercase)
+         (make-kt-member-call (car args) 'lowercase '())]
+        [(jvm-string-contains)
+         (make-kt-member-call (car args) 'contains (cdr args))]
         [(jvm-float-array)
          (make-kt-call (kt-name1 'floatArrayOf) args)]
         [(jvm-float-array-ref)
diff --git a/tests/test-typed-kotlin.ss b/tests/test-typed-kotlin.ss
index 96e1aa7..02d9e64 100644
--- a/tests/test-typed-kotlin.ss
+++ b/tests/test-typed-kotlin.ss
@@ -237,5 +237,25 @@
 (test-contains "String padStart lowers to Kotlin padStart" key-kotlin
   "page.toString().padStart(4, '0')")
 
+(define race-form
+  '(typed-library (sample typed race)
+     (export raceKey)
+     (def (raceKey (text : String)) : String
+       (let ((lower (string-lowercase text)))
+         (if (or (string-contains? lower "andromedan")
+                 (or (string-contains? lower "module_c3")
+                     (string-contains? lower "module c3")))
+           "andromedan"
+           (if (string-contains? lower "federation")
+             "federation"
+             ""))))))
+
+(define race-kotlin (typed-library-form->kotlin-string race-form))
+
+(test-contains "String lowercase lowers to Kotlin lowercase" race-kotlin
+  "val lower: String = text.lowercase()")
+(test-contains "String contains lowers to Kotlin contains" race-kotlin
+  "lower.contains(\"andromedan\")")
+
 (printf "typed-kotlin tests: ~a passed, ~a failed~%" pass fail)
 (when (> fail 0) (exit 1))