I was experimenting with the Type Solver after “require-by-string” got removed (I was using this feature a lot) while trying to make my Package Manager actually useful for the ones using it as well (not just the modules inside). All bugs in this report are related to the New Type Solver itself and thus I am not going to provide any links to games nor system information. If it helps, enabled Beta Features are listed below.
Enabled Beta Features
Assistant Mesh Generation
, Avatar Joint Upgrade
, Dragger QoL Improvements
, Haptic Effects
, Import Queue
, Improved Constraint Tool
, Incremental typechecking and autocompletion in studio
, Multilayer Wrap Fix
, New Luau type solver
, New Studio Camera Controls
, Next Gen Explorer
, Next Gen Studio Preview
, Preferred Text Size Settings
, Revamped Asset Manager
, Studio solid modelling improvements
, Testure Generator
, UI Drag Detectors
, Unified Lighting
, Video Uploads
(Why is there not a “generate list of names” )
In order to see the behaviour yourself, check out BugReport - Type Solver.rbxl (59.6 KB). I tried to describe everything directly inside of the Scripts but I will list it here as well (for people who do not wish to download the place).
With some stuff I am not entirely sure whether they are bugs or not. I thought all was just “how it works” before my friend told me certain things mentioned might be bugs due to the stuff working differently in TS. Still, TS is not luau and it is possible some of the stuff is intented to work as they work.
First case - Problems with Generics
type modules = {
RealTime: {
setTime: () -> ()
},
Logs: {
new: () -> ()
}
}
type getter = <name>(name: name | keyof<modules>) -> index<modules, name>
local GetModule = function() end :: getter
GetModule("") -- ✅ Type Solver gives hints when one starts typing inside the string
local RealTime = GetModule("RealTime") -- ⚠️ Return type: index<modules, name> = not resolved, even though it matches the hint
Real -- (the "resolved" type can be seen by starting to type the rest of the variable name)
Type Checker resolved the following code alright BUT it is not really practical to use (not only due to no autocomplete for the singleton :: "RealTime"
)
local RealTime_fixed = GetModule("RealTime" :: "RealTime")
This (missing autocomplete) happens due to having the generic as another option to the “name” param (at least that is what I think). That leaves the option to put, in fact, any variable in the function and thus regular string is not converted to a singleton.
It would really help to be able to specify the type the generic variable can accept, something like:
type getter = <name: keyof<modules>>(name: name) -> index<modules, name>
Sadly this is not possible as of right now.
Even though this behavour is expected in a way, I consider this a bug since there is no way of avoiding manual type refinement. (Even type functions won’t help as there is no way to turn string into a singleton (as I was told) which … makes sense.)
Another option would be this:
type getter2 = (name: keyof<modules>) -> any
local GetModule2 = function() end :: getter2
GetModule2("") -- ✅ Type Solver gives hints when one starts typing inside the string and the hints are converted to singletons (I think)
One looses the ability to set dynamic return type though.
Second case - Problems with Generics with dynamic params
I wanted to give my Package Manager the ability to handle versions as well though, so let’s imagine the following simplified scenario:
type modules = {
RealTime: {
["1.0.0"]: {
old: true
},
["2.0.0"]: {
new: true
}
},
Logs: {
["1.0.0"]: {
old: true
},
["1.0.1"]: {
new: true
}
}
}
type versions = {
RealTime: "1.0.0" | "2.0.0",
Logs: "1.0.0" | "1.0.1"
}
type getter = <n>(name: n, verion: keyof<index<versions, n>>) -> any
For this use case I gave up on trying to have hints for the name, having to manually retype the string with every module get call as well as giving up the option to return the module itself …
local GetModule = function() end :: getter
GetModule("RealTime" :: "RealTime", )
As you can see here, the second argument is not resolved either. This is probably intentional, having no way to detect the arguments among each other dynamically, but I wanted to provide Developers at least the options of the available versions. If they want to use a module they probably already know the name of it but the versions data would be helpful.
Not sure if being able to specify the generic type for this would help the case though (something like this:)
type getter = <n: keyof<versions>, v: keyof<index<versions, n>>>(name: n, verion: v) -> index<index<modules, n>, v>
If I wanted the Developer to be able to at least use the package values he wants to use, I would have to do this:
type getter2 = <n, v>(name: n, verion: v) -> index<index<modules, n>, v>
local GetModule = function() end :: getter2
local module = GetModule("RealTime" :: "RealTime", "2.0.0" :: "2.0.0")
which resolves the type at the next use of “module” variable (not even at the end of the GetModule()
call - definitely a bug)
This still comes at the cost of not having any hints AND having to retype everything manually.
Third case - Problems with Overloading using multiple params
I was ready to give up but luckily (I guess depends on who is the lucky one here) I remembered function overloading exists. So I went to it and tried them.
All the following getters are, in fact, constructed by a type function dynamically from a list of modules. I am putting here the simplified results of those.
type getter = ((name: "RealTime", version: "1.0.0") -> "@Module[RealTime@1.0.0]")
& ((name: "RealTime", version: "2.0.0") -> "@Module[RealTime@2.0.0]")
& ((name: "Logs", version: "1.0.0") -> "@Module[Logs@1.0.0]")
& ((name: "Logs", version: "1.0.1") -> "@Module[Logs@1.0.1]")
local GetModule = function(name, version) end :: getter
Sounds nice, doesn’t it? (I bet it won’t be that nice when there are going to be a few more modules but … whatever :D
) One gets autocomplete as well! But try to complete the following get call, what do you see during typing?
GetModule("RealTime", "1.")
Also my friend noted here that if the params are merged (unioned), “1.0.0” shouldn’t be there two times (on the right).
That is right, version “1.0.1”. Where did it get from?!
Well, I assume that all the params of the same name (actually the name is irrelevant, it is the position of the param as I will show later) are merged together which … makes sense. Otherwise the autocomplete for the names would not work. But my friend (that is using TS for Roblox projects) pointed out something …
local module = GetModule("RealTime", "1.0.1")
[NEVERMIND] … this should error. In TS, he got 5-line long error starting with “No overload matches this call.”
▀▀▀▀▀▀▀▀▀ ► I forgot to put “–!strict” in the script, my fault. But one still has to retype the values ._.
Take a look at the return type as well but I will get to that in the last example.
The Type Error when
--!strict
present (technically I did nothing wrong as normal user would not understand that he gave the function a string but it wants a singleton).
The description is too long to fit in here so I will send the rest as a reply. I would like to shorten it or write down some kind of summary of the problems so it is easier to get the “important” stuff but (after 4 hours of getting this together) I have no idea what is important and what is not; scared of cutting off the important stuff and adding the unimportant ones.
Expected behavior
I am trying to make a function that returns module
’s body based on a string
input which is the module’s name. My goal is the function to support module names hinting while being able to return the right module body without the excessive need to retype the name of the module to a singleton in each function call. (GetModule("RealTime")
rather than GetModule("RealTime" :: "RealTime")
)
Ideally, generic types would support their own allowed type (something like T extends number
in TS, for example) and type functions would be able to construct functions with param names as well. If nothing, Type Solver could at least convert strings to singletons if only singletons are allowed as the argument (which even though we cannot do in type functions directly, Type Solver is still capable of doing (otherwise GetModule2("RealTime@3.0.0").setTime()
in the fourth case would not be possible, for example)).