Checked paths

Walk from script to any object in your project and shot check knows what is there, so a misspelt child, a value of the wrong type or game is an error before anything runs.

A System project is a tree of folders, scripts and value objects (see One-file projects). shot check reads that tree and types every path in your .shot files from it: each child is a field of its parent, and each value object's Value has its class's type. A plain .luau file gets the same once it starts with --!strict.

A misspelt child

Get a name wrong and check points at it, before anything runs.

This page uses one small project, Shop.shotsystem: every command on it takes --project Shop.shotsystem. shot tree shows it:

Shop.shotsystem
./  (the project)
  Settings/
    Currency  (StringValue)
    Price  (NumberValue)
  Shop/  (folder module)
    Shop.shot
    Types.shot

Currency holds "Coins", Price holds 25 and has an attribute Max = 100, and Shop/Types.shot is empty. Shop reaches the settings by a path from script:

Shop/Shop.shot

types script.Parent.Types

local settings = script.Parent.Parent.Settings

module Shop = {
    function PriceText() : string
        return settings.Price.Value .. " " .. settings.Curency.Value
    end
}

shot check says:

Shop.shotsystem/Shop/Shop.shot:7:41   TypeError: Key 'Curency' not found in external type 'Folder'
1 errors in 1 file (2 files checked, 0.1 s)

The column is where the path starts. Write settings.Currency and it says:

0 errors (2 files checked, 0.1 s)

Where a path starts

Every path starts at script and climbs one folder per .Parent.

In Shop/Shop.shot:

scriptShop.shot itself, a ModuleScript (.shot and .luau files run as modules)
script.Parentthe Shop folder, a ModuleScript too because it holds Shop.shot
script.Parent.Parentthe project's root, a Folder named after the project
script.Parent.Parent.Parentan Instance? to check, and nil when the code runs: nothing is above the root

A plain folder, like Settings, is a Folder. root is the project's root only in scenarios (see Running a project): in a .shot or .luau file it is Unknown global 'root', so climb from script.

Values have Types

A value object's Value has its class's type, so a string can't slip into a sum or a comparison.

Add a function to Shop that compares coins with the wrong value:

function CanAfford(coins : number) : boolean
    return coins >= settings.Currency.Value
end

and check says:

Shop.shotsystem/Shop/Shop.shot:11:10   TypeError: Expected this to be 'number', but got 'string'
1 errors in 1 file (2 files checked, 0.1 s)

Compare with settings.Price.Value and it is 0 errors again. The type of Value, by class:

StringValuestring
NumberValue, IntValuenumber
BoolValueboolean
Color3ValueColor3
Vector3ValueVector3
ObjectValueInstance? (it may point at nothing)

No game, no services

Code brought over from a Roblox game is caught where it reaches for game.

A project's root has nothing above it: no DataModel, so no game, no workspace and no Roblox services. Start the path from game instead of script:

local settings = game.ReplicatedStorage.Settings

and check says:

Shop.shotsystem/Shop/Shop.shot:3:18   TypeError: Unknown global 'game'; consider assigning to it first
1 errors in 1 file (2 files checked, 0.1 s)

The fix is the path from script, script.Parent.Parent.Settings. workspace, Instance (so Instance.new), Enum and task are unknown globals the same way.

Rename an object

Rename an object and check finds every line that still uses the old name.

shot set Settings/Currency Name Money renames Currency:

renamed Settings/Currency.instance to Settings/Money.instance
files changed: Settings/Currency.instance, Settings/Money.instance (shot check shows what this breaks, if anything)

The object commands (create, destroy, set, attr, tag) don't run the check themselves. shot check then says:

Shop.shotsystem/Shop/Shop.shot:7:41   TypeError: Key 'Currency' not found in external type 'Folder'
1 errors in 1 file (2 files checked, 0.1 s)

Fix those lines, or take the rename back with shot undo (and check is back to 0 errors).

Attributes

Read an attribute and check makes you look at what you got.

An attribute is not a child, so settings.Price.Max is an error:

Shop.shotsystem/Shop/Shop.shot:15:10   TypeError: Key 'Max' not found in external type 'NumberValue'

Read it with GetAttribute("Max"). That is unknown?, since the attribute may be missing or hold anything, so returning it from a function that returns a number is an error too:

Shop.shotsystem/Shop/Shop.shot:15:3   TypeError: Expected this to be 'number', but got 'unknown?' caused by: Not all union options are compatible. Expected this to be 'number', but got 'unknown'

Check its type first:

function Limit() : number
    local max = settings.Price:GetAttribute("Max")
    return if type(max) == "number" then max else 0
end

A property hides a child

Name a child like a property and the property wins, in check and when the code runs.

Add a StringValue called Name to Settings (shot create StringValue Settings Name) and read it:

function Title() : string
    return settings.Name.Value
end

settings.Name is the folder's own name, a string, so check says:

Shop.shotsystem/Shop/Shop.shot:20:10   TypeError: Key 'Value' not found in table 'typeof(string)'

Rename the child (shot set Settings/Name Name Title) and read settings.Title.Value: 0 errors. The same goes for any name an instance already has, such as ClassName or Clone.

Finding a child by name

Look a child up by name and you keep its type, as long as the name is one the tree has.

FindFirstChild("Currency")a StringValue, like settings.Currency
FindFirstChild("Curency")an Instance?, so reading .Value on it is an error
FindFirstChild(name)an Instance? too, for a name held in a variable
GetChildren()a list of Instance, {Instance}
WaitForChild("Currency")an error in a .shot file: instances here have no WaitForChild

Instances here have no events either: settings.Price.Changed is a Key 'Changed' not found error.

Plain Luau files

A plain .luau module gets the same checked paths when its first line is --!strict.

Shop/Format.luau

--!strict
local settings = script.Parent.Parent.Settings

local Format = {}

function Format.Price(amount : number) : string
    return amount .. " " .. settings.Curency.Value
end

return Format

shot check says:

Shop.shotsystem/Shop/Format.luau:7:26   TypeError: Key 'Curency' not found in external type 'Folder'
1 errors in 1 file (3 files checked, 0.1 s)

Without the --!strict line the same file checks with 0 errors: Luau's looser mode lets a path kept in a local like settings through. A WaitForChild in a .luau file isn't reported either; it fails when the line runs.

What it doesn't check

  • Scenarios: shot check doesn't look at them (see Running a project). A misspelt child in one fails when it runs, with Curency is not a valid member of Settings.
  • shot check --fast: it skips the type check, so it finds no path mistakes.
  • An empty folder: a folder with nothing in it isn't in the tree check reads, so a path to it is Key 'Sales' not found in external type 'Folder' until something is put in it.
  • A cast: x :: StringValue is taken at its word.

Errors

TypeError: Key 'Curency' not found in external type 'Folder'
TypeError: Expected this to be 'number', but got 'string'
TypeError: Unknown global 'game'; consider assigning to it first
TypeError: Unknown global 'root'; consider assigning to it first
TypeError: Key 'Max' not found in external type 'NumberValue'
TypeError: Expected this to be 'number', but got 'unknown?' caused by: Not all union options are compatible. Expected this to be 'number', but got 'unknown'
TypeError: Key 'Value' not found in table 'typeof(string)'
TypeError: Value of type 'Instance?' could be nil
TypeError: Key 'Value' not found in external type 'Instance'
TypeError: Key 'Changed' not found in external type 'NumberValue'
WaitForChild is Roblox's: a ShotSystem instance has no WaitForChild (everything in the project is there when its code runs), so write the child's name, or FindFirstChild when it may be missing