# Require by String with Relative Paths

**Status**: Implemented

## Summary

We need to add relative paths to `require` statements to facilitate the grouping together of related Luau files into libraries and allow for future package managers to be developed and integrated easily.

## Motivation

The Roblox engine does not currently support require-by-string. One motivation for this RFC is to consolidate require syntax and functionality between the Roblox engine and the broader Luau language itself.

Luau itself currently supports a basic require-by-string syntax that allows for requiring Luau modules by relative or absolute path. Unfortunately, the current implementation has a few issues.

### Current Issues with Relative paths

Currently, relative paths are always evaluated relative to the current working directory that the Luau CLI is running from. This leads to unexpected behavior when requiring modules from “incorrect” working directories.

Suppose the module `math.luau` is located in `/Users/JohnDoe/LuauModules/Math` and contains the following:

    -- Beginning of /Users/JohnDoe/LuauModules/Math/math.luau
    local sqrt = require("../MathHelperFunctions/sqrt")

If we then launched the Luau CLI from the directory `/Users/JohnDoe/Projects/MyCalculator` and required `math.luau` as follows:

    local math = require("/Users/JohnDoe/LuauModules/Math/math")

This would cause the following:

  * The current implementation of require would successfully find `math.luau`, as its absolute path was given.
  * Then, it would execute the contents of `math.luau` from the context of the current working directory `/Users/JohnDoe/Projects/MyCalculator`.
  * When attempting to require `sqrt.luau` from `math.luau`, instead of looking in the directory `/Users/JohnDoe/LuauModules/MathHelperFunctions`, the relative path will be evaluated in relation to the current working directory.
  * This will look for `sqrt.luau` in `/Users/JohnDoe/Projects/MathHelperFunctions`, which is a directory that may not even exist.

