Browse Source

General specialization for polymorphic parameters

Ginger Bill 8 years ago
parent
commit
d16aa79492
6 changed files with 189 additions and 95 deletions
  1. 71 26
      core/_preload.odin
  2. 14 14
      core/math.odin
  3. 1 1
      core/os.odin
  4. 1 1
      core/os_windows.odin
  5. 101 52
      src/check_expr.cpp
  6. 1 1
      src/types.cpp

+ 71 - 26
core/_preload.odin

@@ -305,7 +305,7 @@ resize :: proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAULT_AL
 }
 
 
-copy :: proc(dst, src: []$T) -> int #cc_contextless {
+copy :: proc(dst, src: $T/[]$E) -> int #cc_contextless {
 	n := max(0, min(len(dst), len(src)));
 	if n > 0 do __mem_copy(&dst[0], &src[0], n*size_of(T));
 	return n;
@@ -313,7 +313,7 @@ copy :: proc(dst, src: []$T) -> int #cc_contextless {
 
 
 
-append :: proc(array: ^[]$T, args: ...T) -> int #cc_contextless {
+append :: proc(array: ^$T/[]$E, args: ...E) -> int #cc_contextless {
 	if array == nil do return 0;
 
 	arg_len := len(args);
@@ -322,16 +322,16 @@ append :: proc(array: ^[]$T, args: ...T) -> int #cc_contextless {
 	arg_len = min(cap(array)-len(array), arg_len);
 	if arg_len > 0 {
 		s := cast(^raw.Slice)array;
-		data := cast(^T)s.data;
+		data := cast(^E)s.data;
 		assert(data != nil);
-		sz :: size_of(T);
+		sz :: size_of(E);
 		__mem_copy(data + s.len, &args[0], sz*arg_len);
 		s.len += arg_len;
 	}
 	return len(array);
 }
 
-append :: proc(array: ^[dynamic]$T, args: ...T) -> int {
+append :: proc(array: ^$T/[dynamic]$E, args: ...E) -> int {
 	if array == nil do return 0;
 
 	arg_len := len(args);
@@ -346,37 +346,37 @@ append :: proc(array: ^[dynamic]$T, args: ...T) -> int {
 	// TODO(bill): Better error handling for failed reservation
 	if ok {
 		a := cast(^raw.DynamicArray)array;
-		data := cast(^T)a.data;
+		data := cast(^E)a.data;
 		assert(data != nil);
-		__mem_copy(data + a.len, &args[0], size_of(T) * arg_len);
+		__mem_copy(data + a.len, &args[0], size_of(E) * arg_len);
 		a.len += arg_len;
 	}
 	return len(array);
 }
 
-pop :: proc(array: ^[]$T) -> T #cc_contextless {
-	if array == nil do return T{};
+pop :: proc(array: ^$T/[]$E) -> E #cc_contextless {
+	if array == nil do return E{};
 	assert(len(array) > 0);
 	res := array[len(array)-1];
 	(cast(^raw.Slice)array).len -= 1;
 	return res;
 }
 
-pop :: proc(array: ^[dynamic]$T) -> T #cc_contextless {
-	if array == nil do return T{};
+pop :: proc(array: ^$T/[dynamic]$E) -> E #cc_contextless {
+	if array == nil do return E{};
 	assert(len(array) > 0);
 	res := array[len(array)-1];
 	(cast(^raw.DynamicArray)array).len -= 1;
 	return res;
 }
 
-clear :: proc(slice: ^[]$T) #cc_contextless #inline {
+clear :: proc(slice: ^$T/[]$E) #cc_contextless #inline {
 	if slice != nil do (cast(^raw.Slice)slice).len = 0;
 }
-clear :: proc(array: ^[dynamic]$T) #cc_contextless #inline {
+clear :: proc(array: ^$T/[dynamic]$E) #cc_contextless #inline {
 	if array != nil do (cast(^raw.DynamicArray)array).len = 0;
 }
-clear :: proc(m: ^map[$K]$V) #cc_contextless #inline {
+clear :: proc(m: ^$T/map[$K]$V) #cc_contextless #inline {
 	if m == nil do return;
 	raw_map := cast(^raw.DynamicMap)m;
 	hashes  := cast(^raw.DynamicArray)&raw_map.hashes;
