Reproduction Steps
Create a script with the following code:
--!strict
--[[
Custom format function: Captures {placeholders} for more-readable
placeholders for translators, and utf-8 support.
]]
local function localize_format(pat: string, values: {any})
local inCapture = false
local inEscape = false
local graphs = table.create(utf8.len(pat) :: number)
local omittedGraphs = {}
local currentValueIndex = 1
for s, e in utf8.graphemes(pat) do
local graph = pat:sub(s, e)
if graph == "\\" then
if inEscape then
inEscape = false
table.insert(graphs, graph)
else
inEscape = true
end
elseif inCapture then
if inEscape then
inEscape = false
table.insert(omittedGraphs, graph)
else
if graph == "}" then
inCapture = false
table.insert(graphs, tostring(values[currentValueIndex] or ""))
currentValueIndex = currentValueIndex + 1
elseif graph == "{" then
for i = 1, #omittedGraphs do
table.insert(graphs, omittedGraphs[i])
end
omittedGraphs = {}
else
table.insert(omittedGraphs, graph)
end
end
elseif (graph == "{" and not inEscape) then
inCapture = true
omittedGraphs = {}
table.insert(omittedGraphs, graph)
else
if inEscape then
inEscape = false
end
table.insert(graphs, graph)
end
end
return table.concat(graphs)
end
This code formats placeholders in (valid) utf-8 strings with a table of values. Now it emits the following warnings that weren’t there before:
Expected Behavior
The function used to emit no warnings and typed the “graphs” variable as a table of strings ({string}
).
Actual Behavior
My graphs
variable is inferred as a table of a literal string in this case.
What seems to be happening is
- The line
local graphs = table.create(utf8.len(pat) :: number)
initializes “graphs” as a table with an open type. - The line
local graph = pat:sub(s, e)
creates a variable (“graph”) with a typestring
- The line
if graph == "\\" then
refine’sgraph
's type as being a literal string"\\"
- The line
table.insert(graphs, graph)
, seeing that graph is of type “\”, then infers “graphs” to be of type{"\\"}
This is kind of absurd and any reasonable person would infer that graphs
should be typed as {string}
. Typically people use literal string types when defining their own types, but when inferring types it doesn’t really make sense to use literal string types here. Instead, even though graph
is refined as a literal string type, inserting it into graphs
should still generalize it as a string.
Workaround
Add a type annotation to the local graphs = table.create(utf8.len(pat) :: number)
line, adjusting it to:
local graphs: {string} = table.create(utf8.len(pat) :: number)
Issue Area: Studio
Issue Type: Other
Impact: Moderate
Frequency: Sometimes
Date First Experienced: 2022-02-17 00:02:00 (-07:00)
Date Last Experienced: 2022-02-17 00:02:00 (-07:00)