Implicit parametric polymorphic procedures

This commit is contained in:
Ginger Bill
2017-06-29 20:56:18 +01:00
parent 7e3293fc20
commit 69f7382eec
12 changed files with 271 additions and 91 deletions

View File

@@ -367,7 +367,10 @@ explicit_parametric_polymorphic_procedures :: proc() {
}
main :: proc() {
/*
general_stuff();
foreign_blocks();
default_arguments();
@@ -381,6 +384,7 @@ main :: proc() {
program := "+ + * - /";
accumulator := 0;
*/
for token in program {
match token {

View File

@@ -8,6 +8,8 @@ import (
)
import_load "opengl_constants.odin";
_ := compile_assert(ODIN_OS != "osx");
foreign lib {
Clear :: proc(mask: u32) #link_name "glClear" ---;
ClearColor :: proc(r, g, b, a: f32) #link_name "glClearColor" ---;

View File

@@ -8,7 +8,6 @@ import "strings.odin";
Handle :: i32;
FileTime :: u64;
Errno :: int;
AddressSize :: int;
O_RDONLY :: 0x00000;
@@ -125,9 +124,9 @@ F_OK :: 0; // Test for file existance
foreign libc {
unix_open :: proc(path: ^u8, mode: int) -> Handle #link_name "open" ---;
unix_close :: proc(handle: Handle) #link_name "close" ---;
unix_read :: proc(handle: Handle, buffer: rawptr, count: int) -> AddressSize #link_name "read" ---;
unix_write :: proc(handle: Handle, buffer: rawptr, count: int) -> AddressSize #link_name "write" ---;
unix_lseek :: proc(fs: Handle, offset: AddressSize, whence: int) -> AddressSize #link_name "lseek" ---;
unix_read :: proc(handle: Handle, buffer: rawptr, count: int) -> int #link_name "read" ---;
unix_write :: proc(handle: Handle, buffer: rawptr, count: int) -> int #link_name "write" ---;
unix_lseek :: proc(fs: Handle, offset: int, whence: int) -> int #link_name "lseek" ---;
unix_gettid :: proc() -> u64 #link_name "gettid" ---;
unix_stat :: proc(path: ^u8, stat: ^Stat) -> int #link_name "stat" ---;
unix_access :: proc(path: ^u8, mask: int) -> int #link_name "access" ---;
@@ -168,7 +167,7 @@ close :: proc(fd: Handle) {
unix_close(fd);
}
write :: proc(fd: Handle, data: []u8) -> (AddressSize, Errno) {
write :: proc(fd: Handle, data: []u8) -> (int, Errno) {
assert(fd != -1);
bytes_written := unix_write(fd, &data[0], len(data));
@@ -178,7 +177,7 @@ write :: proc(fd: Handle, data: []u8) -> (AddressSize, Errno) {
return bytes_written, 0;
}
read :: proc(fd: Handle, data: []u8) -> (AddressSize, Errno) {
read :: proc(fd: Handle, data: []u8) -> (int, Errno) {
assert(fd != -1);
bytes_read := unix_read(fd, &data[0], len(data));
@@ -188,7 +187,7 @@ read :: proc(fd: Handle, data: []u8) -> (AddressSize, Errno) {
return bytes_read, 0;
}
seek :: proc(fd: Handle, offset: AddressSize, whence: int) -> (AddressSize, Errno) {
seek :: proc(fd: Handle, offset: int, whence: int) -> (int, Errno) {
assert(fd != -1);
final_offset := unix_lseek(fd, offset, whence);

View File

@@ -266,12 +266,12 @@ String get_fullpath_core(gbAllocator a, String path) {
}
String const ODIN_VERSION = str_lit("0.6.0-dev");
void init_build_context(void) {
BuildContext *bc = &build_context;
bc->ODIN_VENDOR = str_lit("odin");
bc->ODIN_VERSION = str_lit("0.6.0-dev");
bc->ODIN_VERSION = ODIN_VERSION;
bc->ODIN_ROOT = odin_root_dir();
#if defined(GB_SYSTEM_WINDOWS)

View File

@@ -329,7 +329,16 @@ void check_proc_decl(Checker *c, Entity *e, DeclInfo *d) {
check_open_scope(c, pl->type);
defer (check_close_scope(c));
#if 0
if (e->token.string == "sort") {
gb_printf_err("%.*s\n", LIT(e->token.string));
}
#endif
bool prev_allow_polymorphic_types = c->context.allow_polymorphic_types;
c->context.allow_polymorphic_types = true;
check_procedure_type(c, proc_type, pl->type);
c->context.allow_polymorphic_types = prev_allow_polymorphic_types;
bool is_foreign = (pl->tags & ProcTag_foreign) != 0;
bool is_link_name = (pl->tags & ProcTag_link_name) != 0;
@@ -348,11 +357,11 @@ void check_proc_decl(Checker *c, Entity *e, DeclInfo *d) {
error(e->token, "Procedure type of `main` was expected to be `proc()`, got %s", str);
gb_string_free(str);
}
if (proc_type->Proc.calling_convention != ProcCC_Odin &&
proc_type->Proc.calling_convention != ProcCC_Contextless) {
if (pt->calling_convention != ProcCC_Odin &&
pt->calling_convention != ProcCC_Contextless) {
error(e->token, "Procedure `main` cannot have a custom calling convention");
}
proc_type->Proc.calling_convention = ProcCC_Contextless;
pt->calling_convention = ProcCC_Contextless;
}
if (is_inline && is_no_inline) {
@@ -363,7 +372,6 @@ void check_proc_decl(Checker *c, Entity *e, DeclInfo *d) {
error(pl->type, "A foreign procedure cannot have an `export` tag");
}
if (pt->is_polymorphic) {
if (pl->body == NULL) {
error(e->token, "Polymorphic procedures must have a body");
@@ -472,9 +480,15 @@ void check_var_decl(Checker *c, Entity *e, Entity **entities, isize entity_count
}
e->flags |= EntityFlag_Visited;
String context_name = str_lit("variable declaration");
if (type_expr != NULL) {
e->type = check_type(c, type_expr);
}
if (e->type != NULL && is_type_polymorphic(e->type)) {
error(e->token, "Invalid use of a polymorphic type in %.*s", LIT(context_name));
e->type = t_invalid;
}
if (e->Variable.is_foreign) {
@@ -514,7 +528,7 @@ void check_var_decl(Checker *c, Entity *e, Entity **entities, isize entity_count
GB_ASSERT(entities == NULL || entities[0] == e);
Operand operand = {};
check_expr(c, &operand, init_expr);
check_init_variable(c, e, &operand, str_lit("variable declaration"));
check_init_variable(c, e, &operand, context_name);
}
if (type_expr != NULL) {
@@ -527,7 +541,7 @@ void check_var_decl(Checker *c, Entity *e, Entity **entities, isize entity_count
Array<AstNode *> inits;
array_init(&inits, c->allocator, 1);
array_add(&inits, init_expr);
check_init_variables(c, entities, entity_count, inits, str_lit("variable declaration"));
check_init_variables(c, entities, entity_count, inits, context_name);
}
void check_entity_decl(Checker *c, Entity *e, DeclInfo *d, Type *named_type) {

View File

@@ -1073,14 +1073,105 @@ void check_bit_field_type(Checker *c, Type *bit_field_type, Type *named_type, As
}
}
bool is_polymorphic_type_assignable(Checker *c, Type *poly, Type *source, bool compound) {
Operand o = {Addressing_Value};
o.type = source;
switch (poly->kind) {
case Type_Basic:
if (compound) return are_types_identical(poly, source);
return check_is_assignable_to(c, &o, poly);
case Type_Named:
if (compound) return are_types_identical(poly, source);
return check_is_assignable_to(c, &o, poly);
case Type_Generic: {
Type *ds = default_type(source);
gb_memmove(poly, ds, gb_size_of(Type));
return true;
}
case Type_Pointer:
if (source->kind == Type_Pointer) {
return is_polymorphic_type_assignable(c, poly->Pointer.elem, source->Pointer.elem, true);
}
return false;
case Type_Atomic:
if (source->kind == Type_Atomic) {
return is_polymorphic_type_assignable(c, poly->Atomic.elem, source->Atomic.elem, true);
}
return false;
case Type_Array:
if (source->kind == Type_Array &&
poly->Array.count == source->Array.count) {
return is_polymorphic_type_assignable(c, poly->Array.elem, source->Array.elem, true);
}
return false;
case Type_DynamicArray:
if (source->kind == Type_DynamicArray) {
return is_polymorphic_type_assignable(c, poly->DynamicArray.elem, source->DynamicArray.elem, true);
}
return false;
case Type_Vector:
if (source->kind == Type_Vector &&
poly->Vector.count == source->Vector.count) {
return is_polymorphic_type_assignable(c, poly->Vector.elem, source->Vector.elem, true);
}
return false;
case Type_Slice:
if (source->kind == Type_Slice) {
return is_polymorphic_type_assignable(c, poly->Slice.elem, source->Slice.elem, true);
}
return false;
case Type_Record:
if (source->kind == Type_Record) {
// TODO(bill): Polymorphic type assignment
}
return false;
case Type_Tuple:
GB_PANIC("This should never happen");
return false;
case Type_Proc:
if (source->kind == Type_Proc) {
// TODO(bill): Polymorphic type assignment
}
return false;
case Type_Map:
if (source->kind == Type_Map) {
bool key = is_polymorphic_type_assignable(c, poly->Map.key, source->Map.key, true);
bool value = is_polymorphic_type_assignable(c, poly->Map.value, source->Map.value, true);
return key || value;
}
return false;
}
return false;
}
Type *determine_type_from_polymorphic(Checker *c, Type *poly_type, Operand operand) {
if (!is_operand_value(operand)) {
error(operand.expr, "Cannot determine polymorphic type from parameter");
return t_invalid;
}
if (is_polymorphic_type_assignable(c, poly_type, operand.type, false)) {
return poly_type;
}
gbString pts = type_to_string(poly_type);
gbString ots = type_to_string(operand.type);
defer (gb_string_free(pts));
defer (gb_string_free(ots));
error(operand.expr,
"Cannot determine polymorphic type from parameter: `%s` to `%s`\n"
"\tNote: Record and procedure types are not yet supported",
ots, pts);
return t_invalid;
}
Type *check_get_params(Checker *c, Scope *scope, AstNode *_params, bool *is_variadic_, bool *success_, Array<Operand> *operands) {
if (_params == NULL) {
return NULL;
}
bool allow_polymorphic_types = c->context.allow_polymorphic_types;
bool success = true;
ast_node(field_list, FieldList, _params);
Array<AstNode *> params = field_list->list;
@@ -1123,6 +1214,8 @@ Type *check_get_params(Checker *c, Scope *scope, AstNode *_params, bool *is_vari
bool default_is_nil = false;
bool default_is_location = false;
bool is_type_param = false;
bool is_type_polymorphic_type = false;
bool detemine_type_from_operand = false;
if (type_expr == NULL) {
@@ -1157,24 +1250,23 @@ Type *check_get_params(Checker *c, Scope *scope, AstNode *_params, bool *is_vari
if (type_expr->kind == AstNode_HelperType) {
is_type_param = true;
if (operands != NULL) {
Operand o = (*operands)[variable_index];
if (o.mode == Addressing_Type) {
type = o.type;
} else {
error(o.expr, "Expected a type to assign to the type parameter");
type = t_invalid;
success = false;
}
detemine_type_from_operand = true;
type = t_invalid;
} else {
type = make_type_generic(c->allocator, 0, str_lit(""));
}
} else {
type = check_type(c, type_expr);
if (p->flags&FieldFlag_dollar) {
error(type_expr, "`$` is only allowed for polymorphic type parameters at the moment");
type = NULL;
bool prev = c->context.allow_polymorphic_types;
if (operands != NULL) {
c->context.allow_polymorphic_types = true;
}
type = check_type(c, type_expr);
c->context.allow_polymorphic_types = prev;
if (is_type_polymorphic(type)) {
is_type_polymorphic_type = true;
}
}
if (default_value != NULL) {
@@ -1218,12 +1310,7 @@ Type *check_get_params(Checker *c, Scope *scope, AstNode *_params, bool *is_vari
type = t_invalid;
}
if (p->flags&FieldFlag_no_alias) {
if (!is_type_pointer(type)) {
error(params[i], "`#no_alias` can only be applied to fields of pointer type");
p->flags &= ~FieldFlag_no_alias; // Remove the flag
}
}
if (p->flags&FieldFlag_c_vararg) {
if (p->type == NULL ||
p->type->kind != AstNode_Ellipsis) {
@@ -1234,19 +1321,44 @@ Type *check_get_params(Checker *c, Scope *scope, AstNode *_params, bool *is_vari
}
}
for_array(j, p->names) {
AstNode *name = p->names[j];
if (ast_node_expect(name, AstNode_Ident)) {
Entity *param = NULL;
if (is_type_param) {
if (operands != NULL) {
Operand o = (*operands)[variable_index];
if (o.mode == Addressing_Type) {
type = o.type;
} else {
error(o.expr, "Expected a type to assign to the type parameter");
type = t_invalid;
}
}
param = make_entity_type_name(c->allocator, scope, name->Ident.token, type);
param->TypeName.is_type_alias = true;
} else {
if (operands != NULL && is_type_polymorphic_type) {
type = determine_type_from_polymorphic(c, type, (*operands)[variable_index]);
if (type == t_invalid) {
success = false;
}
}
if (p->flags&FieldFlag_no_alias) {
if (!is_type_pointer(type)) {
error(params[i], "`#no_alias` can only be applied to fields of pointer type");
p->flags &= ~FieldFlag_no_alias; // Remove the flag
}
}
param = make_entity_param(c->allocator, scope, name->Ident.token, type,
(p->flags&FieldFlag_using) != 0, false);
param->Variable.default_value = value;
param->Variable.default_is_nil = default_is_nil;
param->Variable.default_is_location = default_is_location;
}
if (p->flags&FieldFlag_no_alias) {
param->flags |= EntityFlag_NoAlias;
@@ -1629,11 +1741,12 @@ bool check_procedure_type(Checker *c, Type *type, AstNode *proc_type_node, Array
if (e->kind != Entity_Variable) {
is_polymorphic = true;
break;
} else if (is_type_polymorphic(e->type)) {
is_polymorphic = true;
break;
}
}
if (operands == NULL) {
// GB_ASSERT(type->Proc.is_polymorphic == is_polymorphic);
}
type->Proc.is_polymorphic = is_polymorphic;
type->Proc.abi_compat_params = gb_alloc_array(c->allocator, Type *, param_count);
@@ -2005,6 +2118,26 @@ bool check_type_internal(Checker *c, AstNode *e, Type **type, Type *named_type)
}
case_end;
case_ast_node(pt, PolyType, e);
AstNode *ident = pt->type;
if (ident->kind != AstNode_Ident) {
error(ident, "Expected an identifier after the $");
*type = t_invalid;
return false;
}
Token token = ident->Ident.token;
Type *t = make_type_generic(c->allocator, 0, token.string);
if (c->context.allow_polymorphic_types) {
Scope *s = c->context.scope;
Entity *e = make_entity_type_name(c->allocator, s, token, t);
e->TypeName.is_type_alias = true;
add_entity(c, s, ident, e);
}
*type = t;
return true;
case_end;
case_ast_node(se, SelectorExpr, e);
Operand o = {};
check_selector(c, &o, e, NULL);
@@ -2224,13 +2357,14 @@ Type *check_type(Checker *c, AstNode *e, Type *named_type) {
}
}
// if (is_type_polymorphic(type)) {
if (is_type_poly_proc(type)) {
#if 0
if (!c->context.allow_polymorphic_types && is_type_polymorphic(type)) {
gbString str = type_to_string(type);
error(e, "Invalid use of a polymorphic type `%s`", str);
gb_string_free(str);
type = t_invalid;
}
#endif
if (is_type_typed(type)) {
add_type_and_value(&c->info, e, Addressing_Type, type, empty_exact_value);

View File

@@ -1717,6 +1717,9 @@ void check_stmt_internal(Checker *c, AstNode *node, u32 flags) {
init_type = check_type(c, vd->type, NULL);
if (init_type == NULL) {
init_type = t_invalid;
} else if (is_type_polymorphic(init_type)) {
error(vd->type, "Invalid use of a polymorphic type in variable declaration");
init_type = t_invalid;
}
}

View File

@@ -261,6 +261,7 @@ struct CheckerContext {
DeclInfo * decl;
u32 stmt_state_flags;
bool in_defer; // TODO(bill): Actually handle correctly
bool allow_polymorphic_types;
String proc_name;
Type * type_hint;
DeclInfo * curr_proc_decl;

View File

@@ -375,7 +375,7 @@ int main(int arg_count, char **arg_ptr) {
return 1;
#endif
} else if (args[1] == "version") {
gb_printf("%.*s version %.*s\n", LIT(args[0]), LIT(build_context.ODIN_VERSION));
gb_printf("%.*s version %.*s\n", LIT(args[0]), LIT(ODIN_VERSION));
return 0;
} else {
usage(args[0]);

View File

@@ -632,6 +632,10 @@ AstNode *clone_ast_node(gbAllocator a, AstNode *node) {
case AstNode_Undef: break;
case AstNode_BasicLit: break;
case AstNode_BasicDirective: break;
case AstNode_PolyType:
n->PolyType.type = clone_ast_node(a, n->PolyType.type);
break;
case AstNode_Ellipsis:
n->Ellipsis.expr = clone_ast_node(a, n->Ellipsis.expr);
break;
@@ -3259,8 +3263,10 @@ u32 check_field_prefixes(AstFile *f, isize name_count, u32 allowed_flags, u32 se
syntax_error(f->curr_token, "`#c_vararg` is not allowed within this field list");
set_flags &= ~FieldFlag_c_vararg;
}
if ((allowed_flags&FieldFlag_dollar) == 0 && (set_flags&FieldFlag_dollar)) {
syntax_error(f->curr_token, "`$` is only allowed within procedures");
// if ((allowed_flags&FieldFlag_dollar) == 0 && (set_flags&FieldFlag_dollar)) {
if ((set_flags&FieldFlag_dollar)) {
// syntax_error(f->curr_token, "`$` is only allowed within procedures");
syntax_error(f->curr_token, "`$` is not yet supported");
set_flags &= ~FieldFlag_dollar;
}
return set_flags;

View File

@@ -36,27 +36,27 @@ TOKEN_KIND(Token__OperatorBegin, "_OperatorBegin"), \
TOKEN_KIND(Token_CmpAnd, "&&"), \
TOKEN_KIND(Token_CmpOr, "||"), \
\
TOKEN_KIND(Token__AssignOpBegin, "_AssignOpBegin"), \
TOKEN_KIND(Token_AddEq, "+="), \
TOKEN_KIND(Token_SubEq, "-="), \
TOKEN_KIND(Token_MulEq, "*="), \
TOKEN_KIND(Token_QuoEq, "/="), \
TOKEN_KIND(Token_ModEq, "%="), \
TOKEN_KIND(Token_ModModEq, "%%="), \
TOKEN_KIND(Token_AndEq, "&="), \
TOKEN_KIND(Token_OrEq, "|="), \
TOKEN_KIND(Token_XorEq, "~="), \
TOKEN_KIND(Token_AndNotEq, "&~="), \
TOKEN_KIND(Token_ShlEq, "<<="), \
TOKEN_KIND(Token_ShrEq, ">>="), \
TOKEN_KIND(Token_CmpAndEq, "&&="), \
TOKEN_KIND(Token_CmpOrEq, "||="), \
TOKEN_KIND(Token__AssignOpEnd, "_AssignOpEnd"), \
TOKEN_KIND(Token_ArrowRight, "->"), \
TOKEN_KIND(Token_ArrowLeft, "<-"), \
TOKEN_KIND(Token_Inc, "++"), \
TOKEN_KIND(Token_Dec, "--"), \
TOKEN_KIND(Token_Undef, "---"), \
TOKEN_KIND(Token__AssignOpBegin, "_AssignOpBegin"), \
TOKEN_KIND(Token_AddEq, "+="), \
TOKEN_KIND(Token_SubEq, "-="), \
TOKEN_KIND(Token_MulEq, "*="), \
TOKEN_KIND(Token_QuoEq, "/="), \
TOKEN_KIND(Token_ModEq, "%="), \
TOKEN_KIND(Token_ModModEq, "%%="), \
TOKEN_KIND(Token_AndEq, "&="), \
TOKEN_KIND(Token_OrEq, "|="), \
TOKEN_KIND(Token_XorEq, "~="), \
TOKEN_KIND(Token_AndNotEq, "&~="), \
TOKEN_KIND(Token_ShlEq, "<<="), \
TOKEN_KIND(Token_ShrEq, ">>="), \
TOKEN_KIND(Token_CmpAndEq, "&&="), \
TOKEN_KIND(Token_CmpOrEq, "||="), \
TOKEN_KIND(Token__AssignOpEnd, "_AssignOpEnd"), \
TOKEN_KIND(Token_ArrowRight, "->"), \
TOKEN_KIND(Token_ThickArrowRight, "=>"), \
TOKEN_KIND(Token_Inc, "++"), \
TOKEN_KIND(Token_Dec, "--"), \
TOKEN_KIND(Token_Undef, "---"), \
\
TOKEN_KIND(Token__ComparisonBegin, "_ComparisonBegin"), \
TOKEN_KIND(Token_CmpEq, "=="), \
@@ -910,7 +910,16 @@ Token tokenizer_get_token(Tokenizer *t) {
case '%': token.kind = token_kind_dub_eq(t, '%', Token_Mod, Token_ModEq, Token_ModMod, Token_ModModEq); break;
case '*': token.kind = token_kind_variant2(t, Token_Mul, Token_MulEq); break;
case '=': token.kind = token_kind_variant2(t, Token_Eq, Token_CmpEq); break;
case '=':
token.kind = Token_Eq;
if (t->curr_rune == '>') {
advance_to_next_rune(t);
token.kind = Token_ThickArrowRight;
} else if (t->curr_rune == '=') {
advance_to_next_rune(t);
token.kind = Token_CmpEq;
}
break;
case '~': token.kind = token_kind_variant2(t, Token_Xor, Token_XorEq); break;
case '!': token.kind = token_kind_variant2(t, Token_Not, Token_NotEq); break;
case '+': token.kind = token_kind_variant3(t, Token_Add, Token_AddEq, '+', Token_Inc); break;
@@ -967,11 +976,7 @@ Token tokenizer_get_token(Tokenizer *t) {
} break;
case '<':
if (t->curr_rune == '-') {
token.kind = Token_ArrowLeft;
} else {
token.kind = token_kind_dub_eq(t, '<', Token_Lt, Token_LtEq, Token_Shl, Token_ShlEq);
}
token.kind = token_kind_dub_eq(t, '<', Token_Lt, Token_LtEq, Token_Shl, Token_ShlEq);
break;
case '>': token.kind = token_kind_dub_eq(t, '>', Token_Gt, Token_GtEq, Token_Shr, Token_ShrEq); break;

View File

@@ -944,7 +944,6 @@ bool is_type_indexable(Type *t) {
}
bool is_type_polymorphic(Type *t) {
t = core_type(t);
switch (t->kind) {
case Type_Generic:
return true;
@@ -974,29 +973,36 @@ bool is_type_polymorphic(Type *t) {
if (t->Proc.is_polymorphic) {
return true;
}
// if (t->Proc.param_count > 0 &&
// is_type_polymorphic(t->Proc.params)) {
// return true;
// }
// if (t->Proc.result_count > 0 &&
// is_type_polymorphic(t->Proc.results)) {
// return true;
// }
#if 0
if (t->Proc.param_count > 0 &&
is_type_polymorphic(t->Proc.params)) {
return true;
}
if (t->Proc.result_count > 0 &&
is_type_polymorphic(t->Proc.results)) {
return true;
}
#endif
break;
// case Type_Record:
// GB_ASSERT(t->Record.kind != TypeRecord_Enum);
// for (isize i = 0; i < t->Record.field_count; i++) {
// if (is_type_polymorphic(t->Record.fields[i]->type)) {
// return true;
// }
// }
// for (isize i = 1; i < t->Record.variant_count; i++) {
// if (is_type_polymorphic(t->Record.variants[i]->type)) {
// return true;
// }
// }
// break;
case Type_Record:
if (t->Record.kind == TypeRecord_Enum) {
if (t->Record.enum_base_type != NULL) {
return is_type_polymorphic(t->Record.enum_base_type);
}
return false;
}
for (isize i = 0; i < t->Record.field_count; i++) {
if (is_type_polymorphic(t->Record.fields[i]->type)) {
return true;
}
}
for (isize i = 1; i < t->Record.variant_count; i++) {
if (is_type_polymorphic(t->Record.variants[i]->type)) {
return true;
}
}
break;
case Type_Map:
if (is_type_polymorphic(t->Map.key)) {
@@ -2263,7 +2269,13 @@ gbString write_type_to_string(gbString str, Type *type) {
break;
case Type_Generic:
str = gb_string_appendc(str, "type");
if (type->Generic.name.len == 0) {
str = gb_string_appendc(str, "type");
} else {
String name = type->Generic.name;
str = gb_string_appendc(str, "$");
str = gb_string_append_length(str, name.text, name.len);
}
break;
case Type_Pointer: