API Stability Notice
Macroforge is under active development. The API is not yet stable and may change between versions. Some documentation sections may be outdated.
Context and IR
Every macro runs with a MacroContextIR: what invoked it, where, and a structured view
(the IR) of the declaration it is attached to. Derive macros usually read the IR through DeriveInput; this page
describes the context and IR types underneath, which attribute macros use directly.
MacroContextIR
A macro gets its context from its input stream with input.context(), or from DeriveInput's context field.
pub struct MacroContextIR {
pub macro_kind: MacroKind, // Derive, Attribute or Call
pub macro_name: String, // e.g. "Debug"
pub module_path: String, // the module the macro was imported from
pub decorator_span: SpanIR, // the whole @derive(...) or @attr(...)
pub macro_name_span: Option<SpanIR>, // just the macro's name in it
pub target_span: SpanIR, // the declaration the macro is attached to
pub file_name: String, // the file being expanded
pub target: TargetIR, // the declaration, as IR
pub target_source: String, // the declaration's source text
pub import_registry: ImportRegistry, // the file's imports
pub config: Option<MacroforgeConfig>, // the project's macroforge.config
pub type_registry: TypeRegistry, // every type in the project
pub resolved_fields: Option<HashMap<String, ResolvedTypeRef>>,
pub abi_version: u32,
}| Method | Returns |
|---|---|
as_class(), as_interface(), as_enum(), as_type_alias(), as_function() | The target as that kind of IR, or None |
error_span() | The macro's name if known, else the whole decorator: the place to point an error |
import_specifier_for(type_name) | The module the file should import a type from; see Type-Aware Macros |
type_registry, resolved_fields and config are covered in Type-Aware Macros, the import methods in Output and Imports.
$name(...)) gets a minimal context: its target is TargetIR::Other, target_source is the text between the parentheses,
and the file name, config and registries are empty.TargetIR
pub enum TargetIR {
Class(ClassIR),
Interface(InterfaceIR),
Enum(EnumIR),
TypeAlias(TypeAliasIR),
Function(FunctionIR),
Other, // a call macro's arguments, or anything unsupported
}An attribute macro on a class method receives the method as a FunctionIR.
Type parameters
Every generic declaration lists its type parameters as declared. declaration() renders one as a generic function would declare it, and declare_all and apply_all render a whole list as <T extends Shape> and <T>. Variance annotations (in, out) are not
kept: TypeScript rejects them on a function.
pub struct TypeParamIR {
pub name: String,
pub constraint: Option<String>, // the type after \`extends\`
pub default: Option<String>, // the type after \`=\`
pub is_const: bool,
}Classes
pub struct ClassIR {
pub name: String,
pub span: SpanIR,
pub body_span: SpanIR, // the braces and everything between them
pub is_abstract: bool,
pub type_params: Vec<TypeParamIR>, // e.g. T, K extends string
pub heritage: Vec<String>, // extends and implements clauses
pub decorators: Vec<DecoratorIR>,
pub fields: Vec<FieldIR>,
pub methods: Vec<MethodSigIR>,
}
pub struct FieldIR {
pub name: String,
pub span: SpanIR,
pub ts_type: String, // the annotation as written, e.g. "string[]"
pub optional: bool, // declared with ?
pub readonly: bool,
pub visibility: Visibility, // Public, Protected or Private
pub decorators: Vec<DecoratorIR>,
}
pub struct MethodSigIR {
pub name: String,
pub span: SpanIR,
pub type_params: Vec<TypeParamIR>,
pub params_src: String, // the parameter list as written
pub return_type_src: String,
pub is_static: bool,
pub is_async: bool,
pub visibility: Visibility,
pub decorators: Vec<DecoratorIR>,
pub body_span: Option<SpanIR>, // None for an abstract method
pub body_src: Option<String>,
}Interfaces
pub struct InterfaceIR {
pub name: String,
pub span: SpanIR,
pub body_span: SpanIR,
pub type_params: Vec<TypeParamIR>,
pub heritage: Vec<String>, // extends clauses
pub decorators: Vec<DecoratorIR>,
pub fields: Vec<InterfaceFieldIR>,
pub methods: Vec<InterfaceMethodIR>,
}
pub struct InterfaceFieldIR {
pub name: String,
pub span: SpanIR,
pub ts_type: String,
pub optional: bool,
pub readonly: bool,
pub decorators: Vec<DecoratorIR>,
}
pub struct InterfaceMethodIR {
pub name: String,
pub span: SpanIR,
pub type_params: Vec<TypeParamIR>,
pub params_src: String,
pub return_type_src: String,
pub optional: bool,
pub decorators: Vec<DecoratorIR>,
}Enums
pub struct EnumIR {
pub name: String,
pub span: SpanIR,
pub body_span: SpanIR,
pub decorators: Vec<DecoratorIR>,
pub variants: Vec<EnumVariantIR>,
pub is_const: bool, // declared const enum
}
pub struct EnumVariantIR {
pub name: String,
pub span: SpanIR,
pub value: EnumValue,
pub decorators: Vec<DecoratorIR>,
}
pub enum EnumValue {
String(String), // Active = "active"
Number(f64), // Low = 1
Auto, // no initializer: one more than the previous member
Expr(String), // Mask = A | B, kept as source text
}EnumValue has a test and an accessor for each kind: is_string()/as_string(), is_number()/as_number(), is_expr()/as_expr() and is_auto().
Type Aliases
pub struct TypeAliasIR {
pub name: String,
pub span: SpanIR,
pub decorators: Vec<DecoratorIR>,
pub type_params: Vec<TypeParamIR>,
pub body: TypeBody,
}
pub enum TypeBody {
Union(Vec<TypeMember>), // A | B | "c"
Intersection(Vec<TypeMember>), // A & B
Object { fields: Vec<InterfaceFieldIR> }, // { a: string }
Tuple(Vec<String>), // [string, number]
Alias(String), // Other<T>
Other(String), // anything else, as source text
}
pub struct TypeMember {
pub kind: TypeMemberKind,
pub decorators: Vec<DecoratorIR>, // JSDoc tags on a union member
}
pub enum TypeMemberKind {
Literal(String), // "active", 42
TypeRef(String), // User
Object { fields: Vec<InterfaceFieldIR> },
Intersection(Vec<TypeMember>),
}TypeBody has is_union(), is_intersection(), is_object(), is_tuple() and is_alias(), each with an as_...() accessor. TypeMember has is_literal(), is_type_ref(), is_object(), their as_...() accessors, as_intersection_members(), type_name() (the name of a literal or type
reference) and has_decorator(name).
Functions
Attribute macros on functions, and on class methods, receive a FunctionIR:
pub struct FunctionIR {
pub name: String,
pub span: SpanIR,
pub body_span: SpanIR,
pub signature_span: SpanIR, // from the start to the body's opening brace
pub is_async: bool,
pub is_generator: bool,
pub is_exported: bool,
pub is_default_export: bool,
pub type_params: Vec<TypeParamIR>,
pub params: Vec<FunctionParamIR>,
pub return_type_src: String,
pub body_src: String,
pub decorators: Vec<DecoratorIR>,
}
pub struct FunctionParamIR {
pub name: String,
pub span: SpanIR,
pub type_src: String,
pub default_src: Option<String>,
pub is_optional: bool,
pub is_rest: bool,
pub decorators: Vec<DecoratorIR>,
}Decorators
pub struct DecoratorIR {
pub name: String, // e.g. "endec"
pub args_src: String, // the arguments as written, e.g. "skip, rename: \\"id\\""
pub span: SpanIR,
}A macro reads its options from args_src. The helpers in macroforge_ts::builtin::derive::common parse the common shapes:
use macroforge_ts::builtin::derive::common::{extract_named_string, has_flag};
for decorator in &field.decorators {
if decorator.name.eq_ignore_ascii_case("validate") {
let required = has_flag(&decorator.args_src, "required");
// rename: "id", rename = "id" or rename("id"); escapes are decoded
let rename = extract_named_string(&decorator.args_src, "rename");
}
}Spans
Every IR node carries a SpanIR. Spans count positions, which are byte offsets plus
one; see Spans for reading the text a
span covers.