Luau Type Checking Release

The following class will give the error “W000:Free types leaked into this module’s public interface. This is an internal Luau error; please report it.” when trying to export its type for use in ModuleScripts requiring it.

--!strict
local Class = {}
Class.__index = Class
function Class.new(): Class
	local self = setmetatable({},Class);
	self.State = false;
	return self;
end
function Class:Modify(): nil
	print(self.State)	--Comment out this line or remove '.State' and the error goes away
	return;
end
export type Class = typeof(Class.new());	--Remove 'export' and the error goes away

Have this error showing up when I checked up on a script, went to the internet and found this post. It seems the type checking doesn’t recognize the fact that the ObjectValue ‘Projectile’ has an assigned value, which is a part, and simply throws an error saying nil does not have the Clone function. image

Thanks for the report! I’m working on stuff related to this, I’ll add this example to my queue. And thanks for producing a minimal example, it’s very nice to work with.

1 Like

image
W000: expected a string or number, got number | string
Can be done using the below function

function a(b: string|number)
    print(""..b)
end
1 Like

I’d like to see generics available in functions, and default values for generics (default parameters would be good too; e.g. function blah(param: string = "roblox") ... end)! I feel something like this should be possible:

function GetValueOrDefault<T = any>(parent: Instance, name: string, defaultValue: T): T
	local child = parent:FindFirstChild(name)

	if child then
		return child.Value or defaultValue
	end

	return defaultValue
end
local possiblyNil = GetValueOrDefault(workspace, "HelloValue")
local alwaysString = GetValueOrDefault<string>(workspace, "WorldValue", "World")

Also the linter could do with some more work. It doesn’t seem to like ternary operations (or when you do the same with if/if-else statements), such as:

return function(val: number?): boolean
    val = type(val) == "number" and val or 0
    val += 1 -- W000: Type 'nil | number' could not be converted into 'number'

    return val >= 10
end
3 Likes

Add tostring().
Try use music.SoundId = "rbxassetid://" .. tostring(id) instead.

Looks like there’s been a regression in the typings for RemoteEvent.OnServerEvent.

The following code is benign, and used to emit no warnings but will now emit a warning:

--!strict
local x: RemoteEvent = Instance.new('RemoteEvent')
x.OnServerEvent:Connect(function(player: Player, shouldNotBeTrusted: any)
end)


'Type (Player, any) -> ()' could not be converted into '(Instance, any) -> nil'

1 Like

Found another regression: script is now typed as a ModuleScript instead of any, and this means you can’t directly access children of the script, even in scenarios where they’re 1000% guaranteed to exist at runtime!

local gui = script.GuiTemplate:Clone()

Yes, I am still in the camp that says unnecessary calls to FindFirstChild is bad practice and makes your code more verbose than it’s supposed to be. Besides this… aren’t we supposed to be able to rely on relative paths to require modules? Interestingly enough, dot-accessing within a require statement is allowed, but for some reason dot-accessing in any other context is not allowed?

This makes me think this is not an unintentional regression, and instead just intentionally poor design…
… which makes me intentionally put ugly statements at the top of my script just so I can work around the type system.
image

3 Likes

I was reporting an issue, I know I can do that already.

We have an upcoming change that will track parts of the datamodel hierarchy for type checking to fix cases like this, but I’m not sure if we intentionally changed the type of script ahead of this - we’ll take a look.

5 Likes

I’m having the same issue. One workaround I found was to do this:

return function( val: number? ): boolean
    local temp = 0

    if val then
        temp = val
    end

    temp += 1

    return temp >= 10
end

Has this changed yet? Is it even slightly faster or just a waste of time?

Looks like they’re adding an assertion operator (::), but in the meantime, I found this plays nicely with Luau (even in strict mode):

--!strict
local function detype(value: any, _: string?): any
    return value
end

return function(val: number?): boolean
    local val: number = type(val) == "number" and detype(val) or 0
    val += 1

    return val >= 10
end

Yeah, it makes an unnecessary function call, but at least it supresses the errors for now. I’m using it sparsely, and just omitting types otherwise (generally my variable names describe what type of input is expected).

The reason I added a second parameter is because you would store a string containing your type assertion for when Roblox make the :: operator live. When it goes live, you can just remove the function call and quotes around the string.

local val: number = type(val) == "number" and detype(val, "::number") or 0
-- local val: number = type(val) == "number" and val::number or 0

This error is valid. The error specifically mentions Instance | nil, which is Instance OR nil. Instance has Clone(), but nil doesn’t, hence the error.

You get the error because another script could easily modify Projectile.Value = nil.

With the upcoming expansion to the context-aware autocomplete will type checking be compatible across scripts?

As it stands currently scripts are not aware of types being used in the module scripts they require. Meaning you would still need to open the code base to check types.

--!strict
local codeTest = {}

function combineStrings(firstString: string,secondString: string): string
	return firstString..secondString
end

return codeTest
--!strict
local code = require(game:GetService("ServerScriptService"):WaitForChild("ModuleScript"))

code.combineStrings(1,3) --There is no warning present here.

Types only show up if you index the other module with . index expressions.
For example:

--!strict
local code = require(game.ServerScriptService.ModuleScript)

code.combineStrings(1,3) --There is no warning present here.

This should show the warning.

Unless your script is a LocalScript in ReplicatedFirst, you don’t actually have to use :WaitForChild by the way, since this module is guaranteed to exist (because it’s a server-visible module, it’s also 100% guaranteed to exist on the server unless some other external code deletes it).

Also, :GetService isn’t really necessary if this is only your own code; if you’re writing a public libary, there’s a chance that people can rename services like ServerScriptService, but if you aren’t renaming it in your own project, then it should already exist the moment both Server and (non-ReplicatedFirst) Client scripts run.

1 Like

Similar to my previous report, the following class interaction will give the error “Free types leaked into this module’s public interface. This is an internal Luau error; please report it.” when trying to export its type for use in ModuleScripts requiring it.

--!strict
local ClassA = {}
ClassA.__index = ClassA
function ClassA.new(): ClassA
	local self = setmetatable({},ClassA);
	self.State = false;
	return self;
end
function ClassA:Modify(): nil
	print(self.State)	--Comment out this line or remove '.State' and the error goes away
	return;
end

local ClassB = {}
ClassB.__index = ClassB
function ClassB.new(): ClassB
	local self = setmetatable({},ClassB);
	self.A = ClassA.new();	--Comment out this line and the error goes away
	return self;
end

type ClassA = typeof(ClassA.new());
export type ClassB = typeof(ClassB.new());	--Remove 'export' and the error goes away

Reporting here as i cannot post bugreports.
image

On this image, you can see that the linter is complaining that the string cannot be converted to nil which is dumb as the code block above it already check if it’s nil, and if it finds it so, it will earlyreturn.
My expected result would be the linter interpreting action.action as string? rather than nil.

2 Likes

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.