Type checking warning after checking for condition?

While checking for a condition in my killstreak class, I cannot then reassign the value within the statement, eg.

Killstreak.End = function(self: Killstreak): boolean
	if self._isActive then
		self._isActive = false --Type Error: Type 'false' could not be converted to 'true'
	end
end

I presume this is an issue related to type inference, but how can it be avoided while still asserting a value within the function to keep the code safe? Using assert() does the same thing.

It is likely that you have annotated _isActive as “true” in your Killstreak type instead of “boolean”

--!strict
type X = {
    Y: true,	
}


local Z: X = {Y = true}


Z.Y = false -- Type 'false' could not be converted to 'true'

Vs.

--!strict
type X = {
    Y: boolean,	
}


local Z: X = {Y = true}


Z.Y = false -- Good!
1 Like

The _isActive property is annotated as boolean, not true. I think your example works because local Z: X = {Y = true} is an assignment of a table of type X to a variable, also of type X. My problem is that during the condition where self._isActive is stated to be true for all code that runs within the block, the type solver infers the attribute as, correctly, true. This causes the variable to become nearly immutable in the eyes of the type solver, as it can, and can only be, of the value that the expression evaluates to a truthy value for, and so I cannot do anything involving the assertion of a variable before assigning it a new value.

Please send your code, that way I can see what’s going on

Killstreak Class and it’s object type:


local Killstreak = {}
Killstreak.__index = Killstreak

export type Killstreak = {
	_started: DateTime,
	_ended: DateTime?,
	_isActive: boolean,
	_players: {string},
	_hostPlayer: string,
	
	new: (pPlayer: string, pPreAlloc: {string}?) -> Killstreak,
	
	End: (self: Killstreak) -> (boolean, string),
	AddPlayer: (self: Killstreak, pPlayerId: string) -> nil,
	
	IsActive: (self: Killstreak) -> boolean,
	GetPlayerIds: (self: Killstreak) -> {string}?,
	GetPlayerLength: (self: Killstreak) -> number,
	GetTimeElapsed: (self: Killstreak) -> DateTime,
	GetTimeStart: (self: Killstreak) -> DateTime,
	GetTimeEnd: (self: Killstreak) -> DateTime,
	GetHostPlayerId: (self: Killstreak) -> string
}

Object constructor:

Killstreak.new = function(pPlayer: string, pPreAlloc: {string}?): Killstreak
	local self = {}
	self._started = DateTime.now()
	self._isActive = true
	self._players = pPreAlloc or {}
	self._hostPlayer = pPlayer
	return setmetatable(self :: Killstreak, Killstreak)
end

End Method:

Killstreak.End = function(self: Killstreak): (boolean, string)
	if not self._isActive then
		return false, "Killstreak is already inactive"
	end
	
	self._isActive = false --Moved the assignment so the assert can happen at the start
                             of the method, the type checker still gives the same warning
	self._ended = DateTime.now()
end

With the code you have provided, I am unable to replicate the error. The typing engine has some issues with staying up-to-date, so you may have made made a change somewhere the masks an original conflict. Try restarting Roblox Studio or closing all your scripts with Ctrl + W, then reopening your problem-script. If the error still persists, then we’ll have to do more digging.

On another note, your constructor has a type conflict. Write the following instead:

return setmetatable(self, Killstreak) :: any

It’s also best that you cast self to the Killstreak type before assembling it:

local self = {] :: Killstreak

Thanks for pointing out the conflict, I probably should also have mentioned that I’m using Roblox’s new type solver, which I forgot is still in beta

Does it complain if you try to coerce?

    self._isActive = false::boolean

I had luck quieting the checker by splitting the class and instance into sep types when I ran into something similar. Something like below to experiment with if all else fails.

local Killstreak: KillstreakImpl = {} :: KillstreakImpl 
Killstreak .__index = Killstreak

export type KillstreakImpl = {
	new: (pPlayer: string, pPreAlloc: {string}?) -> Killstreak,
	End: (self: Killstreak) -> (boolean, string),
	AddPlayer: (self: Killstreak, pPlayerId: string) -> nil,
	IsActive: (self: Killstreak) -> boolean,
	GetPlayerIds: (self: Killstreak) -> {string}?,
	GetPlayerLength: (self: Killstreak) -> number,
	GetTimeElapsed: (self: Killstreak) -> DateTime,
	GetTimeStart: (self: Killstreak) -> DateTime,
	GetTimeEnd: (self: Killstreak) -> DateTime,
	GetHostPlayerId: (self: Killstreak) -> string
}

export type Killstreak = typeof(
	setmetatable({} :: { 
		__index: KillstreakImpl,
		_started: DateTime,
		_ended: DateTime?,
		_isActive: boolean,
		_players: {string},
		_hostPlayer: string,
	}, {} :: KillstreakImpl))

-- Constructor
function Killstreak.new(pPlayer: string, pPreAlloc: {string}?): Killstreak
	local self = setmetatable({}, Killstreak)::Killstreak

It still warns when I coerce it, though no with ‘Could not convert type ‘boolean’ to ‘true’’. I’ll try to split the types once I have the time to.

:+1: Found the ref link for the splitting class and inst (second black box after permalink):
Type checking - Luau