Inheriting a baseclass to a subclass using strict typechecking

Hi,

I am trying to inherit a baseclass to a subclass using strict mode and typechecking. The baseclass in question is BaseMovement. I set the metatable to {}, basemovement and inside the constructor I did setmetatable(BaseMovement.new() :: any, Sprint).

Under return self it warns me

local Sprint = setmetatable({}, BaseMovement)
Sprint.__index = Sprint

export type ClassType = typeof( setmetatable({} :: {
	_connections: Trove.ClassType;
}, Sprint) )

--// constructor
function Sprint.new(): ClassType
	local self = {
		_connections = Trove.new()
	}
	
	setmetatable(BaseMovement.new() :: any, Sprint)
	
	return self
end

--// ALL METHODS USE SELF: CLASSTYPE
function Sprint.Bind(self: ClassType, actionName, inputState, _inputObject): ()
	if inputState == Enum.UserInputState.Begin then
		CameraTweenIn:Play()
	end
	
	if inputState == Enum.UserInputState.End then
		CameraTweenOut:Play()
	end
end

How do I fix this? ALL METHODS USE SELF: CLASSTYPE

2 Likes

Have you tried asserting the type?

local self = {
    _connections = (Trove.new() :: Trove.ClassType)
}
1 Like

No that wouldn’t work, it’s the same as doing:

local self = {
		_connections = Trove.new()
	}
1 Like

I’m a little confused by how you’re doing the inheritance. Did you get this method from a tutorial? It looks like you aren’t applying the inheritance to the new object in the constructor at all, because you never call setmetatable on it.

1 Like

It seems to be because self wasn’t a metatable before, and the type declaration for ClassType is a metatable.

1 Like

I have done this method before but not with strict mode.

--!nonstrict

local BaseClassController = require(script.BaseClassController)
local Example = setmetatable({}, BaseCharacterController)
Example.__index = Example

function Example.new()
	local self = setmetatable(BaseClassController.new() :: any, Example)
	-- this will now inherit all of the things in baseclass

	self.aaaa = nil
    self.character = self:GetCharacter() -- method stored inside of BaseClassController 
    -- which is now autocompletable.

	return self
end
2 Likes

Bumppppppppppppppaaaaaaaaaaaaa

-- Assuming BaseMovement is correctly defined elsewhere
local BaseMovement = require(path.to.BaseMovement)
local Trove = require(path.to.Trove) -- Make sure this is correctly required

local Sprint = setmetatable({}, {__index = BaseMovement})
Sprint.__index = Sprint

-- Assuming you have a valid `ClassType` for Trove or adjust as necessary
export type SprintType = {
	_connections: any; -- Adjust based on your Trove type definitions
	Bind: (self: SprintType, actionName: string, inputState: Enum.UserInputState, _inputObject: any) -> ();
} & BaseMovement.ClassType -- Assuming BaseMovement has a type definition to inherit

function Sprint.new(): SprintType
	local self = setmetatable({
		_connections = Trove.new(),
	}, Sprint)
	
	return self
end

function Sprint:Bind(actionName, inputState, _inputObject)
	if inputState == Enum.UserInputState.Begin then
		-- Assuming CameraTweenIn is defined elsewhere
		CameraTweenIn:Play()
	end
	
	if inputState == Enum.UserInputState.End then
		-- Assuming CameraTweenOut is defined elsewhere
		CameraTweenOut:Play()
	end
end

return Sprint
1 Like

I got a very long warning:

Full Code:

--!strict
--[[
	Sprint sub-class
--]]

--// roblox services
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local TweenService = game:GetService("TweenService")
--// variables
local CurrentCamera = workspace.CurrentCamera
local CameraTweenIn = TweenService:Create(CurrentCamera, TweenInfo.new(0.2), {FieldOfView = 75})
local CameraTweenOut = TweenService:Create(CurrentCamera, TweenInfo.new(0.2), {FieldOfView = 70})
--// Dependencies
local PlayerSettings = require(ReplicatedStorage.PlayerSettings)
local BaseMovement = require(script.Parent.BaseMovement)
local Trove = require(ReplicatedStorage.Packages._Index["sleitnick_trove@1.1.0"]["trove"])
--// class
local Sprint = setmetatable({}, {__index = BaseMovement})
Sprint.__index = Sprint

