I have now been able to reproduce the crash consistently. Paste the following code into a new module and then uncomment line 42. Studio will then crash. I am running windows 10 and have tested this code on a place which has no other scripts. The reason I believed it was a luau issue is because the problematic code is mostly type definitions and setmetatable calls.
Problematic Code
--!strict
-- Paste into a new module and uncomment line 42 to crash studio
----- Module Init -----
-- Generic module init
local GenericModule = setmetatable({}, {
__index = function(_, key) error(tostring(key)..' is not a valid member of Module', 2) end,
__metatable = function(_) error('The metatable is locked', 2) end
})
type LockedMetatable<self> = {
__index: (self, any) -> (),
__metatable: (self) -> ()
}
----- Prototype Init -----
-- Generic prototype init
local GenericPrototype = setmetatable({}, {
__index = function(_, key) error(tostring(key)..' is not a valid member of Generic', 2) end,
__metatable = function(_) error('The metatable is locked', 2) end
})
GenericPrototype.__index = GenericPrototype
GenericPrototype.__newindex = function(_, key) error(tostring(key)..' is not a valid member of Generic', 2) end
GenericPrototype.__metatable = function(_) error('The metatable is locked', 2) end
type GenericMembers = {
Name: string,
}
type GenericMethods<self, prototype> = {
__index: prototype,
__newindex: (self, any) -> (),
__metatable: (self) -> (),
Foo: (self, number) -> ()
}
type GenericPrototype = typeof((function()
-- local tbl: GenericMethods<Generic, GenericPrototype>
local mt: LockedMetatable<GenericPrototype>
return setmetatable(tbl, mt)
end)())
export type Generic = typeof((function()
local tbl: GenericMembers
local mt: GenericPrototype
return setmetatable(tbl, mt)
end)())
----- Prototype Function -----
-- Generic functions for the generic prototype
function GenericModule.NewGeneric(name: string): Generic
local newGeneric = {
Name = name
}
return setmetatable(newGeneric, GenericPrototype)
end
function GenericPrototype:Foo(x: number)
return
end
----- Module Return -----
return GenericModule
So I am just now starting to get into more luau stuff and was wondering a few things that I couldn’t figure out based on reading everything in this thread.
The first question is, is the type for the function arguments suppose to limit the argument to that type, returning an error if its not? because right now I am finding it treat args as anything. if you classify them as a string or number and pass through a boolean, it goes through anyway. Or am I mistaking on how these types work.
The second question is, what would the Player’s type be? Would it be player: Player, or player: Instance, or player: Userdata, or player: Object… im not sure
Would be nice to have a list of types and their use cases!
I’ve tried several different ways but I can’t seem to figure out how to refine an Instance into a BasePart as typeof returns Instance when applied to a Part. What is the recommended way to refine, say, the Parent property of an Instance to a BasePart to be able to access its Position property in strict mode?
@zeuxcg I was scrolling through the beta list and noticed this entry has no “ⓘ” with linked thread, which means if I was not yet familiar with type-checking from keeping up announcements I would not know what this means:
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.
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.
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
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!
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.
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.
--!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
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.