I’m wanting to use type checking in my parameters, but it shows an error
local function PrintModelPart(model: Model)
print(model.A)
end
local Model = Instance.new("Model")
local Part = Instance.new("Part")
Part.Name = "A"
Part.Parent = Model
PrintModelPart(Model)
In the function PrintModelPart you typecast model as specfically a Model. Type checking cannot assume anything more than a pure Model (in other words, it can only be sure of the properties, methods, and events of a Model class instance, but cannot infer anything about any children).
There is a method of (sort of) solving this, however it requires “prefabs” to exist instead of dynamic creation through code:
--Assume there is a Model named "MyModel" with a single child Part named "A" parented to the script this code is in
type MyModel = typeof(script.MyModel)
local function PrintModelPart(model: MyModel)
print(model.A) --This should have code completion and no annoying squiggly lines!
end
--You can use this type for creational pattern functions as well:
local function createMyModel(): MyModel
return script.MyModel:Clone()
end
--And later on still be able to use the type...
local newMyModel = createMyModel()
newMyModel.A.Color = Color3.new(1, 0, 0) --Again, this should have code completion and be perfectly valid in the eyes of the linter!
You can make your own type that includes the properties of a Model aswell as your own like this:
type myModel = Model & {
A: Instance
} -- this is called an intersection, https://create.roblox.com/docs/luau/type-checking#unions-and-intersections
local function PrintModelPart(model: myModel)
print(model.A) -- no more squiggly
end
I used to use this method as well, however I have had issues using it under --!strict and the beta type solver and have heard that it’s not an intended way to construct custom instance hierarchies.
Just general annoyances with the linter giving me warnings when I don’t want it to. Here’s an example of a few pain points:
--!strict
type CustomFolder = Folder & {
PartA: Part
}
local function createCustomFolder(): CustomFolder
local folder = Instance.new("Folder")
local partA = Instance.new("Part")
partA.Name = "PartA"
return folder --Type error under strict mode
end
--Assume there is a Folder (named "CustomFolder") in workspace that matches what you'd expect from the type CustomFolder
local workspaceCustomFolder: CustomFolder = workspace.CustomFolder --Type error under strict mode
local worksWithoutTypeAssignThough = workspace.CustomFolder --No type error here!
local function takesCustomFolder(arg: CustomFolder)
end
takesCustomFolder(workspaceCustomFolder) --No type error here!
takesCustomFolder(worksWithoutTypeAssignThough) --Type error under strict mode
Huh, interesting… it gives out the error that not all intersection parts are compatible – wonder why?
If it would interest you, I actually found a way to fix the error under strict mode – type casting seems to do the trick.
--!strict
type CustomFolder = Folder & {
PartA: Part
}
local function createCustomFolder(): CustomFolder
local folder = Instance.new("Folder")
local partA = Instance.new("Part")
partA.Name = "PartA"
return (folder :: CustomFolder) -- no more type error, still works as intended
end
Yeah, casting can sometimes fix the issue, but as the codebase grows the amount of places that you need to type cast gets to be ridiculous (that is, as long as you can avoid the dreaded “Cannot cast … the types are unrelated” type error!).
I’m pretty sure the type errors are technically (albeit annoyingly so) correctly reported in most cases, and it likely has more to do with a flaw in the design of creating types in that manner / using strict mode where it should not be used.