Avoid a redundant MOVE on some string interpolation setups#2324
Merged
Conversation
This keeps the generated bytecode of string interpolation in line with the result of writing the interpolation manually (e.g. `("%*"):format(xyz)`)
Also means string interpolation may sometimes be faster and use less bytecode, though the improvement is minimal in practice
tommyscholly
requested changes
Apr 6, 2026
Contributor
tommyscholly
left a comment
There was a problem hiding this comment.
This looks good, great catch on the redundant move. Just need the changes to be flagged.
Contributor
Author
|
(Merged with master to resolve a conflict in Compiler, hopefully it isn't too much of a mess git-wise) |
SPY
reviewed
Apr 7, 2026
vegorov-rbx
approved these changes
Apr 9, 2026
tommyscholly
approved these changes
Apr 9, 2026
Collaborator
|
Thank you! |
aatxe
added a commit
that referenced
this pull request
May 8, 2026
Howdy there, folks! We've got another Luau release this week, mainly focused on some pain points with type analysis! **Note**: For folks who are using definition files for providing type definitions for the runtime environment they're working in, we also wanted to highlight that the `declare class` syntax is being entirely cleaned up finally. We added syntax for `declare extern type` many months ago, and with the [Luau classes RFC](https://rfcs.luau.org/syntax-classes.html) accepted, it is particularly prudent that we finalize the cleanup of `declare class`. If you're still on the old syntax, please update to use `declare extern type Foo with ...` to avoid interruption when a future release removes `declare class` entirely. ## Language * The `const` keyword now correctly appears in autocomplete suggestions from Luau. ## Analysis - Fixes false positive `OptionalValueAccess` errors when iterating over a table with an optional indexer type. The VM's generalized iteration already guarantees non-nil values in the loop body, and the type checker now reflects that. Fixes [#2236](#2236). ```luau --!strict type TypeA = { Value: any } local list = {} :: { [string]: TypeA? } for index, a in list do a.Value = 1 -- No longer incorrectly reported as 'TypeA?' could be nil end ``` - Improves bidirectional type inference for unions of tables and functions. Table literals that clearly match one branch of a union are no longer falsely rejected: ```luau --!strict type FnRecord = { handler: (number) -> string, label: string? } type StrRecord = { handler: string, label: string? } type Record = FnRecord | StrRecord -- Previously flagged as not a subtype of `Record`; now correctly accepted as a FnRecord local r: Record = { handler = function(input) return tostring(input) end, label = "test", } ``` - Fixes a crash that could occur when `typeof` is used inside the type arguments of an instantiated method call: ```luau local t = {} function t:f<T>() end local x = 42 t:f<<typeof(x)>>() -- No longer crashes ``` - Fixes missing autocomplete suggestions for string singleton types when the expected type is an intersection containing a string singleton (e.g. `"Foo" & "Foo"` or `keyof<typeof(tbl)> & T`). ## Compiler - Fixes a discrepancy where string interpolation would sometimes emit a redundant `MOVE` instruction that an equivalent `string.format` call would not, when the target register was already correct. (#2324 from @9382, thanks!) ## Internal Contributors Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Annie Tang <annietang@roblox.com> Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com> Co-authored-by: Thomas Schollenberger <tschollenberger@roblox.com> Co-authored-by: Varun Saini <vsaini@roblox.com> Co-authored-by: Vighnesh Vijay <vvijay@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> --------- Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com> Co-authored-by: Varun Saini <61795485+vrn-sn@users.noreply.github.com> Co-authored-by: Sora Kanosue <skanosue@roblox.com> Co-authored-by: Annie Tang <annietang@roblox.com> Co-authored-by: Annie Tang <98965493+annieetang@users.noreply.github.com> Co-authored-by: Ilya Rezvov <irezvov@roblox.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This fixes a discrepancy between string interpolation (
local a = `{xyz}`) and the manually written equivilant version (local a = ("%*"):format(xyz)), where string interpolation would sometimes introduce aMOVEeven when it didn't need to as the target register was marked as temporaryBytecode difference
Code:
local f = `{global}`Before:
After:
This is also technically a performance improvement, though the difference is mostly unnoticeable in normal contexts (I'm seeing a 4-5% improvement on a 1e7 loop which is nothing but the optimisable string interp, so about as good as the case gets)
I've not written a new test case as
InterpStringRegisterCleanupalready covers the unoptimisable case