luau/rfcs/unsealed-table-assign-indexer.md
Alan Jeffrey fe57e7fc92
Update rfcs/unsealed-table-assign-indexer.md
Co-authored-by: Andy Friesen <afriesen@roblox.com>
2021-09-21 16:41:47 -05:00

2 KiB

Unsealed table assignment creates an indexer

In Luau, tables have a state, which can, among others, be "unsealed". An unsealed table is one that we are still constructing. Currently assigning a table literal to an unsealed table creates new properties, but this is a problem in cases where tables are being used as hash tables. We would like to change this so that assigning a table literal to an unsealed table creates an indexer rather than properties.

Motivation

In lua-apps, there is testing code which (simplified) looks like:

local t = { u = {} }
t = { u = { p = 37 } }
t = { u = { q = 5 } }
local x: number? = t.u["p"]
local y: number? = t.u["q"]

Currently, this code doesn't typecheck, due to p and q being unknown properties of t.

In order to support this idiom, we propose that assigning a table with properties of type T to to an unsealed table with no properties should introduce a string indexer of type T.

For example, with this change the type of t is { u: { [string]: number } }.

Drawbacks

Currently table literals are sealed tables, so support width subtyping, which causes the system to be unsound, for example:

local t = { u = {} }
local u : { p: number } = { p = 37, q = "hi" }
t = { u = u }
local x: number? = t.u["p"]
local y: number? = t.u["q"] -- This is "hi" at run-time

The fix for this would be for table literals to be unsealed tables, and to only introduce an indexer when the subtype is unsealed.

This fix needs a change to lua-apps, since sometimes the code uses assignment with a table literal to add properties, and sometimes to initialize a hash table. The code currently in lua-apps looks like:

local t = { } -- Note: no u property
t = { u = { p = 37 } }
t = { u = { q = 5 } }
local x: number? = t.u["p"]
local y: number? = t.u["q"]

Alternatives

Rather than introducing an indexer, we could introduce optional properties. For example we could infer the type of t as { u: { p: number?, q: number? } }.