luau
luau copied to clipboard
Cannot index ClassTypeVar's with variable as index
Example:
--!strict
local inst = Instance.new("Part")
local prop = "Name"
inst[prop] = "Test" -- Error: Expected type table, got 'Part' instead
Not sure if this classifies as a duplicate of #533
This one is expected, the thing that's actually unfortunate is the fact we allow this on tables.
Turning that into a type error is also on my to do list, but we need to replace it with another option that's safer, and more sound. I'm currently thinking that we need something like keyof T which would generate a union of all key types from T.
This being expected seems somewhat disappointing IMO - there are logically valid programs which throw false positives here.
One use case I can think of is programmatically updating a batch of properties on a single instance CTV.
I also model Enum's using CTVs, which causes an issue in code such as Enum.HumanoidRigType[getRigType()], but it seems this doesn't error in Studio as it uses a TTV, so that is an improvement I could make on my end (but making this a type error would break this).
Definitely agree modelling it more soundly with something like keyof T would be awesome though. But I imagine it will never be possible to be completely sound here, in particular if the only type known/inferred for prop is just a general string (or number for arrays)
The problem is that you can not model inst[prop] type unless prop is of a string literal type, and that's not terribly useful. If prop is a string, the type you can infer for inst[prop] is either any or a very large union of all possible property types...
The number for arrays makes sense but that's unsound only because indexers are inherently unsound.
We can infer prop in inst[prop] literally as keyof Part because keyof Part is stronger than string, so string & (keyof Part) results in keyof Part. Basically, the change would just be the inference rule for t[k] from just typeof(k) <: string into typeof(k) <: (keyof typeof(t)), and we don't necessarily need to solve for all the keys of t yet in deferred constraint resolution.
As for the result type of inst[prop], yeah the only valid result here is a union of all properties within Part, but then we also need its children. And that's not even talking about metatables with __index.
We can infer prop in
inst[prop]literally askeyof Part
Are there examples where this would be meaningfully richer? It's definitely more precise than not being able to infer anything, but I wonder if it's truly useful, especially in absence of a value type...
So, here's my specific use case - allowing for the automated deserialization of complex assets. In a framework I created as a more OOP focused version of Elttob's Fusion framework, I need to be able to pass on configured property states to the eventual instance. Here's some example code of what that currently looks like:
local listPadding = fuse.new "UIListLayout" {
Padding = fuse.Computed(Padding, function(padding)
if padding then
return UDim.new(0, padding.Offset*2)
end
end):Else(UDim.new(0,0)),
SortOrder = _PseudoEnum.SortOrder.LayoutOrder,
FillDirection = _PseudoEnum.FillDirection.Horizontal,
HorizontalAlignment = _PseudoEnum.HorizontalAlignment.Center,
VerticalAlignment = _PseudoEnum.VerticalAlignment.Top,
Parent = header,
}
I've ran into the same error, and was referred to here to comment my usecase.
Roblox has a Enum.StudioStyleGuideColor EnumItem which is only indexable in-studio. As my code supports being ran both in-studio and in-game, I implement UI through e.g. GetColor("ScrollBarBackground"), where GetColor returns Enum.StudioStyleGuideColor[ItemName] if the context is Studio, or indexing a table of constant values if ran in-game.
A workaround to the error could look as such:
local function GetColor(ItemName): EnumItem
for _, EnumItem in pairs(Enum.StudioStyleGuideColor:GetEnumItems()) do
if EnumItem.Name == ItemName then
return EnumItem
end
end
error(tostring(ItemName) .. "is not a valid member of \"Enum.StudioStyleGuideColor\"")
end
But this implementation is a lot more complex than what would otherwise be a single-line function. Being able to limit the scope of a type from string to e.g. keyof( typeof(Enum.StudioStyleGuideColor) ) would be very helpful.
The new solver has an implementation of keyof and rawkeyof now (see https://github.com/luau-lang/rfcs/pull/16), so in a broad sense, this style of problem should be solved in the new solver, but I'll put something with dynamic access of class fields into our test suite to make sure this gets dealt with if it's not already working. I don't see any reason why we shouldn't be able to do something better here, but indeed, the result types from the indexing may not be very specific, depending on the particular class you're working with.