Walk.zig (40114B) - Raw
1 //! Find and annotate identifiers with links to their declarations. 2 3 const Walk = @This(); 4 const std = @import("std"); 5 const Ast = std.zig.Ast; 6 const assert = std.debug.assert; 7 const log = std.log; 8 const gpa = std.heap.wasm_allocator; 9 const Oom = error{OutOfMemory}; 10 11 pub const Decl = @import("Decl.zig"); 12 13 pub var files: std.StringArrayHashMapUnmanaged(File) = .empty; 14 pub var decls: std.ArrayListUnmanaged(Decl) = .empty; 15 pub var modules: std.StringArrayHashMapUnmanaged(File.Index) = .empty; 16 17 file: File.Index, 18 19 /// keep in sync with "CAT_" constants in main.js 20 pub const Category = union(enum(u8)) { 21 /// A struct type used only to group declarations. 22 namespace: Ast.Node.Index, 23 /// A container type (struct, union, enum, opaque). 24 container: Ast.Node.Index, 25 global_variable: Ast.Node.Index, 26 /// A function that has not been detected as returning a type. 27 function: Ast.Node.Index, 28 primitive: Ast.Node.Index, 29 error_set: Ast.Node.Index, 30 global_const: Ast.Node.Index, 31 alias: Decl.Index, 32 /// A primitive identifier that is also a type. 33 type, 34 /// Specifically it is the literal `type`. 35 type_type, 36 /// A function that returns a type. 37 type_function: Ast.Node.Index, 38 39 pub const Tag = @typeInfo(Category).@"union".tag_type.?; 40 }; 41 42 pub const File = struct { 43 ast: Ast, 44 /// Maps identifiers to the declarations they point to. 45 ident_decls: std.AutoArrayHashMapUnmanaged(Ast.TokenIndex, Ast.Node.Index) = .empty, 46 /// Maps field access identifiers to the containing field access node. 47 token_parents: std.AutoArrayHashMapUnmanaged(Ast.TokenIndex, Ast.Node.Index) = .empty, 48 /// Maps declarations to their global index. 49 node_decls: std.AutoArrayHashMapUnmanaged(Ast.Node.Index, Decl.Index) = .empty, 50 /// Maps function declarations to doctests. 51 doctests: std.AutoArrayHashMapUnmanaged(Ast.Node.Index, Ast.Node.Index) = .empty, 52 /// root node => its namespace scope 53 /// struct/union/enum/opaque decl node => its namespace scope 54 /// local var decl node => its local variable scope 55 scopes: std.AutoArrayHashMapUnmanaged(Ast.Node.Index, *Scope) = .empty, 56 57 pub fn lookup_token(file: *File, token: Ast.TokenIndex) Decl.Index { 58 const decl_node = file.ident_decls.get(token) orelse return .none; 59 return file.node_decls.get(decl_node) orelse return .none; 60 } 61 62 pub const Index = enum(u32) { 63 _, 64 65 fn add_decl(i: Index, node: Ast.Node.Index, parent_decl: Decl.Index) Oom!Decl.Index { 66 try decls.append(gpa, .{ 67 .ast_node = node, 68 .file = i, 69 .parent = parent_decl, 70 }); 71 const decl_index: Decl.Index = @enumFromInt(decls.items.len - 1); 72 try i.get().node_decls.put(gpa, node, decl_index); 73 return decl_index; 74 } 75 76 pub fn get(i: File.Index) *File { 77 return &files.values()[@intFromEnum(i)]; 78 } 79 80 pub fn get_ast(i: File.Index) *Ast { 81 return &i.get().ast; 82 } 83 84 pub fn path(i: File.Index) []const u8 { 85 return files.keys()[@intFromEnum(i)]; 86 } 87 88 pub fn findRootDecl(file_index: File.Index) Decl.Index { 89 return file_index.get().node_decls.values()[0]; 90 } 91 92 pub fn categorize_decl(file_index: File.Index, node: Ast.Node.Index) Category { 93 const ast = file_index.get_ast(); 94 switch (ast.nodeTag(node)) { 95 .root => { 96 for (ast.rootDecls()) |member| { 97 switch (ast.nodeTag(member)) { 98 .container_field_init, 99 .container_field_align, 100 .container_field, 101 => return .{ .container = node }, 102 else => {}, 103 } 104 } 105 return .{ .namespace = node }; 106 }, 107 108 .global_var_decl, 109 .local_var_decl, 110 .simple_var_decl, 111 .aligned_var_decl, 112 => { 113 const var_decl = ast.fullVarDecl(node).?; 114 if (ast.tokenTag(var_decl.ast.mut_token) == .keyword_var) 115 return .{ .global_variable = node }; 116 const init_node = var_decl.ast.init_node.unwrap() orelse 117 return .{ .global_const = node }; 118 119 return categorize_expr(file_index, init_node); 120 }, 121 122 .fn_proto, 123 .fn_proto_multi, 124 .fn_proto_one, 125 .fn_proto_simple, 126 .fn_decl, 127 => { 128 var buf: [1]Ast.Node.Index = undefined; 129 const full = ast.fullFnProto(&buf, node).?; 130 return categorize_func(file_index, node, full); 131 }, 132 133 else => unreachable, 134 } 135 } 136 137 pub fn categorize_func( 138 file_index: File.Index, 139 node: Ast.Node.Index, 140 full: Ast.full.FnProto, 141 ) Category { 142 return switch (categorize_expr(file_index, full.ast.return_type.unwrap().?)) { 143 .namespace, .container, .error_set, .type_type => .{ .type_function = node }, 144 else => .{ .function = node }, 145 }; 146 } 147 148 pub fn categorize_expr_deep(file_index: File.Index, node: Ast.Node.Index) Category { 149 return switch (categorize_expr(file_index, node)) { 150 .alias => |aliasee| aliasee.get().categorize(), 151 else => |result| result, 152 }; 153 } 154 155 pub fn categorize_expr(file_index: File.Index, node: Ast.Node.Index) Category { 156 const file = file_index.get(); 157 const ast = file_index.get_ast(); 158 //log.debug("categorize_expr tag {s}", .{@tagName(ast.nodeTag(node))}); 159 return switch (ast.nodeTag(node)) { 160 .container_decl, 161 .container_decl_trailing, 162 .container_decl_arg, 163 .container_decl_arg_trailing, 164 .container_decl_two, 165 .container_decl_two_trailing, 166 .tagged_union, 167 .tagged_union_trailing, 168 .tagged_union_enum_tag, 169 .tagged_union_enum_tag_trailing, 170 .tagged_union_two, 171 .tagged_union_two_trailing, 172 => { 173 var buf: [2]Ast.Node.Index = undefined; 174 const container_decl = ast.fullContainerDecl(&buf, node).?; 175 if (ast.tokenTag(container_decl.ast.main_token) != .keyword_struct) { 176 return .{ .container = node }; 177 } 178 for (container_decl.ast.members) |member| { 179 switch (ast.nodeTag(member)) { 180 .container_field_init, 181 .container_field_align, 182 .container_field, 183 => return .{ .container = node }, 184 else => {}, 185 } 186 } 187 return .{ .namespace = node }; 188 }, 189 190 .error_set_decl, 191 .merge_error_sets, 192 => .{ .error_set = node }, 193 194 .identifier => { 195 const name_token = ast.nodeMainToken(node); 196 const ident_name = ast.tokenSlice(name_token); 197 if (std.mem.eql(u8, ident_name, "type")) 198 return .type_type; 199 200 if (isPrimitiveNonType(ident_name)) 201 return .{ .primitive = node }; 202 203 if (std.zig.primitives.isPrimitive(ident_name)) 204 return .type; 205 206 if (file.ident_decls.get(name_token)) |decl_node| { 207 const decl_index = file.node_decls.get(decl_node) orelse .none; 208 if (decl_index != .none) return .{ .alias = decl_index }; 209 return categorize_decl(file_index, decl_node); 210 } 211 212 return .{ .global_const = node }; 213 }, 214 215 .field_access => { 216 const object_node, const field_ident = ast.nodeData(node).node_and_token; 217 const field_name = ast.tokenSlice(field_ident); 218 219 switch (categorize_expr(file_index, object_node)) { 220 .alias => |aliasee| if (aliasee.get().get_child(field_name)) |decl_index| { 221 return .{ .alias = decl_index }; 222 }, 223 else => {}, 224 } 225 226 return .{ .global_const = node }; 227 }, 228 229 .builtin_call_two, 230 .builtin_call_two_comma, 231 .builtin_call, 232 .builtin_call_comma, 233 => { 234 var buf: [2]Ast.Node.Index = undefined; 235 const params = ast.builtinCallParams(&buf, node).?; 236 return categorize_builtin_call(file_index, node, params); 237 }, 238 239 .call_one, 240 .call_one_comma, 241 .call, 242 .call_comma, 243 => { 244 var buf: [1]Ast.Node.Index = undefined; 245 return categorize_call(file_index, node, ast.fullCall(&buf, node).?); 246 }, 247 248 .if_simple, 249 .@"if", 250 => { 251 const if_full = ast.fullIf(node).?; 252 if (if_full.ast.else_expr.unwrap()) |else_expr| { 253 const then_cat = categorize_expr_deep(file_index, if_full.ast.then_expr); 254 const else_cat = categorize_expr_deep(file_index, else_expr); 255 if (then_cat == .type_type and else_cat == .type_type) { 256 return .type_type; 257 } else if (then_cat == .error_set and else_cat == .error_set) { 258 return .{ .error_set = node }; 259 } else if (then_cat == .type or else_cat == .type or 260 then_cat == .namespace or else_cat == .namespace or 261 then_cat == .container or else_cat == .container or 262 then_cat == .error_set or else_cat == .error_set or 263 then_cat == .type_function or else_cat == .type_function) 264 { 265 return .type; 266 } 267 } 268 return .{ .global_const = node }; 269 }, 270 271 .@"switch", .switch_comma => return categorize_switch(file_index, node), 272 273 .optional_type, 274 .array_type, 275 .array_type_sentinel, 276 .ptr_type_aligned, 277 .ptr_type_sentinel, 278 .ptr_type, 279 .ptr_type_bit_range, 280 .anyframe_type, 281 => .type, 282 283 else => .{ .global_const = node }, 284 }; 285 } 286 287 fn categorize_call( 288 file_index: File.Index, 289 node: Ast.Node.Index, 290 call: Ast.full.Call, 291 ) Category { 292 return switch (categorize_expr(file_index, call.ast.fn_expr)) { 293 .type_function => .type, 294 .alias => |aliasee| categorize_decl_as_callee(aliasee, node), 295 else => .{ .global_const = node }, 296 }; 297 } 298 299 fn categorize_decl_as_callee(decl_index: Decl.Index, call_node: Ast.Node.Index) Category { 300 return switch (decl_index.get().categorize()) { 301 .type_function => .type, 302 .alias => |aliasee| categorize_decl_as_callee(aliasee, call_node), 303 else => .{ .global_const = call_node }, 304 }; 305 } 306 307 fn categorize_builtin_call( 308 file_index: File.Index, 309 node: Ast.Node.Index, 310 params: []const Ast.Node.Index, 311 ) Category { 312 const ast = file_index.get_ast(); 313 const builtin_token = ast.nodeMainToken(node); 314 const builtin_name = ast.tokenSlice(builtin_token); 315 if (std.mem.eql(u8, builtin_name, "@import")) { 316 const str_lit_token = ast.nodeMainToken(params[0]); 317 const str_bytes = ast.tokenSlice(str_lit_token); 318 const file_path = std.zig.string_literal.parseAlloc(gpa, str_bytes) catch @panic("OOM"); 319 defer gpa.free(file_path); 320 if (modules.get(file_path)) |imported_file_index| { 321 return .{ .alias = File.Index.findRootDecl(imported_file_index) }; 322 } 323 const base_path = file_index.path(); 324 const resolved_path = std.fs.path.resolvePosix(gpa, &.{ 325 base_path, "..", file_path, 326 }) catch @panic("OOM"); 327 defer gpa.free(resolved_path); 328 log.debug("from '{s}' @import '{s}' resolved='{s}'", .{ 329 base_path, file_path, resolved_path, 330 }); 331 if (files.getIndex(resolved_path)) |imported_file_index| { 332 return .{ .alias = File.Index.findRootDecl(@enumFromInt(imported_file_index)) }; 333 } else { 334 log.warn("import target '{s}' did not resolve to any file", .{resolved_path}); 335 } 336 } else if (std.mem.eql(u8, builtin_name, "@This")) { 337 if (file_index.get().node_decls.get(node)) |decl_index| { 338 return .{ .alias = decl_index }; 339 } else { 340 log.warn("@This() is missing link to Decl.Index", .{}); 341 } 342 } 343 344 return .{ .global_const = node }; 345 } 346 347 fn categorize_switch(file_index: File.Index, node: Ast.Node.Index) Category { 348 const ast = file_index.get_ast(); 349 const full = ast.fullSwitch(node).?; 350 var all_type_type = true; 351 var all_error_set = true; 352 var any_type = false; 353 if (full.ast.cases.len == 0) return .{ .global_const = node }; 354 for (full.ast.cases) |case_node| { 355 const case = ast.fullSwitchCase(case_node).?; 356 switch (categorize_expr_deep(file_index, case.ast.target_expr)) { 357 .type_type => { 358 any_type = true; 359 all_error_set = false; 360 }, 361 .error_set => { 362 any_type = true; 363 all_type_type = false; 364 }, 365 .type, .namespace, .container, .type_function => { 366 any_type = true; 367 all_error_set = false; 368 all_type_type = false; 369 }, 370 else => { 371 all_error_set = false; 372 all_type_type = false; 373 }, 374 } 375 } 376 if (all_type_type) return .type_type; 377 if (all_error_set) return .{ .error_set = node }; 378 if (any_type) return .type; 379 return .{ .global_const = node }; 380 } 381 }; 382 }; 383 384 pub const ModuleIndex = enum(u32) { 385 _, 386 }; 387 388 pub fn add_file(file_name: []const u8, bytes: []u8) !File.Index { 389 const ast = try parse(file_name, bytes); 390 assert(ast.errors.len == 0); 391 const file_index: File.Index = @enumFromInt(files.entries.len); 392 try files.put(gpa, file_name, .{ .ast = ast }); 393 394 var w: Walk = .{ 395 .file = file_index, 396 }; 397 const scope = try gpa.create(Scope); 398 scope.* = .{ .tag = .top }; 399 400 const decl_index = try file_index.add_decl(.root, .none); 401 try struct_decl(&w, scope, decl_index, .root, ast.containerDeclRoot()); 402 403 const file = file_index.get(); 404 shrinkToFit(&file.ident_decls); 405 shrinkToFit(&file.token_parents); 406 shrinkToFit(&file.node_decls); 407 shrinkToFit(&file.doctests); 408 shrinkToFit(&file.scopes); 409 410 return file_index; 411 } 412 413 /// Parses a file and returns its `Ast`. If the file cannot be parsed, returns 414 /// the `Ast` of an empty file, so that the rest of the Autodoc logic does not 415 /// need to handle parse errors. 416 fn parse(file_name: []const u8, source: []u8) Oom!Ast { 417 // Require every source file to end with a newline so that Zig's tokenizer 418 // can continue to require null termination and Autodoc implementation can 419 // avoid copying source bytes from the decompressed tar file buffer. 420 const adjusted_source: [:0]const u8 = s: { 421 if (source.len == 0) 422 break :s ""; 423 if (source[source.len - 1] != '\n') { 424 log.err("{s}: expected newline at end of file", .{file_name}); 425 break :s ""; 426 } 427 source[source.len - 1] = 0; 428 break :s source[0 .. source.len - 1 :0]; 429 }; 430 431 var ast = try Ast.parse(gpa, adjusted_source, .zig); 432 if (ast.errors.len > 0) { 433 defer ast.deinit(gpa); 434 435 const token_offsets = ast.tokens.items(.start); 436 var rendered_err: std.Io.Writer.Allocating = .init(gpa); 437 defer rendered_err.deinit(); 438 for (ast.errors) |err| { 439 const err_offset = token_offsets[err.token] + ast.errorOffset(err); 440 const err_loc = std.zig.findLineColumn(ast.source, err_offset); 441 rendered_err.clearRetainingCapacity(); 442 ast.renderError(err, &rendered_err.writer) catch |e| switch (e) { 443 error.WriteFailed => return error.OutOfMemory, 444 }; 445 log.err("{s}:{d}:{d}: {s}", .{ 446 file_name, err_loc.line + 1, err_loc.column + 1, rendered_err.written(), 447 }); 448 } 449 return Ast.parse(gpa, "", .zig); 450 } 451 return ast; 452 } 453 454 pub const Scope = struct { 455 tag: Tag, 456 457 const Tag = enum { top, local, namespace }; 458 459 const Local = struct { 460 base: Scope = .{ .tag = .local }, 461 parent: *Scope, 462 var_node: Ast.Node.Index, 463 }; 464 465 const Namespace = struct { 466 base: Scope = .{ .tag = .namespace }, 467 parent: *Scope, 468 names: std.StringArrayHashMapUnmanaged(Ast.Node.Index) = .empty, 469 doctests: std.StringArrayHashMapUnmanaged(Ast.Node.Index) = .empty, 470 decl_index: Decl.Index, 471 }; 472 473 fn getNamespaceDecl(start_scope: *Scope) Decl.Index { 474 var it: *Scope = start_scope; 475 while (true) switch (it.tag) { 476 .top => unreachable, 477 .local => { 478 const local: *Local = @alignCast(@fieldParentPtr("base", it)); 479 it = local.parent; 480 }, 481 .namespace => { 482 const namespace: *Namespace = @alignCast(@fieldParentPtr("base", it)); 483 return namespace.decl_index; 484 }, 485 }; 486 } 487 488 pub fn get_child(scope: *Scope, name: []const u8) ?Ast.Node.Index { 489 switch (scope.tag) { 490 .top, .local => return null, 491 .namespace => { 492 const namespace: *Namespace = @alignCast(@fieldParentPtr("base", scope)); 493 return namespace.names.get(name); 494 }, 495 } 496 } 497 498 pub fn lookup(start_scope: *Scope, ast: *const Ast, name: []const u8) ?Ast.Node.Index { 499 var it: *Scope = start_scope; 500 while (true) switch (it.tag) { 501 .top => break, 502 .local => { 503 const local: *Local = @alignCast(@fieldParentPtr("base", it)); 504 const name_token = ast.nodeMainToken(local.var_node) + 1; 505 const ident_name = ast.tokenSlice(name_token); 506 if (std.mem.eql(u8, ident_name, name)) { 507 return local.var_node; 508 } 509 it = local.parent; 510 }, 511 .namespace => { 512 const namespace: *Namespace = @alignCast(@fieldParentPtr("base", it)); 513 if (namespace.names.get(name)) |node| { 514 return node; 515 } 516 it = namespace.parent; 517 }, 518 }; 519 return null; 520 } 521 }; 522 523 fn struct_decl( 524 w: *Walk, 525 scope: *Scope, 526 parent_decl: Decl.Index, 527 node: Ast.Node.Index, 528 container_decl: Ast.full.ContainerDecl, 529 ) Oom!void { 530 const ast = w.file.get_ast(); 531 532 const namespace = try gpa.create(Scope.Namespace); 533 namespace.* = .{ 534 .parent = scope, 535 .decl_index = parent_decl, 536 }; 537 try w.file.get().scopes.putNoClobber(gpa, node, &namespace.base); 538 try w.scanDecls(namespace, container_decl.ast.members); 539 540 for (container_decl.ast.members) |member| switch (ast.nodeTag(member)) { 541 .container_field_init, 542 .container_field_align, 543 .container_field, 544 => try w.container_field(&namespace.base, parent_decl, ast.fullContainerField(member).?), 545 546 .fn_proto, 547 .fn_proto_multi, 548 .fn_proto_one, 549 .fn_proto_simple, 550 .fn_decl, 551 => { 552 var buf: [1]Ast.Node.Index = undefined; 553 const full = ast.fullFnProto(&buf, member).?; 554 const fn_name_token = full.ast.fn_token + 1; 555 const fn_name = ast.tokenSlice(fn_name_token); 556 if (namespace.doctests.get(fn_name)) |doctest_node| { 557 try w.file.get().doctests.put(gpa, member, doctest_node); 558 } 559 const decl_index = try w.file.add_decl(member, parent_decl); 560 const body = if (ast.nodeTag(member) == .fn_decl) ast.nodeData(member).node_and_node[1].toOptional() else .none; 561 try w.fn_decl(&namespace.base, decl_index, body, full); 562 }, 563 564 .global_var_decl, 565 .local_var_decl, 566 .simple_var_decl, 567 .aligned_var_decl, 568 => { 569 const decl_index = try w.file.add_decl(member, parent_decl); 570 try w.global_var_decl(&namespace.base, decl_index, ast.fullVarDecl(member).?); 571 }, 572 573 .@"comptime", 574 => try w.expr(&namespace.base, parent_decl, ast.nodeData(member).node), 575 576 .test_decl => try w.expr(&namespace.base, parent_decl, ast.nodeData(member).opt_token_and_node[1]), 577 578 else => unreachable, 579 }; 580 } 581 582 fn comptime_decl( 583 w: *Walk, 584 scope: *Scope, 585 parent_decl: Decl.Index, 586 full: Ast.full.VarDecl, 587 ) Oom!void { 588 try w.expr(scope, parent_decl, full.ast.type_node); 589 try w.maybe_expr(scope, parent_decl, full.ast.align_node); 590 try w.maybe_expr(scope, parent_decl, full.ast.addrspace_node); 591 try w.maybe_expr(scope, parent_decl, full.ast.section_node); 592 try w.expr(scope, parent_decl, full.ast.init_node); 593 } 594 595 fn global_var_decl( 596 w: *Walk, 597 scope: *Scope, 598 parent_decl: Decl.Index, 599 full: Ast.full.VarDecl, 600 ) Oom!void { 601 try w.maybe_expr(scope, parent_decl, full.ast.type_node); 602 try w.maybe_expr(scope, parent_decl, full.ast.align_node); 603 try w.maybe_expr(scope, parent_decl, full.ast.addrspace_node); 604 try w.maybe_expr(scope, parent_decl, full.ast.section_node); 605 try w.maybe_expr(scope, parent_decl, full.ast.init_node); 606 } 607 608 fn container_field( 609 w: *Walk, 610 scope: *Scope, 611 parent_decl: Decl.Index, 612 full: Ast.full.ContainerField, 613 ) Oom!void { 614 try w.maybe_expr(scope, parent_decl, full.ast.type_expr); 615 try w.maybe_expr(scope, parent_decl, full.ast.align_expr); 616 try w.maybe_expr(scope, parent_decl, full.ast.value_expr); 617 } 618 619 fn fn_decl( 620 w: *Walk, 621 scope: *Scope, 622 parent_decl: Decl.Index, 623 body: Ast.Node.OptionalIndex, 624 full: Ast.full.FnProto, 625 ) Oom!void { 626 for (full.ast.params) |param| { 627 try expr(w, scope, parent_decl, param); 628 } 629 try expr(w, scope, parent_decl, full.ast.return_type.unwrap().?); 630 try maybe_expr(w, scope, parent_decl, full.ast.align_expr); 631 try maybe_expr(w, scope, parent_decl, full.ast.addrspace_expr); 632 try maybe_expr(w, scope, parent_decl, full.ast.section_expr); 633 try maybe_expr(w, scope, parent_decl, full.ast.callconv_expr); 634 try maybe_expr(w, scope, parent_decl, body); 635 } 636 637 fn maybe_expr(w: *Walk, scope: *Scope, parent_decl: Decl.Index, node: Ast.Node.OptionalIndex) Oom!void { 638 if (node.unwrap()) |n| return expr(w, scope, parent_decl, n); 639 } 640 641 fn expr(w: *Walk, scope: *Scope, parent_decl: Decl.Index, node: Ast.Node.Index) Oom!void { 642 const ast = w.file.get_ast(); 643 switch (ast.nodeTag(node)) { 644 .root => unreachable, // Top-level declaration. 645 .test_decl => unreachable, // Top-level declaration. 646 .container_field_init => unreachable, // Top-level declaration. 647 .container_field_align => unreachable, // Top-level declaration. 648 .container_field => unreachable, // Top-level declaration. 649 .fn_decl => unreachable, // Top-level declaration. 650 651 .global_var_decl => unreachable, // Handled in `block`. 652 .local_var_decl => unreachable, // Handled in `block`. 653 .simple_var_decl => unreachable, // Handled in `block`. 654 .aligned_var_decl => unreachable, // Handled in `block`. 655 .@"defer" => unreachable, // Handled in `block`. 656 .@"errdefer" => unreachable, // Handled in `block`. 657 658 .switch_case => unreachable, // Handled in `switchExpr`. 659 .switch_case_inline => unreachable, // Handled in `switchExpr`. 660 .switch_case_one => unreachable, // Handled in `switchExpr`. 661 .switch_case_inline_one => unreachable, // Handled in `switchExpr`. 662 663 .asm_output => unreachable, // Handled in `asmExpr`. 664 .asm_input => unreachable, // Handled in `asmExpr`. 665 666 .for_range => unreachable, // Handled in `forExpr`. 667 668 .assign, 669 .assign_shl, 670 .assign_shl_sat, 671 .assign_shr, 672 .assign_bit_and, 673 .assign_bit_or, 674 .assign_bit_xor, 675 .assign_div, 676 .assign_sub, 677 .assign_sub_wrap, 678 .assign_sub_sat, 679 .assign_mod, 680 .assign_add, 681 .assign_add_wrap, 682 .assign_add_sat, 683 .assign_mul, 684 .assign_mul_wrap, 685 .assign_mul_sat, 686 .shl, 687 .shr, 688 .add, 689 .add_wrap, 690 .add_sat, 691 .sub, 692 .sub_wrap, 693 .sub_sat, 694 .mul, 695 .mul_wrap, 696 .mul_sat, 697 .div, 698 .mod, 699 .shl_sat, 700 701 .bit_and, 702 .bit_or, 703 .bit_xor, 704 .bang_equal, 705 .equal_equal, 706 .greater_than, 707 .greater_or_equal, 708 .less_than, 709 .less_or_equal, 710 .array_cat, 711 712 .array_mult, 713 .error_union, 714 .merge_error_sets, 715 .bool_and, 716 .bool_or, 717 .@"catch", 718 .@"orelse", 719 .array_type, 720 .array_access, 721 .switch_range, 722 => { 723 const lhs, const rhs = ast.nodeData(node).node_and_node; 724 try expr(w, scope, parent_decl, lhs); 725 try expr(w, scope, parent_decl, rhs); 726 }, 727 728 .assign_destructure => { 729 const full = ast.assignDestructure(node); 730 for (full.ast.variables) |variable_node| try expr(w, scope, parent_decl, variable_node); 731 _ = try expr(w, scope, parent_decl, full.ast.value_expr); 732 }, 733 734 .bool_not, 735 .bit_not, 736 .negation, 737 .negation_wrap, 738 .deref, 739 .address_of, 740 .optional_type, 741 .@"comptime", 742 .@"nosuspend", 743 .@"suspend", 744 .@"resume", 745 .@"try", 746 => try expr(w, scope, parent_decl, ast.nodeData(node).node), 747 .unwrap_optional, 748 .grouped_expression, 749 => try expr(w, scope, parent_decl, ast.nodeData(node).node_and_token[0]), 750 .@"return" => try maybe_expr(w, scope, parent_decl, ast.nodeData(node).opt_node), 751 752 .anyframe_type => try expr(w, scope, parent_decl, ast.nodeData(node).token_and_node[1]), 753 .@"break" => try maybe_expr(w, scope, parent_decl, ast.nodeData(node).opt_token_and_opt_node[1]), 754 755 .identifier => { 756 const ident_token = ast.nodeMainToken(node); 757 const ident_name = ast.tokenSlice(ident_token); 758 if (scope.lookup(ast, ident_name)) |var_node| { 759 try w.file.get().ident_decls.put(gpa, ident_token, var_node); 760 } 761 }, 762 .field_access => { 763 const object_node, const field_ident = ast.nodeData(node).node_and_token; 764 try w.file.get().token_parents.put(gpa, field_ident, node); 765 // This will populate the left-most field object if it is an 766 // identifier, allowing rendering code to piece together the link. 767 try expr(w, scope, parent_decl, object_node); 768 }, 769 770 .string_literal, 771 .multiline_string_literal, 772 .number_literal, 773 .unreachable_literal, 774 .enum_literal, 775 .error_value, 776 .anyframe_literal, 777 .@"continue", 778 .char_literal, 779 .error_set_decl, 780 => {}, 781 782 .asm_simple, 783 .@"asm", 784 => { 785 const full = ast.fullAsm(node).?; 786 for (full.ast.items) |n| { 787 // There is a missing call here to expr() for .asm_input and 788 // .asm_output nodes. 789 _ = n; 790 } 791 try expr(w, scope, parent_decl, full.ast.template); 792 }, 793 794 .asm_legacy => {}, 795 796 .builtin_call_two, 797 .builtin_call_two_comma, 798 .builtin_call, 799 .builtin_call_comma, 800 => { 801 var buf: [2]Ast.Node.Index = undefined; 802 const params = ast.builtinCallParams(&buf, node).?; 803 return builtin_call(w, scope, parent_decl, node, params); 804 }, 805 806 .call_one, 807 .call_one_comma, 808 .call, 809 .call_comma, 810 => { 811 var buf: [1]Ast.Node.Index = undefined; 812 const full = ast.fullCall(&buf, node).?; 813 try expr(w, scope, parent_decl, full.ast.fn_expr); 814 for (full.ast.params) |param| { 815 try expr(w, scope, parent_decl, param); 816 } 817 }, 818 819 .if_simple, 820 .@"if", 821 => { 822 const full = ast.fullIf(node).?; 823 try expr(w, scope, parent_decl, full.ast.cond_expr); 824 try expr(w, scope, parent_decl, full.ast.then_expr); 825 try maybe_expr(w, scope, parent_decl, full.ast.else_expr); 826 }, 827 828 .while_simple, 829 .while_cont, 830 .@"while", 831 => { 832 try while_expr(w, scope, parent_decl, ast.fullWhile(node).?); 833 }, 834 835 .for_simple, .@"for" => { 836 const full = ast.fullFor(node).?; 837 for (full.ast.inputs) |input| { 838 if (ast.nodeTag(input) == .for_range) { 839 const start, const end = ast.nodeData(input).node_and_opt_node; 840 try expr(w, scope, parent_decl, start); 841 try maybe_expr(w, scope, parent_decl, end); 842 } else { 843 try expr(w, scope, parent_decl, input); 844 } 845 } 846 try expr(w, scope, parent_decl, full.ast.then_expr); 847 try maybe_expr(w, scope, parent_decl, full.ast.else_expr); 848 }, 849 850 .slice => return slice(w, scope, parent_decl, ast.slice(node)), 851 .slice_open => return slice(w, scope, parent_decl, ast.sliceOpen(node)), 852 .slice_sentinel => return slice(w, scope, parent_decl, ast.sliceSentinel(node)), 853 854 .block_two, 855 .block_two_semicolon, 856 .block, 857 .block_semicolon, 858 => { 859 var buf: [2]Ast.Node.Index = undefined; 860 const statements = ast.blockStatements(&buf, node).?; 861 return block(w, scope, parent_decl, statements); 862 }, 863 864 .ptr_type_aligned, 865 .ptr_type_sentinel, 866 .ptr_type, 867 .ptr_type_bit_range, 868 => { 869 const full = ast.fullPtrType(node).?; 870 try maybe_expr(w, scope, parent_decl, full.ast.align_node); 871 try maybe_expr(w, scope, parent_decl, full.ast.addrspace_node); 872 try maybe_expr(w, scope, parent_decl, full.ast.sentinel); 873 try maybe_expr(w, scope, parent_decl, full.ast.bit_range_start); 874 try maybe_expr(w, scope, parent_decl, full.ast.bit_range_end); 875 try expr(w, scope, parent_decl, full.ast.child_type); 876 }, 877 878 .container_decl, 879 .container_decl_trailing, 880 .container_decl_arg, 881 .container_decl_arg_trailing, 882 .container_decl_two, 883 .container_decl_two_trailing, 884 .tagged_union, 885 .tagged_union_trailing, 886 .tagged_union_enum_tag, 887 .tagged_union_enum_tag_trailing, 888 .tagged_union_two, 889 .tagged_union_two_trailing, 890 => { 891 var buf: [2]Ast.Node.Index = undefined; 892 return struct_decl(w, scope, parent_decl, node, ast.fullContainerDecl(&buf, node).?); 893 }, 894 895 .array_type_sentinel => { 896 const len_expr, const extra_index = ast.nodeData(node).node_and_extra; 897 const extra = ast.extraData(extra_index, Ast.Node.ArrayTypeSentinel); 898 try expr(w, scope, parent_decl, len_expr); 899 try expr(w, scope, parent_decl, extra.elem_type); 900 try expr(w, scope, parent_decl, extra.sentinel); 901 }, 902 .@"switch", .switch_comma => { 903 const full = ast.fullSwitch(node).?; 904 try expr(w, scope, parent_decl, full.ast.condition); 905 for (full.ast.cases) |case_node| { 906 const case = ast.fullSwitchCase(case_node).?; 907 for (case.ast.values) |value_node| { 908 try expr(w, scope, parent_decl, value_node); 909 } 910 try expr(w, scope, parent_decl, case.ast.target_expr); 911 } 912 }, 913 914 .array_init_one, 915 .array_init_one_comma, 916 .array_init_dot_two, 917 .array_init_dot_two_comma, 918 .array_init_dot, 919 .array_init_dot_comma, 920 .array_init, 921 .array_init_comma, 922 => { 923 var buf: [2]Ast.Node.Index = undefined; 924 const full = ast.fullArrayInit(&buf, node).?; 925 try maybe_expr(w, scope, parent_decl, full.ast.type_expr); 926 for (full.ast.elements) |elem| { 927 try expr(w, scope, parent_decl, elem); 928 } 929 }, 930 931 .struct_init_one, 932 .struct_init_one_comma, 933 .struct_init_dot_two, 934 .struct_init_dot_two_comma, 935 .struct_init_dot, 936 .struct_init_dot_comma, 937 .struct_init, 938 .struct_init_comma, 939 => { 940 var buf: [2]Ast.Node.Index = undefined; 941 const full = ast.fullStructInit(&buf, node).?; 942 try maybe_expr(w, scope, parent_decl, full.ast.type_expr); 943 for (full.ast.fields) |field| { 944 try expr(w, scope, parent_decl, field); 945 } 946 }, 947 948 .fn_proto_simple, 949 .fn_proto_multi, 950 .fn_proto_one, 951 .fn_proto, 952 => { 953 var buf: [1]Ast.Node.Index = undefined; 954 return fn_decl(w, scope, parent_decl, .none, ast.fullFnProto(&buf, node).?); 955 }, 956 } 957 } 958 959 fn slice(w: *Walk, scope: *Scope, parent_decl: Decl.Index, full: Ast.full.Slice) Oom!void { 960 try expr(w, scope, parent_decl, full.ast.sliced); 961 try expr(w, scope, parent_decl, full.ast.start); 962 try maybe_expr(w, scope, parent_decl, full.ast.end); 963 try maybe_expr(w, scope, parent_decl, full.ast.sentinel); 964 } 965 966 fn builtin_call( 967 w: *Walk, 968 scope: *Scope, 969 parent_decl: Decl.Index, 970 node: Ast.Node.Index, 971 params: []const Ast.Node.Index, 972 ) Oom!void { 973 const ast = w.file.get_ast(); 974 const builtin_token = ast.nodeMainToken(node); 975 const builtin_name = ast.tokenSlice(builtin_token); 976 if (std.mem.eql(u8, builtin_name, "@This")) { 977 try w.file.get().node_decls.put(gpa, node, scope.getNamespaceDecl()); 978 } 979 980 for (params) |param| { 981 try expr(w, scope, parent_decl, param); 982 } 983 } 984 985 fn block( 986 w: *Walk, 987 parent_scope: *Scope, 988 parent_decl: Decl.Index, 989 statements: []const Ast.Node.Index, 990 ) Oom!void { 991 const ast = w.file.get_ast(); 992 993 var scope = parent_scope; 994 995 for (statements) |node| { 996 switch (ast.nodeTag(node)) { 997 .global_var_decl, 998 .local_var_decl, 999 .simple_var_decl, 1000 .aligned_var_decl, 1001 => { 1002 const full = ast.fullVarDecl(node).?; 1003 try global_var_decl(w, scope, parent_decl, full); 1004 const local = try gpa.create(Scope.Local); 1005 local.* = .{ 1006 .parent = scope, 1007 .var_node = node, 1008 }; 1009 try w.file.get().scopes.putNoClobber(gpa, node, &local.base); 1010 scope = &local.base; 1011 }, 1012 1013 .assign_destructure => { 1014 log.debug("walk assign_destructure not implemented yet", .{}); 1015 }, 1016 1017 .grouped_expression => try expr(w, scope, parent_decl, ast.nodeData(node).node_and_token[0]), 1018 1019 .@"defer" => try expr(w, scope, parent_decl, ast.nodeData(node).node), 1020 .@"errdefer" => try expr(w, scope, parent_decl, ast.nodeData(node).opt_token_and_node[1]), 1021 1022 else => try expr(w, scope, parent_decl, node), 1023 } 1024 } 1025 } 1026 1027 fn while_expr(w: *Walk, scope: *Scope, parent_decl: Decl.Index, full: Ast.full.While) Oom!void { 1028 try expr(w, scope, parent_decl, full.ast.cond_expr); 1029 try maybe_expr(w, scope, parent_decl, full.ast.cont_expr); 1030 try expr(w, scope, parent_decl, full.ast.then_expr); 1031 try maybe_expr(w, scope, parent_decl, full.ast.else_expr); 1032 } 1033 1034 fn scanDecls(w: *Walk, namespace: *Scope.Namespace, members: []const Ast.Node.Index) Oom!void { 1035 const ast = w.file.get_ast(); 1036 1037 for (members) |member_node| { 1038 const name_token = switch (ast.nodeTag(member_node)) { 1039 .global_var_decl, 1040 .local_var_decl, 1041 .simple_var_decl, 1042 .aligned_var_decl, 1043 => ast.nodeMainToken(member_node) + 1, 1044 1045 .fn_proto_simple, 1046 .fn_proto_multi, 1047 .fn_proto_one, 1048 .fn_proto, 1049 .fn_decl, 1050 => blk: { 1051 const ident = ast.nodeMainToken(member_node) + 1; 1052 if (ast.tokenTag(ident) != .identifier) continue; 1053 break :blk ident; 1054 }, 1055 1056 .test_decl => { 1057 const opt_ident_token = ast.nodeData(member_node).opt_token_and_node[0]; 1058 if (opt_ident_token.unwrap()) |ident_token| { 1059 const is_doctest = ast.tokenTag(ident_token) == .identifier; 1060 if (is_doctest) { 1061 const token_bytes = ast.tokenSlice(ident_token); 1062 try namespace.doctests.put(gpa, token_bytes, member_node); 1063 } 1064 } 1065 continue; 1066 }, 1067 1068 else => continue, 1069 }; 1070 1071 const token_bytes = ast.tokenSlice(name_token); 1072 try namespace.names.put(gpa, token_bytes, member_node); 1073 } 1074 } 1075 1076 pub fn isPrimitiveNonType(name: []const u8) bool { 1077 return std.mem.eql(u8, name, "undefined") or 1078 std.mem.eql(u8, name, "null") or 1079 std.mem.eql(u8, name, "true") or 1080 std.mem.eql(u8, name, "false"); 1081 } 1082 1083 //test { 1084 // const gpa = std.testing.allocator; 1085 // 1086 // var arena_instance = std.heap.ArenaAllocator.init(gpa); 1087 // defer arena_instance.deinit(); 1088 // const arena = arena_instance.allocator(); 1089 // 1090 // // example test command: 1091 // // zig test --dep input.zig -Mroot=src/Walk.zig -Minput.zig=/home/andy/dev/zig/lib/std/fs/File/zig 1092 // var ast = try Ast.parse(gpa, @embedFile("input.zig"), .zig); 1093 // defer ast.deinit(gpa); 1094 // 1095 // var w: Walk = .{ 1096 // .arena = arena, 1097 // .token_links = .{}, 1098 // .ast = &ast, 1099 // }; 1100 // 1101 // try w.root(); 1102 //} 1103 1104 fn shrinkToFit(m: anytype) void { 1105 m.shrinkAndFree(gpa, m.entries.len); 1106 }