5.4.12. Macro Tutorial 12: Typeinfo Macros
Previous tutorials transformed calls, functions, structures, blocks,
variants, for-loops, lambda captures, and reader macros. Typeinfo macros
extend the built-in typeinfo expression with custom compile-time
type introspection.
[typeinfo_macro(name="X")] registers a class that extends
AstTypeInfoMacro. The macro is invoked with the syntax
typeinfo X(expr) (gen2) or typeinfo X<subtrait>(expr). During
type inference the compiler calls:
getAstChange(expr, errors) → ExpressionPtrReceives an
ExprTypeInfonode and returns a replacement AST expression — typically a constant (string, int, bool) or an array literal. The returned expression replaces the entiretypeinfocall at compile time. Returnnullwith an error message to report a compilation error.
5.4.12.1. ExprTypeInfo fields
The expr parameter exposes several fields:
Field |
Type |
Description |
|---|---|---|
|
|
The type argument (from |
|
|
The expression argument (if used) |
|
|
The |
|
|
The second |
|
|
The name of the typeinfo trait |
|
|
Source location for error reporting |
typeexpr gives access to the full type declaration. On structure
types, typeexpr.structType.fields is a list of FieldDeclaration
nodes (each with name, _type, flags). On enum types,
typeexpr.enumType.list contains EnumEntry nodes (each with
name).
5.4.12.1.1. Motivation
The typeinfo keyword already provides many built-in traits:
sizeof, typename, is_ref, has_field, is_enum,
is_struct, and dozens more. But sometimes you need domain-specific
compile-time introspection — a description string for serialization, an
array of enum names for UI dropdowns, or a boolean check for conditional
compilation. [typeinfo_macro] lets you add such traits using pure
daslang code.
5.4.12.1.2. The module file
The macro module defines three AstTypeInfoMacro subclasses, each
returning a different expression type (string, array, bool).
Full source: typeinfo_macro_mod.das
5.4.12.2. struct_info — returning a string
typeinfo struct_info(type<T>) builds a description string at compile
time:
[typeinfo_macro(name="struct_info")]
class TypeInfoGetStructInfo : AstTypeInfoMacro {
def override getAstChange(expr : smart_ptr<ExprTypeInfo>;
var errors : das_string) : ExpressionPtr {
if (expr.typeexpr == null) {
errors := "type is missing or not inferred"
return <- default<ExpressionPtr>
}
if (!expr.typeexpr.isStructure) {
errors := "expecting structure type"
return <- default<ExpressionPtr>
}
var result = "{expr.typeexpr.structType.name}("
var first = true
for (i in iter_range(expr.typeexpr.structType.fields)) {
assume fld = expr.typeexpr.structType.fields[i]
if (fld.flags.classMethod) {
continue
}
if (!first) {
result += ", "
}
result += "{fld.name}:{describe(fld._type, false, false, false)}"
first = false
}
result += ")"
return <- new ExprConstString(at = expr.at, value := result)
}
}
Key points:
expr.typeexpr.isStructurevalidates that the type is a structure.expr.typeexpr.structType.fieldsiterates all field declarations.fld.flags.classMethodskips class methods (only data fields appear).describe(fld._type, ...)converts aTypeDeclPtrto a human-readable type name.The result is an
ExprConstString— a compile-time string constant.
5.4.12.3. enum_value_strings — returning an array
typeinfo enum_value_strings(type<E>) returns a fixed-size array of
enum value names:
[typeinfo_macro(name="enum_value_strings")]
class TypeInfoGetEnumValueStrings : AstTypeInfoMacro {
def override getAstChange(expr : smart_ptr<ExprTypeInfo>;
var errors : das_string) : ExpressionPtr {
// ... validation ...
var inscope arr <- new ExprMakeArray(
at = expr.at,
makeType <- typeinfo ast_typedecl(type<string>))
for (i in iter_range(expr.typeexpr.enumType.list)) {
if (true) {
assume entry = expr.typeexpr.enumType.list[i]
var inscope nameExpr <- new ExprConstString(
at = expr.at, value := entry.name)
arr.values |> emplace <| nameExpr
}
}
return <- arr
}
}
Key points:
ExprMakeArrayrequires amakeTypefield — usetypeinfo ast_typedecl(type<string>)to get the AST representation ofstring.expr.typeexpr.enumType.listiterates allEnumEntrynodes.The
if (true)block is needed forvar inscopelifetime scoping (each loop iteration creates and consumes a newinscopesmart pointer).The result is a fixed-size array (
string[N]), not a dynamicarray<string>.
5.4.12.4. has_non_static_method — returning a bool with subtrait
typeinfo has_non_static_method<name>(type<T>) checks whether a class
has a non-static method with the given name. The method name is passed
via the subtrait parameter:
[typeinfo_macro(name="has_non_static_method")]
class TypeInfoHasNonStaticMethod : AstTypeInfoMacro {
def override getAstChange(expr : smart_ptr<ExprTypeInfo>;
var errors : das_string) : ExpressionPtr {
// ... validation ...
if (empty(expr.subtrait)) {
errors := "expecting method name as subtrait"
return <- default<ExpressionPtr>
}
var found = false
for (i in iter_range(expr.typeexpr.structType.fields)) {
assume fld = expr.typeexpr.structType.fields[i]
if (fld.name == expr.subtrait && fld.flags.classMethod) {
found = true
break
}
}
return <- new ExprConstBool(at = expr.at, value = found)
}
}
Key points:
expr.subtraitis the string between angle brackets — intypeinfo has_non_static_method<speak>(type<T>),subtraitis"speak".Class methods are stored as struct fields with the
classMethodflag.The result is
ExprConstBool— a compile-time boolean, perfect for use instatic_ifconditional compilation.
5.4.12.4.1. The usage file
Full source: 12_typeinfo_macro.das
5.4.12.5. Section 1: struct_info
struct Vec3 {
x : float
y : float
z : float
}
struct Person {
name : string
age : int
}
def section1() {
let v = typeinfo struct_info(type<Vec3>)
print(" Vec3: {v}\n")
let p = typeinfo struct_info(type<Person>)
print(" Person: {p}\n")
}
typeinfo struct_info(type<Vec3>) is replaced at compile time with
the constant string "Vec3(x:float, y:float, z:float)". No runtime
reflection is involved.
5.4.12.6. Section 2: enum_value_strings
enum Color {
Red
Green
Blue
}
def section2() {
let colors = typeinfo enum_value_strings(type<Color>)
for (c in colors) {
print(" {c}\n")
}
}
The macro generates a fixed-size string[3] array containing
"Red", "Green", "Blue". Use let (not var <-) to
bind the result — fixed-size arrays are value types.
5.4.12.7. Section 3: has_non_static_method
class Animal {
name : string
def speak() {
print(" {name} says hello\n")
}
}
class Rock {
weight : float
}
def section3() {
let animal_can_speak = typeinfo has_non_static_method<speak>(type<Animal>)
let rock_can_speak = typeinfo has_non_static_method<speak>(type<Rock>)
static_if (typeinfo has_non_static_method<speak>(type<Animal>)) {
print(" (static_if confirmed: Animal can speak)\n")
}
}
The subtrait makes typeinfo macros parametric — the same macro handles
any method name. Since the result is a compile-time bool, it works
directly in static_if for conditional code generation.
5.4.12.7.1. Output
--- Section 1: struct_info ---
Vec3: Vec3(x:float, y:float, z:float)
Person: Person(name:string, age:int)
--- Section 2: enum_value_strings ---
Color values (3):
Red
Green
Blue
Direction values (4):
North
South
East
West
--- Section 3: has_non_static_method ---
Animal has 'speak': true
Animal has 'fly': false
Rock has 'speak': false
(static_if confirmed: Animal can speak)
5.4.12.7.2. Real-world usage
The standard library provides several typeinfo macros:
typeinfo fields_count(type<T>)— number of struct fields (daslib/type_traits.das)typeinfo safe_has_property<name>(expr)— does a type have a property function? (daslib/type_traits.das)typeinfo enum_length(type<E>)— number of enum values (daslib/enum_trait.das)typeinfo enum_names(type<E>)— array of enum value name strings (daslib/enum_trait.das)
See also
Full source:
12_typeinfo_macro.das,
typeinfo_macro_mod.das
Previous tutorial: tutorial_macro_reader_macro
Next tutorial: tutorial_macro_enumeration_macro
Standard library: daslib/type_traits.das, daslib/enum_trait.das
Language reference: Macros — full macro system documentation