Known limitations (v0.18.x)

Active limitations users still hit at HEAD. Each entry explains the gap, why it exists, and the workaround. Closures across the v0.15 / v0.16 / v0.17 cycle (parametric record aliases, polymorphic re-instantiation, wildcard-any soundness, panic-class hardening, head-alias unfolding, auto-TCO for all list ops, negative literal args, multi-line signatures, zero-arg call shapes, Css keyword constants, FFI interface satisfaction, Dict typed-key inference, Sky.Live init request shape, URL-driven route Navigate Msg) are recorded in CHANGELOG.md and the per-version archives under docs/history/. This file lists ONLY what's still active at HEAD.

Language (design floor — by intent)

  1. No higher-kinded types. No Functor / Monad / Applicative classes. Sky's type system is Hindley-Milner (intentional). Use concrete types and explicit andThen / map per ADT.

  2. No where clauses. Use let…in instead. Intentional — one bindings construct, two would be a needless surface.

  3. No custom operators. Only built-in operators (|>, <|, ++, ::, arithmetic, comparison). Intentional — reading other people's code stays predictable.

  4. No row-polymorphic annotation syntax. Sky doesn't parse { r | field : T } in annotations. Use a closed record alias for the function's input. (Row-poly inference does work at the solver level; only the surface syntax is restricted.)

Compiler (defensive bounds)

  1. HM type-checker heap budget on monolithic Std.Ui-heavy modules. For very large monolithic view files (~25+ polymorphic Element Msg helpers + many nested calls) the constraint solver can grow O(N²) in heap. There is no cap. This entry used to say the compiler "defensively caps solver invocations at SKY_SOLVER_BUDGET steps (default max(5,000,000, constraint_count × 200))" and aborts with TYPE ERROR: constraint solver exceeded budget rather than OOMing the host. That fence was the retired Haskell solver's (Solve.hs:708-746); it was not carried into the Rust ty crate and the entry was never updated.

    $ grep -rn 'bump_step\|SKY_SOLVER_BUDGET' rust/crates
    $                                          # nothing
    

    The only surviving mention anywhere is a Go comment noting the knob "is read by the Haskell compiler" (runtime-go/rt/env_prefix.go:24). So: neither SKY_SOLVER_BUDGET nor SKY_SOLVER_BUDGET_FACTOR is read, there is no bump_step, and a constraint-explosion module OOMs the host — it does not abort with a diagnostic. Treat the workaround below as the only mitigation until a budget is reinstated.

    Workaround: split heavy view modules across multiple files (per examples/19-skyforum's 8-module pattern — State.sky holds types only, Update.sky / View/Common.sky / one View module per page / Main.sky dispatcher).

rt.Coerce residual surface (documented sound — not a soundness gap)

  1. rt.Coerce-family narrowing calls remain at typed boundaries (sealed-iface ctor narrowing, parametric record alias, typed list, container, primitive, tuple, map/dict, generic-param erasure). 446 call sites on the canonical examples/26-ui-showcase benchmark — 26-ui-showcase adapter=0 dispatch=0 narrow=446, rust/crates/xtask/coerce_floor.golden:73, which is the gate's own census over exactly this rt.Coerce/rt.As*/rt.Field/rt.SkyCall set.

    This entry said 214, and the next sentence said #677 "would drop ~476 of these sites" — more sites than the count it had just given. The 214 was never re-derived after the golden landed. Quote the golden, not a remembered number: grep 26-ui-showcase rust/crates/xtask/coerce_floor.golden. All sites are documented sound with explicit per-class soundness proofs in docs/history/v0.17/rt-coerce-residual-surface.md. The synchronous-panic gate (defer rt.LogPanicAndExit()) catches any panic that does fire and routes it to an Err-classified clean exit. Sealed-interface ADT emission (#677) would drop ~476 of these sites further; deferred to v0.17.x / v0.18.0 per the v0.17.0 release plan.

Sky.Live + Std.Ui (active items tracked for v0.17.x)

  1. SKY_LIVE_BASE_PATH mounted sub-apps share session-store namespace. When two Sky.Live apps mount under the same parent, they currently share the parent's session ID space (the sky_sid cookie). For multi-tenant deployments use separate cookie names per sub-app via the [live] cfg.

Stdlib

  1. Dict keys must be a primitive to survive iteration. A Dict k v is a Go map[string]V at runtime: every key is encoded to a string on the way in. Lookup (get / member / insert / remove) encodes the probe the same way and therefore works for ANY key type, but the operations that let the key back OUT — toList, keys, values, foldl, map — have to decode it. String, Int, Float, Char and Bool decode, and they do so wherever they are used, including inside a key-polymorphic helper (f : Dict k v -> …), because the encoded key carries its own type tag. One case does not:

    • Composite keys (tuple, list, record, custom type) — now a COMPILE-TIME rejection, [E2008], not a runtime failure. Their stringification is not injective — ("a b", "c") and ("a", "b c") both render {a b c} — so no decoder could be correct, and a defect that can never be fixed downstream belongs to the type checker rather than to the runtime. sky check (and the LSP, live in the editor) refuses Dict ( Int, Int ) v with the offending key type, the five that do work, and the workaround; the old UnsupportedDictKey panic is now unreachable from code that type-checks. The check resolves through type aliases (type alias Coord = ( Int, Int ) is rejected too) and fires only when the key type is CONCRETE — a key-polymorphic Dict k v is ordinary Sky and stays accepted, with a call site that instantiates k to a composite rejected at that call site. Workaround: key by a primitive rendering of the composite (e.g. String.fromInt a ++ ":" ++ String.fromInt b) and keep the structured form in the value, or hold the entries as a List ( k, v ) of pairs if you do not need lookup.

    Making that one WORK would mean changing what a composite key is encoded as, not how it is decoded — a wider change than the decode side. It is closed in the other direction instead: rejected at check time, so it can no longer reach the runtime at all.

Roadmap (not active bugs, just deferred)

What was closed in v0.17.0

(Reference for users upgrading from v0.16.x.)

Full history in CHANGELOG.md + docs/history/v0.17/. (This line named docs/archive/v0.17-design-notes/; no docs/archive/ tree exists — frozen material lives under docs/history/.)