Status: Implemented
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 does not introduce new properties, so it is a type error if they are read. We would like to change this so that assigning a table literal to an unsealed table creates an optional property.
In lua-apps, there is testing code which (simplified) looks like:
local t = { u = {} }
t = { u = { p = 37 } }
t = { u = { q = "hi" } }
local x: number? = t.u.p
local y: string? = t.u.q
Currently, this code doesn’t typecheck, due to p
and q
being unknown properties of t.u
.
In order to support this idiom, we propose that assigning a table to an unsealed table should add an optional property.
For example, before this change the type of t
is { u: {} }
,
and after this change is { u: { p: number?, q: number? } }
.
This is implemented by adding a case to unification where the supertype is an unsealed table, and the subtype is a table with extra properties. Currently the extra properties are ignored, but with this change we would add the property to the unsealed table (making it optional if necessary).
Since tables with optional properties of the same type are subtypes of
tables with indexers, this allows table literals to be used as dictionaries,
for example the type of t
is a subtype of { u: { [string]: number } }
.
Note that we need to add an optional property, otherwise the example above will not typecheck.
local t = { u = {} }
t = { u = { p = 37 } }
t = { u = { q = "hi" } } -- fails because there's no u.p
The implementation of this proposal introduces optional types during unification, and so needs access to an allocator.
Rather than introducing optional properties, we could introduce an indexer. For example we could infer the type of
t
as { u: { [string]: number } }
.