Browse Source

odin query

Output .json file containing information about the program
gingerBill 6 years ago
parent
commit
458ec5922e
12 changed files with 1018 additions and 86 deletions
  1. 1 0
      src/build_settings.cpp
  2. 4 1
      src/check_decl.cpp
  3. 39 27
      src/check_expr.cpp
  4. 9 6
      src/check_stmt.cpp
  5. 14 5
      src/checker.cpp
  6. 8 6
      src/common.cpp
  7. 460 9
      src/main.cpp
  8. 361 0
      src/query_data.cpp
  9. 11 10
      src/string.cpp
  10. 1 2
      src/timings.cpp
  11. 103 19
      src/tokenizer.cpp
  12. 7 1
      src/types.cpp

+ 1 - 0
src/build_settings.cpp

@@ -105,6 +105,7 @@ struct BuildContext {
 	bool   ignore_unknown_attributes;
 	bool   no_bounds_check;
 	bool   no_output_files;
+	bool   print_query_data;
 	bool   no_crt;
 	bool   use_lld;
 	bool   vet;

+ 4 - 1
src/check_decl.cpp

@@ -905,6 +905,9 @@ void check_proc_group_decl(CheckerContext *ctx, Entity *pg_entity, DeclInfo *d)
 				continue;
 			}
 
+			begin_error_block();
+			defer (end_error_block());
+
 			ProcTypeOverloadKind kind = are_proc_types_overload_safe(p->type, q->type);
 			switch (kind) {
 			case ProcOverload_Identical:
@@ -938,7 +941,7 @@ void check_proc_group_decl(CheckerContext *ctx, Entity *pg_entity, DeclInfo *d)
 			}
 
 			if (is_invalid) {
-				gb_printf_err("\tprevious procedure at %.*s(%td:%td)\n", LIT(pos.file), pos.line, pos.column);
+				error_line("\tprevious procedure at %.*s(%td:%td)\n", LIT(pos.file), pos.line, pos.column);
 				q->type = t_invalid;
 			}
 		}

+ 39 - 27
src/check_expr.cpp

@@ -2658,47 +2658,53 @@ void convert_to_typed(CheckerContext *c, Operand *operand, Type *target_type) {
 				target_type = t->Union.variants[first_success_index];
 				break;
 			} else if (valid_count > 1) {
+				begin_error_block();
+				defer (end_error_block());
+
 				GB_ASSERT(first_success_index >= 0);
 				operand->mode = Addressing_Invalid;
 				convert_untyped_error(c, operand, target_type);
 
-				gb_printf_err("Ambiguous type conversion to '%s', which variant did you mean:\n\t", type_str);
+				error_line("Ambiguous type conversion to '%s', which variant did you mean:\n\t", type_str);
 				i32 j = 0;
 				for (i32 i = 0; i < valid_count; i++) {
 					ValidIndexAndScore valid = valids[i];
-					if (j > 0 && valid_count > 2) gb_printf_err(", ");
+					if (j > 0 && valid_count > 2) error_line(", ");
 					if (j == valid_count-1) {
-						if (valid_count == 2) gb_printf_err(" ");
-						gb_printf_err("or ");
+						if (valid_count == 2) error_line(" ");
+						error_line("or ");
 					}
 					gbString str = type_to_string(t->Union.variants[valid.index]);
-					gb_printf_err("'%s'", str);
+					error_line("'%s'", str);
 					gb_string_free(str);
 					j++;
 				}
-				gb_printf_err("\n\n");
+				error_line("\n\n");
 
 				return;
 			} else if (is_type_untyped_undef(operand->type) && type_has_undef(target_type)) {
 				target_type = t_untyped_undef;
 			} else if (!is_type_untyped_nil(operand->type) || !type_has_nil(target_type)) {
+				begin_error_block();
+				defer (end_error_block());
+
 				operand->mode = Addressing_Invalid;
 				convert_untyped_error(c, operand, target_type);
 				if (count > 0) {
-					gb_printf_err("'%s' is a union which only excepts the following types:\n", type_str);
-					gb_printf_err("\t");
+					error_line("'%s' is a union which only excepts the following types:\n", type_str);
+					error_line("\t");
 					for (i32 i = 0; i < count; i++) {
 						Type *v = t->Union.variants[i];
-						if (i > 0 && count > 2) gb_printf_err(", ");
+						if (i > 0 && count > 2) error_line(", ");
 						if (i == count-1) {
-							if (count == 2) gb_printf_err(" ");
-							gb_printf_err("or ");
+							if (count == 2) error_line(" ");
+							error_line("or ");
 						}
 						gbString str = type_to_string(v);
-						gb_printf_err("'%s'", str);
+						error_line("'%s'", str);
 						gb_string_free(str);
 					}
-					gb_printf_err("\n\n");
+					error_line("\n\n");
 
 				}
 				return;
@@ -5099,19 +5105,22 @@ CallArgumentData check_call_arguments(CheckerContext *c, Operand *operand, Type
 				}
 			}
 			if (!all_invalid_type) {
+				begin_error_block();
+				defer (end_error_block());
+
 				error(operand->expr, "No procedures or ambiguous call for procedure group '%s' that match with the given arguments", expr_name);
-				gb_printf_err("\tGiven argument types: (");
+				error_line("\tGiven argument types: (");
 				for_array(i, operands) {
 					Operand o = operands[i];
-					if (i > 0) gb_printf_err(", ");
+					if (i > 0) error_line(", ");
 					gbString type = type_to_string(o.type);
 					defer (gb_string_free(type));
-					gb_printf_err("%s", type);
+					error_line("%s", type);
 				}
-				gb_printf_err(")\n");
+				error_line(")\n");
 
 				if (procs.count > 0) {
-					gb_printf_err("Did you mean to use one of the following:\n");
+					error_line("Did you mean to use one of the following:\n");
 				}
 				for_array(i, procs) {
 					Entity *proc = procs[i];
@@ -5138,25 +5147,28 @@ CallArgumentData check_call_arguments(CheckerContext *c, Operand *operand, Type
 					if (proc->kind == Entity_Variable) {
 						sep = ":=";
 					}
-					// gb_printf_err("\t%.*s %s %s at %.*s(%td:%td) with score %lld\n", LIT(name), sep, pt, LIT(pos.file), pos.line, pos.column, cast(long long)valids[i].score);
-					gb_printf_err("\t%.*s%.*s%.*s %s %s at %.*s(%td:%td)\n", LIT(prefix), LIT(prefix_sep), LIT(name), sep, pt, LIT(pos.file), pos.line, pos.column);
+					// error_line("\t%.*s %s %s at %.*s(%td:%td) with score %lld\n", LIT(name), sep, pt, LIT(pos.file), pos.line, pos.column, cast(long long)valids[i].score);
+					error_line("\t%.*s%.*s%.*s %s %s at %.*s(%td:%td)\n", LIT(prefix), LIT(prefix_sep), LIT(name), sep, pt, LIT(pos.file), pos.line, pos.column);
 				}
 				if (procs.count > 0) {
-					gb_printf_err("\n");
+					error_line("\n");
 				}
 			}
 			result_type = t_invalid;
 		} else if (valid_count > 1) {
+			begin_error_block();
+			defer (end_error_block());
+
 			error(operand->expr, "Ambiguous procedure group call '%s' that match with the given arguments", expr_name);
-			gb_printf_err("\tGiven argument types: (");
+			error_line("\tGiven argument types: (");
 			for_array(i, operands) {
 				Operand o = operands[i];
-				if (i > 0) gb_printf_err(", ");
+				if (i > 0) error_line(", ");
 				gbString type = type_to_string(o.type);
 				defer (gb_string_free(type));
-				gb_printf_err("%s", type);
+				error_line("%s", type);
 			}
-			gb_printf_err(")\n");
+			error_line(")\n");
 
 			for (isize i = 0; i < valid_count; i++) {
 				Entity *proc = procs[valids[i].index];
@@ -5174,8 +5186,8 @@ CallArgumentData check_call_arguments(CheckerContext *c, Operand *operand, Type
 				if (proc->kind == Entity_Variable) {
 					sep = ":=";
 				}
-				gb_printf_err("\t%.*s %s %s at %.*s(%td:%td)\n", LIT(name), sep, pt, LIT(pos.file), pos.line, pos.column);
-				// gb_printf_err("\t%.*s %s %s at %.*s(%td:%td) %lld\n", LIT(name), sep, pt, LIT(pos.file), pos.line, pos.column, valids[i].score);
+				error_line("\t%.*s %s %s at %.*s(%td:%td)\n", LIT(name), sep, pt, LIT(pos.file), pos.line, pos.column);
+				// error_line("\t%.*s %s %s at %.*s(%td:%td) %lld\n", LIT(name), sep, pt, LIT(pos.file), pos.line, pos.column, valids[i].score);
 			}
 			result_type = t_invalid;
 		} else {

+ 9 - 6
src/check_stmt.cpp

@@ -809,6 +809,9 @@ void check_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) {
 		}
 
 		if (unhandled.count > 0) {
+			begin_error_block();
+			defer (begin_error_block());
+
 			if (unhandled.count == 1) {
 				error_no_newline(node, "Unhandled switch case: ");
 			} else {
@@ -817,11 +820,11 @@ void check_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) {
 			for_array(i, unhandled) {
 				Entity *f = unhandled[i];
 				if (i > 0)  {
-					gb_printf_err(", ");
+					error_line(", ");
 				}
-				gb_printf_err("%.*s", LIT(f->token.string));
+				error_line("%.*s", LIT(f->token.string));
 			}
-			gb_printf_err("\n");
+			error_line("\n");
 		}
 	}
 }
