Add Kotlin generic type projections

ober

75fe436882fc90a2d8375b8f2000e6a829f77b7c

diff --git a/lib/jerboa/typed/checker.ss b/lib/jerboa/typed/checker.ss
index ac0b14e..71f7d3d 100644
--- a/lib/jerboa/typed/checker.ss
+++ b/lib/jerboa/typed/checker.ss
@@ -56,6 +56,8 @@
       (Map . 2)
       (Set . 1)
       (MutableSet . 1)
+      (Out . 1)
+      (In . 1)
       (Nullable . 1)
       (Option . 1)
       (Result . 2)
diff --git a/lib/jerboa/typed/kotlin/ast.ss b/lib/jerboa/typed/kotlin/ast.ss
index 60e0771..0d74b7f 100644
--- a/lib/jerboa/typed/kotlin/ast.ss
+++ b/lib/jerboa/typed/kotlin/ast.ss
@@ -16,6 +16,8 @@
 
     kt-type? make-kt-type
     kt-type-name kt-type-nullable? kt-type-args
+    kt-type-projection? make-kt-type-projection
+    kt-type-projection-variance kt-type-projection-type
 
     kt-param? make-kt-param
     kt-param-name kt-param-type kt-param-default
@@ -136,6 +138,7 @@
 
   ;; Types. `name` is a symbol, string, or list of package/name parts.
   (defstruct kt-type (name nullable? args))
+  (defstruct kt-type-projection (variance type))
 
   ;; Function/constructor parameters. When `property` is 'val or 'var, the
   ;; printer emits the parameter as a Kotlin primary-constructor property.
diff --git a/lib/jerboa/typed/kotlin/lower.ss b/lib/jerboa/typed/kotlin/lower.ss
index 668edb1..da2257e 100644
--- a/lib/jerboa/typed/kotlin/lower.ss
+++ b/lib/jerboa/typed/kotlin/lower.ss
@@ -108,6 +108,10 @@
        (make-kt-type 'List #f (list (typed-type->kotlin-type (cadr type))))]
       [(and (pair? type) (eq? (car type) 'Array))
        (make-kt-type 'Array #f (list (typed-type->kotlin-type (cadr type))))]
+      [(and (pair? type) (eq? (car type) 'Out))
+       (make-kt-type-projection 'out (typed-type->kotlin-type (cadr type)))]
+      [(and (pair? type) (eq? (car type) 'In))
+       (make-kt-type-projection 'in (typed-type->kotlin-type (cadr type)))]
       [(and (pair? type) (eq? (car type) 'Vector))
        (make-kt-type 'List #f (list (typed-type->kotlin-type (cadr type))))]
       [(and (pair? type) (eq? (car type) 'MutableList))
diff --git a/lib/jerboa/typed/kotlin/print.ss b/lib/jerboa/typed/kotlin/print.ss
index 47bfbcc..b96e608 100644
--- a/lib/jerboa/typed/kotlin/print.ss
+++ b/lib/jerboa/typed/kotlin/print.ss
@@ -203,6 +203,11 @@
                             (join-strings (map kotlin-type->string args) ", ")
                             ">"))
            (if (kt-type-nullable? type) "?" "")))]
+      [(kt-type-projection? type)
+       (string-append
+         (kotlin-modifier->string (kt-type-projection-variance type))
+         " "
+         (kotlin-type->string (kt-type-projection-type type)))]
       [(symbol? type) (kotlin-symbol-name type)]
       [(string? type) (sanitize-kotlin-ident type)]
       [else (error 'kotlin-type->string "not a Kotlin type" type)]))
diff --git a/tests/test-typed-kotlin.ss b/tests/test-typed-kotlin.ss
index a4721ed..1a54bf8 100644
--- a/tests/test-typed-kotlin.ss
+++ b/tests/test-typed-kotlin.ss
@@ -70,6 +70,17 @@
       (make-kt-member-call (make-kt-name '(ch)) 'isLetterOrDigit '())))
   "{ ch -> ch.isLetterOrDigit() }")
 
+(test "Kotlin type projection printer"
+  (kotlin-type->string
+    (make-kt-type
+      'Array
+      #f
+      (list
+        (make-kt-type-projection
+          'out
+          (make-kt-type 'String #f '())))))
+  "Array<out String>")
+
 (test "checked cast expression printer"
   (kotlin-expr->string
     (make-kt-cast
@@ -1650,6 +1661,15 @@
 (define param-options-kotlin
   (typed-library-form->kotlin-string param-options-form))
 
+(define type-projection-form
+  '(typed-library (sample typed projection)
+     (export use-permissions)
+     (def (use-permissions (permissions : (Array (Out String)))) : Unit
+       (begin))))
+
+(define type-projection-kotlin
+  (typed-library-form->kotlin-string type-projection-form))
+
 (test-contains "typed class declaration lowers constructor and superclass"
   class-declaration-kotlin
   "class ReviewView(context: Context) : View(context) {")
@@ -1689,6 +1709,9 @@
 (test-contains "typed function parameter lowers vararg modifier"
   param-options-kotlin
   "fun row(vararg buttons: Button): Unit")
+(test-contains "typed Out type projection lowers to Kotlin out projection"
+  type-projection-kotlin
+  "fun use_permissions(permissions: Array<out String>): Unit")
 (test-contains "typed def kotlin-name lowers emitted method name"
   kotlin-name-kotlin
   "override fun read(): Int")