Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions docs/number-singletons-heterogeneous-arrays.md
Original file line number Diff line number Diff line change
@@ -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
Comment thread
PhoenixWhitefire marked this conversation as resolved.
* Intervals/bounds
* A type for NaN