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

This commit is contained in:
Loris Cro 2022-03-08 19:57:22 +01:00 committed by Andrew Kelley
parent 03d3929232
commit 3eb90a110f
2 changed files with 182 additions and 120 deletions

View File

@ -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]];
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 += ".";
// 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) {

View File

@ -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