Rules

Look up any error shot check prints: what causes it, and how to fix it.

shot check prints one line per error: <project>/<file>:<line>:<column> <message>. The column is missing when the error is about a whole line. Each message below is written as shot check prints it, with a note beside it when the message alone doesn't say what to do. Names in the messages are examples.

Parse errors: the ShotScript layer

These come from reading the ShotScript layer of a file: its declaration, its members and the keywords around them.

File level

  • a file can only declare one class, module or func one declaration per file; move the rest out
  • a file can only have one types line
  • a file can only have one rootdoc a doc above a func counts as its rootdoc
  • doc must sit above a member inside a class or module a doc below the declaration
  • doc { is never closed with } (also rootdoc {)
  • @server goes on the first line of the file
  • @client needs enabled or disabled after it: @client enabled
  • import needs: import Name[, Name] from path, or import * from path
  • public type needs: public type Name = type (also internal type)
  • class Shape: expected = { after the name class Shape = { ... }, with the =

Class bodies

  • class Shape is never closed with }
  • class Shape: expected public, internal, override, constructor or doc, got 'private'
  • a class method has no function keyword: public Name(...) ... end public function Get() written the Luau way
  • class Shape: a method starts with public, internal or override and has no function keyword: public Name(...) ... end
  • class Shape: a helper only this file uses goes above the class, as a local function
  • module Bank: module members take no public (a module is all public; a helper only this file uses is a local function above it): function Name(...) ... end
  • module Bank: a local goes above the module, not inside it
  • class Shape: extends needs a class name
  • class Shape: implements needs an interface name
  • required is for static functions: public required static Name(...) (the game must call it somewhere)
  • static is for functions only. A field with a value is already a class property: public Speed : type = value any static field; write public Speed : number = 10
  • Speed: a class property needs a type and a value: Speed : type = value public Speed : number = (nothing after the =)
  • override static: a static function belongs to its class and can't be overridden

Module bodies

  • module Bank is never closed with }
  • module Bank: property Count needs a value (Count : type = value)
  • module Bank: expected a property (Name : type = value), function or doc, got 'constructor' modules have no constructors

Functions (methods, constructors, module functions, funcs)

  • public Damage is never closed with end
  • public: expected a name
  • Damage: expected ( after the name
  • Damage: ( is never closed with )
  • Box: < is never closed with >
  • constructor new: a constructor always returns Shape, so it takes no return type (drop : Shape)

Interface bodies (see Interfaces)

  • interface Payer is never closed with }
  • interface Payer: a member is only its signature, Name(params) : Return, or a field, Name : Type (all public, no body, no public)
  • interface Payer: an interface has no generics

match (see Match)

  • match: each case starts its line with its value and then, like "Pickup" then (several: "Pickup", "Shop" then)
  • match needs at least one case, like "Pickup" then ...
  • match is never closed with end
  • match: a case after else is never reached (else goes last)
  • match: only one else

uses (see Uses)

  • uses Wallet Utils: give folders from the project root, like uses Wallet, Market, Utils/Signal (or uses nothing, uses everything)
  • a file can only have one uses line

split module / diffsource func

  • split module Market is never closed with }
  • split module Market has no Client -> line (or Server)
  • split module Market: Client is given twice
  • split module Market: Client -> needs a path
  • split module Market: expected Client -> path or Server -> path, got 'Other'

