Type Checking - NightrainsRbx/RobloxLsp Wiki

Attention: Type checking is currently in BETA and it could lack features, don't use it for serious projects, yet.

Type checking is heavily based on the official Luau type checking, but it's not completely the same.

Type checking mode

There are two modes, strict and non-strict, you can set the mode in robloxLsp.typeChecking.mode, by default is set to Disabled, which means that the type checker will not run.

In non-strict mode, the type checker will only check variables whose types can be explicitly known, it's a little bit stricter than Luau official type checking. This is the mode you should use if you want to avoid false positives, and in this mode, the type checker will run faster.

In strict mode, type inference will be performed on variables whose type is unknown, type inference in Roblox LSP is in some parts different than in Roblox Studio.

Changing the mode in a file with comments

In Roblox Studio, you can add at the top of the file --!strict, --!nonstrict or --!nocheck to specify the type inference mode in that file, wherein Roblox LSP this is different.

In Roblox LSP, use the comment [email protected] mode: name where name is strict, nonstrict, nocheck or default, the mode default is the one you set in robloxLsp.typeChecking.mode, the code below this comment will be checked with the mode you specified. You can change the type checking mode in different parts of your code.

local function func() 
    return true 
end
local var: string
--[email protected] mode: nonstrict
var = func()
--[email protected] mode: strict
var = func() -- Error: Type 'boolean' could not be converted into 'string'.
--[email protected] mode: nocheck
var = 2

These comments have no effect if the mode in robloxLsp.typeChecking.mode is set to Disabled.

Built-in types

Built-in types in Roblox LSP are also different than in Roblox Studio.

Primitive Types

There are primitive types like string, number, boolean, thread, nil, and userdata, these are just like in Roblox Studio.

Another primitive type is symbol, which is a type that can't be inferred from literals, it works pretty much like Typescript symbol, but it's not a unique symbol (meaning that is not tied to its declaration).

function and table also exist but they are for internal use, these two represent "any function" and "any table" respectively.

Utility Types

These are predefined types that can be useful, these types can be shadowed by your own type aliases:

  • Array<T>: { [number] : T }
  • Dictionary<T>: { [string] : T }
  • WithMeta<T, M>: T @metatable M

Third-Party Libraries

Roblox LSP has predefined types for some commonly used third-party libraries, for example Roact, it can automatically detect if the module you are requiring is one of those libraries.

At the moment, Roblox LSP supports:

  • Roact
  • Rodux
  • Roact-Rodux
  • TestEz (only in files ending with .spec.lua)

The types for these libraries are sometimes inside a namespace, for example in Roact, there is Roact.Component.

These types are defined in .luau files located in https://github.com/NightrainsRbx/RobloxLsp/tree/master/server/def, if you want to contribute, you can PR your own types or fix errors in others.

External Libraries

If you want to define a type alias that can be used across all your files, you can use external libraries, these are .lua files that are loaded by the server, and whose exported type aliases are available across all the workspace, you can define the path of these files in robloxLsp.workspace.library, they don't need to be inside your root folder. These files can't reference other external libraries.

In an .lua file set in robloxLsp.workspace.library:

export type MyType = {
    field: string
}

In other files in your workspace:

local var: MyType
var.field = 2 -- Error: Type 'number' could not be converted into 'string'.

Type Checking Options

You can change how type checking works with some options, they can be specified in robloxLsp.typeChecking.options.

ignore-extra-fields (boolean: false)

When enabled, any extra fields in a table in comparison to another will be allowed, for example:

local t1: {
    name: string
}
local t2: {
    name: string,
    age: number
}
t1 = t2 -- This gives an error when 'ignore-extra-fields' is set to false, otherwise it's accepted.

union-bivariance (boolean: false)

This was implemented because type refinements are not usable yet. If enabled, when comparing a union type with another type, it will check if it can be converted into that type or vice-versa (A to B or B to A).

local v1: string
local v2: string | number

v1 = v2 -- This gives an error when 'union-bivariance' is set to false, otherwise it's accepted.

v2 = v1 -- This is always ok.

Also, when checking if a union contains a field, ignores if it can't be found in other types in the union.

infer-instance-from-unknown (boolean: false)

When enabled, unknown members of Instance classes will be inferred as Instance, or Instance | any if the type is exactly Instance.

-- Here var is of type 'Instance', unless we know the type of `Something`.
-- If 'infer-instance-from-unknown' is set to false, this gives error if 'Something' is not found.
local var = game.ReplicatedStorage.Something

recursive-get-type (boolean: false)

When enabled and if the type checking mode is set to non-strict, when getting the explicit type of a variable, if it doesn't have one, the value is checked and successively. This makes non-strict stricter without resorting to more complex type inference.

local a: string

local b = a

local c: number = b -- Error: Type 'string' could not be converted into 'number'

Compatibility with EmmyLua annotations

EmmyLua comments are completely ignored by the type checker.

Using Selene and Type checking

Selene can be used by disabling robloxLsp.diagnostics.enable, this will disable diagnostics completely including syntax errors, but it won't disable type checking.

⚠️ **GitHub.com Fallback** ⚠️