Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Callback arguments not inferred correctly on function overloads in new solver #1412

Open
avion-sandwich-gout-television-asterion opened this issue Sep 14, 2024 · 1 comment
Labels
bug Something isn't working new solver This issue is specific to the new solver.

Comments

@avion-sandwich-gout-television-asterion
Copy link

I'm using Charm state management library on Roblox Studio, I enabled the long awaited new Beta solver feature.
Although, it now requires me to explicitly cast the argument inside callbacks when I want to mutate a state.
Which is not something I should do when I have to mutate state, I expect the argument inside the callback (referred as "old" in the examples) to be implicitly typed, or else it would be harder to work with more complex types like string constants.

This bug happens on the type solver beta, and worked perfectly before.

It seems to only happen when the arguments in the callback are from an overload:

type SetAtom<T> = (newValue: T) -> T -- May be any function maybe
type DispatchAtom<T> = (callback: (old: T) -> T) -> T -- I expect old to be T

type Atom<T> = DispatchAtom<T> -- Inferred correctly
type Atom<T> = DispatchAtom<T>&SetAtom<T> -- Not inferred correctly

Here is a replica of the bug by recreating the types seen in Charm:

--!strict
-- This type is used in Charm (https://github.com/littensy/charm)
-- Placeholder function
local function atom(initialValue)
	return function(...)
		return initialValue
	end
end

-- Set the state using a callback similar to most UI librairies with state management
type DispatchAtom<T> = (callback: (old: T) -> T) -> T

-- Set the state old fashionned way
type SetAtom<T> = (newValue: T) -> T

-- Get the state
type Molecule<T> = () -> T

-- An example of Charm state, which have 3 ways of interacting with it
type Atom<T> = Molecule<T>&DispatchAtom<T>&SetAtom<T>

local coins = atom(10) :: Atom<number>

local a = coins(10) -- OK
local b = coins() -- OK
local c = coins(function(old)
	-- old is inferred as unknown here, 
	-- old must be inferred as number
	-- this can be worked around by explicitly typing old as number
	-- will even throw a warning telling that the overload argument is not compatible
	return old + 1
end) -- Not OK
-- c is inferred as a number, OK I guess ?

You can also reproduce the issue by using Charm directly:

--!strict
local charm = require("charm")

local coins = charm.atom(10) --OK
local a = coins() --OK
local b = coins(10) --OK

local c = coins(function(old)
	-- old is still inferred as unknown, or even "a"
	return old + 1
end) -- Not OK

info

image

@avion-sandwich-gout-television-asterion avion-sandwich-gout-television-asterion added the bug Something isn't working label Sep 14, 2024
@avion-sandwich-gout-television-asterion avion-sandwich-gout-television-asterion changed the title Callback arguments not inferred correctly on function overloads Callback arguments not inferred correctly on function overloads in new solver Sep 14, 2024
@avion-sandwich-gout-television-asterion
Copy link
Author

It seems that function overloads themselves makes Luau panic with various features! Just saw it with type literals!
Be sure to throw some unit testing for overloads specifically :<

-- In most cases, it is inferred as string, and not a literal.
local gameState = charm.atom("Intermission") :: charm.Atom<"Intermission">
gameState("Intermission") -- Inferred as string, not OK!

local gameState = charm.atom("Intermission") :: charm.Atom<"Intermission"|"Game">
gameState("Intermission") -- Still not OK!

local gameState = charm.atom("Intermission"::"Intermission"|"Game")
gameState("Intermission") -- Still not OK!


-- Type casting to literals or any is a workaround
local gameState = charm.atom("Intermission") :: charm.Atom<"Intermission"|"Game">
gameState("Intermission" :: "Intermission") -- OK?!

local gameState = charm.atom("Intermission"::"Intermission"|"Game")
gameState("Intermission" :: "Intermission") -- OK?!

while task.wait(1) do
	local state = Defs.GlobalState.State -- Is a reference to a charm atom
	if state() == "Game" then -- Literal comparison works, OK
		state("Intermission") -- Not OK
	else
		state("Game") -- Not OK
	end
end

@aatxe aatxe added the new solver This issue is specific to the new solver. label Sep 14, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working new solver This issue is specific to the new solver.
Development

No branches or pull requests

2 participants