New Type Solver: Table types with union fields declared using 'setmetatable<T, U>' gives type error at instance creation

The type works if it describes only a simple table, but not completely if declaring it involves the use of the ‘setmetatable<T, U>’ type function as described below:

  • If the union field of the table is either an instance of the union, it throws an error:
--!strict

local Class = {}
Class.__index = Class


export type ClassType = setmetatable<{
	foo : boolean | number
}, typeof(Class)>

local function newClass(): ClassType
	return setmetatable({
		foo = true
	}, Class) --TypeError: Has the 2nd component of the union as `number`, and `boolean` is not exactly `number`
end
  • vice versa:
return setmetatable({
	foo = 3.14
}, Class) --TypeError: Has the 1st component of the union as `boolean`, and `number` is not exactly `boolean`
  • But if the union has a singleton, it works if the value’s type is the said singleton:
export type ClassType = setmetatable<{
	foo : "hello" | number
}, typeof(Class)>

local function newClass(): ClassType
	return setmetatable({
		foo = "hello"
	}, Class) --works as expected
end
  • and throws an error if it’s not:
return setmetatable({
	foo = -77
}, Class) --TypeError: Has the 1st component of the union as `"hello"`, and `number` is not exactly `"hello"`

Hi there, thanks for the report. This is a limitation of how bidirectional type inference functions, you can work around it safely by casting your foo field’s value to the appropriate type, but we are working actively on improving bidirectional typing to address situations like this. Someone will follow up when there is a fix for this particular situation involving setmetatable.

Workaround:

--!strict

local Class = {}
Class.__index = Class

export type ClassType = setmetatable<{
	foo : boolean | number
}, typeof(Class)>

local function newClass(): ClassType
	return setmetatable({
		foo = true :: boolean | number
	}, Class)
end