Luau infer's my table's type... as a table of literal strings?!?!

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

  1. The line local graphs = table.create(utf8.len(pat) :: number) initializes “graphs” as a table with an open type.
  2. The line local graph = pat:sub(s, e) creates a variable (“graph”) with a type string
  3. The line if graph == "\\" then refine’s graph's type as being a literal string "\\"
  4. 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)

1 Like

Thank you for the report.

We do not plan to infer literal types at this stage, but a few places where it happens as a side-effect still got into the initial release of this feature.
We have fixes ready for this issue for a future release.
In the meantime, we plan to disable reporting of these errors involving literal types.

4 Likes