Commit 1014cfdf3b

Andrew Kelley <andrew@ziglang.org>
2019-10-16 07:29:16
generated docs: progress towards generic types being useful
See #3406
1 parent 47dfaf3
lib/std/special/docs/index.html
@@ -104,6 +104,16 @@
         background-color: #FFBB4D;
         color: #000;
       }
+      #listFnExamples {
+        list-style-type: none;
+        margin: 0;
+        padding: 0;
+      }
+      #listFnExamples li {
+        padding: 0.5em 0;
+        white-space: nowrap;
+        overflow-x: auto;
+      }
       #logo {
         width: 8em;
         padding: 0.5em 1em;
@@ -289,7 +299,6 @@
       <pre id="fnProtoCode"></pre>
     </div>
     <h1 id="hdrName" class="hidden"></h1>
-    <div id="fnExamples" class="hidden"></div>
     <div id="fnNoExamples" class="hidden">
       <p>This function is not tested or referenced.</p>
     </div>
@@ -357,6 +366,10 @@
       <ul id="listErrSets">
       </ul>
     </div>
+    <div id="fnExamples" class="hidden">
+        <h2>Examples</h2>
+        <ul id="listFnExamples"></ul>
+    </div>
     </section>
     <div id="helpDialog" class="hidden">
       <h1>Keyboard Shortcuts</h1>
lib/std/special/docs/main.js
@@ -26,6 +26,7 @@
     var domTableFnErrors = document.getElementById("tableFnErrors");
     var domFnErrorsAnyError = document.getElementById("fnErrorsAnyError");
     var domFnExamples = document.getElementById("fnExamples");
+    var domListFnExamples = document.getElementById("listFnExamples");
     var domFnNoExamples = document.getElementById("fnNoExamples");
     var domDeclNoRef = document.getElementById("declNoRef");
     var domSearch = document.getElementById("search");
@@ -64,6 +65,9 @@
         declNames: [],
         // these will be all types, except the last one may be a type or a decl
         declObjs: [],
+
+        // (a, b, c, d) comptime call; result is the value the docs refer to
+        callName: null,
     };
     var curNavSearch = "";
     var curSearchIndex = -1;
@@ -237,20 +241,29 @@
             renderErrorSet(errSetType);
         }
 
-        var protoSrcIndex;
+        var fnObj = zigAnalysis.fns[fnDecl.value];
+        var protoSrcIndex = fnObj.src;
         if (typeIsGenericFn(fnDecl.type)) {
-            protoSrcIndex = fnDecl.value;
-
             var instantiations = nodesToFnsMap[protoSrcIndex];
             var calls = nodesToCallsMap[protoSrcIndex];
             if (instantiations == null && calls == null) {
                 domFnNoExamples.classList.remove("hidden");
-            } else {
-                // TODO show examples
+            } else if (calls != null) {
+                if (fnObj.combined === undefined) fnObj.combined = allCompTimeFnCallsResult(calls);
+                if (fnObj.combined != null) renderContainer(fnObj.combined);
+
+                resizeDomList(domListFnExamples, calls.length, '<li></li>');
+
+                for (var callI = 0; callI < calls.length; callI += 1) {
+                    var liDom = domListFnExamples.children[callI];
+                    liDom.innerHTML = getCallHtml(fnDecl, calls[callI]);
+                }
+
                 domFnExamples.classList.remove("hidden");
+            } else if (instantiations != null) {
+                // TODO
             }
         } else {
-            protoSrcIndex = zigAnalysis.fns[fnDecl.value].src;
 
             domFnExamples.classList.add("hidden");
             domFnNoExamples.classList.add("hidden");
@@ -349,13 +362,15 @@
         }
     }
 
-    function navLink(pkgNames, declNames) {
+    function navLink(pkgNames, declNames, callName) {
         if (pkgNames.length === 0 && declNames.length === 0) {
             return '#';
-        } else if (declNames.length === 0) {
+        } else if (declNames.length === 0 && callName == null) {
             return '#' + pkgNames.join('.');
-        } else {
+        } else if (callName == null) {
             return '#' + pkgNames.join('.') + ';' + declNames.join('.');
+        } else {
+            return '#' + pkgNames.join('.') + ';' + declNames.join('.') + ';' + callName;
         }
     }
 
@@ -367,6 +382,22 @@
         return navLink(curNav.pkgNames, curNav.declNames.concat([childName]));
     }
 
