Luau new type solver has type checking issues using coroutine.wrap iterators or when fed variables defined as "any"

When using a custom iterator which is implemented with coroutine.wrap, the type checker gives the error: "Type Error: next() does not return enough values":

This is compared to the old type checker providing the correct types

Old Type Solver Img

Code
--!strict

local function CustomIterator()
	return coroutine.wrap(function()
		coroutine.yield(1, "foo")
		coroutine.yield(2, "bar")
	end)
end

for i: number, v: string in CustomIterator() do
	
end

This lead me to set the return type of the CustomIterator to “any”, however this came with its own bug that popped up where the defined types of the loop variables would become “any” despite being typed.

Once again this is not an issue with the old type solver

Old Type Solver Img

Code
--!strict

local function CustomIterator(): any
	return coroutine.wrap(function()
		coroutine.yield(1, "foo")
		coroutine.yield(2, "bar")
	end)
end

for i: number, v: string in CustomIterator() do
	
	-- This should clearly warn!
	local sound: Sound = v
	
end

I would assume this is not intended as I defined “v” to be a string.

This case occurs in any case where a variable with “any” is passed into a for loop, including when using pairs.

More Examples

--!strict

local foo: any = {}

for i: number, v: string in foo do
	
	-- This should clearly warn!
	local sound: Sound = v
	
end

--!strict

local foo: any = {}

for i: number, v: string in pairs(foo) do
	
	-- This should clearly warn!
	local sound: Sound = v
	
end

Hopefully these couple of things get fixed (especially passing “any” into a for loop and types being nullified, since I can see that probably causing some issues when using libraries).

Workaround for coroutine iterators

Workaround I ended up using was to cast the returned coroutine as the function with return types.

--!strict

local function CustomIterator(): () -> (number, string)
	return coroutine.wrap(function()
		coroutine.yield(1, "foo")
		coroutine.yield(1, "bar")
	end) :: ()->(number, string)
end

for i: number, v: string in CustomIterator() do
	print(i, v)
end

Hey, thanks for the report!

We can look into improving the error feedback in this case, but correctly inferring the way calls to coroutine.yield influence coroutine.wrap is a pretty difficult thing to do. Luau isn’t up to that task yet.

For now, the best workaround is to annotate your iterator function.

The reason why it looks like it works in the old solver is because it’s making faulty judgments. Try this:

--!strict

function my_iter()
	return coroutine.wrap(function()
		coroutine.yield(1, "one")
		coroutine.yield(2, "two")
	end)
end

for i, v: boolean in my_iter() do -- no error here
	print('iter ' .. tostring(i) .. " -> " .. tostring(v))
	local a: Sound = v -- error: type mismatch between Sound and boolean
end

Unfortunately that does have the error: "next() does not return enough values".

As for this second issue, this becomes a lot more prevalent when using Instance:GetDescendants() since GetDescendants() returns the type "{any}"

part is now the "any" type despite being clearly typed prior.

If you annotate the generator, everything should work fine:

function my_iter(): () -> (number, string)
    return coroutine.wrap(function(...)
        coroutine.yield(1, "one")
        coroutine.yield(2, "two")
    end) :: any
end

for i, v in my_iter() do -- OK
    print('iter ' .. tostring(i) .. " -> " .. tostring(v))
end

for i: number, v: number in my_iter() do -- Type mismatch between number and string
    print('iter ' .. tostring(i) .. " -> " .. tostring(v))
end
1 Like