After a broken line in a class or module body, the parser stays quiet until the next real member, so one unclosed ( is one error.

Project errors: Types lines and imports

These come from following a file's types line and its folder's imports to the files they name.

The types line

  • types path must start with script or @shot: Types
  • types file not found: script.Parent.Typse

Imports

  • import: nothing at @shot.Prizs no file there; a folder without an entry point is imported through one of its files
  • import: @shot.Wallet has no public type Reasn not there, or not written yet
  • import: Slot is internal to @shot.Wallet (class types and internal types stay in their folder)

ShotScript's rules

The file reads fine but breaks one of ShotScript's rules. They are grouped by topic, most with the doc that explains the rule.

Files

  • Wallet.shot declares Walet: the name must match the file
  • declares nothing yet: a .shot file needs one class, module or func, or @client / @server on its first line to be a script
  • needs a types line (types script.Parent.Types)
  • a Types file holds imports and types only, not a class
  • Runner.shot is a @client script: a script runs and returns nothing, so it can't declare a class
  • ShapeClass is the generated type of class Shape: pick another name
  • import only works in a Types file: move it to this folder's Types.shot (every file of the folder sees what it imports)

Docs (see Doc comments)

  • a doc can only have one section Rules
  • `Bank.Giev` in this doc: Bank has no Giev (renamed or removed?)
  • `Nope.Thing` in this doc: Nope names nothing in this project, Roblox or Luau (renamed or removed?)
  • `Nope/MarketClient.Open` in this doc: no folder Nope (renamed or removed?)
  • `@shot.Wallet.Nope` in this doc: nothing at @shot.Wallet.Nope (renamed or removed?)
  • `self.Speed` in this doc: self only means something in a class's docs (renamed or removed?)

Requires and sides (see Server and client; use follows the same rules, see Files, USE)

  • client code requires server MarketServer (Market/MarketServer.shot)
  • both code requires server MarketServer (Market/MarketServer.shot) make the requiring file server-only
  • Client is a @client script (Init/Client.shot): a script can't be required
  • client code uses Market.Server the nil half of a split module

Classes (see Classes)

  • internal Shape._hp is used outside Shapes internal is folder-wide
  • init() only works inside a class
  • module only works inside a module: in class Shape use self (Shape.Name for statics and class properties)
  • parameter factor of Scale has no type: write factor : Type (untyped, it is any and mistakes with it go unnoticed)
  • class property Marks is one table shared by every object, and Mark changes it: give each object its own in the constructor (self.Marks = {}) a class property's table is shared; one that is only read is fine
  • internal Shape.Tick is used outside Shapes checked on the receiver's type when it is known (pen : Pen, use, require, self), by name otherwise
  • module is a module's own name in ShotScript: rename this one a local or parameter named module
  • IsDead already exists in Shape: mark it override
  • override Fly: no Fly in Shape or above
  • override Fly: class Bird extends nothing
  • override IsDead is (number) -> boolean but Shape.IsDead is () -> boolean parameter and return types must match
  • extends Shap: no class with that name
  • extends Shape: it lives in another folder, so require it first (local Shape = require(...))

Interfaces (see Interfaces)

  • Vault implements Payer but has no Worth() : number
  • Wallet implements Payer, but a member doesn't fit it: Property 'Pay' is not compatible. ... found by Luau's type checker
  • Chest implements Priced but its PriceFor is internal: what an interface promises is public
  • Chest implements Priced but its PriceFor is static, not a method PriceFor(userId : number) : number
  • implements Priced: no interface called Priced (shot find Priced)
  • implements Priced: Priced is not visible here; import it in this folder's Types.shot: import Priced from @shot.Market
  • use Payer: an interface is only a type, with nothing to require. ...
  • interface Payer can't be its folder's entry file: an interface has no module behind it. ...

match (see Match)

  • match on Reason has no case for "Prize" (added at Wallet/Types.shot:2): add the case, or an else
  • match case "Shpo" is not one of the values it matches on: "Gift" | "Pickup" | "Shop"
  • match on a string that is not a union of string values: shot can't tell whether its cases cover everything, so give it an else

required (see Modules, REQUIRED FUNCTIONS)

  • required Bank.Start is never called: the game runs without it

uses (see Uses)

  • Market may use Wallet and Utils (Market/Types.shot:1), not Prizes: add Prizes there (calling it and hearing its declared events both count as using it)
  • Wallet and Market would use each other (Market/Types.shot:1 lets Market use Wallet): drop one direction, e.g. turn a call into an event the caller declares in its Types.shot and the other imports, which reverses that link
  • uses goes in the system's Types.shot, where it covers every file of the folder

events (see Events)

  • CoinsGiven is Wallet's event (Wallet/Types.shot:3): only files in Wallet publish it
  • CoinsGiven is published only by Wallet, Shop (Wallet/Types.shot:1), and this publish is from Prizes: its listeners count on those publishers alone, so publish through one of them (call the function of theirs that publishes it), or, if Prizes is meant to publish it too, add it to the list: published by Wallet, Shop, Prizes at Wallet/Types.shot:1
  • event CoinsGiven: published by names Shpo, which is no system of this project (nearest: Shop); published by lists systems, the project's top folders, like published by Wallet
  • EventBus.Subscribe("CoinsGiven", ...) skips the declared payload of CoinsGiven(player : Player, amount : number, reason : Reason) (Wallet/Types.shot:3): write CoinsGiven.Subscribe(function(player : Player, amount : number, reason : Reason) ... end)
  • event CoinsGiven is already declared at Prizes/Types.shot:3: an event's name is its EventBus topic, so each name is declared once
  • note: event CoinsGiven is never heard; published at Wallet/Bank.shot:8 a note, not an error

owned fields (see Owned fields)

  • Purse.Coins belongs to Wallet: change it with WalletServer.Grant instead (Grant also tells the player how many coins they have; a write to Coins does not)
  • owns Purse.Coin: Purse has no field Coin (nearest: Coins)
  • owns Purse.Gems via WalletServer.Grnt: WalletServer has no function or method Grnt (nearest: Grant)
  • owns Chest.Gold: Chest is Prizes's (Prizes/Chest.shot), and a system owns only fields of its own classes and modules: move this line to Prizes/Types.shot
  • owns Purse.Add: Add is a method of Purse, not a field; owns guards a field (Purse has Coins and Gems), and the method goes after via
  • owns goes in the system's Types.shot, the system whose class or module has the field: move this line there

start order (see Start order)

  • Market.Start() runs before Wallet.Start() (line 8), but Market.Start must run after it (Market/Market.shot:12: after Wallet.Start {it prices gems with the rates Wallet loads}): move this line below line 8; or, to keep this order, make Market.Start do that without Wallet.Start (when it is first needed, not at start), then drop after Wallet.Start there; or if Market no longer needs that order, drop after Wallet.Start there
  • after Walet.Start {it prices gems with the rates Wallet loads}: no script calls Walet.Start() (nearest: Wallet.Start): write the name as Init/Server.shot calls it
  • after Market.Start and Market/Market.shot:12's after Wallet.Start {it prices gems with the rates Wallet loads} can't both hold: each wants its Start first. Keep the one that is still true and drop the other
  • a comma goes between two names: after Wallet.Start, Bank.Start
  • no script calls Start of Market/Market.shot, so its after can't be checked: call it from the script that starts the game (Init/Server.shot), or drop the clause

remotes (see Remotes)

  • Board.ByUser : {[number] : number} goes through WalletSnapshot: Roblox sends number keys as strings ("1002"), so a lookup by number on the other side reads nil; key it by tostring(id) ({[string] : number})
  • lists that can hold nil, mixed tables, functions and class instances in a payload, a payload list filled by index inside an if, and Fire calls or handlers that don't match the signature: the whole list is in Remotes

split and diffsource targets

  • split Market: nothing at Client -> script.Parent.MarketClien
  • diffsource UniqueIdentifier: Client -> script.Parent.UIDClient is not a func
  • diffsource UniqueIdentifier: Client -> UIDServer is a server func
  • diffsource UniqueIdentifier: Client is () -> string but Server is () -> number

Luau errors

Everything else comes from Luau's type checker (Studio's, the old solver), mapped back to the .shot line. The message starts with its kind: TypeError, SyntaxError, or a lint name. These are the usual ones.

