The following script results in the warning W000: (2,1) Expected to return 0 values, but 1 is returned here
--!strict
return
But the script below results in no warning
--!strict
return 0
The following script results in the warning W000: (2,1) Expected to return 0 values, but 1 is returned here
--!strict
return
But the script below results in no warning
--!strict
return 0
Are there any types that are implicitly nullable, or that will be implicitly nullable?
local part = Instance.new("Part")
part = nil
This raises a warning because the part variable is not of type Part?
, but in my opinion, like objects in other languages, Instance
should be implicitly nullable.
Another one that is mildly irritating is references to tables. In all of my custom classes I offer a Dispose
method that detaches everything from its containing type (by setting it to nil) so that GC can pick it up. For instance:
type MyType = {
Var: {[any]: any}
}
function Dispose(obj: MyType)
obj.Var = nil
end
This will raise a warning since the table is not nullable.
Luau beginning to look like C#, I like this.
The answer is: Yes.
I’ve tried rejoining my game (in studio) but I crashed everytime.
Is it not possible to have mixed type ordered tuples?
A tuple is a fixed length ordered array, I often use them when storing two or three related values.
Here is an example of all the syntaxes I tried and the errors they produce:
--!strict
-- Syntax error: Expected ':' when parsing table field, got ','
type ConnectionArgument = { string, string, boolean }
-- Syntax error: Expected type, got '1'
type ConnectionArgument = { 1: string, 2: string, 3: boolean }
-- Syntax error: Expected type, got '1'
type ConnectionArgument = { [1]: string, [2]: string, [3]: boolean }
-- W000: Type 'boolean' could not be converted into 'string'
type ConnectionArgument = typeof({ '', '', false })
-- W000: Type 'boolean' could not be converted into 'string'
local argument: ConnectionArgument = { 'number', 'ArgOne', false }
-- This is how I would later use the tuple, all three values always used together
local class, name, optional = unpack(argument)
An interesting problem with this is that the index signature syntax (i.e. {[string]: any}
) directly conflicts with the syntax of using literal types as keys in an interface. Would {[1]: any}
mean that key ‘1’ must have a value of “any” type, or does it mean that all keys must have a 1
type, and all values have an any
type?
Granted, literal types aren’t supported yet, but I’d imagine that they should be down the road.
Found a second issue, using assert will result in type refinement as expected.
But when I assert a property to be nil it will now not allow anything to be assigned to that property.
I commonly use this to catch errors for methods which should only be called once per instance.
A third issue is that there is no syntax which allows vararg function types.
I would like ConnectionHandler to be a vararg which always has the first argument as type Player.
Using ...
in the type define will result in an error, and so will trying to add extra args to handler
Here is some example code and the error produced:
Network.AssertClass is a custom function which raises an error if param one is not of type param two.
-- If ', ...' is added: Syntax error: Expected type, got '...'
export type ConnectionHandler = (Player) -> ()
export type Connection = {
--[[ Unrelated fields omited ]]
Callback: ConnectionHandler?
}
--- Add a callback to this connection, called when a client calls fetch
function Connection:Register(callback: ConnectionHandler)
assert(self.Callback == nil, 'Connection already has a callback registered')
Network.AssertClass(callback, 'function', 'Argument #1 to Register')
--W000: Type '(Player) -> ()' could not be converted into 'nil'
self.Callback = callback
end
-- Example handler that might be passed to Connection:Register
-- W000: Type '(Player, any, any) -> ()' could not be converted into '(Player) -> ()'
local handler: ConnectionHandler = function(player: Player, argOne: any, argTwo: any)
print(player.UserId)
end
local str = "a"
print(
(
str == "a" and "a"
or str == "b" and "b"
or "z"
).."c"
)
results in W000: (173,9) expected a string or number, got boolean | string
I think there should be a button in Script Analysis that will hide selected type warnings and make them dark grey somewhere in hidden warnings folder and won’t underline them because there is so many problems with this checker that sometimes there is no workaround other than using –!nocheck but that is not the best solution too after all. I think if you could somehow hide them with ability to show them again it would make it a lot better, something like in planes when there is master caution, pilots
click the button and it stops flashing.
Also there are issues with remote events and bindable events where you can’t have more than 1 argument in Fire or FireServer/Client, it will throw warning like this
Good stuff, that’s an issue I ran into quite often! Thanks, and keep up the good work!
It looks like a recent update caused a regression where do end
blocks can make output extremely verbose and unreadable in some cases for strict mode:
Simplified Repro:
Here I have defined a decently-sized interface type, and a variable x
being assigned to it, except that I have an extraneous field, which should output a warning.
--!strict
type DefinedType = {
foo: string,
fighters: string,
dave: string,
pat: string,
nate: string,
chris: string,
taylor: string,
rami: string
}
local x: DefinedType = {
extraneousField = 'This is extraneous',
foo = 'Hello',
fighters = '',
dave = '',
pat = 'string',
nate = 'string',
chris = 'string',
taylor = 'string',
rami = 'string'
}
The warning here is extremely readable and easy to diagnose the issue:
However, if I scope the last statement in a do end
block, it all the sudden makes the output much more verbose and less readable:
--!strict
type DefinedType = {
foo: string,
fighters: string,
dave: string,
pat: string,
nate: string,
chris: string,
taylor: string,
rami: string
}
do
local x: DefinedType = {
extraneousField = 'This is extraneous',
foo = 'Hello',
fighters = '',
dave = '',
pat = 'string',
nate = 'string',
chris = 'string',
taylor = 'string',
rami = 'string'
}
end
When working with code that has more complex types, this becomes completely unmanageable and impossible to diagnose the issue aside from trial and error, guessing where things are going wrong; the warning won’t even fit on the screen with my 1440p monitor!
Another small bug report:
I have a table typed as {[Player]: BasePart?}
, since there both can and can not be a BasePart at an arbitrary Player
-typed key. Unfortunately, when iterating over this record with pairs
, it also sets the value to a type that is nilable, even though it’s impossible to have a nil value with a non-nil key on a table.
This is in a heartbeat-bound polling loop, so even though it’s not too big of a deal to check if proxy then
, it’s still a needless truthiness check that I have to place here that affects runtime performance because it thinks proxy
can be nil here.
One major grievance I have is the method for importing external types. I have a module right now that requires a module and assigns it to a variable for the sole purpose of using it for type annotations (you can see the type module here). This is annoying, since it has runtime consequences and is a bit unwieldy.
Would it be possible for us to get an import
keyword or something like it so that we can import the types from another module/script without having to require it directly?
Currently converting my 3D skybox module to use types, but forgot to annotate them on the functions themselves. Got this messed up error which left me confused for a while:
W000: (531,4) Type '({+ AmbientColorDay: Color3, AmbientColorNight: Color3, ... 3 more ... +}, Color3) -> ()' could not be converted into 't1 where Skybox3D = {| AmbientColorDay: Color3, AmbientColorNight: Color3, ... 19 more ... |} ; t1 = (Skybox3D, Color3) -> ()'
Here are the code snippets that caused this:
type Skybox3D = {
...
SetViewportFrameZIndex:(Skybox3D,number)->(),
...
SetWorldScale:(Skybox3D,number)->(),
SetCameraOrigin:(Skybox3D,Vector3)->(),
SetLightSource:(Skybox3D,string)->(),
SetAmbientColorDay:(Skybox3D,Color3)->(),
SetAmbientColorNight:(Skybox3D,Color3)->(),
SetLightColorDay:(Skybox3D,Color3)->(),
SetLightColorNight:(Skybox3D,Color3)->(),
SetActive:(Skybox3D,boolean)->(),
Destroy:(Skybox3D)->(),
}
...
local skySetWorldScale = function(self,scale)
...
end
local skySetCameraOrigin = function(self,ori)
...
end
local skySetViewportFrameZIndex = function(self,zindex)
...
end
local skySetLightSource = function(self,source)
...
end
local skySetAmbientColorDay = function(self,ambientday)
...
end
local skySetAmbientColorNight = function(self,ambientnight)
...
end
local skySetLightColorDay = function(self,lightday)
...
end
local skySetLightColorNight = function(self,lightnight)
...
end
local skySetActive = function(self,active)
...
end
local skyDestroy = function(self)
...
end
I’m having an interesting issue. As you can probably guess, should the type be nil then number 1
will be used for the assignment.
I have no idea how to solve this problem but can we just not have this warning? It seems like it’s going to rarely if ever be useful.
It seems types alias statements have some amount of scope to them, so it’s almost always better to feed types annotations forwards. The exception is that you can circularly reference types within another type alias.
I’m not sure if this is intentional behavior for the type system or a bug to be fixed, but I’ve been using the following (extremely verbose, but type safe to external users of the module) boilerplate for typed OOP code:
--!strict
type ClassName_Members = {
}
type ClassName_Methods = {
}
type ClassName_Metatable = { __index: ClassName_Methods }
export type ClassName = typeof((function()
local t: ClassName_Members
local u: ClassName_Metatable
return setmetatable(t, u)
end)())
local ClassName = {}
local ClassName_Methods: ClassName_Methods = {
}
local ClassName_Metatable: ClassName_Metatable = {
__index = ClassName_Methods
}
function ClassName.new(): ClassName
local self = {}
setmetatable(self, ClassName_Metatable)
-- Name self type to make output less verbose
type InferredSelf = typeof(self)
return self
end
return ClassName
You can ctrl+f and replace the text ‘ClassName’ with your class’s name
It’s super redundant and verbose, but it does get the job done.
As an example of this in action, I’m currently writing a custom character framework that forks the core “PlayerModule” script with a typesafe style of OOP:
@zeuxcg
Found a small typing issue with the Connect method of RBXScriptSignal
--!strict
local function subscribe(signal: RBXScriptSignal): RBXScriptConnection
return signal:Connect(function() end)
end
Here’s some incorrect code path parsing.
That seems to work, though I’ll be the first one to say “wow I hate this”.
It seems like in order to make it properly typesafe you only have to add types to the metatable though, which seems… more reasonable. Not quite good though.
Thank you for the tip about making internal types though. That helps with debugging a lot.