Emit typed Kotlin method modifiers

ober

835ac801531485197d234dca7ec9147764c31130

diff --git a/lib/jerboa/typed/checker.ss b/lib/jerboa/typed/checker.ss
index e608d65..b38aece 100644
--- a/lib/jerboa/typed/checker.ss
+++ b/lib/jerboa/typed/checker.ss
@@ -846,6 +846,9 @@
       (duplicate-errors 'duplicate-param
         "duplicate parameter name"
         (map typed-param-name (typed-def-params def)))
+      (duplicate-errors 'duplicate-modifier
+        "duplicate modifier"
+        (typed-def-modifiers def))
       (effect-errors (typed-def-effects def))
       (append-map
         (lambda (param) (check-type (typed-param-type param) type-names))
diff --git a/lib/jerboa/typed/kotlin/lower.ss b/lib/jerboa/typed/kotlin/lower.ss
index b0d1262..23e8d4b 100644
--- a/lib/jerboa/typed/kotlin/lower.ss
+++ b/lib/jerboa/typed/kotlin/lower.ss
@@ -993,7 +993,7 @@
         (error 'lower-def "missing elaborated IR for typed definition" (typed-def-name def)))
       (make-kt-function
         #f
-        '()
+        (typed-def-modifiers def)
         (typed-def-name def)
         (map lower-param (typed-def-params def))
         (typed-type->kotlin-type (typed-def-return-type def))
diff --git a/lib/jerboa/typed/parser.ss b/lib/jerboa/typed/parser.ss
index b47999c..726cc42 100644
--- a/lib/jerboa/typed/parser.ss
+++ b/lib/jerboa/typed/parser.ss
@@ -72,7 +72,8 @@
 
     typed-def? make-typed-def
     typed-def-name typed-def-params typed-def-return-type
-    typed-def-effects typed-def-body typed-def-source)
+    typed-def-effects typed-def-modifiers
+    typed-def-body typed-def-source)
 
   (import (chezscheme) ; jerboa-security: suppress direct-chezscheme-import-user-code -- trusted typed compiler front-end module
           (only (jerboa core) def defstruct)
@@ -92,7 +93,7 @@
   (defstruct typed-variant (name cases source))
   (defstruct typed-variant-case (name fields source))
   (defstruct typed-param (name type source))
-  (defstruct typed-def (name params return-type effects body source))
+  (defstruct typed-def (name params return-type effects modifiers body source))
 
   (def (strip-source-annotations datum)
     (cond
@@ -406,22 +407,50 @@
         form))
     form)
 
-  (def (parse-def-effects-and-body body)
-    (if (and (pair? body) (effects-marker? (datum-value (car body))))
-      (begin
-        (unless (pair? (cdr body))
-          (error 'parse-typed-def
-            "expected an effects list after #:effects"
-            body))
-        (let ([effects (parse-effects-form
-                         (strip-source-annotations (cadr body)))]
-              [rest (cddr body)])
-          (when (null? rest)
-            (error 'parse-typed-def
-              "def with effects still needs a body"
-              body))
-          (values effects rest)))
-      (values '() body)))
+  (def (modifiers-form? form)
+    (let ([form (strip-source-annotations form)])
+      (and (pair? form)
+           (eq? (car form) 'modifiers))))
+
+  (def (parse-modifiers-form form)
+    (expect-proper-list 'parse-typed-def form)
+    (unless (symbol-list? form)
+      (error 'parse-typed-def
+        "modifiers must be symbols"
+        form))
+    (cdr form))
+
+  (def (parse-def-options-and-body body)
+    (let loop ([rest body] [effects '()] [modifiers '()] [seen-effects? #f] [seen-modifiers? #f])
+      (cond
+        [(and (pair? rest) (effects-marker? (datum-value (car rest))))
+         (when seen-effects?
+           (error 'parse-typed-def "duplicate #:effects option" body))
+         (unless (pair? (cdr rest))
+           (error 'parse-typed-def
+             "expected an effects list after #:effects"
+             body))
+         (loop (cddr rest)
+               (parse-effects-form
+                 (strip-source-annotations (cadr rest)))
+               modifiers
+               #t
+               seen-modifiers?)]
+        [(and (pair? rest) (modifiers-form? (car rest)))
+         (when seen-modifiers?
+           (error 'parse-typed-def "duplicate modifiers option" body))
+         (loop (cdr rest)
+               effects
+               (parse-modifiers-form
+                 (strip-source-annotations (car rest)))
+               seen-effects?
+               #t)]
+        [else
+         (when (null? rest)
+           (error 'parse-typed-def
+             "def with options still needs a body"
+             body))
+         (values effects modifiers rest)])))
 
   (def (parse-def form)
     (let* ([source (datum-source form)]
@@ -441,13 +470,14 @@
             "expected : or -> before return type"
             form))
         (let-values ([(name params) (parse-def-head head)])
-          (let-values ([(effects parsed-body)
-                        (parse-def-effects-and-body raw-body)])
+          (let-values ([(effects modifiers parsed-body)
+                        (parse-def-options-and-body raw-body)])
             (make-typed-def
               name
               params
               (parse-typed-type return-type)
               effects
+              modifiers
               parsed-body
               source))))))
 
diff --git a/tests/test-typed-kotlin.ss b/tests/test-typed-kotlin.ss
index 44ec153..3f8d68a 100644
--- a/tests/test-typed-kotlin.ss
+++ b/tests/test-typed-kotlin.ss
@@ -1489,6 +1489,9 @@
            (begin
              (set! field value)
              (markDirty))))
+       (def (performClick) : Bool
+         (modifiers override)
+         #t)
        (def (ready) : Bool
          (densityReady density)))))
 
@@ -1507,6 +1510,9 @@
 (test-contains "typed class declaration lowers member function"
   class-declaration-kotlin
   "fun ready(): Boolean {\n        return densityReady(density)")
+(test-contains "typed class declaration lowers method modifiers"
+  class-declaration-kotlin
+  "override fun performClick(): Boolean {\n        return true")
 
 (printf "typed-kotlin tests: ~a passed, ~a failed~%" pass fail)
 (when (> fail 0) (exit 1))