luau icon indicating copy to clipboard operation
luau copied to clipboard

Conditionals with separate returns is failing to infer a union in the New Type Solver

Open PhoenixWhitefire opened this issue 3 months ago • 5 comments

(This issue was rewritten after the discussion below to capture the actual bug being reported.)

The following code does not typecheck or infer correctly under the New Type Solver:

--!strict

local function choice(a, b, c)
	if a then
		return b
	else
		return c
	end
end

local b = choice(true, "Hello", 42)
local c = choice(false, "Hello", 42)

print(b, c)

For inference, this produces choice : <a, b>(a: a, b: b, c: b) -> b which is a reasonable type since b can be instantiated with a union here, but automatic instantiation fails to do this correctly (likely owing to changes to bidirectional type inference) and so you instead get an error on the two calls:

local b = choice(true, "Hello", 42) -- number is not string
local c = choice(false, "Hello", 42) -- number is not string

For typechecking, if we annotate the arguments explicitly, we also fail to infer the union type for the function overall, e.g.

--!strict

local function choice(a, b: string, c: number) 
	if a then
		return b
	else
		return c -- number is not string
	end
end

local b = choice(true, "Hello", 42) 
local c = choice(false, "Hello", 42)

print(b, c)

We would expect that choice infers to have a return type of string | number and the function calls succeed.

PhoenixWhitefire avatar Oct 12 '25 12:10 PhoenixWhitefire

the generic type a can be instantiated with a union, so <a>(unknown, a, a) -> a could very well be instantiated to be (unknown, string | number, string | number) -> string | number. is there some actual need you have for inference to produce two separate generics with a union for the result here? if so, you should substantiate that in this issue, especially given that you can clearly annotate the program to have the exact desired signature.

aatxe avatar Oct 12 '25 19:10 aatxe

the generic type a can be instantiated with a union, so <a>(unknown, a, a) -> a could very well be instantiated to be (unknown, string | number, string | number) -> string | number. is there some actual need you have for inference to produce two separate generics with a union for the result here? if so, you should substantiate that in this issue, especially given that you can clearly annotate the program to have the exact desired signature.

My mistake, the "main point" of this Issue doesn't make any sense.

However, is the type error in the last snippet intentional? The return value has been inferred as string and not string | number.

--!strict

local function choice(a, b: string, c: number)
	if a then
		return b
	else
		return c -- "Type 'number' could not be converted into 'string'"
	end
end

local b = choice(true, "Hello", 42)
local c = choice(false, "Hello", 42)

print(b, c)

PhoenixWhitefire avatar Oct 13 '25 11:10 PhoenixWhitefire

If you're using the old type solver (which it seems like you are), it's expected, but undesirable. The video you linked with zeux is talking about what we have been building in the new type solver which does support inferring the result type as b | c here.

aatxe avatar Oct 13 '25 17:10 aatxe

If you're using the old type solver (which it seems like you are), it's expected, but undesirable. The video you linked with zeux is talking about what we have been building in the new type solver which does support inferring the result type as b | c here.

I checked Luau LSP, the luau-analyze tool (with --fflags=LuauSolverV2) and Studio. It looks to me like the issue is occurring in the New Solver

Image Image Image Image

Additional snippet using type functions but without the New Solver enabled to show that the switch was actually having an effect:

Image

PhoenixWhitefire avatar Oct 14 '25 17:10 PhoenixWhitefire

That is a bug then, I'll adjust your issue description.

aatxe avatar Oct 14 '25 19:10 aatxe