Pārlūkot izejas kodu

Merge branch 'master' of https://github.com/odin-lang/Odin

Dale Weiler 4 gadi atpakaļ
vecāks
revīzija
b39a4f3e3b

+ 3 - 0
core/encoding/json/marshal.odin

@@ -139,6 +139,9 @@ marshal_arg :: proc(b: ^strings.Builder, v: any) -> Marshal_Error {
 	case runtime.Type_Info_Pointer:
 		return .Unsupported_Type;
 
+	case runtime.Type_Info_Multi_Pointer:
+		return .Unsupported_Type;
+
 	case runtime.Type_Info_Procedure:
 		return .Unsupported_Type;
 

+ 42 - 0
core/fmt/fmt.odin

@@ -1574,6 +1574,48 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) {
 			fmt_pointer(fi, ptr, verb);
 		}
 
+	case runtime.Type_Info_Multi_Pointer:
+		ptr := (^rawptr)(v.data)^;
+		if verb != 'p' && info.elem != nil {
+			a := any{ptr, info.elem.id};
+
+			elem := runtime.type_info_base(info.elem);
+			if elem != nil {
+				#partial switch e in elem.variant {
+				case runtime.Type_Info_Array,
+				     runtime.Type_Info_Slice,
+				     runtime.Type_Info_Dynamic_Array,
+				     runtime.Type_Info_Map:
+					if ptr == nil {
+						io.write_string(fi.writer, "<nil>");
+						return;
+					}
+					if fi.record_level < 1 {
+					  	fi.record_level += 1;
+						defer fi.record_level -= 1;
+						io.write_byte(fi.writer, '&');
+						fmt_value(fi, a, verb);
+						return;
+					}
+
+				case runtime.Type_Info_Struct,
+				     runtime.Type_Info_Union:
+					if ptr == nil {
+						io.write_string(fi.writer, "<nil>");
+						return;
+					}
+					if fi.record_level < 1 {
+						fi.record_level += 1;
+						defer fi.record_level -= 1;
+						io.write_byte(fi.writer, '&');
+						fmt_value(fi, a, verb);
+						return;
+					}
+				}
+			}
+		}
+		fmt_pointer(fi, ptr, verb);
+
 	case runtime.Type_Info_Array:
 		if (verb == 's' || verb == 'q') && reflect.is_byte(info.elem) {
 			s := strings.string_from_ptr((^byte)(v.data), info.count);

+ 8 - 0
core/odin/ast/ast.odin

@@ -656,6 +656,14 @@ Pointer_Type :: struct {
 	elem:    ^Expr,
 }
 
+Multi_Pointer_Type :: struct {
+	using node: Expr,
+	open:    tokenizer.Pos,
+	pointer: tokenizer.Pos,
+	close:   tokenizer.Pos,
+	elem:    ^Expr,
+}
+
 Array_Type :: struct {
 	using node: Expr,
 	open:  tokenizer.Pos,

+ 2 - 0
core/odin/ast/clone.odin

@@ -251,6 +251,8 @@ clone_node :: proc(node: ^Node) -> ^Node {
 		r.results = auto_cast clone(r.results);
 	case Pointer_Type:
 		r.elem = clone(r.elem);
+	case Multi_Pointer_Type:
+		r.elem = clone(r.elem);
 	case Array_Type:
 		r.len  = clone(r.len);
 		r.elem = clone(r.elem);

+ 2 - 0
core/odin/ast/walk.odin

@@ -349,6 +349,8 @@ walk :: proc(v: ^Visitor, node: ^Node) {
 		walk(v, n.results);
 	case Pointer_Type:
 		walk(v, n.elem);
+	case Multi_Pointer_Type:
+		walk(v, n.elem);
 	case Array_Type:
 		if n.tag != nil {
 			walk(v, n.tag);

+ 2 - 0
core/odin/doc-format/doc_format.odin

@@ -166,6 +166,7 @@ Type_Kind :: enum u32le {
 	SOA_Struct_Dynamic = 19,
 	Relative_Pointer   = 20,
 	Relative_Slice     = 21,
+	Multi_Pointer      = 22,
 }
 
 Type_Elems_Cap :: 4;
@@ -222,6 +223,7 @@ Type :: struct {
 	// .Simd_Vector        - 1 type:    0=element
 	// .Relative_Pointer   - 2 types:   0=pointer type, 1=base integer
 	// .Relative_Slice     - 2 types:   0=slice type, 1=base integer
+	// .Multi_Pointer      - 1 type:    0=element
 	types: Array(Type_Index),
 
 	// Used by:

+ 40 - 14
core/odin/parser/parser.odin

@@ -848,12 +848,24 @@ parse_for_stmt :: proc(p: ^Parser) -> ^ast.Stmt {
 		if !is_range && parse_control_statement_semicolon_separator(p) {
 			init = cond;
 			cond = nil;
-			if p.curr_tok.kind != .Semicolon {
-				cond = parse_simple_stmt(p, nil);
-			}
-			expect_semicolon(p, cond);
-			if p.curr_tok.kind != .Open_Brace && p.curr_tok.kind != .Do {
-				post = parse_simple_stmt(p, nil);
+
+
+			if f.curr_tok.kind == .Open_Brace || f.curr_tok.kind == .Do {
+				error(p, f.curr_tok.pos, "Expected ';', followed by a condition expression and post statement, got %s", token.tokens[f.curr_tok.kind]);
+			} else {
+				if p.curr_tok.kind != .Semicolon {
+					cond = parse_simple_stmt(p, nil);
+				}
+
+				if p.curr_tok.text != ";" {
+					error(p, p.curr_tok.pos, "Expected ';', got %s", tokenizer.token_to_string(p.curr_tok));
+				} else {
+					expect_semicolon(p, nil);
+				}
+
+				if p.curr_tok.kind != .Open_Brace && p.curr_tok.kind != .Do {
+					post = parse_simple_stmt(p, nil);
+				}
 			}
 		}
 	}
@@ -2428,18 +2440,26 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
 		tok := expect_token(p, .Pointer);
 		elem := parse_type(p);
 		ptr := ast.new(ast.Pointer_Type, tok.pos, elem.end);
+		ptr.pointer = tok.pos;
 		ptr.elem = elem;
 		return ptr;
 
+
 	case .Open_Bracket:
 		open := expect_token(p, .Open_Bracket);
 		count: ^ast.Expr;
-		if p.curr_tok.kind == .Question {
-			tok := expect_token(p, .Question);
-			q := ast.new(ast.Unary_Expr, tok.pos, end_pos(tok));
-			q.op = tok;
-			count = q;
-		} else if p.curr_tok.kind == .Dynamic {
+		switch p.curr_tok.kind {
+		case .Pointer:
+			tok := expect_token(p, .Pointer);
+			close := expect_token(p, .Close_Bracket);
+			elem := parse_type(p);
+			t := ast.new(ast.Multi_Pointer_Type, open.pos, elem.end_pos);
+			t.open = open.pos;
+			t.pointer = tok.pos;
+			t.close = close.pos;
+			t.elem = elem;
+			return t;
+		case .Dynamic:
 			tok := expect_token(p, .Dynamic);
 			close := expect_token(p, .Close_Bracket);
 			elem := parse_type(p);
@@ -2448,12 +2468,18 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
 			da.dynamic_pos = tok.pos;
 			da.close = close.pos;
 			da.elem = elem;
-
 			return da;
-		} else if p.curr_tok.kind != .Close_Bracket {
+		case .Question:
+			tok := expect_token(p, .Question);
+			q := ast.new(ast.Unary_Expr, tok.pos, end_pos(tok));
+			q.op = tok;
+			count = q;
+		case:
 			p.expr_level += 1;
 			count = parse_expr(p, false);
 			p.expr_level -= 1;
+		case .Close_Bracket:
+			// handle below
 		}
 		close := expect_token(p, .Close_Bracket);
 		elem := parse_type(p);

+ 11 - 0
core/reflect/reflect.odin

@@ -18,6 +18,7 @@ Type_Info_Boolean          :: runtime.Type_Info_Boolean;
 Type_Info_Any              :: runtime.Type_Info_Any;
 Type_Info_Type_Id          :: runtime.Type_Info_Type_Id;
 Type_Info_Pointer          :: runtime.Type_Info_Pointer;
+Type_Info_Multi_Pointer    :: runtime.Type_Info_Multi_Pointer;
 Type_Info_Procedure        :: runtime.Type_Info_Procedure;
 Type_Info_Array            :: runtime.Type_Info_Array;
 Type_Info_Enumerated_Array :: runtime.Type_Info_Enumerated_Array;
@@ -50,6 +51,7 @@ Type_Kind :: enum {
 	Any,
 	Type_Id,
 	Pointer,
+	Multi_Pointer,
 	Procedure,
 	Array,
 	Enumerated_Array,
@@ -82,6 +84,7 @@ type_kind :: proc(T: typeid) -> Type_Kind {
 		case Type_Info_Any:              return .Any;
 		case Type_Info_Type_Id:          return .Type_Id;
 		case Type_Info_Pointer:          return .Pointer;
+		case Type_Info_Multi_Pointer:    return .Multi_Pointer;
 		case Type_Info_Procedure:        return .Procedure;
 		case Type_Info_Array:            return .Array;
 		case Type_Info_Enumerated_Array: return .Enumerated_Array;
@@ -172,6 +175,7 @@ typeid_elem :: proc(id: typeid) -> typeid {
 		case 256: return f64;
 		}
 	case Type_Info_Pointer:          return v.elem.id;
+	case Type_Info_Multi_Pointer:    return v.elem.id;
 	case Type_Info_Array:            return v.elem.id;
 	case Type_Info_Enumerated_Array: return v.elem.id;
 	case Type_Info_Slice:            return v.elem.id;
@@ -298,6 +302,13 @@ index :: proc(val: any, i: int, loc := #caller_location) -> any {
 		}
 		return index({ptr, a.elem.id}, i, loc);
 
+	case Type_Info_Multi_Pointer:
+		ptr := (^rawptr)(val.data)^;
+		if ptr == nil {
+			return nil;
+		}
+		return index({ptr, a.elem.id}, i, loc);
+
 	case Type_Info_Array:
 		runtime.bounds_check_error_loc(loc, i, a.count);
 		offset := uintptr(a.elem.size * i);

+ 28 - 32
core/reflect/types.odin

@@ -21,13 +21,11 @@ are_types_identical :: proc(a, b: ^Type_Info) -> bool {
 
 	switch x in a.variant {
 	case Type_Info_Named:
-		y, ok := b.variant.(Type_Info_Named);
-		if !ok { return false; }
+		y := b.variant.(Type_Info_Named) or_return;
 		return x.base == y.base;
 
 	case Type_Info_Integer:
-		y, ok := b.variant.(Type_Info_Integer);
-		if !ok { return false; }
+		y := b.variant.(Type_Info_Integer) or_return;
 		return x.signed == y.signed && x.endianness == y.endianness;
 
 	case Type_Info_Rune:
@@ -63,13 +61,15 @@ are_types_identical :: proc(a, b: ^Type_Info) -> bool {
 		return ok;
 
 	case Type_Info_Pointer:
-		y, ok := b.variant.(Type_Info_Pointer);
-		if !ok { return false; }
+		y := b.variant.(Type_Info_Pointer) or_return;
+		return are_types_identical(x.elem, y.elem);
+
+	case Type_Info_Multi_Pointer:
+		y := b.variant.(Type_Info_Multi_Pointer) or_return;
 		return are_types_identical(x.elem, y.elem);
 
 	case Type_Info_Procedure:
-		y, ok := b.variant.(Type_Info_Procedure);
-		if !ok { return false; }
+		y := b.variant.(Type_Info_Procedure) or_return;
 		switch {
 		case x.variadic   != y.variadic,
 		     x.convention != y.convention:
@@ -79,31 +79,26 @@ are_types_identical :: proc(a, b: ^Type_Info) -> bool {
 		return are_types_identical(x.params, y.params) && are_types_identical(x.results, y.results);
 
 	case Type_Info_Array:
-		y, ok := b.variant.(Type_Info_Array);
-		if !ok { return false; }
+		y := b.variant.(Type_Info_Array) or_return;
 		if x.count != y.count { return false; }
 		return are_types_identical(x.elem, y.elem);
 
 	case Type_Info_Enumerated_Array:
-		y, ok := b.variant.(Type_Info_Enumerated_Array);
-		if !ok { return false; }
+		y := b.variant.(Type_Info_Enumerated_Array) or_return;
 		if x.count != y.count { return false; }
 		return are_types_identical(x.index, y.index) &&
 		       are_types_identical(x.elem, y.elem);
 
 	case Type_Info_Dynamic_Array:
-		y, ok := b.variant.(Type_Info_Dynamic_Array);
-		if !ok { return false; }
+		y := b.variant.(Type_Info_Dynamic_Array) or_return;
 		return are_types_identical(x.elem, y.elem);
 
 	case Type_Info_Slice:
-		y, ok := b.variant.(Type_Info_Slice);
-		if !ok { return false; }
+		y := b.variant.(Type_Info_Slice) or_return;
 		return are_types_identical(x.elem, y.elem);
 
 	case Type_Info_Tuple:
-		y, ok := b.variant.(Type_Info_Tuple);
-		if !ok { return false; }
+		y := b.variant.(Type_Info_Tuple) or_return;
 		if len(x.types) != len(y.types) { return false; }
 		for _, i in x.types {
 			xt, yt := x.types[i], y.types[i];
@@ -114,8 +109,7 @@ are_types_identical :: proc(a, b: ^Type_Info) -> bool {
 		return true;
 
 	case Type_Info_Struct:
-		y, ok := b.variant.(Type_Info_Struct);
-		if !ok { return false; }
+		y := b.variant.(Type_Info_Struct) or_return;
 	   	switch {
 		case len(x.types)    != len(y.types),
 		     x.is_packed     != y.is_packed,
@@ -138,8 +132,7 @@ are_types_identical :: proc(a, b: ^Type_Info) -> bool {
 		return true;
 
 	case Type_Info_Union:
-		y, ok := b.variant.(Type_Info_Union);
-		if !ok { return false; }
+		y := b.variant.(Type_Info_Union) or_return;
 		if len(x.variants) != len(y.variants) { return false; }
 
 		for _, i in x.variants {
@@ -153,28 +146,23 @@ are_types_identical :: proc(a, b: ^Type_Info) -> bool {
 		return false;
 
 	case Type_Info_Map:
-		y, ok := b.variant.(Type_Info_Map);
-		if !ok { return false; }
+		y := b.variant.(Type_Info_Map) or_return;
 		return are_types_identical(x.key, y.key) && are_types_identical(x.value, y.value);
 
 	case Type_Info_Bit_Set:
-		y, ok := b.variant.(Type_Info_Bit_Set);
-		if !ok { return false; }
+		y := b.variant.(Type_Info_Bit_Set) or_return;
 		return x.elem == y.elem && x.lower == y.lower && x.upper == y.upper;
 
 	case Type_Info_Simd_Vector:
-		y, ok := b.variant.(Type_Info_Simd_Vector);
-		if !ok { return false; }
+		y := b.variant.(Type_Info_Simd_Vector) or_return;
 		return x.count == y.count && x.elem == y.elem;
 
 	case Type_Info_Relative_Pointer:
-		y, ok := b.variant.(Type_Info_Relative_Pointer);
-		if !ok { return false; }
+		y := b.variant.(Type_Info_Relative_Pointer) or_return;
 		return x.base_integer == y.base_integer && x.pointer == y.pointer;
 
 	case Type_Info_Relative_Slice:
-		y, ok := b.variant.(Type_Info_Relative_Slice);
-		if !ok { return false; }
+		y := b.variant.(Type_Info_Relative_Slice) or_return;
 		return x.base_integer == y.base_integer && x.slice == y.slice;
 	}
 
@@ -257,6 +245,11 @@ is_pointer :: proc(info: ^Type_Info) -> bool {
 	_, ok := type_info_base(info).variant.(Type_Info_Pointer);
 	return ok;
 }
+is_multi_pointer :: proc(info: ^Type_Info) -> bool {
+	if info == nil { return false; }
+	_, ok := type_info_base(info).variant.(Type_Info_Multi_Pointer);
+	return ok;
+}
 is_procedure :: proc(info: ^Type_Info) -> bool {
 	if info == nil { return false; }
 	_, ok := type_info_base(info).variant.(Type_Info_Procedure);
@@ -424,6 +417,9 @@ write_type_writer :: proc(w: io.Writer, ti: ^Type_Info) -> (n: int) {
 			write_string(w, "^");
 			write_type(w, info.elem);
 		}
+	case Type_Info_Multi_Pointer:
+		write_string(w, "[^]");
+		write_type(w, info.elem);
 	case Type_Info_Procedure:
 		n += write_string(w, "proc");
 		if info.params == nil {

+ 6 - 1
core/runtime/core.odin

@@ -74,6 +74,9 @@ Type_Info_Type_Id    :: struct {};
 Type_Info_Pointer :: struct {
 	elem: ^Type_Info, // nil -> rawptr
 };
+Type_Info_Multi_Pointer :: struct {
+	elem: ^Type_Info,
+};
 Type_Info_Procedure :: struct {
 	params:     ^Type_Info, // Type_Info_Tuple
 	results:    ^Type_Info, // Type_Info_Tuple
@@ -184,6 +187,7 @@ Type_Info :: struct {
 		Type_Info_Any,
 		Type_Info_Type_Id,
 		Type_Info_Pointer,
+		Type_Info_Multi_Pointer,
 		Type_Info_Procedure,
 		Type_Info_Array,
 		Type_Info_Enumerated_Array,
@@ -214,6 +218,7 @@ Typeid_Kind :: enum u8 {
 	Any,
 	Type_Id,
 	Pointer,
+	Multi_Pointer,
 	Procedure,
 	Array,
 	Enumerated_Array,
@@ -340,7 +345,7 @@ Context :: struct {
 
 
 Raw_String :: struct {
-	data: ^byte,
+	data: [^]byte,
 	len:  int,
 }
 

+ 18 - 0
core/runtime/error_checks.odin

@@ -45,6 +45,24 @@ slice_handle_error :: proc "contextless" (file: string, line, column: i32, lo, h
 	bounds_trap();
 }
 
+multi_pointer_slice_handle_error :: proc "contextless" (file: string, line, column: i32, lo, hi: int) -> ! {
+	print_caller_location(Source_Code_Location{file, line, column, ""});
+	print_string(" Invalid slice indices: ");
+	print_i64(i64(lo));
+	print_string(":");
+	print_i64(i64(hi));
+	print_byte('\n');
+	bounds_trap();
+}
+
+
+multi_pointer_slice_expr_error :: proc "contextless" (file: string, line, column: i32, lo, hi: int) {
+	if lo <= hi {
+		return;
+	}
+	multi_pointer_slice_handle_error(file, line, column, lo, hi);
+}
+
 slice_expr_error_hi :: proc "contextless" (file: string, line, column: i32, hi: int, len: int) {
 	if 0 <= hi && hi <= len {
 		return;

+ 3 - 0
core/runtime/print.odin

@@ -203,6 +203,9 @@ print_type :: proc "contextless" (ti: ^Type_Info) {
 			print_string("^");
 			print_type(info.elem);
 		}
+	case Type_Info_Multi_Pointer:
+		print_string("[^]");
+		print_type(info.elem);
 	case Type_Info_Procedure:
 		print_string("proc");
 		if info.params == nil {

+ 83 - 15
src/check_expr.cpp

@@ -592,10 +592,25 @@ i64 check_distance_between_types(CheckerContext *c, Operand *operand, Type *type
 #if 1
 
 
-	// TODO(bill): Should I allow this implicit conversion at all?!
 	// rawptr <- ^T
 	if (are_types_identical(type, t_rawptr) && is_type_pointer(src)) {
-	    return 5;
+		return 5;
+	}
+	// rawptr <- [^]T
+	if (are_types_identical(type, t_rawptr) && is_type_multi_pointer(src)) {
+		return 5;
+	}
+	// ^T <- [^]T
+	if (dst->kind == Type_Pointer && src->kind == Type_MultiPointer) {
+		if (are_types_identical(dst->Pointer.elem, src->MultiPointer.elem)) {
+			return 4;
+		}
+	}
+	// [^]T <- ^T
+	if (dst->kind == Type_MultiPointer && src->kind == Type_Pointer) {
+		if (are_types_identical(dst->MultiPointer.elem, src->Pointer.elem)) {
+			return 4;
+		}
 	}
 #endif
 
@@ -930,6 +945,16 @@ bool is_polymorphic_type_assignable(CheckerContext *c, Type *poly, Type *source,
 			return is_polymorphic_type_assignable(c, poly->Pointer.elem, source->Pointer.elem, true, modify_type);
 		}
 		return false;
+
+	case Type_MultiPointer:
+		if (source->kind == Type_MultiPointer) {
+			isize level = check_is_assignable_to_using_subtype(source->MultiPointer.elem, poly->MultiPointer.elem);
+			if (level > 0) {
+				return true;
+			}
+			return is_polymorphic_type_assignable(c, poly->MultiPointer.elem, source->MultiPointer.elem, true, modify_type);
+		}
+		return false;
 	case Type_Array:
 		if (source->kind == Type_Array) {
 			if (poly->Array.generic_count != nullptr) {
@@ -2392,6 +2417,15 @@ bool check_is_castable_to(CheckerContext *c, Operand *operand, Type *y) {
 	if (is_type_pointer(src) && is_type_pointer(dst)) {
 		return true;
 	}
+	if (is_type_multi_pointer(src) && is_type_multi_pointer(dst)) {
+		return true;
+	}
+	if (is_type_multi_pointer(src) && is_type_pointer(dst)) {
+		return true;
+	}
+	if (is_type_pointer(src) && is_type_multi_pointer(dst)) {
+		return true;
+	}
 
 	// uintptr <-> pointer
 	if (is_type_uintptr(src) && is_type_pointer(dst)) {
@@ -2400,16 +2434,18 @@ bool check_is_castable_to(CheckerContext *c, Operand *operand, Type *y) {
 	if (is_type_pointer(src) && is_type_uintptr(dst)) {
 		return true;
 	}
+	if (is_type_uintptr(src) && is_type_multi_pointer(dst)) {
+		return true;
+	}
+	if (is_type_multi_pointer(src) && is_type_uintptr(dst)) {
+		return true;
+	}
 
 	// []byte/[]u8 <-> string (not cstring)
 	if (is_type_u8_slice(src) && (is_type_string(dst) && !is_type_cstring(dst))) {
 		return true;
 	}
-	if ((is_type_string(src) && !is_type_cstring(src)) && is_type_u8_slice(dst)) {
-		// if (is_type_typed(src)) {
-			// return true;
-		// }
-	}
+
 	// cstring -> string
 	if (are_types_identical(src, t_cstring) && are_types_identical(dst, t_string)) {
 		if (operand->mode != Addressing_Constant) {
@@ -2421,6 +2457,10 @@ bool check_is_castable_to(CheckerContext *c, Operand *operand, Type *y) {
 	if (are_types_identical(src, t_cstring) && is_type_u8_ptr(dst)) {
 		return !is_constant;
 	}
+	// cstring -> [^]u8
+	if (are_types_identical(src, t_cstring) && is_type_u8_multi_ptr(dst)) {
+		return !is_constant;
+	}
 	// cstring -> rawptr
 	if (are_types_identical(src, t_cstring) && is_type_rawptr(dst)) {
 		return !is_constant;
@@ -2430,6 +2470,10 @@ bool check_is_castable_to(CheckerContext *c, Operand *operand, Type *y) {
 	if (is_type_u8_ptr(src) && are_types_identical(dst, t_cstring)) {
 		return !is_constant;
 	}
+	// [^]u8 -> cstring
+	if (is_type_u8_multi_ptr(src) && are_types_identical(dst, t_cstring)) {
+		return !is_constant;
+	}
 	// rawptr -> cstring
 	if (is_type_rawptr(src) && are_types_identical(dst, t_cstring)) {
 		return !is_constant;
@@ -3328,7 +3372,7 @@ void convert_to_typed(CheckerContext *c, Operand *operand, Type *target_type) {
 	operand->type = target_type;
 }
 
-bool check_index_value(CheckerContext *c, bool open_range, Ast *index_value, i64 max_count, i64 *value, Type *type_hint=nullptr) {
+bool check_index_value(CheckerContext *c, Type *main_type, bool open_range, Ast *index_value, i64 max_count, i64 *value, Type *type_hint=nullptr) {
 	Operand operand = {Addressing_Invalid};
 	check_expr_with_type_hint(c, &operand, index_value, type_hint);
 	if (operand.mode == Addressing_Invalid) {
@@ -3367,7 +3411,7 @@ bool check_index_value(CheckerContext *c, bool open_range, Ast *index_value, i64
 	if (operand.mode == Addressing_Constant &&
 	    (c->state_flags & StateFlag_no_bounds_check) == 0) {
 		BigInt i = exact_value_to_integer(operand.value).value_integer;
-		if (i.sign && !is_type_enum(index_type)) {
+		if (i.sign && !is_type_enum(index_type) && !is_type_multi_pointer(main_type)) {
 			gbString expr_str = expr_to_string(operand.expr);
 			error(operand.expr, "Index '%s' cannot be a negative value", expr_str);
 			gb_string_free(expr_str);
@@ -6102,6 +6146,13 @@ bool check_set_index_data(Operand *o, Type *t, bool indirection, i64 *max_count,
 		}
 		break;
 
+	case Type_MultiPointer:
+		o->type = t->MultiPointer.elem;
+		if (o->mode != Addressing_Constant) {
+			o->mode = Addressing_Variable;
+		}
+		return true;
+
 	case Type_Array:
 		*max_count = t->Array.count;
 		if (indirection) {
@@ -8133,13 +8184,9 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type
 		}
 
 		i64 index = 0;
-		bool ok = check_index_value(c, false, ie->index, max_count, &index, index_type_hint);
+		bool ok = check_index_value(c, t, false, ie->index, max_count, &index, index_type_hint);
 		if (is_const) {
 			if (index < 0) {
-				if (max_count < 0) {
-
-				}
-
 				gbString str = expr_to_string(o->expr);
 				error(o->expr, "Cannot index a constant '%s'", str);
 				error_line("\tSuggestion: store the constant into a variable in order to index it with a variable index\n");
@@ -8206,6 +8253,11 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type
 			o->type = alloc_type_slice(t->Array.elem);
 			break;
 
+		case Type_MultiPointer:
+			valid = true;
+			o->type = type_deref(o->type);
+			break;
+
 		case Type_Slice:
 			valid = true;
 			o->type = type_deref(o->type);
@@ -8262,7 +8314,7 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type
 					capacity = max_count;
 				}
 				i64 j = 0;
-				if (check_index_value(c, true, nodes[i], capacity, &j)) {
+				if (check_index_value(c, t, true, nodes[i], capacity, &j)) {
 					index = j;
 				}
 
@@ -8291,6 +8343,16 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type
 			}
 		}
 
+		if (t->kind == Type_MultiPointer && se->high != nullptr) {
+			/*
+				x[:]   -> [^]T
+				x[i:]  -> [^]T
+				x[:n]  -> []T
+				x[i:n] -> []T
+			*/
+			o->type = alloc_type_slice(t->MultiPointer.elem);
+		}
+
 		o->mode = Addressing_Value;
 
 		if (is_type_string(t) && max_count >= 0) {
@@ -8424,6 +8486,7 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type
 	case Ast_PolyType:
 	case Ast_ProcType:
 	case Ast_PointerType:
+	case Ast_MultiPointerType:
 	case Ast_ArrayType:
 	case Ast_DynamicArrayType:
 	case Ast_StructType:
@@ -8869,6 +8932,11 @@ gbString write_expr_to_string(gbString str, Ast *node, bool shorthand) {
 		str = write_expr_to_string(str, pt->type, shorthand);
 	case_end;
 
+	case_ast_node(pt, MultiPointerType, node);
+		str = gb_string_appendc(str, "[^]");
+		str = write_expr_to_string(str, pt->type, shorthand);
+	case_end;
+
 	case_ast_node(at, ArrayType, node);
 		str = gb_string_append_rune(str, '[');
 		if (at->count != nullptr &&

+ 6 - 0
src/check_type.cpp

@@ -2525,6 +2525,12 @@ bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, Type *named_t
 		return true;
 	case_end;
 
+	case_ast_node(pt, MultiPointerType, e);
+		*type = alloc_type_multi_pointer(check_type(ctx, pt->type));
+		set_base_type(named_type, *type);
+		return true;
+	case_end;
+
 	case_ast_node(rt, RelativeType, e);
 		GB_ASSERT(rt->tag->kind == Ast_CallExpr);
 		ast_node(ce, CallExpr, rt->tag);

+ 11 - 0
src/checker.cpp

@@ -1545,6 +1545,10 @@ void add_type_info_type_internal(CheckerContext *c, Type *t) {
 		add_type_info_type_internal(c, bt->Pointer.elem);
 		break;
 
+	case Type_MultiPointer:
+		add_type_info_type_internal(c, bt->MultiPointer.elem);
+		break;
+
 	case Type_Array:
 		add_type_info_type_internal(c, bt->Array.elem);
 		add_type_info_type_internal(c, alloc_type_pointer(bt->Array.elem));
@@ -1754,6 +1758,10 @@ void add_min_dep_type_info(Checker *c, Type *t) {
 		add_min_dep_type_info(c, bt->Pointer.elem);
 		break;
 
+	case Type_MultiPointer:
+		add_min_dep_type_info(c, bt->MultiPointer.elem);
+		break;
+
 	case Type_Array:
 		add_min_dep_type_info(c, bt->Array.elem);
 		add_min_dep_type_info(c, alloc_type_pointer(bt->Array.elem));
@@ -1993,6 +2001,7 @@ void generate_minimum_dependency_set(Checker *c, Entity *start) {
 			str_lit("bounds_check_error"),
 			str_lit("slice_expr_error_hi"),
 			str_lit("slice_expr_error_lo_hi"),
+			str_lit("multi_pointer_slice_expr_error"),
 		};
 		for (isize i = 0; i < gb_count_of(bounds_check_entities); i++) {
 			force_add_dependency_entity(c, c->info.runtime_package->scope, bounds_check_entities[i]);
@@ -2400,6 +2409,7 @@ void init_core_type_info(Checker *c) {
 	t_type_info_any              = find_core_type(c, str_lit("Type_Info_Any"));
 	t_type_info_typeid           = find_core_type(c, str_lit("Type_Info_Type_Id"));
 	t_type_info_pointer          = find_core_type(c, str_lit("Type_Info_Pointer"));
+	t_type_info_multi_pointer    = find_core_type(c, str_lit("Type_Info_Multi_Pointer"));
 	t_type_info_procedure        = find_core_type(c, str_lit("Type_Info_Procedure"));
 	t_type_info_array            = find_core_type(c, str_lit("Type_Info_Array"));
 	t_type_info_enumerated_array = find_core_type(c, str_lit("Type_Info_Enumerated_Array"));
@@ -2426,6 +2436,7 @@ void init_core_type_info(Checker *c) {
 	t_type_info_any_ptr              = alloc_type_pointer(t_type_info_any);
 	t_type_info_typeid_ptr           = alloc_type_pointer(t_type_info_typeid);
 	t_type_info_pointer_ptr          = alloc_type_pointer(t_type_info_pointer);
+	t_type_info_multi_pointer_ptr    = alloc_type_pointer(t_type_info_multi_pointer);
 	t_type_info_procedure_ptr        = alloc_type_pointer(t_type_info_procedure);
 	t_type_info_array_ptr            = alloc_type_pointer(t_type_info_array);
 	t_type_info_enumerated_array_ptr = alloc_type_pointer(t_type_info_enumerated_array);

+ 1 - 0
src/docs_format.cpp

@@ -81,6 +81,7 @@ enum OdinDocTypeKind : u32 {
 	OdinDocType_SOAStructDynamic = 19,
 	OdinDocType_RelativePointer  = 20,
 	OdinDocType_RelativeSlice    = 21,
+	OdinDocType_MultiPointer     = 22,
 };
 
 enum OdinDocTypeFlag_Basic : u32 {

+ 4 - 0
src/docs_writer.cpp

@@ -530,6 +530,10 @@ OdinDocTypeIndex odin_doc_type(OdinDocWriter *w, Type *type) {
 		doc_type.kind = OdinDocType_Pointer;
 		doc_type.types = odin_doc_type_as_slice(w, type->Pointer.elem);
 		break;
+	case Type_MultiPointer:
+		doc_type.kind = OdinDocType_MultiPointer;
+		doc_type.types = odin_doc_type_as_slice(w, type->MultiPointer.elem);
+		break;
 	case Type_Array:
 		doc_type.kind = OdinDocType_Array;
 		doc_type.elem_count_len = 1;

+ 56 - 0
src/llvm_backend_expr.cpp

@@ -1274,6 +1274,25 @@ lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) {
 		res.value = LLVMBuildPointerCast(p->builder, value.value, lb_type(m, t), "");
 		return res;
 	}
+	if (is_type_multi_pointer(src) && is_type_pointer(dst)) {
+		lbValue res = {};
+		res.type = t;
+		res.value = LLVMBuildPointerCast(p->builder, value.value, lb_type(m, t), "");
+		return res;
+	}
+	if (is_type_pointer(src) && is_type_multi_pointer(dst)) {
+		lbValue res = {};
+		res.type = t;
+		res.value = LLVMBuildPointerCast(p->builder, value.value, lb_type(m, t), "");
+		return res;
+	}
+	if (is_type_multi_pointer(src) && is_type_multi_pointer(dst)) {
+		lbValue res = {};
+		res.type = t;
+		res.value = LLVMBuildPointerCast(p->builder, value.value, lb_type(m, t), "");
+		return res;
+	}
+
 
 
 
@@ -1708,6 +1727,7 @@ lbValue lb_emit_comp(lbProcedure *p, TokenKind op_kind, lbValue left, lbValue ri
 	if (is_type_integer(a) ||
 	    is_type_boolean(a) ||
 	    is_type_pointer(a) ||
+	    is_type_multi_pointer(a) ||
 	    is_type_proc(a) ||
 	    is_type_enum(a)) {
 		LLVMIntPredicate pred = {};
@@ -2838,6 +2858,21 @@ lbAddr lb_build_addr(lbProcedure *p, Ast *expr) {
 			return lb_addr(v);
 		}
 
+		case Type_MultiPointer: {
+			lbValue multi_ptr = {};
+			multi_ptr = lb_build_expr(p, ie->expr);
+			if (deref) {
+				multi_ptr = lb_emit_load(p, multi_ptr);
+			}
+			lbValue index = lb_build_expr(p, ie->index);
+			lbValue v = {};
+
+			LLVMValueRef indices[1] = {index.value};
+			v.value = LLVMBuildGEP(p->builder, multi_ptr.value, indices, 1, "");
+			v.type = t;
+			return lb_addr(v);
+		}
+
 		case Type_RelativeSlice: {
 			lbAddr slice_addr = {};
 			if (deref) {
@@ -2952,6 +2987,27 @@ lbAddr lb_build_addr(lbProcedure *p, Ast *expr) {
 			return slice;
 		}
 
+		case Type_MultiPointer: {
+			lbAddr res = lb_add_local_generated(p, type_of_expr(expr), false);
+			if (se->high == nullptr) {
+				lbValue offset = base;
+				LLVMValueRef indices[1] = {low.value};
+				offset.value = LLVMBuildGEP(p->builder, offset.value, indices, 1, "");
+				lb_addr_store(p, res, offset);
+			} else {
+				lb_emit_multi_pointer_slice_bounds_check(p, se->open, low, high);
+
+				LLVMValueRef indices[1] = {low.value};
+				LLVMValueRef ptr = LLVMBuildGEP(p->builder, base.value, indices, 1, "");
+				LLVMValueRef len = LLVMBuildSub(p->builder, high.value, low.value, "");
+				// TODO(bill): bounds_check for negative length
+				LLVMValueRef gep0 = lb_emit_struct_ep(p, res.addr, 0).value;
+				LLVMValueRef gep1 = lb_emit_struct_ep(p, res.addr, 1).value;
+				LLVMBuildStore(p->builder, ptr, gep0);
+				LLVMBuildStore(p->builder, len, gep1);
+			}
+			return res;
+		}
 
 		case Type_Array: {
 			Type *slice_type = alloc_type_slice(type->Array.elem);

+ 35 - 0
src/llvm_backend_general.cpp

@@ -420,6 +420,31 @@ void lb_emit_bounds_check(lbProcedure *p, Token token, lbValue index, lbValue le
 	lb_emit_runtime_call(p, "bounds_check_error", args);
 }
 
+void lb_emit_multi_pointer_slice_bounds_check(lbProcedure *p, Token token, lbValue low, lbValue high) {
+	if (build_context.no_bounds_check) {
+		return;
+	}
+	if ((p->state_flags & StateFlag_no_bounds_check) != 0) {
+		return;
+	}
+
+	low = lb_emit_conv(p, low, t_int);
+	high = lb_emit_conv(p, high, t_int);
+
+	lbValue file = lb_find_or_add_entity_string(p->module, get_file_path_string(token.pos.file_id));
+	lbValue line = lb_const_int(p->module, t_i32, token.pos.line);
+	lbValue column = lb_const_int(p->module, t_i32, token.pos.column);
+
+	auto args = array_make<lbValue>(permanent_allocator(), 5);
+	args[0] = file;
+	args[1] = line;
+	args[2] = column;
+	args[3] = low;
+	args[4] = high;
+
+	lb_emit_runtime_call(p, "multi_pointer_slice_expr_error", args);
+}
+
 void lb_emit_slice_bounds_check(lbProcedure *p, Token token, lbValue low, lbValue high, lbValue len, bool lower_value_used) {
 	if (build_context.no_bounds_check) {
 		return;
@@ -810,6 +835,13 @@ LLVMTypeRef llvm_addr_type(lbValue addr_val) {
 
 lbValue lb_emit_load(lbProcedure *p, lbValue value) {
 	GB_ASSERT(value.value != nullptr);
+	if (is_type_multi_pointer(value.type)) {
+		Type *vt = base_type(value.type);
+		GB_ASSERT(vt->kind == Type_MultiPointer);
+		Type *t = vt->MultiPointer.elem;
+		LLVMValueRef v = LLVMBuildLoad2(p->builder, llvm_addr_type(value), value.value, "");
+		return lbValue{v, t};
+	}
 	GB_ASSERT(is_type_pointer(value.type));
 	Type *t = type_deref(value.type);
 	LLVMValueRef v = LLVMBuildLoad2(p->builder, llvm_addr_type(value), value.value, "");
@@ -1568,6 +1600,9 @@ LLVMTypeRef lb_type_internal(lbModule *m, Type *type) {
 	case Type_Pointer:
 		return LLVMPointerType(lb_type(m, type->Pointer.elem), 0);
 
+	case Type_MultiPointer:
+		return LLVMPointerType(lb_type(m, type->Pointer.elem), 0);
+
 	case Type_Array: {
 		m->internal_type_level -= 1;
 		LLVMTypeRef t = LLVMArrayType(lb_type(m, type->Array.elem), cast(unsigned)type->Array.count);

+ 15 - 0
src/llvm_backend_type.cpp

@@ -40,6 +40,7 @@ lbValue lb_typeid(lbModule *m, Type *type) {
 		if (flags & BasicFlag_Rune)     kind = Typeid_Rune;
 	} break;
 	case Type_Pointer:         kind = Typeid_Pointer;          break;
+	case Type_MultiPointer:    kind = Typeid_Multi_Pointer;    break;
 	case Type_Array:           kind = Typeid_Array;            break;
 	case Type_EnumeratedArray: kind = Typeid_Enumerated_Array; break;
 	case Type_Slice:           kind = Typeid_Slice;            break;
@@ -396,6 +397,20 @@ void lb_setup_type_info_data(lbProcedure *p) { // NOTE(bill): Setup type_info da
 			lb_emit_store(p, tag, res);
 			break;
 		}
+		case Type_MultiPointer: {
+			tag = lb_const_ptr_cast(m, variant_ptr, t_type_info_multi_pointer_ptr);
+			lbValue gep = lb_get_type_info_ptr(m, t->MultiPointer.elem);
+
+			LLVMValueRef vals[1] = {
+				gep.value,
+			};
+
+			lbValue res = {};
+			res.type = type_deref(tag.type);
+			res.value = llvm_const_named_struct(lb_type(m, res.type), vals, gb_count_of(vals));
+			lb_emit_store(p, tag, res);
+			break;
+		}
 		case Type_Array: {
 			tag = lb_const_ptr_cast(m, variant_ptr, t_type_info_array_ptr);
 			i64 ez = type_size_of(t->Array.elem);

+ 32 - 10
src/parser.cpp

@@ -337,6 +337,9 @@ Ast *clone_ast(Ast *node) {
 	case Ast_PointerType:
 		n->PointerType.type = clone_ast(n->PointerType.type);
 		break;
+	case Ast_MultiPointerType:
+		n->MultiPointerType.type = clone_ast(n->MultiPointerType.type);
+		break;
 	case Ast_ArrayType:
 		n->ArrayType.count = clone_ast(n->ArrayType.count);
 		n->ArrayType.elem  = clone_ast(n->ArrayType.elem);
@@ -985,7 +988,12 @@ Ast *ast_pointer_type(AstFile *f, Token token, Ast *type) {
 	result->PointerType.type = type;
 	return result;
 }
-
+Ast *ast_multi_pointer_type(AstFile *f, Token token, Ast *type) {
+	Ast *result = alloc_ast_node(f, Ast_MultiPointerType);
+	result->MultiPointerType.token = token;
+	result->MultiPointerType.type = type;
+	return result;
+}
 Ast *ast_array_type(AstFile *f, Token token, Ast *count, Ast *elem) {
 	Ast *result = alloc_ast_node(f, Ast_ArrayType);
 	result->ArrayType.token = token;
@@ -2317,7 +2325,11 @@ Ast *parse_operand(AstFile *f, bool lhs) {
 	case Token_OpenBracket: {
 		Token token = expect_token(f, Token_OpenBracket);
 		Ast *count_expr = nullptr;
-		if (f->curr_token.kind == Token_Question) {
+		if (f->curr_token.kind == Token_Pointer) {
+			expect_token(f, Token_Pointer);
+			expect_token(f, Token_CloseBracket);
+			return ast_multi_pointer_type(f, token, parse_type(f));
+		} else if (f->curr_token.kind == Token_Question) {
 			count_expr = ast_unary_expr(f, expect_token(f, Token_Question), nullptr);
 		} else if (allow_token(f, Token_dynamic)) {
 			expect_token(f, Token_CloseBracket);
@@ -4133,16 +4145,26 @@ Ast *parse_for_stmt(AstFile *f) {
 		if (!is_range && parse_control_statement_semicolon_separator(f)) {
 			init = cond;
 			cond = nullptr;
-			if (f->curr_token.kind != Token_Semicolon) {
-				cond = parse_simple_stmt(f, StmtAllowFlag_None);
-			}
-			expect_semicolon(f, cond);
-			if (f->curr_token.kind != Token_OpenBrace &&
-			    f->curr_token.kind != Token_do) {
-				post = parse_simple_stmt(f, StmtAllowFlag_None);
+
+			if (f->curr_token.kind == Token_OpenBrace || f->curr_token.kind == Token_do) {
+				syntax_error(f->curr_token, "Expected ';', followed by a condition expression and post statement, got %.*s", LIT(token_strings[f->curr_token.kind]));
+			} else {
+				if (f->curr_token.kind != Token_Semicolon) {
+					cond = parse_simple_stmt(f, StmtAllowFlag_None);
+				}
+
+				if (f->curr_token.string != ";") {
+					syntax_error(f->curr_token, "Expected ';', got %.*s", LIT(token_to_string(f->curr_token)));
+				} else {
+					expect_semicolon(f, nullptr);
+				}
+
+				if (f->curr_token.kind != Token_OpenBrace &&
+				    f->curr_token.kind != Token_do) {
+					post = parse_simple_stmt(f, StmtAllowFlag_None);
+				}
 			}
 		}
-
 	}
 
 

+ 4 - 0
src/parser.hpp

@@ -604,6 +604,10 @@ AST_KIND(_TypeBegin, "", bool) \
 		Ast *tag; \
 		Ast *type; \
 	}) \
+	AST_KIND(MultiPointerType, "multi pointer type", struct { \
+		Token token; \
+		Ast *type; \
+	}) \
 	AST_KIND(ArrayType, "array type", struct { \
 		Token token; \
 		Ast *count; \

+ 2 - 0
src/parser_pos.cpp

@@ -95,6 +95,7 @@ Token ast_token(Ast *node) {
 	case Ast_ProcType:         return node->ProcType.token;
 	case Ast_RelativeType:     return ast_token(node->RelativeType.tag);
 	case Ast_PointerType:      return node->PointerType.token;
+	case Ast_MultiPointerType: return node->MultiPointerType.token;
 	case Ast_ArrayType:        return node->ArrayType.token;
 	case Ast_DynamicArrayType: return node->DynamicArrayType.token;
 	case Ast_StructType:       return node->StructType.token;
@@ -312,6 +313,7 @@ Token ast_end_token(Ast *node) {
 	case Ast_RelativeType:
 		return ast_end_token(node->RelativeType.type);
 	case Ast_PointerType:      return ast_end_token(node->PointerType.type);
+	case Ast_MultiPointerType: return ast_end_token(node->MultiPointerType.type);
 	case Ast_ArrayType:        return ast_end_token(node->ArrayType.elem);
 	case Ast_DynamicArrayType: return ast_end_token(node->DynamicArrayType.elem);
 	case Ast_StructType:

+ 77 - 33
src/types.cpp

@@ -192,47 +192,48 @@ struct TypeProc {
 	bool     optional_ok;
 };
 
-#define TYPE_KINDS                                        \
-	TYPE_KIND(Basic, BasicType)                           \
-	TYPE_KIND(Named, struct {                             \
+#define TYPE_KINDS                                                \
+	TYPE_KIND(Basic, BasicType)                               \
+	TYPE_KIND(Named, struct {                                 \
 		String  name;                                     \
 		Type *  base;                                     \
 		Entity *type_name; /* Entity_TypeName */          \
-	})                                                    \
-	TYPE_KIND(Generic, struct {                           \
+	})                                                        \
+	TYPE_KIND(Generic, struct {                               \
 		i64     id;                                       \
 		String  name;                                     \
 		Type *  specialized;                              \
 		Scope * scope;                                    \
 		Entity *entity;                                   \
-	})                                                    \
-	TYPE_KIND(Pointer, struct { Type *elem; })            \
-	TYPE_KIND(Array,   struct {                           \
+	})                                                        \
+	TYPE_KIND(Pointer, struct { Type *elem; })                \
+	TYPE_KIND(MultiPointer, struct { Type *elem; })           \
+	TYPE_KIND(Array,   struct {                               \
 		Type *elem;                                       \
 		i64   count;                                      \
 		Type *generic_count;                              \
-	})                                                    \
-	TYPE_KIND(EnumeratedArray, struct {                   \
+	})                                                        \
+	TYPE_KIND(EnumeratedArray, struct {                       \
 		Type *elem;                                       \
 		Type *index;                                      \
 		ExactValue min_value;                             \
 		ExactValue max_value;                             \
 		i64 count;                                        \
 		TokenKind op;                                     \
-	})                                                    \
-	TYPE_KIND(Slice,   struct { Type *elem; })            \
-	TYPE_KIND(DynamicArray, struct { Type *elem; })       \
-	TYPE_KIND(Map, struct {                               \
+	})                                                        \
+	TYPE_KIND(Slice,   struct { Type *elem; })                \
+	TYPE_KIND(DynamicArray, struct { Type *elem; })           \
+	TYPE_KIND(Map, struct {                                   \
 		Type *key;                                        \
 		Type *value;                                      \
 		Type *entry_type;                                 \
 		Type *generated_struct_type;                      \
 		Type *internal_type;                              \
 		Type *lookup_result_type;                         \
-	})                                                    \
-	TYPE_KIND(Struct,  TypeStruct)                        \
-	TYPE_KIND(Union,   TypeUnion)                         \
-	TYPE_KIND(Enum, struct {                              \
+	})                                                        \
+	TYPE_KIND(Struct,  TypeStruct)                            \
+	TYPE_KIND(Union,   TypeUnion)                             \
+	TYPE_KIND(Enum, struct {                                  \
 		Array<Entity *> fields;                           \
 		Ast *node;                                        \
 		Scope *  scope;                                   \
@@ -242,31 +243,31 @@ struct TypeProc {
 		ExactValue max_value;                             \
 		isize min_value_index;                            \
 		isize max_value_index;                            \
-	})                                                    \
-	TYPE_KIND(Tuple, struct {                             \
+	})                                                        \
+	TYPE_KIND(Tuple, struct {                                 \
 		Array<Entity *> variables; /* Entity_Variable */  \
 		Array<i64>      offsets;                          \
 		bool            are_offsets_being_processed;      \
 		bool            are_offsets_set;                  \
 		bool            is_packed;                        \
-	})                                                    \
-	TYPE_KIND(Proc, TypeProc)                             \
-	TYPE_KIND(BitSet, struct {                            \
+	})                                                        \
+	TYPE_KIND(Proc, TypeProc)                                 \
+	TYPE_KIND(BitSet, struct {                                \
 		Type *elem;                                       \
 		Type *underlying;                                 \
 		i64   lower;                                      \
 		i64   upper;                                      \
 		Ast * node;                                       \
-	})                                                    \
-	TYPE_KIND(SimdVector, struct {                        \
+	})                                                        \
+	TYPE_KIND(SimdVector, struct {                            \
 		i64   count;                                      \
 		Type *elem;                                       \
-	})                                                    \
-	TYPE_KIND(RelativePointer, struct {                   \
+	})                                                        \
+	TYPE_KIND(RelativePointer, struct {                       \
 		Type *pointer_type;                               \
 		Type *base_integer;                               \
-	})                                                    \
-	TYPE_KIND(RelativeSlice, struct {                     \
+	})                                                        \
+	TYPE_KIND(RelativeSlice, struct {                         \
 		Type *slice_type;                                 \
 		Type *base_integer;                               \
 	})
@@ -325,6 +326,7 @@ enum Typeid_Kind : u8 {
 	Typeid_Any,
 	Typeid_Type_Id,
 	Typeid_Pointer,
+	Typeid_Multi_Pointer,
 	Typeid_Procedure,
 	Typeid_Array,
 	Typeid_Enumerated_Array,
@@ -605,6 +607,7 @@ gb_global Type *t_type_info_typeid               = nullptr;
 gb_global Type *t_type_info_string               = nullptr;
 gb_global Type *t_type_info_boolean              = nullptr;
 gb_global Type *t_type_info_pointer              = nullptr;
+gb_global Type *t_type_info_multi_pointer        = nullptr;
 gb_global Type *t_type_info_procedure            = nullptr;
 gb_global Type *t_type_info_array                = nullptr;
 gb_global Type *t_type_info_enumerated_array     = nullptr;
@@ -631,6 +634,7 @@ gb_global Type *t_type_info_typeid_ptr           = nullptr;
 gb_global Type *t_type_info_string_ptr           = nullptr;
 gb_global Type *t_type_info_boolean_ptr          = nullptr;
 gb_global Type *t_type_info_pointer_ptr          = nullptr;
+gb_global Type *t_type_info_multi_pointer_ptr    = nullptr;
 gb_global Type *t_type_info_procedure_ptr        = nullptr;
 gb_global Type *t_type_info_array_ptr            = nullptr;
 gb_global Type *t_type_info_enumerated_array_ptr = nullptr;
@@ -779,6 +783,12 @@ Type *alloc_type_pointer(Type *elem) {
 	return t;
 }
 
+Type *alloc_type_multi_pointer(Type *elem) {
+	Type *t = alloc_type(Type_MultiPointer);
+	t->MultiPointer.elem = elem;
+	return t;
+}
+
 Type *alloc_type_array(Type *elem, i64 count, Type *generic_count = nullptr) {
 	if (generic_count != nullptr) {
 		Type *t = alloc_type(Type_Array);
@@ -948,10 +958,10 @@ Type *type_deref(Type *t) {
 		if (bt == nullptr) {
 			return nullptr;
 		}
-		if (bt != nullptr && bt->kind == Type_Pointer) {
+		if (bt->kind == Type_Pointer) {
 			return bt->Pointer.elem;
 		}
-		if (bt != nullptr && bt->kind == Type_RelativePointer) {
+		if (bt->kind == Type_RelativePointer) {
 			return type_deref(bt->RelativePointer.pointer_type);
 		}
 	}
@@ -1084,6 +1094,8 @@ bool is_type_ordered(Type *t) {
 		return (t->Basic.flags & BasicFlag_Ordered) != 0;
 	case Type_Pointer:
 		return true;
+	case Type_MultiPointer:
+		return true;
 	}
 	return false;
 }
@@ -1157,6 +1169,10 @@ bool is_type_pointer(Type *t) {
 	}
 	return t->kind == Type_Pointer;
 }
+bool is_type_multi_pointer(Type *t) {
+	t = base_type(t);
+	return t->kind == Type_MultiPointer;
+}
 bool is_type_tuple(Type *t) {
 	t = base_type(t);
 	return t->kind == Type_Tuple;
@@ -1259,6 +1275,13 @@ bool is_type_u8_ptr(Type *t) {
 	}
 	return false;
 }
+bool is_type_u8_multi_ptr(Type *t) {
+	t = base_type(t);
+	if (t->kind == Type_MultiPointer) {
+		return is_type_u8(t->Slice.elem);
+	}
+	return false;
+}
 bool is_type_rune_array(Type *t) {
 	t = base_type(t);
 	if (t->kind == Type_Array) {
@@ -1348,7 +1371,8 @@ bool is_type_union_maybe_pointer(Type *t) {
 	t = base_type(t);
 	if (t->kind == Type_Union && t->Union.maybe) {
 		if (t->Union.variants.count == 1) {
-			return is_type_pointer(t->Union.variants[0]);
+			Type *v = t->Union.variants[0];
+			return is_type_pointer(v) || is_type_multi_pointer(v);
 		}
 	}
 	return false;
@@ -1360,7 +1384,7 @@ bool is_type_union_maybe_pointer_original_alignment(Type *t) {
 	if (t->kind == Type_Union && t->Union.maybe) {
 		if (t->Union.variants.count == 1) {
 			Type *v = t->Union.variants[0];
-			if (is_type_pointer(v)) {
+			if (is_type_pointer(v) || is_type_multi_pointer(v)) {
 				return type_align_of(v) == type_align_of(t);
 			}
 		}
@@ -1614,6 +1638,8 @@ bool is_type_indexable(Type *t) {
 	case Type_DynamicArray:
 	case Type_Map:
 		return true;
+	case Type_MultiPointer:
+		return true;
 	case Type_EnumeratedArray:
 		return true;
 	case Type_RelativeSlice:
@@ -1836,6 +1862,7 @@ bool type_has_nil(Type *t) {
 	case Type_Slice:
 	case Type_Proc:
 	case Type_Pointer:
+	case Type_MultiPointer:
 	case Type_DynamicArray:
 	case Type_Map:
 		return true;
@@ -1890,6 +1917,8 @@ bool is_type_comparable(Type *t) {
 		return true;
 	case Type_Pointer:
 		return true;
+	case Type_MultiPointer:
+		return true;
 	case Type_Enum:
 		return is_type_comparable(core_type(t));
 	case Type_EnumeratedArray:
@@ -1955,6 +1984,7 @@ bool is_type_simple_compare(Type *t) {
 		return false;
 
 	case Type_Pointer:
+	case Type_MultiPointer:
 	case Type_Proc:
 	case Type_BitSet:
 		return true;
@@ -2157,6 +2187,12 @@ bool are_types_identical(Type *x, Type *y) {
 		}
 		break;
 
+	case Type_MultiPointer:
+		if (y->kind == Type_MultiPointer) {
+			return are_types_identical(x->MultiPointer.elem, y->MultiPointer.elem);
+		}
+		break;
+
 	case Type_Named:
 		if (y->kind == Type_Named) {
 			return x->Named.type_name == y->Named.type_name;
@@ -3139,6 +3175,9 @@ i64 type_size_of_internal(Type *t, TypePath *path) {
 	case Type_Pointer:
 		return build_context.word_size;
 
+	case Type_MultiPointer:
+		return build_context.word_size;
+
 	case Type_Array: {
 		i64 count, align, size, alignment;
 		count = t->Array.count;
@@ -3509,6 +3548,11 @@ gbString write_type_to_string(gbString str, Type *type) {
 		str = write_type_to_string(str, type->Pointer.elem);
 		break;
 
+	case Type_MultiPointer:
+		str = gb_string_appendc(str, "[^]");
+		str = write_type_to_string(str, type->Pointer.elem);
+		break;
+
 	case Type_EnumeratedArray:
 		str = gb_string_append_rune(str, '[');
 		str = write_type_to_string(str, type->EnumeratedArray.index);