Luau
luau.org.web.brid.gy
Luau
@luau.org.web.brid.gy
Luau (lowercase u, /ˈlu.aʊ/) is a fast, small, safe, gradually typed embeddable scripting language derived from Lua.

[bridged from https://luau.org/ on the web: https://fed.brid.gy/web/luau.org ]
Luau Recap: July 2024
Hello everyone! While the Luau team is actively working on a big rewrite of the type inference and type checking engines (more news about that in the near future), we wanted to go over other changes and updates since our last recap back in October. ## Official Luau mascot Luau has recently adopted a Hawaiian monk seal mascot named Hina, after the Hawaiian goddess of the moon. Please welcome Hina the Seal! ## Native Code Generation We are happy to announce that the native code feature is out from the ‘Preview’ state and is fully supported for X64 (Intel/AMD) or A64 (ARM) processor architectures. As a refresher, native code generation is the feature that allows Luau scripts that have been previously executed by interpreting bytecode inside the Luau VM to instead compile to machine code that the CPU understands and executes directly. Since the release of the Preview, we have worked on improving code performance, memory use of the system, correctness and stability. Some highlights: * Improved performance of the bit32 library functions * Improved performance of numerical loops * Optimized table array and property lookups * Added native support for new buffer type operations * Code optimizations based on knowing which types are returned from operations * Code optimizations based on function argument type annotations * This includes support for SIMD operations for annotated vector arguments There are many other small improvements in generated code performance and size and we have plans for additional optimizations. ### Native function attribute For a better control of what code runs natively, we have introduced new syntax for function attributes: @native -- function compiles natively local function foo() ... end This is the first attribute to become available and we are working on the ability to mark functions as deprecated using the `@deprecated` attribute. More on that here. ### Type information for runtime optimizations Native code generation works on any code without having to modify it. In certain situations, this means that the native compiler cannot be sure about the types involved in the operation. Consider a simple function, working on a few values: local function MulAddScaled(a, b, c) return a * b * 0.75 + c * 0.25 end Native compiler assumes that operations are most likely being performed on numbers and generates the appropriate fast path. But what if the function is actually called with a vector type? local intlPos = MulAddScaled(Part.Position, v, vector(12, 0, 0)) To handle this, a slower path was generated to handle any other potential type of the argument. Because this path is not chosen as the first possible option, extra checking overhead prevents code from running as fast as it can. When we announced the last update, we had already added some support for following the types used as arguments. local function MulAddScaled(a: vector, b: vector, c: vector) return a * b * 0.75 + c * 0.25 end > **_NOTE:_** `vector` type is not enabled by default, check out `defaultOptions` and `setupVectorHelpers` functions in `Conformance.test.cpp` file as an example of the `vector` library setup. Since then, we have extended this to support type information on locals, following complex types and even inferring results of additional operations. type Vertex = { p: vector, uv: vector, n: vector, t: vector, b: vector, h: number } type Mesh = { vertices: {Vertex}, indices: {number} } function calculate_normals(mesh: Mesh) for i = 1,#mesh.indices,3 do local a = mesh.vertices[mesh.indices[i]] local b = mesh.vertices[mesh.indices[i + 1]] local c = mesh.vertices[mesh.indices[i + 2]] local vba = a.p - b.p -- Inferred as a vector operation local vca = a.p - c.p local n = vba:Cross(vca) -- Knows that Cross returns vector a.n += n -- Inferred as a vector operation b.n += n c.n += n end end As can be seen, often it’s enough to annotate the type of the data structure and correct fast-path vector code will be generated from that without having to specify the type of each local or temporary. > **_NOTE:_** Advanced inference and operation lowering is enabled by using custom `HostIrHooks` callbacks. Check out ‘Vector’ test with ‘IrHooks’ option in `Conformance.test.cpp` and `ConformanceIrHooks.h` file for an example of the setup. Note that support for native lowering hooks allows generation of CPU code that is multiple times faster than a generic metatable call. Even when native compiler doesn’t have a specific optimization for a type, if the type can be resolved, shorter code sequences are generated and more optimizations can be made between separate operations. > **_NOTE:_** `HostIrHooks` callbacks also enable type inference and lowering for your custom userdata types. Check out ‘NativeUserdata’ test with ‘IrHooks’ option in `Conformance.test.cpp` and `ConformanceIrHooks.h` file for an example of the setup. ## Runtime changes ### Stricter `utf8` library validation `utf8` library will now correctly validate UTF-8 and reject inputs that have surrogates. `utf8.len` will return `nil` followed by the byte offset, `utf8.codepoint` and `utf8.codes` will error. This matches how other kinds of input errors were previously handled by those functions. Strings that are validated using `utf8.len` will now always work properly with `utf8.nfcnormalize` and `utf8.graphemes` functions. Custom per-character validation logic is no longer required to check if a string is valid under `utf8` requirements. ### Imprecise integer number warning Luau stores numbers as 64-bit floating-point values. Integer values up to 2^53 are supported, but higher numbers might experience rounding. For example, both 10000000000000000 and 9223372036854775808 are larger than 2^53, but match the rounding, while 10000000000000001 gets rounded down to 10000000000000000. In cases where rounding takes place, you will get a warning message. If the large value is intended and rounding can be ignored, just add “.0” to the number to remove the warning: local a = 10000000000000001 -- Number literal exceeded available precision and was truncated to closest representable number local b = 10000000000000001.0 -- Ok, but rounds to 10000000000000000 ### Leading `|` and `&` in types It is now possible to start your union and intersection types with a symbol. This can help align the type components more cleanly: type Options = | { tag: "cat", laziness: number } | { tag: "dog", happiness: number } You can find more information and examples in the proposal ## Analysis Improvements While our main focus is on a type-checking engine rewrite that is nearing completion, we have fixed some of the issues in the current one. * Relational operator errors are more conservative now and generate less false positive errors * It is not an error to iterate over table properties when indexer is not part of the type * Type packs with cycles are now correctly described in error messages * Improved error message when value that is not a function is being used in a call * Fixed stability issues which caused Studio to crash * Improved performance for code bases with large number of scripts and complex types ## Runtime Improvements When converting numbers to strings in scientific notation, we will now skip the trailing ‘.’. For example, `tostring(1e+30)` now outputs ‘1e+30’ instead of ‘1.e+30’. This improves compatibility with data formats like JSON. But please keep in mind that unless you are using JSON5, Luau can still output ‘inf’ and ‘nan’ numbers which might not be supported. * Construction of tables with 17-32 properties or 33-64 array elements is now 30% faster. * `table.concat` method is now 2x faster when the separator is not used and 40% faster otherwise. * `table.maxn` method is now 5-14x faster. * vector constants are now stored in the constant table and avoid runtime construction. * Operations like 5/x and 5-x with any constant on the left-hand-side are now performed faster, one less minor thing to think about! * It is no longer possible to crash the server on a hang in the `string` library methods. ## Luau as a supported language on GitHub Lastly, if you have open-source or even private projects on GitHub which use Luau, you might be happy to learn that Luau now has official support on GitHub for `.luau` file extension. This includes recognizing files as using Luau programming language and having support for syntax highlighting. A big thanks goes to our open source community for their generous contributions including pushing for broader Luau support: * birds3345 * bjornbytes * Gskartwii * jackdotink * JohnnyMorganz * khvzak * kostadinsh * mttsner * mxruben * petrihakkinen * zeux
luau.org
November 20, 2024 at 2:52 AM
Luau Recap: October 2023
We’re still quite busy working on some big type checking updates that we hope to talk about soon, but we have a few equally exciting updates to share in the meantime! Let’s dive in! ## Floor Division Luau now has a floor division operator. It is spelled `//`: local a = 10 // 3 -- a == 3 a //= 2 -- a == 1 For numbers, `a // b` is equivalent to `math.floor(a / b)`, and you can also overload this operator by implementing the `__idiv` metamethod. The syntax and semantics are borrowed from Lua 5.3 (although Lua 5.3 has an integer type while we don’t, we tried to match the behavior to be as close as possible). ## Native Codegen Preview We are actively working on our new native code generation module that can significantly improve the performance of compute-dense scripts by compiling them to X64 (Intel/AMD) or A64 (ARM) machine code and executing that natively. We aim to support all AArch64 hardware with the current focus being Apple Silicon (M1-M3) chips, and all Intel/AMD hardware that supports AVX1 (with no planned support for earlier systems). When the hardware does not support native code generation, any code that would be compiled as native just falls back to the interpreted execution. When working with open-source releases, binaries now have native code generation support compiled in by default; you need to pass `--codegen` command line flag to enable it. If you use Luau as a library in a third-party application, you would need to manually link `Luau.CodeGen` library and call the necessary functions to compile specific modules as needed - or keep using the interpreter if you want to! If you work in Roblox Studio, we have integrated native code generation preview as a beta feature, which currently requires manual annotation of select scripts with `--!native` comment. Our goal for the native code generation is to help reach ultimate performance for code that needs to process data very efficiently, but not necessarily to accelerate every line of code, and not to replace the interpreter. We remain committed to maximizing interpreted execution performance, as not all platforms will support native code generation, and it’s not always practical to use native code generation for large code bases because it has a larger memory impact than bytecode. We intend for this to unlock new performance opportunities for complex features and algorithms, e.g. code that spends a lot of time working with numbers and arrays, but not to dramatically change performance on UI code or code that spends a lot of its time calling Lua functions like `table.sort`, or external C functions (like Roblox engine APIs). Importantly, native code generation does not change our behavior or correctness expectations. Code compiled natively should give the same results when it executes as non-native code (just take a little less time), and it should not result in any memory safety or sandboxing issues. If you ever notice native code giving a different result from non-native code, please submit a bug report. We continue to work on many code size and performance improvements; here’s a short summary of what we’ve done in the last couple of months, and there’s more to come! * Repeated access to table fields with the same object and name are now optimized (e.g. `t.x = t.x + 5` is faster) * Numerical `for` loops are now compiled more efficiently, yielding significant speedups on hot loops * Bit operations with constants are now compiled more efficiently on X64 (for example, `bit32.lshift(x, 1)` is faster); this optimization was already in place for A64 * Repeated access to array elements with the same object and index is now faster in certain cases * Performance of function calls has been marginally improved on X64 and A64 * Fix code generation for some `bit32.extract` variants where we could produce incorrect results * `table.insert` is now faster when called with two arguments as it’s compiled directly to native code * To reduce code size, module code outside of functions is not compiled natively unless it has loops ## Analysis Improvements The `break` and `continue` keywords can now be used in loop bodies to refine variables. This was contributed by a community member - thank you, AmberGraceSoftware! function f(objects: { { value: string? } }) for _, object in objects do if not object.value then continue end local x: string = object.value -- ok! end end When type information is present, we will now emit a warning when `#` or `ipairs` is used on a table that has no numeric keys or indexers. This helps avoid common bugs like using `#t == 0` to check if a dictionary is empty. local message = { data = { 1, 2, 3 } } if #message == 0 then -- Using '#' on a table without an array part is likely a bug end Finally, some uses of `getfenv`/`setfenv` are now flagged as deprecated. We do not plan to remove support for `getfenv`/`setfenv` but we actively discourage its use as it disables many optimizations throughout the compiler, runtime, and native code generation, and interferes with type checking and linting. ## Autocomplete Improvements We used to have a bug that would arise in the following situation: --!strict type Direction = "Left" | "Right" local dir: Direction = "Left" if dir == ""| then end (imagine the cursor is at the position of the `|` character in the `if` statement) We used to suggest `Left` and `Right` even though they are not valid completions at that position. This is now fixed. We’ve also added a complete suggestion for anonymous functions if one would be valid at the requested position. For example: local p = Instance.new('Part') p.Touched:Connect( You will see a completion suggestion `function (anonymous autofilled)`. Selecting that will cause the following to be inserted into your code: local p = Instance.new('Part') p.Touched:Connect(function(otherPart: BasePart) end We also fixed some confusing editor feedback in the following case: game:FindFirstChild( Previously, the signature help tooltip would erroneously tell you that you needed to pass a `self` argument. We now correctly offer the signature `FindFirstChild(name: string, recursive: boolean?): Instance` ## Runtime Improvements * `string.format`’s handling of `%*` and `%s` is now 1.5-2x faster * `tonumber` and `tostring` are now 1.5x and 2.5x faster respectively when working on primitive types * Compiler now recognizes `math.pi` and `math.huge` and performs constant folding on the expressions that involve these at `-O2`; for example, `math.pi*2` is now free. * Compiler now optimizes `if...then...else` expressions into AND/OR form when possible (for example, `if x then x else y` now compiles as `x or y`) * We had a few bugs around `repeat..until` statements when the `until` condition referred to local variables defined in the loop body. These bugs have been fixed. * Fix an oversight that could lead to `string.char` and `string.sub` generating potentially unlimited amounts of garbage and exhausting all available memory. * We had a bug that could cause the compiler to unroll loops that it really shouldn’t. This could result in massive bytecode bloat. It is now fixed. ## luau-lang on GitHub If you’ve been paying attention to our GitHub projects, you may have noticed that we’ve moved `luau` repository to a new luau-lang GitHub organization! This is purely an organizational change but it’s helping us split a few repositories for working with documentation and RFCs and be more organized with pull requests in different areas. Make sure to update your bookmarks and star our main repository if you haven’t already! Lastly, a big thanks to our open source community for their generous contributions: * MagelessMayhem * cassanof * LoganDark * j-hui * xgqt * jdpatdiscord * Someon1e * AmberGraceSoftware * RadiantUwU * SamuraiCrow
luau.org
November 20, 2024 at 2:52 AM
Luau Recap: July 2023
Our team is still spending a lot of time working on upcoming replacement for our type inference engine as well as working on native code generation to improve runtime performance. However, we also worked on unrelated improvements during this time that are summarized here. Cross-posted to the [Roblox Developer Forum.] ## Analysis improvements Indexing table intersections using `x["prop"]` syntax has been fixed and no longer reports a false positive error: type T = { foo: string } & { bar: number } local x: T = { foo = "1", bar = 2 } local y = x["bar"] -- This is no longer an error Generic `T...` type is now convertible to `...any` variadic parameter. This solves issues people had with variadic functions and variadic argument: local function foo(...: any) print(...) end local function bar<T...>(...: T...) foo(...) -- This is no longer an error end We have also improved our general typechecking performance by ~17% and by additional ~30% in modules with complex types. Other fixes include: * Fixed issue with type `T?` not being convertible to `T | T` or `T?` which could’ve generated confusing errors * Return type of `os.date` is now inferred as `DateTypeResult` when argument is “ _t” or “!_ t” ## Runtime improvements Out-of-memory exception handling has been improved. `xpcall` handlers will now actually be called with a “not enough memory” string and whatever string/object they return will be correctly propagated. Other runtime improvements we’ve made: * Performance of `table.sort` was improved further. It now guarantees N*log(N) time complexity in the worst case * Performance of `table.concat` was improved by ~5-7% * Performance of `math.noise` was improved by ~30% * Inlining of functions is now possible even when they used to compute their own arguments * Improved logic for determining whether inlining a function or unrolling a loop is profitable ## Autocomplete improvements An issue with exported types not being suggested is now fixed. ## Debugger improvements We have fixed the search for the closest executable breakpoint line. Previously, breakpoints might have been skipped in `else` blocks at the end of a function. This simplified example shows the issue: local function foo(isIt) if isIt then print("yes") else -- When 'true' block exits the function, breakpoint couldn't be placed here print("no") end end ## Thanks A very special thanks to all of our open source contributors: * Petri Häkkinen * JohnnyMorganz * Gael * Jan * Alex Orlenko * mundusnine * Ben Mactavsin * RadiatedExodus * Lodinu Kalugalage * MagelessMayhem * Someon1e
luau.org
November 20, 2024 at 2:52 AM
Luau Recap: March 2023
How the time flies! The team has been busy since the last November Luau Recap working on some large updates that are coming in the future, but before those arrive, we have some improvements that you can already use! Cross-posted to the [Roblox Developer Forum.] ## Improved type refinements Type refinements handle constraints placed on variables inside conditional blocks. In the following example, while variable `a` is declared to have type `number?`, inside the `if` block we know that it cannot be `nil`: local function f(a: number?) if a ~= nil then a *= 2 -- no type errors end ... end One limitation we had previously is that after a conditional block, refinements were discarded. But there are cases where `if` is used to exit the function early, making the following code essentially act as a hidden `else` block. We now correctly preserve such refinements and you should be able to remove `assert` function calls that were only used to get rid of false positive errors about types being `nil`. local function f(x: string?) if not x then return end -- x is a 'string' here end Throwing calls like `error()` or `assert(false)` instead of a `return` statement are also recognized. local function f(x: string?) if not x then error('first argument is nil') end -- x is 'string' here end Existing complex refinements like `type`/`typeof`, tagged union checks and other are expected to work as expected. ## Marking table.getn/foreach/foreachi as deprecated `table.getn`, `table.foreach` and `table.foreachi` were deprecated in Lua 5.1 that Luau is based on, and removed in Lua 5.2. `table.getn(x)` is equivalent to `rawlen(x)` when ‘x’ is a table; when ‘x’ is not a table, `table.getn` produces an error. It’s difficult to imagine code where `table.getn(x)` is better than either `#x` (idiomatic) or `rawlen(x)` (fully compatible replacement). `table.getn` is also slower than both alternatives and was marked as deprecated. `table.foreach` is equivalent to a `for .. pairs` loop; `table.foreachi` is equivalent to a `for .. ipairs` loop; both may also be replaced by generalized iteration. Both functions are significantly slower than equivalent for loop replacements, are more restrictive because the function can’t yield. Because both functions bring no value over other library or language alternatives, they were marked deprecated as well. You may have noticed linter warnings about places where these functions are used. For compatibility, these functions are not going to be removed. ## Autocomplete improvements When table key type is defined to be a union of string singletons, those keys can now autocomplete in locations marked as ‘^’: type Direction = "north" | "south" | "east" | "west" local a: {[Direction]: boolean} = {[^] = true} local b: {[Direction]: boolean} = {["^"]} local b: {[Direction]: boolean} = {^} We also fixed incorrect and incomplete suggestions inside the header of `if`, `for` and `while` statements. ## Runtime improvements On the runtime side, we added multiple optimizations. `table.sort` is now ~4.1x faster (when not using a predicate) and ~2.1x faster when using a simple predicate. We also have ideas on how improve the sorting performance in the future. `math.floor`, `math.ceil` and `math.round` now use specialized processor instructions. We have measured ~7-9% speedup in math benchmarks that heavily used those functions. A small improvement was made to builtin library function calls, getting a 1-2% improvement in code that contains a lot of fastcalls. Finally, a fix was made to table array part resizing that brings large improvement to performance of large tables filled as an array, but at an offset (for example, starting at 10000 instead of 1). Aside from performance, a correctness issue was fixed in multi-assignment expressions. arr[1], n = n, n - 1 In this example, `n - 1` was assigned to `n` before `n` was assigned to `arr[1]`. This issue has now been fixed. ## Analysis improvements Multiple changes were made to improve error messages and type presentation. * Table type strings are now shown with newlines, to make them easier to read * Fixed unions of `nil` types displaying as a single `?` character * “Type pack A cannot be converted to B” error is not reported instead of a cryptic “Failed to unify type packs” * Improved error message for value count mismatch in assignments like `local a, b = 2` You may have seen error messages like `Type 'string' cannot be converted to 'string?'` even though usually it is valid to assign `local s: string? = 'hello'` because `string` is a sub-type of `string?`. This is true in what is called Covariant use contexts, but doesn’t hold in Invariant use contexts, like in the example below: local a: { x: Model } local b: { x: Instance } = a -- Type 'Model' could not be converted into 'Instance' in an invariant context In this example, while `Model` is a sub-type of `Instance` and can be used where `Instance` is required. The same is not true for a table field because when using table `b`, `b.x` can be assigned an `Instance` that is not a `Model`. When `b` is an alias to `a`, this assignment is not compatible with `a`’s type annotation. * * * Some other light changes to type inference include: * `string.match` and `string.gmatch` are now defined to return optional values as match is not guaranteed at runtime * Added an error when unrelated types are compared with `==`/`~=` * Fixed issues where variable after `typeof(x) == 'table'` could not have been used as a table ## Thanks A very special thanks to all of our open source contributors: * niansa/tuxifan * B. Gibbons * Epix * Harold Cindy * Qualadore
luau.org
November 20, 2024 at 2:52 AM
Luau Recap: November 2022
While the team is busy to bring some bigger things in the future, we have made some small improvements this month. Cross-posted to the [Roblox Developer Forum.] ## Analysis improvements We have improved tagged union type refinements to only include unhandled type cases in the `else` branch of the `if` statement: type Ok<T> = { tag: "ok", value: T } type Err = { tag: "error", msg: string } type Result<T> = Ok<T> | Err function unwrap<T>(r: Result<T>): T? if r.tag == "ok" then return r.value else -- Luau now understands that 'r' here can only be the 'Err' part print(r.msg) return nil end end For better inference, we updated the definition of `Enum.SomeType:GetEnumItems()` to return `{Enum.SomeType}` instead of common `{EnumItem}` and the return type of `next` function now includes the possibility of key being `nil`. Finally, if you use `and` operator on non-boolean values, `boolean` type will no longer be added by the type inference: local function f1(a: number?) -- 'x' is still a 'number?' and doesn't become 'boolean | number' local x = a and 5 end ## Error message improvements We now give an error when built-in types are being redefined: type string = number -- Now an error: Redefinition of type 'string' We also had a parse error missing in case you forgot your default type pack parameter value. We accepted the following code silently without raising an issue: type Foo<T... = > = nil -- Now an error: Expected type, got '>' Error about function argument count mismatch no longer points at the last argument, but instead at the function in question. So, instead of: function myfunction(a: number, b:number) end myfunction(123) ~~~ We now highlight this: function myfunction(a: number, b:number) end myfunction(123) ~~~~~~~~~~ If you iterate over a table value that could also be `nil`, you get a better explanation in the error message: local function f(t: {number}?) for i,v in t do -- Value of type {number}? could be nil --... end end Previously it was `Cannot call non-function {number}?` which was confusing. And speaking of confusing, some of you might have seen an error like `Type 'string' could not be converted into 'string'`. This was caused by Luau having both a primitive type `string` and a table type coming from `string` library. Since the way you can get the type of the `string` library table is by using `typeof(string)`, the updated error message will mirror that and report `Type 'string' could not be converted into 'typeof(string)'`. Parsing now recovers with a more precise error message if you forget a comma in table constructor spanning multiple lines: local t = { a = 1 b = 2 -- Expected ',' after table constructor element c = 3 -- Expected ',' after table constructor element }
luau.org
November 20, 2024 at 2:52 AM
Luau Recap: September &amp; October 2022
Luau is our new language that you can read more about at https://luau-lang.org. Cross-posted to the [Roblox Developer Forum.] ## Semantic subtyping One of the most important goals for Luau is to avoid _false positives_ , that is cases where Script Analysis reports a type error, but in fact the code is correct. This is very frustrating, especially for beginners. Spending time chasing down a gnarly type error only to discover that it was the type system that’s wrong is nobody’s idea of fun! We are pleased to announce that a major component of minimizing false positives has landed, _semantic subtyping_ , which removes a class of false positives caused by failures of subtyping. For example, in the program local x : CFrame = CFrame.new() local y : Vector3 | CFrame if (math.random()) then y = CFrame.new() else y = Vector3.new() end local z : Vector3 | CFrame = x * y -- Type Error! an error is reported, even though there is no problem at runtime. This is because `CFrame`’s multiplication has two overloads: ((CFrame, CFrame) -> CFrame) & ((CFrame, Vector3) -> Vector3) The current syntax-driven algorithm for subtyping is not sophisticated enough to realize that this is a subtype of the desired type: (CFrame, Vector3 | CFrame) -> (Vector3 | CFrame) Our new algorithm is driven by the semantics of subtyping, not the syntax of types, and eliminates this class of false positives. If you want to know more about semantic subtyping in Luau, check out our technical blog post on the subject. ## Other analysis improvements * Improve stringification of function types. * Improve parse error warnings in the case of missing tokens after a comma. * Improve typechecking of expressions involving variadics such as `{ ... }`. * Make sure modules don’t return unbound generic types. * Improve cycle detection in stringifying types. * Improve type inference of combinations of intersections and generic functions. * Improve typechecking when calling a function which returns a variadic e.g. `() -> (number...)`. * Improve typechecking when passing a function expression as a parameter to a function. * Improve error reporting locations. * Remove some sources of memory corruption and crashes. ## Other runtime and debugger improvements * Improve performance of accessing debug info. * Improve performance of `getmetatable` and `setmetatable`. * Remove a source of freezes in the debugger. * Improve GC accuracy and performance. ## Thanks Thanks for all the contributions! * AllanJeremy * JohnnyMorganz * jujhar16 * petrihakkinen
luau.org
November 20, 2024 at 2:52 AM
Luau Recap: July &amp; August 2022
Luau is our new language that you can read more about at https://luau-lang.org. Cross-posted to the [Roblox Developer Forum.] ## Tables now support `__len` metamethod See the RFC Support `__len` metamethod for tables and `rawlen` function for more details. With generalized iteration released in May, custom containers are easier than ever to use. The only thing missing was the fact that tables didn’t respect `__len`. Simply, tables now honor the `__len` metamethod, and `rawlen` is also added with similar semantics as `rawget` and `rawset`: local my_cool_container = setmetatable({ items = { 1, 2 } }, { __len = function(self) return #self.items end }) print(#my_cool_container) --> 2 print(rawlen(my_cool_container)) --> 0 ## `never` and `unknown` types See the RFC `never` and `unknown` types for more details. We’ve added two new types, `never` and `unknown`. These two types are the opposites of each other by the fact that there’s no value that inhabits the type `never`, and the dual of that is every value inhabits the type `unknown`. Type inference may infer a variable to have the type `never` if and only if the set of possible types becomes empty, for example through type refinements. function f(x: string | number) if typeof(x) == "string" and typeof(x) == "number" then -- x: never end end This is useful because we still needed to ascribe a type to `x` here, but the type we used previously had unsound semantics. For example, it was possible to be able to _expand_ the domain of a variable once the user had proved it impossible. With `never`, narrowing a type from `never` yields `never`. Conversely, `unknown` can be used to enforce a stronger contract than `any`. That is, `unknown` and `any` are similar in terms of allowing every type to inhabit them, and other than `unknown` or `any`, `any` allows itself to inhabit into a different type, whereas `unknown` does not. function any(): any return 5 end function unknown(): unknown return 5 end -- no type error, but assigns a number to x which expects string local x: string = any() -- has type error, unknown cannot be converted into string local y: string = unknown() To be able to do this soundly, you must apply type refinements on a variable of type `unknown`. local u = unknown() if typeof(u) == "string" then local y: string = u -- no type error end A use case of `unknown` is to enforce type safety at implementation sites for data that do not originate in code, but from over the wire. ## Argument names in type packs when instantiating a type We had a bug in the parser which erroneously allowed argument names in type packs that didn’t fold into a function type. That is, the below syntax did not generate a parse error when it should have. Foo<(a: number, b: string)> ## New IntegerParsing lint See the announcement for more details. We include this here for posterity. We’ve introduced a new lint called IntegerParsing. Right now, it lints three classes of errors: 1. Truncation of binary literals that resolves to a value over 64 bits, 2. Truncation of hexadecimal literals that resolves to a value over 64 bits, and 3. Double hexadecimal prefix. For 1.) and 2.), they are currently not planned to become a parse error, so action is not strictly required here. For 3.), this will be a breaking change! See the rollout plan for details. ## New ComparisonPrecedence lint We’ve also introduced a new lint called `ComparisonPrecedence`. It fires in two particular cases: 1. `not X op Y` where `op` is `==` or `~=`, or 2. `X op Y op Z` where `op` is any of the comparison or equality operators. In languages that uses `!` to negate the boolean i.e. `!x == y` looks fine because `!x` _visually_ binds more tightly than Lua’s equivalent, `not x`. Unfortunately, the precedences here are identical, that is `!x == y` is `(!x) == y` in the same way that `not x == y` is `(not x) == y`. We also apply this on other operators e.g. `x <= y == y`. -- not X == Y is equivalent to (not X) == Y; consider using X ~= Y, or wrap one of the expressions in parentheses to silence if not x == y then end -- not X ~= Y is equivalent to (not X) ~= Y; consider using X == Y, or wrap one of the expressions in parentheses to silence if not x ~= y then end -- not X <= Y is equivalent to (not X) <= Y; wrap one of the expressions in parentheses to silence if not x <= y then end -- X <= Y == Z is equivalent to (X <= Y) == Z; wrap one of the expressions in parentheses to silence if x <= y == 0 then end As a special exception, this lint pass will not warn for cases like `x == not y` or `not x == not y`, which both looks intentional as it is written and interpreted. ## Function calls returning singleton types incorrectly widened Fix a bug where widening was a little too happy to fire in the case of function calls returning singleton types or union thereof. This was an artifact of the logic that knows not to infer singleton types in cases that makes no sense to. function f(): "abc" | "def" return if math.random() > 0.5 then "abc" else "def" end -- previously reported that 'string' could not be converted into '"abc" | "def"' local x: "abc" | "def" = f() ## `string` can be a subtype of a table with a shape similar to `string` The function `my_cool_lower` is a function `<a...>(t: t1) -> a... where t1 = {+ lower: (t1) -> a... +}`. function my_cool_lower(t) return t:lower() end Even though `t1` is a table type, we know `string` is a subtype of `t1` because `string` also has `lower` which is a subtype of `t1`’s `lower`, so this call site now type checks. local s: string = my_cool_lower("HI") ## Other analysis improvements * `string.gmatch`/`string.match`/`string.find` may now return more precise type depending on the patterns used * Fix a bug where type arena ownership invariant could be violated, causing stability issues * Fix a bug where internal type error could be presented to the user * Fix a false positive with optionals & nested tables * Fix a false positive in non-strict mode when using generalized iteration * Improve autocomplete behavior in certain cases for `:` calls * Fix minor inconsistencies in synthesized names for types with metatables * Fix autocomplete not suggesting globals defined after the cursor * Fix DeprecatedGlobal warning text in cases when the global is deprecated without a suggested alternative * Fix an off-by-one error in type error text for incorrect use of `string.format` ## Other runtime improvements * Comparisons with constants are now significantly faster when using clang as a compiler (10-50% gains on internal benchmarks) * When calling non-existent methods on tables or strings, `foo:bar` now produces a more precise error message * Improve performance for iteration of tables * Fix a bug with negative zero in vector components when using vectors as table keys * Compiler can now constant fold builtins under -O2, for example `string.byte("A")` is compiled to a constant * Compiler can model the cost of builtins for the purpose of inlining/unrolling * Local reassignment i.e. `local x = y :: T` is free iff neither `x` nor `y` is mutated/captured * Improve `debug.traceback` performance by 1.15-1.75x depending on the platform * Fix a corner case with table assignment semantics when key didn’t exist in the table and `__newindex` was defined: we now use Lua 5.2 semantics and call `__newindex`, which results in less wasted space, support for NaN keys in `__newindex` path and correct support for frozen tables * Reduce parser C stack consumption which fixes some stack overflow crashes on deeply nested sources * Improve performance of `bit32.extract`/`replace` when width is implied (~3% faster chess) * Improve performance of `bit32.extract` when field/width are constants (~10% faster base64) * `string.format` now supports a new format specifier, `%*`, that accepts any value type and formats it using `tostring` rules ## Thanks Thanks for all the contributions! * natteko * JohnnyMorganz * khvzak * Anaminus * memery-rbx * jaykru * Kampfkarren * XmiliaH * Mactavsin
luau.org
November 20, 2024 at 2:52 AM