User Macros
User macros are macros that can be defined by user code. They allow you to tap into metadata about type parameters and the call site.
Defining a user macro
You define a macro simply by using the @metadata macro
tag and using one of Flamework's macro types as a parameter.
User macros can be defined on classes (constructors), methods, and plain functions.
If you'd like users to be able to pass arguments to a macro argument, you are able to use a union. Whenever Flamework encounters a macro argument with a union, it will use the first macro type it encounters, or ignore the argument if a user has passed one.
Certain Flamework macros use this behavior to allow users to pass strings where Flamework would otherwise generate an ID, such as id?: string | Modding.Generic<T, "id">
.
/** @metadata macro */
function macro<T>(abc?: Modding.Generic<T, "id">, xyz?: Modding.CallerMany<"line" | "char">) {
assert(abc && xyz);
print(abc, `${xyz.line}:${xyz.char}`);
}
macro<MyInterface>();
Nesting user macros
If you have a user macro that you'd like to call from within another user macro, you will need to include the macro's metadata in your signature, and pass it down.
With Modding.GenericMany
and Modding.CallerMany
, you can include additional metadata and it can still be passed down.
For more complex macros, it is recommended to export your macro's metadata so that consumers can use your macro in their own.
type BaseMacroMetadata<T> = Modding.GenericMany<T, "id" | "text">;
/** @metadata macro */
function baseMacro<T>(abc?: BaseMacroMetadata<T>) {}
/** @metadata macro */
function newMacro<T>(param: string, macro?: BaseMacroMetadata<T>) {
return baseMacro<T>(macro);
}
Advanced User Macros
Flamework supports a more advanced form of user macros allowing you to perform arbitrary conditions, mapped types and similar at compile time.
This is achieved using the Modding.Many
user macro API.
I've provided a list of syntax that you can use, but it is not exhaustive and Flamework can generate most types without any issues.
Exposing your macro metadata
In libraries, it is recommended that you specify macro metadata in an interface and expose it publicly. This allows users to easily nest your macro inside of their own.
export interface DoSomethingMacro<T> {}
declare function doSomething<T>(metadata?: Modding.Many<DoSomethingMacro<T>>);
Objects / Tuples
You can provide an object or a tuple to Modding.Many
and Flamework will generate an identical value when the function is called, for example:
declare function macro<T>(metadata?: Modding.Many<{ a: Modding.Generic<T, "id">, b: Modding.Caller<"uuid"> }>);
declare function macro<T>(metadata?: Modding.Many<[Modding.Generic<T, "id">, Modding.Caller<"uuid">]>);
Arrays
Besides tuples, Flamework also supports generating arrays using de-unification.
This means that you are able to turn a union (e.g keyof T
) into an array of all constituents.
declare function macro<T>(keysOfT?: Modding.Many<(keyof T)[]>);
Mapped types
You are able to use mapped types to generate derivatives of an object which can be used to fetch additional information about members of a type.
declare function macro<T>(guardsForEachMember?: Modding.Many<{ [k in keyof T]: Modding.Generic<T[k], "guard"> }>);
Literals / Conditionals
You can use conditional types to simulate if statements at compile-time. Flamework supports generating most literal values such as numbers, strings, booleans and undefined.
declare function macro<T>(isString?: Modding.Many<T extends string ? true : false>);
Utility types
Flamework provides some additional utility types besides Modding.Generic
and Modding.Caller
which you can find here.
Modding.Hash
Modding.Hash
allows you to generate a UUID based off a string (and an optional context.)
Modding.Obfuscate
This behaves identically to Modding.Hash
except it is only enabled when Flamework obfuscation is enabled.
Modding.TupleLabels
This retrieves the labels of a tuple and can be used in conjunction with Parameters<T>
to retrieve parameter names from a function type.
Generic Metadata
Generic metadata exposes some built-in Flamework functions for types, such as guard generation and ID generation. The input does not have to be a type parameter, and you can use it on any types, such as a mapped type.
This metadata can be accessed through Modding.Generic<T, M>
where T
is the target type, and M
is a string literal for the name of the metadata.
Whilst Modding.Generic
can only retrieve one metadata at a time, Modding.GenericMany
can generate an object given a union of metadata, e.g "id" | "text"
.
interface GenericMetadata<T> {
/**
* The ID of the type.
*/
id: string;
/**
* A string equivalent of the type, such as the one displayed in your code editor.
*/
text: string;
/**
* A generated guard for the type.
*/
guard: t.check<T>;
}
Callsite Metadata
You can access callsite metadata by using Modding.Caller<M>
. Metadata about the source text ignores leading and trailing trivia.
Whilst Modding.Caller
can only retrieve one metadata at a time, Modding.CallerMany
can generate an object given a union of metadata, e.g "line" | "character"
.
interface CallerMetadata {
/**
* The starting line of the expression.
*/
line: number;
/**
* The char at the start of the expression relative to the starting line.
*/
character: number;
/**
* The width of the expression.
* This includes the width of multiline statements.
*/
width: number;
/**
* A unique identifier that can be used to identify exact callsites.
* This can be used for hooks.
*/
uuid: string;
/**
* The source text for the expression.
*/
text: string;
}