Jekyll2024-01-10T13:24:34+00:00https://luau-lang.org/feed.xmlLuauA fast, small, safe, gradually typed embeddable scripting language derived from Lua
Luau Recap: October 20232023-11-01T00:00:00+00:002023-11-01T00:00:00+00:00https://luau-lang.org/2023/11/01/luau-recap-october-2023<p>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!</p>
<p>Let’s dive in!</p>
<h2 id="floor-division">Floor Division</h2>
<p>Luau now has a floor division operator. It is spelled <code class="language-plaintext highlighter-rouge">//</code>:</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">local</span> <span class="n">a</span> <span class="o">=</span> <span class="mi">10</span> <span class="o">//</span> <span class="mi">3</span> <span class="c1">-- a == 3</span>
<span class="n">a</span> <span class="o">//=</span> <span class="mi">2</span> <span class="c1">-- a == 1</span>
</code></pre></div></div>
<p>For numbers, <code class="language-plaintext highlighter-rouge">a // b</code> is equivalent to <code class="language-plaintext highlighter-rouge">math.floor(a / b)</code>, and you can also overload this operator by implementing the <code class="language-plaintext highlighter-rouge">__idiv</code> 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).</p>
<h2 id="native-codegen-preview">Native Codegen Preview</h2>
<p>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.</p>
<p>When working with <a href="https://github.com/luau-lang/luau/releases">open-source releases</a>, binaries now have native code generation support compiled in by default; you need to pass <code class="language-plaintext highlighter-rouge">--codegen</code> command line flag to enable it. If you use Luau as a library in a third-party application, you would need to manually link <code class="language-plaintext highlighter-rouge">Luau.CodeGen</code> 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 <a href="https://devforum.roblox.com/t/luau-native-code-generation-preview-studio-beta/2572587">as a beta feature</a>, which currently requires manual annotation of select scripts with <code class="language-plaintext highlighter-rouge">--!native</code> comment.</p>
<p>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 <code class="language-plaintext highlighter-rouge">table.sort</code>, or external C functions (like Roblox engine APIs).</p>
<p>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.</p>
<p>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!</p>
<ul>
<li>Repeated access to table fields with the same object and name are now optimized (e.g. <code class="language-plaintext highlighter-rouge">t.x = t.x + 5</code> is faster)</li>
<li>Numerical <code class="language-plaintext highlighter-rouge">for</code> loops are now compiled more efficiently, yielding significant speedups on hot loops</li>
<li>Bit operations with constants are now compiled more efficiently on X64 (for example, <code class="language-plaintext highlighter-rouge">bit32.lshift(x, 1)</code> is faster); this optimization was already in place for A64</li>
<li>Repeated access to array elements with the same object and index is now faster in certain cases</li>
<li>Performance of function calls has been marginally improved on X64 and A64</li>
<li>Fix code generation for some <code class="language-plaintext highlighter-rouge">bit32.extract</code> variants where we could produce incorrect results</li>
<li><code class="language-plaintext highlighter-rouge">table.insert</code> is now faster when called with two arguments as it’s compiled directly to native code</li>
<li>To reduce code size, module code outside of functions is not compiled natively unless it has loops</li>
</ul>
<h2 id="analysis-improvements">Analysis Improvements</h2>
<p>The <code class="language-plaintext highlighter-rouge">break</code> and <code class="language-plaintext highlighter-rouge">continue</code> keywords can now be used in loop bodies to refine variables. This was contributed by a community member - thank you, <a href="https://github.com/AmberGraceSoftware">AmberGraceSoftware</a>!</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">function</span> <span class="nf">f</span><span class="p">(</span><span class="n">objects</span><span class="p">:</span> <span class="p">{</span> <span class="p">{</span> <span class="n">value</span><span class="p">:</span> <span class="n">string</span><span class="err">?</span> <span class="p">}</span> <span class="p">})</span>
<span class="k">for</span> <span class="n">_</span><span class="p">,</span> <span class="n">object</span> <span class="k">in</span> <span class="n">objects</span> <span class="k">do</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">object</span><span class="p">.</span><span class="n">value</span> <span class="k">then</span>
<span class="n">continue</span>
<span class="k">end</span>
<span class="kd">local</span> <span class="n">x</span><span class="p">:</span> <span class="n">string</span> <span class="o">=</span> <span class="n">object</span><span class="p">.</span><span class="n">value</span> <span class="c1">-- ok!</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>When type information is present, we will now emit a warning when <code class="language-plaintext highlighter-rouge">#</code> or <code class="language-plaintext highlighter-rouge">ipairs</code> is used on a table that has no numeric keys or indexers. This helps avoid common bugs like using <code class="language-plaintext highlighter-rouge">#t == 0</code> to check if a dictionary is empty.</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">local</span> <span class="n">message</span> <span class="o">=</span> <span class="p">{</span> <span class="n">data</span> <span class="o">=</span> <span class="p">{</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span> <span class="p">}</span> <span class="p">}</span>
<span class="k">if</span> <span class="o">#</span><span class="n">message</span> <span class="o">==</span> <span class="mi">0</span> <span class="k">then</span> <span class="c1">-- Using '#' on a table without an array part is likely a bug</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Finally, some uses of <code class="language-plaintext highlighter-rouge">getfenv</code>/<code class="language-plaintext highlighter-rouge">setfenv</code> are now flagged as deprecated. We do not plan to remove support for <code class="language-plaintext highlighter-rouge">getfenv</code>/<code class="language-plaintext highlighter-rouge">setfenv</code> 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.</p>
<h2 id="autocomplete-improvements">Autocomplete Improvements</h2>
<p>We used to have a bug that would arise in the following situation:</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">--!strict</span>
<span class="nb">type</span> <span class="n">Direction</span> <span class="o">=</span> <span class="s2">"Left"</span> <span class="err">|</span> <span class="s2">"Right"</span>
<span class="kd">local</span> <span class="n">dir</span><span class="p">:</span> <span class="n">Direction</span> <span class="o">=</span> <span class="s2">"Left"</span>
<span class="k">if</span> <span class="n">dir</span> <span class="o">==</span> <span class="s2">""</span><span class="err">|</span> <span class="k">then</span>
<span class="k">end</span>
</code></pre></div></div>
<p>(imagine the cursor is at the position of the <code class="language-plaintext highlighter-rouge">|</code> character in the <code class="language-plaintext highlighter-rouge">if</code> statement)</p>
<p>We used to suggest <code class="language-plaintext highlighter-rouge">Left</code> and <code class="language-plaintext highlighter-rouge">Right</code> even though they are not valid completions at that position. This is now fixed.</p>
<p>We’ve also added a complete suggestion for anonymous functions if one would be valid at the requested position. For example:</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">local</span> <span class="n">p</span> <span class="o">=</span> <span class="n">Instance</span><span class="p">.</span><span class="n">new</span><span class="p">(</span><span class="s1">'Part'</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">Touched</span><span class="p">:</span><span class="n">Connect</span><span class="p">(</span>
</code></pre></div></div>
<p>You will see a completion suggestion <code class="language-plaintext highlighter-rouge">function (anonymous autofilled)</code>. Selecting that will cause the following to be inserted into your code:</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">local</span> <span class="n">p</span> <span class="o">=</span> <span class="n">Instance</span><span class="p">.</span><span class="n">new</span><span class="p">(</span><span class="s1">'Part'</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">Touched</span><span class="p">:</span><span class="n">Connect</span><span class="p">(</span><span class="k">function</span><span class="p">(</span><span class="n">otherPart</span><span class="p">:</span> <span class="n">BasePart</span><span class="p">)</span> <span class="k">end</span>
</code></pre></div></div>
<p>We also fixed some confusing editor feedback in the following case:</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">game</span><span class="p">:</span><span class="n">FindFirstChild</span><span class="p">(</span>
</code></pre></div></div>
<p>Previously, the signature help tooltip would erroneously tell you that you needed to pass a <code class="language-plaintext highlighter-rouge">self</code> argument. We now correctly offer the signature <code class="language-plaintext highlighter-rouge">FindFirstChild(name: string, recursive: boolean?): Instance</code></p>
<h2 id="runtime-improvements">Runtime Improvements</h2>
<ul>
<li><code class="language-plaintext highlighter-rouge">string.format</code>’s handling of <code class="language-plaintext highlighter-rouge">%*</code> and <code class="language-plaintext highlighter-rouge">%s</code> is now 1.5-2x faster</li>
<li><code class="language-plaintext highlighter-rouge">tonumber</code> and <code class="language-plaintext highlighter-rouge">tostring</code> are now 1.5x and 2.5x faster respectively when working on primitive types</li>
<li>Compiler now recognizes <code class="language-plaintext highlighter-rouge">math.pi</code> and <code class="language-plaintext highlighter-rouge">math.huge</code> and performs constant folding on the expressions that involve these at <code class="language-plaintext highlighter-rouge">-O2</code>; for example, <code class="language-plaintext highlighter-rouge">math.pi*2</code> is now free.</li>
<li>Compiler now optimizes <code class="language-plaintext highlighter-rouge">if...then...else</code> expressions into AND/OR form when possible (for example, <code class="language-plaintext highlighter-rouge">if x then x else y</code> now compiles as <code class="language-plaintext highlighter-rouge">x or y</code>)</li>
<li>We had a few bugs around <code class="language-plaintext highlighter-rouge">repeat..until</code> statements when the <code class="language-plaintext highlighter-rouge">until</code> condition referred to local variables defined in the loop body. These bugs have been fixed.</li>
<li>Fix an oversight that could lead to <code class="language-plaintext highlighter-rouge">string.char</code> and <code class="language-plaintext highlighter-rouge">string.sub</code> generating potentially unlimited amounts of garbage and exhausting all available memory.</li>
<li>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.</li>
</ul>
<h2 id="luau-lang-on-github">luau-lang on GitHub</h2>
<p>If you’ve been paying attention to our GitHub projects, you may have noticed that we’ve moved <code class="language-plaintext highlighter-rouge">luau</code> repository to a new <a href="https://github.com/luau-lang">luau-lang GitHub organization</a>! 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 <a href="https://github.com/luau-lang/luau">star our main repository</a> if you haven’t already!</p>
<p>Lastly, a big thanks to our <a href="https://github.com/luau-lang/luau">open source community</a> for their generous contributions:</p>
<ul>
<li><a href="https://github.com/MagelessMayhem">MagelessMayhem</a></li>
<li><a href="https://github.com/cassanof">cassanof</a></li>
<li><a href="https://github.com/LoganDark">LoganDark</a></li>
<li><a href="https://github.com/j-hui">j-hui</a></li>
<li><a href="https://github.com/xgqt">xgqt</a></li>
<li><a href="https://github.com/jdpatdiscord">jdpatdiscord</a></li>
<li><a href="https://github.com/Someon1e">Someon1e</a></li>
<li><a href="https://github.com/AmberGraceSoftware">AmberGraceSoftware</a></li>
<li><a href="https://github.com/RadiantUwU">RadiantUwU</a></li>
<li><a href="https://github.com/SamuraiCrow">SamuraiCrow</a></li>
</ul>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!Luau Recap: July 20232023-07-28T00:00:00+00:002023-07-28T00:00:00+00:00https://luau-lang.org/2023/07/28/luau-recap-july-2023<p>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.</p>
<p>However, we also worked on unrelated improvements during this time that are summarized here.</p>
<p>[Cross-posted to the <a href="https://devforum.roblox.com/t/luau-recap-july-2023/">Roblox Developer Forum</a>.]</p>
<h2 id="analysis-improvements">Analysis improvements</h2>
<p>Indexing table intersections using <code class="language-plaintext highlighter-rouge">x["prop"]</code> syntax has been fixed and no longer reports a false positive error:</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">type</span> <span class="n">T</span> <span class="o">=</span> <span class="p">{</span> <span class="n">foo</span><span class="p">:</span> <span class="n">string</span> <span class="p">}</span> <span class="err">&</span> <span class="p">{</span> <span class="n">bar</span><span class="p">:</span> <span class="n">number</span> <span class="p">}</span>
<span class="kd">local</span> <span class="n">x</span><span class="p">:</span> <span class="n">T</span> <span class="o">=</span> <span class="p">{</span> <span class="n">foo</span> <span class="o">=</span> <span class="s2">"1"</span><span class="p">,</span> <span class="n">bar</span> <span class="o">=</span> <span class="mi">2</span> <span class="p">}</span>
<span class="kd">local</span> <span class="n">y</span> <span class="o">=</span> <span class="n">x</span><span class="p">[</span><span class="s2">"bar"</span><span class="p">]</span> <span class="c1">-- This is no longer an error</span>
</code></pre></div></div>
<p>Generic <code class="language-plaintext highlighter-rouge">T...</code> type is now convertible to <code class="language-plaintext highlighter-rouge">...any</code> variadic parameter.</p>
<p>This solves issues people had with variadic functions and variadic argument:</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">local</span> <span class="k">function</span> <span class="nf">foo</span><span class="p">(</span><span class="o">...</span><span class="p">:</span> <span class="n">any</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="o">...</span><span class="p">)</span>
<span class="k">end</span>
<span class="kd">local</span> <span class="k">function</span> <span class="nf">bar</span><span class="o"><</span><span class="n">T</span><span class="o">...></span><span class="p">(</span><span class="o">...</span><span class="p">:</span> <span class="n">T</span><span class="o">...</span><span class="p">)</span>
<span class="n">foo</span><span class="p">(</span><span class="o">...</span><span class="p">)</span> <span class="c1">-- This is no longer an error</span>
<span class="k">end</span>
</code></pre></div></div>
<p>We have also improved our general typechecking performance by ~17% and by additional ~30% in modules with complex types.</p>
<p>Other fixes include:</p>
<ul>
<li>Fixed issue with type <code class="language-plaintext highlighter-rouge">T?</code> not being convertible to <code class="language-plaintext highlighter-rouge">T | T</code> or <code class="language-plaintext highlighter-rouge">T?</code> which could’ve generated confusing errors</li>
<li>Return type of <code class="language-plaintext highlighter-rouge">os.date</code> is now inferred as <code class="language-plaintext highlighter-rouge">DateTypeResult</code> when argument is “<em>t” or “!</em>t”</li>
</ul>
<h2 id="runtime-improvements">Runtime improvements</h2>
<p>Out-of-memory exception handling has been improved.
<code class="language-plaintext highlighter-rouge">xpcall</code> handlers will now actually be called with a “not enough memory” string and whatever string/object they return will be correctly propagated.</p>
<p>Other runtime improvements we’ve made:</p>
<ul>
<li>Performance of <code class="language-plaintext highlighter-rouge">table.sort</code> was improved further. It now guarantees N*log(N) time complexity in the worst case</li>
<li>Performance of <code class="language-plaintext highlighter-rouge">table.concat</code> was improved by ~5-7%</li>
<li>Performance of <code class="language-plaintext highlighter-rouge">math.noise</code> was improved by ~30%</li>
<li>Inlining of functions is now possible even when they used to compute their own arguments</li>
<li>Improved logic for determining whether inlining a function or unrolling a loop is profitable</li>
</ul>
<h2 id="autocomplete-improvements">Autocomplete improvements</h2>
<p>An issue with exported types not being suggested is now fixed.</p>
<h2 id="debugger-improvements">Debugger improvements</h2>
<p>We have fixed the search for the closest executable breakpoint line.</p>
<p>Previously, breakpoints might have been skipped in <code class="language-plaintext highlighter-rouge">else</code> blocks at the end of a function.
This simplified example shows the issue:</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">local</span> <span class="k">function</span> <span class="nf">foo</span><span class="p">(</span><span class="n">isIt</span><span class="p">)</span>
<span class="k">if</span> <span class="n">isIt</span> <span class="k">then</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"yes"</span><span class="p">)</span>
<span class="k">else</span>
<span class="c1">-- When 'true' block exits the function, breakpoint couldn't be placed here</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"no"</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<h2 id="thanks">Thanks</h2>
<p>A very special thanks to all of our open source contributors:</p>
<ul>
<li><a href="https://github.com/petrihakkinen">Petri Häkkinen</a></li>
<li><a href="https://github.com/JohnnyMorganz">JohnnyMorganz</a></li>
<li><a href="https://github.com/TheGreatSageEqualToHeaven">Gael</a></li>
<li><a href="https://github.com/Jan200101">Jan</a></li>
<li><a href="https://github.com/khvzak">Alex Orlenko</a></li>
<li><a href="https://github.com/mundusnine">mundusnine</a></li>
<li><a href="https://github.com/BenMactavsin">Ben Mactavsin</a></li>
<li><a href="https://github.com/RealEthanPlayzDev">RadiatedExodus</a></li>
<li><a href="https://github.com/imlodinu">Lodinu Kalugalage</a></li>
<li><a href="https://github.com/MagelessMayhem">MagelessMayhem</a></li>
<li><a href="https://github.com/Someon1e">Someon1e</a></li>
</ul>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.Luau Recap: March 20232023-03-31T00:00:00+00:002023-03-31T00:00:00+00:00https://luau-lang.org/2023/03/31/luau-recap-march-2023<p>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!</p>
<p>[Cross-posted to the <a href="https://devforum.roblox.com/t/luau-recap-march-2023/">Roblox Developer Forum</a>.]</p>
<h2 id="improved-type-refinements">Improved type refinements</h2>
<p>Type refinements handle constraints placed on variables inside conditional blocks.</p>
<p>In the following example, while variable <code class="language-plaintext highlighter-rouge">a</code> is declared to have type <code class="language-plaintext highlighter-rouge">number?</code>, inside the <code class="language-plaintext highlighter-rouge">if</code> block we know that it cannot be <code class="language-plaintext highlighter-rouge">nil</code>:</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">local</span> <span class="k">function</span> <span class="nf">f</span><span class="p">(</span><span class="n">a</span><span class="p">:</span> <span class="n">number</span><span class="err">?</span><span class="p">)</span>
<span class="k">if</span> <span class="n">a</span> <span class="o">~=</span> <span class="kc">nil</span> <span class="k">then</span>
<span class="n">a</span> <span class="o">*=</span> <span class="mi">2</span> <span class="c1">-- no type errors</span>
<span class="k">end</span>
<span class="o">...</span>
<span class="k">end</span>
</code></pre></div></div>
<p>One limitation we had previously is that after a conditional block, refinements were discarded.</p>
<p>But there are cases where <code class="language-plaintext highlighter-rouge">if</code> is used to exit the function early, making the following code essentially act as a hidden <code class="language-plaintext highlighter-rouge">else</code> block.</p>
<p>We now correctly preserve such refinements and you should be able to remove <code class="language-plaintext highlighter-rouge">assert</code> function calls that were only used to get rid of false positive errors about types being <code class="language-plaintext highlighter-rouge">nil</code>.</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">local</span> <span class="k">function</span> <span class="nf">f</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="n">string</span><span class="err">?</span><span class="p">)</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">x</span> <span class="k">then</span> <span class="k">return</span> <span class="k">end</span>
<span class="c1">-- x is a 'string' here</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Throwing calls like <code class="language-plaintext highlighter-rouge">error()</code> or <code class="language-plaintext highlighter-rouge">assert(false)</code> instead of a <code class="language-plaintext highlighter-rouge">return</code> statement are also recognized.</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">local</span> <span class="k">function</span> <span class="nf">f</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="n">string</span><span class="err">?</span><span class="p">)</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">x</span> <span class="k">then</span> <span class="nb">error</span><span class="p">(</span><span class="s1">'first argument is nil'</span><span class="p">)</span> <span class="k">end</span>
<span class="c1">-- x is 'string' here</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Existing complex refinements like <code class="language-plaintext highlighter-rouge">type</code>/<code class="language-plaintext highlighter-rouge">typeof</code>, tagged union checks and other are expected to work as expected.</p>
<h2 id="marking-tablegetnforeachforeachi-as-deprecated">Marking table.getn/foreach/foreachi as deprecated</h2>
<p><code class="language-plaintext highlighter-rouge">table.getn</code>, <code class="language-plaintext highlighter-rouge">table.foreach</code> and <code class="language-plaintext highlighter-rouge">table.foreachi</code> were deprecated in Lua 5.1 that Luau is based on, and removed in Lua 5.2.</p>
<p><code class="language-plaintext highlighter-rouge">table.getn(x)</code> is equivalent to <code class="language-plaintext highlighter-rouge">rawlen(x)</code> when ‘x’ is a table; when ‘x’ is not a table, <code class="language-plaintext highlighter-rouge">table.getn</code> produces an error.</p>
<p>It’s difficult to imagine code where <code class="language-plaintext highlighter-rouge">table.getn(x)</code> is better than either <code class="language-plaintext highlighter-rouge">#x</code> (idiomatic) or <code class="language-plaintext highlighter-rouge">rawlen(x)</code> (fully compatible replacement).</p>
<p><code class="language-plaintext highlighter-rouge">table.getn</code> is also slower than both alternatives and was marked as deprecated.</p>
<p><code class="language-plaintext highlighter-rouge">table.foreach</code> is equivalent to a <code class="language-plaintext highlighter-rouge">for .. pairs</code> loop; <code class="language-plaintext highlighter-rouge">table.foreachi</code> is equivalent to a <code class="language-plaintext highlighter-rouge">for .. ipairs</code> loop; both may also be replaced by generalized iteration.</p>
<p>Both functions are significantly slower than equivalent for loop replacements, are more restrictive because the function can’t yield.</p>
<p>Because both functions bring no value over other library or language alternatives, they were marked deprecated as well.</p>
<p>You may have noticed linter warnings about places where these functions are used. For compatibility, these functions are not going to be removed.</p>
<h2 id="autocomplete-improvements">Autocomplete improvements</h2>
<p>When table key type is defined to be a union of string singletons, those keys can now autocomplete in locations marked as ‘^’:</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">type</span> <span class="n">Direction</span> <span class="o">=</span> <span class="s2">"north"</span> <span class="err">|</span> <span class="s2">"south"</span> <span class="err">|</span> <span class="s2">"east"</span> <span class="err">|</span> <span class="s2">"west"</span>
<span class="kd">local</span> <span class="n">a</span><span class="p">:</span> <span class="p">{[</span><span class="n">Direction</span><span class="p">]:</span> <span class="n">boolean</span><span class="p">}</span> <span class="o">=</span> <span class="p">{[</span><span class="o">^</span><span class="p">]</span> <span class="o">=</span> <span class="kc">true</span><span class="p">}</span>
<span class="kd">local</span> <span class="n">b</span><span class="p">:</span> <span class="p">{[</span><span class="n">Direction</span><span class="p">]:</span> <span class="n">boolean</span><span class="p">}</span> <span class="o">=</span> <span class="p">{[</span><span class="s2">"^"</span><span class="p">]}</span>
<span class="kd">local</span> <span class="n">b</span><span class="p">:</span> <span class="p">{[</span><span class="n">Direction</span><span class="p">]:</span> <span class="n">boolean</span><span class="p">}</span> <span class="o">=</span> <span class="p">{</span><span class="o">^</span><span class="p">}</span>
</code></pre></div></div>
<p>We also fixed incorrect and incomplete suggestions inside the header of <code class="language-plaintext highlighter-rouge">if</code>, <code class="language-plaintext highlighter-rouge">for</code> and <code class="language-plaintext highlighter-rouge">while</code> statements.</p>
<h2 id="runtime-improvements">Runtime improvements</h2>
<p>On the runtime side, we added multiple optimizations.</p>
<p><code class="language-plaintext highlighter-rouge">table.sort</code> is now ~4.1x faster (when not using a predicate) and ~2.1x faster when using a simple predicate.</p>
<p>We also have ideas on how improve the sorting performance in the future.</p>
<p><code class="language-plaintext highlighter-rouge">math.floor</code>, <code class="language-plaintext highlighter-rouge">math.ceil</code> and <code class="language-plaintext highlighter-rouge">math.round</code> now use specialized processor instructions. We have measured ~7-9% speedup in math benchmarks that heavily used those functions.</p>
<p>A small improvement was made to builtin library function calls, getting a 1-2% improvement in code that contains a lot of fastcalls.</p>
<p>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).</p>
<p>Aside from performance, a correctness issue was fixed in multi-assignment expressions.</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">arr</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">n</span> <span class="o">=</span> <span class="n">n</span><span class="p">,</span> <span class="n">n</span> <span class="o">-</span> <span class="mi">1</span>
</code></pre></div></div>
<p>In this example, <code class="language-plaintext highlighter-rouge">n - 1</code> was assigned to <code class="language-plaintext highlighter-rouge">n</code> before <code class="language-plaintext highlighter-rouge">n</code> was assigned to <code class="language-plaintext highlighter-rouge">arr[1]</code>. This issue has now been fixed.</p>
<h2 id="analysis-improvements">Analysis improvements</h2>
<p>Multiple changes were made to improve error messages and type presentation.</p>
<ul>
<li>Table type strings are now shown with newlines, to make them easier to read</li>
<li>Fixed unions of <code class="language-plaintext highlighter-rouge">nil</code> types displaying as a single <code class="language-plaintext highlighter-rouge">?</code> character</li>
<li>“Type pack A cannot be converted to B” error is not reported instead of a cryptic “Failed to unify type packs”</li>
<li>Improved error message for value count mismatch in assignments like <code class="language-plaintext highlighter-rouge">local a, b = 2</code></li>
</ul>
<p>You may have seen error messages like <code class="language-plaintext highlighter-rouge">Type 'string' cannot be converted to 'string?'</code> even though usually it is valid to assign <code class="language-plaintext highlighter-rouge">local s: string? = 'hello'</code> because <code class="language-plaintext highlighter-rouge">string</code> is a sub-type of <code class="language-plaintext highlighter-rouge">string?</code>.</p>
<p>This is true in what is called Covariant use contexts, but doesn’t hold in Invariant use contexts, like in the example below:</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">local</span> <span class="n">a</span><span class="p">:</span> <span class="p">{</span> <span class="n">x</span><span class="p">:</span> <span class="n">Model</span> <span class="p">}</span>
<span class="kd">local</span> <span class="n">b</span><span class="p">:</span> <span class="p">{</span> <span class="n">x</span><span class="p">:</span> <span class="n">Instance</span> <span class="p">}</span> <span class="o">=</span> <span class="n">a</span> <span class="c1">-- Type 'Model' could not be converted into 'Instance' in an invariant context</span>
</code></pre></div></div>
<p>In this example, while <code class="language-plaintext highlighter-rouge">Model</code> is a sub-type of <code class="language-plaintext highlighter-rouge">Instance</code> and can be used where <code class="language-plaintext highlighter-rouge">Instance</code> is required.</p>
<p>The same is not true for a table field because when using table <code class="language-plaintext highlighter-rouge">b</code>, <code class="language-plaintext highlighter-rouge">b.x</code> can be assigned an <code class="language-plaintext highlighter-rouge">Instance</code> that is not a <code class="language-plaintext highlighter-rouge">Model</code>. When <code class="language-plaintext highlighter-rouge">b</code> is an alias to <code class="language-plaintext highlighter-rouge">a</code>, this assignment is not compatible with <code class="language-plaintext highlighter-rouge">a</code>’s type annotation.</p>
<hr />
<p>Some other light changes to type inference include:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">string.match</code> and <code class="language-plaintext highlighter-rouge">string.gmatch</code> are now defined to return optional values as match is not guaranteed at runtime</li>
<li>Added an error when unrelated types are compared with <code class="language-plaintext highlighter-rouge">==</code>/<code class="language-plaintext highlighter-rouge">~=</code></li>
<li>Fixed issues where variable after <code class="language-plaintext highlighter-rouge">typeof(x) == 'table'</code> could not have been used as a table</li>
</ul>
<h2 id="thanks">Thanks</h2>
<p>A very special thanks to all of our open source contributors:</p>
<ul>
<li><a href="https://github.com/niansa">niansa/tuxifan</a></li>
<li><a href="https://github.com/bmg817">B. Gibbons</a></li>
<li><a href="https://github.com/EpixScripts">Epix</a></li>
<li><a href="https://github.com/HaroldCindy">Harold Cindy</a></li>
<li><a href="https://github.com/Qualadore">Qualadore</a></li>
</ul>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!String Interpolation2023-02-02T00:00:00+00:002023-02-02T00:00:00+00:00https://luau-lang.org/2023/02/02/luau-string-interpolation<p>String interpolation is the new syntax introduced to Luau that allows you to create a string literal with expressions inside of that string literal.</p>
<p>In short, it’s a safer and more ergonomic alternative over <code class="language-plaintext highlighter-rouge">string.format</code>.</p>
<p>Here’s a quick example of a string interpolation:</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">local</span> <span class="n">combos</span> <span class="o">=</span> <span class="p">{</span><span class="mi">2</span><span class="p">,</span> <span class="mi">7</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">5</span><span class="p">}</span>
<span class="nb">print</span><span class="p">(</span><span class="err">`</span><span class="n">The</span> <span class="n">lock</span> <span class="n">combination</span> <span class="n">is</span> <span class="p">{</span><span class="nb">table.concat</span><span class="p">(</span><span class="n">combos</span><span class="p">)}.</span> <span class="n">Again</span><span class="p">,</span> <span class="p">{</span><span class="nb">table.concat</span><span class="p">(</span><span class="n">combos</span><span class="p">,</span> <span class="s2">", "</span><span class="p">)}.</span><span class="err">`</span><span class="p">)</span>
<span class="c1">--> The lock combination is 27185. Again, 2, 7, 1, 8, 5.</span>
</code></pre></div></div>
<p>String interpolation also composes well with the <code class="language-plaintext highlighter-rouge">__tostring</code> metamethod.</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">local</span> <span class="n">balance</span> <span class="o">=</span> <span class="nb">setmetatable</span><span class="p">({</span> <span class="n">value</span> <span class="o">=</span> <span class="mi">500</span> <span class="p">},</span> <span class="p">{</span>
<span class="n">__tostring</span> <span class="o">=</span> <span class="k">function</span><span class="p">(</span><span class="n">self</span><span class="p">)</span>
<span class="k">return</span> <span class="s2">"$"</span> <span class="o">..</span> <span class="nb">tostring</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">value</span><span class="p">)</span>
<span class="k">end</span>
<span class="p">})</span>
<span class="nb">print</span><span class="p">(</span><span class="err">`</span><span class="n">You</span> <span class="n">have</span> <span class="p">{</span><span class="n">balance</span><span class="p">}</span><span class="err">!`</span><span class="p">)</span>
<span class="c1">--> You have $500!</span>
</code></pre></div></div>
<p>To find out more details about this feature, check out <a href="/syntax#string-interpolation">Luau Syntax page</a>.</p>
<p>This is also the first major language feature implemented in a <a href="https://github.com/Roblox/luau/pull/614">contribution</a> from the open-source community. Thanks <a href="https://github.com/Kampfkarren">Kampfkarren</a>!</p>String interpolation is the new syntax introduced to Luau that allows you to create a string literal with expressions inside of that string literal.Luau Recap: November 20222022-11-30T00:00:00+00:002022-11-30T00:00:00+00:00https://luau-lang.org/2022/11/30/luau-recap-november-2022<p>While the team is busy to bring some bigger things in the future, we have made some small improvements this month.</p>
<p>[Cross-posted to the <a href="https://devforum.roblox.com/t/luau-recap-november-2022/">Roblox Developer Forum</a>.]</p>
<h2 id="analysis-improvements">Analysis improvements</h2>
<p>We have improved tagged union type refinements to only include unhandled type cases in the <code class="language-plaintext highlighter-rouge">else</code> branch of the <code class="language-plaintext highlighter-rouge">if</code> statement:</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">type</span> <span class="n">Ok</span><span class="o"><</span><span class="n">T</span><span class="o">></span> <span class="o">=</span> <span class="p">{</span> <span class="n">tag</span><span class="p">:</span> <span class="s2">"ok"</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">T</span> <span class="p">}</span>
<span class="nb">type</span> <span class="n">Err</span> <span class="o">=</span> <span class="p">{</span> <span class="n">tag</span><span class="p">:</span> <span class="s2">"error"</span><span class="p">,</span> <span class="n">msg</span><span class="p">:</span> <span class="n">string</span> <span class="p">}</span>
<span class="nb">type</span> <span class="n">Result</span><span class="o"><</span><span class="n">T</span><span class="o">></span> <span class="o">=</span> <span class="n">Ok</span><span class="o"><</span><span class="n">T</span><span class="o">></span> <span class="err">|</span> <span class="n">Err</span>
<span class="k">function</span> <span class="nf">unwrap</span><span class="o"><</span><span class="n">T</span><span class="o">></span><span class="p">(</span><span class="n">r</span><span class="p">:</span> <span class="n">Result</span><span class="o"><</span><span class="n">T</span><span class="o">></span><span class="p">):</span> <span class="n">T</span><span class="err">?</span>
<span class="k">if</span> <span class="n">r</span><span class="p">.</span><span class="n">tag</span> <span class="o">==</span> <span class="s2">"ok"</span> <span class="k">then</span>
<span class="k">return</span> <span class="n">r</span><span class="p">.</span><span class="n">value</span>
<span class="k">else</span>
<span class="c1">-- Luau now understands that 'r' here can only be the 'Err' part</span>
<span class="nb">print</span><span class="p">(</span><span class="n">r</span><span class="p">.</span><span class="n">msg</span><span class="p">)</span>
<span class="k">return</span> <span class="kc">nil</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>For better inference, we updated the definition of <code class="language-plaintext highlighter-rouge">Enum.SomeType:GetEnumItems()</code> to return <code class="language-plaintext highlighter-rouge">{Enum.SomeType}</code> instead of common <code class="language-plaintext highlighter-rouge">{EnumItem}</code> and the return type of <code class="language-plaintext highlighter-rouge">next</code> function now includes the possibility of key being <code class="language-plaintext highlighter-rouge">nil</code>.</p>
<p>Finally, if you use <code class="language-plaintext highlighter-rouge">and</code> operator on non-boolean values, <code class="language-plaintext highlighter-rouge">boolean</code> type will no longer be added by the type inference:</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">local</span> <span class="k">function</span> <span class="nf">f1</span><span class="p">(</span><span class="n">a</span><span class="p">:</span> <span class="n">number</span><span class="err">?</span><span class="p">)</span>
<span class="c1">-- 'x' is still a 'number?' and doesn't become 'boolean | number'</span>
<span class="kd">local</span> <span class="n">x</span> <span class="o">=</span> <span class="n">a</span> <span class="ow">and</span> <span class="mi">5</span>
<span class="k">end</span>
</code></pre></div></div>
<h2 id="error-message-improvements">Error message improvements</h2>
<p>We now give an error when built-in types are being redefined:</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">type</span> <span class="n">string</span> <span class="o">=</span> <span class="n">number</span> <span class="c1">-- Now an error: Redefinition of type 'string'</span>
</code></pre></div></div>
<p>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:</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">type</span> <span class="n">Foo</span><span class="o"><</span><span class="n">T</span><span class="o">...</span> <span class="o">=</span> <span class="o">></span> <span class="o">=</span> <span class="kc">nil</span> <span class="c1">-- Now an error: Expected type, got '>'</span>
</code></pre></div></div>
<p>Error about function argument count mismatch no longer points at the last argument, but instead at the function in question.
So, instead of:</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">function</span> <span class="nf">myfunction</span><span class="p">(</span><span class="n">a</span><span class="p">:</span> <span class="n">number</span><span class="p">,</span> <span class="n">b</span><span class="p">:</span><span class="n">number</span><span class="p">)</span> <span class="k">end</span>
<span class="n">myfunction</span><span class="p">(</span><span class="mi">123</span><span class="p">)</span>
<span class="err">~~~</span>
</code></pre></div></div>
<p>We now highlight this:</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">function</span> <span class="nf">myfunction</span><span class="p">(</span><span class="n">a</span><span class="p">:</span> <span class="n">number</span><span class="p">,</span> <span class="n">b</span><span class="p">:</span><span class="n">number</span><span class="p">)</span> <span class="k">end</span>
<span class="n">myfunction</span><span class="p">(</span><span class="mi">123</span><span class="p">)</span>
<span class="err">~~~~~~~~~~</span>
</code></pre></div></div>
<p>If you iterate over a table value that could also be <code class="language-plaintext highlighter-rouge">nil</code>, you get a better explanation in the error message:</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">local</span> <span class="k">function</span> <span class="nf">f</span><span class="p">(</span><span class="n">t</span><span class="p">:</span> <span class="p">{</span><span class="n">number</span><span class="p">}</span><span class="err">?</span><span class="p">)</span>
<span class="k">for</span> <span class="n">i</span><span class="p">,</span><span class="n">v</span> <span class="k">in</span> <span class="n">t</span> <span class="k">do</span> <span class="c1">-- Value of type {number}? could be nil</span>
<span class="c1">--...</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Previously it was <code class="language-plaintext highlighter-rouge">Cannot call non-function {number}?</code> which was confusing.</p>
<p>And speaking of confusing, some of you might have seen an error like <code class="language-plaintext highlighter-rouge">Type 'string' could not be converted into 'string'</code>.</p>
<p>This was caused by Luau having both a primitive type <code class="language-plaintext highlighter-rouge">string</code> and a table type coming from <code class="language-plaintext highlighter-rouge">string</code> library. Since the way you can get the type of the <code class="language-plaintext highlighter-rouge">string</code> library table is by using <code class="language-plaintext highlighter-rouge">typeof(string)</code>, the updated error message will mirror that and report <code class="language-plaintext highlighter-rouge">Type 'string' could not be converted into 'typeof(string)'</code>.</p>
<p>Parsing now recovers with a more precise error message if you forget a comma in table constructor spanning multiple lines:</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">local</span> <span class="n">t</span> <span class="o">=</span> <span class="p">{</span>
<span class="n">a</span> <span class="o">=</span> <span class="mi">1</span>
<span class="n">b</span> <span class="o">=</span> <span class="mi">2</span> <span class="c1">-- Expected ',' after table constructor element</span>
<span class="n">c</span> <span class="o">=</span> <span class="mi">3</span> <span class="c1">-- Expected ',' after table constructor element</span>
<span class="p">}</span>
</code></pre></div></div>While the team is busy to bring some bigger things in the future, we have made some small improvements this month.Luau origins and evolution2022-11-04T00:00:00+00:002022-11-04T00:00:00+00:00https://luau-lang.org/2022/11/04/luau-origins-and-evolution<p>At the heart of Roblox technology lies <a href="https://luau-lang.org">Luau</a>, a scripting language derived from Lua 5.1 that is being <a href="https://github.com/Roblox/luau">developed</a> by an internal team of programming language experts with the help of open source contributors.</p>
<p>It powers all user-generated content on Roblox, providing access to a very rich set of APIs that allows manipulation of objects in the 3D world, backend API access, UI interaction and more. Hundreds of thousands of developers write code in Luau every month, with top experiences using hundreds of thousands of lines of code, adding up to hundreds of millions of lines of code across the platform. For many of them, it is the first programming language they learn, and one they spend the majority of their time programming in. Using a set of extended APIs developers also customize their workflows by writing plugins to Roblox Studio, where they work on their experiences, using an extended API surface to interact with all aspects of the editor.</p>
<p>It also powers a lot of application code that Roblox engineers are writing: Universal App, the gateway to the worlds of Roblox that is used by tens of millions of people every day, has 95% of its functionality implemented in Luau, and Roblox Studio has a lot of builtin critical functionality such as part and terrain editors, marketplace browser, avatar and animation editors, material manager and more, implemented in Luau as a plugin, mostly using the same APIs that developers have access to. Every week, updates to this internal codebase that is now over 2 million lines large, are shipped to all Roblox users.</p>
<p>In addition to Roblox use cases, Luau is also open-source and is seeing an increased adoption in other projects and applications.</p>
<p>But why did we use Lua in the first place, and why did we decide to pursue building a new language on top of it?</p>
<h1 id="early-beginnings">Early beginnings</h1>
<p>Around 2006, when a very early version of the Roblox platform was developed, the question of user generated behaviors emerged. Before that, users were able to build non-interactive content on Roblox, and the only form of interaction was physics simulation. While this provided rich emergent behavior, it was hard to build gameplay on top of this: for example, to build a Capture The Flag game, you need to handle collision between players and flags spread throughout the map with a bit of logic that dictates how to adjust team points and when to remove or recreate the objects.</p>
<p>After an early and brief misstep when we decided to add a few gameplay objects to the core definition of Roblox worlds (some developers may recognize FlagStand as a class name…), the Roblox co-founder Erik Cassel realized that an approach like this is fundamentally limiting the power of user generated content. It’s not enough to give creators the basic blocks on top of which to build their creations, it’s critical to expose the power of a full Turing-complete programming language. Without this, the expressive capability and the reach of the platform would have been restricted far too much.</p>
<p>But which programming language to choose? This is where <a href="https://lua.org/">Lua</a>, which was, and still is, one of the dominant programming languages used in video games, comes in.</p>
<p>In addition to its simplicity, which made the language easy to learn and get productive in, Lua was the fastest scripting language compared to popular alternatives like Python or JavaScript at the time<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>, designed to be embedded which meant an easy ability to expose APIs from the host application to the scripts as well as high degree of execution control from the host, and implemented coroutines, a very powerful concurrency primitive that allowed to easily and intuitively script behaviors for independent actors in game using linear control flow.</p>
<p>Instead of having a large standard library, the expectation was that the embedding application would define a set of APIs that that application needed, as well as establish policies of running the code - which gave us a lot of freedom in how to structure the APIs and when the scripts would get triggered during the simulation of a single frame.</p>
<h1 id="power-of-simplicity">Power of simplicity</h1>
<p>Lua is a simple language. What does simplicity mean for us?</p>
<p>Being a simple language means having a small set of features. Lua has all the fundamental features but doesn’t have a lot of syntax sugar - this means the language is easier to teach and learn, and you rarely run into code that’s difficult to understand syntactically because it uses an unfamiliar construct. Of course, this also means that some programs in Lua are longer than equivalent programs in languages that have more dedicated constructs to solve specific problems, such as list comprehensions in Python.</p>
<p>Being a simple language means having a minimal set of rules for every feature. Lua does deviate from this in certain respects (which is to say, the language could have been even simpler!), but notably for a dynamic language the behavior of fundamental operators is generally easy to explain and unsurprising - for example, two values in Lua are equal iff they have the same type and the same value, as such <code class="language-plaintext highlighter-rouge">0 == “0”</code> is <code class="language-plaintext highlighter-rouge">false</code>; as another example, <code class="language-plaintext highlighter-rouge">for</code> loops introduce unique variable bindings on every iteration, as such capturing the iteration variable in a closure produces unique values. These decisions lead to more concise and efficient implementation and eliminate a class of bugs in programs.</p>
<p>Being a simple language means having a small implementation. This may be immaterial to people writing code in the language, but it leads to an implementation that can be of higher quality; simpler implementations can also be easier to optimize for memory or performance, and are easier to build upon.</p>
<p>Developers on the Roblox platform have very diverse programming backgrounds. Some are writing their first line of code in Roblox Studio, while others have computer science degrees and experience working in multiple different programming languages. While it’s always possible to support two different programming languages that target different segments of the audience, that fragments the ecosystem and makes the programming story less consistent (impacting documentation, tutorials, code reuse, ability for community members to help each other write code, presents challenges with interaction between different languages in the same experience and more). A better outcome is one where a single language can serve both audiences - this requires a language that strikes a balance between simplicity and generality, and while Lua isn’t perfect here, it’s great as a foundation for a language like this<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup>.</p>
<p>In many ways, Lua is simultaneously simple and pragmatic: many parts of the language are difficult to make much better without a lot of added complexity, but at the same time it requires little in the way of extra functionality to be able to solve problems efficiently. That said, no language is perfect, and within several areas of Lua we felt that the tradeoffs weren’t quite right for our use case.</p>
<h1 id="respectful-evolution">Respectful evolution</h1>
<p>In 2019, we decided to build <a href="https://luau-lang.org">Luau</a> - a language derived from Lua and compatible with Lua 5.1, which is the version we’ve been using all these years. At the time we evaluated other routes, but ultimately settled on this as the most optimal long-term.</p>
<p>On one hand, we loved a lot of things about Lua - both design wise and implementation wise, while there were some decisions we felt were suboptimal, by and large it was an almost perfect foundation for what we’ve set out to achieve.</p>
<p>On the other hand, we’ve been running into the limitations of Lua on large code bases in absence of type checking, performance was good but not great, and some missing features would have been great to have.</p>
<p>Some of the things we’ve been missing have been added in later versions of Lua, yet we were still using Lua 5.1. While we would have loved to use a later version of the language standard, Lua 5.x releases are not backwards compatible, and some releases remove support for features that are in wide use at Roblox. For Roblox, backwards compatibility is an essential feature of the platform - while we don’t have a guarantee that content created 10 years ago still works, to the extent that we can achieve that without restricting the platform evolution too much, we try.</p>
<p>What we’ve realized is that Lua is a great foundation for a perfect language that we can build for Roblox.</p>
<p>We would maintain backwards compatibility with Lua 5.1 but evolve the language from there; sometimes this means taking later features from Lua that don’t conflict with the existing language or our design values, sometimes this means innovating beyond what Lua has done. Crucially, we must maintain the balance between simplicity and power - we still value simplicity, we still need to avoid a feature explosion to ensure that the features compose and are of high quality, and we still need the language to be a good fit for beginners.</p>
<p>One of the largest limitations that we’ve seen is the lack of type checking making it easy to make mistakes in large code bases, as such <a href="https://luau-lang.org/typecheck">support for type checking</a> was a requirement for Luau. However, it’s important that the type checker is mostly transparent to the developers who don’t want to invest the time to learn it - anything else would change the learning curve too much for the language to be suitable for beginners. As such, we’ve investing in gradual typing, and our type checker is learning to strike a balance between inferring useful types for completely untyped programs (which, among other things, greatly enhances editing experience through type-aware autocomplete), and the lack of false positive diagnostics that can be confusing and distracting.</p>
<p>While we did need to introduce <a href="https://luau-lang.org/syntax">extra syntax</a> to the language - most notably, to support optional type annotations - it was important for us to maintain the cohesion of the overall syntax. We aren’t seeking to make a new language with a syntax alien to Lua programmers - Luau programs are still recognizably Lua, and to the extent possible we try to avoid new syntactic features. In a sense, we still want the syntax, semantics, and the runtime to be simple and minimal - but at the same time we have important problems to solve with respect to ergonomics, robustness and performance of the language, and solving some of them requires having slightly more complex syntax, semantics, or implementation.</p>
<p>So in finding ways to evolve Luau, we strive to design features that feel like they would be at home in Lua. At the same time, we’ve adopted a more open evolution process - the language development is driven <a href="https://github.com/Roblox/luau/blob/master/rfcs/README.md">through RFCs</a> that are designs open to the public that anyone can contribute to - this is in contrast with Lua, which has a very closed development process, and is one of the reasons why it would have been difficult for us to keep using Lua as we wouldn’t get a say in its development. At the same time, to ensure the design criterias are met, it’s important that the Luau development team at Roblox maintains a final say over design and implementation of the language<sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">3</a></sup>, while taking the community’s proposals and input into consideration.</p>
<h1 id="importance-of-co-design">Importance of co-design</h1>
<p>Luau language is developed in concert with the language compiler, runtime, type checker and other analysis tools, autocomplete engine and other tooling, and that development is guided by the vast volume of existing Luau code, both internal and external.</p>
<p>This is one of the key principles behind our evolution philosophy - neither layer is developed in isolation, and instead concerns at every level inform all other aspects of the language design and implementation.</p>
<p>This means that when designing language features, we make sure that they can be implemented efficiently, type checked properly, can be supported well in editing and analysis tools and have a positive impact on the code internal and external engineers write. When we find issues in any component, we can always ask, what changes to other components or even language design would make for a better overall solution.</p>
<p>This avoids some classes of design problems, for example we won’t specify a language feature that has a prohibitively high implementation cost, as it violates our simplicity criteria, or that is impractical to implement efficiently, as that would create a performance hazard. This also means that when implementing various components of the language we cross-check the concerns and applicability of these across the entire stack - for example, we’ve reworked our auto-complete system to use the same type inference engine that the type checking / analysis tools use, which had immense benefits for the experience of editing code, but also applied significant back pressure on the type inference itself, forcing us to improve it substantially and fix a lot of corner cases that would otherwise have lingered unnoticed.</p>
<p>Whenever we develop features, optimizations, improve our analysis engine or enhance the standard libraries, we also heavily rely on code written in Luau to validate our hypotheses. When working on new features we find motivation in the real problems that we see our developers face. For example, we implemented the <a href="https://luau-lang.org/syntax#if-then-else-expressions">new ternary operator</a> after seeing a large set of cases where existing Lua’s <code class="language-plaintext highlighter-rouge">a and b or c</code> pattern was error-prone for boolean values, which made it easy to accidentally introduce a mistake that was hard to identify automatically. All optimizations and new analysis features are validated on our internal 2M LOC codebase before being added to Luau, which allows us to quickly get initial validation of ideas, or invalidate some approaches as infeasible / unhelpful.</p>
<p>In addition to that, while we don’t have direct access to community-developed source code for privacy reasons, we can run experiments and collect telemetry<sup id="fnref:4" role="doc-noteref"><a href="#fn:4" class="footnote" rel="footnote">4</a></sup>, which also helps us make decisions regarding backwards compatibility. Due to <a href="https://www.hyrumslaw.com/">Hyrum’s law</a>, technically any change in the language or libraries, no matter how small, would be backwards incompatible - instead we adopt the notion of pragmatic balance between strict backwards compatibility<sup id="fnref:5" role="doc-noteref"><a href="#fn:5" class="footnote" rel="footnote">5</a></sup> and pragmatic compatibility concerns. For example, later versions of Lua make some library functions like <code class="language-plaintext highlighter-rouge">table.insert</code>/<code class="language-plaintext highlighter-rouge">table.remove</code> more strict with how they handle out of range indices. We have evaluated this change for compatibility by collecting telemetry on the use of out of range indices in these functions on the Roblox platform and concluded that applying the stricter checking would break existing programs, and instead had to slightly adjust the rules for out of range behavior in ways that was benign for existing code but prevented catastrophic performance degradation for large out of range indices. Because we couldn’t afford to introduce new runtime errors in this case, we also added a set of linting rules to our analysis engine to flag potential misuse of <code class="language-plaintext highlighter-rouge">table.insert</code>/<code class="language-plaintext highlighter-rouge">table.remove</code> before the code ever gets to run - this diagnostics is informational and as such doesn’t affect backwards compatibility, but does help prevent mistakes.</p>
<p>There are also cases where this co-design approach prevents introduction of features that can lead to easy misuse, which can be difficult to see in the design of the feature itself, but becomes more apparent when you consider features in context of the entire ecosystem. This is a good thing - it means co-design acts as a forcing function on the language simplicity and makes it easier to flag potential bad interactions between different language features, or language features and tooling, or language features and existing programming patterns that are in widespread use in real-world code. By making sure that all features are validated for their impact across the stack and on code written in Luau, we ultimately get a better, simpler and more cohesive language.</p>
<h1 id="efficient-execution">Efficient execution</h1>
<p>One of the critical goals in front of Luau is efficiency, both from the performance and memory perspective. There’s only so many milliseconds in a frame, and we simultaneously see the need to increase the scale and complexity of simulated experiences, which requires more memory and computation, as well as the need to fit more comfortably into smaller budgets of performance memory for better experience on smaller devices. In fact, one of the motivations for Luau in 2019 has been improved performance, as we saw many opportunities to go beyond Lua with a redesigned implementation.</p>
<p>Crucially, our performance needs are somewhat unique and require somewhat unique solutions.</p>
<p>We need Luau to run on many platforms where native code generation is either prohibited by the platform vendor or impractical due to tight memory constraints. As such, in terms of execution performance it’s critical that we have a very fast interpreter<sup id="fnref:6" role="doc-noteref"><a href="#fn:6" class="footnote" rel="footnote">6</a></sup>. However, we have freedom in terms of high level design of the entire stack - for example, clients never see the source code of the scripts as all compilation to bytecode happens on the server; this gives us an opportunity to perform more involved and expensive optimizations during that process as well as have the smallest possible startup time on the client without complex pre-parse steps. Notably, our bytecode compiler performs a series of high level optimizations including function inlining and loop unrolling that in other dynamic languages is often left to the just-in-time compiler.</p>
<p>Another area where performance is critical is garbage collection. Garbage collection is crucial for the language’s simplicity as it makes memory management easier to reason about, but it does require a substantial amount of implementation effort to keep it efficient. For Roblox and for any other game engine or interactive simulation, latency is critical and so our collector is heavily optimized for that - to the extent possible collection is incremental and stop-the-world pauses are very brief. Another part of the performance story here however is the language and data structure design - by making sure that core data types are efficient in how they are laid out in memory we reduce the amount of work garbage collector takes to trace the heap, and, as another example of co-design, we try to make sure that language features are conscious of the impact they have on memory and garbage collection efficiency.</p>
<p>However, from a whole-platform standpoint there’s a lot of performance aspects that go beyond single-threaded execution. This is an active area of research and development for the team, as to really leverage the hardware the code is running on we need to think about SIMD, hardware thread utilization as well as running code in a cluster of nodes. These considerations inform current and future development of the runtime and the language (for example, our runtime now supports efficient operations on short SIMD vectors even in interpreted mode, and the VM is fairly lightweight to instantiate which makes running many VMs per core practical, with message passing or access to shared Roblox data model used to make gameplay features feasible to implement), but we’re definitely in the early days here - our first implementation of parallel script execution in Roblox just <a href="https://devforum.roblox.com/t/full-release-of-parallel-luau-v1/1836187">shipped earlier this year</a>. This is likely the area where a lot of future innovations will happen as well.</p>
<h1 id="future">Future</h1>
<p>We’re very happy with the success of Luau - in several years we’ve established consistent processes for evolving the language and so far we found a good balance between simplicity, ease of use, performance and robustness of the language, its implementation and the tooling surrounding it. The language keeps continuously evolving but at a pace that is easy to stay on top of - in 2022 we shipped a few syntactic extensions for type annotations but no changes to the syntax of the language outside of types, and only one major <a href="https://luau-lang.org/syntax#generalized-iteration">semantic change to the for loop iteration</a> that actually made the language easier to use by avoiding the need to specify the table traversal style via <code class="language-plaintext highlighter-rouge">pairs</code>/<code class="language-plaintext highlighter-rouge">ipairs</code>. We try to make sure that the features are general and provide enough extensibility so that libraries can be built on top of the language to make it easier to write code, while also making it practical to use the language without complex supporting frameworks.</p>
<p>There’s still a lot of ground to cover, and we’ll be working on Luau for years to come. We’re in the process of building the next version of our type inference / checking engine to make sure that all users of the language regardless of their expertise benefit from it, we’ve started investing in native code generation as we’re reaching the limits of interpreted performance (although some exciting opportunities for compiler optimization are still on the horizon), and there’s still a lot of hard design and implementation work ahead of us for some important language features and standard libraries. And as mentioned, our execution model will likely see a lot of innovation as we push the boundaries of hardware utilization across cores and nodes.</p>
<p>Overall, Luau is like an iceberg - the surface is simple to learn and use, but it hides the tremendous amount of careful design, engineering and attention to detail, and we plan to continue to invest in it while trying to keep the outer surface comparatively small. We’re excited to see how far we can take it!</p>
<div class="footnotes" role="doc-endnotes">
<ol>
<li id="fn:1" role="doc-endnote">
<p>High-performance JavaScript engines didn’t exist at the time! LuaJIT was around the corner and redefined the performance expectations of dynamic languages. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
<li id="fn:2" role="doc-endnote">
<p>In fact, scaling to large teams of expert programmers is one of the core motivations behind our creating Luau, while a requirement to still be suitable for beginner programmers guides our evolution direction. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
<li id="fn:3" role="doc-endnote">
<p>This would have been difficult to drive in any existing large established language like JavaScript or Python. <a href="#fnref:3" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
<li id="fn:4" role="doc-endnote">
<p>This is limited to Roblox platform and doesn’t exist in open-source releases. <a href="#fnref:4" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
<li id="fn:5" role="doc-endnote">
<p>Which we do follow in some areas, such as syntactic compatibility - all existing programs that parse must continue to parse the same way as the language evolves. <a href="#fnref:5" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
<li id="fn:6" role="doc-endnote">
<p>Some design decisions and implementation techniques are documented on our <a href="https://luau-lang.org/performance">performance page</a>. <a href="#fnref:6" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
</ol>
</div>Arseny KapoulkineAt the heart of Roblox technology lies Luau, a scripting language derived from Lua 5.1 that is being developed by an internal team of programming language experts with the help of open source contributors.Luau Recap: September & October 20222022-11-01T00:00:00+00:002022-11-01T00:00:00+00:00https://luau-lang.org/2022/11/01/luau-recap-september-october-2022<p>Luau is our new language that you can read more about at <a href="https://luau-lang.org">https://luau-lang.org</a>.</p>
<p>[Cross-posted to the <a href="https://devforum.roblox.com/t/luau-recap-september-october-2022/">Roblox Developer Forum</a>.]</p>
<h2 id="semantic-subtyping">Semantic subtyping</h2>
<p>One of the most important goals for Luau is to avoid <em>false
positives</em>, 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!</p>
<p>We are pleased to announce that a major component of minimizing false
positives has landed, <em>semantic subtyping</em>, which removes a class of false positives caused
by failures of subtyping. For example, in the program</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kd">local</span> <span class="n">x</span> <span class="p">:</span> <span class="n">CFrame</span> <span class="o">=</span> <span class="n">CFrame</span><span class="p">.</span><span class="n">new</span><span class="p">()</span>
<span class="kd">local</span> <span class="n">y</span> <span class="p">:</span> <span class="n">Vector3</span> <span class="err">|</span> <span class="n">CFrame</span>
<span class="k">if</span> <span class="p">(</span><span class="nb">math.random</span><span class="p">())</span> <span class="k">then</span>
<span class="n">y</span> <span class="o">=</span> <span class="n">CFrame</span><span class="p">.</span><span class="n">new</span><span class="p">()</span>
<span class="k">else</span>
<span class="n">y</span> <span class="o">=</span> <span class="n">Vector3</span><span class="p">.</span><span class="n">new</span><span class="p">()</span>
<span class="k">end</span>
<span class="kd">local</span> <span class="n">z</span> <span class="p">:</span> <span class="n">Vector3</span> <span class="err">|</span> <span class="n">CFrame</span> <span class="o">=</span> <span class="n">x</span> <span class="o">*</span> <span class="n">y</span> <span class="c1">-- Type Error!</span>
</code></pre></div></div>
<p>an error is reported, even though there is no problem at runtime. This
is because <code class="language-plaintext highlighter-rouge">CFrame</code>’s multiplication has two overloads:</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="p">((</span><span class="n">CFrame</span><span class="p">,</span> <span class="n">CFrame</span><span class="p">)</span> <span class="o">-></span> <span class="n">CFrame</span><span class="p">)</span>
<span class="err">&</span> <span class="p">((</span><span class="n">CFrame</span><span class="p">,</span> <span class="n">Vector3</span><span class="p">)</span> <span class="o">-></span> <span class="n">Vector3</span><span class="p">)</span>
</code></pre></div></div>
<p>The current syntax-driven algorithm for subtyping is not sophisticated
enough to realize that this is a subtype of the desired type:</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="p">(</span><span class="n">CFrame</span><span class="p">,</span> <span class="n">Vector3</span> <span class="err">|</span> <span class="n">CFrame</span><span class="p">)</span> <span class="o">-></span> <span class="p">(</span><span class="n">Vector3</span> <span class="err">|</span> <span class="n">CFrame</span><span class="p">)</span>
</code></pre></div></div>
<p>Our new algorithm is driven by the semantics of subtyping, not the syntax of types,
and eliminates this class of false positives.</p>
<p>If you want to know more about semantic subtyping in Luau, check out our
<a href="https://luau-lang.org/2022/10/31/luau-semantic-subtyping.html">technical blog post</a>
on the subject.</p>
<h2 id="other-analysis-improvements">Other analysis improvements</h2>
<ul>
<li>Improve stringification of function types.</li>
<li>Improve parse error warnings in the case of missing tokens after a comma.</li>
<li>Improve typechecking of expressions involving variadics such as <code class="language-plaintext highlighter-rouge">{ ... }</code>.</li>
<li>Make sure modules don’t return unbound generic types.</li>
<li>Improve cycle detection in stringifying types.</li>
<li>Improve type inference of combinations of intersections and generic functions.</li>
<li>Improve typechecking when calling a function which returns a variadic e.g. <code class="language-plaintext highlighter-rouge">() -> (number...)</code>.</li>
<li>Improve typechecking when passing a function expression as a parameter to a function.</li>
<li>Improve error reporting locations.</li>
<li>Remove some sources of memory corruption and crashes.</li>
</ul>
<h2 id="other-runtime-and-debugger-improvements">Other runtime and debugger improvements</h2>
<ul>
<li>Improve performance of accessing debug info.</li>
<li>Improve performance of <code class="language-plaintext highlighter-rouge">getmetatable</code> and <code class="language-plaintext highlighter-rouge">setmetatable</code>.</li>
<li>Remove a source of freezes in the debugger.</li>
<li>Improve GC accuracy and performance.</li>
</ul>
<h2 id="thanks">Thanks</h2>
<p>Thanks for all the contributions!</p>
<ul>
<li><a href="https://github.com/AllanJeremy">AllanJeremy</a></li>
<li><a href="https://github.com/JohnnyMorganz">JohnnyMorganz</a></li>
<li><a href="https://github.com/jujhar16">jujhar16</a></li>
<li><a href="https://github.com/petrihakkinen">petrihakkinen</a></li>
</ul>Luau is our new language that you can read more about at https://luau-lang.org.Semantic Subtyping in Luau2022-10-31T00:00:00+00:002022-10-31T00:00:00+00:00https://luau-lang.org/2022/10/31/luau-semantic-subtyping<p>Luau is the first programming language to put the power of semantic subtyping in the hands of millions of creators.</p>
<h2 id="minimizing-false-positives">Minimizing false positives</h2>
<p>One of the issues with type error reporting in tools like the Script Analysis widget in Roblox Studio is <em>false positives</em>. These are warnings that are artifacts of the analysis, and don’t correspond to errors which can occur at runtime. For example, the program</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kd">local</span> <span class="n">x</span> <span class="o">=</span> <span class="n">CFrame</span><span class="p">.</span><span class="n">new</span><span class="p">()</span>
<span class="kd">local</span> <span class="n">y</span>
<span class="k">if</span> <span class="p">(</span><span class="nb">math.random</span><span class="p">())</span> <span class="k">then</span>
<span class="n">y</span> <span class="o">=</span> <span class="n">CFrame</span><span class="p">.</span><span class="n">new</span><span class="p">()</span>
<span class="k">else</span>
<span class="n">y</span> <span class="o">=</span> <span class="n">Vector3</span><span class="p">.</span><span class="n">new</span><span class="p">()</span>
<span class="k">end</span>
<span class="kd">local</span> <span class="n">z</span> <span class="o">=</span> <span class="n">x</span> <span class="o">*</span> <span class="n">y</span>
</code></pre></div></div>
<p>reports a type error which cannot happen at runtime, since <code class="language-plaintext highlighter-rouge">CFrame</code> supports multiplication by both <code class="language-plaintext highlighter-rouge">Vector3</code> and <code class="language-plaintext highlighter-rouge">CFrame</code>. (Its type is <code class="language-plaintext highlighter-rouge">((CFrame, CFrame) -> CFrame) & ((CFrame, Vector3) -> Vector3)</code>.)</p>
<p>False positives are especially poor for onboarding new users. If a type-curious creator switches on typechecking and is immediately faced with a wall of spurious red squiggles, there is a strong incentive to immediately switch it off again.</p>
<p>Inaccuracies in type errors are inevitable, since it is impossible to decide ahead of time whether a runtime error will be triggered. Type system designers have to choose whether to live with false positives or false negatives. In Luau this is determined by the mode: <code class="language-plaintext highlighter-rouge">strict</code> mode errs on the side of false positives, and <code class="language-plaintext highlighter-rouge">nonstrict</code> mode errs on the side of false negatives.</p>
<p>While inaccuracies are inevitable, we try to remove them whenever possible, since they result in spurious errors, and imprecision in type-driven tooling like autocomplete or API documentation.</p>
<h2 id="subtyping-as-a-source-of-false-positives">Subtyping as a source of false positives</h2>
<p>One of the sources of false positives in Luau (and many other similar languages like TypeScript or Flow) is <em>subtyping</em>. Subtyping is used whenever a variable is initialized or assigned to, and whenever a function is called: the type system checks that the type of the expression is a subtype of the type of the variable. For example, if we add types to the above program</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kd">local</span> <span class="n">x</span> <span class="p">:</span> <span class="n">CFrame</span> <span class="o">=</span> <span class="n">CFrame</span><span class="p">.</span><span class="n">new</span><span class="p">()</span>
<span class="kd">local</span> <span class="n">y</span> <span class="p">:</span> <span class="n">Vector3</span> <span class="err">|</span> <span class="n">CFrame</span>
<span class="k">if</span> <span class="p">(</span><span class="nb">math.random</span><span class="p">())</span> <span class="k">then</span>
<span class="n">y</span> <span class="o">=</span> <span class="n">CFrame</span><span class="p">.</span><span class="n">new</span><span class="p">()</span>
<span class="k">else</span>
<span class="n">y</span> <span class="o">=</span> <span class="n">Vector3</span><span class="p">.</span><span class="n">new</span><span class="p">()</span>
<span class="k">end</span>
<span class="kd">local</span> <span class="n">z</span> <span class="p">:</span> <span class="n">Vector3</span> <span class="err">|</span> <span class="n">CFrame</span> <span class="o">=</span> <span class="n">x</span> <span class="o">*</span> <span class="n">y</span>
</code></pre></div></div>
<p>then the type system checks that the type of <code class="language-plaintext highlighter-rouge">CFrame</code> multiplication is a subtype of <code class="language-plaintext highlighter-rouge">(CFrame, Vector3 | CFrame) -> (Vector3 | CFrame)</code>.</p>
<p>Subtyping is a very useful feature, and it supports rich type constructs like type union (<code class="language-plaintext highlighter-rouge">T | U</code>) and intersection (<code class="language-plaintext highlighter-rouge">T & U</code>). For example, <code class="language-plaintext highlighter-rouge">number?</code> is implemented as a union type <code class="language-plaintext highlighter-rouge">(number | nil)</code>, inhabited by values that are either numbers or <code class="language-plaintext highlighter-rouge">nil</code>.</p>
<p>Unfortunately, the interaction of subtyping with intersection and union types can have odd results. A simple (but rather artificial) case in older Luau was:</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kd">local</span> <span class="n">x</span> <span class="p">:</span> <span class="p">(</span><span class="n">number</span><span class="err">?</span><span class="p">)</span> <span class="err">&</span> <span class="p">(</span><span class="n">string</span><span class="err">?</span><span class="p">)</span> <span class="o">=</span> <span class="kc">nil</span>
<span class="kd">local</span> <span class="n">y</span> <span class="p">:</span> <span class="kc">nil</span> <span class="o">=</span> <span class="kc">nil</span>
<span class="n">y</span> <span class="o">=</span> <span class="n">x</span> <span class="c1">-- Type '(number?) & (string?)' could not be converted into 'nil'</span>
<span class="n">x</span> <span class="o">=</span> <span class="n">y</span>
</code></pre></div></div>
<p>This error is caused by a failure of subtyping, the old subtyping algorithm reports that <code class="language-plaintext highlighter-rouge">(number?) & (string?)</code> is not a subtype of <code class="language-plaintext highlighter-rouge">nil</code>. This is a false positive, since <code class="language-plaintext highlighter-rouge">number & string</code> is uninhabited, so the only possible inhabitant of <code class="language-plaintext highlighter-rouge">(number?) & (string?)</code> is <code class="language-plaintext highlighter-rouge">nil</code>.</p>
<p>This is an artificial example, but there are real issues raised by creators caused by the problems, for example <a href="https://devforum.roblox.com/t/luau-recap-july-2021/1382101/5">https://devforum.roblox.com/t/luau-recap-july-2021/1382101/5</a>. Currently, these issues mostly affect creators making use of sophisticated type system features, but as we make type inference more accurate, union and intersection types will become more common, even in code with no type annotations.</p>
<p>This class of false positives no longer occurs in Luau, as we have moved from our old approach of <em>syntactic subtyping</em> to an alternative called <em>semantic subtyping</em>.</p>
<h2 id="syntactic-subtyping">Syntactic subtyping</h2>
<p>AKA “what we did before.”</p>
<p>Syntactic subtyping is a syntax-directed recursive algorithm. The interesting cases to deal with intersection and union types are:</p>
<ul>
<li>Reflexivity: <code class="language-plaintext highlighter-rouge">T</code> is a subtype of <code class="language-plaintext highlighter-rouge">T</code></li>
<li>Intersection L: <code class="language-plaintext highlighter-rouge">(T₁ & … & Tⱼ)</code> is a subtype of <code class="language-plaintext highlighter-rouge">U</code> whenever some of the <code class="language-plaintext highlighter-rouge">Tᵢ</code> are subtypes of <code class="language-plaintext highlighter-rouge">U</code></li>
<li>Union L: <code class="language-plaintext highlighter-rouge">(T₁ | … | Tⱼ)</code> is a subtype of <code class="language-plaintext highlighter-rouge">U</code> whenever all of the <code class="language-plaintext highlighter-rouge">Tᵢ</code> are subtypes of <code class="language-plaintext highlighter-rouge">U</code></li>
<li>Intersection R: <code class="language-plaintext highlighter-rouge">T</code> is a subtype of <code class="language-plaintext highlighter-rouge">(U₁ & … & Uⱼ)</code> whenever <code class="language-plaintext highlighter-rouge">T</code> is a subtype of all of the <code class="language-plaintext highlighter-rouge">Uᵢ</code></li>
<li>Union R: <code class="language-plaintext highlighter-rouge">T</code> is a subtype of <code class="language-plaintext highlighter-rouge">(U₁ | … | Uⱼ)</code> whenever <code class="language-plaintext highlighter-rouge">T</code> is a subtype of some of the <code class="language-plaintext highlighter-rouge">Uᵢ</code>.</li>
</ul>
<p>For example:</p>
<ul>
<li>By Reflexivity: <code class="language-plaintext highlighter-rouge">nil</code> is a subtype of <code class="language-plaintext highlighter-rouge">nil</code></li>
<li>so by Union R: <code class="language-plaintext highlighter-rouge">nil</code> is a subtype of <code class="language-plaintext highlighter-rouge">number?</code></li>
<li>and: <code class="language-plaintext highlighter-rouge">nil</code> is a subtype of <code class="language-plaintext highlighter-rouge">string?</code></li>
<li>so by Intersection R: <code class="language-plaintext highlighter-rouge">nil</code> is a subtype of <code class="language-plaintext highlighter-rouge">(number?) & (string?)</code>.</li>
</ul>
<p>Yay! Unfortunately, using these rules:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">number</code> isn’t a subtype of <code class="language-plaintext highlighter-rouge">nil</code></li>
<li>so by Union L: <code class="language-plaintext highlighter-rouge">(number?)</code> isn’t a subtype of <code class="language-plaintext highlighter-rouge">nil</code></li>
<li>and: <code class="language-plaintext highlighter-rouge">string</code> isn’t a subtype of <code class="language-plaintext highlighter-rouge">nil</code></li>
<li>so by Union L: <code class="language-plaintext highlighter-rouge">(string?)</code> isn’t a subtype of <code class="language-plaintext highlighter-rouge">nil</code></li>
<li>so by Intersection L: <code class="language-plaintext highlighter-rouge">(number?) & (string?)</code> isn’t a subtype of <code class="language-plaintext highlighter-rouge">nil</code>.</li>
</ul>
<p>This is typical of syntactic subtyping: when it returns a “yes” result, it is correct, but when it returns a “no” result, it might be wrong. The algorithm is a <em>conservative approximation</em>, and since a “no” result can lead to type errors, this is a source of false positives.</p>
<h2 id="semantic-subtyping">Semantic subtyping</h2>
<p>AKA “what we do now.”</p>
<p>Rather than thinking of subtyping as being syntax-directed, we first consider its semantics, and later return to how the semantics is implemented. For this, we adopt semantic subtyping:</p>
<ul>
<li>The semantics of a type is a set of values.</li>
<li>Intersection types are thought of as intersections of sets.</li>
<li>Union types are thought of as unions of sets.</li>
<li>Subtyping is thought of as set inclusion.</li>
</ul>
<p>For example:</p>
<table>
<thead>
<tr>
<th>Type</th>
<th>Semantics</th>
</tr>
</thead>
<tbody>
<tr>
<td><code class="language-plaintext highlighter-rouge">number</code></td>
<td>{ 1, 2, 3, … }</td>
</tr>
<tr>
<td><code class="language-plaintext highlighter-rouge">string</code></td>
<td>{ “foo”, “bar”, … }</td>
</tr>
<tr>
<td><code class="language-plaintext highlighter-rouge">nil</code></td>
<td>{ nil }</td>
</tr>
<tr>
<td><code class="language-plaintext highlighter-rouge">number?</code></td>
<td>{ nil, 1, 2, 3, … }</td>
</tr>
<tr>
<td><code class="language-plaintext highlighter-rouge">string?</code></td>
<td>{ nil, “foo”, “bar”, … }</td>
</tr>
<tr>
<td><code class="language-plaintext highlighter-rouge">(number?) & (string?)</code></td>
<td>{ nil, 1, 2, 3, … } ∩ { nil, “foo”, “bar”, … } = { nil }</td>
</tr>
</tbody>
</table>
<p>and since subtypes are interpreted as set inclusions:</p>
<table>
<thead>
<tr>
<th>Subtype</th>
<th>Supertype</th>
<th>Because</th>
</tr>
</thead>
<tbody>
<tr>
<td><code class="language-plaintext highlighter-rouge">nil</code></td>
<td><code class="language-plaintext highlighter-rouge">number?</code></td>
<td>{ nil } ⊆ { nil, 1, 2, 3, … }</td>
</tr>
<tr>
<td><code class="language-plaintext highlighter-rouge">nil</code></td>
<td><code class="language-plaintext highlighter-rouge">string?</code></td>
<td>{ nil } ⊆ { nil, “foo”, “bar”, … }</td>
</tr>
<tr>
<td><code class="language-plaintext highlighter-rouge">nil</code></td>
<td><code class="language-plaintext highlighter-rouge">(number?) & (string?)</code></td>
<td>{ nil } ⊆ { nil }</td>
</tr>
<tr>
<td><code class="language-plaintext highlighter-rouge">(number?) & (string?)</code></td>
<td><code class="language-plaintext highlighter-rouge">nil</code></td>
<td>{ nil } ⊆ { nil }</td>
</tr>
</tbody>
</table>
<p>So according to semantic subtyping, <code class="language-plaintext highlighter-rouge">(number?) & (string?)</code> is equivalent to <code class="language-plaintext highlighter-rouge">nil</code>, but syntactic subtyping only supports one direction.</p>
<p>This is all fine and good, but if we want to use semantic subtyping in tools, we need an algorithm, and it turns out checking semantic subtyping is non-trivial.</p>
<h2 id="semantic-subtyping-is-hard">Semantic subtyping is hard</h2>
<p>NP-hard to be precise.</p>
<p>We can reduce graph coloring to semantic subtyping by coding up a graph as a Luau type such that checking subtyping on types has the same result as checking for the impossibility of coloring the graph</p>
<p>For example, coloring a three-node, two color graph can be done using types:</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">type</span> <span class="n">Red</span> <span class="o">=</span> <span class="s2">"red"</span>
<span class="nb">type</span> <span class="n">Blue</span> <span class="o">=</span> <span class="s2">"blue"</span>
<span class="nb">type</span> <span class="n">Color</span> <span class="o">=</span> <span class="n">Red</span> <span class="err">|</span> <span class="n">Blue</span>
<span class="nb">type</span> <span class="n">Coloring</span> <span class="o">=</span> <span class="p">(</span><span class="n">Color</span><span class="p">)</span> <span class="o">-></span> <span class="p">(</span><span class="n">Color</span><span class="p">)</span> <span class="o">-></span> <span class="p">(</span><span class="n">Color</span><span class="p">)</span> <span class="o">-></span> <span class="n">boolean</span>
<span class="nb">type</span> <span class="n">Uncolorable</span> <span class="o">=</span> <span class="p">(</span><span class="n">Color</span><span class="p">)</span> <span class="o">-></span> <span class="p">(</span><span class="n">Color</span><span class="p">)</span> <span class="o">-></span> <span class="p">(</span><span class="n">Color</span><span class="p">)</span> <span class="o">-></span> <span class="kc">false</span>
</code></pre></div></div>
<p>Then a graph can be encoded as an overload function type with
subtype <code class="language-plaintext highlighter-rouge">Uncolorable</code> and supertype <code class="language-plaintext highlighter-rouge">Coloring</code>, as an overloaded
function which returns <code class="language-plaintext highlighter-rouge">false</code> when a constraint is violated. Each
overload encodes one constraint. For example a line has constraints
saying that adjacent nodes cannot have the same color:</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">type</span> <span class="n">Line</span> <span class="o">=</span> <span class="n">Coloring</span>
<span class="err">&</span> <span class="p">((</span><span class="n">Red</span><span class="p">)</span> <span class="o">-></span> <span class="p">(</span><span class="n">Red</span><span class="p">)</span> <span class="o">-></span> <span class="p">(</span><span class="n">Color</span><span class="p">)</span> <span class="o">-></span> <span class="kc">false</span><span class="p">)</span>
<span class="err">&</span> <span class="p">((</span><span class="n">Blue</span><span class="p">)</span> <span class="o">-></span> <span class="p">(</span><span class="n">Blue</span><span class="p">)</span> <span class="o">-></span> <span class="p">(</span><span class="n">Color</span><span class="p">)</span> <span class="o">-></span> <span class="kc">false</span><span class="p">)</span>
<span class="err">&</span> <span class="p">((</span><span class="n">Color</span><span class="p">)</span> <span class="o">-></span> <span class="p">(</span><span class="n">Red</span><span class="p">)</span> <span class="o">-></span> <span class="p">(</span><span class="n">Red</span><span class="p">)</span> <span class="o">-></span> <span class="kc">false</span><span class="p">)</span>
<span class="err">&</span> <span class="p">((</span><span class="n">Color</span><span class="p">)</span> <span class="o">-></span> <span class="p">(</span><span class="n">Blue</span><span class="p">)</span> <span class="o">-></span> <span class="p">(</span><span class="n">Blue</span><span class="p">)</span> <span class="o">-></span> <span class="kc">false</span><span class="p">)</span>
</code></pre></div></div>
<p>A triangle is similar, but the end points also cannot have the same color:</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">type</span> <span class="n">Triangle</span> <span class="o">=</span> <span class="n">Line</span>
<span class="err">&</span> <span class="p">((</span><span class="n">Red</span><span class="p">)</span> <span class="o">-></span> <span class="p">(</span><span class="n">Color</span><span class="p">)</span> <span class="o">-></span> <span class="p">(</span><span class="n">Red</span><span class="p">)</span> <span class="o">-></span> <span class="kc">false</span><span class="p">)</span>
<span class="err">&</span> <span class="p">((</span><span class="n">Blue</span><span class="p">)</span> <span class="o">-></span> <span class="p">(</span><span class="n">Color</span><span class="p">)</span> <span class="o">-></span> <span class="p">(</span><span class="n">Blue</span><span class="p">)</span> <span class="o">-></span> <span class="kc">false</span><span class="p">)</span>
</code></pre></div></div>
<p>Now, <code class="language-plaintext highlighter-rouge">Triangle</code> is a subtype of <code class="language-plaintext highlighter-rouge">Uncolorable</code>, but <code class="language-plaintext highlighter-rouge">Line</code> is not, since the line can be 2-colored.
This can be generalized to any finite graph with any finite number of colors, and so subtype checking is NP-hard.</p>
<p>We deal with this in two ways:</p>
<ul>
<li>we cache types to reduce memory footprint, and</li>
<li>give up with a “Code Too Complex” error if the cache of types gets too large.</li>
</ul>
<p>Hopefully this doesn’t come up in practice much. There is good evidence that issues like this don’t arise in practice from experience with type systems like that of Standard ML, which is <a href="https://dl.acm.org/doi/abs/10.1145/96709.96748">EXPTIME-complete</a>, but in practice you have to go out of your way to code up Turing Machine tapes as types.</p>
<h2 id="type-normalization">Type normalization</h2>
<p>The algorithm used to decide semantic subtyping is <em>type normalization</em>.
Rather than being directed by syntax, we first rewrite types to be normalized, then check subtyping on normalized types.</p>
<p>A normalized type is a union of:</p>
<ul>
<li>a normalized nil type (either <code class="language-plaintext highlighter-rouge">never</code> or <code class="language-plaintext highlighter-rouge">nil</code>)</li>
<li>a normalized number type (either <code class="language-plaintext highlighter-rouge">never</code> or <code class="language-plaintext highlighter-rouge">number</code>)</li>
<li>a normalized boolean type (either <code class="language-plaintext highlighter-rouge">never</code> or <code class="language-plaintext highlighter-rouge">true</code> or <code class="language-plaintext highlighter-rouge">false</code> or <code class="language-plaintext highlighter-rouge">boolean</code>)</li>
<li>a normalized function type (either <code class="language-plaintext highlighter-rouge">never</code> or an intersection of function types)
etc</li>
</ul>
<p>Once types are normalized, it is straightforward to check semantic subtyping.</p>
<p>Every type can be normalized (sigh, with some technical restrictions around generic type packs). The important steps are:</p>
<ul>
<li>removing intersections of mismatched primitives, e.g. <code class="language-plaintext highlighter-rouge">number & bool</code> is replaced by <code class="language-plaintext highlighter-rouge">never</code>, and</li>
<li>removing unions of functions, e.g. <code class="language-plaintext highlighter-rouge">((number?) -> number) | ((string?) -> string)</code> is replaced by <code class="language-plaintext highlighter-rouge">(nil) -> (number | string)</code>.</li>
</ul>
<p>For example, normalizing <code class="language-plaintext highlighter-rouge">(number?) & (string?)</code> removes <code class="language-plaintext highlighter-rouge">number & string</code>, so all that is left is <code class="language-plaintext highlighter-rouge">nil</code>.</p>
<p>Our first attempt at implementing type normalization applied it liberally, but this resulted in dreadful performance (complex code went from typechecking in less than a minute to running overnight). The reason for this is annoyingly simple: there is an optimization in Luau’s subtyping algorithm to handle reflexivity (<code class="language-plaintext highlighter-rouge">T</code> is a subtype of <code class="language-plaintext highlighter-rouge">T</code>) that performs a cheap pointer equality check. Type normalization can convert pointer-identical types into semantically-equivalent (but not pointer-identical) types, which significantly degrades performance.</p>
<p>Because of these performance issues, we still use syntactic subtyping as our first check for subtyping, and only perform type normalization if the syntactic algorithm fails. This is sound, because syntactic subtyping is a conservative approximation to semantic subtyping.</p>
<h2 id="pragmatic-semantic-subtyping">Pragmatic semantic subtyping</h2>
<p>Off-the-shelf semantic subtyping is slightly different from what is implemented in Luau, because it requires models to be <em>set-theoretic</em>, which requires that inhabitants of function types “act like functions.” There are two reasons why we drop this requirement.</p>
<p><strong>Firstly</strong>, we normalize function types to an intersection of functions, for example a horrible mess of unions and intersections of functions:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>((number?) -> number?) | (((number) -> number) & ((string?) -> string?))
</code></pre></div></div>
<p>normalizes to an overloaded function:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>((number) -> number?) & ((nil) -> (number | string)?)
</code></pre></div></div>
<p>Set-theoretic semantic subtyping does not support this normalization, and instead normalizes functions to <em>disjunctive normal form</em> (unions of intersections of functions). We do not do this for ergonomic reasons: overloaded functions are idiomatic in Luau, but DNF is not, and we do not want to present users with such non-idiomatic types.</p>
<p>Our normalization relies on rewriting away unions of function types:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>((A) -> B) | ((C) -> D) → (A & C) -> (B | D)
</code></pre></div></div>
<p>This normalization is sound in our model, but not in set-theoretic models.</p>
<p><strong>Secondly</strong>, in Luau, the type of a function application <code class="language-plaintext highlighter-rouge">f(x)</code> is <code class="language-plaintext highlighter-rouge">B</code> if <code class="language-plaintext highlighter-rouge">f</code> has type <code class="language-plaintext highlighter-rouge">(A) -> B</code> and <code class="language-plaintext highlighter-rouge">x</code> has type <code class="language-plaintext highlighter-rouge">A</code>. Unexpectedly, this is not always true in set-theoretic models, due to uninhabited types. In set-theoretic models, if <code class="language-plaintext highlighter-rouge">x</code> has type <code class="language-plaintext highlighter-rouge">never</code> then <code class="language-plaintext highlighter-rouge">f(x)</code> has type <code class="language-plaintext highlighter-rouge">never</code>. We do not want to burden users with the idea that function application has a special corner case, especially since that corner case can only arise in dead code.</p>
<p>In set-theoretic models, <code class="language-plaintext highlighter-rouge">(never) -> A</code> is a subtype of <code class="language-plaintext highlighter-rouge">(never) -> B</code>, no matter what <code class="language-plaintext highlighter-rouge">A</code> and <code class="language-plaintext highlighter-rouge">B</code> are. This is not true in Luau.</p>
<p>For these two reasons (which are largely about ergonomics rather than anything technical) we drop the set-theoretic requirement, and use <em>pragmatic</em> semantic subtyping.</p>
<h2 id="negation-types">Negation types</h2>
<p>The other difference between Luau’s type system and off-the-shelf semantic subtyping is that Luau does not support all negated types.</p>
<p>The common case for wanting negated types is in typechecking conditionals:</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">-- initially x has type T</span>
<span class="k">if</span> <span class="p">(</span><span class="nb">type</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="o">==</span> <span class="s2">"string"</span><span class="p">)</span> <span class="k">then</span>
<span class="c1">-- in this branch x has type T & string</span>
<span class="k">else</span>
<span class="c1">-- in this branch x has type T & ~string</span>
<span class="k">end</span>
</code></pre></div></div>
<p>This uses a negated type <code class="language-plaintext highlighter-rouge">~string</code> inhabited by values that are not strings.</p>
<p>In Luau, we only allow this kind of typing refinement on <em>test types</em> like <code class="language-plaintext highlighter-rouge">string</code>, <code class="language-plaintext highlighter-rouge">function</code>, <code class="language-plaintext highlighter-rouge">Part</code> and so on, and <em>not</em> on structural types like <code class="language-plaintext highlighter-rouge">(A) -> B</code>, which avoids the common case of general negated types.</p>
<h2 id="prototyping-and-verification">Prototyping and verification</h2>
<p>During the design of Luau’s semantic subtyping algorithm, there were changes made (for example initially we thought we were going to be able to use set-theoretic subtyping). During this time of rapid change, it was important to be able to iterate quickly, so we initially implemented a <a href="https://github.com/luau-lang/agda-typeck">prototype</a> rather than jumping straight to a production implementation.</p>
<p>Validating the prototype was important, since subtyping algorithms can have unexpected corner cases. For this reason, we adopted Agda as the prototyping language. As well as supporting unit testing, Agda supports mechanized verification, so we are confident in the design.</p>
<p>The prototype does not implement all of Luau, just the functional subset, but this was enough to discover subtle feature interactions that would probably have surfaced as difficult-to-fix bugs in production.</p>
<p>Prototyping is not perfect, for example the main issues that we hit in production were about performance and the C++ standard library, which are never going to be caught by a prototype. But the production implementation was otherwise fairly straightforward (or at least as straightforward as a 3kLOC change can be).</p>
<h2 id="next-steps">Next steps</h2>
<p>Semantic subtyping has removed one source of false positives, but we still have others to track down:</p>
<ul>
<li>overloaded function applications and operators,</li>
<li>property access on expressions of complex type,</li>
<li>read-only properties of tables,</li>
<li>variables that change type over time (aka typestates),</li>
<li>…</li>
</ul>
<p>The quest to remove spurious red squiggles continues!</p>
<h2 id="acknowledgments">Acknowledgments</h2>
<p>Thanks to Giuseppe Castagna and Ben Greenman for helpful comments on drafts of this post.</p>
<h2 id="further-reading">Further reading</h2>
<p>If you want to find out more about Luau and semantic subtyping, you might want to check out…</p>
<ul>
<li>Luau. <a href="https://luau-lang.org/">https://luau-lang.org/</a></li>
<li>Lily Brown, Andy Friesen and Alan Jeffrey, <em>Goals of the Luau Type System</em>, Human Aspects of Types and Reasoning Assistants (HATRA), 2021. <a href="https://arxiv.org/abs/2109.11397">https://arxiv.org/abs/2109.11397</a></li>
<li>Luau Typechecker Prototype. <a href="https://github.com/luau-lang/agda-typeck">https://github.com/luau-lang/agda-typeck</a></li>
<li>Agda. <a href="https://agda.readthedocs.io/">https://agda.readthedocs.io/</a></li>
<li>Andrew M. Kent. <em>Down and Dirty with Semantic Set-theoretic Types</em>, 2021. <a href="https://pnwamk.github.io/sst-tutorial/">https://pnwamk.github.io/sst-tutorial/</a></li>
<li>Giuseppe Castagna, <em>Covariance and Contravariance</em>, Logical Methods in Computer Science 16(1), 2022. <a href="https://arxiv.org/abs/1809.01427">https://arxiv.org/abs/1809.01427</a></li>
<li>Giuseppe Castagna and Alain Frisch, <em>A gentle introduction to semantic subtyping</em>, Proc. Principles and practice of declarative programming (PPDP), pp 198–208, 2005. <a href="https://doi.org/10.1145/1069774.1069793">https://doi.org/10.1145/1069774.1069793</a></li>
<li>Giuseppe Castagna, Mickaël Laurent, Kim Nguyễn, Matthew Lutze, <em>On Type-Cases, Union Elimination, and Occurrence Typing</em>, Principles of Programming Languages (POPL), 2022. <a href="https://doi.org/10.1145/3498674">https://doi.org/10.1145/3498674</a></li>
<li>Giuseppe Castagna, <em>Programming with union, intersection, and negation types</em>, 2022. <a href="https://arxiv.org/abs/2111.03354">https://arxiv.org/abs/2111.03354</a></li>
<li>Sam Tobin-Hochstadt and Matthias Felleisen, <em>Logical types for untyped languages</em>. International Conference on Functional Programming (ICFP), 2010. <a href="https://doi.org/10.1145/1863543.1863561">https://doi.org/10.1145/1863543.1863561</a></li>
<li>José Valim, <em>My Future with Elixir: set-theoretic types</em>, 2022. <a href="https://elixir-lang.org/blog/2022/10/05/my-future-with-elixir-set-theoretic-types/">https://elixir-lang.org/blog/2022/10/05/my-future-with-elixir-set-theoretic-types/</a></li>
</ul>
<p>Some other languages which support semantic subtyping…</p>
<ul>
<li>ℂDuce <a href="https://www.cduce.org/">https://www.cduce.org/</a></li>
<li>Ballerina <a href="https://ballerina.io">https://ballerina.io</a></li>
<li>Elixir <a href="https://elixir-lang.org/">https://elixir-lang.org/</a></li>
<li>eqWAlizer <a href="https://github.com/WhatsApp/eqwalizer">https://github.com/WhatsApp/eqwalizer</a></li>
</ul>
<p>And if you want to see the production code, it’s in the C++ definitions of <a href="https://github.com/Roblox/luau/blob/d6aa35583e4be14304d2a17c7d11c8819756beb6/Analysis/src/Unifier.cpp#L868">tryUnifyNormalizedTypes</a> and <a href="https://github.com/Roblox/luau/blob/d6aa35583e4be14304d2a17c7d11c8819756beb6/Analysis/include/Luau/Normalize.h#L134">NormalizedType</a> in the <a href="https://github.com/Roblox/luau">open source Luau repo</a>.</p>Alan JeffreyLuau is the first programming language to put the power of semantic subtyping in the hands of millions of creators.Luau Recap: July & August 20222022-08-29T00:00:00+00:002022-08-29T00:00:00+00:00https://luau-lang.org/2022/08/29/luau-recap-august-2022<p>Luau is our new language that you can read more about at <a href="https://luau-lang.org">https://luau-lang.org</a>.</p>
<p>[Cross-posted to the <a href="https://devforum.roblox.com/t/luau-recap-july-august-2022/">Roblox Developer Forum</a>.]</p>
<h2 id="tables-now-support-__len-metamethod">Tables now support <code class="language-plaintext highlighter-rouge">__len</code> metamethod</h2>
<p>See the RFC <a href="https://github.com/Roblox/luau/blob/master/rfcs/len-metamethod-rawlen.md">Support <code class="language-plaintext highlighter-rouge">__len</code> metamethod for tables and <code class="language-plaintext highlighter-rouge">rawlen</code> function</a> for more details.</p>
<p>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 <code class="language-plaintext highlighter-rouge">__len</code>.</p>
<p>Simply, tables now honor the <code class="language-plaintext highlighter-rouge">__len</code> metamethod, and <code class="language-plaintext highlighter-rouge">rawlen</code> is also added with similar semantics as <code class="language-plaintext highlighter-rouge">rawget</code> and <code class="language-plaintext highlighter-rouge">rawset</code>:</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">local</span> <span class="n">my_cool_container</span> <span class="o">=</span> <span class="nb">setmetatable</span><span class="p">({</span> <span class="n">items</span> <span class="o">=</span> <span class="p">{</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span> <span class="p">}</span> <span class="p">},</span> <span class="p">{</span>
<span class="n">__len</span> <span class="o">=</span> <span class="k">function</span><span class="p">(</span><span class="n">self</span><span class="p">)</span> <span class="k">return</span> <span class="o">#</span><span class="n">self</span><span class="p">.</span><span class="n">items</span> <span class="k">end</span>
<span class="p">})</span>
<span class="nb">print</span><span class="p">(</span><span class="o">#</span><span class="n">my_cool_container</span><span class="p">)</span> <span class="c1">--> 2</span>
<span class="nb">print</span><span class="p">(</span><span class="nb">rawlen</span><span class="p">(</span><span class="n">my_cool_container</span><span class="p">))</span> <span class="c1">--> 0</span>
</code></pre></div></div>
<h2 id="never-and-unknown-types"><code class="language-plaintext highlighter-rouge">never</code> and <code class="language-plaintext highlighter-rouge">unknown</code> types</h2>
<p>See the RFC <a href="https://github.com/Roblox/luau/blob/master/rfcs/never-and-unknown-types.md"><code class="language-plaintext highlighter-rouge">never</code> and <code class="language-plaintext highlighter-rouge">unknown</code> types</a> for more details.</p>
<p>We’ve added two new types, <code class="language-plaintext highlighter-rouge">never</code> and <code class="language-plaintext highlighter-rouge">unknown</code>. These two types are the opposites of each other by the fact that there’s no value that inhabits the type <code class="language-plaintext highlighter-rouge">never</code>, and the dual of that is every value inhabits the type <code class="language-plaintext highlighter-rouge">unknown</code>.</p>
<p>Type inference may infer a variable to have the type <code class="language-plaintext highlighter-rouge">never</code> if and only if the set of possible types becomes empty, for example through type refinements.</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">function</span> <span class="nf">f</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="n">string</span> <span class="err">|</span> <span class="n">number</span><span class="p">)</span>
<span class="k">if</span> <span class="n">typeof</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="o">==</span> <span class="s2">"string"</span> <span class="ow">and</span> <span class="n">typeof</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="o">==</span> <span class="s2">"number"</span> <span class="k">then</span>
<span class="c1">-- x: never</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>This is useful because we still needed to ascribe a type to <code class="language-plaintext highlighter-rouge">x</code> here, but the type we used previously had unsound semantics. For example, it was possible to be able to <em>expand</em> the domain of a variable once the user had proved it impossible. With <code class="language-plaintext highlighter-rouge">never</code>, narrowing a type from <code class="language-plaintext highlighter-rouge">never</code> yields <code class="language-plaintext highlighter-rouge">never</code>.</p>
<p>Conversely, <code class="language-plaintext highlighter-rouge">unknown</code> can be used to enforce a stronger contract than <code class="language-plaintext highlighter-rouge">any</code>. That is, <code class="language-plaintext highlighter-rouge">unknown</code> and <code class="language-plaintext highlighter-rouge">any</code> are similar in terms of allowing every type to inhabit them, and other than <code class="language-plaintext highlighter-rouge">unknown</code> or <code class="language-plaintext highlighter-rouge">any</code>, <code class="language-plaintext highlighter-rouge">any</code> allows itself to inhabit into a different type, whereas <code class="language-plaintext highlighter-rouge">unknown</code> does not.</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">function</span> <span class="nf">any</span><span class="p">():</span> <span class="n">any</span> <span class="k">return</span> <span class="mi">5</span> <span class="k">end</span>
<span class="k">function</span> <span class="nf">unknown</span><span class="p">():</span> <span class="n">unknown</span> <span class="k">return</span> <span class="mi">5</span> <span class="k">end</span>
<span class="c1">-- no type error, but assigns a number to x which expects string</span>
<span class="kd">local</span> <span class="n">x</span><span class="p">:</span> <span class="n">string</span> <span class="o">=</span> <span class="n">any</span><span class="p">()</span>
<span class="c1">-- has type error, unknown cannot be converted into string</span>
<span class="kd">local</span> <span class="n">y</span><span class="p">:</span> <span class="n">string</span> <span class="o">=</span> <span class="n">unknown</span><span class="p">()</span>
</code></pre></div></div>
<p>To be able to do this soundly, you must apply type refinements on a variable of type <code class="language-plaintext highlighter-rouge">unknown</code>.</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">local</span> <span class="n">u</span> <span class="o">=</span> <span class="n">unknown</span><span class="p">()</span>
<span class="k">if</span> <span class="n">typeof</span><span class="p">(</span><span class="n">u</span><span class="p">)</span> <span class="o">==</span> <span class="s2">"string"</span> <span class="k">then</span>
<span class="kd">local</span> <span class="n">y</span><span class="p">:</span> <span class="n">string</span> <span class="o">=</span> <span class="n">u</span> <span class="c1">-- no type error</span>
<span class="k">end</span>
</code></pre></div></div>
<p>A use case of <code class="language-plaintext highlighter-rouge">unknown</code> is to enforce type safety at implementation sites for data that do not originate in code, but from over the wire.</p>
<h2 id="argument-names-in-type-packs-when-instantiating-a-type">Argument names in type packs when instantiating a type</h2>
<p>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.</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Foo</span><span class="o"><</span><span class="p">(</span><span class="n">a</span><span class="p">:</span> <span class="n">number</span><span class="p">,</span> <span class="n">b</span><span class="p">:</span> <span class="n">string</span><span class="p">)</span><span class="o">></span>
</code></pre></div></div>
<h2 id="new-integerparsing-lint">New IntegerParsing lint</h2>
<p>See <a href="https://devforum.roblox.com/t/improving-binary-and-hexadecimal-integer-literal-parsing-rules-in-luau/">the announcement</a> for more details. We include this here for posterity.</p>
<p>We’ve introduced a new lint called IntegerParsing. Right now, it lints three classes of errors:</p>
<ol>
<li>Truncation of binary literals that resolves to a value over 64 bits,</li>
<li>Truncation of hexadecimal literals that resolves to a value over 64 bits, and</li>
<li>Double hexadecimal prefix.</li>
</ol>
<p>For 1.) and 2.), they are currently not planned to become a parse error, so action is not strictly required here.</p>
<p>For 3.), this will be a breaking change! See <a href="https://devforum.roblox.com/t/improving-binary-and-hexadecimal-integer-literal-parsing-rules-in-luau/#rollout-5">the rollout plan</a> for details.</p>
<h2 id="new-comparisonprecedence-lint">New ComparisonPrecedence lint</h2>
<p>We’ve also introduced a new lint called <code class="language-plaintext highlighter-rouge">ComparisonPrecedence</code>. It fires in two particular cases:</p>
<ol>
<li><code class="language-plaintext highlighter-rouge">not X op Y</code> where <code class="language-plaintext highlighter-rouge">op</code> is <code class="language-plaintext highlighter-rouge">==</code> or <code class="language-plaintext highlighter-rouge">~=</code>, or</li>
<li><code class="language-plaintext highlighter-rouge">X op Y op Z</code> where <code class="language-plaintext highlighter-rouge">op</code> is any of the comparison or equality operators.</li>
</ol>
<p>In languages that uses <code class="language-plaintext highlighter-rouge">!</code> to negate the boolean i.e. <code class="language-plaintext highlighter-rouge">!x == y</code> looks fine because <code class="language-plaintext highlighter-rouge">!x</code> <em>visually</em> binds more tightly than Lua’s equivalent, <code class="language-plaintext highlighter-rouge">not x</code>. Unfortunately, the precedences here are identical, that is <code class="language-plaintext highlighter-rouge">!x == y</code> is <code class="language-plaintext highlighter-rouge">(!x) == y</code> in the same way that <code class="language-plaintext highlighter-rouge">not x == y</code> is <code class="language-plaintext highlighter-rouge">(not x) == y</code>. We also apply this on other operators e.g. <code class="language-plaintext highlighter-rouge">x <= y == y</code>.</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">-- not X == Y is equivalent to (not X) == Y; consider using X ~= Y, or wrap one of the expressions in parentheses to silence</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">x</span> <span class="o">==</span> <span class="n">y</span> <span class="k">then</span> <span class="k">end</span>
<span class="c1">-- not X ~= Y is equivalent to (not X) ~= Y; consider using X == Y, or wrap one of the expressions in parentheses to silence</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">x</span> <span class="o">~=</span> <span class="n">y</span> <span class="k">then</span> <span class="k">end</span>
<span class="c1">-- not X <= Y is equivalent to (not X) <= Y; wrap one of the expressions in parentheses to silence</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">x</span> <span class="o"><=</span> <span class="n">y</span> <span class="k">then</span> <span class="k">end</span>
<span class="c1">-- X <= Y == Z is equivalent to (X <= Y) == Z; wrap one of the expressions in parentheses to silence</span>
<span class="k">if</span> <span class="n">x</span> <span class="o"><=</span> <span class="n">y</span> <span class="o">==</span> <span class="mi">0</span> <span class="k">then</span> <span class="k">end</span>
</code></pre></div></div>
<p>As a special exception, this lint pass will not warn for cases like <code class="language-plaintext highlighter-rouge">x == not y</code> or <code class="language-plaintext highlighter-rouge">not x == not y</code>, which both looks intentional as it is written and interpreted.</p>
<h2 id="function-calls-returning-singleton-types-incorrectly-widened">Function calls returning singleton types incorrectly widened</h2>
<p>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.</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">function</span> <span class="nf">f</span><span class="p">():</span> <span class="s2">"abc"</span> <span class="err">|</span> <span class="s2">"def"</span>
<span class="k">return</span> <span class="k">if</span> <span class="nb">math.random</span><span class="p">()</span> <span class="o">></span> <span class="mi">0</span><span class="p">.</span><span class="mi">5</span> <span class="k">then</span> <span class="s2">"abc"</span> <span class="k">else</span> <span class="s2">"def"</span>
<span class="k">end</span>
<span class="c1">-- previously reported that 'string' could not be converted into '"abc" | "def"'</span>
<span class="kd">local</span> <span class="n">x</span><span class="p">:</span> <span class="s2">"abc"</span> <span class="err">|</span> <span class="s2">"def"</span> <span class="o">=</span> <span class="n">f</span><span class="p">()</span>
</code></pre></div></div>
<h2 id="string-can-be-a-subtype-of-a-table-with-a-shape-similar-to-string"><code class="language-plaintext highlighter-rouge">string</code> can be a subtype of a table with a shape similar to <code class="language-plaintext highlighter-rouge">string</code></h2>
<p>The function <code class="language-plaintext highlighter-rouge">my_cool_lower</code> is a function <code class="language-plaintext highlighter-rouge"><a...>(t: t1) -> a... where t1 = {+ lower: (t1) -> a... +}</code>.</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">function</span> <span class="nf">my_cool_lower</span><span class="p">(</span><span class="n">t</span><span class="p">)</span>
<span class="k">return</span> <span class="n">t</span><span class="p">:</span><span class="n">lower</span><span class="p">()</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Even though <code class="language-plaintext highlighter-rouge">t1</code> is a table type, we know <code class="language-plaintext highlighter-rouge">string</code> is a subtype of <code class="language-plaintext highlighter-rouge">t1</code> because <code class="language-plaintext highlighter-rouge">string</code> also has <code class="language-plaintext highlighter-rouge">lower</code> which is a subtype of <code class="language-plaintext highlighter-rouge">t1</code>’s <code class="language-plaintext highlighter-rouge">lower</code>, so this call site now type checks.</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">local</span> <span class="n">s</span><span class="p">:</span> <span class="n">string</span> <span class="o">=</span> <span class="n">my_cool_lower</span><span class="p">(</span><span class="s2">"HI"</span><span class="p">)</span>
</code></pre></div></div>
<h2 id="other-analysis-improvements">Other analysis improvements</h2>
<ul>
<li><code class="language-plaintext highlighter-rouge">string.gmatch</code>/<code class="language-plaintext highlighter-rouge">string.match</code>/<code class="language-plaintext highlighter-rouge">string.find</code> may now return more precise type depending on the patterns used</li>
<li>Fix a bug where type arena ownership invariant could be violated, causing stability issues</li>
<li>Fix a bug where internal type error could be presented to the user</li>
<li>Fix a false positive with optionals & nested tables</li>
<li>Fix a false positive in non-strict mode when using generalized iteration</li>
<li>Improve autocomplete behavior in certain cases for <code class="language-plaintext highlighter-rouge">:</code> calls</li>
<li>Fix minor inconsistencies in synthesized names for types with metatables</li>
<li>Fix autocomplete not suggesting globals defined after the cursor</li>
<li>Fix DeprecatedGlobal warning text in cases when the global is deprecated without a suggested alternative</li>
<li>Fix an off-by-one error in type error text for incorrect use of <code class="language-plaintext highlighter-rouge">string.format</code></li>
</ul>
<h2 id="other-runtime-improvements">Other runtime improvements</h2>
<ul>
<li>Comparisons with constants are now significantly faster when using clang as a compiler (10-50% gains on internal benchmarks)</li>
<li>When calling non-existent methods on tables or strings, <code class="language-plaintext highlighter-rouge">foo:bar</code> now produces a more precise error message</li>
<li>Improve performance for iteration of tables</li>
<li>Fix a bug with negative zero in vector components when using vectors as table keys</li>
<li>Compiler can now constant fold builtins under -O2, for example <code class="language-plaintext highlighter-rouge">string.byte("A")</code> is compiled to a constant</li>
<li>Compiler can model the cost of builtins for the purpose of inlining/unrolling</li>
<li>Local reassignment i.e. <code class="language-plaintext highlighter-rouge">local x = y :: T</code> is free iff neither <code class="language-plaintext highlighter-rouge">x</code> nor <code class="language-plaintext highlighter-rouge">y</code> is mutated/captured</li>
<li>Improve <code class="language-plaintext highlighter-rouge">debug.traceback</code> performance by 1.15-1.75x depending on the platform</li>
<li>Fix a corner case with table assignment semantics when key didn’t exist in the table and <code class="language-plaintext highlighter-rouge">__newindex</code> was defined: we now use Lua 5.2 semantics and call <code class="language-plaintext highlighter-rouge">__newindex</code>, which results in less wasted space, support for NaN keys in <code class="language-plaintext highlighter-rouge">__newindex</code> path and correct support for frozen tables</li>
<li>Reduce parser C stack consumption which fixes some stack overflow crashes on deeply nested sources</li>
<li>Improve performance of <code class="language-plaintext highlighter-rouge">bit32.extract</code>/<code class="language-plaintext highlighter-rouge">replace</code> when width is implied (~3% faster chess)</li>
<li>Improve performance of <code class="language-plaintext highlighter-rouge">bit32.extract</code> when field/width are constants (~10% faster base64)</li>
<li><code class="language-plaintext highlighter-rouge">string.format</code> now supports a new format specifier, <code class="language-plaintext highlighter-rouge">%*</code>, that accepts any value type and formats it using <code class="language-plaintext highlighter-rouge">tostring</code> rules</li>
</ul>
<h2 id="thanks">Thanks</h2>
<p>Thanks for all the contributions!</p>
<ul>
<li><a href="https://github.com/natteko">natteko</a></li>
<li><a href="https://github.com/JohnnyMorganz">JohnnyMorganz</a></li>
<li><a href="https://github.com/khvzak">khvzak</a></li>
<li><a href="https://github.com/Anaminus">Anaminus</a></li>
<li><a href="https://github.com/memery-rbx">memery-rbx</a></li>
<li><a href="https://github.com/jaykru">jaykru</a></li>
<li><a href="https://github.com/Kampfkarren">Kampfkarren</a></li>
<li><a href="https://github.com/XmiliaH">XmiliaH</a></li>
<li><a href="https://github.com/Mactavsin">Mactavsin</a></li>
</ul>Luau is our new language that you can read more about at https://luau-lang.org.Luau Recap: June 20222022-07-07T00:00:00+00:002022-07-07T00:00:00+00:00https://luau-lang.org/2022/07/07/luau-recap-june-2022<p>Luau is our new language that you can read more about at <a href="https://luau-lang.org">https://luau-lang.org</a>.</p>
<p>[Cross-posted to the <a href="https://devforum.roblox.com/t/luau-recap-june-2022/">Roblox Developer Forum</a>.]</p>
<h1 id="lower-bounds-calculation">Lower bounds calculation</h1>
<p>A common problem that Luau has is that it primarily works by inspecting expressions in your program and narrowing the <em>upper bounds</em> of the values that can inhabit particular variables. In other words, each time we see a variable used, we eliminate possible sets of values from that variable’s domain.</p>
<p>There are some important cases where this doesn’t produce a helpful result. Take this function for instance:</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">function</span> <span class="nf">find_first_if</span><span class="p">(</span><span class="n">vec</span><span class="p">,</span> <span class="n">f</span><span class="p">)</span>
<span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">e</span> <span class="k">in</span> <span class="nb">ipairs</span><span class="p">(</span><span class="n">vec</span><span class="p">)</span> <span class="k">do</span>
<span class="k">if</span> <span class="n">f</span><span class="p">(</span><span class="n">e</span><span class="p">)</span> <span class="k">then</span>
<span class="k">return</span> <span class="n">i</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">return</span> <span class="kc">nil</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Luau scans the function from top to bottom and first sees the line <code class="language-plaintext highlighter-rouge">return i</code>. It draws from this the inference that <code class="language-plaintext highlighter-rouge">find_first_if</code> must return the type of <code class="language-plaintext highlighter-rouge">i</code>, namely <code class="language-plaintext highlighter-rouge">number</code>.</p>
<p>This is fine, but things go sour when we see the line <code class="language-plaintext highlighter-rouge">return nil</code>. Since we are always narrowing, we take from this line the judgement that the return type of the function is <code class="language-plaintext highlighter-rouge">nil</code>. Since we have already concluded that the function must return <code class="language-plaintext highlighter-rouge">number</code>, Luau reports an error.</p>
<p>What we actually want to do in this case is to take these <code class="language-plaintext highlighter-rouge">return</code> statements as inferences about the <em>lower</em> bound of the function’s return type. Instead of saying “this function must return values of type <code class="language-plaintext highlighter-rouge">nil</code>,” we should instead say “this function may <em>also</em> return values of type <code class="language-plaintext highlighter-rouge">nil</code>.”</p>
<p>Lower bounds calculation does precisely this. Moving forward, Luau will instead infer the type <code class="language-plaintext highlighter-rouge">number?</code> for the above function.</p>
<p>This does have one unfortunate consequence: If a function has no return type annotation, we will no longer ever report a type error on a <code class="language-plaintext highlighter-rouge">return</code> statement. We think this is the right balance but we’ll be keeping an eye on things just to be sure.</p>
<p>Lower-bounds calculation is larger and a little bit riskier than other things we’ve been working on so we’ve set up a beta feature in Roblox Studio to enable them. It is called “Experimental Luau language features.”</p>
<p>Please try it out and let us know what you think!</p>
<h2 id="known-bug">Known bug</h2>
<p>We have a known bug with certain kinds of cyclic types when lower-bounds calculation is enabled. The following, for instance, is known to be problematic.</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">type</span> <span class="n">T</span> <span class="o">=</span> <span class="p">{</span><span class="n">T</span><span class="err">?</span><span class="p">}</span><span class="err">?</span> <span class="c1">-- spuriously reduces to {nil}?</span>
</code></pre></div></div>
<p>We hope to have this fixed soon.</p>
<h1 id="all-table-literals-now-result-in-unsealed-tables">All table literals now result in unsealed tables</h1>
<p>Previously, the only way to create a sealed table was by with a literal empty table. We have relaxed this somewhat: Any table created by a <code class="language-plaintext highlighter-rouge">{}</code> expression is considered to be unsealed within the scope where it was created:</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">local</span> <span class="n">T</span> <span class="o">=</span> <span class="p">{}</span>
<span class="n">T</span><span class="p">.</span><span class="n">x</span> <span class="o">=</span> <span class="mi">5</span> <span class="c1">-- OK</span>
<span class="kd">local</span> <span class="n">V</span> <span class="o">=</span> <span class="p">{</span><span class="n">x</span><span class="o">=</span><span class="mi">5</span><span class="p">}</span>
<span class="n">V</span><span class="p">.</span><span class="n">y</span> <span class="o">=</span> <span class="mi">2</span> <span class="c1">-- previously disallowed. Now OK.</span>
<span class="k">function</span> <span class="nf">mkTable</span><span class="p">()</span>
<span class="k">return</span> <span class="p">{</span><span class="n">x</span> <span class="o">=</span> <span class="mi">5</span><span class="p">}</span>
<span class="k">end</span>
<span class="kd">local</span> <span class="n">U</span> <span class="o">=</span> <span class="n">mkTable</span><span class="p">()</span>
<span class="n">U</span><span class="p">.</span><span class="n">y</span> <span class="o">=</span> <span class="mi">2</span> <span class="c1">-- Still disallowed: U is sealed</span>
</code></pre></div></div>
<h1 id="other-fixes">Other fixes</h1>
<ul>
<li>Adjust indentation and whitespace when creating multiline string representations of types, resulting in types that are easier to read.</li>
<li>Some small bugfixes to autocomplete</li>
<li>Fix a case where accessing a nonexistent property of a table would not result in an error being reported.</li>
<li>Improve parser recovery for the incorrect code <code class="language-plaintext highlighter-rouge">function foo() -> ReturnType</code> (the correct syntax is <code class="language-plaintext highlighter-rouge">function foo(): ReturnType</code>)</li>
<li>Improve the parse error offered for code that improperly uses the <code class="language-plaintext highlighter-rouge">function</code> keyword to start a type eg <code class="language-plaintext highlighter-rouge">type T = function</code></li>
<li>Some small crash fixes and performance improvements</li>
</ul>
<h1 id="thanks">Thanks!</h1>
<p>A very special thanks to all of our open source contributors:</p>
<ul>
<li><a href="https://github.com/AllanJeremy">Allan N Jeremy</a></li>
<li><a href="https://github.com/danielnachun">Daniel Nachun</a></li>
<li><a href="https://github.com/JohnnyMorganz/">JohnnyMorganz</a></li>
<li><a href="https://github.com/petrihakkinen">Petri Häkkinen</a></li>
<li><a href="https://github.com/Qualadore">Qualadore</a></li>
</ul>Luau is our new language that you can read more about at https://luau-lang.org.