Names, types and requires

  • TypeError: Unknown global 'Bank'; consider assigning to it first the name is used above its declaration (Modules), or a module function is called without module.
  • TypeError: Unknown type 'Pos' not in the folder's types: define it in Types.shot or import it (Types)
  • TypeError: Unknown type 'Wallet'; it is declared in Wallet: add import Wallet from @shot.Wallet to Market/Types.shot a public type of another system: the error names the import line and the Types file it goes in
  • TypeError: Unknown require: game/ReplicatedStorage/Shot/Prizes a folder without an entry point; require one of its files
  • SyntaxError: Invalid attribute '@shot' @shot followed by something that isn't a path (@shot alone, or @shot.); write @shot.System.File (Files)

Type checker traps (see Luau tips)

  • TypeError: Key 'Tool' not found in table '{ ["Food" | "Tool"]: number }'
  • TypeError: Expected this to be '"Food" | "Tool"', but got 'string'
  • TypeError: Key 'Id' is missing from 'true' in the type 'Def | true'
  • TypeError: Cannot call a value of type {Def} in union: {Def} | {| |}

Classes (see Classes)

  • DuplicateLocal: Function parameter 'self' already defined a method parameter named self (Classes, PARAMETERS)
  • TypeError: Cannot cast 'string' into 'number' because the types are unrelated a class property's default doesn't fit its type (Classes, CLASS PROPERTIES)