From 54e404bca17c9784ecc3eea7df5896612d32fc8b Mon Sep 17 00:00:00 2001 From: agra Date: Thu, 28 May 2026 07:50:59 +0300 Subject: [PATCH] =?UTF-8?q?ffi=20any=5Fto=5Fstring=20optional=20dispatch?= =?UTF-8?q?=20=E2=80=94=20expected-failing=20lock-in?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `examples/178-any-to-string-optional.sx` prints a struct whose three fields are `?s64` / `?string` / `?bool`, in both Some and None form. The struct-print path goes through `field_value(s, i) -> Any` and then `any_to_string(Any)`. Today: `any_to_string` has no `case optional:` arm and `resolveTypeCategoryTags` has no "optional" category — every optional field falls through to the `` default. Expected output captures the working post-fix form (`a: 42`, `b: hi`, `c: true` for Some; `null` across the board for None). The next commit adds `optional_to_string` + `case optional:` to std and "optional" to `resolveTypeCategoryTags`. Variadic auto-unwrap (`packVariadicCallArgs`) keeps printing direct `print(opt)` calls correctly today; this fix closes the gap for struct fields, slice elements, and anywhere else an optional flows through Any. --- examples/178-any-to-string-optional.sx | 31 +++++++++++++++++++ .../expected/178-any-to-string-optional.exit | 1 + tests/expected/178-any-to-string-optional.txt | 2 ++ 3 files changed, 34 insertions(+) create mode 100644 examples/178-any-to-string-optional.sx create mode 100644 tests/expected/178-any-to-string-optional.exit create mode 100644 tests/expected/178-any-to-string-optional.txt diff --git a/examples/178-any-to-string-optional.sx b/examples/178-any-to-string-optional.sx new file mode 100644 index 0000000..3fb45c4 --- /dev/null +++ b/examples/178-any-to-string-optional.sx @@ -0,0 +1,31 @@ +// any_to_string didn't handle optionals. A struct field of type `?T` +// printed as `` (any_to_string's "no case matched" default) +// because there was no `case optional:` arm, and no dispatch table +// entry mapping `?T` TypeIds to the optional category. +// +// The variadic auto-unwrap path (packVariadicCallArgs) papered over +// this for direct `print("{}\n", opt)` calls — it stringified +// optionals to either the inner value's repr or `"null"` BEFORE +// boxing as Any. But anywhere else that boxes an optional and reads +// it back through any_to_string (struct field printing, +// `xx opt : Any`, future user code) hit the `` floor. +// +// Locks in the fix: each ?T variant routes through `case optional:` +// → `optional_to_string(cast(type) val)` → either the inner value's +// `any_to_string` representation or the literal `"null"`. + +#import "modules/std.sx"; + +S :: struct { + a: ?s64; + b: ?string; + c: ?bool; +} + +main :: () { + s1 := S.{ a = 42, b = "hi", c = true }; + print("{}\n", s1); + s2 := S.{ a = null, b = null, c = null }; + print("{}\n", s2); + 0; +} diff --git a/tests/expected/178-any-to-string-optional.exit b/tests/expected/178-any-to-string-optional.exit new file mode 100644 index 0000000..573541a --- /dev/null +++ b/tests/expected/178-any-to-string-optional.exit @@ -0,0 +1 @@ +0 diff --git a/tests/expected/178-any-to-string-optional.txt b/tests/expected/178-any-to-string-optional.txt new file mode 100644 index 0000000..af1e77f --- /dev/null +++ b/tests/expected/178-any-to-string-optional.txt @@ -0,0 +1,2 @@ +S{a: 42, b: hi, c: true} +S{a: null, b: null, c: null}