--!strict
local constructorClass = {}
local Constructee = {}
Constructee.__index = Constructee
export type constructee = typeof(setmetatable({}, Constructee)) & {
test: number?
}
function constructorClass.new(): constructee
local self = {}
self.test = nil --> could be nil or a number
setmetatable(self :: any, Constructee)
return self :: constructee --> Cannot cast '{ test = nil }' into Constructee & blah blah because the types are unrelated.
end
return constructorClass
There isn’t a warning when I remove --!strict but I want to know what’s causing the warning in the first place?
For context, the self.test number variable may or may not exist in my experience due to in-experience factors, so I made it number? which should’ve allowed this type cast?
How can I properly type check while allowing for nil in this scenario?
Even exporting the type as test: number | nil still brings a warning and also when I use number? and self.test = 100. I’m unsure how to get what I want in this situation.
Edit: After declaring it as test: number & nil, the warnings have been silenced, but I don’t know if this is a proper solution because this is for custom types and that’s not what my intentions are.
Edit 2: I also tried setting self.test as self.test = 521 :: number? and it silenced the warnings again but this is noisy and I’m looking for a better solution because if I do add custom types then asserting possibly nil values will be really ugly.
The problem here is that you making a type with metatables, where in this situation it isn’t needed.
--!strict
local constructorClass = {}
local Constructee = {}
Constructee.__index = Constructee
export type constructee = {test: number?}
function constructorClass.new(): constructee
local self = {}
self.test = nil --> could be nil or a number
setmetatable(self :: any, Constructee)
return self
end
return constructorClass
I see why. It doesn’t pick up methods because there’s no index to them. Yeah, sorry but i’m not sure. You would have to create the methods inside the function which probably isn’t desired.
That isn’t desired, but I think I found a solution.
--!strict
local constructorClass = {}
local Constructee = {}
Constructee.__index = Constructee
export type constructee = typeof(setmetatable({}, Constructee)) & {
test: number?
}
function constructorClass.new(): constructee
local self = {}
self.test = nil
return setmetatable(self :: any, Constructee) :: constructee -->magik solution
end
return constructorClass
No warnings. Does this look appropriate to you? I’m not entirely sure what my change did so I’d like to hear your opinion on what I even did differently.
This might not be correct. You used the word “any” but it’s important to know the specific types of all values all the time, especially if you’re using --strict mode. this could be
--!strict
local constructorClass = {}
local Constructee = {}
Constructee.__index = Constructee
function constructorClass.new(): constructee
local self = {}
self.test = nil
setmetatable(self, Constructee)
return self
end
export type constructee = typeof(constructorClass.new())
return constructorClass
Thanks for the help. My final issue is if I wanted to use inheritance, like so:
--base class
local constructorClass = {}
local Constructee = {}
Constructee.__index = Constructee
function constructorClass.new(): constructee
local self = {}
self.BaseClassNumber = 24
setmetatable(self, Constructee)
return self
end
export type constructee = typeof(constructorClass.new())
return constructorClass
local baseClass = require(script.Parent)
--subclass (inherits from code above)
function constructorClass.new(): constructee
local self = baseClass.new()
self.test = "testing"
setmetatable(self, Constructee)
return self
end
I would also like to know the same thing.
The problem is that the base class is sealed and you can’t add any more members.
Type checking and OOP are a headache. That’s why I try to use OOP as little as possible.
I feel like a solution for my inheritance idea is to use my former semi-solution where I’m a tad unsure of it’s members and their types while using your solution for the absolute sub classes.
In my project, I’m not inheriting from objects but inheriting functions and general data, so the reasons you listed why my original solution wasn’t a good one may not apply to my situation.
I’ll take some aspirin and code my thinking into reality and I’ll reply if it worked or not. Thank you for the help though. OOP and type checking are headaches.
After digging into OOP type checking topics and blindly guessing in the dark on how to fix my code, I believe I’ve achieved heavenly status of code that works in a favorable way for my experience.
Before looking into the code, I tried my best to follow the Luau styling guide as if everyone in the world is going to judge this script with that level of intensity. Please correct any errors if you see them.
--!strict
local furnitureClass = {}
local Furniture = {}
Furniture.__index = Furniture
export type class = typeof(setmetatable({}, Furniture)) & {
seatOccupied: boolean,
seat: () -> any?
}
function Furniture:sit(): ()
self.seatOccupied = true
end
function furnitureClass.new(): class
local self = {}
self.seatOccupied = false
return setmetatable(self :: any, Furniture) :: class
end
return furnitureClass
This is the furniture base class. It holds data and methods relative to all furniture objects. For my example I’ll only have a singular couch subclass but it can be expanded for arm chairs, stools, etc.
I created the constructor function outside of the furniture class to prevent useless code smell where you could, without error, do Furniture.new().new().new().new(). When using auto complete to navigate my methods and values, I find that I would always see .new() even when it was an already constructed class object, making it 100% useless.
--!strict
local Furniture = require(script.Parent)
local couchClass = {}
local Couch = {}
Couch.__index = Couch
export type class = typeof(Furniture.new()) & typeof(Couch) & {
pulledOut: boolean,
sleeping: boolean,
sleep: () -> any?,
pullout: () -> any?
}
function Couch:sleep(): ()
if self.pulledOut then
self.sleeping = true
end
end
function Couch:pullout(): ()
if not self.seatOccupied then
self.pulledOut = true
end
end
function couchClass.new(): class
local self: Furniture.class & any = Furniture.new()
self.pulledOut = false
self.sleeping = false
return setmetatable(self :: any, Couch) :: class
end
return couchClass
There’s not much to say. The couch class inherits values and methods from the furniture class while also constructing some of its own. If you wanted your couch to have a plushie or anything you could also use composition and it’d work well if it was predefined in the export type declaration.
Also, again, I separated the couch constructor and class to prevent .new() clog when using the Studio auto complete feature.
--!strict
local Couch = require(script.Parent:WaitForChild("Furniture"):WaitForChild("Couch"))
local myCouch = Couch.new()
myCouch:pullout()
And finally, the code that made it all worth it. Not really, but.
As you can see, when using the variable myCouch, the auto complete will allow me to see everything. It also doesn’t have the .new() option because it’s already created.
Anyways, I dislike this level of OOP and type checking, I don’t even know if what I’ve done here is correct. I think this was too complicated for something as simple as a couch and it’s borderline evil.