@@ -385,7 +385,7 @@ clear :: proc(m: ^map[$K]$V) #cc_contextless #inline {
 	entries.len = 0;
 }
 
-reserve :: proc(array: ^[dynamic]$T, capacity: int) -> bool {
+reserve :: proc(array: ^$T/[dynamic]$E, capacity: int) -> bool {
 	if array == nil do return false;
 	a := cast(^raw.DynamicArray)array;
 
@@ -396,11 +396,11 @@ reserve :: proc(array: ^[dynamic]$T, capacity: int) -> bool {
 	}
 	assert(a.allocator.procedure != nil);
 
-	old_size  := a.cap * size_of(T);
-	new_size  := capacity * size_of(T);
+	old_size  := a.cap * size_of(E);
+	new_size  := capacity * size_of(E);
 	allocator := a.allocator;
 
-	new_data := allocator.procedure(allocator.data, Allocator.Mode.Resize, new_size, align_of(T), a.data, old_size, 0);
+	new_data := allocator.procedure(allocator.data, Allocator.Mode.Resize, new_size, align_of(E), a.data, old_size, 0);
 	if new_data == nil do return false;
 
 	a.data = new_data;
@@ -409,7 +409,7 @@ reserve :: proc(array: ^[dynamic]$T, capacity: int) -> bool {
 }
 
 
-__get_map_header :: proc(m: ^map[$K]$V) -> __MapHeader #cc_contextless {
+__get_map_header :: proc(m: ^$T/map[$K]$V) -> __MapHeader #cc_contextless {
 	header := __MapHeader{m = cast(^raw.DynamicMap)m};
 	Entry :: struct {
 		key:   __MapKey;
@@ -459,11 +459,11 @@ __get_map_key :: proc(key: $K) -> __MapKey #cc_contextless {
 	return map_key;
 }
 
-reserve :: proc(m: ^map[$K]$V, capacity: int) {
+reserve :: proc(m: ^$T/map[$K]$V, capacity: int) {
 	if m != nil do __dynamic_map_reserve(__get_map_header(m), capacity);
 }
 
-delete :: proc(m: ^map[$K]$V, key: K) {
+delete :: proc(m: ^$T/map[$K]$V, key: K) {
 	if m != nil do __dynamic_map_delete(__get_map_header(m), __get_map_key(key));
 }
 
@@ -480,16 +480,61 @@ new_clone :: proc(data: $T) -> ^T #inline {
 	return ptr;
 }
 
-free :: proc(ptr:   rawptr)      do free_ptr(ptr);
-free :: proc(str:   string)      do free_ptr((cast(^raw.String)&str).data);
-free :: proc(array: [dynamic]$T) do free_ptr((cast(^raw.DynamicArray)&array).data);
-free :: proc(slice: []$T)        do free_ptr((cast(^raw.Slice)&slice).data);
-free :: proc(m:     map[$K]$V) {
+free :: proc(ptr:   rawptr)         do free_ptr(ptr);
+free :: proc(str:   $T/string)      do free_ptr((cast(^raw.String)&str).data);
+free :: proc(array: $T/[dynamic]$E) do free_ptr((cast(^raw.DynamicArray)&array).data);
+free :: proc(slice: $T/[]$E)        do free_ptr((cast(^raw.Slice)&slice).data);
+free :: proc(m:     $T/map[$K]$V) {
 	raw := cast(^raw.DynamicMap)&m;
 	free(raw.hashes);
 	free(raw.entries.data);
 }
 
+// NOTE(bill): This code works but I will prefer having `make` a built-in procedure
+// to have better error messages
+/*
+make :: proc(T: type/[]$E, len: int, using location := #caller_location) -> T {
+	cap := len;
+	__slice_expr_error(fully_pathed_filename, int(line), int(column), 0, len, cap);
+	data := cast(^E)alloc(len * size_of(E), align_of(E));
+	for i in 0..len do (data+i)^ = E{};
+	s := raw.Slice{data = data, len = len, cap = len};
+	return (cast(^T)&s)^;
+}
+make :: proc(T: type/[]$E, len, cap: int, using location := #caller_location) -> T {
+	__slice_expr_error(fully_pathed_filename, int(line), int(column), 0, len, cap);
+	data := cast(^E)alloc(len * size_of(E), align_of(E));
+	for i in 0..len do (data+i)^ = E{};
+	s := raw.Slice{data = data, len = len, cap = len};
+	return (cast(^T)&s)^;
+}
+make :: proc(T: type/[dynamic]$E, len: int = 8, using location := #caller_location) -> T {
+	cap := len;
+	__slice_expr_error(fully_pathed_filename, int(line), int(column), 0, len, cap);
+	data := cast(^E)alloc(cap * size_of(E), align_of(E));
+	for i in 0..len do (data+i)^ = E{};
+	s := raw.DynamicArray{data = data, len = len, cap = cap, allocator = context.allocator};
+	return (cast(^T)&s)^;
+}
+make :: proc(T: type/[dynamic]$E, len, cap: int, using location := #caller_location) -> T {
+	__slice_expr_error(fully_pathed_filename, int(line), int(column), 0, len, cap);
+	data := cast(^E)alloc(cap * size_of(E), align_of(E));
+	for i in 0..len do (data+i)^ = E{};
+	s := raw.DynamicArray{data = data, len = len, cap = cap, allocator = context.allocator};
+	return (cast(^T)&s)^;
+}
+
+make :: proc(T: type/map[$K]$V, cap: int = 16, using location := #caller_location) -> T {
+	if cap < 0 do cap = 16;
+
+	m: T;
+	header := __get_map_header(&m);
+	__dynamic_map_reserve(header, cap);
+	return m;
+}
+*/
+
+
 
 default_resize_align :: proc(old_memory: rawptr, old_size, new_size, alignment: int) -> rawptr {
 	if old_memory == nil do return alloc(new_size, alignment);

+ 14 - 14
core/math.odin

@@ -112,36 +112,36 @@ to_degrees :: proc(radians: f32) -> f32 do return radians * 360 / TAU;
 
 
 
-dot :: proc(a, b: Vec2) -> f32 { c := a*b; return c.x + c.y; }
-dot :: proc(a, b: Vec3) -> f32 { c := a*b; return c.x + c.y + c.z; }
-dot :: proc(a, b: Vec4) -> f32 { c := a*b; return c.x + c.y + c.z + c.w; }
+dot :: proc(a, b: $T/[vector 2]$E) -> E { c := a*b; return c.x + c.y; }
+dot :: proc(a, b: $T/[vector 3]$E) -> E { c := a*b; return c.x + c.y + c.z; }
+dot :: proc(a, b: $T/[vector 4]$E) -> E { c := a*b; return c.x + c.y + c.z + c.w; }
 
-cross :: proc(x, y: Vec3) -> Vec3 {
+cross :: proc(x, y: $T/[vector 3]$E) -> T {
 	a := swizzle(x, 1, 2, 0) * swizzle(y, 2, 0, 1);
 	b := swizzle(x, 2, 0, 1) * swizzle(y, 1, 2, 0);
-	return a - b;
+	return T(a - b);
 }
 
 
-mag :: proc(v: Vec2) -> f32 do return sqrt(dot(v, v));
-mag :: proc(v: Vec3) -> f32 do return sqrt(dot(v, v));
-mag :: proc(v: Vec4) -> f32 do return sqrt(dot(v, v));
+mag :: proc(v: $T/[vector 2]$E) -> E do return sqrt(dot(v, v));
+mag :: proc(v: $T/[vector 3]$E) -> E do return sqrt(dot(v, v));
+mag :: proc(v: $T/[vector 4]$E) -> E do return sqrt(dot(v, v));
 
-norm :: proc(v: Vec2) -> Vec2 do return v / mag(v);
-norm :: proc(v: Vec3) -> Vec3 do return v / mag(v);
-norm :: proc(v: Vec4) -> Vec4 do return v / mag(v);
+norm :: proc(v: $T/[vector 2]$E) -> T do return v / mag(v);
+norm :: proc(v: $T/[vector 3]$E) -> T do return v / mag(v);
+norm :: proc(v: $T/[vector 4]$E) -> T do return v / mag(v);
 
-norm0 :: proc(v: Vec2) -> Vec2 {
+norm0 :: proc(v: $T/[vector 2]$E) -> T {
 	m := mag(v);
 	return m == 0 ? 0 : v/m;
 }
 
-norm0 :: proc(v: Vec3) -> Vec3 {
+norm0 :: proc(v: $T/[vector 3]$E) -> T {
 	m := mag(v);
 	return m == 0 ? 0 : v/m;
 }
 
-norm0 :: proc(v: Vec4) -> Vec4 {
+norm0 :: proc(v: $T/[vector 4]$E) -> T {
 	m := mag(v);
 	return m == 0 ? 0 : v/m;
 }

+ 1 - 1
core/os.odin

@@ -24,7 +24,7 @@ read_entire_file :: proc(name: string) -> ([]u8, bool) {
 		return nil, true;
 	}
 
-	data := make([]u8, length);
+	data := make([]u8, int(length));
 	if data == nil {
 		return nil, false;
 	}

+ 1 - 1
core/os_windows.odin

@@ -306,7 +306,7 @@ _alloc_command_line_arguments :: proc() -> []string {
 
 	arg_count: i32;
 	arg_list_ptr := win32.command_line_to_argv_w(win32.get_command_line_w(), &arg_count);
-	arg_list := make([]string, arg_count);
+	arg_list := make([]string, int(arg_count));
 	for _, i in arg_list do arg_list[i] = alloc_ucs2_to_utf8((arg_list_ptr+i)^);
 	return arg_list;
 }

+ 101 - 52
src/check_expr.cpp

@@ -375,7 +375,7 @@ bool find_or_generate_polymorphic_procedure_from_parameters(Checker *c, Entity *
 	return find_or_generate_polymorphic_procedure(c, base_entity, nullptr, operands, poly_proc_data, false);
 }
 
-bool check_type_specialization_to(Checker *c, Type *type, Type *specialization, bool modify_type = false);
+bool check_type_specialization_to(Checker *c, Type *specialization, Type *type, bool compound, bool modify_type);
 bool is_polymorphic_type_assignable(Checker *c, Type *poly, Type *source, bool compound, bool modify_type);
 
 
@@ -1111,7 +1111,7 @@ void check_struct_type(Checker *c, Type *struct_type, AstNode *node, Array<Opera
 					if (type_expr->TypeType.specialization != nullptr) {
 						AstNode *s = type_expr->TypeType.specialization;
 						specialization = check_type(c, s);
-						if (!is_type_polymorphic_struct(specialization)) {
+						if (false && !is_type_polymorphic_struct(specialization)) {
 							gbString str = type_to_string(specialization);
 							defer (gb_string_free(str));
 							error(s, "Expected a polymorphic record, got %s", str);
@@ -1625,7 +1625,7 @@ void check_bit_field_type(Checker *c, Type *bit_field_type, Type *named_type, As
 }
 
 
-bool check_type_specialization_to(Checker *c, Type *type, Type *specialization, bool modify_type) {
+bool check_type_specialization_to(Checker *c, Type *specialization, Type *type, bool compound, bool modify_type) {
 	if (type == nullptr ||
 	    type == t_invalid) {
 		return true;
@@ -1637,42 +1637,40 @@ bool check_type_specialization_to(Checker *c, Type *type, Type *specialization,
 		return false;
 	}
 	// gb_printf_err("#1 %s %s\n", type_to_string(type), type_to_string(specialization));
-	if (t->kind != Type_Record) {
-		return false;
-	}
-	bool show_stuff = false && modify_type;
-
-	if (show_stuff) gb_printf_err("#1 %s %s\n", type_to_string(type), type_to_string(specialization));
-	if (t->Record.polymorphic_parent == specialization) {
-		return true;
-	}
-	if (show_stuff) gb_printf_err("#2 %s %s\n", type_to_string(t->Record.polymorphic_parent), type_to_string(specialization));
-	if (t->Record.polymorphic_parent == s->Record.polymorphic_parent) {
-		GB_ASSERT(s->Record.polymorphic_params != nullptr);
-		GB_ASSERT(t->Record.polymorphic_params != nullptr);
+	if (t->kind == Type_Record) {
+		if (t->Record.polymorphic_parent == specialization) {
+			return true;
+		}
 
-		if (show_stuff) gb_printf_err("#3 %s -> %s\n", type_to_string(type), type_to_string(specialization));
+		if (t->Record.polymorphic_parent == s->Record.polymorphic_parent) {
+			GB_ASSERT(s->Record.polymorphic_params != nullptr);
+			GB_ASSERT(t->Record.polymorphic_params != nullptr);
 
-		TypeTuple *s_tuple = &s->Record.polymorphic_params->Tuple;
-		TypeTuple *t_tuple = &t->Record.polymorphic_params->Tuple;
-		GB_ASSERT(t_tuple->variable_count == s_tuple->variable_count);
-		for (isize i = 0; i < s_tuple->variable_count; i++) {
-			Entity *s_e = s_tuple->variables[i];
-			Entity *t_e = t_tuple->variables[i];
-			Type *st = s_e->type;
-			Type *tt = t_e->type;
-			if (show_stuff) gb_printf_err("\t@ %s -> %s\n", type_to_string(st), type_to_string(tt));
-			if (show_stuff && base_type(st)->kind == Type_Generic) gb_printf_err("\t$%.*s\n", LIT(base_type(st)->Generic.name));
-			bool ok = is_polymorphic_type_assignable(c, st, tt, true, modify_type);
-			if (show_stuff) gb_printf_err("\t$ %s -> %s\n\n", type_to_string(st), type_to_string(tt));
-		}
+			TypeTuple *s_tuple = &s->Record.polymorphic_params->Tuple;
+			TypeTuple *t_tuple = &t->Record.polymorphic_params->Tuple;
+			GB_ASSERT(t_tuple->variable_count == s_tuple->variable_count);
+			for (isize i = 0; i < s_tuple->variable_count; i++) {
+				Entity *s_e = s_tuple->variables[i];
+				Entity *t_e = t_tuple->variables[i];
+				Type *st = s_e->type;
+				Type *tt = t_e->type;
+				bool ok = is_polymorphic_type_assignable(c, st, tt, true, modify_type);
+			}
 
+			if (modify_type) {
+				// NOTE(bill): This is needed in order to change the actual type but still have the types defined within it
+				gb_memmove(specialization, type, gb_size_of(Type));
+			}
 
-		if (modify_type) {
-			// NOTE(bill): This is needed in order to change the actual type but still have the types defined within it
-			gb_memmove(specialization, type, gb_size_of(Type));
+			return true;
 		}
+	}
 
+	if (specialization->kind == Type_Named &&
+	    type->kind != Type_Named) {
+		return false;
+	}
+	if (is_polymorphic_type_assignable(c, base_type(specialization), base_type(type), compound, modify_type)) {
 		return true;
 	}
 
@@ -1684,11 +1682,11 @@ bool is_polymorphic_type_assignable(Checker *c, Type *poly, Type *source, bool c
 	o.type = source;
 	switch (poly->kind) {
 	case Type_Basic:
-		if (compound) return are_types_identical(source, poly);
+		if (compound) return are_types_identical(poly, source);
 		return check_is_assignable_to(c, &o, poly);
 
 	case Type_Named: {
-		if (check_type_specialization_to(c, source, poly, modify_type)) {
+		if (check_type_specialization_to(c, poly, source, compound, modify_type)) {
 			return true;
 		}
 		if (compound) return are_types_identical(poly, source);
@@ -1698,7 +1696,7 @@ bool is_polymorphic_type_assignable(Checker *c, Type *poly, Type *source, bool c
 	case Type_Generic: {
 		if (poly->Generic.specialized != nullptr) {
 			Type *s = poly->Generic.specialized;
-			if (!check_type_specialization_to(c, source, s, modify_type)) {
+			if (!check_type_specialization_to(c, s, source, compound, modify_type)) {
 				return false;
 			}
 		}
@@ -1867,9 +1865,21 @@ Type *check_get_params(Checker *c, Scope *scope, AstNode *_params, bool *is_vari
 			variable_count += gb_max(f->names.count, 1);
 		}
 	}
+	isize min_variable_count = variable_count;
+	for (isize i = params.count-1; i >= 0; i--) {
+		AstNode *field = params[i];
+		if (field->kind == AstNode_Field) {
+			ast_node(f, Field, field);
+			if (f->default_value == nullptr)  {
+				break;
+			}
+			min_variable_count--;
+		}
+	}
+
 
 	if (operands != nullptr) {
-		GB_ASSERT_MSG(operands->count >= variable_count, "%td vs %td", operands->count, variable_count);
+		GB_ASSERT_MSG(operands->count >= min_variable_count, "%td vs %td", operands->count, variable_count);
 	}
 
 
@@ -2033,14 +2043,19 @@ Type *check_get_params(Checker *c, Scope *scope, AstNode *_params, bool *is_vari
 					}
 					if (is_type_polymorphic_struct(type)) {
 						error(o.expr, "Cannot pass polymorphic struct as a parameter");
+						success = false;
 						type = t_invalid;
 					}
-					if (specialization != nullptr && !check_type_specialization_to(c, type, specialization)) {
-						gbString t = type_to_string(type);
-						gbString s = type_to_string(specialization);
-						error(o.expr, "Cannot convert type `%s` to the specialization `%s`", t, s);
-						gb_string_free(s);
-						gb_string_free(t);
+					bool modify_type = !c->context.no_polymorphic_errors;
+					if (specialization != nullptr && !check_type_specialization_to(c, specialization, type, false, modify_type)) {
+						if (!c->context.no_polymorphic_errors) {
+							gbString t = type_to_string(type);
+							gbString s = type_to_string(specialization);
+							error(o.expr, "Cannot convert type `%s` to the specialization `%s`", t, s);
+							gb_string_free(s);
+							gb_string_free(t);
+						}
+						success = false;
 						type = t_invalid;
 					}
 				}
@@ -2856,7 +2871,7 @@ bool check_type_internal(Checker *c, AstNode *e, Type **type, Type *named_type)
 		if (pt->specialization != nullptr) {
 			AstNode *s = pt->specialization;
 			specific = check_type(c, s);
-			if (!is_type_polymorphic_struct(specific)) {
+			if (false && !is_type_polymorphic_struct(specific)) {
 				gbString str = type_to_string(specific);
 				error(s, "Expected a polymorphic record, got %s", str);
 				gb_string_free(str);
@@ -2986,7 +3001,7 @@ bool check_type_internal(Checker *c, AstNode *e, Type **type, Type *named_type)
 		Type *elem = check_type(c, vt->elem);
 		Type *be = base_type(elem);
 		i64 count = check_array_or_map_count(c, vt->count, false);
-		if (is_type_vector(be) || (!is_type_boolean(be) && !is_type_numeric(be))) {
+		if (is_type_vector(be) || (!is_type_boolean(be) && !is_type_numeric(be) && be->kind != Type_Generic)) {
 			gbString err_str = type_to_string(elem);
 			error(vt->elem, "Vector element type must be numerical or a boolean, got `%s`", err_str);
 			gb_string_free(err_str);
@@ -6313,7 +6328,7 @@ CallArgumentData check_call_arguments(Checker *c, Operand *operand, Type *proc_t
 			gb_sort_array(valids, valid_count, valid_proc_and_score_cmp);
 			i64 best_score = valids[0].score;
 			Entity *best_entity = procs[valids[0].index];
-			for (isize i = 0; i < valid_count; i++) {
+			for (isize i = 1; i < valid_count; i++) {
 				if (best_score > valids[i].score) {
 					valid_count = i;
 					break;
@@ -6345,7 +6360,9 @@ CallArgumentData check_call_arguments(Checker *c, Operand *operand, Type *proc_t
 			for (isize i = 0; i < overload_count; i++) {
 				Entity *proc = procs[i];
 				TokenPos pos = proc->token.pos;
-				Type *t = base_type(proc->type); GB_ASSERT(t->kind == Type_Proc);
+				Type *t = base_type(proc->type);
+				if (t == t_invalid) continue;
+				GB_ASSERT(t->kind == Type_Proc);
 				gbString pt;
 				if (t->Proc.node != NULL) {
 					pt = expr_to_string(t->Proc.node);
@@ -6356,6 +6373,9 @@ CallArgumentData check_call_arguments(Checker *c, Operand *operand, Type *proc_t
 				// gb_printf_err("\t%.*s :: %s at %.*s(%td:%td)\n", LIT(name), pt, LIT(pos.file), pos.line, pos.column);
 				gb_string_free(pt);
 			}
+			if (overload_count > 0) {
+				gb_printf_err("\n");
+			}
 			result_type = t_invalid;
 		} else if (valid_count > 1) {
 			error(operand->expr, "Ambiguous procedure call `%.*s` tha match with the given arguments", LIT(name));
@@ -6878,7 +6898,7 @@ ExprKind check_call_expr(Checker *c, Operand *operand, AstNode *call) {
 }
 
 
-ExprKind check_macro_call_expr(Checker *c, Operand *operand, AstNode *call) {
+ExprKind Ov(Checker *c, Operand *operand, AstNode *call) {
 	GB_ASSERT(call->kind == AstNode_MacroCallExpr);
 	ast_node(mce, MacroCallExpr, call);
 
@@ -7893,7 +7913,7 @@ ExprKind check_expr_base_internal(Checker *c, Operand *o, AstNode *node, Type *t
 	case_end;
 
 	case_ast_node(ce, MacroCallExpr, node);
-		return check_macro_call_expr(c, o, node);
+		return Ov(c, o, node);
 	case_end;
 
 	case_ast_node(de, DerefExpr, node);
@@ -8178,6 +8198,10 @@ gbString write_expr_to_string(gbString str, AstNode *node) {
 	case_ast_node(pt, PolyType, node);
 		str = gb_string_appendc(str, "$");
 		str = write_expr_to_string(str, pt->type);
+		if (pt->specialization != nullptr) {
+			str = gb_string_appendc(str, "/");
+			str = write_expr_to_string(str, pt->specialization);
+		}
 	case_end;
 
 	case_ast_node(pt, PointerType, node);
@@ -8210,6 +8234,13 @@ gbString write_expr_to_string(gbString str, AstNode *node) {
 		str = write_expr_to_string(str, vt->elem);
 	case_end;
 
+	case_ast_node(mt, MapType, node);
+		str = gb_string_appendc(str, "map[");
+		str = write_expr_to_string(str, mt->key);
+		str = gb_string_appendc(str, "]");
+		str = write_expr_to_string(str, mt->value);
+	case_end;
+
 	case_ast_node(f, Field, node);
 		if (f->flags&FieldFlag_using) {
 			str = gb_string_appendc(str, "using ");
@@ -8229,9 +8260,23 @@ gbString write_expr_to_string(gbString str, AstNode *node) {
 			str = write_expr_to_string(str, name);
 		}
 		if (f->names.count > 0) {
-			str = gb_string_appendc(str, ": ");
+			if (f->type == nullptr && f->default_value != nullptr) {
+				str = gb_string_appendc(str, " ");
+			}
+			str = gb_string_appendc(str, ":");
 		}
-		str = write_expr_to_string(str, f->type);
+		if (f->type != nullptr) {
+			str = gb_string_appendc(str, " ");
+			str = write_expr_to_string(str, f->type);
+		}
+		if (f->default_value != nullptr) {
+			if (f->type != nullptr) {
+				str = gb_string_appendc(str, " ");
+			}
+			str = gb_string_appendc(str, "= ");
+			str = write_expr_to_string(str, f->default_value);
+		}
+
 	case_end;
 
 	case_ast_node(f, FieldList, node);
@@ -8297,8 +8342,12 @@ gbString write_expr_to_string(gbString str, AstNode *node) {
 		str = gb_string_appendc(str, ")");
 	case_end;
 
-	case_ast_node(ht, TypeType, node);
+	case_ast_node(tt, TypeType, node);
 		str = gb_string_appendc(str, "type");
+		if (tt->specialization) {
+			str = gb_string_appendc(str, "/");
+			str = write_expr_to_string(str, tt->specialization);
+		}
 	case_end;
 
 	case_ast_node(pt, ProcType, node);

+ 1 - 1
src/types.cpp

@@ -1121,7 +1121,7 @@ bool are_types_identical(Type *x, Type *y) {
 	switch (x->kind) {
 	case Type_Generic:
 		if (y->kind == Type_Generic) {
-			return true; // TODO(bill): Is this correct?
+			return are_types_identical(x->Generic.specialized, y->Generic.specialized);
 		}
 		break;