@@ -1040,13 +1043,13 @@ void check_type_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) {
 			for_array(i, unhandled) {
 				Type *t = unhandled[i];
 				if (i > 0)  {
-					gb_printf_err(", ");
+					error_line(", ");
 				}
 				gbString s = type_to_string(t);
-				gb_printf_err("%s", s);
+				error_line("%s", s);
 				gb_string_free(s);
 			}
-			gb_printf_err("\n");
+			error_line("\n");
 		}
 	}
 }

+ 14 - 5
src/checker.cpp

@@ -826,13 +826,14 @@ void destroy_checker_context(CheckerContext *ctx) {
 	destroy_checker_poly_path(ctx->poly_path);
 }
 
-void init_checker(Checker *c, Parser *parser) {
+bool init_checker(Checker *c, Parser *parser) {
+	c->parser = parser;
+
 	if (global_error_collector.count > 0) {
-		gb_exit(1);
+		return false;
 	}
 	gbAllocator a = heap_allocator();
 
-	c->parser = parser;
 	init_checker_info(&c->info);
 
 	array_init(&c->procs_to_check, a);
@@ -846,6 +847,7 @@ void init_checker(Checker *c, Parser *parser) {
 	c->allocator = heap_allocator();
 
 	c->init_ctx = make_checker_context(c);
+	return true;
 }
 
 void destroy_checker(Checker *c) {
@@ -2189,7 +2191,11 @@ DECL_ATTRIBUTE_PROC(var_decl_attribute) {
 			    model == "localexec") {
 				ac->thread_local_model = model;
 			} else {
-				error(elem, "Invalid thread local model '%.*s'", LIT(model));
+				error(elem, "Invalid thread local model '%.*s'. Valid models:", LIT(model));
+				error_line("\tdefault\n");
+				error_line("\tlocaldynamic\n");
+				error_line("\tinitialexec\n");
+				error_line("\tlocalexec\n");
 			}
 		} else {
 			error(elem, "Expected either no value or a string for '%.*s'", LIT(name));
@@ -2597,10 +2603,13 @@ void check_collect_value_decl(CheckerContext *c, Ast *decl) {
 
 			if (e->kind != Entity_Procedure) {
 				if (fl != nullptr) {
+					begin_error_block();
+					defer (end_error_block());
+
 					AstKind kind = init->kind;
 					error(name, "Only procedures and variables are allowed to be in a foreign block, got %.*s", LIT(ast_strings[kind]));
 					if (kind == Ast_ProcType) {
-						gb_printf_err("\tDid you forget to append '---' to the procedure?\n");
+						error_line("\tDid you forget to append '---' to the procedure?\n");
 					}
 				}
 			}

+ 8 - 6
src/common.cpp

@@ -73,16 +73,18 @@ GB_ALLOCATOR_PROC(heap_allocator_proc) {
 		ptr = _aligned_realloc(old_memory, size, alignment);
 		break;
 	#else
-	case gbAllocation_Alloc:
+	case gbAllocation_Alloc: {
+		isize aligned_size = align_formula_isize(size, alignment);
 		// TODO(bill): Make sure this is aligned correctly
-		ptr = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, align_formula_isize(size, alignment));
-		break;
+		ptr = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, aligned_size);
+	} break;
 	case gbAllocation_Free:
 		HeapFree(GetProcessHeap(), 0, old_memory);
 		break;
-	case gbAllocation_Resize:
-		ptr = HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, old_memory, align_formula_isize(size, alignment));
-		break;
+	case gbAllocation_Resize: {
+		isize aligned_size = align_formula_isize(size, alignment);
+		ptr = HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, old_memory, aligned_size);
+	} break;
 	#endif
 
 #elif defined(GB_SYSTEM_LINUX)

+ 460 - 9
src/main.cpp

@@ -16,6 +16,7 @@
 #include "ir.cpp"
 #include "ir_opt.cpp"
 #include "ir_print.cpp"
+#include "query_data.cpp"
 
 // NOTE(bill): 'name' is used in debugging and profiling modes
 i32 system_exec_command_line_app(char *name, char *fmt, ...) {
@@ -162,6 +163,7 @@ void usage(String argv0) {
 	print_usage_line(1, "build     compile .odin file as executable");
 	print_usage_line(1, "run       compile and run .odin file");
 	print_usage_line(1, "check     parse and type check .odin file");
+	print_usage_line(1, "query     parse, type check, and output a .json file containing information about the program");
 	print_usage_line(1, "docs      generate documentation for a .odin file");
 	print_usage_line(1, "version   print version");
 }
@@ -813,6 +815,437 @@ void remove_temp_files(String output_base) {
 #undef EXT_REMOVE
 }
 
+
+
+int query_data_package_compare(void const *a, void const *b) {
+	AstPackage *x = *cast(AstPackage *const *)a;
+	AstPackage *y = *cast(AstPackage *const *)b;
+
+	if (x == y) {
+		return 0;
+	}
+
+	if (x != nullptr && y != nullptr) {
+		return string_compare(x->name, y->name);
+	} else if (x != nullptr && y == nullptr) {
+		return -1;
+	} else if (x == nullptr && y != nullptr) {
+		return +1;
+	}
+	return 0;
+}
+
+int query_data_definition_compare(void const *a, void const *b) {
+	Entity *x = *cast(Entity *const *)a;
+	Entity *y = *cast(Entity *const *)b;
+
+	if (x == y) {
+		return 0;
+	} else if (x != nullptr && y == nullptr) {
+		return -1;
+	} else if (x == nullptr && y != nullptr) {
+		return +1;
+	}
+
+	if (x->pkg != y->pkg) {
+		i32 res = query_data_package_compare(&x->pkg, &y->pkg);
+		if (res != 0) {
+			return res;
+		}
+	}
+
+	return string_compare(x->token.string, y->token.string);
+}
+
+int entity_name_compare(void const *a, void const *b) {
+	Entity *x = *cast(Entity *const *)a;
+	Entity *y = *cast(Entity *const *)b;
+	if (x == y) {
+		return 0;
+	} else if (x != nullptr && y == nullptr) {
+		return -1;
+	} else if (x == nullptr && y != nullptr) {
+		return +1;
+	}
+	return string_compare(x->token.string, y->token.string);
+}
+
+void generate_and_print_query_data(Checker *c, Timings *timings) {
+	query_value_allocator = heap_allocator();
+
+
+	auto *root = query_value_map();
+
+	if (global_error_collector.errors.count > 0) {
+		auto *errors = query_value_array();
+		root->add("errors", errors);
+		for_array(i, global_error_collector.errors) {
+			String err = string_trim_whitespace(global_error_collector.errors[i]);
+			errors->add(err);
+		}
+
+	}
+
+	{ // Packages
+		auto *packages = query_value_array();
+		root->add("packages", packages);
+
+		auto sorted_packages = array_make<AstPackage *>(query_value_allocator, 0, c->info.packages.entries.count);
+		defer (array_free(&sorted_packages));
+
+		for_array(i, c->info.packages.entries) {
+			AstPackage *pkg = c->info.packages.entries[i].value;
+			if (pkg != nullptr) {
+				array_add(&sorted_packages, pkg);
+			}
+		}
+		gb_sort_array(sorted_packages.data, sorted_packages.count, query_data_package_compare);
+		packages->reserve(sorted_packages.count);
+
+		for_array(i, sorted_packages) {
+			AstPackage *pkg = sorted_packages[i];
+			String name = pkg->name;
+			String fullpath = pkg->fullpath;
+
+			auto *files = query_value_array();
+			files->reserve(pkg->files.count);
+			for_array(j, pkg->files) {
+				AstFile *f = pkg->files[j];
+				files->add(f->fullpath);
+			}
+
+			auto *package = query_value_map();
+			package->reserve(3);
+			packages->add(package);
+
+			package->add("name", pkg->name);
+			package->add("fullpath", pkg->fullpath);
+			package->add("files", files);
+		}
+	}
+
+	if (c->info.definitions.count > 0) {
+		auto *definitions = query_value_array();
+		root->add("definitions", definitions);
+
+		auto sorted_definitions = array_make<Entity *>(query_value_allocator, 0, c->info.definitions.count);
+		defer (array_free(&sorted_definitions));
+
+		for_array(i, c->info.definitions) {
+			Entity *e = c->info.definitions[i];
+			String name = e->token.string;
+			if (is_blank_ident(name)) {
+				continue;
+			}
+			if ((e->scope->flags & (ScopeFlag_Pkg|ScopeFlag_File)) == 0) {
+				continue;
+			}
+			if (e->parent_proc_decl != nullptr) {
+				continue;
+			}
+			switch (e->kind) {
+			case Entity_Builtin:
+			case Entity_Nil:
+			case Entity_Label:
+				continue;
+			}
+			if (e->pkg == nullptr) {
+				continue;
+			}
+			if (e->token.pos.line == 0) {
+				continue;
+			}
+			if (e->kind == Entity_Procedure) {
+				Type *t = base_type(e->type);
+				if (t->kind != Type_Proc) {
+					continue;
+				}
+				if (t->Proc.is_poly_specialized) {
+					continue;
+				}
+			}
+			if (e->kind == Entity_TypeName) {
+				Type *t = base_type(e->type);
+				if (t->kind == Type_Struct) {
+					if (t->Struct.is_poly_specialized) {
+						continue;
+					}
+				}
+				if (t->kind == Type_Union) {
+					if (t->Union.is_poly_specialized) {
+						continue;
+					}
+				}
+			}
+
+			array_add(&sorted_definitions, e);
+		}
+
+		gb_sort_array(sorted_definitions.data, sorted_definitions.count, query_data_definition_compare);
+		definitions->reserve(sorted_definitions.count);
+
+		for_array(i, sorted_definitions) {
+			Entity *e = sorted_definitions[i];
+			String name = e->token.string;
+
+			auto *def = query_value_map();
+			def->reserve(16);
+			definitions->add(def);
+
+			def->add("package",     e->pkg->name);
+			def->add("name",        name);
+			def->add("filepath",    e->token.pos.file);
+			def->add("line",        e->token.pos.line);
+			def->add("column",      e->token.pos.column);
+			def->add("file_offset", e->token.pos.offset);
+
+			switch (e->kind) {
+			case Entity_Constant:    def->add("kind", str_lit("constant"));        break;
+			case Entity_Variable:    def->add("kind", str_lit("variable"));        break;
+			case Entity_TypeName:    def->add("kind", str_lit("type name"));       break;
+			case Entity_Procedure:   def->add("kind", str_lit("procedure"));       break;
+			case Entity_ProcGroup:   def->add("kind", str_lit("procedure group")); break;
+			case Entity_ImportName:  def->add("kind", str_lit("import name"));     break;
+			case Entity_LibraryName: def->add("kind", str_lit("library name"));    break;
+			default: GB_PANIC("Invalid entity kind to be added");
+			}
+
+
+			if (e->type != nullptr && e->type != t_invalid) {
+				Type *t = e->type;
+				Type *bt = t;
+
+				switch (e->kind) {
+				case Entity_TypeName:
+					if (!e->TypeName.is_type_alias) {
+						bt = base_type(t);
+					}
+					break;
+				}
+
+				{
+					gbString str = type_to_string(t);
+					String type_str = make_string(cast(u8 *)str, gb_string_length(str));
+					def->add("type", type_str);
+				}
+				if (t != bt) {
+					gbString str = type_to_string(bt);
+					String type_str = make_string(cast(u8 *)str, gb_string_length(str));
+					def->add("base_type", type_str);
+				}
+				{
+					String type_kind = {};
+					Type *bt = base_type(t);
+					switch (bt->kind) {
+					case Type_Pointer:      type_kind = str_lit("pointer");       break;
+					case Type_Opaque:       type_kind = str_lit("opaque");        break;
+					case Type_Array:        type_kind = str_lit("array");         break;
+					case Type_Slice:        type_kind = str_lit("slice");         break;
+					case Type_DynamicArray: type_kind = str_lit("dynamic array"); break;
+					case Type_Map:          type_kind = str_lit("map");           break;
+					case Type_Struct:       type_kind = str_lit("struct");        break;
+					case Type_Union:        type_kind = str_lit("union");         break;
+					case Type_Enum:         type_kind = str_lit("enum");          break;
+					case Type_Proc:         type_kind = str_lit("procedure");     break;
+					case Type_BitField:     type_kind = str_lit("bit field");     break;
+					case Type_BitSet:       type_kind = str_lit("bit set");       break;
+					case Type_SimdVector:   type_kind = str_lit("simd vector");   break;
+
+					case Type_Generic:
+					case Type_Tuple:
+					case Type_BitFieldValue:
+						GB_PANIC("Invalid definition type");
+						break;
+					}
+					if (type_kind.len > 0) {
+						def->add("type_kind", type_kind);
+					}
+				}
+			}
+
+			if (e->kind == Entity_TypeName) {
+				def->add("size",  type_size_of(e->type));
+				def->add("align", type_align_of(e->type));
+
+
+				if (is_type_struct(e->type)) {
+					auto *data = query_value_map();
+					data->reserve(6);
+
+					def->add("data", data);
+
+					Type *t = base_type(e->type);
+					GB_ASSERT(t->kind == Type_Struct);
+
+					if (t->Struct.is_polymorphic) {
+						data->add("polymorphic", t->Struct.is_polymorphic);
+					}
+					if (t->Struct.is_poly_specialized) {
+						data->add("polymorphic_specialized", t->Struct.is_poly_specialized);
+					}
+					if (t->Struct.is_packed) {
+						data->add("packed", t->Struct.is_packed);
+					}
+					if (t->Struct.is_raw_union) {
+						data->add("raw_union", t->Struct.is_raw_union);
+					}
+
+					auto *fields = query_value_array();
+					data->add("fields", fields);
+					fields->reserve(t->Struct.fields.count);
+					fields->packed = true;
+
+					for_array(j, t->Struct.fields) {
+						Entity *e = t->Struct.fields[j];
+						String name = e->token.string;
+						if (is_blank_ident(name)) {
+							continue;
+						}
+
+						fields->add(name);
+					}
+				} else if (is_type_union(e->type)) {
+					auto *data = query_value_map();
+					data->reserve(4);
+
+					def->add("data", data);
+					Type *t = base_type(e->type);
+					GB_ASSERT(t->kind == Type_Union);
+
+					if (t->Union.is_polymorphic) {
+						data->add("polymorphic", t->Union.is_polymorphic);
+					}
+					if (t->Union.is_poly_specialized) {
+						data->add("polymorphic_specialized", t->Union.is_poly_specialized);
+					}
+
+					auto *variants = query_value_array();
+					variants->reserve(t->Union.variants.count);
+					data->add("variants", variants);
+
+					for_array(j, t->Union.variants) {
+						Type *vt = t->Union.variants[j];
+
+						gbString str = type_to_string(vt);
+						String type_str = make_string(cast(u8 *)str, gb_string_length(str));
+						variants->add(type_str);
+					}
+				}
+			}
+
+			if (e->kind == Entity_Procedure) {
+				Type *t = base_type(e->type);
+				GB_ASSERT(t->kind == Type_Proc);
+
+				bool is_polymorphic = t->Proc.is_polymorphic;
+				bool is_poly_specialized = t->Proc.is_poly_specialized;
+				bool ok = is_polymorphic || is_poly_specialized;
+				if (ok) {
+					auto *data = query_value_map();
+					data->reserve(4);
+
+					def->add("data", data);
+					if (is_polymorphic) {
+						data->add("polymorphic", is_polymorphic);
+					}
+					if (is_poly_specialized) {
+						data->add("polymorphic_specialized", is_poly_specialized);
+					}
+				}
+			}
+
+			if (e->kind == Entity_ProcGroup) {
+				auto *procedures = query_value_array();
+				procedures->reserve(e->ProcGroup.entities.count);
+
+				for_array(j, e->ProcGroup.entities) {
+					Entity *p = e->ProcGroup.entities[j];
+
+					auto *procedure = query_value_map();
+					procedure->reserve(2);
+					procedure->packed = true;
+
+					procedures->add(procedure);
+
+					procedure->add("package", p->pkg->name);
+					procedure->add("name",    p->token.string);
+				}
+				def->add("procedures", procedures);
+			}
+
+			DeclInfo *di = e->decl_info;
+			if (di != nullptr) {
+				if (di->is_using) {
+					def->add("using", query_value_boolean(true));
+				}
+			}
+		}
+	}
+
+	if (build_context.show_timings) {
+		Timings *t = timings;
+		timings__stop_current_section(t);
+		t->total.finish = time_stamp_time_now();
+		isize max_len = gb_min(36, t->total.label.len);
+		for_array(i, t->sections) {
+			TimeStamp ts = t->sections[i];
+			max_len = gb_max(max_len, ts.label.len);
+		}
+		t->total_time_seconds = time_stamp_as_s(t->total, t->freq);
+
+		auto *tims = query_value_map();
+		tims->reserve(8);
+		root->add("timings", tims);
+		tims->add("time_unit", str_lit("s"));
+
+		tims->add(t->total.label, cast(f64)t->total_time_seconds);
+
+
+		Parser *p = c->parser;
+		if (p != nullptr) {
+			isize lines    = p->total_line_count;
+			isize tokens   = p->total_token_count;
+			isize files    = 0;
+			isize packages = p->packages.count;
+			isize total_file_size = 0;
+			for_array(i, p->packages) {
+				files += p->packages[i]->files.count;
+				for_array(j, p->packages[i]->files) {
+					AstFile *file = p->packages[i]->files[j];
+					total_file_size += file->tokenizer.end - file->tokenizer.start;
+				}
+			}
+
+			tims->add("total_lines",     lines);
+			tims->add("total_tokens",    tokens);
+			tims->add("total_files",     files);
+			tims->add("total_packages",  packages);
+			tims->add("total_file_size", total_file_size);
+
+			auto *sections = query_value_map();
+			sections->reserve(t->sections.count);
+			tims->add("sections", sections);
+			for_array(i, t->sections) {
+				TimeStamp ts = t->sections[i];
+				f64 section_time = time_stamp_as_s(ts, t->freq);
+
+				auto *section = query_value_map();
+				section->reserve(2);
+				sections->add(ts.label, section);
+				section->add("time", section_time);
+				section->add("total_fraction", section_time/t->total_time_seconds);
+			}
+		}
+	}
+
+
+	print_query_data_as_json(root, true);
+	gb_printf("\n");
+}
+
+
+
+
 i32 exec_llvm_opt(String output_base) {
 #if defined(GB_SYSTEM_WINDOWS)
 	// For more passes arguments: http://llvm.org/docs/Passes.html
@@ -928,6 +1361,14 @@ int main(int arg_count, char **arg_ptr) {
 		}
 		build_context.no_output_files = true;
 		init_filename = args[2];
+	} else if (command == "query") {
+		if (args.count < 3) {
+			usage(args[0]);
+			return 1;
+		}
+		build_context.no_output_files = true;
+		build_context.print_query_data = true;
+		init_filename = args[2];
 	} else if (command == "docs") {
 		if (args.count < 3) {
 			usage(args[0]);
@@ -988,21 +1429,27 @@ int main(int arg_count, char **arg_ptr) {
 		// generate_documentation(&parser);
 		return 0;
 	}
-
-
 	timings_start_section(&timings, str_lit("type check"));
 
 	Checker checker = {0};
 
-	init_checker(&checker, &parser);
-	defer (destroy_checker(&checker));
+	bool checked_inited = init_checker(&checker, &parser);
+	defer (if (checked_inited) {
+		destroy_checker(&checker);
+	});
+
+	if (checked_inited) {
+		check_parsed_files(&checker);
+	}
 
-	check_parsed_files(&checker);
 
-#if 1
 	if (build_context.no_output_files) {
-		if (build_context.show_timings) {
-			show_timings(&checker, &timings);
+		if (build_context.print_query_data) {
+			generate_and_print_query_data(&checker, &timings);
+		} else {
+			if (build_context.show_timings) {
+				show_timings(&checker, &timings);
+			}
 		}
 
 		if (global_error_collector.count != 0) {
@@ -1012,6 +1459,10 @@ int main(int arg_count, char **arg_ptr) {
 		return 0;
 	}
 
+	if (!checked_inited) {
+		return 1;
+	}
+
 	irGen ir_gen = {0};
 	if (!ir_gen_init(&ir_gen, &checker)) {
 		return 1;
@@ -1296,6 +1747,6 @@ int main(int arg_count, char **arg_ptr) {
 			system_exec_command_line_app("odin run", "\"%.*s\" %.*s", LIT(complete_path), LIT(run_args_string));
 		}
 	#endif
-#endif
+
 	return 0;
 }

+ 361 - 0
src/query_data.cpp

@@ -0,0 +1,361 @@
+struct QueryValue;
+struct QueryValuePair;
+
+gbAllocator query_value_allocator = {};
+
+enum QueryKind {
+	Query_Invalid,
+	Query_String,
+	Query_Boolean,
+	Query_Integer,
+	Query_Float,
+	Query_Array,
+	Query_Map,
+};
+
+struct QueryValuePair {
+	String key;
+	QueryValue *value;
+};
+
+
+struct QueryValue {
+	QueryKind kind;
+	bool packed;
+};
+
+struct QueryValueString : QueryValue {
+	QueryValueString(String const &v) {
+		kind = Query_String;
+		value = v;
+		packed = false;
+	}
+	String value;
+};
+
+struct QueryValueBoolean : QueryValue {
+	QueryValueBoolean(bool v) {
+		kind = Query_Boolean;
+		value = v;
+		packed = false;
+	}
+	bool value;
+};
+
+struct QueryValueInteger : QueryValue {
+	QueryValueInteger(i64 v) {
+		kind = Query_Integer;
+		value = v;
+		packed = false;
+	}
+	i64 value;
+};
+
+struct QueryValueFloat : QueryValue {
+	QueryValueFloat(f64 v) {
+		kind = Query_Float;
+		value = v;
+		packed = false;
+	}
+	f64 value;
+};
+
+struct QueryValueArray : QueryValue {
+	QueryValueArray() {
+		kind = Query_Array;
+		array_init(&value, query_value_allocator);
+		packed = false;
+	}
+	QueryValueArray(Array<QueryValue *> const &v) {
+		kind = Query_Array;
+		value = v;
+		packed = false;
+	}
+	Array<QueryValue *> value;
+
+	void reserve(isize cap) {
+		array_reserve(&value, cap);
+	}
+	void add(QueryValue *v) {
+		array_add(&value, v);
+	}
+	void add(char const *v) {
+		add(make_string_c(cast(char *)v));
+	}
+	void add(String const &v) {
+		auto val = gb_alloc_item(query_value_allocator, QueryValueString);
+		*val = QueryValueString(v);
+		add(val);
+	}
+	void add(bool v) {
+		auto val = gb_alloc_item(query_value_allocator, QueryValueBoolean);
+		*val = QueryValueBoolean(v);
+		add(val);
+	}
+	void add(i64 v) {
+		auto val = gb_alloc_item(query_value_allocator, QueryValueInteger);
+		*val = QueryValueInteger(v);
+		add(val);
+	}
+	void add(f64 v) {
+		auto val = gb_alloc_item(query_value_allocator, QueryValueFloat);
+		*val = QueryValueFloat(v);
+		add(val);
+	}
+};
+
+struct QueryValueMap : QueryValue {
+	QueryValueMap() {
+		kind = Query_Map;
+		array_init(&value, query_value_allocator);
+		packed = false;
+	}
+	QueryValueMap(Array<QueryValuePair> const &v) {
+		kind = Query_Map;
+		value = v;
+		packed = false;
+	}
+	Array<QueryValuePair> value;
+
+
+	void reserve(isize cap) {
+		array_reserve(&value, cap);
+	}
+	void add(char const *k, QueryValue *v) {
+		add(make_string_c(cast(char *)k), v);
+	}
+	void add(String const &k, QueryValue *v) {
+		QueryValuePair kv = {k, v};
+		array_add(&value, kv);
+	}
+
+	void add(char const *k, String const &v) {
+		auto val = gb_alloc_item(query_value_allocator, QueryValueString);
+		*val = QueryValueString(v);
+		add(k, val);
+	}
+	void add(char const *k, char const *v) {
+		add(k, make_string_c(cast(char *)v));
+	}
+	void add(char const *k, bool v) {
+		auto val = gb_alloc_item(query_value_allocator, QueryValueBoolean);
+		*val = QueryValueBoolean(v);
+		add(k, val);
+	}
+	void add(char const *k, i64 v) {
+		auto val = gb_alloc_item(query_value_allocator, QueryValueInteger);
+		*val = QueryValueInteger(v);
+		add(k, val);
+	}
+	void add(char const *k, f64 v) {
+		auto val = gb_alloc_item(query_value_allocator, QueryValueFloat);
+		*val = QueryValueFloat(v);
+		add(k, val);
+	}
+	void add(String const &k, String const &v) {
+		auto val = gb_alloc_item(query_value_allocator, QueryValueString);
+		*val = QueryValueString(v);
+		add(k, val);
+	}
+	void add(String const &k, char const *v) {
+		add(k, make_string_c(cast(char *)v));
+	}
+	void add(String const &k, bool v) {
+		auto val = gb_alloc_item(query_value_allocator, QueryValueBoolean);
+		*val = QueryValueBoolean(v);
+		add(k, val);
+	}
+	void add(String const &k, i64 v) {
+		auto val = gb_alloc_item(query_value_allocator, QueryValueInteger);
+		*val = QueryValueInteger(v);
+		add(k, val);
+	}
+	void add(String const &k, f64 v) {
+		auto val = gb_alloc_item(query_value_allocator, QueryValueFloat);
+		*val = QueryValueFloat(v);
+		add(k, val);
+	}
+};
+
+
+#define DEF_QUERY_PROC(TYPE, VALUETYPE, NAME) TYPE *NAME(VALUETYPE value) { \
+	auto v = gb_alloc_item(query_value_allocator, TYPE); \
+	*v = TYPE(value); \
+	return v; \
+}
+#define DEF_QUERY_PROC0(TYPE, NAME) TYPE *NAME() { \
+	auto v = gb_alloc_item(query_value_allocator, TYPE); \
+	*v = TYPE(); \
+	return v; \
+}
+
+DEF_QUERY_PROC(QueryValueString,  String const &,                query_value_string);
+DEF_QUERY_PROC(QueryValueBoolean, bool,                          query_value_boolean);
+DEF_QUERY_PROC(QueryValueInteger, i64,                           query_value_integer);
+DEF_QUERY_PROC(QueryValueFloat,   f64,                           query_value_float);
+DEF_QUERY_PROC(QueryValueArray,   Array<QueryValue *> const &,   query_value_array);
+DEF_QUERY_PROC(QueryValueMap,     Array<QueryValuePair> const &, query_value_map);
+DEF_QUERY_PROC0(QueryValueArray,  query_value_array);
+DEF_QUERY_PROC0(QueryValueMap,    query_value_map);
+
+isize qprintf(bool format, isize indent, char const *fmt, ...) {
+	if (format) while (indent --> 0) {
+		gb_printf("\t");
+	}
+	va_list va;
+	va_start(va, fmt);
+	isize res = gb_printf_va(fmt, va);
+	va_end(va);
+	return res;
+}
+
+bool qv_valid_char(u8 c) {
+	if (c >= 0x80) {
+		return false;
+	}
+
+	switch (c) {
+	case '\"':
+	case '\n':
+	case '\r':
+	case '\t':
+	case '\v':
+	case '\f':
+		return false;
+	}
+
+	return true;
+}
+
+void print_query_data_as_json(QueryValue *value, bool format = true, isize indent = 0) {
+	if (value == nullptr) {
+		gb_printf("null");
+		return;
+	}
+	switch (value->kind) {
+	case Query_String: {
+		auto v = cast(QueryValueString *)value;
+		String name = v->value;
+		isize extra = 0;
+		for (isize i = 0; i < name.len; i++) {
+			u8 c = name[i];
+			if (!qv_valid_char(c)) {
+				extra += 5;
+			}
+		}
+
+		if (extra == 0) {
+			gb_printf("\"%.*s\"", LIT(name));
+			return;
+		}
+
+		char const hex_table[] = "0123456789ABCDEF";
+		isize buf_len = name.len + extra + 2 + 1;
+
+		gbTempArenaMemory tmp = gb_temp_arena_memory_begin(&string_buffer_arena);
+		defer (gb_temp_arena_memory_end(tmp));
+
+		u8 *buf = gb_alloc_array(string_buffer_allocator, u8, buf_len);
+
+		isize j = 0;
+
+		for (isize i = 0; i < name.len; i++) {
+			u8 c = name[i];
+			if (qv_valid_char(c)) {
+				buf[j+0] = c;
+				j += 1;
+			} else if (c == '"') {
+				buf[j+0] = '\\';
+				buf[j+1] = '\"';
+				j += 2;
+			} else {
+				switch (c) {
+				case '\n': buf[j+0] = '\\'; buf[j+1] = 'n'; j += 2; break;
+				case '\r': buf[j+0] = '\\'; buf[j+1] = 'r'; j += 2; break;
+				case '\t': buf[j+0] = '\\'; buf[j+1] = 't'; j += 2; break;
+				case '\v': buf[j+0] = '\\'; buf[j+1] = 'v'; j += 2; break;
+				case '\f':
+				default:
+					buf[j+0] = '\\';
+					buf[j+1] = hex_table[0];
+					buf[j+2] = hex_table[0];
+					buf[j+3] = hex_table[c >> 4];
+					buf[j+4] = hex_table[c & 0x0f];
+					j += 5;
+					break;
+				}
+			}
+		}
+
+		gb_printf("\"%s\"", buf);
+		return;
+	}
+	case Query_Boolean: {
+		auto v = cast(QueryValueBoolean *)value;
+		if (v->value) {
+			gb_printf("true");
+		} else {
+			gb_printf("false");
+		}
+		return;
+	}
+	case Query_Integer: {
+		auto v = cast(QueryValueInteger *)value;
+		gb_printf("%lld", v->value);
+		return;
+	}
+	case Query_Float: {
+		auto v = cast(QueryValueFloat *)value;
+		gb_printf("%f", v->value);
+		return;
+	}
+	case Query_Array: {
+		auto v = cast(QueryValueArray *)value;
+		if (v->value.count > 0) {
+			bool ff = format && !v->packed;
+			gb_printf("[");
+			if (ff) gb_printf("\n");
+			for_array(i, v->value) {
+				qprintf(ff, indent+1, "");
+				print_query_data_as_json(v->value[i], ff, indent+1);
+				if (i < v->value.count-1) {
+					gb_printf(",");
+					if (!ff && format) {
+						gb_printf(" ");
+					}
+				}
+				if (ff) gb_printf("\n");
+			}
+			qprintf(ff, indent, "]");
+		} else {
+			gb_printf("[]");
+		}
+		return;
+	}
+	case Query_Map: {
+		auto v = cast(QueryValueMap *)value;
+		if (v->value.count > 0) {
+			bool ff = format && !v->packed;
+			gb_printf("{");
+			if (ff) gb_printf("\n");
+			for_array(i, v->value) {
+				auto kv = v->value[i];
+				qprintf(ff, indent+1, "\"%.*s\":", LIT(kv.key));
+				if (format) gb_printf(" ");
+				print_query_data_as_json(kv.value, ff, indent+1);
+				if (i < v->value.count-1) {
+					gb_printf(",");
+					if (!ff && format) {
+						gb_printf(" ");
+					}
+				}
+				if (ff) gb_printf("\n");
+			}
+			qprintf(ff, indent, "}");
+		} else {
+			gb_printf("{}");
+		}
+		return;
+	}
+	}
+}

+ 11 - 10
src/string.cpp

@@ -562,19 +562,20 @@ bool unquote_char(String s, u8 quote, Rune *rune, bool *multiple_bytes, String *
 // 0 == failure
 // 1 == original memory
 // 2 == new allocation
-i32 unquote_string(gbAllocator a, String *s_) {
+i32 unquote_string(gbAllocator a, String *s_, u8 quote=0) {
 	String s = *s_;
 	isize n = s.len;
-	u8 quote;
-	if (n < 2) {
-		return 0;
-	}
-	quote = s[0];
-	if (quote != s[n-1]) {
-		return 0;
+	if (quote == 0) {
+		if (n < 2) {
+			return 0;
+		}
+		quote = s[0];
+		if (quote != s[n-1]) {
+			return 0;
+		}
+		s.text += 1;
+		s.len -= 2;
 	}
-	s.text += 1;
-	s.len -= 2;
 
 	if (quote == '`') {
 		if (string_contains_char(s, '`')) {

+ 1 - 2
src/timings.cpp

@@ -165,8 +165,7 @@ void timings_print_all(Timings *t, TimingUnit unit = TimingUnit_Millisecond) {
 	timings__stop_current_section(t);
 	t->total.finish = time_stamp_time_now();
 
-	max_len = t->total.label.len;
-	max_len = 36;
+	max_len = gb_min(36, t->total.label.len);
 	for_array(i, t->sections) {
 		TimeStamp ts = t->sections[i];
 		max_len = gb_max(max_len, ts.label.len);

+ 103 - 19
src/tokenizer.cpp

@@ -183,13 +183,85 @@ struct ErrorCollector {
 	TokenPos prev;
 	i64     count;
 	i64     warning_count;
+	bool    in_block;
 	gbMutex mutex;
+
+	Array<u8> error_buffer;
+	Array<String> errors;
 };
 
 gb_global ErrorCollector global_error_collector;
 
+#define MAX_ERROR_COLLECTOR_COUNT (36)
+
+
 void init_global_error_collector(void) {
 	gb_mutex_init(&global_error_collector.mutex);
+	array_init(&global_error_collector.errors, heap_allocator());
+	array_init(&global_error_collector.error_buffer, heap_allocator());
+}
+
+
+void begin_error_block(void) {
+	gb_mutex_lock(&global_error_collector.mutex);
+	global_error_collector.in_block = true;
+}
+
+void end_error_block(void) {
+	if (global_error_collector.error_buffer.count > 0) {
+		isize n = global_error_collector.error_buffer.count;
+		u8 *text = gb_alloc_array(heap_allocator(), u8, n+1);
+		gb_memmove(text, global_error_collector.error_buffer.data, n);
+		text[n] = 0;
+		array_add(&global_error_collector.errors, make_string(text, n));
+		global_error_collector.error_buffer.count = 0;
+
+		// gbFile *f = gb_file_get_standard(gbFileStandard_Error);
+		// gb_file_write(f, text, n);
+	}
+
+	global_error_collector.in_block = false;
+	gb_mutex_unlock(&global_error_collector.mutex);
+}
+
+
+#define ERROR_OUT_PROC(name) void name(char *fmt, va_list va)
+typedef ERROR_OUT_PROC(ErrorOutProc);
+
+ERROR_OUT_PROC(default_error_out_va) {
+	gbFile *f = gb_file_get_standard(gbFileStandard_Error);
+
+	char buf[4096] = {};
+	isize len = gb_snprintf_va(buf, gb_size_of(buf), fmt, va);
+	isize n = len-1;
+	if (global_error_collector.in_block) {
+		isize cap = global_error_collector.error_buffer.count + n;
+		array_reserve(&global_error_collector.error_buffer, cap);
+		u8 *data = global_error_collector.error_buffer.data + global_error_collector.error_buffer.count;
+		gb_memmove(data, buf, n);
+		global_error_collector.error_buffer.count += n;
+	} else {
+		gb_mutex_lock(&global_error_collector.mutex);
+		{
+			u8 *text = gb_alloc_array(heap_allocator(), u8, n+1);
+			gb_memmove(text, buf, n);
+			text[n] = 0;
+			array_add(&global_error_collector.errors, make_string(text, n));
+		}
+		gb_mutex_unlock(&global_error_collector.mutex);
+
+	}
+	gb_file_write(f, buf, n);
+}
+
+
+ErrorOutProc *error_out_va = default_error_out_va;
+
+void error_out(char *fmt, ...) {
+	va_list va;
+	va_start(va, fmt);
+	error_out_va(fmt, va);
+	va_end(va);
 }
 
 void warning_va(Token token, char *fmt, va_list va) {
@@ -197,30 +269,29 @@ void warning_va(Token token, char *fmt, va_list va) {
 	global_error_collector.warning_count++;
 	// NOTE(bill): Duplicate error, skip it
 	if (token.pos.line == 0) {
-		gb_printf_err("Error: %s\n", gb_bprintf_va(fmt, va));
+		error_out("Warning: %s\n", gb_bprintf_va(fmt, va));
 	} else if (global_error_collector.prev != token.pos) {
 		global_error_collector.prev = token.pos;
-		gb_printf_err("%.*s(%td:%td) Warning: %s\n",
-		              LIT(token.pos.file), token.pos.line, token.pos.column,
-		              gb_bprintf_va(fmt, va));
+		error_out("%.*s(%td:%td) Warning: %s\n",
+		          LIT(token.pos.file), token.pos.line, token.pos.column,
+		          gb_bprintf_va(fmt, va));
 	}
 
 	gb_mutex_unlock(&global_error_collector.mutex);
 }
 
-#define MAX_ERROR_COLLECTOR_COUNT (36)
 
 void error_va(Token token, char *fmt, va_list va) {
 	gb_mutex_lock(&global_error_collector.mutex);
 	global_error_collector.count++;
 	// NOTE(bill): Duplicate error, skip it
 	if (token.pos.line == 0) {
-		gb_printf_err("Error: %s\n", gb_bprintf_va(fmt, va));
+		error_out("Error: %s\n", gb_bprintf_va(fmt, va));
 	} else if (global_error_collector.prev != token.pos) {
 		global_error_collector.prev = token.pos;
-		gb_printf_err("%.*s(%td:%td) %s\n",
-		              LIT(token.pos.file), token.pos.line, token.pos.column,
-		              gb_bprintf_va(fmt, va));
+		error_out("%.*s(%td:%td) %s\n",
+		          LIT(token.pos.file), token.pos.line, token.pos.column,
+		          gb_bprintf_va(fmt, va));
 	}
 	gb_mutex_unlock(&global_error_collector.mutex);
 	if (global_error_collector.count > MAX_ERROR_COLLECTOR_COUNT) {
@@ -228,17 +299,23 @@ void error_va(Token token, char *fmt, va_list va) {
 	}
 }
 
+void error_line_va(char *fmt, va_list va) {
+	gb_mutex_lock(&global_error_collector.mutex);
+	error_out_va(fmt, va);
+	gb_mutex_unlock(&global_error_collector.mutex);
+}
+
 void error_no_newline_va(Token token, char *fmt, va_list va) {
 	gb_mutex_lock(&global_error_collector.mutex);
 	global_error_collector.count++;
 	// NOTE(bill): Duplicate error, skip it
 	if (token.pos.line == 0) {
-		gb_printf_err("Error: %s", gb_bprintf_va(fmt, va));
+		error_out("Error: %s", gb_bprintf_va(fmt, va));
 	} else if (global_error_collector.prev != token.pos) {
 		global_error_collector.prev = token.pos;
-		gb_printf_err("%.*s(%td:%td) %s",
-		              LIT(token.pos.file), token.pos.line, token.pos.column,
-		              gb_bprintf_va(fmt, va));
+		error_out("%.*s(%td:%td) %s",
+		          LIT(token.pos.file), token.pos.line, token.pos.column,
+		          gb_bprintf_va(fmt, va));
 	}
 	gb_mutex_unlock(&global_error_collector.mutex);
 	if (global_error_collector.count > MAX_ERROR_COLLECTOR_COUNT) {
@@ -253,11 +330,11 @@ void syntax_error_va(Token token, char *fmt, va_list va) {
 	// NOTE(bill): Duplicate error, skip it
 	if (global_error_collector.prev != token.pos) {
 		global_error_collector.prev = token.pos;
-		gb_printf_err("%.*s(%td:%td) Syntax Error: %s\n",
+		error_out("%.*s(%td:%td) Syntax Error: %s\n",
 		              LIT(token.pos.file), token.pos.line, token.pos.column,
 		              gb_bprintf_va(fmt, va));
 	} else if (token.pos.line == 0) {
-		gb_printf_err("Syntax Error: %s\n", gb_bprintf_va(fmt, va));
+		error_out("Syntax Error: %s\n", gb_bprintf_va(fmt, va));
 	}
 
 	gb_mutex_unlock(&global_error_collector.mutex);
@@ -272,11 +349,11 @@ void syntax_warning_va(Token token, char *fmt, va_list va) {
 	// NOTE(bill): Duplicate error, skip it
 	if (global_error_collector.prev != token.pos) {
 		global_error_collector.prev = token.pos;
-		gb_printf_err("%.*s(%td:%td) Syntax Warning: %s\n",
-		              LIT(token.pos.file), token.pos.line, token.pos.column,
-		              gb_bprintf_va(fmt, va));
+		error_out("%.*s(%td:%td) Syntax Warning: %s\n",
+		          LIT(token.pos.file), token.pos.line, token.pos.column,
+		          gb_bprintf_va(fmt, va));
 	} else if (token.pos.line == 0) {
-		gb_printf_err("Warning: %s\n", gb_bprintf_va(fmt, va));
+		error_out("Warning: %s\n", gb_bprintf_va(fmt, va));
 	}
 
 	gb_mutex_unlock(&global_error_collector.mutex);
@@ -307,6 +384,13 @@ void error(TokenPos pos, char *fmt, ...) {
 	va_end(va);
 }
 
+void error_line(char *fmt, ...) {
+	va_list va;
+	va_start(va, fmt);
+	error_line_va(fmt, va);
+	va_end(va);
+}
+
 
 void syntax_error(Token token, char *fmt, ...) {
 	va_list va;

+ 7 - 1
src/types.cpp

@@ -2765,7 +2765,13 @@ gbString write_type_to_string(gbString str, Type *type) {
 
 	case Type_Generic:
 		if (type->Generic.name.len == 0) {
-			str = gb_string_appendc(str, "type");
+			if (type->Generic.entity != nullptr) {
+				String name = type->Generic.entity->token.string;
+				str = gb_string_append_rune(str, '$');
+				str = gb_string_append_length(str, name.text, name.len);
+			} else {
+				str = gb_string_appendc(str, "type");
+			}
 		} else {
 			String name = type->Generic.name;
 			str = gb_string_append_rune(str, '$');