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)
-
No higher-kinded types. No
Functor/Monad/Applicativeclasses. Sky's type system is Hindley-Milner (intentional). Use concrete types and explicitandThen/mapper ADT. -
No
whereclauses. Uselet…ininstead. Intentional — one bindings construct, two would be a needless surface. -
No custom operators. Only built-in operators (
|>,<|,++,::, arithmetic, comparison). Intentional — reading other people's code stays predictable. -
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)
-
HM type-checker heap budget on monolithic Std.Ui-heavy modules. For very large monolithic view files (~25+ polymorphic
Element Msghelpers + 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 atSKY_SOLVER_BUDGETsteps (defaultmax(5,000,000, constraint_count × 200))" and aborts withTYPE ERROR: constraint solver exceeded budgetrather than OOMing the host. That fence was the retired Haskell solver's (Solve.hs:708-746); it was not carried into the Rusttycrate and the entry was never updated.$ grep -rn 'bump_step\|SKY_SOLVER_BUDGET' rust/crates $ # nothingThe 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: neitherSKY_SOLVER_BUDGETnorSKY_SOLVER_BUDGET_FACTORis read, there is nobump_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.skyholds types only,Update.sky/View/Common.sky/ one View module per page /Main.skydispatcher).
rt.Coerce residual surface (documented sound — not a soundness gap)
-
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 canonicalexamples/26-ui-showcasebenchmark —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 thisrt.Coerce/rt.As*/rt.Field/rt.SkyCallset.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 indocs/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 anErr-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)
SKY_LIVE_BASE_PATHmounted 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 (thesky_sidcookie). For multi-tenant deployments use separate cookie names per sub-app via the[live]cfg.
Stdlib
-
Dictkeys must be a primitive to survive iteration. ADict k vis a Gomap[string]Vat 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,CharandBooldecode, 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) refusesDict ( Int, Int ) vwith the offending key type, the five that do work, and the workaround; the oldUnsupportedDictKeypanic 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-polymorphicDict k vis ordinary Sky and stays accepted, with a call site that instantiateskto 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 aList ( 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.
- Composite keys (tuple, list, record, custom type) — now a
COMPILE-TIME rejection,
Roadmap (not active bugs, just deferred)
-
Install-time Go-binding generation deferral.
sky installcurrently emits the full.skycache/go/<pkg>_bindings.go(Stripe: 76k FFI symbols, ~330k lines). A future build-time, reachable-only generation pass would drop Stripe install from ~8 min to ~10 s. -
Sub-app Sky-side API.
MountSubAppis currently Go-side (rt.MountSubAppin generatedmain.go). A Sky-sideLive.app { subApps = [...] }API is on the v0.17.x list. -
Lambda-typed OUTPUT for ALL call sites. Typed routing for
List.map/Maybe.mapetc. usesrt.List_mapT[A, any]— input typed, outputany. ForcingBto concrete would need per-call-site monomorphisation that doesn't conflict with Sky's curry shape. -
Sealed-interface ADT emission (#677). Would drop the rt.Coerce/AsListT floor by ~75 % on UI-heavy examples. Multi-session work per CLAUDE.md §0.2 N-strikes circuit-breaker (3 prior swap attempts produced regressions — requires re-classification before a 4th attempt). Deferred to v0.17.x patches or v0.18.0.
What was closed in v0.17.0
(Reference for users upgrading from v0.16.x.)
- Negative literal arguments (
f -1parses correctly asf (-1)) - Multi-line function signatures (both
: Tand-> Tcontinuation) - Zero-arg call shape arity gate (StrictHmArityGate, code
[E2007]) Css.*keyword constants are bare values (Css.zeronotCss.zero ())Dict.toListtyped-key inference works inline AND let-boundsky checkempirically validates Go interface satisfaction- Non-tail-recursive list operations now CPS / accumulator-rewritten (13/13 List/Maybe/Result list ops on constant Go stack)
- 3-tuple literals at top-level parse correctly
- Sky.Live
initreceives fullRequest(path / query / method / headers / cookies) - URL-driven route matches fire
NavigateMsg
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/.)