// Copyright (c) 2020-2024 Joe Conigliaro. All rights reserved. // Use of this source code is governed by an MIT license // that can be found in the LICENSE file. module ast pub type FlatNodeId = int pub const invalid_flat_node_id = FlatNodeId(-1) // FlatNodeKind is a dense tag for every stored AST node variant. pub enum FlatNodeKind { file stmt_asm stmt_assert stmt_assign stmt_block stmt_comptime stmt_const_decl stmt_defer stmt_directive stmt_empty stmt_enum_decl stmt_expr stmt_flow_control stmt_fn_decl stmt_for_in stmt_for stmt_global_decl stmt_import stmt_interface_decl stmt_label stmt_module stmt_return stmt_struct_decl stmt_type_decl stmt_attributes expr_array_init expr_as_cast expr_assoc expr_basic_literal expr_call expr_call_or_cast expr_cast expr_comptime expr_empty expr_fn_literal expr_generic_arg_or_index expr_generic_args expr_ident expr_if expr_if_guard expr_index expr_infix expr_init expr_keyword expr_keyword_operator expr_lambda expr_lock expr_map_init expr_match expr_modifier expr_or expr_paren expr_postfix expr_prefix expr_range expr_select expr_selector expr_sql expr_string_inter expr_string expr_tuple expr_unsafe typ_anon_struct typ_array_fixed typ_array typ_channel typ_fn typ_generic typ_map typ_nil typ_none typ_option typ_result typ_thread typ_tuple aux_attribute aux_field_init aux_field_decl aux_parameter aux_match_branch aux_string_inter } // FlatEdge represents a parent->child relationship. pub struct FlatEdge { pub: child_id FlatNodeId } // FlatNode stores a compact, index-based AST node. pub struct FlatNode { pub: kind FlatNodeKind data_idx int = -1 first_edge int edge_count int } // FlatFile maps a source file to its flat root node. pub struct FlatFile { pub: file_id FlatNodeId name_idx int mod_idx int } // FlatAst is a contiguous AST graph representation. pub struct FlatAst { pub mut: files []FlatFile nodes []FlatNode edges []FlatEdge strings []string } // FlatAstStats captures high-level memory and shape metrics. pub struct FlatAstStats { pub: file_roots int nodes int edges int strings int string_bytes u64 bytes_estimate u64 } // LegacyAstStats captures estimated dynamic memory and node shape for the existing AST. pub struct LegacyAstStats { pub mut: files int expr_nodes int stmt_nodes int type_nodes int aux_nodes int node_bytes u64 array_bytes u64 string_entries int string_bytes u64 bytes_estimate u64 } // flatten_files converts recursive v2 AST files into a flat, index-based graph. pub fn flatten_files(files []File) FlatAst { mut b := new_flat_builder() for file in files { file_id := b.add_file(file) b.flat.files << FlatFile{ file_id: file_id name_idx: b.intern(file.name) mod_idx: b.intern(file.mod) } } return b.flat } // legacy_ast_stats estimates dynamic memory and node counts for the recursive AST. pub fn legacy_ast_stats(files []File) LegacyAstStats { mut w := LegacyAstWalker{} w.walk_files(files) return w.stats } // count_legacy_nodes returns the total legacy node count used by the walker. pub fn count_legacy_nodes(files []File) int { stats := legacy_ast_stats(files) return stats.files + stats.expr_nodes + stats.stmt_nodes + stats.type_nodes + stats.aux_nodes } // stats returns aggregate shape and estimated memory usage for FlatAst. pub fn (flat &FlatAst) stats() FlatAstStats { mut string_bytes := u64(0) for s in flat.strings { string_bytes += u64(s.len) } mut bytes := u64(flat.files.len) * u64(sizeof(FlatFile)) bytes += u64(flat.nodes.len) * u64(sizeof(FlatNode)) bytes += u64(flat.edges.len) * u64(sizeof(FlatEdge)) bytes += u64(flat.strings.len) * u64(sizeof(string)) bytes += string_bytes return FlatAstStats{ file_roots: flat.files.len nodes: flat.nodes.len edges: flat.edges.len strings: flat.strings.len string_bytes: string_bytes bytes_estimate: bytes } } // count_reachable_nodes traverses from file roots and counts unique reachable nodes. pub fn (flat &FlatAst) count_reachable_nodes() int { if flat.nodes.len == 0 || flat.files.len == 0 { return 0 } mut seen := []bool{len: flat.nodes.len} mut stack := []int{cap: flat.files.len} for file in flat.files { stack << file.file_id } mut count := 0 for stack.len > 0 { node_id := stack.pop() if node_id < 0 || node_id >= flat.nodes.len { continue } if seen[node_id] { continue } seen[node_id] = true count++ node := flat.nodes[node_id] for i in 0 .. node.edge_count { edge := flat.edges[node.first_edge + i] stack << edge.child_id } } return count } // node_type_name returns the short type-name tag associated with a flat node. pub fn (flat &FlatAst) node_type_name(node_id FlatNodeId) string { if node_id < 0 || node_id >= flat.nodes.len { return '' } return flat.nodes[node_id].kind.str() } struct FlatBuilder { mut: flat FlatAst string_ids map[string]int } fn new_flat_builder() FlatBuilder { return FlatBuilder{ flat: FlatAst{ files: []FlatFile{} nodes: []FlatNode{cap: 1024} edges: []FlatEdge{cap: 2048} strings: []string{cap: 512} } string_ids: map[string]int{} } } fn (mut b FlatBuilder) intern(s string) int { if idx := b.string_ids[s] { return idx } idx := b.flat.strings.len b.flat.strings << s b.string_ids[s] = idx return idx } fn (mut b FlatBuilder) add_node(kind FlatNodeKind, data string, edges []FlatEdge) FlatNodeId { first_edge := b.flat.edges.len if edges.len > 0 { b.flat.edges << edges } node_id := FlatNodeId(b.flat.nodes.len) b.flat.nodes << FlatNode{ kind: kind data_idx: if data.len > 0 { b.intern(data) } else { -1 } first_edge: first_edge edge_count: edges.len } return node_id } fn (mut b FlatBuilder) push_edge(mut edges []FlatEdge, child FlatNodeId) { if child == invalid_flat_node_id { return } edges << FlatEdge{ child_id: child } } fn (mut b FlatBuilder) push_expr(mut edges []FlatEdge, expr Expr) { b.push_edge(mut edges, b.add_expr(expr)) } fn (mut b FlatBuilder) push_stmt(mut edges []FlatEdge, stmt Stmt) { b.push_edge(mut edges, b.add_stmt(stmt)) } fn (mut b FlatBuilder) push_type(mut edges []FlatEdge, typ Type) { b.push_edge(mut edges, b.add_type(typ)) } fn (mut b FlatBuilder) push_attribute(mut edges []FlatEdge, attr Attribute) { b.push_edge(mut edges, b.add_attribute(attr)) } fn (mut b FlatBuilder) push_field_init(mut edges []FlatEdge, field FieldInit) { b.push_edge(mut edges, b.add_field_init(field)) } fn (mut b FlatBuilder) push_field_decl(mut edges []FlatEdge, field FieldDecl) { b.push_edge(mut edges, b.add_field_decl(field)) } fn (mut b FlatBuilder) push_parameter(mut edges []FlatEdge, param Parameter) { b.push_edge(mut edges, b.add_parameter(param)) } fn (mut b FlatBuilder) push_match_branch(mut edges []FlatEdge, branch MatchBranch) { b.push_edge(mut edges, b.add_match_branch(branch)) } fn (mut b FlatBuilder) push_string_inter(mut edges []FlatEdge, inter StringInter) { b.push_edge(mut edges, b.add_string_inter(inter)) } fn (mut b FlatBuilder) collect_children[T](node T, mut edges []FlatEdge) { $for field in T.fields { $if field.typ is Expr { b.push_expr(mut edges, node.$(field.name)) } $else $if field.typ is []Expr { for child in node.$(field.name) { b.push_expr(mut edges, child) } } $else $if field.typ is Stmt { b.push_stmt(mut edges, node.$(field.name)) } $else $if field.typ is []Stmt { for child in node.$(field.name) { b.push_stmt(mut edges, child) } } $else $if field.typ is Type { b.push_type(mut edges, node.$(field.name)) } $else $if field.typ is []Type { for child in node.$(field.name) { b.push_type(mut edges, child) } } $else $if field.typ is Attribute { b.push_attribute(mut edges, node.$(field.name)) } $else $if field.typ is []Attribute { for child in node.$(field.name) { b.push_attribute(mut edges, child) } } $else $if field.typ is FieldInit { b.push_field_init(mut edges, node.$(field.name)) } $else $if field.typ is []FieldInit { for child in node.$(field.name) { b.push_field_init(mut edges, child) } } $else $if field.typ is FieldDecl { b.push_field_decl(mut edges, node.$(field.name)) } $else $if field.typ is []FieldDecl { for child in node.$(field.name) { b.push_field_decl(mut edges, child) } } $else $if field.typ is Parameter { b.push_parameter(mut edges, node.$(field.name)) } $else $if field.typ is []Parameter { for child in node.$(field.name) { b.push_parameter(mut edges, child) } } $else $if field.typ is MatchBranch { b.push_match_branch(mut edges, node.$(field.name)) } $else $if field.typ is []MatchBranch { for child in node.$(field.name) { b.push_match_branch(mut edges, child) } } $else $if field.typ is StringInter { b.push_string_inter(mut edges, node.$(field.name)) } $else $if field.typ is []StringInter { for child in node.$(field.name) { b.push_string_inter(mut edges, child) } } $else $if field.typ is Ident { b.push_expr(mut edges, Expr(node.$(field.name))) } $else $if field.typ is []Ident { for child in node.$(field.name) { b.push_expr(mut edges, Expr(child)) } } $else $if field.typ is ImportStmt { b.push_stmt(mut edges, Stmt(node.$(field.name))) } $else $if field.typ is []ImportStmt { for child in node.$(field.name) { b.push_stmt(mut edges, Stmt(child)) } } $else $if field.typ is AnonStructType { b.push_type(mut edges, Type(node.$(field.name))) } $else $if field.typ is ArrayFixedType { b.push_type(mut edges, Type(node.$(field.name))) } $else $if field.typ is ArrayType { b.push_type(mut edges, Type(node.$(field.name))) } $else $if field.typ is ChannelType { b.push_type(mut edges, Type(node.$(field.name))) } $else $if field.typ is FnType { b.push_type(mut edges, Type(node.$(field.name))) } $else $if field.typ is GenericType { b.push_type(mut edges, Type(node.$(field.name))) } $else $if field.typ is MapType { b.push_type(mut edges, Type(node.$(field.name))) } $else $if field.typ is NilType { b.push_type(mut edges, Type(node.$(field.name))) } $else $if field.typ is NoneType { b.push_type(mut edges, Type(node.$(field.name))) } $else $if field.typ is OptionType { b.push_type(mut edges, Type(node.$(field.name))) } $else $if field.typ is ResultType { b.push_type(mut edges, Type(node.$(field.name))) } $else $if field.typ is ThreadType { b.push_type(mut edges, Type(node.$(field.name))) } $else $if field.typ is TupleType { b.push_type(mut edges, Type(node.$(field.name))) } } } fn (mut b FlatBuilder) add_file(file File) FlatNodeId { mut edges := []FlatEdge{} for attr in file.attributes { b.push_attribute(mut edges, attr) } for imp in file.imports { b.push_stmt(mut edges, Stmt(imp)) } for stmt in file.stmts { b.push_stmt(mut edges, stmt) } return b.add_node(.file, file.name, edges) } fn (mut b FlatBuilder) add_stmt(stmt Stmt) FlatNodeId { if stmt is []Attribute { mut edges := []FlatEdge{} for attr in stmt { b.push_attribute(mut edges, attr) } return b.add_node(.stmt_attributes, '', edges) } mut edges := []FlatEdge{} mut data := '' mut kind := FlatNodeKind.stmt_empty match stmt { AsmStmt { kind = .stmt_asm data = stmt.arch } AssertStmt { kind = .stmt_assert b.collect_children(stmt, mut edges) } AssignStmt { kind = .stmt_assign data = stmt.op.str() b.collect_children(stmt, mut edges) } BlockStmt { kind = .stmt_block b.collect_children(stmt, mut edges) } ComptimeStmt { kind = .stmt_comptime b.collect_children(stmt, mut edges) } ConstDecl { kind = .stmt_const_decl if stmt.is_public { data = 'pub' } b.collect_children(stmt, mut edges) } DeferStmt { kind = .stmt_defer data = stmt.mode.str() b.collect_children(stmt, mut edges) } Directive { kind = .stmt_directive data = '${stmt.name}:${stmt.value}' } EmptyStmt { kind = .stmt_empty } EnumDecl { kind = .stmt_enum_decl data = stmt.name if stmt.is_public { data = 'pub ${stmt.name}' } b.collect_children(stmt, mut edges) } ExprStmt { kind = .stmt_expr b.collect_children(stmt, mut edges) } FlowControlStmt { kind = .stmt_flow_control data = '${stmt.op.str()}:${stmt.label}' } FnDecl { kind = .stmt_fn_decl data = stmt.name if stmt.is_public { data = 'pub ${data}' } if stmt.is_method { data = 'method ${data}' } if stmt.is_static { data = 'static ${data}' } b.collect_children(stmt, mut edges) } ForInStmt { kind = .stmt_for_in b.collect_children(stmt, mut edges) } ForStmt { kind = .stmt_for b.collect_children(stmt, mut edges) } GlobalDecl { kind = .stmt_global_decl b.collect_children(stmt, mut edges) } ImportStmt { kind = .stmt_import data = if stmt.alias.len > 0 { '${stmt.name}:${stmt.alias}' } else { stmt.name } b.collect_children(stmt, mut edges) } InterfaceDecl { kind = .stmt_interface_decl data = stmt.name if stmt.is_public { data = 'pub ${stmt.name}' } b.collect_children(stmt, mut edges) } LabelStmt { kind = .stmt_label data = stmt.name b.collect_children(stmt, mut edges) } ModuleStmt { kind = .stmt_module data = stmt.name } ReturnStmt { kind = .stmt_return b.collect_children(stmt, mut edges) } StructDecl { kind = .stmt_struct_decl data = stmt.name if stmt.is_public { data = 'pub ${data}' } if stmt.is_union { data = 'union ${data}' } b.collect_children(stmt, mut edges) } TypeDecl { kind = .stmt_type_decl data = stmt.name if stmt.is_public { data = 'pub ${data}' } b.collect_children(stmt, mut edges) } []Attribute {} } return b.add_node(kind, data, edges) } fn (mut b FlatBuilder) add_expr(expr Expr) FlatNodeId { match expr { Type { return b.add_type(expr) } FieldInit { return b.add_field_init(expr) } else {} } mut edges := []FlatEdge{} mut data := '' mut kind := FlatNodeKind.expr_empty match expr { ArrayInitExpr { kind = .expr_array_init b.collect_children(expr, mut edges) } AsCastExpr { kind = .expr_as_cast b.collect_children(expr, mut edges) } AssocExpr { kind = .expr_assoc b.collect_children(expr, mut edges) } BasicLiteral { kind = .expr_basic_literal data = '${expr.kind.str()}:${expr.value}' } CallExpr { kind = .expr_call b.collect_children(expr, mut edges) } CallOrCastExpr { kind = .expr_call_or_cast b.collect_children(expr, mut edges) } CastExpr { kind = .expr_cast b.collect_children(expr, mut edges) } ComptimeExpr { kind = .expr_comptime b.collect_children(expr, mut edges) } EmptyExpr { kind = .expr_empty } FnLiteral { kind = .expr_fn_literal b.collect_children(expr, mut edges) } GenericArgOrIndexExpr { kind = .expr_generic_arg_or_index b.collect_children(expr, mut edges) } GenericArgs { kind = .expr_generic_args b.collect_children(expr, mut edges) } Ident { kind = .expr_ident data = expr.name } IfExpr { kind = .expr_if b.collect_children(expr, mut edges) } IfGuardExpr { kind = .expr_if_guard b.collect_children(expr, mut edges) } IndexExpr { kind = .expr_index if expr.is_gated { data = 'gated' } b.collect_children(expr, mut edges) } InfixExpr { kind = .expr_infix data = expr.op.str() b.collect_children(expr, mut edges) } InitExpr { kind = .expr_init b.collect_children(expr, mut edges) } Keyword { kind = .expr_keyword data = expr.tok.str() } KeywordOperator { kind = .expr_keyword_operator data = expr.op.str() b.collect_children(expr, mut edges) } LambdaExpr { kind = .expr_lambda b.collect_children(expr, mut edges) } LockExpr { kind = .expr_lock b.collect_children(expr, mut edges) } MapInitExpr { kind = .expr_map_init b.collect_children(expr, mut edges) } MatchExpr { kind = .expr_match b.collect_children(expr, mut edges) } ModifierExpr { kind = .expr_modifier data = expr.kind.str() b.collect_children(expr, mut edges) } OrExpr { kind = .expr_or b.collect_children(expr, mut edges) } ParenExpr { kind = .expr_paren b.collect_children(expr, mut edges) } PostfixExpr { kind = .expr_postfix data = expr.op.str() b.collect_children(expr, mut edges) } PrefixExpr { kind = .expr_prefix data = expr.op.str() b.collect_children(expr, mut edges) } RangeExpr { kind = .expr_range data = expr.op.str() b.collect_children(expr, mut edges) } SelectExpr { kind = .expr_select b.collect_children(expr, mut edges) } SelectorExpr { kind = .expr_selector b.collect_children(expr, mut edges) } SqlExpr { kind = .expr_sql b.collect_children(expr, mut edges) } StringInterLiteral { kind = .expr_string_inter if expr.values.len > 0 { data = '${expr.values[0]}:${expr.values.len}' } b.collect_children(expr, mut edges) } StringLiteral { kind = .expr_string data = '${expr.kind.str()}:${expr.value}' } Tuple { kind = .expr_tuple b.collect_children(expr, mut edges) } Type {} UnsafeExpr { kind = .expr_unsafe b.collect_children(expr, mut edges) } FieldInit {} } return b.add_node(kind, data, edges) } fn (mut b FlatBuilder) add_type(typ Type) FlatNodeId { mut edges := []FlatEdge{} mut data := '' mut kind := FlatNodeKind.typ_nil match typ { AnonStructType { kind = .typ_anon_struct b.collect_children(typ, mut edges) } ArrayFixedType { kind = .typ_array_fixed b.collect_children(typ, mut edges) } ArrayType { kind = .typ_array b.collect_children(typ, mut edges) } ChannelType { kind = .typ_channel b.collect_children(typ, mut edges) } FnType { kind = .typ_fn b.collect_children(typ, mut edges) } GenericType { kind = .typ_generic data = typ.name.name() b.collect_children(typ, mut edges) } MapType { kind = .typ_map b.collect_children(typ, mut edges) } NilType { kind = .typ_nil } NoneType { kind = .typ_none } OptionType { kind = .typ_option b.collect_children(typ, mut edges) } ResultType { kind = .typ_result b.collect_children(typ, mut edges) } ThreadType { kind = .typ_thread b.collect_children(typ, mut edges) } TupleType { kind = .typ_tuple b.collect_children(typ, mut edges) } } return b.add_node(kind, data, edges) } fn (mut b FlatBuilder) add_attribute(attr Attribute) FlatNodeId { mut edges := []FlatEdge{} b.collect_children(attr, mut edges) return b.add_node(.aux_attribute, attr.name, edges) } fn (mut b FlatBuilder) add_field_init(field FieldInit) FlatNodeId { mut edges := []FlatEdge{} b.collect_children(field, mut edges) return b.add_node(.aux_field_init, field.name, edges) } fn (mut b FlatBuilder) add_field_decl(field FieldDecl) FlatNodeId { mut edges := []FlatEdge{} b.collect_children(field, mut edges) return b.add_node(.aux_field_decl, field.name, edges) } fn (mut b FlatBuilder) add_parameter(param Parameter) FlatNodeId { mut edges := []FlatEdge{} b.collect_children(param, mut edges) name := if param.is_mut { 'mut ${param.name}' } else { param.name } return b.add_node(.aux_parameter, name, edges) } fn (mut b FlatBuilder) add_match_branch(branch MatchBranch) FlatNodeId { mut edges := []FlatEdge{} b.collect_children(branch, mut edges) return b.add_node(.aux_match_branch, '', edges) } fn (mut b FlatBuilder) add_string_inter(inter StringInter) FlatNodeId { mut edges := []FlatEdge{} b.collect_children(inter, mut edges) data := '${inter.format.str()}:${inter.width}:${inter.precision}' return b.add_node(.aux_string_inter, data, edges) } struct LegacyAstWalker { mut: stats LegacyAstStats } fn (mut w LegacyAstWalker) walk_files(files []File) { w.stats.files = files.len w.stats.node_bytes += u64(files.len) * u64(sizeof(File)) w.add_array_storage(sizeof(File), files.len) for file in files { w.walk_file(file) } w.stats.bytes_estimate = w.stats.node_bytes + w.stats.array_bytes + w.stats.string_bytes } fn (mut w LegacyAstWalker) add_array_storage(elem_size u32, len int) { if len <= 0 || elem_size == 0 { return } w.stats.array_bytes += u64(elem_size) * u64(len) } fn (mut w LegacyAstWalker) add_string(s string) { w.stats.string_entries++ w.stats.string_bytes += u64(s.len) } fn (mut w LegacyAstWalker) walk_file(file File) { w.scan_dynamic(file) } fn (mut w LegacyAstWalker) walk_stmt(stmt Stmt) { if stmt is []Attribute { w.add_array_storage(sizeof(Attribute), stmt.len) for attr in stmt { w.walk_attribute(attr) } return } w.stats.stmt_nodes++ w.stats.node_bytes += u64(sizeof(Stmt)) match stmt { AssignStmt { w.scan_dynamic(stmt) } AssertStmt { w.scan_dynamic(stmt) } AsmStmt { w.scan_dynamic(stmt) } BlockStmt { w.scan_dynamic(stmt) } ComptimeStmt { w.scan_dynamic(stmt) } ConstDecl { w.scan_dynamic(stmt) } DeferStmt { w.scan_dynamic(stmt) } Directive { w.scan_dynamic(stmt) } EmptyStmt {} EnumDecl { w.scan_dynamic(stmt) } ExprStmt { w.scan_dynamic(stmt) } FlowControlStmt { w.scan_dynamic(stmt) } FnDecl { w.scan_dynamic(stmt) } ForInStmt { w.scan_dynamic(stmt) } ForStmt { w.scan_dynamic(stmt) } GlobalDecl { w.scan_dynamic(stmt) } ImportStmt { w.scan_dynamic(stmt) } InterfaceDecl { w.scan_dynamic(stmt) } LabelStmt { w.scan_dynamic(stmt) } ModuleStmt { w.scan_dynamic(stmt) } ReturnStmt { w.scan_dynamic(stmt) } StructDecl { w.scan_dynamic(stmt) } TypeDecl { w.scan_dynamic(stmt) } []Attribute {} } } fn (mut w LegacyAstWalker) walk_expr(expr Expr) { match expr { Type { w.walk_type(expr) return } FieldInit { w.walk_field_init(expr) return } else {} } w.stats.expr_nodes++ w.stats.node_bytes += u64(sizeof(Expr)) match expr { ArrayInitExpr { w.scan_dynamic(expr) } AsCastExpr { w.scan_dynamic(expr) } AssocExpr { w.scan_dynamic(expr) } BasicLiteral { w.scan_dynamic(expr) } CallExpr { w.scan_dynamic(expr) } CallOrCastExpr { w.scan_dynamic(expr) } CastExpr { w.scan_dynamic(expr) } ComptimeExpr { w.scan_dynamic(expr) } EmptyExpr {} FnLiteral { w.scan_dynamic(expr) } GenericArgOrIndexExpr { w.scan_dynamic(expr) } GenericArgs { w.scan_dynamic(expr) } Ident { w.scan_dynamic(expr) } IfExpr { w.scan_dynamic(expr) } IfGuardExpr { w.scan_dynamic(expr) } IndexExpr { w.scan_dynamic(expr) } InfixExpr { w.scan_dynamic(expr) } InitExpr { w.scan_dynamic(expr) } Keyword {} KeywordOperator { w.scan_dynamic(expr) } LambdaExpr { w.scan_dynamic(expr) } LockExpr { w.scan_dynamic(expr) } MapInitExpr { w.scan_dynamic(expr) } MatchExpr { w.scan_dynamic(expr) } ModifierExpr { w.scan_dynamic(expr) } OrExpr { w.scan_dynamic(expr) } ParenExpr { w.scan_dynamic(expr) } PostfixExpr { w.scan_dynamic(expr) } PrefixExpr { w.scan_dynamic(expr) } RangeExpr { w.scan_dynamic(expr) } SelectExpr { w.scan_dynamic(expr) } SelectorExpr { w.scan_dynamic(expr) } SqlExpr { w.scan_dynamic(expr) } StringInterLiteral { w.scan_dynamic(expr) } StringLiteral { w.scan_dynamic(expr) } Tuple { w.scan_dynamic(expr) } Type {} UnsafeExpr { w.scan_dynamic(expr) } FieldInit {} } } fn (mut w LegacyAstWalker) walk_type(typ Type) { w.stats.type_nodes++ w.stats.node_bytes += u64(sizeof(Type)) match typ { AnonStructType { w.scan_dynamic(typ) } ArrayFixedType { w.scan_dynamic(typ) } ArrayType { w.scan_dynamic(typ) } ChannelType { w.scan_dynamic(typ) } FnType { w.scan_dynamic(typ) } GenericType { w.scan_dynamic(typ) } MapType { w.scan_dynamic(typ) } NilType {} NoneType {} OptionType { w.scan_dynamic(typ) } ResultType { w.scan_dynamic(typ) } ThreadType { w.scan_dynamic(typ) } TupleType { w.scan_dynamic(typ) } } } fn (mut w LegacyAstWalker) walk_attribute(attr Attribute) { w.stats.aux_nodes++ w.stats.node_bytes += u64(sizeof(Attribute)) w.scan_dynamic(attr) } fn (mut w LegacyAstWalker) walk_field_init(field FieldInit) { w.stats.aux_nodes++ w.stats.node_bytes += u64(sizeof(FieldInit)) w.scan_dynamic(field) } fn (mut w LegacyAstWalker) walk_field_decl(field FieldDecl) { w.stats.aux_nodes++ w.stats.node_bytes += u64(sizeof(FieldDecl)) w.scan_dynamic(field) } fn (mut w LegacyAstWalker) walk_parameter(param Parameter) { w.stats.aux_nodes++ w.stats.node_bytes += u64(sizeof(Parameter)) w.scan_dynamic(param) } fn (mut w LegacyAstWalker) walk_match_branch(branch MatchBranch) { w.stats.aux_nodes++ w.stats.node_bytes += u64(sizeof(MatchBranch)) w.scan_dynamic(branch) } fn (mut w LegacyAstWalker) walk_string_inter(inter StringInter) { w.stats.aux_nodes++ w.stats.node_bytes += u64(sizeof(StringInter)) w.scan_dynamic(inter) } fn (mut w LegacyAstWalker) scan_dynamic[T](node T) { $for field in T.fields { $if field.typ is string { w.add_string(node.$(field.name)) } $else $if field.typ is []string { arr := node.$(field.name) w.add_array_storage(sizeof(string), arr.len) for v in arr { w.add_string(v) } } $else $if field.typ is Expr { w.walk_expr(node.$(field.name)) } $else $if field.typ is []Expr { arr := node.$(field.name) w.add_array_storage(sizeof(Expr), arr.len) for v in arr { w.walk_expr(v) } } $else $if field.typ is Stmt { w.walk_stmt(node.$(field.name)) } $else $if field.typ is []Stmt { arr := node.$(field.name) w.add_array_storage(sizeof(Stmt), arr.len) for v in arr { w.walk_stmt(v) } } $else $if field.typ is Type { w.walk_type(node.$(field.name)) } $else $if field.typ is []Type { arr := node.$(field.name) w.add_array_storage(sizeof(Type), arr.len) for v in arr { w.walk_type(v) } } $else $if field.typ is Attribute { w.walk_attribute(node.$(field.name)) } $else $if field.typ is []Attribute { arr := node.$(field.name) w.add_array_storage(sizeof(Attribute), arr.len) for v in arr { w.walk_attribute(v) } } $else $if field.typ is FieldInit { w.walk_field_init(node.$(field.name)) } $else $if field.typ is []FieldInit { arr := node.$(field.name) w.add_array_storage(sizeof(FieldInit), arr.len) for v in arr { w.walk_field_init(v) } } $else $if field.typ is FieldDecl { w.walk_field_decl(node.$(field.name)) } $else $if field.typ is []FieldDecl { arr := node.$(field.name) w.add_array_storage(sizeof(FieldDecl), arr.len) for v in arr { w.walk_field_decl(v) } } $else $if field.typ is Parameter { w.walk_parameter(node.$(field.name)) } $else $if field.typ is []Parameter { arr := node.$(field.name) w.add_array_storage(sizeof(Parameter), arr.len) for v in arr { w.walk_parameter(v) } } $else $if field.typ is MatchBranch { w.walk_match_branch(node.$(field.name)) } $else $if field.typ is []MatchBranch { arr := node.$(field.name) w.add_array_storage(sizeof(MatchBranch), arr.len) for v in arr { w.walk_match_branch(v) } } $else $if field.typ is StringInter { w.walk_string_inter(node.$(field.name)) } $else $if field.typ is []StringInter { arr := node.$(field.name) w.add_array_storage(sizeof(StringInter), arr.len) for v in arr { w.walk_string_inter(v) } } $else $if field.typ is Ident { w.walk_expr(Expr(node.$(field.name))) } $else $if field.typ is []Ident { arr := node.$(field.name) w.add_array_storage(sizeof(Ident), arr.len) for v in arr { w.walk_expr(Expr(v)) } } $else $if field.typ is ImportStmt { w.walk_stmt(Stmt(node.$(field.name))) } $else $if field.typ is []ImportStmt { arr := node.$(field.name) w.add_array_storage(sizeof(ImportStmt), arr.len) for v in arr { w.walk_stmt(Stmt(v)) } } } }