This will be Luau's last release for the year! Happy holidays and we'll see y'all in 2026.
Analysis
- Fix a bug in type checking functions where oversaturation (passing too many arguments to a function) was sometimes missed, for example:
type T = { method: (T, number) -> number }
local function onT(a: T)
a:method(5, 7) -- This would not error prior
end- Type mismatch error wording has been vastly improved: instead of talking about "X could not be converted into Y," we talk about "expecting Y" but "getting X."
- Builtin type functions now use improved logic for overload resolution.
- Type annotations on for-in loops are now respected, for example:
function my_iter(): any
return {}
end
for index: number, value: string in my_iter() do
-- This now errors, whereas prior we'd infer `index` to be of type `any`
local ohno: boolean = index
end- Function statements for table members now have their types checked, for example the following used to not error:
local Library: { isnan: (number) -> number } = {} :: any
function Library.isnan(s: string): boolean
return s == "NaN"
end- Nil-able lambdas can now participate in bidirectional inference, for example:
local listdir: (string, ((string) -> boolean)?) -> { string } = nil :: any
listdir("my_directory", function (path)
-- `path` will now have type `string` here
end)- Internal compiler errors when operating on exceptionally large types should be less common, thanks to some non-recursive implementations of common visitors.
- Fix a crash that could occur when deserializing user defined type functions, one such example being:
type function identity(t: type)
return t
end
type func<parameters...> = typeof(function(...: parameters...) end)
local whomp: <T>(arg1: T) -> identity<T>
whomp(function(...) end :: func<any>)- Fix instantiation picking unhelpful (and sometimes plain incorrect) bounds. Fixes #2125. Fixes #2118.
local foo: <P>(constructor: (P) -> any) -> (P) -> any = (nil :: any)
-- Prior `fn` would be of type `(never) -> any`, and will now be the more useful
-- ({ test: true }) -> any
local fn = foo(function (value: { test: true })
return value.test
end)- Fix a class of constraint solving incomplete errors that could occur when referencing globals inside lambdas.
types.unionofandtypes.intersectionofwill now attempt to simplify their arguments, avoiding producing nested intersections and unions, as well as removing identity types likeneverandunknown.- The
setmetatabletype function now waits for its arguments before resolving, fixing a class of constraint ordering bugs. Fixes #2106:
local MyClass = {}
local MyClassMetatable = table.freeze({ __index = MyClass })
type MyClass = setmetatable<{ name: string }, typeof(MyClassMetatable)>
function MyClass.new(name: string): MyClass
return setmetatable({ name = name }, MyClassMetatable)
end
function MyClass.hello(self: MyClass): string
return `Hello, {self.name}!`
end
local instance = MyClass.new("World")
-- This would sometimes be inferred as `any` due to constraint ordering bugs.
local g = instance:hello()Native Codegen
We now internally encode true float registers, as opposed to treating all floats as doubles (as Luau does). This allows us to skip conversions to and from a double when lowering operations on floats. Additionally, we've added several optimizations for converting in between different kinds of numbers (32-bit integers, 32-bit unsigned integers, floats, doubles, etc.).
Consequently, an important notice for users of CodeGen::HostIrHooks: we are updating the behavior of LOAD_FLOAT, DOT_VEC, EXTRACT_VEC, BUFFER_READF32, BUFFER_WRITEF32, and STORE_VECTOR to produce or consume single floating-point number instead of a double. When FFlag::LuauCodegenSplitFloat is enabled, FLOAT_TO_NUM and NUM_TO_FLOAT have to be used for explicit conversions.
- Double the amount of registers that are spilt for ints and floats on x64. This fixes NCG failing to compile snippets that end up generating many transient numbers. Fixes #1468.
- Fix a bug where NCG might improperly claim a stored value (and its tag) can be elided after a GC check.
- NCG now optimizes buffer and userdata loads that can be computed from previous stores (e.g. if you write
42to the nth byte in a buffer and then immediately read said byte). - NCG will now try to remove unused temporary userdata allocations
- Fix a bug where NCG might claim a spilt register has been restored but not actually do so, causing a garbage read from a register.
- NCG now optimizes upvalue loads and stores.
- NCG now optimizes
andandorwithout generating basic blocks, allowing more optimizations to occur. - Fix an NCG bug where, on arm64, restoring spilling registers may require using an (already occupied) temporary register.
- Runtime type checks internal to a function that are redundant with type checks added through annotations are now elided.
- NCG had an off-by-one bug where loop step detection was not kicking in for non-constant end ranges in for-in loops:
i = 1,10would be optimized andi = 1,#twould not be.
Require
- Added a
to_alias_overridecallback: this allows embedders to provide an alias that cannot be overridden by users.
Co-authored-by: Andy Friesen [email protected]
Co-authored-by: Annie Tang [email protected]
Co-authored-by: Ariel Weiss [email protected]
Co-authored-by: Hunter Goldstein [email protected]
Co-authored-by: Ilya Rezvov [email protected]
Co-authored-by: Varun Saini [email protected]
Co-authored-by: Vyacheslav Egorov [email protected]