zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

commit 3eb90a110f2c4b6eea82b0fcd7c8cab386594437 (tree)
parent 03d392923208550c21f2f3813379830b76c54133
Author: Loris Cro <kappaloris@gmail.com>
Date:   Tue,  8 Mar 2022 19:57:22 +0100

autodoc: add support for pointers and comptime expressions in decl paths

Diffstat:
Mlib/docs/main.js | 193+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Msrc/Autodoc.zig | 113+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
2 files changed, 184 insertions(+), 122 deletions(-)

diff --git a/lib/docs/main.js b/lib/docs/main.js @@ -144,13 +144,33 @@ return typeKind === typeKinds.ErrorSet || typeKindIsContainer(typeKind); } + function findCteInDeclPath(path) { + for (var i = path.length - 1; i >= 0; i -= 1) { + const decl = zigAnalysis.decls[path[i]]; + if ("comptimeExpr" in decl.value) { + return decl; + } + + if ("declPath" in decl.value) { + const res = findCteInDeclPath(decl.value.declPath); + if (res !== null) { + return res; + } + } + } + + return null; + } + function resolveValue(value) { var i = 0; while(i < 1000) { i += 1; if ("declPath" in value) { - console.assert(value.declPath.length == 1); // only support declRefs for now + if (value.hasCte) { + return findCteInDeclPath(value.declPath).value; + } value = zigAnalysis.decls[value.declPath[0]].value; continue; } @@ -171,30 +191,34 @@ } if ("declPath" in decl.value) { - console.assert(decl.value.declPath.length == 1); // only support declRefs for now - decl = zigAnalysis.decls[decl.value.declPath[0]]; + if (decl.value.hasCte) { + decl = findCteInDeclPath(decl.value.declPath); + } else { + decl = zigAnalysis.decls[decl.value.declPath[0]]; + } + continue; } if ("int" in decl.value) { - return resolveTypeRefToTypeId(decl.value.int.typeRef); + return decl.value.int.typeRef; } if ("float" in decl.value) { - return resolveTypeRefToTypeId(decl.value.float.typeRef); + return decl.value.float.typeRef; } if ("array" in decl.value) { - return resolveTypeRefToTypeId(decl.value.array.typeRef); + return decl.value.array.typeRef; } if ("struct" in decl.value) { - return resolveTypeRefToTypeId(decl.value.struct.typeRef); + return decl.value.struct.typeRef; } if ("comptimeExpr" in decl.value) { const cte = zigAnalysis.comptimeExprs[decl.value.comptimeExpr]; - return resolveTypeRefToTypeId(cte.typeRef); + return cte.typeRef; } if ("call" in decl.value) { @@ -205,7 +229,7 @@ console.assert("type" in fn_decl_value); //TODO handle comptimeExpr const fn_type = zigAnalysis.types[fn_decl_value.type]; console.assert(fn_type.kind === typeKinds.Fn); - return resolveTypeRefToTypeId(fn_type.ret); + return fn_type.ret; } console.log("TODO: handle in `typeOfDecl` more cases: ", decl); @@ -215,23 +239,6 @@ console.assert(false); } - function resolveTypeRefToTypeId(ref) { - if ("unspecified" in ref) { - console.log("found an unspecified type!") - return -1; - } - - if ("declRef" in ref) { - return typeOfDecl(ref.declRef); - } - - if ("type" in ref) { - return ref.type; - } - - console.assert(false); - } - function render() { domStatus.classList.add("hidden"); domFnProto.classList.add("hidden"); @@ -369,7 +376,7 @@ console.assert("type" in value); var typeObj = zigAnalysis.types[value.type]; - domFnProtoCode.innerHTML = typeIndexName(value.type, true, true, fnDecl); + domFnProtoCode.innerHTML = typeValueName(value, true, true, fnDecl); var docsSource = null; var srcNode = zigAnalysis.astNodes[fnDecl.src]; @@ -462,9 +469,6 @@ var value = typeObj.params[i]; - var valueType = resolveValue(value); - console.assert("type" in valueType); - var argTypeIndex = valueType.type; var html = '<pre>' + escapeHtml(fieldNode.name) + ": "; if (isVarArgs && i === typeObj.params.length - 1) { html += '...'; @@ -482,8 +486,6 @@ } else if ("type" in value) { var name = zigAnalysis.types[value.type].name; html += '<span class="tok-kw">' + escapeHtml(name) + '</span>'; - } else if (argTypeIndex != null) { - html += typeIndexName(argTypeIndex, true, true); } else { html += '<span class="tok-kw">var</span>'; } @@ -654,13 +656,16 @@ } } - function typeIndexName(typeIndex, wantHtml, wantLink, fnDecl, linkFnNameDecl) { - return typeValueName({ type: typeIndex }, wantHtml, wantLink, fnDecl, linkFnNameDecl); - } - function typeValueName(typeValue, wantHtml, wantLink, fnDecl, linkFnNameDecl) { if ("declPath" in typeValue) { - console.assert(typeValue.declPath.length == 1); + if (typeValue.hasCte) { + // TODO: find the cte, print it nicely + if (wantLink) { + return '<a href=""># CTE TODO #</a>'; + } else { + return "# CTE TODO #"; + } + } var declIndex = typeValue.declPath[0]; var name = zigAnalysis.decls[declIndex].name; var declPath = getCanonDeclPath(declIndex); @@ -695,12 +700,17 @@ } } - function shouldSkipParamName(typeIndex, paramName) { - var typeObj = zigAnalysis.types[typeIndex]; - if (typeObj.kind === typeKinds.Pointer && getPtrSize(typeObj) === pointerSizeEnum.One) { - typeIndex = typeObj.child; + function shouldSkipParamName(typeRef, paramName) { + var resolvedTypeRef = resolveValue(typeRef); + if ("type" in resolvedTypeRef) { + var typeObj = zigAnalysis.types[resolvedTypeRef.type]; + if (typeObj.kind === typeKinds.Pointer && + getPtrSize(typeObj) === pointerSizeEnum.One) { + const value = resolveValue(typeObj.child); + return typeValueName(value, false, true).toLowerCase() === paramName; + } } - return typeIndexName(typeIndex, false, true).toLowerCase() === paramName; + return false; } function getPtrSize(typeObj) { @@ -716,20 +726,25 @@ for (var arg_i = 0; arg_i < callObj.args.length; arg_i += 1) { if (arg_i !== 0) html += ', '; var argObj = callObj.args[arg_i]; - html += getValueText(argObj.type, argObj.value, true, true); + html += getValueText(argObj, argObj.value, true, true); } html += ')'; return html; } - function getValueText(typeIndex, value, wantHtml, wantLink) { - var typeObj = zigAnalysis.types[typeIndex]; + function getValueText(typeRef, value, wantHtml, wantLink) { + var resolvedTypeRef = resolveValue(typeRef); + if ("comptimeExpr" in resolvedTypeRef) { + return "# CTE TODO #"; + } + console.assert("type" in resolvedTypeRef); + var typeObj = zigAnalysis.types[typeRef.type]; switch (typeObj.kind) { case typeKinds.Type: return typeIndexName(value, wantHtml, wantLink); case typeKinds.Fn: var fnObj = zigAnalysis.fns[value]; - return typeIndexName(fnObj.type, wantHtml, wantLink); + return typeValueName(fnObj, wantHtml, wantLink); case typeKinds.Int: if (wantHtml) { return '<span class="tok-number">' + value + '</span>'; @@ -923,11 +938,10 @@ if (i != 0) { payloadHtml += ', '; } + var value = typeObj.params[i]; var paramValue = resolveValue(value); - console.assert("type" in paramValue); - var argTypeIndex = paramValue.type; - + var isCte = "comptimeExpr" in paramValue; if (fields != null) { var paramNode = zigAnalysis.astNodes[fields[i]]; @@ -956,7 +970,7 @@ var paramName = paramNode.name; if (paramName != null) { // skip if it matches the type name - if (argTypeIndex == null || !shouldSkipParamName(argTypeIndex, paramName)) { + if (!shouldSkipParamName(paramValue, paramName)) { payloadHtml += paramName + ': '; } } @@ -975,10 +989,10 @@ payloadHtml += '<span class="tok-kw" style="color:lightblue;">' + escapeHtml(decl.name) + '</span>'; payloadHtml += '</a>'; } else if ("type" in value) { - var name = zigAnalysis.types[value.type].name; + var name = typeValueName(value, false); payloadHtml += '<span class="tok-kw">' + escapeHtml(name) + '</span>'; - } else if (argTypeIndex != null) { - payloadHtml += typeIndexName(argTypeIndex, wantHtml, wantSubLink); + } else if ("comptimeExpr" in value) { + payloadHtml += '<span class="tok-kw"> # CTE TODO #</span>'; } else if (wantHtml) { payloadHtml += '<span class="tok-kw">var</span>'; } else { @@ -1152,7 +1166,7 @@ function renderValue(decl) { - var declTypeId = typeOfDecl(decl); + var declTypeRef = typeOfDecl(decl); var declValueText = ""; switch(Object.keys(decl.value)[0]) { case "int": @@ -1170,7 +1184,7 @@ } domFnProtoCode.innerHTML = '<span class="tok-kw">const</span> ' + - escapeHtml(decl.name) + ': ' + typeIndexName(declTypeId, true, true) + + escapeHtml(decl.name) + ': ' + typeValueName(declTypeRef, true, true) + " = " + declValueText; var docs = zigAnalysis.astNodes[decl.src].docs; @@ -1183,9 +1197,9 @@ } function renderVar(decl) { - var declTypeId = typeOfDecl(decl); + var declTypeRef = typeOfDecl(decl); domFnProtoCode.innerHTML = '<span class="tok-kw">var</span> ' + - escapeHtml(decl.name) + ': ' + typeIndexName(declTypeId, true, true); + escapeHtml(decl.name) + ': ' + typeValueName(declTypeRef, true, true); var docs = zigAnalysis.astNodes[decl.src].docs; if (docs != null) { @@ -1221,8 +1235,9 @@ var value = zigAnalysis.types[declValue.type]; var kind = value.kind; if (kind === typeKinds.Fn) { - //if (allCompTimeFnCallsHaveTypeResult(decl.type, declTypeId)) { - if (resolveTypeRefToTypeId(value.ret) == typeTypeId) { + // TODO: handle CTE return types when we know their type. + const resVal = resolveValue(value.ret); + if ("type" in resVal && resVal.type == typeTypeId) { typesList.push(decl); } else { fnsList.push(decl); @@ -1300,7 +1315,7 @@ var declType = resolveValue(decl.value); console.assert("type" in declType); - tdFnCode.innerHTML = typeIndexName(declType.type, true, true, decl, navLinkDecl(decl.name)); + tdFnCode.innerHTML = typeValueName(declType, true, true, decl, navLinkDecl(decl.name)); var docs = zigAnalysis.astNodes[decl.src].docs; if (docs != null) { @@ -1327,35 +1342,37 @@ } else { var field = container.fields[i]; html += ": "; - if (typeof(field) === 'object') { - if (field.failure === true) { - html += '<span class="tok-kw" style="color:red;">#FAILURE#</span>'; - } else if ("declPath" in field) { - for (var j = field.declPath.length - 1; j >= 0; j--) { - var decl = zigAnalysis.decls[field.declPath[j]]; - - html += '<a href="'+navLinkDecl(decl.name)+'">'; - html += '<span class="tok-kw" style="color:lightblue;">' + - escapeHtml(decl.name) + '</span>'; - html += '</a>'; - if (j != 0) html += "."; + if (field.failure === true) { + html += '<span class="tok-kw" style="color:red;">#FAILURE#</span>'; + } else if ("declPath" in field) { + for (var j = field.declPath.length - 1; j >= 0; j--) { + var decl = zigAnalysis.decls[field.declPath[j]]; + + // TODO: handle nested decl paths properly! + if (field.hasCte) { + html += "<a href=\"\"># CTE TODO #</a>"; + break; } - // at the end of the for loop this is the value of `decl` - //decl = zigAnalysis.decls[field.declPath[0]]; - - var val = resolveValue(decl.value); - console.assert("type" in val); - var valType = zigAnalysis.types[val.type]; - var valTypeName = typeShorthandName(valType); - html += ' ('+ valTypeName +')'; - } else if ("type" in field) { - var name = zigAnalysis.types[field.type].name; - html += '<span class="tok-kw">' + escapeHtml(name) + '</span>'; - } else { - html += '<span class="tok-kw">var</span>'; + + html += '<a href="'+navLinkDecl(decl.name)+'">'; + html += '<span class="tok-kw" style="color:lightblue;">' + + escapeHtml(decl.name) + '</span>'; + html += '</a>'; + if (j != 0) html += "."; } + // at the end of the for loop this is the value of `decl` + //decl = zigAnalysis.decls[field.declPath[0]]; + + var val = resolveValue(decl.value); + console.assert("type" in val); + var valType = zigAnalysis.types[val.type]; + var valTypeName = typeShorthandName(valType); + html += ' ('+ valTypeName +')'; + } else if ("type" in field) { + var name = zigAnalysis.types[field.type].name; + html += '<span class="tok-kw">' + escapeHtml(name) + '</span>'; } else { - html += typeIndexName(field, true, true); + html += '<span class="tok-kw">var</span>'; } } @@ -1385,7 +1402,7 @@ tdNameA.setAttribute('href', navLinkDecl(decl.name)); tdNameA.textContent = decl.name; - tdType.innerHTML = typeIndexName(typeOfDecl(decl), true, true); + tdType.innerHTML = typeValueName(typeOfDecl(decl), true, true); var docs = zigAnalysis.astNodes[decl.src].docs; if (docs != null) { @@ -1412,7 +1429,7 @@ tdNameA.setAttribute('href', navLinkDecl(decl.name)); tdNameA.textContent = decl.name; - tdType.innerHTML = typeIndexName(typeOfDecl(decl), true, true); + tdType.innerHTML = typeValueName(typeOfDecl(decl), true, true); var docs = zigAnalysis.astNodes[decl.src].docs; if (docs != null) { diff --git a/src/Autodoc.zig b/src/Autodoc.zig @@ -30,7 +30,7 @@ decl_paths_pending_on_types: std.AutoHashMapUnmanaged( const DeclPathResumeInfo = struct { file: *File, - path: []usize, + decl_path: DocData.DeclPath, }; var arena_allocator: std.heap.ArenaAllocator = undefined; @@ -347,7 +347,7 @@ const DocData = struct { Int: struct { name: []const u8 }, Float: struct { name: []const u8 }, Pointer: struct { - name: []const u8, + size: std.builtin.TypeInfo.Pointer.Size, child: TypeRef, }, Array: struct { @@ -427,6 +427,20 @@ const DocData = struct { .Int => |v| try printTypeBody(v, options, w), .Float => |v| try printTypeBody(v, options, w), .Type => |v| try printTypeBody(v, options, w), + .Pointer => |v| { + if (options.whitespace) |ws| try ws.outputIndent(w); + try w.print( + \\"size": {}, + \\ + , .{@enumToInt(v.size)}); + if (options.whitespace) |ws| try ws.outputIndent(w); + try w.print( + \\"child": + , .{}); + + if (options.whitespace) |*ws| ws.indent_level += 1; + try v.child.jsonStringify(options, w); + }, else => { std.debug.print( "TODO: add {s} to `DocData.Type.jsonStringify`\n", @@ -458,9 +472,14 @@ const DocData = struct { } }; + const DeclPath = struct { + path: []usize, // indexes in `decls` + hasCte: bool = false, // a prefix of this path could not be resolved + }; + const TypeRef = union(enum) { unspecified, - declPath: []usize, // indexes in `decls` + declPath: DeclPath, type: usize, // index in `types` comptimeExpr: usize, // index in `comptimeExprs` @@ -490,9 +509,9 @@ const DocData = struct { , .{ @tagName(self), v }); }, .declPath => |v| { - try w.print("{{ \"declPath\": [", .{}); - for (v) |d, i| { - const comma = if (i == v.len - 1) "]}" else ","; + try w.print("{{ \"hasCte\": {}, \"declPath\": [", .{v.hasCte}); + for (v.path) |d, i| { + const comma = if (i == v.path.len - 1) "]}" else ","; try w.print("{d}{s}", .{ d, comma }); } }, @@ -509,7 +528,7 @@ const DocData = struct { @"struct": Struct, bool: bool, type: usize, // index in `types` - declPath: []usize, // indices in `decl` + declPath: DeclPath, int: struct { typeRef: TypeRef, value: usize, // direct value @@ -584,9 +603,9 @@ const DocData = struct { w, ), .declPath => |v| { - try w.print("{{ \"declPath\": [", .{}); - for (v) |d, i| { - const comma = if (i == v.len - 1) "]}" else ","; + try w.print("{{ \"hasCte\": {}, \"declPath\": [", .{v.hasCte}); + for (v.path) |d, i| { + const comma = if (i == v.path.len - 1) "]}" else ","; try w.print("{d}{s}", .{ d, comma }); } }, @@ -676,6 +695,19 @@ fn walkInstruction( }, }; }, + .ptr_type_simple => { + const ptr = data[inst_index].ptr_type_simple; + const type_slot_index = self.types.items.len; + const elem_type_ref = try self.walkRef(file, parent_scope, ptr.elem_type); + try self.types.append(self.arena, .{ + .Pointer = .{ + .size = ptr.size, + .child = walkResultToTypeRef(elem_type_ref), + }, + }); + + return DocData.WalkResult{ .type = type_slot_index }; + }, .array_init => { const pl_node = data[inst_index].pl_node; const extra = file.zir.extraData(Zir.Inst.MultiOp, pl_node.payload_index); @@ -770,7 +802,7 @@ fn walkInstruction( const decls_slot_index = parent_scope.resolveDeclName(str_tok.start); var path = try self.arena.alloc(usize, 1); path[0] = decls_slot_index; - return DocData.WalkResult{ .declPath = path }; + return DocData.WalkResult{ .declPath = .{ .path = path } }; }, .field_val, .field_call_bind, .field_ptr => { const pl_node = data[inst_index].pl_node; @@ -844,8 +876,9 @@ fn walkInstruction( // the analyzed data corresponding to the top-most decl of this path. // We are now going to reverse loop over `path` to resolve each name // to its corresponding index in `decls`. - try self.tryResolveDeclPath(file, path.items); - return DocData.WalkResult{ .declPath = path.items }; + var decl_path: DocData.DeclPath = .{ .path = path.items }; + try self.tryResolveDeclPath(file, &decl_path); + return DocData.WalkResult{ .declPath = decl_path }; }, .int_type => { const int_type = data[inst_index].int_type; @@ -893,7 +926,7 @@ fn walkInstruction( return DocData.WalkResult{ .call = call_slot_index }; }, - .func => { + .func, .func_inferred => { const fn_info = file.zir.getFnInfo(@intCast(u32, inst_index)); try self.ast_nodes.ensureUnusedCapacity(self.arena, fn_info.total_params_len); @@ -960,18 +993,18 @@ fn walkInstruction( return DocData.WalkResult{ .type = self.types.items.len - 1 }; }, .extended => { - // TODO: this assumes that we always return a type when analyzing - // an extended instruction. Also we willingfully not reserve - // a slot for functions (handled right above) despite them - // being stored in `types`. The reason why we reserve a slot - // in here, is for decl paths and their resolution system. + // NOTE: this code + the subsequent defer block are working towards + // solving pending decl paths that depend on a type to be analyzed. + // When we don't find a type, the defer will run anyway but shouldn't + // ever be able to find a match inside `decl_paths_pending_on_types` + // TODO: extract this logic into a function and only call it when appropriate. const type_slot_index = self.types.items.len; try self.types.append(self.arena, .{ .Unanalyzed = {} }); defer { if (self.decl_paths_pending_on_types.get(type_slot_index)) |paths| { - for (paths.items) |resume_info| { - self.tryResolveDeclPath(resume_info.file, resume_info.path) catch { + for (paths.items) |*resume_info| { + self.tryResolveDeclPath(resume_info.file, &resume_info.decl_path) catch { @panic("Out of memory"); }; } @@ -1537,8 +1570,8 @@ fn walkDecls( // Unblock any pending decl path that was waiting for this decl. if (self.decl_paths_pending_on_decls.get(decls_slot_index)) |paths| { - for (paths.items) |resume_info| { - try self.tryResolveDeclPath(resume_info.file, resume_info.path); + for (paths.items) |*resume_info| { + try self.tryResolveDeclPath(resume_info.file, &resume_info.decl_path); } _ = self.decl_paths_pending_on_decls.remove(decls_slot_index); @@ -1560,10 +1593,12 @@ fn tryResolveDeclPath( self: *Autodoc, /// File from which the decl path originates. file: *File, - path: []usize, + decl_path: *DocData.DeclPath, ) error{OutOfMemory}!void { + const path: []usize = decl_path.path; + var i: usize = path.len; - while (i > 1) { + outer: while (i > 1) { i -= 1; const decl_index = path[i]; const string_index = path[i - 1]; @@ -1580,7 +1615,7 @@ fn tryResolveDeclPath( if (!res.found_existing) res.value_ptr.* = .{}; try res.value_ptr.*.append(self.arena, .{ .file = file, - .path = path[0 .. i + 1], + .decl_path = .{ .path = path[0 .. i + 1] }, }); return; @@ -1590,15 +1625,25 @@ fn tryResolveDeclPath( switch (parent.value) { else => { std.debug.panic( - "TODO: handle `{s}`in walkInstruction.field_val\n \"{s}\":{}", + "TODO: handle `{s}`in tryResolveDecl.field_val\n \"{s}\":{}", .{ @tagName(parent.value), parent.name, parent.value }, ); }, + .comptimeExpr => { + // Since we hit a cte, we leave the remaining strings unresolved + // and completely give up on resolving this decl path. + decl_path.hasCte = true; + break :outer; + }, .declPath => |dp| { - if (self.pending_decl_paths.getPtr(&dp[0])) |waiter_list| { + if (dp.hasCte) { + decl_path.hasCte = true; + break :outer; + } + if (self.pending_decl_paths.getPtr(&dp.path[0])) |waiter_list| { try waiter_list.append(self.arena, .{ .file = file, - .path = path[0 .. i + 1], + .decl_path = .{ .path = path[0 .. i + 1] }, }); // This decl path is pending completion @@ -1610,7 +1655,7 @@ fn tryResolveDeclPath( return; } - const final_decl_index = dp[0]; + const final_decl_index = dp.path[0]; // For the purpose of being able to call tryResolveDeclPath again, // we momentarily replace the decl index present in `path[i]` // with the final decl in `dp`. @@ -1619,7 +1664,7 @@ fn tryResolveDeclPath( // will not get fully resolved (also in the case that final_decl is // not resolved yet). path[i] = final_decl_index; - try self.tryResolveDeclPath(file, path); + try self.tryResolveDeclPath(file, decl_path); path[i] = decl_index; }, .type => |t_index| switch (self.types.items[t_index]) { @@ -1643,7 +1688,7 @@ fn tryResolveDeclPath( if (!res.found_existing) res.value_ptr.* = .{}; try res.value_ptr.*.append(self.arena, .{ .file = file, - .path = path[0 .. i + 1], + .decl_path = .{ .path = path[0 .. i + 1] }, }); return; @@ -1677,8 +1722,8 @@ fn tryResolveDeclPath( // attempting to resolve any other decl. _ = self.pending_decl_paths.remove(&path[0]); - for (waiter_list.items) |resume_info| { - try self.tryResolveDeclPath(resume_info.file, resume_info.path); + for (waiter_list.items) |*resume_info| { + try self.tryResolveDeclPath(resume_info.file, &resume_info.decl_path); } // TODO: this is where we should free waiter_list, but its in the arena // that said, we might want to store it elsewhere and reclaim memory asap