The new version of Studio brings a pair of little improvements to Luau:
First, we should be much smarter about doing type inference on groups of functions that call one another. Code like the following should work fine now:
--!strict
local Foo = {}
Foo.__index = Foo
function Foo.new()
local self = setmetatable({}, Foo)
self.fooConn = function()
-- Previously, we would warn that we can't find Foo:method
-- OK now.
self:method()
end
return self
end
function Foo:method()
print("foo")
end
local foo = Foo.new()
foo.fooConn()
Secondly, we’ve fixed a bug where long method call chains would require inordinate amounts of CPU and RAM to typecheck.
local s = ("hello"):lower():lower():lower() -- If you did this a few dozen times, Studio started having a bad time.
I’ve just enabled initial support for require tracing in Luau. Here’s how it works:
Luau can trace through a require() call if it looks like one of the following:
local M = require(workspace.This.Whatever.CoolThing)
local N = require(script.Parent.Something.Useful)
If Luau sees something like this, it will ascribe the proper type to M or N above.
Secondly, all type aliases defined in a required script are available. Just qualify them with the name of your import alias:
-- Lib
--!strict
local exports = {}
local CoolThing = {}
CoolThing.__index = CoolThing
function CoolThing.new() return setmetatable({}, CoolThing) end
function CoolThing:method() print('Hello World!') end
-- Define a type name for our class
type CoolThing = typeof(CoolThing.new())
exports.CoolThing = CoolThing
return exports
-- SomethingElse
--!strict
local Lib = require(script.Parent.Lib)
local t: Lib.CoolThing = Lib.CoolThing.new()
-- ^ We have to name the script that the type comes from
t:blah() -- error: CoolThing has no method :blah()
t:method() -- OK!
Something that doesn’t work yet, but is in the works is tracing requires through calls to game:GetService. Stay tuned!
I’m impressed! This is starting to look more and more like the kind of Typed Lua I’ve been anticipating. The typeof operator in particular is going to be very useful for many things, let alone exporting types between modules.
There is one problem with this method of exporting types between modules though. Since types are static information, there are many cases in which you would want to use types from another module without necessarily calling require on that module. One example I’ve already mentioned before on this thread is in OOP code where a parent class instantiates a child class, and the child class needs the type (but not the value) of the parent class; in this case, it’s hard to avoid a require deadlock unless you manually type each class, and put these typings in a separate module from the class implementation (similar to the role of header files in C++)
Is it possible you could do statements like type MyClass = typeof(require(path.to.MyClass))? This would solve the problem quite elegantly, and make it easier to avoid require deadlocks.
Also, this feature seems to be broken for me right now
As for relative paths, I can’t seem to get the local t: Lib.CoolThing syntax to work.
EDIT: It also looks like the most recent update doesn’t emit any type mismatch warnings in nonstrict mode (allowing statements like local x: string = 2). Is this a bug?
I honestly hadn’t thought about typeof(require(X).Y). It may already work as a side effect of the way the inference engine handles typeof(). As for as the type checker is concerned, typeof(X) is the same work as X.
Both of the things you’ve pointed out look like bugs to me. In particular, requires that are rooted at game don’t seem to work the way they should. We’ll take care of it.
Sorry from annoying, but i have something very strange…
I am trying to make my own class by utilising the new Luau Type and i want to make it possible to make this:
local MyClass: MyClassType = MyClass.new()
and as @fun_enthusiast has say and write, i tried with this. I not am very good with the type keyword, so i tried:
local Spirits = {}
Spirits.__index = Spirits
function Spirits.new(Name:string?, LV1: number?, LV2: number?, LV3: number?,
LV4:number?, ActualLv:string?)
--The newSpirit
local newSpirit = {}
setmetatable(newSpirit, Spirits)
--The Name
newSpirit.Name = Name
--The Level
newSpirit.LV1 = LV1
newSpirit.LV2 = LV2
newSpirit.LV3 = LV3
newSpirit.LV4 = LV4
--The ActualLeve
newSpirit.ActualLevel = ActualLv or tostring(LV1)
return newSpirit
end
--Others Things
type Type = typeof(Spirits.new())
Spirits.Type = Type
function getLv(Spirit:Type) => number
return tonumber(Spirit.ActualLv)
end
function Spirits:CompareSpirits(Spirit1:Type, Spirit2:Type)
if getLv(Spirit1) < getLv(Spirit2) then
print("First Choice")
elseif getLv(Spirit2) > getLv(Spirit1) then
print("Second Choice")
elseif getLv(Spirit1) == getLv(Spirit2) then
print("Equals")
end
end
return Spirits
but it say me: Unknow symbol ‘Type’, so i tried with this:
local Spirits = {}
Spirits.__index = Spirits
function Spirits.new(Name:string?, LV1: number?, LV2: number?, LV3: number?,
LV4:number?, ActualLv:string?)
--The newSpirit
local newSpirit = {}
setmetatable(newSpirit, Spirits)
--The Name
newSpirit.Name = Name
--The Level
newSpirit.LV1 = LV1
newSpirit.LV2 = LV2
newSpirit.LV3 = LV3
newSpirit.LV4 = LV4
--The ActualLeve
newSpirit.ActualLevel = ActualLv or tostring(LV1)
return newSpirit
end
--Others Things
Spirits.ScriptingType = typeof(Spirits.new())
function getLv(Spirit:typeof(Spirits.new())) => number
return tonumber(Spirit.ActualLv)
end
function Spirits:CompareSpirits(Spirit1:typeof(Spirits.new()), Spirit2:typeof(Spirits.new()))
if getLv(Spirit1) < getLv(Spirit2) then
print("First Choice")
elseif getLv(Spirit2) > getLv(Spirit1) then
print("Second Choice")
elseif getLv(Spirit1) == getLv(Spirit2) then
print("Equals")
end
end
return Spirits
but it look so, so weird… I not more see the warning, but i not really know if this was the right method, so i am asking here. I know this was in Beta, so i not know if this was my error or a Beta problem.
But @fun_enthusiast, if i want to define my custom part type, then? I am working on a custom class, so i want to make the same only that it work for my class, how to attempt this?
I think the thing that’s tripping you up is that types and values are totally separate things.
Values are everything in Lua (not just Luau) that occupies memory by existing. Tables, functions, numbers, and booleans are all examples of values.
Types, by contrast, are basically imaginary. They don’t occupy any memory or do anything when people play your game. They exist only when you are writing your game in Roblox Studio.
local MyTable = {}
-- ^ Creates a name for a *value*
type MyType = {}
-- ^ Creates a name for a *type*
local x = MyType -- No good because there is no *value* called MyType
local x: MyTable = {} -- No good because there is no *type* called MyTable
So, when you design an OO style object in Lua, you really want to do two related things at the same time:
You want to define a kind of value that can do a bunch of useful stuff, and
You want Luau to know about and track objects that match the shape of those values.
The simplest way to do this is to name the type of the value. This is a distinct step from writing the code to create the value itself. Because they are totally separate, it is easy (and a good idea) to give them the same name.
The way to do this for your own data types is to use the type keyword:
-- First, we create the value MyObject
local MyObject = {}
MyObject.__index = MyObject
function MyObject.new()
return setmetatable({}, MyObject)
end
-- Second, we create a type that has the same name as the value
type MyObject = typeof(MyObject.new())
-- ^ Type Name ^ Value name