lloyAlloy on GitHub
The process

A change to the language is written down first.

An RFC is one Markdown file that says what changes, why, what the compiler emits, and what it costs. It stays open for at least two weeks, so there is time to read it and to raise a concern. The proposals on this page are the merged ones: the design is settled and the feature may be built. A proposal that is still under discussion is an open pull request.

The open proposals live on GitHub →

Filter by area
SyntaxImplemented

Luau parity and lighter headers

A Luau file reads in Alloy the way Luau reads it, and five Alloy forms now mean what they look like. The words trait, impl, remote, macro, attribute, namespace, private, and public are names away from their declarations. [[ ... ]] is a long string. A declaration header needs no as when its body starts on the next line. A match guard is written where. A catch-all arm counts toward an exhaustive match, a value name in a pattern reports, and in on a { } literal with items reports. An arm can run statements before its value, and return in it leaves the function. @allow also takes the rustc and Clippy names of the lints Alloy has.

SyntaxImplemented

Parallel Luau: actor scripts, parallel blocks, and typed messages

Four pieces, each small on its own. A file named x.server.actor.aly or x.client.actor.aly builds into an Actor that holds the script, the way .server. and .client. already pick a script class. A parallel do ... end block runs desynchronized, and the compiler refuses the writes the engine refuses. A message declaration is a typed channel between actors, the way remote is a typed channel across the network. A reply(...) clause gives a message a typed answer that any script can take.

StdAccepted

Error, Exception, and throw

The std gains two types and the language gains one word. Error is a value a failure carries, built with new Error("message") and extended by any struct that implements the Error trait. throw e raises it, which try do ... end catches as an Err. Exception is the same shape for a condition that must be seen and must not stop the caller. warn(e) reports one: its __tostring writes the name, the message, and the traceback, so Luau's own warn needs no new word beside it.

ConfigAccepted

The std by import

A file imports the std names it uses: import { HashMap, Set } from "@alloy/std/collections". The std is laid out in modules by subject, and @alloy/std re-exports all of them. The names the language owns stay ambient: Future, Result, Ok, Err, Array, and the operator traits. A project that wants more ambient names says so: [std] globals = "all" makes every std name ambient, and a list, globals = ["Signal", "Iter"], makes those names ambient. Serialize and Deserialize move to @alloy/std/serde, and @derive takes them as imported names. The emit does not change in any case.

SyntaxImplemented

Type negation with ~

A type may be negated. ~number is every value that is not a number, and it reads in a binding, a parameter, a return, a union, and a generic bound. The compiler lowers it to a Luau type function, so the analyzer checks it and the editor already prints it.

SyntaxImplemented

Parameter destructuring

A parameter may be a table pattern instead of a name: function draw({ x, y }: Point) binds x and y from the argument. A field may carry its own type, function foo({ bar: string, baz: number }), and the pattern then states the parameter's type. ...rest takes the fields the pattern does not name.

SyntaxImplemented

as on a match scrutinee

match expr as name with binds the value under match to name for every arm, guard, and default. The value is computed once. The name is a plain local of the match; nothing else changes.

AttributesNoted for later

Conditional attribute contracts

A requires clause may end in where <condition>, a condition over the attribute's own arguments. The clause applies only when the condition holds. The compiler reads the condition from the literal arguments at the use site, the same way each reads a list today. Nothing runs.

SyntaxNoted for later

Statics: the static keyword for members of a type

A type gains members that belong to it and not to a value, and one word marks every one of them: static. A struct body takes static fields and static const constants, read as Wallet.made and Wallet.MAX; an impl takes static function, called as Wallet.new(), Vector3.zero(), Status.describe(s). A function in an impl with no self and no static is an error with a fix, and self inside a static function is an error. This proposal gives the syntax, the emit, the checks, and the editor's answers, and settles the questions the last probe rounds raised for static functions.

GlobalsImplemented

Remove global

global leaves the language. A global local, global const, global function, global struct, global type, global namespace, and global attribute each become an error that names the import to write. The side rules that exist only for globals leave with it: --@alloy-side, --@alloy-file-side, the [contexts] table, the shared-context detection, and the mutable_global lint.

AttributesImplemented

Attribute contracts

An attribute declaration may state what the thing it sits on must carry: a member of a given name, kind, type, and visibility. The compiler checks it where the attribute is used. The required set may come from the attribute's own arguments, so one attribute serves many shapes.