Add typed Kotlin cast externs

ober

344119da5afd49cc97ed78676474b98d0da8fc97

diff --git a/lib/jerboa/typed/checker.ss b/lib/jerboa/typed/checker.ss
index 902e50c..ac0b14e 100644
--- a/lib/jerboa/typed/checker.ss
+++ b/lib/jerboa/typed/checker.ss
@@ -412,6 +412,8 @@
                      [(external) 'kotlin-call]
                      [(call) 'kotlin-call]
                      [(value) 'kotlin-value]
+                     [(cast) 'kotlin-cast]
+                     [(safe-cast) 'kotlin-safe-cast]
                      [(member-call) 'kotlin-member-call]
                      [(member-get) 'kotlin-member-get]
                      [(member-set) 'kotlin-member-set]
diff --git a/lib/jerboa/typed/core.ss b/lib/jerboa/typed/core.ss
index 0751ba5..dc76ca7 100644
--- a/lib/jerboa/typed/core.ss
+++ b/lib/jerboa/typed/core.ss
@@ -218,6 +218,8 @@
       debug-string
       kotlin-call
       kotlin-value
+      kotlin-cast
+      kotlin-safe-cast
       kotlin-member-call
       kotlin-member-get
       kotlin-member-set
diff --git a/lib/jerboa/typed/kotlin/ast.ss b/lib/jerboa/typed/kotlin/ast.ss
index 02baec9..60e0771 100644
--- a/lib/jerboa/typed/kotlin/ast.ss
+++ b/lib/jerboa/typed/kotlin/ast.ss
@@ -82,6 +82,9 @@
     kt-member-get? make-kt-member-get
     kt-member-get-target kt-member-get-name
 
+    kt-cast? make-kt-cast
+    kt-cast-expr kt-cast-type kt-cast-safe?
+
     kt-index-get? make-kt-index-get
     kt-index-get-target kt-index-get-index
 
@@ -165,6 +168,7 @@
   (defstruct kt-call (callee args))
   (defstruct kt-member-call (target name args))
   (defstruct kt-member-get (target name))
+  (defstruct kt-cast (expr type safe?))
   (defstruct kt-index-get (target index))
   (defstruct kt-lambda (params body))
   (defstruct kt-object-expr (super-types body))
diff --git a/lib/jerboa/typed/kotlin/lower.ss b/lib/jerboa/typed/kotlin/lower.ss
index de17965..668edb1 100644
--- a/lib/jerboa/typed/kotlin/lower.ss
+++ b/lib/jerboa/typed/kotlin/lower.ss
@@ -664,6 +664,20 @@
          (make-kt-call (make-kt-name (info-ref info 'path (list operator))) args)]
         [(kotlin-value)
          (make-kt-name (info-ref info 'path (list operator)))]
+        [(kotlin-cast)
+         (make-kt-cast
+           (car args)
+           (typed-type->kotlin-type (info-ref info 'type (typed-ir-node-type ir)))
+           #f)]
+        [(kotlin-safe-cast)
+         (make-kt-cast
+           (car args)
+           (typed-type->kotlin-type
+             (let ([target-type (info-ref info 'type (typed-ir-node-type ir))])
+               (if (and (pair? target-type) (eq? (car target-type) 'Nullable))
+                 (cadr target-type)
+                 target-type)))
+           #t)]
         [(kotlin-member-call)
          (make-kt-member-call
            (car args)
diff --git a/lib/jerboa/typed/kotlin/print.ss b/lib/jerboa/typed/kotlin/print.ss
index 55f6f90..47bfbcc 100644
--- a/lib/jerboa/typed/kotlin/print.ss
+++ b/lib/jerboa/typed/kotlin/print.ss
@@ -262,6 +262,7 @@
         (kt-try-finally? expr)
         (kt-block? expr)
         (kt-when? expr)
+        (kt-cast? expr)
         (kt-object-expr? expr)
         (kt-lambda? expr)))
 
@@ -309,6 +310,12 @@
          (kotlin-member-target->string (kt-member-get-target expr))
          "."
          (kotlin-member-name (kt-member-get-name expr)))]
+      [(kt-cast? expr)
+       (parenthesize
+         (string-append
+           (kotlin-expr->string (kt-cast-expr expr))
+           (if (kt-cast-safe? expr) " as? " " as ")
+           (kotlin-type->string (kt-cast-type expr))))]
       [(kt-index-get? expr)
        (string-append
          (kotlin-member-target->string (kt-index-get-target expr))
diff --git a/lib/jerboa/typed/parser.ss b/lib/jerboa/typed/parser.ss
index 3c9b3cd..7034512 100644
--- a/lib/jerboa/typed/parser.ss
+++ b/lib/jerboa/typed/parser.ss
@@ -661,6 +661,14 @@
          (list (cons 'kind 'value)
                (cons 'path (cdr form)))]
         [(and (= (length form) 2)
+              (eq? (car form) 'kotlin-cast))
+         (list (cons 'kind 'cast)
+               (cons 'type (parse-typed-type (cadr form))))]
+        [(and (= (length form) 2)
+              (eq? (car form) 'kotlin-safe-cast))
+         (list (cons 'kind 'safe-cast)
+               (cons 'type (parse-typed-type (cadr form))))]
+        [(and (= (length form) 2)
               (eq? (car form) 'kotlin-member-call)
               (symbol? (cadr form)))
          (list (cons 'kind 'member-call)
@@ -680,7 +688,7 @@
          (list (cons 'kind 'external))]
         [else
          (error 'parse-typed-extern
-          "expected (kotlin-call PackageOrObject function), (kotlin-value PackageOrObject property), (kotlin-member-call method), (kotlin-member-get property), (kotlin-member-set property), or (kotlin-external)"
+          "expected (kotlin-call PackageOrObject function), (kotlin-value PackageOrObject property), (kotlin-cast Type), (kotlin-safe-cast Type), (kotlin-member-call method), (kotlin-member-get property), (kotlin-member-set property), or (kotlin-external)"
           form)])))
 
   (def (parse-extern form)
diff --git a/tests/test-typed-checker.ss b/tests/test-typed-checker.ss
index fa2e877..49d96d3 100644
--- a/tests/test-typed-checker.ss
+++ b/tests/test-typed-checker.ss
@@ -652,6 +652,22 @@
          (reviewViewSelectedGroupId-set view id))))
   '())
 
+(test "extern kotlin cast forms can be used from typed code"
+  (error-kinds
+    '(typed-library (body extern-cast-ok)
+       (export checked safe)
+       (type Any)
+       (type HttpsURLConnection)
+       (extern (asHttpsURLConnection (value : Any)) : HttpsURLConnection
+         (kotlin-cast HttpsURLConnection))
+       (extern (asHttpsURLConnectionOrNull (value : Any)) : (Nullable HttpsURLConnection)
+         (kotlin-safe-cast HttpsURLConnection))
+       (def (checked (value : Any)) : HttpsURLConnection
+         (asHttpsURLConnection value))
+       (def (safe (value : Any)) : (Nullable HttpsURLConnection)
+         (asHttpsURLConnectionOrNull value))))
+  '())
+
 (test "effect annotation accepted"
   (error-kinds
     '(typed-library (effects accepted)
diff --git a/tests/test-typed-kotlin.ss b/tests/test-typed-kotlin.ss
index dc326f6..a4721ed 100644
--- a/tests/test-typed-kotlin.ss
+++ b/tests/test-typed-kotlin.ss
@@ -70,6 +70,22 @@
       (make-kt-member-call (make-kt-name '(ch)) 'isLetterOrDigit '())))
   "{ ch -> ch.isLetterOrDigit() }")
 
+(test "checked cast expression printer"
+  (kotlin-expr->string
+    (make-kt-cast
+      (make-kt-name '(connection))
+      (make-kt-type 'HttpsURLConnection #f '())
+      #f))
+  "(connection as HttpsURLConnection)")
+
+(test "safe cast expression printer"
+  (kotlin-expr->string
+    (make-kt-cast
+      (make-kt-name '(connection))
+      (make-kt-type 'HttpsURLConnection #f '())
+      #t))
+  "(connection as? HttpsURLConnection)")
+
 (test "zero-argument lambda expression printer"
   (kotlin-expr->string
     (make-kt-lambda
@@ -344,6 +360,7 @@
 (define extern-form
   '(typed-library (sample typed extern)
      (export find defaultOptions)
+     (type Any)
      (type Context)
      (type BoxType)
      (type TextRecognizerOptions)
@@ -351,10 +368,18 @@
        (kotlin-call BoxTypes resolve))
      (extern (textRecognizerDefaultOptions) : TextRecognizerOptions
        (kotlin-value TextRecognizerOptions DEFAULT_OPTIONS))
+     (extern (asRecognizer (value : Any)) : TextRecognizerOptions
+       (kotlin-cast TextRecognizerOptions))
+     (extern (maybeBoxType (value : Any)) : (Nullable BoxType)
+       (kotlin-safe-cast BoxType))
      (def (find (context : Context) (raw : String)) : (Nullable BoxType)
        (boxTypesResolve context raw))
      (def (defaultOptions) : TextRecognizerOptions
-       (textRecognizerDefaultOptions))))
+       (textRecognizerDefaultOptions))
+     (def (castRecognizer (value : Any)) : TextRecognizerOptions
+       (asRecognizer value))
+     (def (safeCastBoxType (value : Any)) : (Nullable BoxType)
+       (maybeBoxType value))))
 
 (define extern-kotlin (typed-library-form->kotlin-string extern-form))
 
@@ -366,6 +391,14 @@
   extern-kotlin
   "return TextRecognizerOptions.DEFAULT_OPTIONS")
 
+(test-contains "extern Kotlin cast lowers to checked cast"
+  extern-kotlin
+  "return (value as TextRecognizerOptions)")
+
+(test-contains "extern Kotlin safe cast lowers to nullable checked cast"
+  extern-kotlin
+  "return (value as? BoxType)")
+
 (define member-extern-form
   '(typed-library (sample typed memberextern)
      (export width copyPixels setSelectedGroup)