Luau typechecking does not carry type guards after return statements

Reproduction Steps

When using something like a guard clause in a function, type guards do not propagate to the rest of the function.

Use a variable with two or more possible types, check for its type, and return, break, or continue if it’s not one of those types. The rest of the scope will not have the correct types applied to the variable.

Expected Behavior

This method of type checking should propagate to the scope outside of the return

function optionalArgWithGuardClause(arg: number?)
	if not arg then return end
	
	for i=1, arg do --this should work! arg should be of type "number"
	end
end

And here’s an example with a break/continue guard:

function optionalArgWithGuardClause(arg: number?)
	for i=1, 5 do
		if not arg then continue end
		if not arg then break end
		
		print(arg) --arg should be of type "number" here
	end
end

Actual Behavior

The correct type does not get applied after the guard clause.

function optionalArgWithGuardClause(arg: number?)
	if not arg then return end
	
	for i=1, arg do --type error: arg is "number?"
	end
end

Again, here’s the example with the break/continue guard:

function optionalArgWithGuardClause(arg: number?)
	for i=1, 5 do
		if not arg then continue end
		if not arg then break end
		
		print(arg) --type error: arg is "number?"
	end
end

Issue Area: Studio
Issue Type: Other
Impact: Moderate
Frequency: Sometimes
Date First Experienced: 2022-11-22 00:11:00 (-05:00)

4 Likes

Thank you for the report.
We do plan to fix the issue, it has came up multiple times before.
Unfortunately, while we do have people working in this area, it will come packaged with other related improvements and they all together need more time.

4 Likes

As an update, in March we released a fix for return statements.
Our team is working on a new Luau type analysis that does support continue/break, but that is going to be released later.
We actually backported the ‘return/error/assert’ part from it into the old type analysis, but the part for ‘break/continue’ wasn’t as compatible as statements that abort the whole function.

2 Likes

Hey! Is there any update for a timeline on type analysis support for continue? We use --!strict everywhere and we’ve gotten in the habit of writing code like:

		-- make a normal breakable :-)
		local selectedNormal = rollNormal(areaID, areaData)
		if not selectedNormal then 
			continue
		end
		assert(selectedNormal)

		if parentType == BreakableTypes.ParentType.Instance then
			createNormalInInstance(selectedNormal, parentID, areaID, setting)
		else
			createNormalInZone(selectedNormal, parentID, areaID, setting)
		end

The fact that there’s a non-zero runtime hit when using assert to fix the problem is a tough pill to swallow (not to mention it’s just plain ugly.)

3 Likes

This fix should arrive in the first week of October.

6 Likes

Closing out the loop here, the continue and break support has been implemented and flags enabled.