export type ClassType = {
	_connections: Trove.ClassType,
	Bind: (self: ClassType, actionName: string, inputState: Enum.UserInputState, _inputObject: any) -> ();
} & BaseMovement.ClassType -- Assuming BaseMovement has a type definition to inherit

--// constructor
function Sprint.new(): ClassType
	local self = setmetatable({
		_connections = Trove.new(),
	}, Sprint)

	return self
end

function Sprint.Bind(self: ClassType, actionName, inputState, _inputObject): ()
	if not self.character then
		return
	end

	local Character = self.character
	local Humanoid = Character:FindFirstChild("Humanoid") :: Humanoid
	
	if inputState == Enum.UserInputState.Begin then
		CameraTweenIn:Play()
		Humanoid.WalkSpeed = PlayerSettings.CharacterSprintSpeed
	end

	if inputState == Enum.UserInputState.End then
		CameraTweenOut:Play()
		Humanoid.WalkSpeed = PlayerSettings.CharacterWalkSpeed
	end
end

function Sprint.GetParameters()
	return {
		actionName = "Sprint", -- mandatory
		createTouchButton = false, -- optional, will always be false on default
		inputs = { -- mandatory
			Enum.KeyCode.LeftShift,
			Enum.KeyCode.RightShift
		}
	}
end

return Sprint.new()

When I removed the Bind Type, I got a shorter warning:

Despite the warning, the autocomplete works!

Here’s the Base Class if you need it. Thanks for your help :blush:.

--!strict

--[[
	The base class for 'movement' // superclass
--]]

--// roblox services
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
--// Dependencies
local Trove = require(ReplicatedStorage.Packages._Index["sleitnick_trove@1.1.0"]["trove"])
--// class
local BaseMovement = {}
BaseMovement.__index = BaseMovement

export type ClassType = typeof( setmetatable({} :: {
	character: Model?;
	BaseConnections: Trove.ClassType;
	
	isWalking: boolean;
	isSprinting: boolean;
	isWallrunning: boolean;
	isFloating: boolean;
	isSliding: boolean;
	
}, BaseMovement) )

function BaseMovement.new(): ClassType
	local self = {
		character = nil,
		-- states?
		isWalking = false,
		isSprinting = false,
		isWallrunning = false,
		isFloating = false,
		isSliding = false,
		
		
		BaseConnections = Trove.new()
	}
	
	setmetatable(self, BaseMovement)
	
	task.spawn(function()
		while task.wait(3) do
			print(self.isSprinting)
		end
	end)
	
	self:_init()
	
	return self
end

function BaseMovement._init(self: ClassType): ()
	self.BaseConnections:Connect(Players.LocalPlayer.CharacterAdded, function(character: Model) 
		if not self.character then
			self.character = character
		end
	end)

	if Players.LocalPlayer then
		if not self.character then
			self.character = Players.LocalPlayer.Character:: Model
		end
	end
end

function BaseMovement.GetCharacter(self: ClassType): ()
	return self.character
end

return BaseMovement

Here is some improvements integrated into your base class script if you need it

--!strict

--[[
    The base class for 'movement' // superclass
--]]

--// roblox services
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
--// Dependencies
local Trove = require(ReplicatedStorage.Packages._Index["sleitnick_trove@1.1.0"]["trove"])
--// class
local BaseMovement = {}
BaseMovement.__index = BaseMovement

export type ClassType = typeof(setmetatable({} :: {
    character: Model?;
    BaseConnections: Trove.ClassType;

    isWalking: boolean;
    isSprinting: boolean;
    isWallrunning: boolean;
    isFloating: boolean;
    isSliding: boolean;

}, BaseMovement))

function BaseMovement.new(): ClassType
    local self = {
        character = nil,
        -- states?
        isWalking = false,
        isSprinting = false,
        isWallrunning = false,
        isFloating = false,
        isSliding = false,

        BaseConnections = Trove.new()
    }

    setmetatable(self, BaseMovement)

    task.spawn(function()
        while task.wait(3) do
            print(self.isSprinting)
        end
    end)

    self:_init()

    return self
