From 301cda2e46303c875c1aaf7778e29b05a534504a Mon Sep 17 00:00:00 2001 From: Dmitri Sotnikov Date: Fri, 26 Jun 2026 20:08:23 +0000 Subject: [PATCH] Add java.util.Optional (#241) A value class libraries return from getters (java.time, and the java.net.http client shim that aws-api's java backend builds on). Statics of/ofNullable/empty, methods isPresent/isEmpty/get/orElse/orElseGet/ifPresent/toString, value-equal so (= (Optional/of x) (Optional/of x)). Five JVM-certified corpus rows. Runtime .ss, no re-mint. Co-authored-by: Yogthos --- host/chez/java/host-static-classes.ss | 27 +++++++++++++++++++++++++++ test/chez/corpus.edn | 5 +++++ 2 files changed, 32 insertions(+) diff --git a/host/chez/java/host-static-classes.ss b/host/chez/java/host-static-classes.ss index 3790de2..46379e2 100644 --- a/host/chez/java/host-static-classes.ss +++ b/host/chez/java/host-static-classes.ss @@ -795,6 +795,33 @@ (cons "nextFloat" (lambda (self) (random 1.0))) (cons "nextBoolean" (lambda (self) (fx=? 0 (random 2)))))) +;; --- java.util.Optional ----------------------------------------------------- +;; Returned by getters across java.time / java.net.http (e.g. HttpRequest.timeout, +;; HttpClient.connectTimeout). Value-equal so (= (Optional/of x) (Optional/of x)). +(define (jt-optional present? value) (make-jhost "optional" (vector present? value))) +(define jt-optional-empty (jt-optional #f jolt-nil)) +(define (opt? x) (and (jhost? x) (string=? (jhost-tag x) "optional"))) +(define (opt-present? o) (vector-ref (jhost-state o) 0)) +(define (opt-value o) (vector-ref (jhost-state o) 1)) +(let ((statics (list (cons "of" (lambda (v) (if (jolt-nil? v) (error #f "Optional.of(null)") (jt-optional #t v)))) + (cons "ofNullable" (lambda (v) (if (jolt-nil? v) jt-optional-empty (jt-optional #t v)))) + (cons "empty" (lambda _ jt-optional-empty))))) + (register-class-statics! "Optional" statics) + (register-class-statics! "java.util.Optional" statics)) +(register-host-methods! "optional" + (list (cons "isPresent" (lambda (o) (opt-present? o))) + (cons "isEmpty" (lambda (o) (not (opt-present? o)))) + (cons "get" (lambda (o) (if (opt-present? o) (opt-value o) (error #f "Optional.get() on empty Optional")))) + (cons "orElse" (lambda (o d) (if (opt-present? o) (opt-value o) d))) + (cons "orElseGet" (lambda (o f) (if (opt-present? o) (opt-value o) (jolt-invoke f)))) + (cons "ifPresent" (lambda (o f) (when (opt-present? o) (jolt-invoke f (opt-value o))) jolt-nil)) + (cons "toString" (lambda (o) (if (opt-present? o) + (string-append "Optional[" (jolt-str-render-one (opt-value o)) "]") + "Optional.empty"))))) +(register-eq-arm! (lambda (a b) (or (opt? a) (opt? b))) + (lambda (a b) (and (opt? a) (opt? b) (eq? (opt-present? a) (opt-present? b)) + (or (not (opt-present? a)) (jolt=2 (opt-value a) (opt-value b)))))) + ;; --- minimal JVM class/interface ancestry ----------------------------------- ;; A handful of libraries reflect over the class hierarchy — e.g. core.memoize ;; validates its first argument with (some #{IFn AFn Runnable Callable} diff --git a/test/chez/corpus.edn b/test/chez/corpus.edn index 0bfe430..7e5eb0f 100644 --- a/test/chez/corpus.edn +++ b/test/chez/corpus.edn @@ -560,6 +560,11 @@ {:suite "interop / java.time chrono-fields" :label "YearMonth PROLEPTIC_MONTH" :expected "24244" :actual "(.getLong (java.time.YearMonth/of 2020 5) java.time.temporal.ChronoField/PROLEPTIC_MONTH)"} {:suite "interop / java.time chrono-fields" :label "ISO_OFFSET_DATE_TIME keeps fractional seconds" :expected "1594033153417" :actual "(.toEpochMilli (.toInstant (java.time.ZonedDateTime/parse \"2020-07-06T10:59:13.417Z\" java.time.format.DateTimeFormatter/ISO_OFFSET_DATE_TIME)))"} {:suite "interop / java.time chrono-fields" :label "ISO_OFFSET_DATE_TIME without fraction" :expected "1594033153000" :actual "(.toEpochMilli (.toInstant (java.time.ZonedDateTime/parse \"2020-07-06T10:59:13Z\" java.time.format.DateTimeFormatter/ISO_OFFSET_DATE_TIME)))"} + {:suite "interop / java.util Optional" :label "of + get" :expected "42" :actual "(.get (java.util.Optional/of 42))"} + {:suite "interop / java.util Optional" :label "empty isPresent" :expected "false" :actual "(.isPresent (java.util.Optional/empty))"} + {:suite "interop / java.util Optional" :label "value equality" :expected "true" :actual "(= (java.util.Optional/of 5) (java.util.Optional/of 5))"} + {:suite "interop / java.util Optional" :label "empty orElse" :expected "7" :actual "(.orElse (java.util.Optional/empty) 7)"} + {:suite "interop / java.util Optional" :label "ofNullable nil is empty" :expected "true" :actual "(= (java.util.Optional/empty) (java.util.Optional/ofNullable nil))"} {:suite "interop / java.time shims" :label "Instant/now is current" :expected "true" :actual "(> (.toEpochMilli (Instant/now)) 1500000000000)"} {:suite "interop / java.time shims" :label "sql types are not" :expected "false" :actual "(instance? java.sql.Timestamp #inst \"2020-01-01T00:00:00Z\")"} {:suite "interop / StringReader & StringBuilder" :label "StringReader read" :expected "[97 98 -1]" :actual "(let [r (java.io.StringReader. \"ab\")] [(.read r) (.read r) (.read r)])"}