diff --git a/docs/number-singletons-heterogeneous-arrays.md b/docs/number-singletons-heterogeneous-arrays.md new file mode 100644 index 00000000..05a270fc --- /dev/null +++ b/docs/number-singletons-heterogeneous-arrays.md @@ -0,0 +1,115 @@ + +# Integral `number` singletons and heterogeneous arrays + +## Summary +Allow for singletons of the `number` primitive, similar to `boolean` (`true`/`false`). + +Allow for them to be used like table properties to form heterogeneous arrays. + +## Motivation +Programmers may want to use numeric constants as enumerations. Consider: + +```luau +type Deserialized = { + Items: { integer } +} + +local function convertFromV1ToV2(_: { string }): { integer } + -- ... +end + +local function deserialize(Serialized): Deserialized + local deserialized = {} + + if Serialized.Version == 1 then + deserialized.Items = convertFromV1ToV2(Serialized.Items) + elseif Serialized.Version == 2 then + deserialized.Items = Serialized.Items + else + error("unexpected version") + end + + return deserialized +end +``` + +As it stands, this code cannot be easily typechecked as the type of `Serialized` would have to be narrowed based on the numeric singleton `.Version`. +The desired type of `Serialized` would be: + +```luau +type SerializedStructure = { + Version: 1, + Items: { string } +} | { + Version: 2, + Items: { integer } +} +``` + +, however, this is currently inexpressible. `Version` would have to be left annotated as `number`, which prevents `deserialize` from typechecking +as the union of `Items` is not narrowed from `{ string } | { integer }`. + +Certain runtimes may expose platform-level APIs that use enumerations that are represented as `number`s. + +Programmers may want to annotate that a table only has a certain limited set of valid number indices. + +They may want to annotate that elements at different indices of a table have different types, opting for arrays instead of dictionaries for performance. + +## Design + +Integral numeric literals valid in the value language (`0`, `0xF2`, `0b11001`, etc) will be made valid for use in the type language, similar to literals +such as `nil` or string literals like `"hello"`. They will be inferred as either their singleton or their supertype (`number`) with the same rules as +string singletons. +Literals with a decimal component are disallowed and raise a syntax error. + +Unions of the singletons will be narrowable through refinement similar to strings: + +```luau +local x: 1 | 0b10 | 0x3 = getX() + +if x == 1 or x == 2 then + local y: 1 | 2 = x -- No type error + local z: 3 = x -- Type error +end +``` + +They may be used to define a dependent type similar to table properties: + +```luau +type T = { + [1]: string, + [2]: vector +} + +local x: T = { "hello", vector.zero } +local y = x[1] --> string +local z = x[2] --> vector + +local a: 1 | 2 = getIndex() +local w = x[a] --> string | vector +``` + +They intersect like table properties: + +```luau +type T = { + [1]: boolean +} + +type U = { + [2]: buffer +} + +type V = T & U --> { [1]: boolean, [2]: buffer } +``` + +As syntactic sugar, `{ T, U }` as a table type will be evaluated as `{ [1]: T, [2]: U }`. + +## Drawbacks +* Increases complexity of the type system + +## Alternatives +* Allow for decimals +* Singletons for `integer`s +* Intervals/bounds +* A type for NaN