+    function navLinkCall(callObj) {
+        var declNamesCopy = curNav.declNames.concat([]);
+        var callName = declNamesCopy.pop();
+
+        callName += '(';
+        for (var arg_i = 0; arg_i < callObj.args.length; arg_i += 1) {
+            if (arg_i !== 0) callName += ',';
+            var argObj = callObj.args[arg_i];
+            callName += getValueText(argObj.type, argObj.value, false, false);
+        }
+        callName += ')';
+
+        declNamesCopy.push(callName);
+        return navLink(curNav.pkgNames, declNamesCopy);
+    }
+
     function resizeDomListDl(dlDom, desiredLen) {
         // add the missing dom entries
         var i, ev;
@@ -426,6 +457,40 @@
         return (typeObj.len == null) ? pointerSizeEnum.One : typeObj.len;
     }
 
+    function getCallHtml(fnDecl, callIndex) {
+        var callObj = zigAnalysis.calls[callIndex];
+
+        // TODO make these links work
+        //var html = '<a href="' + navLinkCall(callObj) + '">' + escapeHtml(fnDecl.name) + '</a>(';
+        var html = escapeHtml(fnDecl.name) + '(';
+        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 += ')';
+        return html;
+    }
+
+    function getValueText(typeIndex, value, wantHtml, wantLink) {
+        var typeObj = zigAnalysis.types[typeIndex];
+        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);
+            case typeKinds.Int:
+                if (wantHtml) {
+                    return '<span class="tok-number">' + value + '</span>';
+                } else {
+                    return value + "";
+                }
+            default:
+                throw new Error("TODO implement getValueText for this type");
+        }
+    }
+
     function typeName(typeObj, wantHtml, wantSubLink, fnDecl, linkFnNameDecl) {
         switch (typeObj.kind) {
             case typeKinds.Array:
@@ -544,6 +609,12 @@
                 } else {
                     return "void";
                 }
+            case typeKinds.EnumLiteral:
+                if (wantHtml) {
+                    return '<span class="tok-type">(enum literal)</span>';
+                } else {
+                    return "(enum literal)";
+                }
             case typeKinds.NoReturn:
                 if (wantHtml) {
                     return '<span class="tok-type">noreturn</span>';
@@ -592,6 +663,15 @@
                 }
                 payloadHtml += '(';
                 if (typeObj.args != null) {
+                    var fields = null;
+                    var isVarArgs = false;
+                    if (fnDecl != null) {
+                        var fnObj = zigAnalysis.fns[fnDecl.value];
+                        var fnNode = zigAnalysis.astNodes[fnObj.src];
+                        fields = fnNode.fields;
+                        isVarArgs = fnNode.varArgs;
+                    }
+
                     for (var i = 0; i < typeObj.args.length; i += 1) {
                         if (i != 0) {
                             payloadHtml += ', ';
@@ -599,10 +679,31 @@
 
                         var argTypeIndex = typeObj.args[i];
 
-                        if (fnDecl != null && zigAnalysis.astNodes[fnDecl.src].fields != null) {
-                            var paramDeclIndex = zigAnalysis.astNodes[fnDecl.src].fields[i];
-                            var paramName = zigAnalysis.astNodes[paramDeclIndex].name;
+                        if (fields != null) {
+                            var paramNode = zigAnalysis.astNodes[fields[i]];
+
+                            if (paramNode.varArgs) {
+                                payloadHtml += '...';
+                                continue;
+                            }
+
+                            if (paramNode.noalias) {
+                                if (wantHtml) {
+                                    payloadHtml += '<span class="tok-kw">noalias</span> ';
+                                } else {
+                                    payloadHtml += 'noalias ';
+                                }
+                            }
+
+                            if (paramNode.comptime) {
+                                if (wantHtml) {
+                                    payloadHtml += '<span class="tok-kw">comptime</span> ';
+                                } else {
+                                    payloadHtml += 'comptime ';
+                                }
+                            }
 
+                            var paramName = paramNode.name;
                             if (paramName != null) {
                                 // skip if it matches the type name
                                 if (argTypeIndex == null || !shouldSkipParamName(argTypeIndex, paramName)) {
@@ -611,7 +712,9 @@
                             }
                         }
 
-                        if (argTypeIndex != null) {
+                        if (isVarArgs && i === typeObj.args.length - 1) {
+                            payloadHtml += '...';
+                        } else if (argTypeIndex != null) {
                             payloadHtml += typeIndexName(argTypeIndex, wantHtml, wantSubLink);
                         } else if (wantHtml) {
                             payloadHtml += '<span class="tok-kw">var</span>';
@@ -690,7 +793,7 @@
     }
 
     function allCompTimeFnCallsHaveTypeResult(typeIndex, value) {
-        var srcIndex = typeIsGenericFn(typeIndex) ? value : zigAnalysis.fns[value].src;
+        var srcIndex = zigAnalysis.fns[value].src;
         var calls = nodesToCallsMap[srcIndex];
         if (calls == null) return false;
         for (var i = 0; i < calls.length; i += 1) {
@@ -700,6 +803,90 @@
         return true;
     }
 
+    function allCompTimeFnCallsResult(calls) {
+        var firstTypeObj = null;
+        var containerObj = {
+            privDecls: [],
+        };
+        for (var callI = 0; callI < calls.length; callI += 1) {
+            var call = zigAnalysis.calls[calls[callI]];
+            if (call.result.type !== typeTypeId) return null;
+            var typeObj = zigAnalysis.types[call.result.value];
+            if (!typeKindIsContainer(typeObj.kind)) return null;
+            if (firstTypeObj == null) {
+                firstTypeObj = typeObj;
+                containerObj.src = typeObj.src;
+            } else if (firstTypeObj.src !== typeObj.src) {
+                return null;
+            }
+
+            if (containerObj.fields == null) {
+                containerObj.fields = (typeObj.fields || []).concat([]);
+            } else for (var fieldI = 0; fieldI < typeObj.fields.length; fieldI += 1) {
+                var prev = containerObj.fields[fieldI];
+                var next = typeObj.fields[fieldI];
+                if (prev === next) continue;
+                if (typeof(prev) === 'object') {
+                    if (prev[next] == null) prev[next] = typeObj;
+                } else {
+                    containerObj.fields[fieldI] = {};
+                    containerObj.fields[fieldI][prev] = firstTypeObj;
+                    containerObj.fields[fieldI][next] = typeObj;
+                }
+            }
+
+            if (containerObj.pubDecls == null) {
+                containerObj.pubDecls = (typeObj.pubDecls || []).concat([]);
+            } else for (var declI = 0; declI < typeObj.pubDecls.length; declI += 1) {
+                var prev = containerObj.pubDecls[declI];
+                var next = typeObj.pubDecls[declI];
+                if (prev === next) continue;
+                // TODO instead of showing "examples" as the public declarations,
+                // do logic like this:
+                //if (typeof(prev) !== 'object') {
+                //    var newDeclId = zigAnalysis.decls.length;
+                //    prev = clone(zigAnalysis.decls[prev]);
+                //    prev.id = newDeclId;
+                //    zigAnalysis.decls.push(prev);
+                //    containerObj.pubDecls[declI] = prev;
+                //}
+                //mergeDecls(prev, next, firstTypeObj, typeObj);
+            }
+        }
+        for (var declI = 0; declI < containerObj.pubDecls.length; declI += 1) {
+            var decl = containerObj.pubDecls[declI];
+            if (typeof(decl) === 'object') {
+                containerObj.pubDecls[declI] = containerObj.pubDecls[declI].id;
+            }
+        }
+        return containerObj;
+    }
+
+    function mergeDecls(declObj, nextDeclIndex, firstTypeObj, typeObj) {
+        var nextDeclObj = zigAnalysis.decls[nextDeclIndex];
+        if (declObj.type != null && nextDeclObj.type != null && declObj.type !== nextDeclObj.type) {
+            if (typeof(declObj.type) !== 'object') {
+                var prevType = declObj.type;
+                declObj.type = {};
+                declObj.type[prevType] = firstTypeObj;
+                declObj.value = null;
+            }
+            declObj.type[nextDeclObj.type] = typeObj;
+        } else if (declObj.type == null && nextDeclObj != null) {
+            declObj.type = nextDeclObj.type;
+        }
+        if (declObj.value != null && nextDeclObj.value != null && declObj.value !== nextDeclObj.value) {
+            if (typeof(declObj.value) !== 'object') {
+                var prevValue = declObj.value;
+                declObj.value = {};
+                declObj.value[prevValue] = firstTypeObj;
+            }
+            declObj.value[nextDeclObj.value] = typeObj;
+        } else if (declObj.value == null && nextDeclObj.value != null) {
+            declObj.value = nextDeclObj.value;
+        }
+    }
+
     function renderValue(decl) {
         domFnProtoCode.innerHTML = '<span class="tok-kw">const</span> ' +
             escapeHtml(decl.name) + ': ' + typeIndexName(decl.type, true, true);
@@ -733,13 +920,15 @@
         var fnsList = [];
         var varsList = [];
         var valsList = [];
+
         for (var i = 0; i < container.pubDecls.length; i += 1) {
             var decl = zigAnalysis.decls[container.pubDecls[i]];
+
             if (decl.kind === 'var') {
                 varsList.push(decl);
                 continue;
             } else if (decl.kind === 'const' && decl.type != null) {
-                if (decl.type == typeTypeId) {
+                if (decl.type === typeTypeId) {
                     if (typeIsErrSet(decl.value)) {
                         errSetsList.push(decl);
                     } else if (typeIsStructWithNoFields(decl.value)) {
@@ -838,7 +1027,12 @@
                 if (container.kind === typeKinds.Enum) {
                     html += ' = <span class="tok-number">' + field + '</span>';
                 } else {
-                    html += ": " + typeIndexName(field, true, true);
+                    html += ": ";
+                    if (typeof(field) === 'object') {
+                        html += '<span class="tok-kw">var</span>';
+                    } else {
+                        html += typeIndexName(field, true, true);
+                    }
                 }
 
                 html += ',</pre>';
@@ -1042,13 +1236,16 @@
         return list;
     }
 
-    function declCanRepresentTypeKind(typeKind) {
-        return typeKind === typeKinds.ErrorSet ||
-            typeKind === typeKinds.Struct ||
+    function typeKindIsContainer(typeKind) {
+        return typeKind === typeKinds.Struct ||
             typeKind === typeKinds.Union ||
             typeKind === typeKinds.Enum;
     }
 
+    function declCanRepresentTypeKind(typeKind) {
+        return typeKind === typeKinds.ErrorSet || typeKindIsContainer(typeKind);
+    }
+
     function computeCanonDeclPaths() {
         var list = new Array(zigAnalysis.decls.length);
         canonTypeDecls = new Array(zigAnalysis.types.length);
@@ -1406,4 +1603,18 @@
     function byNameProperty(a, b) {
         return operatorCompare(a.name, b.name);
     }
+
+    function clone(obj) {
+        var res = {};
+        for (var key in obj) {
+            res[key] = obj[key];
+        }
+        return res;
+    }
+
+    function firstObjectKey(obj) {
+        for (var key in obj) {
+            return key;
+        }
+    }
 })();
lib/std/hash_map.zig
@@ -36,7 +36,8 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3
         size: usize,
         max_distance_from_start_index: usize,
         allocator: *Allocator,
-        // this is used to detect bugs where a hashtable is edited while an iterator is running.
+
+        /// This is used to detect bugs where a hashtable is edited while an iterator is running.
         modification_count: debug_u32,
 
         const Self = @This();
lib/std/meta.zig
@@ -542,3 +542,13 @@ pub fn intToEnum(comptime Tag: type, tag_int: var) IntToEnumError!Tag {
     }
     return error.InvalidEnumTag;
 }
+
+/// Given a type and a name, return the field index according to source order.
+/// Returns `null` if the field is not found.
+pub fn fieldIndex(comptime T: type, comptime name: []const u8) ?comptime_int {
+    inline for (fields(T)) |field, i| {
+        if (mem.eql(u8, field.name, name))
+            return comptime_int(i);
+    }
+    return null;
+}
src/dump_analysis.cpp
@@ -456,6 +456,10 @@ static uint32_t anal_dump_get_fn_id(AnalDumpCtx *ctx, ZigFn *fn) {
     auto existing_entry = ctx->fn_map.put_unique(fn, fn_id);
     if (existing_entry == nullptr) {
         ctx->fn_list.append(fn);
+
+        // poke the fn
+        (void)anal_dump_get_type_id(ctx, fn->type_entry);
+        (void)anal_dump_get_node_id(ctx, fn->proto_node);
     } else {
         fn_id = existing_entry->value;
     }
@@ -700,11 +704,7 @@ static void anal_dump_value(AnalDumpCtx *ctx, AstNode *source_node, ZigType *ty,
         case ZigTypeIdFn: {
             if (value->data.x_ptr.special == ConstPtrSpecialFunction) {
                 ZigFn *val_fn = value->data.x_ptr.data.fn.fn_entry;
-                if (val_fn->type_entry->data.fn.is_generic) {
-                    anal_dump_node_ref(ctx, val_fn->proto_node);
-                } else {
-                    anal_dump_fn_ref(ctx, val_fn);
-                }
+                anal_dump_fn_ref(ctx, val_fn);
             } else {
                 jw_null(&ctx->jw);
             }
@@ -758,6 +758,7 @@ static void anal_dump_type(AnalDumpCtx *ctx, ZigType *ty) {
     switch (ty->id) {
         case ZigTypeIdMetaType:
         case ZigTypeIdBool:
+        case ZigTypeIdEnumLiteral:
             break;
         case ZigTypeIdStruct: {
             if (ty->data.structure.is_slice) {
@@ -1072,13 +1073,25 @@ static void anal_dump_node(AnalDumpCtx *ctx, const AstNode *node) {
     jw_object_field(jw, "col");
     jw_int(jw, node->column);
 
-    const Buf *doc_comments_buf;
+    const Buf *doc_comments_buf = nullptr;
+    const Buf *name_buf = nullptr;
+    const ZigList<AstNode *> *field_nodes = nullptr;
+    bool is_var_args = false;
+    bool is_noalias = false;
+    bool is_comptime = false;
+
     switch (node->type) {
         case NodeTypeParamDecl:
             doc_comments_buf = &node->data.param_decl.doc_comments;
+            name_buf = node->data.param_decl.name;
+            is_var_args = node->data.param_decl.is_var_args;
+            is_noalias = node->data.param_decl.is_noalias;
+            is_comptime = node->data.param_decl.is_comptime;
             break;
         case NodeTypeFnProto:
             doc_comments_buf = &node->data.fn_proto.doc_comments;
+            field_nodes = &node->data.fn_proto.params;
+            is_var_args = node->data.fn_proto.is_var_args;
             break;
         case NodeTypeVariableDeclaration:
             doc_comments_buf = &node->data.variable_declaration.doc_comments;
@@ -1088,55 +1101,50 @@ static void anal_dump_node(AnalDumpCtx *ctx, const AstNode *node) {
             break;
         case NodeTypeStructField:
             doc_comments_buf = &node->data.struct_field.doc_comments;
+            name_buf = node->data.struct_field.name;
+            break;
+        case NodeTypeContainerDecl:
+            field_nodes = &node->data.container_decl.fields;
             break;
         default:
-            doc_comments_buf = nullptr;
             break;
     }
+
     if (doc_comments_buf != nullptr && doc_comments_buf->list.length != 0) {
         jw_object_field(jw, "docs");
         jw_string(jw, buf_ptr(doc_comments_buf));
     }
 
-    const Buf *name_buf;
-    switch (node->type) {
-        case NodeTypeStructField:
-            name_buf = node->data.struct_field.name;
-            break;
-        case NodeTypeParamDecl:
-            name_buf = node->data.param_decl.name;
-            break;
-        default:
-            name_buf = nullptr;
-            break;
-    }
     if (name_buf != nullptr) {
         jw_object_field(jw, "name");
         jw_string(jw, buf_ptr(name_buf));
     }
 
-    const ZigList<AstNode *> *fieldNodes;
-    switch (node->type) {
-        case NodeTypeContainerDecl:
-            fieldNodes = &node->data.container_decl.fields;
-            break;
-        case NodeTypeFnProto:
-            fieldNodes = &node->data.fn_proto.params;
-            break;
-        default:
-            fieldNodes = nullptr;
-            break;
-    }
-    if (fieldNodes != nullptr) {
+    if (field_nodes != nullptr) {
         jw_object_field(jw, "fields");
         jw_begin_array(jw);
-        for (size_t i = 0; i < fieldNodes->length; i += 1) {
+        for (size_t i = 0; i < field_nodes->length; i += 1) {
             jw_array_elem(jw);
-            anal_dump_node_ref(ctx, fieldNodes->at(i));
+            anal_dump_node_ref(ctx, field_nodes->at(i));
         }
         jw_end_array(jw);
     }
 
+    if (is_var_args) {
+        jw_object_field(jw, "varArgs");
+        jw_bool(jw, true);
+    }
+
+    if (is_comptime) {
+        jw_object_field(jw, "comptime");
+        jw_bool(jw, true);
+    }
+
+    if (is_noalias) {
+        jw_object_field(jw, "noalias");
+        jw_bool(jw, true);
+    }
+
     jw_end_object(jw);
 }
 
@@ -1235,59 +1243,76 @@ void zig_print_analysis_dump(CodeGen *g, FILE *f, const char *one_indent, const
     jw_object_field(jw, "calls");
     jw_begin_array(jw);
     {
+        ZigList<ZigVar *> var_stack = {};
+
         auto it = g->memoized_fn_eval_table.entry_iterator();
         for (;;) {
             auto *entry = it.next();
             if (!entry)
                 break;
 
-            jw_array_elem(jw);
-            jw_begin_object(jw);
-
-            jw_object_field(jw, "args");
-            jw_begin_object(jw);
+            var_stack.resize(0);
+            ZigFn *fn = nullptr;
 
             Scope *scope = entry->key;
             while (scope != nullptr) {
                 if (scope->id == ScopeIdVarDecl) {
                     ZigVar *var = reinterpret_cast<ScopeVarDecl *>(scope)->var;
-                    jw_object_field(jw, var->name);
-                    jw_begin_object(jw);
-                    jw_object_field(jw, "type");
-                    anal_dump_type_ref(&ctx, var->var_type);
-                    jw_object_field(jw, "value");
-                    anal_dump_value(&ctx, scope->source_node, var->var_type, var->const_value);
-                    jw_end_object(jw);
+                    var_stack.append(var);
                 } else if (scope->id == ScopeIdFnDef) {
-                    jw_end_object(jw);
+                    fn = reinterpret_cast<ScopeFnDef *>(scope)->fn_entry;
+                    break;
+                }
+                scope = scope->parent;
+            }
+            ConstExprValue *result = entry->value;
+
+            assert(fn != nullptr);
+
+            jw_array_elem(jw);
+            jw_begin_object(jw);
+
+            jw_object_field(jw, "fn");
+            anal_dump_fn_ref(&ctx, fn);
+
+            jw_object_field(jw, "result");
+            {
+                jw_begin_object(jw);
+
+                jw_object_field(jw, "type");
+                anal_dump_type_ref(&ctx, result->type);
+
+                jw_object_field(jw, "value");
+                anal_dump_value(&ctx, scope->source_node, result->type, result);
+
+                jw_end_object(jw);
+            }
+
+            if (var_stack.length != 0) {
+                jw_object_field(jw, "args");
+                jw_begin_array(jw);
 
-                    jw_object_field(jw, "fn");
-                    ZigFn *fn = reinterpret_cast<ScopeFnDef *>(scope)->fn_entry;
-                    anal_dump_fn_ref(&ctx, fn);
+                while (var_stack.length != 0) {
+                    ZigVar *var = var_stack.pop();
 
-                    ConstExprValue *result = entry->value;
-                    jw_object_field(jw, "result");
+                    jw_array_elem(jw);
                     jw_begin_object(jw);
+
                     jw_object_field(jw, "type");
-                    anal_dump_type_ref(&ctx, result->type);
+                    anal_dump_type_ref(&ctx, var->var_type);
+
                     jw_object_field(jw, "value");
-                    anal_dump_value(&ctx, scope->source_node, result->type, result);
+                    anal_dump_value(&ctx, scope->source_node, var->var_type, var->const_value);
+
                     jw_end_object(jw);
-                    break;
                 }
-                scope = scope->parent;
+                jw_end_array(jw);
             }
+
             jw_end_object(jw);
         }
-    }
-    jw_end_array(jw);
 
-    jw_object_field(jw, "fns");
-    jw_begin_array(jw);
-    for (uint32_t i = 0; i < ctx.fn_list.length; i += 1) {
-        ZigFn *fn = ctx.fn_list.at(i);
-        jw_array_elem(jw);
-        anal_dump_fn(&ctx, fn);
+        var_stack.deinit();
     }
     jw_end_array(jw);
 
@@ -1315,6 +1340,15 @@ void zig_print_analysis_dump(CodeGen *g, FILE *f, const char *one_indent, const
     }
     jw_end_array(jw);
 
+    jw_object_field(jw, "fns");
+    jw_begin_array(jw);
+    for (uint32_t i = 0; i < ctx.fn_list.length; i += 1) {
+        ZigFn *fn = ctx.fn_list.at(i);
+        jw_array_elem(jw);
+        anal_dump_fn(&ctx, fn);
+    }
+    jw_end_array(jw);
+
     jw_object_field(jw, "errors");
     jw_begin_array(jw);
     for (uint32_t i = 0; i < ctx.err_list.length; i += 1) {
tools/merge_anal_dumps.zig
@@ -2,6 +2,8 @@ const builtin = @import("builtin");
 const std = @import("std");
 const json = std.json;
 const mem = std.mem;
+const fieldIndex = std.meta.fieldIndex;
+const TypeId = builtin.TypeId;
 
 pub fn main() anyerror!void {
     var arena = std.heap.ArenaAllocator.init(std.heap.direct_allocator);
@@ -61,6 +63,81 @@ const Error = struct {
     }
 };
 
+const simple_types = [_][]const u8{
+    "Type",
+    "Void",
+    "Bool",
+    "NoReturn",
+    "ComptimeFloat",
+    "ComptimeInt",
+    "Undefined",
+    "Null",
+    "AnyFrame",
+    "EnumLiteral",
+};
+
+const Type = union(builtin.TypeId) {
+    Type,
+    Void,
+    Bool,
+    NoReturn,
+    ComptimeFloat,
+    ComptimeInt,
+    Undefined,
+    Null,
+    AnyFrame,
+    EnumLiteral,
+
+    Int: Int,
+    Float: usize, // bits
+
+    Vector: Array,
+    Optional: usize, // payload type index
+    Pointer: Pointer,
+    Array: Array,
+
+    Struct, // TODO
+    ErrorUnion, // TODO
+    ErrorSet, // TODO
+    Enum, // TODO
+    Union, // TODO
+    Fn, // TODO
+    BoundFn, // TODO
+    ArgTuple, // TODO
+    Opaque, // TODO
+    Frame, // TODO
+
+    const Int = struct {
+        bits: usize,
+        signed: bool,
+    };
+
+    const Pointer = struct {
+        elem: usize,
+        alignment: usize,
+        is_const: bool,
+        is_volatile: bool,
+        allow_zero: bool,
+        host_int_bytes: usize,
+        bit_offset_in_host: usize,
+    };
+
+    const Array = struct {
+        elem: usize,
+        len: usize,
+    };
+
+    fn hash(t: Type) u32 {
+        var hasher = std.hash.Wyhash.init(0);
+        std.hash.autoHash(&hasher, builtin.TypeId(t));
+        return @truncate(u32, hasher.final());
+    }
+
+    fn eql(a: Type, b: Type) bool {
+        return std.meta.eql(a, b);
+    }
+};
+
 const Dump = struct {
     zig_id: ?[]const u8 = null,
     zig_version: ?[]const u8 = null,
@@ -79,6 +156,10 @@ const Dump = struct {
     error_list: std.ArrayList(Error),
     error_map: ErrorMap,
 
+    const TypeMap = std.HashMap(Type, usize, Type.hash, Type.eql);
+    type_list: std.ArrayList(Type),
+    type_map: TypeMap,
+
     fn init(allocator: *mem.Allocator) Dump {
         return Dump{
             .targets = std.ArrayList([]const u8).init(allocator),
@@ -88,6 +169,8 @@ const Dump = struct {
             .node_map = NodeMap.init(allocator),
             .error_list = std.ArrayList(Error).init(allocator),
             .error_map = ErrorMap.init(allocator),
+            .type_list = std.ArrayList(Type).init(allocator),
+            .type_map = TypeMap.init(allocator),
         };
     }
 
@@ -165,6 +248,66 @@ const Dump = struct {
             }
             try other_error_to_mine.putNoClobber(i, gop.kv.value);
         }
+
+        // Merge types. Now it starts to get advanced.
+        // First we identify all the simple types and merge those.
+        // Example: void, type, noreturn
+        // We can also do integers and floats.
+        const other_types = root.Object.get("types").?.value.Array.toSliceConst();
+        var other_types_to_mine = std.AutoHashMap(usize, usize).init(self.a());
+        for (other_types) |other_type_json, i| {
+            const type_kind = jsonObjInt(other_type_json, "kind");
+            switch (type_kind) {
+                fieldIndex(TypeId, "Int").? => {
+                    var signed: bool = undefined;
+                    var bits: usize = undefined;
+                    if (other_type_json.Object.get("i")) |kv| {
+                        signed = true;
+                        bits = @intCast(usize, kv.value.Integer);
+                    } else if (other_type_json.Object.get("u")) |kv| {
+                        signed = false;
+                        bits = @intCast(usize, kv.value.Integer);
+                    } else {
+                        unreachable;
+                    }
+                    const other_type = Type{
+                        .Int = Type.Int{
+                            .bits = bits,
+                            .signed = signed,
+                        },
+                    };
+                    try self.mergeOtherType(other_type, i, &other_types_to_mine);
+                },
+                fieldIndex(TypeId, "Float").? => {
+                    const other_type = Type{
+                        .Float = jsonObjInt(other_type_json, "bits"),
+                    };
+                    try self.mergeOtherType(other_type, i, &other_types_to_mine);
+                },
+                else => {},
+            }
+
+            inline for (simple_types) |simple_type_name| {
+                if (type_kind == std.meta.fieldIndex(builtin.TypeId, simple_type_name).?) {
+                    const other_type = @unionInit(Type, simple_type_name, {});
+                    try self.mergeOtherType(other_type, i, &other_types_to_mine);
+                }
+            }
+        }
+    }
+
+    fn mergeOtherType(
+        self: *Dump,
+        other_type: Type,
+        other_type_index: usize,
+        other_types_to_mine: *std.AutoHashMap(usize, usize),
+    ) !void {
+        const gop = try self.type_map.getOrPut(other_type);
+        if (!gop.found_existing) {
+            gop.kv.value = self.type_list.len;
+            try self.type_list.append(other_type);
+        }
+        try other_types_to_mine.putNoClobber(other_type_index, gop.kv.value);
     }
 
     fn render(self: *Dump, stream: var) !void {
@@ -204,6 +347,36 @@ const Dump = struct {
 
         try jw.endObject();
 
+        try jw.objectField("types");
+        try jw.beginArray();
+        for (self.type_list.toSliceConst()) |t| {
+            try jw.arrayElem();
+            try jw.beginObject();
+
+            try jw.objectField("kind");
+            try jw.emitNumber(@enumToInt(builtin.TypeId(t)));
+
+            switch (t) {
+                .Int => |int| {
+                    if (int.signed) {
+                        try jw.objectField("i");
+                    } else {
+                        try jw.objectField("u");
+                    }
+                    try jw.emitNumber(int.bits);
+                },
+                .Float => |bits| {
+                    try jw.objectField("bits");
+                    try jw.emitNumber(bits);
+                },
+
+                else => {},
+            }
+
+            try jw.endObject();
+        }
+        try jw.endArray();
+
         try jw.objectField("errors");
         try jw.beginArray();
         for (self.error_list.toSliceConst()) |zig_error| {