Browse Source

Polymorphic type specialization for procedures

Ginger Bill 8 years ago
parent
commit
9a8759efef
8 changed files with 307 additions and 89 deletions
  1. 1 1
      LICENSE
  2. 156 8
      code/demo.odin
  3. 10 8
      src/check_decl.cpp
  4. 95 52
      src/check_expr.cpp
  5. 13 6
      src/check_stmt.cpp
  6. 20 9
      src/ir.cpp
  7. 3 0
      src/parser.cpp
  8. 9 5
      src/types.cpp

+ 1 - 1
LICENSE

@@ -1,4 +1,4 @@
-Copyright (c) 2016 Ginger Bill. All rights reserved.
+Copyright (c) 2016-2017 Ginger Bill. All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are met:

+ 156 - 8
code/demo.odin

@@ -1,5 +1,116 @@
 import "fmt.odin";
 
+Table :: struct(Key, Value: type) {
+	Slot :: struct {
+		occupied: bool;
+		hash:     u32;
+		key:      Key;
+		value:    Value;
+	}
+	SIZE_MIN :: 32;
+
+	count:           int;
+	allocator:       Allocator;
+	slots:           []Slot;
+}
+
+allocate :: proc(table: ^$T/Table, capacity: int) {
+	c := context;
+	if table.allocator.procedure != nil do c.allocator = table.allocator;
+
+	push_context c {
+		table.slots = make([]T.Slot, max(capacity, T.SIZE_MIN));
+	}
+}
+
+expand :: proc(table: ^$T/Table) {
+	c := context;
+	if table.allocator.procedure != nil do c.allocator = table.allocator;
+
+	push_context c {
+		old_slots := table.slots;
+
+		cap := max(2*cap(table.slots), T.SIZE_MIN);
+		allocate(table, cap);
+
+		for s in old_slots do if s.occupied {
+			put(table, s.key, s.value);
+		}
+
+		free(old_slots);
+	}
+}
+
+put :: proc(table: ^$T/Table, key: T.Key, value: T.Value) {
+// put :: proc(table: ^Table($K, $V), key: K, value: V) {
+	hash := get_hash(key); // Ad-hoc method which would fail in differentcope
+	index := find_index(table, key, hash);
+	if index < 0 {
+		if f64(table.count) >= 0.75*cast(f64)cap(table.slots) {
+			expand(table);
+		}
+		assert(table.count <= cap(table.slots));
+
+		hash := get_hash(key);
+		index = cast(int)(hash % cast(u32)cap(table.slots));
+
+		for table.slots[index].occupied {
+			index += 1;
+			if index >= cap(table.slots) {
+				index = 0;
+			}
+		}
+
+		table.count++;
+	}
+
+	slot := &table.slots[index];
+	slot.occupied = true;
+	slot.hash     = hash;
+	slot.key      = key;
+	slot.value    = value;
+}
+
+
+// find :: proc(table: ^$T/Table, key: T.Key) -> (T.Value, bool) {
+find :: proc(table: ^Table($Key, $Value), key: Key) -> (Value, bool) {
+	hash := get_hash(key);
+	index := find_index(table, key, hash);
+	if index < 0 {
+		return Value{}, false;
+	}
+	return table.slots[index].value, true;
+}
+
+find_index :: proc(table: ^Table($Key, $Value), key: Key, hash: u32) -> int {
+	if cap(table.slots) <= 0 do return -1;
+
+	slot := int(hash % cast(u32)cap(table.slots));
+
+	index := slot;
+	for table.slots[index].occupied {
+		if table.slots[index].hash == hash {
+			if table.slots[index].key == key {
+				return index;
+			}
+		}
+
+		index++;
+		if index >= cap(table.slots) do index = 0;
+	}
+
+	return -1;
+}
+
+get_hash :: proc(s: string) -> u32 {
+	// djb2
+	hash: u32 = 5381;
+	for i in 0..len(s) do hash = (hash<<5) + hash + u32(s[i]);
+	return hash;
+}
+
+
+
 Vector :: struct(N: int, T: type) {
 	using _: raw_union {
 		using e: [N]T;
@@ -16,14 +127,51 @@ Vector :: struct(N: int, T: type) {
 
 Vector3 :: Vector(3, f32);
 
+add :: proc(a, b: $T/Vector) -> T {
+	c := a;
+	for i in 0..3 {
+		c[i] += b[i];
+	}
+	return c;
+}
+
+foo1 :: proc(a: type/Vector)         { fmt.println("foo1", a{}); }
+// foo2 :: proc(a: type/Vector(3, f32)) {}
+foo3 :: proc(a: type/Vector(3, $T))  {fmt.println("foo3", a{}); }
+// foo4 :: proc(a: type/Vector3)        {}
+
+
+
 main :: proc() {
-	v: Vector3;
-	v[0] = 1;
-	v[1] = 4;
-	v[2] = 9;
-	fmt.println(v.e);
-	v.x = 4;
-	v.y = 9;
-	v.z = 16;
+	foo1(Vector(3, f32));
+	foo1(Vector3);
+	foo3(Vector(3, f32));
+	foo3(Vector3);
+
+
+	a, b: Vector3;
+	a[0] = 1;
+	a[1] = 4;
+	a[2] = 9;
+
+	b.x = 3;
+	b.y = 4;
+	b.z = 5;
+
+	v := add(a, b);
 	fmt.println(v.v);
+
+
+	table: Table(string, int);
+
+	for i in 0..36 do put(&table, "Hellope", i);
+	for i in 0..42 do put(&table, "World!",  i);
+
+
+	found, _ := find(&table, "Hellope");
+	fmt.printf("found is %v\n", found);
+
+	found, _ = find(&table, "World!");
+	fmt.printf("found is %v\n", found);
+
 }

+ 10 - 8
src/check_decl.cpp

@@ -60,7 +60,9 @@ Type *check_init_variable(Checker *c, Entity *e, Operand *operand, String contex
 			t = default_type(t);
 		}
 		if (is_type_polymorphic(t)) {
-			error(e->token, "Invalid use of a polymorphic type in %.*s", LIT(context_name));
+			gbString str = type_to_string(t);
+			defer (gb_string_free(str));
+			error(e->token, "Invalid use of a polymorphic type `%s` in %.*s", str, LIT(context_name));
 			e->type = t_invalid;
 			return nullptr;
 		}
@@ -400,17 +402,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
+
+
 
 	auto prev_context = c->context;
 	c->context.allow_polymorphic_types = true;
 	check_procedure_type(c, proc_type, pl->type);
 	c->context = prev_context;
 
+	TypeProc *pt = &proc_type->Proc;
+
 	bool is_foreign         = (pl->tags & ProcTag_foreign)   != 0;
 	bool is_link_name       = (pl->tags & ProcTag_link_name) != 0;
 	bool is_export          = (pl->tags & ProcTag_export)    != 0;
@@ -419,7 +420,6 @@ void check_proc_decl(Checker *c, Entity *e, DeclInfo *d) {
 	bool is_require_results = (pl->tags & ProcTag_require_results) != 0;
 
 
-	TypeProc *pt = &proc_type->Proc;
 
 	if (d->scope->is_file && e->token.string == "main") {
 		if (pt->param_count != 0 ||
@@ -559,7 +559,9 @@ void check_var_decl(Checker *c, Entity *e, Entity **entities, isize entity_count
 		e->type = check_type(c, type_expr);
 	}
 	if (e->type != nullptr && is_type_polymorphic(base_type(e->type))) {
-		error(e->token, "Invalid use of a polymorphic type in %.*s", LIT(context_name));
+		gbString str = type_to_string(e->type);
+		defer (gb_string_free(str));
+		error(e->token, "Invalid use of a polymorphic type `%s` in %.*s", str, LIT(context_name));
 		e->type = t_invalid;
 	}
 

+ 95 - 52
src/check_expr.cpp

@@ -156,6 +156,7 @@ bool find_or_generate_polymorphic_procedure(Checker *c, Entity *base_entity, Typ
 	//                                                                           //
 	///////////////////////////////////////////////////////////////////////////////
 
+
 	if (base_entity == nullptr) {
 		return false;
 	}
@@ -163,6 +164,7 @@ bool find_or_generate_polymorphic_procedure(Checker *c, Entity *base_entity, Typ
 	if (!is_type_proc(base_entity->type)) {
 		return false;
 	}
+	String name = base_entity->token.string;
 
 	Type *src = base_type(base_entity->type);
 	Type *dst = nullptr;
@@ -372,6 +374,9 @@ 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 is_polymorphic_type_assignable(Checker *c, Type *poly, Type *source, bool compound, bool modify_type);
+
 
 i64 check_distance_between_types(Checker *c, Operand *operand, Type *type) {
 	if (operand->mode == Addressing_Invalid ||
@@ -467,10 +472,11 @@ i64 check_distance_between_types(Checker *c, Operand *operand, Type *type) {
 		}
 	}
 
+#if 0
 	if (are_types_identical(dst, src) && (!is_type_named(dst) || !is_type_named(src))) {
 		return 1;
 	}
-
+#endif
 
 	if (is_type_bit_field_value(operand->type) && is_type_integer(type)) {
 		Type *bfv = base_type(operand->type);
@@ -504,6 +510,13 @@ i64 check_distance_between_types(Checker *c, Operand *operand, Type *type) {
 	}
 #endif
 
+	if (is_type_polymorphic(dst) && !is_type_polymorphic(src)) {
+		bool modify_type = !c->context.no_polymorphic_errors;
+		if (is_polymorphic_type_assignable(c, type, s, false, modify_type)) {
+			return 2;
+		}
+	}
+
 	if (is_type_union(dst)) {
 		for (isize i = 0; i < dst->Union.variant_count; i++) {
 			Type *vt = dst->Union.variants[i];
@@ -679,21 +692,21 @@ void check_assignment(Checker *c, Operand *operand, Type *type, String context_n
 		defer (gb_string_free(op_type_str));
 		defer (gb_string_free(expr_str));
 
-		if (operand->mode == Addressing_Builtin) {
-			// TODO(bill): is this a good enough error message?
+		switch (operand->mode) {
+		case Addressing_Builtin:
 			// TODO(bill): Actually allow built in procedures to be passed around and thus be created on use
 			error(operand->expr,
 			      "Cannot assign built-in procedure `%s` in %.*s",
 			      expr_str,
 			      LIT(context_name));
-		} else if (operand->mode == Addressing_Type) {
-			// TODO(bill): is this a good enough error message?
-			// TODO(bill): Actually allow built in procedures to be passed around and thus be created on use
+			break;
+		case Addressing_Type:
 			error(operand->expr,
 			      "Cannot assign `%s` which is a type in %.*s",
 			      op_type_str,
 			      LIT(context_name));
-		} else {
+			break;
+		default:
 			// TODO(bill): is this a good enough error message?
 			error(operand->expr,
 			      "Cannot assign value `%s` of type `%s` to `%s` in %.*s",
@@ -701,6 +714,7 @@ void check_assignment(Checker *c, Operand *operand, Type *type, String context_n
 			      op_type_str,
 			      type_str,
 			      LIT(context_name));
+			break;
 		}
 		operand->mode = Addressing_Invalid;
 
@@ -1087,7 +1101,8 @@ void check_struct_type(Checker *c, Type *struct_type, AstNode *node, Array<Opera
 					if (poly_operands != nullptr) {
 						Operand operand = (*poly_operands)[entities.count];
 						if (is_type_param) {
-							GB_ASSERT(operand.mode == Addressing_Type);
+							GB_ASSERT(operand.mode == Addressing_Type ||
+							          operand.mode == Addressing_Invalid);
 							if (is_type_polymorphic(base_type(operand.type))) {
 								is_polymorphic = true;
 								can_check_fields = false;
@@ -1545,19 +1560,59 @@ void check_bit_field_type(Checker *c, Type *bit_field_type, Type *named_type, As
 	}
 }
 
-bool is_polymorphic_type_assignable_to_specific(Checker *c, Type *source, Type *specific) {
-	if (!is_type_struct(specific)) {
-		return false;
+
+bool check_type_specialization_to(Checker *c, Type *type, Type *specialization, bool modify_type) {
+	if (type == nullptr ||
+	    type == t_invalid) {
+		return true;
 	}
 
-	if (!is_type_struct(source)) {
+	Type *t = base_type(type);
+	Type *s = base_type(specialization);
+	if (t->kind != s->kind) {
 		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 (show_stuff) gb_printf_err("#3 %s -> %s\n", type_to_string(type), type_to_string(specialization));
+
+		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));
+		}
+
+
+		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));
+		}
 
-	source = base_type(source);
-	GB_ASSERT(source->kind == Type_Record && source->Record.kind == TypeRecord_Struct);
+		return true;
+	}
 
-	return are_types_identical(source->Record.polymorphic_parent, specific);
+	return false;
 }
 
 bool is_polymorphic_type_assignable(Checker *c, Type *poly, Type *source, bool compound, bool modify_type) {
@@ -1565,17 +1620,21 @@ 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(poly, source);
+		if (compound) return are_types_identical(source, poly);
 		return check_is_assignable_to(c, &o, poly);
 
-	case Type_Named:
+	case Type_Named: {
+		if (check_type_specialization_to(c, source, poly, modify_type)) {
+			return true;
+		}
 		if (compound) return are_types_identical(poly, source);
 		return check_is_assignable_to(c, &o, poly);
+	}
 
 	case Type_Generic: {
-		if (poly->Generic.specific != nullptr) {
-			Type *s = poly->Generic.specific;
-			if (!is_polymorphic_type_assignable_to_specific(c, source, s)) {
+		if (poly->Generic.specialized != nullptr) {
+			Type *s = poly->Generic.specialized;
+			if (!check_type_specialization_to(c, source, s, modify_type)) {
 				return false;
 			}
 		}
@@ -1587,7 +1646,7 @@ bool is_polymorphic_type_assignable(Checker *c, Type *poly, Type *source, bool c
 	}
 	case Type_Pointer:
 		if (source->kind == Type_Pointer) {
-			return is_polymorphic_type_assignable(c, poly->Pointer.elem, source->Atomic.elem, true, modify_type);
+			return is_polymorphic_type_assignable(c, poly->Pointer.elem, source->Pointer.elem, true, modify_type);
 		}
 		return false;
 	case Type_Atomic:
@@ -1640,7 +1699,6 @@ bool is_polymorphic_type_assignable(Checker *c, Type *poly, Type *source, bool c
 
 	case Type_Record:
 		if (source->kind == Type_Record) {
-			// TODO(bill): Polymorphic type assignment
 			// return check_is_assignable_to(c, &o, poly);
 		}
 		return false;
@@ -1718,33 +1776,6 @@ Type *determine_type_from_polymorphic(Checker *c, Type *poly_type, Operand opera
 	return t_invalid;
 }
 
-bool check_type_specialization_to(Checker *c, Type *type, Type *specialization) {
-	if (type == nullptr ||
-	    type == t_invalid) {
-		return true;
-	}
-
-	Type *t = base_type(type);
-	Type *s = base_type(specialization);
-	if (t->kind != s->kind) {
-		return false;
-	}
-	// gb_printf_err("#1 %s %s\n", type_to_string(type), type_to_string(specialization));
-	if (t->kind != Type_Record) {
-		return false;
-	}
-	// gb_printf_err("#2 %s %s\n", type_to_string(type), type_to_string(specialization));
-	if (t->Record.polymorphic_parent == specialization) {
-		return true;
-	}
-	// gb_printf_err("#3 %s %s\n", type_to_string(t->Record.polymorphic_parent), type_to_string(specialization));
-	if (t->Record.polymorphic_parent == s->Record.polymorphic_parent) {
-		return true;
-	}
-
-
-	return false;
-}
 
 Type *check_get_params(Checker *c, Scope *scope, AstNode *_params, bool *is_variadic_, bool *success_, Array<Operand> *operands) {
 	if (_params == nullptr) {
@@ -4323,7 +4354,7 @@ Entity *check_selector(Checker *c, Operand *operand, AstNode *node, Type *type_h
 		Entity *e = scope_lookup_entity(c->context.scope, op_name);
 
 		bool is_alias = false;
-		while (e->kind == Entity_Alias) {
+		while (e != nullptr && e->kind == Entity_Alias) {
 			GB_ASSERT(e->Alias.base != nullptr);
 			e = e->Alias.base;
 			is_alias = true;
@@ -6549,6 +6580,10 @@ CallArgumentError check_polymorphic_struct_type(Checker *c, Operand *operand, As
 					Entity *p = tuple->variables[j];
 					Operand o = ordered_operands[j];
 					if (p->kind == Entity_TypeName) {
+						if (is_type_polymorphic(o.type)) {
+							// NOTE(bill): Do not add polymorphic version to the gen_types
+							ok = false;
+						}
 						if (!are_types_identical(o.type, p->type)) {
 							ok = false;
 						}
@@ -7619,12 +7654,14 @@ ExprKind check_expr_base_internal(Checker *c, Operand *o, AstNode *node, Type *t
 
 		if (!valid) {
 			gbString str = expr_to_string(o->expr);
+			gbString type_str = type_to_string(o->type);
+			defer (gb_string_free(str));
+			defer (gb_string_free(type_str));
 			if (is_const) {
 				error(o->expr, "Cannot index a constant `%s`", str);
 			} else {
-				error(o->expr, "Cannot index `%s`", str);
+				error(o->expr, "Cannot index `%s` of type `%s`", str, type_str);
 			}
-			gb_string_free(str);
 			o->mode = Addressing_Invalid;
 			o->expr = node;
 			return kind;
@@ -8009,6 +8046,12 @@ gbString write_expr_to_string(gbString str, AstNode *node) {
 		str = write_expr_to_string(str, ta->type);
 		str = gb_string_appendc(str, ")");
 	case_end;
+		case_ast_node(tc, TypeCast, node);
+		str = gb_string_appendc(str, "cast(");
+		str = write_expr_to_string(str, tc->type);
+		str = gb_string_appendc(str, ")");
+		str = write_expr_to_string(str, tc->expr);
+	case_end;
 
 	case_ast_node(ie, IndexExpr, node);
 		str = write_expr_to_string(str, ie->expr);

+ 13 - 6
src/check_stmt.cpp

@@ -1608,16 +1608,21 @@ void check_stmt_internal(Checker *c, AstNode *node, u32 flags) {
 			Entity *e = nullptr;
 
 			bool is_selector = false;
-			if (expr->kind == AstNode_Ident) {
-				Operand o = {};
+			Operand o = {};
+			switch (expr->kind) {
+			case AstNode_Ident:
 				e = check_ident(c, &o, expr, nullptr, nullptr, true);
-			} else if (expr->kind == AstNode_SelectorExpr) {
-				Operand o = {};
+				break;
+			case AstNode_SelectorExpr:
 				e = check_selector(c, &o, expr, nullptr);
 				is_selector = true;
-			} else if (expr->kind == AstNode_Implicit) {
+				break;
+			case AstNode_Implicit:
 				error(us->token, "`using` applied to an implicit value");
 				continue;
+			default:
+				error(us->token, "`using` can only be applied to an entity, got %.*s", LIT(ast_node_strings[expr->kind]));
+				continue;
 			}
 
 			if (!check_using_stmt_entity(c, us, expr, is_selector, e)) {
@@ -1722,7 +1727,9 @@ void check_stmt_internal(Checker *c, AstNode *node, u32 flags) {
 			if (init_type == nullptr) {
 				init_type = t_invalid;
 			} else if (is_type_polymorphic(base_type(init_type))) {
-				error(vd->type, "Invalid use of a polymorphic type in variable declaration");
+				gbString str = type_to_string(init_type);
+				error(vd->type, "Invalid use of a polymorphic type `%s` in variable declaration", str);
+				gb_string_free(str);
 				init_type = t_invalid;
 			}
 		}

+ 20 - 9
src/ir.cpp

@@ -2826,6 +2826,9 @@ irValue *ir_emit_conv(irProcedure *proc, irValue *value, Type *t) {
 	}
 
 	if (are_types_identical(src, dst)) {
+		if (!are_types_identical(src_type, t)) {
+			return ir_emit_transmute(proc, value, t);
+		}
 		return value;
 	}
 
@@ -5752,19 +5755,24 @@ void ir_build_constant_value_decl(irProcedure *proc, AstNodeValueDecl *vd) {
 			continue;
 		}
 
-		bool polymorphic = is_type_polymorphic(e->type);
-		if (polymorphic && !is_type_struct(e->type)) {
-			continue;
-		}
-		if (map_get(&proc->module->min_dep_map, hash_pointer(e)) == nullptr) {
-			// NOTE(bill): Nothing depends upon it so doesn't need to be built
-			continue;
-		}
+		String entity_name = e->token.string;
 
 		if (e->kind == Entity_TypeName) {
+			bool polymorphic_struct = false;
+			if (e->type != nullptr && e->kind == Entity_TypeName) {
+				Type *bt = base_type(e->type);
+				if (bt->kind == Type_Record) {
+					polymorphic_struct = bt->Record.is_polymorphic;
+				}
+			}
+
+			if (!polymorphic_struct && map_get(&proc->module->min_dep_map, hash_pointer(e)) == nullptr) {
+				continue;
+			}
+
 			// NOTE(bill): Generate a new name
 			// parent_proc.name-guid
-			String ts_name = e->token.string;
+			String ts_name = entity_name;
 
 			isize name_len = proc->name.len + 1 + ts_name.len + 1 + 10 + 1;
 			u8 *name_text = gb_alloc_array(proc->module->allocator, u8, name_len);
@@ -5787,6 +5795,9 @@ void ir_build_constant_value_decl(irProcedure *proc, AstNodeValueDecl *vd) {
 					auto procs = *found;
 					for_array(i, procs) {
 						Entity *e = procs[i];
+						if (map_get(&proc->module->min_dep_map, hash_pointer(e)) == nullptr) {
+							continue;
+						}
 						DeclInfo *d = decl_info_of_entity(info, e);
 						ir_build_poly_proc(proc, &d->proc_lit->ProcLit, e);
 					}

+ 3 - 0
src/parser.cpp

@@ -2215,6 +2215,9 @@ AstNode *convert_stmt_to_body(AstFile *f, AstNode *stmt) {
 		syntax_error(stmt, "Expected a normal statement rather than a block statement");
 		return stmt;
 	}
+	if (stmt->kind == AstNode_EmptyStmt) {
+		syntax_error(stmt, "Expected a non-empty statement");
+	}
 	GB_ASSERT(is_ast_node_stmt(stmt) || is_ast_node_decl(stmt));
 	Token open = ast_node_token(stmt);
 	Token close = ast_node_token(stmt);

+ 9 - 5
src/types.cpp

@@ -108,7 +108,7 @@ struct TypeRecord {
 	TYPE_KIND(Generic, struct {                           \
 		i64    id;                                        \
 		String name;                                      \
-		Type * specific;                                  \
+		Type * specialized;                               \
 	})                                                    \
 	TYPE_KIND(Pointer, struct { Type *elem; })            \
 	TYPE_KIND(Atomic,  struct { Type *elem; })            \
@@ -486,11 +486,11 @@ Type *make_type_basic(gbAllocator a, BasicType basic) {
 	return t;
 }
 
-Type *make_type_generic(gbAllocator a, i64 id, String name, Type *specific) {
+Type *make_type_generic(gbAllocator a, i64 id, String name, Type *specialized) {
 	Type *t = alloc_type(a, Type_Generic);
 	t->Generic.id = id;
 	t->Generic.name = name;
-	t->Generic.specific = specific;
+	t->Generic.specialized = specialized;
 	return t;
 }
 
@@ -1632,6 +1632,10 @@ Selection lookup_field_with_selection(gbAllocator a, Type *type_, String field_n
 				}
 			}
 		}
+		if (type->kind == Type_Generic && type->Generic.specialized != nullptr) {
+			Type *specialized = type->Generic.specialized;
+			return lookup_field_with_selection(a, specialized, field_name, is_type, sel);
+		}
 
 	} else if (type->Record.kind == Type_Union) {
 		if (field_name == "__tag") {
@@ -2291,9 +2295,9 @@ gbString write_type_to_string(gbString str, Type *type) {
 			String name = type->Generic.name;
 			str = gb_string_appendc(str, "$");
 			str = gb_string_append_length(str, name.text, name.len);
-			if (type->Generic.specific != nullptr) {
+			if (type->Generic.specialized != nullptr) {
 				str = gb_string_appendc(str, "/");
-				str = write_type_to_string(str, type->Generic.specific);
+				str = write_type_to_string(str, type->Generic.specialized);
 			}
 		}
 		break;