This behavior is [problematic](https://github.com/Roblox/luau/issues/959), and puts an unnecessary emphasis on the directory from which the Luau CLI is running. A better solution is to evaluate relative paths in relation to the file that is requiring them.

## Design

### Package management

While package management itself is outside of the scope of this RFC, we want to make it possible for a package management solution to be developed in a way that integrates well with our require syntax.

To require a Luau module under the current implementation, we must require it either by relative or absolute path:

### String syntax

Our require syntax should:

  * Allow a library written in Luau to be imported into the Roblox engine and “just work”.
  * Support compile-time file resolution _where possible_ for type checking.
  * Be consistent across platforms and both inside and outside of Roblox.
  * Attempt to remain consistent with the existing Luau path syntax.

For compatibility across platforms, we will automatically map `/` onto `\`.

### Path resolution

**This section is now obsoleted by [this RFC](https://github.com/luau-lang/rfcs/pull/56):** The implicit resolution order described below is no longer supported. If multiple files exist that could match a given `require` path, an error is thrown.

If we find files with the same name but different extensions, then we will attempt to require a file with the following extensions (in this order):

  1. `.luau`
  2. `.lua`
  3. All other file extensions are invalid.

If the string resolves to a directory instead of a file, then we will attempt to require a file in that directory with the following name (in this order):

  1. `init.luau`
  2. `init.lua`

### Relative paths

**This section is now partially obsoleted by [this RFC](https://github.com/luau-lang/rfcs/pull/56):** Relative paths _must_ begin with either `./` or `../`, and the `paths` variable is no longer supported.

Modules can be required relative to the requiring file’s location in the filesystem (note, this is different from the current implementation, which evaluates all relative paths in relation to the current working directory).

If we are trying to require a module called `MyModule.luau` in `C:/MyLibrary`:

    local MyModule = require("MyModule")

    -- From C:/MyLibrary/SubDirectory/SubModule.luau
    local MyModule = require("../MyModule")

    -- From C:/MyOtherLibrary/MainModule.luau
    local MyModule = require("../MyLibrary/MyModule")

Relative paths may begin with `./` or `../`, which denote the directory of the requiring file and its parent directory, respectively. When a relative path does begin with one of these prefixes, it will only be resolved relative to the requiring file. If these prefixes are not provided, path resolution will fallback to checking the paths in the `paths` configuration variable, as described later.

When a require statement is executed directly in a REPL input prompt (not in a file), relative paths will be evaluated in relation to the pseudo-file `stdin`, located in the current working directory. If the code being executed is not tied to a file (e.g. using `loadstring`), executing any require statements in this code will result in an error.

### Absolute paths

Absolute paths will no longer be supported in `require` statements, as they are unportable. The only way to require by absolute path will be through a explicitly defined paths or aliases defined in configuration files, as described in another [RFC](https://github.com/Roblox/luau/pull/1061).

### Implementing changes to relative paths

The current implementation of evaluating relative paths (in relation to the current working directory) can be found in [lua_require](https://github.com/Roblox/luau/blob/e25de95445f2d635a125ab463426bb7fda017093/CLI/Repl.cpp#L133).

When reading in the contents of a module using [readFile](https://github.com/Roblox/luau/blob/e25de95445f2d635a125ab463426bb7fda017093/CLI/FileUtils.cpp#L47), the function `fopen`/`_wfopen` is called, which itself evaluates relative paths in relation to the CWD. In order to implement relative paths in relation to the requiring file, we have two options when evaluating a relative path.

**Assume the following:**

  * Current working directory: `"/Users/johndoe/project/subdirectory/cwd"`
  * Requiring file’s location: `"/Users/johndoe/project/requirer.luau"`
  * Relative path given to require by user: `"./sibling"`

**Approach 1: Translate to the “correct” relative path**

  * Translated relative path given to `fopen`/`_wfopen`: `"../../sibling"`

**Approach 2: Convert the given relative path into its corresponding absolute path**

  * Translated relative path given to `fopen`/`_wfopen`: `"/Users/johndoe/project/sibling"`

Although `fopen`/`_wfopen` can handle both relative (to CWD) and absolute paths, the second approach makes more sense for our use case. We already need absolute paths for caching, as explained in the “Caching” section, so we might as well generate these absolute paths during the path resolution stage. With the first approach, we would need to call `realpath` to convert the relative-to-CWD path into an absolute path for caching, which is an unnecessary extra step.

However, for security reasons, we don’t want to expose absolute paths directly to Luau scripts (for example, through `debug.info`). To prevent this, even though we will cache and read files by absolute path (which helps reduce cache misses), the `chunkname` used to load code [here](https://github.com/Roblox/luau/blob/e25de95445f2d635a125ab463426bb7fda017093/CLI/Repl.cpp#L152) will be the file’s path relative to the current working directory. This way, the output of `debug.info` will be unaffected by this RFC’s proposed changes.

  * In the example above, the requiring file’s stored `chunkname` would be `"../../requirer.luau"`, and its cache key would be `"/Users/johndoe/project/requirer.luau"` (was set when it was required).
  * When resolving the path `"./sibling"`, we would apply this to `"../../requirer.luau"`, obtaining the relative-to-cwd path `"../../sibling.luau"`.
  * This would become the `chunkname` of `sibling.luau` during loading.
  * This would then be converted to an absolute path `"/Users/johndoe/project/sibling"` for caching by resolving it relative to the CWD.

In the case of an aliased path, it doesn’t make sense to make the path relative to the CWD. In this case, the alias would remain in the `chunkname` to prevent leaking any absolute paths.

#### Where to begin traversal

One key assumption of this section is that we will have the absolute path of the requiring file when requiring a module by relative path.

While we could add an explicit reference to this directory to the `lua_State`, we already have an internal mechanism that allows us to get this information. We essentially want to call the `C++` equivalent of `debug.info(1, "s")` when we enter `lua_require`, which would return the name of the file that called `require`, or `stdin` if the module was required directly from the CLI.

As an example, we might do something like this in `lua_require`:

    static int lua_require(lua_State* L)
        lua_Debug ar;
        lua_getinfo(L, 1, "s", &ar);

        // Path of requiring file
        const char* basepath = ar.source;

        // ...

#### Impact on `debug.info` output

The current implementation also has a slight inconsistency that should be addressed. When executing a Luau script directly (launching Luau with a command-line argument: `"luau script.luau"`), that base script’s name is internally stored with a file extension. However, files that are later required are stored with this extension omitted. As a result, the output of `debug.info` depends on whether the file was the base Luau script being executed or was required as a dependency of the base script.

For consistency, we propose storing the file extension in `lua_require` and always outputting it when `debug.info` is called.

## Use cases

### Improvements to relative-path requires

By interpreting relative paths relative to the requiring file’s location, Luau projects can now have internal dependencies. For example, in [Roact’s current implementation](https://github.com/Roblox/roact), `Component.lua` requires `assign.lua` [like this](https://github.com/Roblox/roact/blob/beb0bc2706b307b04204abdcf129385fd3cb3e6f/src/Component.lua#L1C1-L1C45):

    local assign = require(script.Parent.assign)

By using “Roblox-style” syntax (referring to Roblox Instances in the require statement), `Component.lua` is able to perform a relative-to-requiring-script require. However, with the proposed changes in this RFC, we could instead do this with clean syntax that works outside of the context of Roblox:

    local assign = require("./assign")

(Of course, for this to work in the Roblox engine, there needs to be support for require-by-string in the engine. This is being discussed internally.)

## Drawbacks

### Backwards compatibility

Luau libraries are already not compatible with existing Lua libraries. This is because Lua favors the `.` based require syntax instead and relies on the `LUA_PATH` environment variable to search for modules, whereas Luau currently supports a basic require-by-string syntax.

  * Libraries are fully compatible with the Roblox engine, as require-by-string is currently unimplemented.
  * Luau currently implements relative paths in relation to the current working directory.
  * We propose changing this behavior and breaking backwards compatibility on this front.
  * With the current implementation, requiring a library that itself contains relative-path require statements [can become a mess](https://github.com/Roblox/luau/issues/959) if the Luau VM is not launched from the “correct” working directory.
  * We propose the following change: relative paths passed to require statements will be evaluated in relation to the requiring file’s location, not in relation to the current working directory.
    * Caveat 1: if the relative-path require is executed directly in a REPL input prompt (not in a file), it will be evaluated relative to the pseudo-file `stdin`, located in the current working directory.
    * Caveat 2: if the code being executed is not tied to a file (e.g. using `loadstring`), executing require statements contained in this code will throw an error.
  * If this causes issues, we can later introduce a default alias for the current working directory (e.g. `@CWD`).

## Alternatives

### Different ways of importing packages

In considering alternatives to enhancing relative imports in Luau, one can draw inspiration from other language systems. An elegant solution is the package import system similar to Dart’s approach. Instead of relying on file-specific paths, this proposed system would utilize an absolute `package:` syntax:

    import 'package:my_package/my_file.lua';

Undesirable because this would be redundant with the [alias RFC](https://github.com/Roblox/luau/pull/1061).


* [Home](https://docs.sentry.io/)
  * [Platforms](https://docs.sentry.io/platforms/)
  * [Native](https://docs.sentry.io/platforms/native/)
  * [Minidumps](https://docs.sentry.io/platforms/native/guides/minidumps/)

Copy page

# Minidumps

## Learn about how Sentry processes Minidump crash reports.

Sentry can process Minidump crash reports, a memory dump used on Windows and by open-source libraries like [_Breakpad_](https://docs.sentry.io/platforms/native/guides/breakpad/) or [_Crashpad_](https://docs.sentry.io/platforms/native/guides/crashpad/).

This page describes **standalone** usage of Minidumps with Sentry. We strongly encourage you to use our [Native SDK](https://docs.sentry.io/platforms/native/), as Minidumps uploads are very limited in functionality.

In order to receive symbolicated stack traces, you have to upload debug information to Sentry. For more information, see [Debug Information Files](https://docs.sentry.io/platforms/native/guides/minidumps/data-management/debug-files/).

## What is a Minidump?

Minidumps are files containing the most important memory regions of a crashed process. When the process crashes, the minidump is written to the user's disk and can later be uploaded to Sentry. A minidump typically includes:

  * The runtime stack of each thread that was active during the time of the crash. This allows you to reconstruct stack traces for all stacks and even infer variable values in some cases.
  * Thread contexts -- that is, register values -- at the time of the crash. This is especially relevant for stack walking.
  * Optionally, the process heap. By default, this is not included to keep minidumps at a reasonable size. Sentry does not read the heap so that it can be safely omitted. When using Sentry SDKs, no heap memory is included in minidumps.
  * The crash reason and an optional memory address associated to with the crash. For example, memory access violations. In the case of assertions, the assertion message is also included in the dump.
  * Meta data about the CPU architecture and the user’s operating system.

##### A Word on Data Privacy

Minidumps are memory dumps of the process at the moment it crashes. As such, they might contain sensitive information on the target system, such as environment variables, local pathnames or maybe even in-memory representations of input fields, including passwords. **Sentry does not store these memory dumps by default**. Once processed, they are removed immediately, and all sensitive information is stripped from the resulting issues. You can configure Sentry to store the minidump as attachment.

In addition to this information, you can add further metadata specific to Sentry, which can help in organizing and analyzing issues. For more information, see Passing Additional Data.

## Creating and Uploading Minidumps

Depending on your operating system and programming language, there are various alternatives to create minidumps and upload them to Sentry. See the following resources for libraries that support generating minidump crash reports:

  * [Native SDK](https://docs.sentry.io/platforms/native/)
  * [Google Breakpad](https://docs.sentry.io/platforms/native/guides/breakpad/)
  * [Google Crashpad](https://docs.sentry.io/platforms/native/guides/crashpad/)

If you have already integrated a library that generates minidumps and would just like to upload them to Sentry, you need to configure the _Minidump Endpoint URL_ , which can be found at _Project Settings > Client Keys (DSN)_. This endpoint expects a `POST` request with the minidump in the `upload_file_minidump` field:

Bash

Copied

    curl -X POST \
      '___MINIDUMP_URL___' \
      -F upload_file_minidump=@mini.dmp

To send additional information, add more form fields to this request. For a full description of fields accepted by Sentry, see Passing Additional Data.

## Passing Additional Data

You can add more information to crash reports by merely adding more fields to the upload HTTP request. All these fields will be collected in the “Extra Data” section in Sentry:

Bash

Copied

    curl -X POST \
      '___MINIDUMP_URL___' \
      -F upload_file_minidump=@mini.dmp \
      -F custom_field=value

Additionally, you can set all attributes corresponding to the Sentry event interface in a `sentry` field. This field either accepts JSON data or its values can be flattened with the bracket syntax. For example, to set the release and add a tag, send:

Bash

Copied

    # As JSON
    curl -X POST \
      '___MINIDUMP_URL___' \
      -F upload_file_minidump=@mini.dmp \
      -F 'sentry={"release":"my-project-name@2.3.12","tags":{"mytag":"value"}}'

    # flattened
    curl -X POST \
      '___MINIDUMP_URL___' \
      -F upload_file_minidump=@mini.dmp \
      -F 'sentry[release]=my-project-name@2.3.12' \
      -F 'sentry[tags][mytag]=value'

For the full list of supported values, see [_Event Payloads_](https://develop.sentry.dev/sdk/foundations/transport/event-payloads/) and linked documents.

If your minidump's size exceeds the limits in the next section, you can compress it using `gzip`, `zstd`, `bzip2` or `xz` and refer the `upload_file_minidump` to the compressed file instead of the plain minidump.

## Size Limits

Event ingestion imposes limits on the size and number of fields in multipart uploads. These limits are subject to future change and defined currently as:

  * _40MB_ for a compressed minidump request
  *  _200MB_ for the full body after decompression
  *  _200MB_ for all files combined
  *  _200MB_ for each file individually
  *  _1MB_ for event payloads

## Store Minidumps as Attachments

[Minidumps](https://docs.sentry.io/platforms/native/guides/minidumps/#what-is-a-minidump) may contain sensitive information about the target system, such as environment variables, local pathnames, or in-memory representations of input fields, including passwords. By default, Sentry only uses minidump files to create events and immediately drops them. All sensitive information is stripped from the resulting events.

All attachments types, including log files, screenshots and minidumps (if you enable Store Minidumps As Attachments), are stored for 30 days when sent to Sentry. Note that Sentry does not apply data scrubbing to attachments.

### Enabling Minidump Storage

You can enable _Store Minidumps As Attachments_ in your organization or project settings under **Security & Privacy**. By default, this setting is disabled. Determine the maximum number of crash reports that will be stored per issue; disabled, unlimited, or maximum per issue:

If you set a limit per issue, as in the example above, a limit of 5, Sentry will store the first 5 attachments associated with this issue, but drop any that follow. To make room for additional attachments, delete them. Sentry will then accept attachments until the limit is reached again.

[PreviousWelcome to Sentry](https://docs.sentry.io/)

[NextData Management](https://docs.sentry.io/platforms/native/guides/minidumps/data-management)

Was this helpful?

Yes 👍No 👎

How can we improve this page?

Email (optional)

Submit feedback

**Help improve this content**
Our documentation is open source and available on GitHub. Your contributions are welcome, whether fixing a typo (drat!) or suggesting an update ("yeah, this would be better").

[How to contribute](https://docs.sentry.io/contributing/) | [Edit this page](https://github.com/getsentry/sentry-docs/edit/master/docs/platforms/native/guides/minidumps/index.mdx) | [Create a docs issue](https://github.com/getsentry/sentry-docs/issues/new/choose) | [Get support](https://sentry.zendesk.com/hc/en-us/)


# Configure analysis via .luaurc

**Status**: Implemented

## Summary

Introduces a way to configure type checker and linter using JSON-like .luaurc files

## Motivation

While Luau analysis tools try to provide sensible defaults, it’s difficult to establish the rules that work for all code. For example, some packages may decide that unused variables aren’t interesting; other packages may decide that all files should be using strict typechecking mode.

While it’s possible to configure some aspects of analysis behavior using –! comments, it can be cumbersome to replicate this in all files.

## Design

To solve this problem, we are going to introduce support for `.luaurc` files for users of command-line Luau tools. For a given .lua file, Luau will search for .luaurc files starting from the folder that the .lua file is in; all files in the ancestry chain will be parsed and their configuration applied. When multiple files are used, the file closer to the .lua file overrides the settings.

.luaurc is a JSON file that can also contain comments and trailing commas. The file can have the following keys:

  * `"languageMode"`: type checking mode, can be one of “nocheck”, “nonstrict”, “strict”
  * `"lint"`: lints to enable; points to an object that maps string literals that correspond to the names of linting rules (see https://luau-lang.org/lint), or `"*"` that means “all rules”, to a boolean (to enable/disable the lint)
  * `"lintErrors"`: a boolean that controls whether lint issues are reported as errors or warnings (off by default)
  * `"typeErrors"`: a boolean that controls whether type issues are reported as errors or warnings (on by default)
  * `"globals"`: extra global values; points to an array of strings where each string names a global that the type checker and linter must assume is valid and of type `any`

Example of a valid .luaurc file:

        "languageMode": "nonstrict",
        "lint": { "*": true, "LocalUnused": false },
        "lintErrors": true,
        "globals": ["expect"] // TestEZ

Note that in absence of a configuration file, we will use default settings: languageMode will be set to nonstrict, a set of lint warnings is going to be enabled by default (this proposal doesn’t detail that set - that will be subject to a different proposal), type checking issues are going to be treated as errors, lint issues are going to be treated as warnings.

## Design – compatibility

Today we support .robloxrc files; this proposal will keep parsing legacy specification of configuration for compatibility:

  * Top-level `"language"` key can refer to an object that has `"languageMode"` key that also defines language mode
  * Top-level `"lint"` object values can refer to a string `"disabled"`/`"enabled"`/`"fatal"` instead of a boolean as a value.

These keys are only going to be supported for compatibility and only when the file name is .robloxrc (which is only going to be parsed by internal Roblox command line tools but this proposal mentions it for completeness).

## Drawbacks

The introduction of configuration files means that it’s now impossible to type check or lint sources in isolation, which complicates the code setup.

File-based JSON configuration may or may not map cleanly to environments that don’t support files, such as Roblox Studio.

Using JSON5 instead of vanilla JSON limits the interoperability.

There’s no way to force specific lints to be fatal, although this can be solved in the future by promoting the “compatibility” feature where one can specify a string to a non-compatibility feature.

## Alternatives

It’s possible to consider forcing users to specify the source settings via `--!` comments exclusively. This is problematic as it may require excessive amounts of annotation though, which this proposal aims to simplify.

The format of the configuration file does not have to be JSON; for example, it can be a valid Luau source file which is the approach luacheck takes. This makes it more difficult to repurpose the .luaurc file to use third-party processing tools though, e.g. a package manager would need to learn how to parse Luau syntax to store configuration in .luaurc.

It’s possible to use the old style of lint rule specification with “enabled”/”fatal”/etc., but it’s more verbose and is more difficult to use in common scenarios, such as “all enabled lints are fatal and these are the ones we need to enable in addition to the default set” is impossible to specify.


A lightweight project spec that enables Luau projects to be created in a platform-agnostic manner.

By reducing the dependency of a project into a set of Luau plugin modules, the burden of porting Luau code to be compatible from one environment to the next is massively reduced in scope, as only the plugin modules would need to be ported, rather than a given codebase.

The primary example of this being applied in practice is the 'penumbra' Luau graphics rendering pipeline.

Link: https://github.com/Crazyblox/Penumbra

# Project directory layout:
     ┣━ .pajamasrc.luau
     ┗━ init.luau

# Explained:
### .pajamasrc.luau:
The Pajamas runtime starts validating a Pajamas project by looking for this file.

It contains project details, launch parameter options, and specific runtime configuration.

### Starting a pajamas project:
A project must be sat directly as a child of `pajamas/`, maintaining a siblance with the `/plugin` directory in order to be able to function as intended and interface appropriately within the host's environment.

When pajamas is told to initalise a project, it will first look for the `/.pajamasrc.luau` module, which contains information relevant to how the project should be run:
Example:
```luau
return {
    schema = "0.0.2",
    project = {
        name = "Penumbra",
        author = "Crazyblox",
        version = "26.04.0",
        launch_note = "Thanks for using Penumbra! Contributions & reports are welcome at https://github.com/Crazyblox/Penumbra"
    },
    runtime = {
        default =    { run = true },
        roblox =    { run_server = false, run_client = true }
    },
    plugin = {
        -- Possibly redundant for runtime purposes.
        ["display"] =    { include = true },
        ["event"] =        { include = true },
        ["input"] =        { include = true },
        ["task"] =        { include = true },
        ["vm"] =        { include = true }
    },
    params = {
        -- 'input' is used to sanitise & assert type input.
        -- 'input' value is derived from 'default' if not declared.
        -- 'default' value is used as a fallback if no launch parameter is supplied.
        [1] = { name = "Path",        input = "string" },
        [2] = { name = "X",         default = 320 },
        [3] = { name = "Y",         default = 240 },
        [4] = { name = "Threads",     default = 1 },
        [5] = { name = "Resample",     default = true },
        [6] = { name = "RenderNum", default = -1 }

Once `/.pajamasrc.luau` is validated, it will then look to run the project's `/init.luau` module. At that point, pajamas has done its job, and the given project would begin to run.

Any time a new process is spawned, including on initial project startup and when a new process is created via `/plugin/process.luau`, `/init.luau` will be passed and called in order to intialise the new process.

Pajamas does NOT need to be initialised multiple times via new processes. Only 1 process should be responsible for communicating with and handling pajamas itself.

### The point of plugins:
Plugin modules are intended to handle interfacing with the Luau runtime the project is running on, enabling all Luau code within a given project to not depend on runtime instrinsics.

The goal is that, by deferring requirements for running on different platforms down to a specific set of plugin modules, developers would find themselves writing code where their logic is wholly separate from the environment they developed it in, allowing for more portability and giving Luau more opportunity for growth.

For example, Roblox/Lune's 'task' standard library would be returned under a simple plugin module, ensuring Luau purity for init and library modules and allowing other runtimes to provide their own version of 'task' when a standard library isn't provided.


## Overload Your Classes.

Tired of the traditional class system on Luau?
Tired of learning complex systems, and wish for something easier?
Wish to do OOP in the right way, like in many languages such as C++?
Then you're in the right place.

Introducing: Class++.
Classes were never this easy to create, and advanced before on Luau.

**[Documentation](https://tenebrisnoctua.github.io/ClassPP/)** **[Download Latest](https://github.com/TenebrisNoctua/ClassPP/releases/latest)** **[Get it on Roblox Creator Store](https://create.roblox.com/store/asset/18312821151/Class)**


# Overload your Classes.

Tired of the traditional class system on Luau?
Tired of learning complex systems, and wish for something easier?
Wish to do OOP in the right way, like in many languages such as C++?

Then you're in the right place.
Introducing: _Class++._
Classes were never this easy to create, and advanced before on Luau.

Back to top


# Static Members

Static members are members of the class that will exist regardless of whether or not any objects of the class are created.
Once a static member has been created, they cannot be destroyed or modified in any way. Static members are also global, so all class objects have access to them, and their property will be the same across all of the objects.

To create a static member in Class++, you have to use the `class.static(<accessSpecifier>, <name>, <property>)` function.

    local class = ClassPP.class

    local Test = class "Test" {
        Public = {
            TestValue = 0

    Test.static("Public", "StaticTestValue", function(firstArgument)
        print(firstArgument) -- This will not point to any object, instead it will print whatever it is called with! In this case, it will print "Hi!".
    end)

    local newTest = Test.new()
    newTest:StaticTestValue() -- This will error!

    Test.StaticTestValue("Hi!") -- This will work fine!

You can also give properties that aren't functions. To retrieve those properties, you can do `class.<memberName>.property` or `class.<memberName>.p` for short.

    local class = ClassPP.class

    local Test = class "Test" {
        Public = {
            TestValue = 0

    Test.static("Public", "StaticTestValue", 1)

    print(Test.StaticTestValue.property) -- Prints "1"!
    print(Test.StaticTestValue.p) -- Also prints "1"!

Back to top


#  Errors

When Class++ displays an error message to the output, it will have a short ID at the end. This allows you to identify what kind of error or message that is.

You can use that ID to find the details about that error or message down below.

## attemptToModifyReadOnlyTable

    Cannot modify a read only table.

You've attempted modify a read-only table within Class++. This usually occurs when you try to modify the [`Inherits`](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/data-types/class/#inherits-class-read-only), [`Friends`](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/data-types/class/#friends-class-read-only) and [`Statics`](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/data-types/class/#statics-string-any-read-only) tables of a `class`.

## attemptToCreateObjectFromAbstractClass

    Cannot create an object from an abstracted class.

You tried to create an `object` from an abstract `class`. Abstract classes are not meant to create objects from, rather, they are meant to be a base-class that a `class` inherits from.

## attemptToExtendAFinalClass

    Cannot create an inherited class from a final class.

You tried to create a `class` from a final `class`. Final classes are not meant to be inherited from.

## cannotCallFunctionError

    Cannot call function ... directly.

You've tried to call a function within a `class` without creating an `object`. Class functions are not meant to be used directly, rather, they are to be used from objects. _(Excluding static members.)_

## cannotCallFunctionFrom

    Cannot call function ... from ...

You've tried to call a function within an `object` that cannot be called within certain functions, such as `super()` in `destructor`.

## cannotFindBaseClassMethod

    Property ... cannot be found or is not a function in the base class ...

You've tried to call a property with `super()` in the base `class` that cannot be found, or wasn't a function.

## classAlreadyExists

    Class ... already exists.

You've tried to create a `class` that already has been created with the same name. Consider changing the name of the new `class`.

## classClassDataInternal

    The property classData of the class ... is Internal. It cannot be used.

You've attempted to access the `classData` property of a `class` directly. This property is meant to be used internally, and is not meant for external use. This property only shows up in the auto-complete to support type-checking in certain cases.

## classDataNotTable

    Given classData value for the class ... is not a table.

You've given a `classData` value that is not a table to the `class` function. The `class` function only takes a table as its `classData` parameter.

## classDataContainsMetatable

    Given classData value for the class ... contains a metatable.

You've given a `classData` value that contains a metatable attached to it. For security and performance reasons, `classData` parameter only takes a raw table without any metatables attached to it.

## classMemberCannotSetToNil

    Class members cannot be set to nil.

You've attempted to set a class member within the `object` to nil. Setting member values to nil causes certain bugs to happen _(due to the nature of dictionaries)_ , so it is not allowed to set them to `nil.` Consider setting it to a value such as `false` to indicate that it is no longer in use.

## classMemberNotFound

    This class has no member named ...

You've tried to index the `object` with a member that does not exist within the `classData` table. Members can only be added or removed through changing the `classData` table given to the `class` function.

## classNameNotString

    Class name is not a string or is nil.

The `class` function expected a `string` as it's name, but you provided something else. A `class`'s name can only be a `string`.

## classNotFound

    A class with the given name cannot be found.

A `class` cannot be found with the provided name argument. Make sure the name is correct, or if the `class` actually exists.

## classNoMatchingFunctionError

    No match for ... function in this class.

You've used an operator on an `object` which had no overloads set for it.

## classObjectLocked

    This class object has been locked.

You've tried to use an `object` after it's been locked.

## classPropertyIsPrivate

    Property ... is private in this class.

You've tried to access a private member of an `object` outside of a class function.

## classPropertyIsProtected

    Property ... is protected in this class.

You've tried to access a protected member of an `object` outside of a class function.

## classPropertyIsInternal

    Property ... is internal in this class. You cannot access it.

You've tried to access an internal member of an `object`. Internal members are not accessible.

## classPropertyRedeclaration

    Cannot redeclare property ...

You've tried to declare a property that has already been declared in another access-specifier. You can only declare a member in one access-specifier.

## expectedFunctionError

    Expected function, got: ...

The `functionsTable` that you provided to `class.overload` function had a value that wasn't a function.

## extendsDeprecated

    Extends is now deprecated. Use the "class" function instead.

You've tried to use the now deprecated `extends` function for inheritance.
See the discussion [`#4`](https://github.com/TenebrisNoctua/ClassPP/discussions/4) for more info.

## invalidAccessSpecifierError

    Given value is not a valid access specifier.

You've provided a value that wasn't a valid access specifier. Make sure the given `string` contains a valid access-specifier.

## invalidAccessSpecifierInClassData

    Given classData value for the class ... contains invalid access specifier(s) or function(s).

You've given a `classData` value that contains invalid access-specifiers or functions. Make sure the access-specifier or function names are correct.

## invalidDestructorCall

    Cannot call Destroy without an object argument. Try calling the function with the ':' operator.

You've tried to call the `Destroy()` without providing the `object` as its first argument. Calling it with a ':' like `:Destroy()`, passes the `object` argument automatically.

## invalidModifierArgument

    Given argument is not a class table.

You've given a value to a modifier function that wasn't a table containing classes.

## invalidPropertyDecleration

    Cannot create reserved property ...

You've tried to create a reserved property in a `class`.
Some property names are reserved for certain functions that come by default for every `object`, so you cannot declare them inside the `classData` value.

## invalidSuperCall

    Cannot call super in a multi-inherited class, or in a non-inherited class.

You've tried to call the `super()` function from a `class` that either wasn't inheriting from another `class`, or it was inheriting from multiple `class`es.

## illegalModifierCombination

    Cannot create an illegal combination of modifiers: ...

You've tried to create an illegal combination of modifiers. Certain modifiers have opposite functionality, so combining them is not possible.

## nonNativeOperatorCall

    Operator function ... cannot be called without its operator.

You've tried to call an operator function without its operator. Operator functions are meant to be called with their operators.

## overloadfunctionArgumentMismatch

    A function to handle the given number of arguments ... have not been provided for the overloaded function: ...

You haven't provided a function to the `functionsTable` to handle the given number of arguments for an overloaded function.

## overloadfunctionNameNotSet

    A function name has not been set for the overloaded function.

You haven't provided a name for the overloaded function.

## overloadfunctionTableNotGiven

    A function table has not been given for the overloaded function.

You haven't provided a `functionsTable` for the overloaded function.

## staticMemberNameNotSet

    A name has not been set for the static member.

You haven't provided a name for the static member.

## typeofObjectNotFound

    The given object's type cannot be found.

The provided object's type cannot be found.

## unhandledError

    An unhandled error occured in "...": ...

An unhandled error has occured in a specific function given to Class++. This error does not halt the execution of the thread, so be mindful of the bugs that may occur in your code.

## unknownError

    An unknown error has occured.

Class++ ran into a problem, but it cannot associate it with a valid error type. This is meant to be a fallback error, and may only occur if the internal code isn't running properly.

Back to top


#  getClass :[class](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/data-types/class)

    function classpp.getClass(className: string): class

Returns the `class` with the given `className`, if it exists.

## Default syntax

    local Class = ClassPP.getClass("Class")

## Parameters

className: [string](https://create.roblox.com/docs/luau/strings) | The name of the desired `class`.

## Returns

[class](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/data-types/class)


#  autoObjectDestruction :[boolean](https://create.roblox.com/docs/luau/booleans)

This property indicates whether or not a created [`object`](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/data-types/object/) will automatically be destroyed when its reference has been garbage collected. It is set to `true` by default, and allows Class++ to automatically call the `destructor` function (if defined) of an `object` automatically.

It can be set to `false` if desired, however, you will have to manually destroy every `object` if you do, to prevent memory leaks.

Back to top


# Types

## Intellisense

Class++ in 2.0, has been rewritten from ground up to support the new type-solver and its capabilities. This allows Class++ to be more intelligent with the `class` and `object` types, finally allowing the intellisense to be far better than what it used to be.

    local Person = class "Person" {
        Public = {
            Age = 0,
            Name = "",
            Personality = "",
            Likes = {},
            Dislikes = {},
            Job = "",
            getSecrets = function(self)
                return self.Secrets
            end,
        },
        Private = {
            Secrets = {}

    local newPerson = Person.new() -- Now has intellisense for members in all access specifiers, and obtains the correct types for every member!

This also applies to classes, where before you tried to create an inherited class, the returned class type would not be correct, and new objects would also not have the correct type. For example, some members of an inherited class would be missing from auto-complete.

Now, this has also been fixed:

    local class = ClassPP.class

    local A = class "A" {
        Public = {
            Variable_A = 1

    local B = class "B" {
        Public = {
            Variable_B = 1

    local C = class "C" (A, B) { -- Derived Class
        Public = {
            Variable_C = 1

    local newObject = C.new() -- {Variable_A: number, Variable_B: number, Variable_C: number}

Though, as much as this update brings in an intellisense much better than before, it is still limited. Like in the previous versions of Class++, to support all the features of types in Luau, you have to create a custom type and assign it to the created objects.

In the tutorial below, you will learn how to create a basic `Person` type and assign it to the created object, to enable the support.

## Creating a Basic Custom Class Type

    local class = ClassPP.class

    type Person = {
        Age: number,
        Name: string,
        Personality: string,
        Job: string,
        Secrets: {string},
        Likes: {string},
        Dislikes: {string},
        getSecrets: (self: Person) -> {string}

    local Person = class "Person" {
        Public = {
            Age = 0,
            Name = "",
            Personality = "",
            Likes = {},
            Dislikes = {},
            Job = "",
            getSecrets = function(self: Person)
                return self.Secrets
            end,
        },
        Private = {
            Secrets = {}

    local newPerson: Person = Person.new()
    -- This object now fully supports all type features of Luau!

In the example above, we created a custom type called `Person` for the Person class, and inserted the types of all the members inside it, and declared the new created object as of the Person type. This now allows us to use all the features of Luau types.

Info

Since class objects belong to the base type `userdata`, you can type cast them to either your custom types, or any other existing type you wish.

## Typechecking for Classes and Class Objects

Class++ also comes with its own [`Type`](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/class-functions/type/type/) API that allows you to get the types of `class`es and `object`s.

    local class = ClassPP.class
    local type, typeof = ClassPP.Type.type, ClassPP.Type.typeof

    type Person = {
        Age: number,
        Name: string,
        Personality: string,
        Job: string,
        Secrets: {string},
        Likes: {string},
        Dislikes: {string},
        getSecrets: (self: Person) -> {string}

    local Person = class "Person" {
        Public = {
            Age = 0,
            Name = "",
            Personality = "",
            Likes = {},
            Dislikes = {},
            Job = "",
            getSecrets = function(self: Person)
                return self.Secrets
            end,
        },
        Private = {
            Secrets = {}

    local newPerson: Person = Person.new()
    print(type(Person), ",", type(newPerson)) -- Prints "Class, Object"!
    print(typeof(Person), ",", typeof(newPerson)) -- Prints "Person, Person"!

### Type.type

`Type.type` will return the true type of the given object. It behaves the same as the built-in Luau `type` function, but with additional support for `class`es and `object`s. For example, if the provided object is a `class`, it will return a string called "Class". This is due to the `class` object belonging to the base [`Class`](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/data-types/class/) type.

The same applies to `object`s, where if the provided object is an `object`, it will return a string called "Object". This is due to the `object` belonging to the base [`Object`](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/data-types/object/) type.

### Type.typeof

`Type.typeof` will return the type of the given object. Like `Type.type`, it behaves the same as the built-in Luau `typeof` function, but with additional support for `class`es and `object`s. For example, if the provided object is a `class`, it will return a string containing the name of the `class`. This is due to `class`es are also being types on their own. They can also be represented as types.

For `object`s, this function will return the type of the `class` they belong to. For example, using this function with an `object` created from a "Person" `class`, will return "Person" as its type.

Info

`Type.type` and `Type.typeof` functions can be used to replace the built-in `type` and `typeof` functions, as they behave the same with other provided objects. For example, using `Type.typeof` and `typeof` with a `string` will both return "string".

Warning

Using the `class.Name` property may create bugs in certain places as the Type API makes sure the given object is an actual `class` object before returning its type. It's recommended that you use the Type API instead of the `.Name` property.

Back to top


#  final :[class](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/data-types/class)

    function classpp.final(classTable: {class}): class

Marks the given [`class`](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/data-types/class/) or [`class`](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/data-types/class/)es as final. If given multiple, then the first class will be returned.

## Default Syntax

    local Car = final { class "Car" {
        Public = {
            ...
        },
        ...

## Without Syntax Sugar

    local Car = final({class("Car")({
        Public = {
            ...
        },
        ...
    })})

## Parameters

classTable: [{class}](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/data-types/class) | The desired `class` or `class`es to be set to final.

## Returns

[class](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/data-types/class)


# Classes and Objects

A class is an user defined data structure. It's made out of members and member functions(methods). Those members and member functions can then be accessed by creating an object from that class. Think of a class as a blueprint for an object.

## Creating a Class

To create a new class with Class++, you use the `class()` function of the main library. The first argument must be a string that defines the name of the class, and the next argument must be the `classData`, a table that contains all the access specifiers and the member data.

    local class = ClassPP.class

    local person = class "Person" {
        Public = {
            Name = "",
            Age = 0

In the above example, we created a new class with the name `Person`, and under the public access specifier, we created two members named: `Name` and `Age` with certain default values assigned to them. These members and their default values will be transferred to every object created from this class.

## Creating an Object

Now that we have a class, we can create an object from it. In Class++, to create an object, you use the `.new()` method of a class. When called, this method returns an object created from this class, and like it's been mentioned above, it will contain all of the members and their default values from the class it's been created from.

    local class = ClassPP.class

    local person = class "Person" {
        Public = {
            Name = "",
            Age = 0

    local newPerson = person.new()
    print(newPerson.Age) -- Prints "0"!

### Updating an Object

Of course, by indexing the object with a specific member, you can update that specific member's associated value. There are some restrictions that can be applied to this however, which you will learn in later pages.

    local class = ClassPP.class

    local person = class "Person" {
        Public = {
            Name = "",
            Age = 0

    local newPerson = person.new()
    newPerson.Age = 21

    print(newPerson.Age) -- Prints "21"!

Warning

In Class++, members and their values must be defined through either the `class()` function, or by simply indexing a created class and assigning a new member to an access specifier through it. Trying to define a new member through the object will cause an error.

    local class = ClassPP.class

    local person = class "Person" {
        Public = {
            Name = "",
            Age = 0

    local newPerson = person.new()
    newPerson.Personality = "Cheerful" -- This will error! This class has no member named "Personality".

Back to top


#  type :[string](https://create.roblox.com/docs/luau/strings)

    function Type.type(object: any): string

Returns the true type of the given `object`. Behaves the same as the built-in `type()` function, but with additional support for `class`es and `object`s.

## Default syntax

    local objectType = ClassPP.Type.type(object)

## Parameters

object: [object](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/data-types/object) | The `object`.

## Returns

[string](https://create.roblox.com/docs/luau/strings)


# Optimization

Class++ is designed to be as optimized as possible for a class and objects system. To achieve the best performance, you can avoid certain scenarios that will slow Class++ down.

## Skipping Operations

Class++ will skip certain unnecessary operations made by the user. For example, if you try to set a value to a member that is exactly the same as the one the member had before, Class++ will skip this operation.

    local newClass = class "Test" {
        Public = {
            test = "hello"

    local newObject = newClass.new()
    newObject.test = "hello" -- This operation will be skipped.

However, you still shouldn't trust on Class++ to skip these operations, as it will still try to check if the thread is able to access the member or not. To get the best performance, avoid setting member values to the same values as before.

## Accessing Members

Accessing members in Class++ is _very fast*_ , and the performance overhead that Class++ causes should not be your primary concern. However, there are certain practices that you can apply to gain as much performance as possible out of Class++.

  1. Always access private members from class functions.
This allows Class++ to achieve the best performance when fetching the member values. This also supports [encapsulation](https://tenebrisnoctua.github.io/ClassPP/latest/tutorials/best-practices/encapsulation/), and will encourage you to adopt coding styles that will be the best for you in the long-run.
  2. Use public members when necessary.
If a member does not contain important data or is an interface, then it should be a public member. This greatly increases the performance when accessing that member, and once again promotes a better coding style.

Class++ tries to be as lightweight as possible. However, you should still try to avoid using systems or patterns that may impact the performance of your programs when using Class++. You can check out other sections of the tutorials to adapt the recommended patterns and syntax.

###### *: About 3 μs (3 microseconds)

Back to top


#  version

    export type version = {
        major: number,
        minor: number,
        patch: number,
        beta: boolean

Describes a version of Class++ source code.

## Properties

### major  : number

The major version number. Two versions with different major numbers are expected to be incompatible, and have breaking changes.

### minor  : number

The minor version number. Two versions with different minor, but not major numbers are expected to be mostly compatible, but may have certain changes.

### patch  : number

The patch version number. Two versions with different patch, but not major or minor numbers are expected to be highly compatible. The patch version number only increases when bug fixes or very minor changes have been made.

### beta  : boolean

Describes whether or not the version is beta. If `true`, then the version is seperate from release versions, and may only be available on certain platforms.

Back to top


# Inheritance

Just like in other object oriented languages, Class++ allows you to inherit classes. We group the inheritance concept into two categories: derived class (child), and the base class (parent). Inheritance implements an _is-a_ relationship between classes.

    local class = ClassPP.class

    local Vehicle = class "Vehicle" { -- Base Class
        Public = {
            Brand = "Tesla",
            Model = "S",
            License_Plate = "BITE 1987",
            Year = 2012,
            honk = function(self)
                print("honk honk!")
            end

    local Car = class "Car" (Vehicle, nil) { -- Derived Class
        Public = {
            License_Plate = "A1B2C3"

    local newCar = Car.new()
    newCar:honk()
    print(newCar.Brand, newCar.Model, newCar.License_Plate, newCar.Year) -- Prints "Tesla S A1B2C3 2012"!

In this example, we have 2 classes: The "Vehicle" class (base), and the "Car" class (child).
We created the Car class by providing the `class` function a list of classes to inherit from (in this case, only the Vehicle class), and the `classData` table after.

When you create a class using this method, you create a new derived class that inherits all of the members and member functions from the class(es) provided, so you don't need to re-declare them again. The members are overwritable in the derived class, like in the example above, you can modify the `License_Plate`'s default value to anything you wish. The same applies to other members.

Question

"Why should I use Inheritence?" It's very useful for code reusability: reusing members and functions of an existing class when you're creating a new class will save you a lot of time and effort, defining same members over and over again may cause spaghetti code and decrease code readability.

## Protected Access Specifier

In Class++, private members can never be inherited by derived classes. But what if you wanted to make a member able to be inherited by other classes, while still preventing outside access to it?

The protected access specifier allows you to achieve exactly this, the members stored in this access specifier can be inherited by derived classes, while still being inaccessible from the outside world.

    local class = ClassPP.class

    local Car = class "Car" {
        Public = {
            Brand = "Lamborghini",
        },
        Protected = {
            License_Plate = "XXXX"

    local BiggerCar = class "BiggerCar" (Car, nil) {
        Public = {
            Brand = "Tesla"

    function BiggerCar.Public:printLicensePlate()
        print(self.License_Plate) -- Will print "XXXX"!
    end

    local newCar = BiggerCar.new()
    newCar:printLicensePlate()

In this example, we put the member `License_Plate` under the protected access specifier, and created a new class that inherits from the "Car" class. This derived class and its methods will now be able to access this member.

Members in the protected access specifier are overwritable by members in other access specifiers. This means, when you define a member in the public or the private access specifier that has the same name as a member in the protected access specifier, this new member will overwrite the protected member, meaning the protected member will now be removed.

    local class = ClassPP.class

    local Car = class "Car" {
        Public = {
            Brand = "Lamborghini",
        },
        Protected = {
            License_Plate = "XXXX"

    local BiggerCar = class "BiggerCar" (Car, nil) {
        Public = {
            License_Plate = "A1B2C3"

    function BiggerCar.Public:printLicensePlate()
        print(self.License_Plate) -- Will print "A1B2C3"!
    end

    local newCar = BiggerCar.new()
    newCar:printLicensePlate()

## super

Let's say that you want to access a method in a base class from a derived class, how would you do it? Creating a new object from the base class and calling the method would be tedious, as it would take longer to write and would reduce performance and increase memory usage.

Fortunately, for this, you can just call the default `super` method of an object, which allows you to call the method in a base class that has the same name of the method that `super` has been called from.

    local class = ClassPP.class

    local A = class "A" {
        Public = {
            getVariable = function(self)
                return self.Variable_A
            end
        },
        Protected = {
            Variable_A = 1

    local B = class "B" (A, nil) {
        Public = {
            Variable_B = 1,
            getVariable = function(self)
                return self:super()
            end

    local newObject = B.new()
    print(newObject:getVariable()) -- 1

In this example, we created a new class called "B" that inherits from "A". In both classes, we have a method called `getVariable`. In the base class, this method returns the value of the member `Variable_A`, and in the derived class, this method returns the value from the `getVariable` method from the base class, by calling the `super()` method.

Warning

`super` cannot be used within classes that have multi-inheritance. This is due to ambiguity that occurs with functions that have the same name in classes that have multi-inheritance.

## Multilevel-Inheritance

A class can also be derived from one class, which can be derived from another class:

    local class = ClassPP.class

    local Person = class "Person" { -- Base Class
        Public = {
            Name = "",
            Age = 0,
            Gender = "",
            Height = 0

    local Child = class "Child" (Person, nil) { -- Derived Class
        Public = {
            Age = 9,
            Energetic = true

    local Student = class "Student" (Child, nil) { -- Derived Class from a Derived Class
        Public = {
            SchoolId = 0,
            Grade = 0,
            Behaviour = "Good"

    local newStudent = Student.new()
    print(newStudent.Name, newStudent.Age, newStudent.Gender, newStudent.Height, newStudent.Age, newStudent.Energetic, newStudent.SchoolId, newStudent.Grade, newStudent.Behaviour)
    -- Prints " 9  0 9 true 0 0 Good"! (Spaces represent empty strings)

## Multi-Inheritance

Warning

Multi-Inheritance is not Multilevel-Inheritance. While their names may be similar, they represent different concepts.

A class can also be derived from multiple classes:

    local class = ClassPP.class

    local A = class "A" {
        Public = {
            Variable_A = 1

    local B = class "B" {
        Public = {
            Variable_B = 1

    local C = class "C" (A, B) { -- Derived Class
        Public = {
            Variable_C = 1

    local newObject = C.new() -- {Variable_A: number, Variable_B: number, Variable_C: number}

### The Diamond Problem

The Diamond Problem is an ambiguity that arises when two classes, let's say "B" and "C", that inherits from a class called "A", and another class "D" that inherits from both "B" and "C". If there is a method in "A" that "B" and "C" have overridden, and "D" does not override it, then which version of the method does "D" inherit from: that of "B", or that of "C"?

    local class = ClassPP.class

    local A = class "A" {
        Public = {
            method = function(self)
                print("Method from A")
            end

    local B = class "B" (A, nil) {
        Public = {
            method = function(self)
                print("Method from B")
            end

    local C = class "C" (A, nil) { -- Derived Class
        Public = {
            method = function(self)
                print("Method from C")
            end

    local D = class "D" (B, C) { -- Derived Class
        Public = {
            -- Which method will D have, B's or C's?

### How Class++ solves the Diamond Problem

In Class++, the class inheritation system does not work by referencing the parent classes, but rather for the sake of performance, it _combines_ the given classes into a new one. The combination is done in an order, from the first given class argument to the last. Certain Access Specifiers such as Protected can change how this system behaves.

To create class "D", Class++ takes the class arguments one by one in an order, from left to right, and combines their `classData` and the given `classData` table into a new class. So due to this order, class "B"'s members and methods will automatically be overwritten by class "C"'s, therefore the method that will exist on class "D", will be the method of class "C"'s.

This is similar to the Python's [MRO (Method Resolution Order)](https://docs.python.org/3/howto/mro.html).

    local class = ClassPP.class

    local A = class "A" {
        Public = {
            method = function(self)
                print("Method from A")
            end

    local B = class "B" (A, nil) {
        Public = {
            method = function(self)
                print("Method from B")
            end

    local C = class "C" (A, nil) { -- Derived Class
        Public = {
            method = function(self)
                print("Method from C")
            end

    local D = class "D" (B, C) {} -- Derived Class

    local newObject = D.new()
    newObject:method() -- Prints "Method from C"!

Back to top


# Error Handling

Class++ is designed in a way that makes class and object access safe and optimized. But unfortunately, it cannot stop errors occuring in your program altogether. For example, it cannot stop you from trying to access private or non-existent data.

So it is up to you to be able to deal with the errors that occur while the program is running.

## Error Types

In Class++ there are two types of errors that can occur during runtime: Fatal and Non-Fatal.

### Fatal Errors

Fatal errors are the type of errors that will completely crash your program. These errors are the most dangerous ones, naturally.

Info

Fatal errors in Luau can normally be created by using the built-in `error()` function:

    error("This will error!") -- Halts the execution, and prints the stack to the output.

In Class++, a fatal error will occur if you encounter a set limitation. For example, if you try to access a private member outside of a class method, it will cause a fatal error.

    -- Assuming this is the main thread of the script.
    local newObject = class.new()
    newObject.PrivateProperty = "Hello!" -- Will cause a fatal error.

### Non-Fatal Errors

Non-Fatal errors are the type of errors that will not crash your program. While less dangerous, this still does not mean that your program will continue to work as expected. For example, if a `destructor` function fails to run, Class++ will still destroy the object, while ensuring the program keeps running. But this does not mean that destructor has ran correctly, and there might be an object or a system that could not be cleared up, thus causing a memory leak.

    local newClass = class "Test" {
        destructor = function(self)
            error("An unknown error occured!")
            self.object:Destroy() -- This line will never be reached, causing the object to never be destroyed!
        end,
        Public = {
            object = {}

## Tips

Be careful. To ensure your program is running safely, every operation that can fail should be put inside either a `pcall()` or a `xpcall()`. These functions allow you to safely handle any errors that may occur in your code.

_A full list of fatal and non-fatal errors can be found in the[Errors](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/general/errors/) page._

Back to top


#  classData

    export type classData = {
        constructor: (any, ...any) -> ()?,
        destructor: (any) -> ()?,
        Public: {[string]: any}?,
        Private: {[string]: any}?,
        Protected: {[string]: any}?,
        Friend: {any}?

The `classData` table that is given to the [`class()`](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/class-functions/main/class/) function that contains data about the desired [`class`](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/data-types/class/).

## Properties

### constructor  : (self: any, ...any) -> ()?

The `constructor` function that will be called when an `object` gets created.

### destructor  : (self: any) -> ()?

The `destructor` function that will be called when an `object` gets destroyed.

### Public  : {[string]: any}?

The access specifier that contains properties that can be globally accessable.

### Private  : {[string]: any}?

The access specifier that contains properties that can only be accessed inside the `class`.

### Protected  : {[string]: any}?

The access specifier that contains properties that can only be accessed inside the `class` and inherited classes.

### Friend  : {any}?

The access specifier that contains functions and classes that can access the `class`'s `Private` properties.

Back to top


#  version :[version](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/types/version)

The version of the Class++ source code.
If `beta` is true, then the version is beta and is seperate from the release versions.

Back to top


# Class Constructors and Destructors

Up until this point, we have only updated an object's members from either on the main thread, or inside of a class method. This is fine when it comes to public members, as they can be accessed anywhere. However, when it comes to other member types, such as private members, this can become _tedious_ , as you have to call a class method every single time if you want to update them after object creation.

Fortunately, to solve these issues, there are 2 special functions that you can define in every class, called: `constructor` and `destructor`.

## Class Constructors

A constructor is a special function that gets called when an object is created. To create a constructor, you have to specifically define a function called `constructor` outside of the access specifiers. Constructors can be really useful for setting initial values for certain members.

    local class = ClassPP.class

    local Car = class "Car" {
        constructor = function(self)
            self.License_Plate = "YYYY"
        end,
        Public = {
            Brand = "Lamborghini",
        },
        Private = {
            License_Plate = "XXXX"

    local newCar = Car.new()

When `Car.new()` is called, it will automatically call the constructor function.

### Constructor Parameters

Constructors, like regular class methods, can take parameters. Unlike class methods however, a constructor will always have `self` (the object) as the first parameter, regardless of how you call the `class.new()` function.

    local class = ClassPP.class

    local Car = class "Car" {
        constructor = function(self, brand, model, licensePlate)
            self.License_Plate = licensePlate
            self.Brand = brand
            self.Model = model
        end,
        Public = {
            Brand = "",
            Model = "",
            License_Plate = ""
        },

    local newCar = Car.new("ABCD", "Ford", "Mustang")
    print(newCar.Brand, newCar.Model, newCar.License_Plate) -- Prints "ABCD, Ford, Mustang"!

## Class Destructors

A destructor is a special function that runs when you call the default `:Destroy()` method on an object. To create a destructor, you have to specifically define a function called `destructor` outside of the access specifiers.

Info

`object:Destroy()` is a reserved special method that all objects have. Due to this, you cannot define any member or a method that is called "Destroy".

    local class = ClassPP.class

    local Car = class "Car" {
        constructor = function(self, brand, model, licensePlate)
            self.License_Plate = licensePlate
            self.Brand = brand
            self.Model = model
        end,
        destructor = function(self)
            self.License_Plate = ""
            self.Brand = ""
            self.Model = ""
        end,
        Public = {
            Brand = "",
            Model = "",
            License_Plate = ""
        },

    local newCar = Car.new("ABCD", "Ford", "Mustang")
    print(newCar.Brand, newCar.Model, newCar.License_Plate)

    newCar:Destroy() -- The class object will now be destroyed
    newCar = nil

Unlike `constructor`, the `destructor` function does not take any additional parameters, and the only parameter will be the `self` pointing to the object. After the `destructor` is called, all members inside the object will be set to `nil`, and the object will be locked, preventing any further access. At this stage, the object should be treated as completely empty and gone, so you should remove all references to the object to prevent memory leaks.

Instances, threads and connections inside an object will automatically be destroyed during the clearing process as well.

### Automatic Object Destruction

Unlike any other class and object creation system in Luau, Class++ comes with a built-in automatic object destruction system. This system automatically destroys an object when it deems there are no longer any references made to the object itself.

If this system detects that there are no longer any references made to an object, it will first call the `destructor` function for the object, if defined. And next, it will apply the standard destruction protocol for the object, clearing any data left inside.

This system reduces potential memory leaks that may happen due to non-destroyed objects that still have data stored in memory. However, if you wish to apply full manual memory management instead, you can set the [`autoObjectDestruction`](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/general/autoObjectDestruction/) property of the main library to `false`.

Info

Constructor and Destructor functions can also be written in the outside class definition syntax.

Back to top


#  typeof :[string](https://create.roblox.com/docs/luau/strings)

    function Type.typeof(object: any): string

Returns the type of the given `object`. Behaves the same as the built-in Roblox `typeof()` function, but with additional support for `class`es and `object`s.

## Default syntax

    local objectType = ClassPP.Type.typeof(object)

## Parameters

object: [object](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/data-types/object) | The `object`.

## Returns

[string](https://create.roblox.com/docs/luau/strings)


# Access Specifiers

In the previous page, you may have noticed that we have defined our members inside a specific table, called "Public", inside the `classData` table. This table represents an "Access Specifier". It allows you to modify the access control of a specific member, essentially allowing you to control where the member can be accessed from. This concept comes from certain object oriented languages such as C++, allowing you to apply many OOP concepts effectively in Luau.

There are currently 4 access specifiers in Class++: `Public`, `Private`, `Protected`, and `Friend`.
(`Protected` and `Friend` are mentioned in later pages.)

## Public Access Specifier

The public access specifier allows the member to be accessed from anywhere. Whether it be from a function inside the class or from the main thread, the member is accessible and can be modified.

    local class = ClassPP.class

    local Car = class "Car" {
        Public = {
            Brand = "Lamborghini",

    local newCar = Car.new()
    newCar.Brand = "Tesla"

In this example, we created a new class with a public member `Brand` that has the default value of `Lamborghini`.
Then we created a new object from this class and modified the `Brand` member of this object. Now the value is set to `Tesla`.

## Private Access Specifier

The private access specifier allows the member to only be accessed from class functions, essentially locking the member from the outside world.

    local class = ClassPP.class

    local Car = class "Car" {
        Public = {
            Brand = "Lamborghini",
        },
        Private = {
            License_Plate = "XXXX"

    local newCar = Car.new()
    newCar.License_Plate = "YYYY" -- This will error!

In this example, we updated the previous class with a private member named `License_Plate`, this member is private and can only be accessed from the class functions, so trying to access or modify it outside of the class functions will cause an error.

Warning

A member _(property/attribute)_ can be declared only once. This means a member can only be defined under one access specifier. Attempting to declare a member in multiple access specifiers will cause an error.

Back to top


#  Object

An `object` created from the [`class.new()`](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/data-types/class/#new-object) function. Contains all of the members given from the `class` object.

Warning

`object`s act as a proxy, as they belong to the base type `userdata`. They allow access to members in the `objectData`, but they cannot be changed. This is to make access-specifiers work in the best way possible.

### Summary

### Properties

  * __locked: Indicates if the `object` is locked.
  * __type: Indicates the type of the `object`.
  * __objtype: Indicates the `class` type of the `object`.

### Methods

  * constructor: The constructor function of the `object`, if it exists. Cannot be directly called.
  * destructor: The destructor function of the `object`, if it exists. Cannot be directly called.
  * Destroy: Calls the `destructor` function, destroys all the instances inside the `objectData` and clears it, and sets the `__locked` property to `true`.
  * super: Calls the method with the same name of the function that it's been called from in the parent `class`, if it exists.

## Properties

### __locked  : boolean

Internal

Indicates if an `object` has been locked. If set to true, the `object`'s metamethods will no longer work and all the members will become unaccessable.

### __type  : string

Internal

Indicates the type of the `object`. For `object`s created from `class.new()`, it will always be "Object".

### __objtype  : string

Internal

Indicates the `class` type of the `object`. Can be used to determine which `class` the `object` belongs to by using the [`Type.typeof()`](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/class-functions/type/typeof/) function.

## Methods

### constructor  : void

Internal

The `constructor` function of the `object`, if it has been set in the `classData` table. This function is internal, and it will only be called when the `object` is created by using the `class.new()` function.

#### Returns

void

### destructor  : void

Internal

The `destructor` function of the `object`, if it has been set in the `classData` table. This function accepts no parameters and is internal, it will only be called when `object:Destroy()` is called.

#### Returns

void

### Destroy  : void

Calling this method will destroy and clean the `object`. This method will first trigger the `destructor` function, then after, it will clear the internal `objectData` table and set the `__locked` property of the `object` to `true`. Instances inside `objectData` will automatically be destroyed and cleared too.

After this function runs, the `object` will no longer be accessible in any way, so make sure to remove all references to the `object` to allow for the garbage collector to clear it. This prevents memory leaks.

#### Returns

void

### super  : any

This method allows you to refer to the parent `class`'s methods. Calling this method will call the function with the same name as the function it's been called from in the parent `class`, if it exists, and return the result.

Warning

`super` does not support being called in `class`es created from multi-inheritance, as it would create ambiguity. It also does not support calling functions that are defined in the `Private` access-specifier in the parent `class`.

Back to top


#  abstract :[class](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/data-types/class)

    function classpp.abstract(classTable: {class}): class

Marks the given [`class`](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/data-types/class/) or [`class`](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/data-types/class/)es as abstract. If given multiple, then the first class will be returned.

## Default Syntax

    local Car = abstract { class "Car" {
        Public = {
            ...
        },
        ...

## Without Syntax Sugar

    local Car = abstract({class("Car")({
        Public = {
            ...
        },
        ...
    })})

## Parameters

classTable: [{class}](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/data-types/class) | The desired `class` or `class`es to be set to abstract.

## Returns

[class](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/data-types/class)


#  inClassScope :[boolean](https://create.roblox.com/docs/luau/booleans)

    function Util.inClassScope(class: class, includeInherited: boolean, includeFriend: boolean, classes: {[string]: class}?, defaultLevel: number?): boolean

This function checks if the current thread is allowed to access a specific `class` property from a certain access specifier.

## Default syntax

    local isAllowed = Util.inClassScope(class, false, true, Classes) -- For the Private access specifier

## Parameters

class: [class](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/data-types/class) | The `class` that the check will be made from.
includeInherited: [boolean](https://create.roblox.com/docs/luau/booleans) | Determines if the inherited `class`es can access.
includeFriend: [boolean](https://create.roblox.com/docs/luau/booleans) | Determines if the friend `class`es can access.
classes: [{[string]: class}](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/data-types/class) | A table that contains classes. If `includeFriend` is set to `true`, this table will be given to the [Util.checkFriendship](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/class-functions/utility/checkFriendship) function.
defaultLevel: [number](https://create.roblox.com/docs/luau/numbers) | Determines the default call stack level the function will start doing the checks on.

## Returns

[boolean](https://create.roblox.com/docs/luau/booleans)


#  checkFriendship :[boolean](https://create.roblox.com/docs/luau/booleans)

    function Util.checkFriendship(class: class, methodName: string, method: () -> (), classes: {[string]: class}): boolean

This function checks if the given `method` and it's `methodName` is in the given `class`'s `Friend` access specifier.

## Default syntax

    local isAFriend = ClassPP.Util.checkFriendship(class, methodName, method, classes)

## Parameters

class: [class](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/data-types/class) | The `class` that the check will be made from.
methodName: [string](https://create.roblox.com/docs/luau/strings) | The name of the given `method`.
class: [class](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/data-types/class) | The `method` that will be checked.
classes: [{[string]: class}](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/data-types/class) | A table that contains all of the created `class`es. Since a `class` can also be inserted to the `Friend` access specifier through a `string` that contains the `class`'s name, this table is used to find the `class`es set with this method.

## Returns

[boolean](https://create.roblox.com/docs/luau/booleans)


#  Class

A `class` object created from the [`class()`](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/class-functions/main/class/) function. Contains all of the members given from the [`classData`](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/types/classData/).

### Summary

### Properties

  * Name: An unique identifier of the `class`.
  * Inherits: A table that contains classes that the `class` inherits from.
  * Friends: A table that contains functions or clases that can access the `Private` members of the `class`.
  * Statics: A table that contains the static members of the `class`. These members can only be called through the `class` object.

### Methods

  * new: Returns a new `[object](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/data-types/object)` that contains all of the members given from the `class`.
  * extends: Used to create a new `class` that inherits the `class` it's been created from. **_(Deprecated)_ **
  * overload: Creates an overloaded function with the given access specifier, name and the function table, and saves it to the `class`'s `classData`.

## Properties

### Name  : string

This is an unique identifier of the `class`. Using the [`getClass()`](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/class-functions/main/getClass/) function with this property will retrieve the desired `class`. Since names are unique, for every class, you have to use a different name.

### Inherits  : {class}

Read Only

This table contains all the `class`es that the `class` is inheriting from.

### Friends  : {class}

Read Only

This table contains all the functions and the `class`es that can access all of the `Private` members of the `class`.

### Statics  : {[string]: any}

Read Only

This table contains all the members that are static, meaning they only belong to the `class`. These members won't replicate to the `object`s, and can only be called through the `class` object.

## Methods

### new  : object

Creates and returns a new [`object`](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/data-types/object/) that contains all of the members given from the `class`.

#### Returns

[object](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/data-types/object)

### extends  : void

Used to create and return a new `class` that inherits the `class` it's been created from.

Deprecated

This method has been deprecated and should not be used for new work. Instead, use the [`class`](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/class-functions/main/class/) method.

### overload  : void

Creates an overloaded function with the given parameters, and saves it to the `class`'s `classData` table.

#### Parameters

accessSpecifier: [string](https://create.roblox.com/docs/luau/strings) | The access specifier of the overloaded function.
name: [string](https://create.roblox.com/docs/luau/strings) | The name of the overloaded function.
functionTable: [{(...any) -> (any)}](https://create.roblox.com/docs/luau/tables) | The function table that contains all the functions with different amount of arguments for the overloaded function.

#### Returns

void

### static  : void

Creates a new static member with the given parameters, and saves it to the `class`'s `Statics` table. These members do not replicate to the objects, as they belong to the `class`.

#### Parameters

accessSpecifier: [string](https://create.roblox.com/docs/luau/strings) | The access specifier of the static member.
name: [string](https://create.roblox.com/docs/luau/strings) | The name of the static member.
property: [any](https://create.roblox.com/docs/luau/type-checking#types) | The property that will be saved to the static member. This property can then be accessed through indexing the `class` with the member name, such as: `class.memberName.property`. (Or `class.memberName.p`)

#### Returns

void

Back to top


# API Reference

Welcome to the API Reference section of Class++ !
This section is more technical, and is not recommended for beginners. Check out the [tutorials](https://tenebrisnoctua.github.io/ClassPP/latest/tutorials/) for a guided experience!

You can use the left bar to find the API members grouped by their category. Additionally, you can use the search bar to find a specific API member.

Back to top


#  class :[class](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/data-types/class)

    function classpp.class(className: string): (classData: classData) -> class

Creates a new [`class`](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/data-types/class/) with the given [`classData`](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/types/classData/) table.

## Default Syntax

    local Class = class "Class" {
        constructor = function(self)
            ...
        end,
        destructor = function(self)
            ...
        end,
        Public = {
            ...
        },
        Private = {
            ...
        },
        Protected = {
            ...
        },
        Friend = {
            ...

## Without Syntax Sugar

    local Class = class("Class")({
        constructor = function(self)
            ...
        end,
        destructor = function(self)
            ...
        end,
        Public = {
            ...
        },
        Private = {
            ...
        },
        Protected = {
            ...
        },
        Friend = {
            ...
    })

## Parameters

className: [string](https://create.roblox.com/docs/luau/strings) | An unique name for the `class`.
inheritedClasses: [...class](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/data-types/class) | The `class`es the created `class` will inherit from. (Optional)
classData: [classData](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/types/classData) | The `classData` table that contains the data such as access specifiers for the `class`.

## Returns

[class](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/data-types/class)


# Encapsulation

Encapsulation is a fundamental concept in OOP that combines data (properties) and methods (class functions) that work with that data into a single unit known as a class. This protective layer around the data allows it to maintain its integrity and prevents unauthorized access.

For example, imagine a coffee machine. A coffee machine has a grinder, brewer, and many other internal components that allows it to function. But you don't need to know how it works, you only need to know which buttons to press to get your coffee. This is similar to how it works in OOP.

## Why Encapsulation?

  * **Data Protection**: By making the data private, you hide it from the outside world. Therefore, you protect it from accidental or intentional modifications from outside the class, ensuring its integrity. It also protects sensitive information.
  * **Data Validation**: It allows you to ensure that the given data to the class is valid, and thus allowing for a safer system.
  * **Increased Maintainability**: By controlling the access, any internal implementation changes are less likely to effect other external parts of the program.
  * **Increased Flexibility**: It also allows for a cleaner interface, and due to the internal parts being private, the interface can be reused effectively.

## Implementation

    local class = ClassPP.class

    local Student = class "Student" {
        Public = {
            -- Getter method for name
            getName = function(self)
                return self.Name
            end,

            -- Setter method for name
            setName = function(self, name)
                self.Name = name
            end,

            -- Getter method for age
            getAge = function(self)
                return self.Age
            end,

            -- Setter method for age
            setAge = function(self, age)
                if age > 0 then
                    self.Age = age
                end
            end
        },

        Private = {
            Name = "",
            Age = 0

In the example above, the `Student` class has private members called "Name" and "Age". The setter for the member "Age" includes a basic validation check. Using these getter and setter methods, we are able to access the values for these members.

## Tips

If you want to implement encapsulation, make sure all the important data are set as private, to protect them from external modification. Using getter and setter methods for that data will allow for a much safer experience while allowing flexibility in changing the internal implementation. Like in the example on the previous section, also implementing validation checks will greatly improve data integrity.

Back to top


# Class Functions

So far, we have only created simple members that hold simple values, what about _functions_?

Just like in other object oriented languages, there are two ways to define a function _(method)_ inside a class:

  * Inside class definition
  * Outside class definition

Info

Class Functions are also called Class Methods, and we will use this term from now on in later pages of the tutorials.

## Inside Class Definition

    local class = ClassPP.class

    local Car = class "Car" {
        Public = {
            Brand = "Lamborghini",
            getLicensePlate = function(self)
                print(self.License_Plate)
            end
        },
        Private = {
            License_Plate = "XXXX"

In this example, we have defined a new function inside the public access specifier called `getLicensePlate`, this function when called will print the license plate of our object.

Now, let's create an object from this class:

    local class = ClassPP.class

    local Car = class "Car" {
        Public = {
            Brand = "Lamborghini",
            getLicensePlate = function(self)
                print(self.License_Plate)
            end
        },
        Private = {
            License_Plate = "XXXX"

    local newCar = Car.new()
    newCar:getLicensePlate()

You might have noticed that we've called the `getLicensePlate` function with the `:` (colon) operator. This is a syntax sugar that we use to pass the object itself as the first argument to a function. Calling a function with the `:` operator is equivalent to calling it like `object.method(object)`, but it makes our job easier because we don't have to manually pass the object every single time when calling the function.

Due to this, the first argument of a function will always be what we call `self`, that is a pointer to the object. We use it to access the object's properties inside the function.

### Functions with multiple parameters

    local class = ClassPP.class

    local Car = class "Car" {
        Public = {
            Brand = "Lamborghini",
            setLicensePlate = function(self, newPlate)
                self.License_Plate = newPlate
            end,
            getLicensePlate = function(self)
                return self.License_Plate
            end
        },
        Private = {
            License_Plate = "XXXX"

    local newCar = Car.new()
    newCar:setLicensePlate("A1B2C3") -- Calling the function with an argument
    print(newCar:getLicensePlate()) -- Prints "A1B2C3"!

In the example above, we added a new function called `setLicensePlate` that updates the private member `License_Plate` with the provided `newPlate` parameter. We've also updated the `getLicensePlate` function to return the private member `License_Plate`, which we then use to print the now updated value of the member. Make sure to remember that `self` should always be the first parameter, and all the other parameters should come after it.

## Outside Class Definition

You're not limited to always defining functions inside of the `classData` itself. You can also define it outside of the `classData`, by using the method shown below.

    local class = ClassPP.class

    local Car = class "Car" {
        Public = {
            Brand = "Lamborghini",
        },
        Private = {
            License_Plate = "XXXX"

    function Car.Public:getLicensePlate(number) -- You should always define a function through the Class.AccessSpecifier:FunctionName syntax.
        print(self.License_Plate, number) -- Prints "XXXX 1"!
    end

    local newCar = Car.new()
    newCar:getLicensePlate(1)

In this example, we defined a function outside of the class by specifying the class name, then the access specifier, followed by the `:` operator and the name of the function.

### Typechecking

Unlike any other object oriented language such as C++, Class++ does not require you to define class functions inside the `classData` first to use the outside class definition method. However, it comes with certain disadvantages.

Unfortunately, defining class functions this way alone will not give you any intellisense when you're calling the function through the object. This is due to the limitations of the Luau's typechecking system. So, to get around this, we define the class function first inside the `classData` with only its parameter types, and then using the outside class definition method, we define the actual body of the function outside of the `classData`.

    local ClassPP = require('./ClassPP')
    local class = ClassPP.class

    local Car = class "Car" {
        Public = {
            Brand = "Lamborghini",
            getLicensePlate = function(number) end
        },
        Private = {
            License_Plate = "XXXX"

    function Car.Public:getLicensePlate(number)
        print(self.License_Plate, number) -- Still prints "XXXX 1"!
    end

    local newCar = Car.new()
    newCar:getLicensePlate(1)

Recommended

Outside Class Definition method with the syntax above is recommended as it allows for a better formatting style.

Back to top


# Function Overloading

Just like in other object oriented languages, Class++ supports function overloading.

Function Overloading is a feature where multiple functions can share the same name, but they are different from each other with different amount of arguments.

To do function overloading in Class++, you have to use the `class.overload(<accessSpecifier>, <name>, <functionTable>)` function.

    local class = ClassPP.class

    local Test = class "Test" {
        Public = {
            ValueA = 0,
            ValueB = 0

    Test.overload("Public", "Set", {
        function(self, a, b)
            print(a, b)
            self.ValueA = a
            self.ValueB = b
        end,
        function(self, a)
            print(a)
            self.ValueA = a
        end,
        function(self)
            print(self.ValueA, self.ValueB)
        end
    })

    local newTest = Test.new()
    newTest:Set(1, 2) -- Prints "1 2"
    newTest:Set(3) -- Prints "3"
    newTest:Set() -- Prints "3 2"

In this example, to create an overloaded function, we used the `.overload()` function with the `Public` access specifier, and `Set` for the function name. Then we gave a table with multiple functions with different arguments that each do a different action. When we call the `:Set()` function through the object, depending on the amount of the given arguments, only the function that accepts the same amount of arguments will be ran.

Warning

Giving multiple functions that have the same amount of arguments will cause only one of these functions to be ran. Only create one function for a specific amount of arguments.

Back to top


# Non-Access Specifiers

Unlike access specifiers, non-access specifiers do not modify the access control of a member, but rather provide other functionality for classes.

## Final

Using this non-access specifier will make the given class final, meaning this class now cannot be inherited by other classes.

    local class, final = ClassPP.class, ClassPP.final

    local Car = final { class "Car" {
        Public = {
            Brand = "Ford",

    local BiggerCar = class "BiggerCar" (Car, nil) { -- This will error!
        Public = {
            Brand = "Tesla"

## Abstract

Using this non-access specifier will make the given class an abstract class, meaning this class now cannot be used to create objects. To access an abstract class's members, you need to create a class that inherits from this abstract class.

    local class, abstract = ClassPP.class, ClassPP.abstract

    local BaseCar = abstract { class "BaseCar" {
        Public = {
            Brand = "",
            Model = "",
            Year = 0,
            honk = function(self)
                print("honk honk!")
            end

    local Car = class "Car" (BaseCar, nil) {
        Public = {
            Brand = "Ford",
            Model = "Mustang",
            Year = 2023

    local newBaseCarObj = BaseCar.new() -- This will error!
    local newCarObj = Car.new() -- This will work fine!
    newCarObj:honk()

Warning

Creating Private members in an abstract class will cause these members to never be accessible, since Private members of a class will never be inherited to another class.

    local class, abstract = ClassPP.class, ClassPP.abstract

    local BaseCar = abstract { class "BaseCar" {
        Public = {
            Brand = "",
            Model = "",
            Year = 0,
            honk = function(self)
                print("honk honk!")
            end
        },
        Private = {
            Secret = "" -- This property will never be accessible!

Warning

You cannot make a class both abstract and final, as they have opposite meanings. An abstract class must be subclassed, whereas a final class cannot be subclassed. Attempting to form an illegal combination between final and abstract methods will cause an error.

Back to top