Conditionals in oop messing with type annotations

I want to add a conditional to my oop .new function to make it warn with context rather than just erroring.

local Ball = {}
Ball.__index = Ball

local BallSize = Vector3.new(1,1,1)


function Ball.new(Name)
	if not Name then warn("Name not provided") return end--Important part!!!!
	
	local NewBall = {}
	NewBall.Ball = Instance.new("Part")
	NewBall.Ball.Size = BallSize
	NewBall.Ball.Parent = game.Workspace
	NewBall.Ball.Position = Vector3.new(0,5,0)
	NewBall.Ball.Shape = "Ball"
	NewBall.Ball.Anchored = true
	NewBall.Ball.TopSurface = Enum.SurfaceType.Smooth
	NewBall.Ball.BottomSurface = Enum.SurfaceType.Smooth
	NewBall.Ball.Name = Name
	
	local BillboardGui = Instance.new("BillboardGui")
	BillboardGui.Parent = NewBall.Ball
	BillboardGui.AlwaysOnTop = true
	BillboardGui.MaxDistance = 50
	BillboardGui.Size = UDim2.new(4,0,1,0)
	
	local TextBox = Instance.new("TextLabel")
	TextBox.Parent = BillboardGui
	TextBox.TextScaled = true
	TextBox.Text = Name
	TextBox.Size = UDim2.new(1,0,1,0)
	TextBox.BackgroundTransparency = 1
	
	NewBall.Connections = {}
	
	local self = setmetatable(NewBall, Ball)
	return self
end

function Ball:Connect(BallToConnectTo)
	print(self)
	self.Connections[BallToConnectTo.Ball.Name] = BallToConnectTo
	
	print(self)
end

return Ball

When I do this, the type annotations go away, which makes it very frustrating to try and write the code using this module.
With the condition commented out:


With it uncommented:

I tried to look up manual type annotations to add it myself, How do I improve and add autocomplete to my OOP module?, Type checking - Luau and Type checking | Documentation - Roblox Creator Hub, come to mind, as well as a bunch more that are tangential.
These did not help me but revealed the problem was the conditional.
I also tried moving the setmetatable() above the conditional, but that also did not work

Is it possible to have a conditional inside an OOP statement like this? Or not?

Well, looking at your code I see you aren’t annotating the return type.

Are you sure that

type Ball = {} -- Make a type here for your Ball class

function Ball.new(name): Ball
    ...
end

does not work? AFAIK Luau does not care if the annotation is “correct,” so it should work just fine.

Just tested it, it seems to work. That is, unless I’ve misunderstood your question.

Also, remember to annotate the types of your arguments!

.new should look like

function Ball.new(name: string): Ball
    ...
end

I’d argue that this is an error that does not need to be handled. Trust the developer (yourself) to fill out the function properly, and allow the script to error if not. Otherwise, assign a default value for Name if it is nil.

1 Like

I might be missunderstanding you here, but changing

local Ball = {}
Ball.__index = Ball

local BallSize = Vector3.new(1,1,1)


function Ball.new(Name: string)

to

local Ball = {}
Ball.__index = Ball

local BallSize = Vector3.new(1,1,1)

type Ball = {}

function Ball.new(Name: string): Ball

Removes the type anotations even if the condition is commented out. Commenting out the : ball on the .new line fixes it again though.
This does not appear to work:

I said to define a type. You defined an alias for an empty table. That’s why the function has no autocomplete. There’s nothing to autocomplete.

type Ball = { Ball: Part, Connections: {Ball} }

you gotta actually define your type lol

this is 100% not your entire type, but it’s a start

True, the conditional is not needed or necissary. It just is weird that it does not work, and I wanted to see if it is.

I believe it doesn’t work because Luau sees that there is a possibility of the function terminating early, so it can’t guarantee it returns a Ball. You fix that by annotating the return type. You said doing that is “tangential” but it’s not. It’s needed.

It is not “weird.” It makes perfect sense why it doesn’t work. Luau has no way of knowing what it will return when you add that conditional, so while the function may WORK, the autocomplete does not.

btw i’m not trying to be rude if that’s how i sound

I see you marked one of my replies as the solution. If you still need help, feel free to ping me.

Setting the conditional to return the class does seem to fix the autocomplete not showing up.

local Ball = {}
Ball.__index = Ball

local BallSize = Vector3.new(1,1,1)


function Ball.new(Name: string)
	if not Name then warn("Name not provided") return Ball end


This fixes my original problem but does result in the side effect that the Ball the BallMaker and self is nil (as the metatable was never set) This is the solution and I just need to add a break line of code if the object == the class.

Given that I need to put that break line of code in the local script, I probably will just set the name to something like “unnamed” as @dostakek says or just let it error.

I am new to OOP and type checking so if returning the class is unorthodox, then that is why. But it works for now, I probably would need to find a better solution If I pursue this. It also explains why I think this behavior is “weird”

Thank you both for all your help!

Or, you could define a type alias and annotate the return type. That way, your early return doesn’t need to return the class. Lmk if you need help with that!

By the time you need to put any conditionals in the constructor, you should probably make a new inherited class instead. Notice how none of the Roblox API constructors ever fail to create an object under normal conditions. (other than type checks, but that is a topic for another time)

For trivial stuff like a name, you could simply do something like this:

NewBall.Ball.Name = Name or "Unnamed"
2 Likes

This is always my favorite usage of or. Well, I guess the intended usage of being a boolean operator is nice too.

I have decided to simply use @Sleazel’s suggestion as it is simpler and easier to switch out, This is very early in my project, so I don’t know exactly how I want to structure it and set up a bunch of types that might not be used later. Thank you for everything though!

1 Like

It’s great to get in the habit of defining types, even if you don’t know if you’ll use them in the future.

You should also use --!strict. It might feel like a pain at first, and…it is a pain, but the benefits outweigh the consequences. Look at this code (It’s not ugly, but strict Luau needs you to do stuff you wouldn’t do otherwise)

function getConfigFromGui(gui: ScreenGui): ScreenConfig
	local config: { [string]: any } do
		local module = gui:WaitForChild("Config", 3)
		if module ~= nil and module:IsA("ModuleScript") then
			config = require(module) :: { [string]: any }
		else
			config = {}
		end
	end
	return {
		enabledAtStart = if config.enabledAtStart ~= nil then config.enabledAtStart else false,
		transitionIn = config.transitionIn or emptyPlaceholder,
		transitionOut = config.transitionOut or emptyPlaceholder,
		ignoreGuiManager = if config.ignoreGuiManager ~= nil then config.ignoreGuiManager else false,
	}
end

But it’s a lot harder for there to be runtime errors when using --!strict.

1 Like

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