end

function BaseMovement._init(self: ClassType): ()
    self.BaseConnections:Connect(Players.LocalPlayer.CharacterAdded, function(character: Model)
        if not self.character then
            self.character = character
        end
    end)

    if Players.LocalPlayer.Character then
        self.character = Players.LocalPlayer.Character
    end
end

function BaseMovement.GetCharacter(self: ClassType): Model?
    return self.character
end

return BaseMovement
1 Like

Here’s an update on the ClassType

export type ClassType = typeof( setmetatable({} ::  {
	_connections: Trove.ClassType;
	--Bind: (self: ClassType, actionName: string, inputState: Enum.UserInputState, _inputObject: any) -> ();
} , Sprint) ) & BaseMovement.ClassType

I added the typeof metatable which I original had. It didn’t do anything and I still have the warning though.

It defining a custom class type

-- Assuming Sprint and BaseMovement.ClassType are defined elsewhere and compatible.
local MyClass = setmetatable({}, Sprint)
MyClass.__index = MyClass

function MyClass.new()
    local instance = setmetatable({
        _connections = {}; -- Assuming Trove.ClassType or similar initialization here.
    }, MyClass)
    return instance
end

-- Example method definition
function MyClass:ExampleMethod()
    print("Example method called")
end

-- Assuming BaseMovement.ClassType has its methods and properties defined elsewhere.
-- You would then manually ensure that MyClass includes those methods and properties,
-- either through direct definition or metatable manipulation.

return MyClass
1 Like
--!strict
--[[
	Sprint sub-class
--]]

--// roblox services
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local TweenService = game:GetService("TweenService")
--// variables
local CurrentCamera = workspace.CurrentCamera
local CameraTweenIn = TweenService:Create(CurrentCamera, TweenInfo.new(0.2), {FieldOfView = 75})
local CameraTweenOut = TweenService:Create(CurrentCamera, TweenInfo.new(0.2), {FieldOfView = 70})
--// Dependencies
local PlayerSettings = require(ReplicatedStorage.PlayerSettings)
local BaseMovement = require(script.Parent.BaseMovement)
local Trove = require(ReplicatedStorage.Packages._Index["sleitnick_trove@1.1.0"]["trove"])
--// class
local Sprint = setmetatable({}, {__index = BaseMovement})
Sprint.__index = Sprint

export type ClassType = typeof( setmetatable({} ::  {
	_connections: Trove.ClassType;
	--Bind: (self: ClassType, actionName: string, inputState: Enum.UserInputState, _inputObject: any) -> ();
} , Sprint) ) & BaseMovement.ClassType

--export type ClassType = {

--// constructor
function Sprint.new(): ClassType
	local self = setmetatable(BaseMovement.new() :: any, Sprint)
	
	self._connections = Trove.new()
	
	return self
end

function Sprint.Bind(self: ClassType, actionName, inputState, _inputObject): ()
	if not self.character then
		return
	end
	
	local Character = self.character
	local Humanoid = Character:FindFirstChild("Humanoid") :: Humanoid
	
	if inputState == Enum.UserInputState.Begin then
		self.isSprinting = true
		CameraTweenIn:Play()
		Humanoid.WalkSpeed = PlayerSettings.CharacterSprintSpeed
	end

	if inputState == Enum.UserInputState.End then
		CameraTweenOut:Play()
		Humanoid.WalkSpeed = PlayerSettings.CharacterWalkSpeed
		self.isSprinting = false
	end

end

function Sprint.GetParameters()
	return {
		actionName = "Sprint", -- mandatory
		createTouchButton = false, -- optional, will always be false on default
		inputs = { -- mandatory
			Enum.KeyCode.LeftShift,
			Enum.KeyCode.RightShift
		}
	}
end

return Sprint.new()

local self = setmetatable(BaseMovement.new() :: any, Sprint) was able to remove the warning. Thanks for your help!

2 Likes