Browse Source

Add `union #maybe`

gingerBill 5 years ago
parent
commit
0f399a7294
10 changed files with 241 additions and 63 deletions
  1. 12 1
      core/fmt/fmt.odin
  2. 2 1
      core/runtime/core.odin
  3. 23 0
      examples/demo/demo.odin
  4. 61 37
      src/check_expr.cpp
  5. 6 0
      src/check_type.cpp
  6. 44 10
      src/ir.cpp
  7. 28 4
      src/ir_print.cpp
  8. 20 3
      src/parser.cpp
  9. 1 0
      src/parser.hpp
  10. 44 7
      src/types.cpp

+ 12 - 1
core/fmt/fmt.odin

@@ -1525,10 +1525,21 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) {
 			return;
 			return;
 		}
 		}
 
 
+
+		if info.maybe && len(info.variants) == 1 && reflect.is_pointer(info.variants[0]) {
+			if v.data == nil {
+				strings.write_string(fi.buf, "nil");
+			} else {
+				id := info.variants[0].id;
+				fmt_arg(fi, any{v.data, id}, verb);
+			}
+			return;
+		}
+
+		tag: i64 = -1;
 		tag_ptr := uintptr(v.data) + info.tag_offset;
 		tag_ptr := uintptr(v.data) + info.tag_offset;
 		tag_any := any{rawptr(tag_ptr), info.tag_type.id};
 		tag_any := any{rawptr(tag_ptr), info.tag_type.id};
 
 
-		tag: i64 = -1;
 		switch i in tag_any {
 		switch i in tag_any {
 		case u8:   tag = i64(i);
 		case u8:   tag = i64(i);
 		case i8:   tag = i64(i);
 		case i8:   tag = i64(i);

+ 2 - 1
core/runtime/core.odin

@@ -111,6 +111,7 @@ Type_Info_Union :: struct {
 	tag_type:     ^Type_Info,
 	tag_type:     ^Type_Info,
 	custom_align: bool,
 	custom_align: bool,
 	no_nil:       bool,
 	no_nil:       bool,
+	maybe:        bool,
 };
 };
 Type_Info_Enum :: struct {
 Type_Info_Enum :: struct {
 	base:      ^Type_Info,
 	base:      ^Type_Info,
@@ -1131,7 +1132,7 @@ __dynamic_array_reserve :: proc(array_: rawptr, elem_size, elem_align: int, cap:
 		array.allocator = context.allocator;
 		array.allocator = context.allocator;
 	}
 	}
 	assert(array.allocator.procedure != nil);
 	assert(array.allocator.procedure != nil);
-	
+
 	if cap <= array.cap do return true;
 	if cap <= array.cap do return true;
 
 
 	old_size  := array.cap * elem_size;
 	old_size  := array.cap * elem_size;

+ 23 - 0
examples/demo/demo.odin

@@ -1907,6 +1907,28 @@ constant_literal_expressions :: proc() {
 	fmt.println(STRING_CONST[3:][:4]);
 	fmt.println(STRING_CONST[3:][:4]);
 }
 }
 
 
+union_maybe :: proc() {
+	fmt.println("\n#union #maybe");
+
+	Maybe :: union(T: typeid) #maybe {T};
+
+	i: Maybe(u8);
+	p: Maybe(^u8); // No tag is stored for pointers, nil is the sentinel value
+
+	#assert(size_of(i) == size_of(u8) + size_of(u8));
+	#assert(size_of(p) == size_of(^u8));
+
+	i = 123;
+	x := i.?;
+	y, y_ok := p.?;
+	p = &x;
+	z, z_ok := p.?;
+
+	fmt.println(i, p);
+	fmt.println(x, &x);
+	fmt.println(y, y_ok);
+	fmt.println(z, z_ok);
+}
 
 
 main :: proc() {
 main :: proc() {
 	when true {
 	when true {
@@ -1937,5 +1959,6 @@ main :: proc() {
 		threading_example();
 		threading_example();
 		soa_struct_layout();
 		soa_struct_layout();
 		constant_literal_expressions();
 		constant_literal_expressions();
+		union_maybe();
 	}
 	}
 }
 }

+ 61 - 37
src/check_expr.cpp

@@ -8572,8 +8572,6 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type
 			o->expr = node;
 			o->expr = node;
 			return kind;
 			return kind;
 		}
 		}
-		Type *t = check_type(c, ta->type);
-
 		if (o->mode == Addressing_Constant) {
 		if (o->mode == Addressing_Constant) {
 			gbString expr_str = expr_to_string(o->expr);
 			gbString expr_str = expr_to_string(o->expr);
 			error(o->expr, "A type assertion cannot be applied to a constant expression: '%s'", expr_str);
 			error(o->expr, "A type assertion cannot be applied to a constant expression: '%s'", expr_str);
@@ -8594,54 +8592,80 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type
 
 
 		bool src_is_ptr = is_type_pointer(o->type);
 		bool src_is_ptr = is_type_pointer(o->type);
 		Type *src = type_deref(o->type);
 		Type *src = type_deref(o->type);
-		Type *dst = t;
 		Type *bsrc = base_type(src);
 		Type *bsrc = base_type(src);
-		Type *bdst = base_type(dst);
 
 
 
 
-		if (is_type_union(src)) {
-			bool ok = false;
-			for_array(i, bsrc->Union.variants) {
-				Type *vt = bsrc->Union.variants[i];
-				if (are_types_identical(vt, dst)) {
-					ok = true;
-					break;
-				}
+		if (ta->type != nullptr && ta->type->kind == Ast_UnaryExpr && ta->type->UnaryExpr.op.kind == Token_Question) {
+			if (!is_type_union(src)) {
+				gbString str = type_to_string(o->type);
+				error(o->expr, "Type assertions with .? can only operate on unions with 1 variant, got %s", str);
+				gb_string_free(str);
+				o->mode = Addressing_Invalid;
+				o->expr = node;
+				return kind;
 			}
 			}
-
-			if (!ok) {
-				gbString expr_str = expr_to_string(o->expr);
-				gbString dst_type_str = type_to_string(t);
-				defer (gb_string_free(expr_str));
-				defer (gb_string_free(dst_type_str));
-				if (bsrc->Union.variants.count == 0) {
-					error(o->expr, "Cannot type assert '%s' to '%s' as this is an empty union", expr_str, dst_type_str);
-				} else {
-					error(o->expr, "Cannot type assert '%s' to '%s' as it is not a variant of that union", expr_str, dst_type_str);
-				}
+			if (bsrc->Union.variants.count != 1) {
+				error(o->expr, "Type assertions with .? can only operate on unions with 1 variant, got %lld", cast(long long)bsrc->Union.variants.count);
 				o->mode = Addressing_Invalid;
 				o->mode = Addressing_Invalid;
 				o->expr = node;
 				o->expr = node;
 				return kind;
 				return kind;
 			}
 			}
 
 
 			add_type_info_type(c, o->type);
 			add_type_info_type(c, o->type);
-			add_type_info_type(c, t);
+			add_type_info_type(c, bsrc->Union.variants[0]);
 
 
-			o->type = t;
-			o->mode = Addressing_OptionalOk;
-		} else if (is_type_any(src)) {
-			o->type = t;
+			o->type = bsrc->Union.variants[0];
 			o->mode = Addressing_OptionalOk;
 			o->mode = Addressing_OptionalOk;
-
-			add_type_info_type(c, o->type);
-			add_type_info_type(c, t);
 		} else {
 		} else {
-			gbString str = type_to_string(o->type);
-			error(o->expr, "Type assertions can only operate on unions and 'any', got %s", str);
-			gb_string_free(str);
-			o->mode = Addressing_Invalid;
-			o->expr = node;
-			return kind;
+			Type *t = check_type(c, ta->type);
+			Type *dst = t;
+			Type *bdst = base_type(dst);
+
+
+			if (is_type_union(src)) {
+				bool ok = false;
+				for_array(i, bsrc->Union.variants) {
+					Type *vt = bsrc->Union.variants[i];
+					if (are_types_identical(vt, dst)) {
+						ok = true;
+						break;
+					}
+				}
+
+				if (!ok) {
+					gbString expr_str = expr_to_string(o->expr);
+					gbString dst_type_str = type_to_string(t);
+					defer (gb_string_free(expr_str));
+					defer (gb_string_free(dst_type_str));
+					if (bsrc->Union.variants.count == 0) {
+						error(o->expr, "Cannot type assert '%s' to '%s' as this is an empty union", expr_str, dst_type_str);
+					} else {
+						error(o->expr, "Cannot type assert '%s' to '%s' as it is not a variant of that union", expr_str, dst_type_str);
+					}
+					o->mode = Addressing_Invalid;
+					o->expr = node;
+					return kind;
+				}
+
+				add_type_info_type(c, o->type);
+				add_type_info_type(c, t);
+
+				o->type = t;
+				o->mode = Addressing_OptionalOk;
+			} else if (is_type_any(src)) {
+				o->type = t;
+				o->mode = Addressing_OptionalOk;
+
+				add_type_info_type(c, o->type);
+				add_type_info_type(c, t);
+			} else {
+				gbString str = type_to_string(o->type);
+				error(o->expr, "Type assertions can only operate on unions and 'any', got %s", str);
+				gb_string_free(str);
+				o->mode = Addressing_Invalid;
+				o->expr = node;
+				return kind;
+			}
 		}
 		}
 
 
 		add_package_dependency(c, "runtime", "type_assertion_check");
 		add_package_dependency(c, "runtime", "type_assertion_check");

+ 6 - 0
src/check_type.cpp

@@ -748,11 +748,17 @@ void check_union_type(CheckerContext *ctx, Type *union_type, Ast *node, Array<Op
 
 
 	union_type->Union.variants = variants;
 	union_type->Union.variants = variants;
 	union_type->Union.no_nil = ut->no_nil;
 	union_type->Union.no_nil = ut->no_nil;
+	union_type->Union.maybe = ut->maybe;
 	if (union_type->Union.no_nil) {
 	if (union_type->Union.no_nil) {
 		if (variants.count < 2) {
 		if (variants.count < 2) {
 			error(ut->align, "A union with #no_nil must have at least 2 variants");
 			error(ut->align, "A union with #no_nil must have at least 2 variants");
 		}
 		}
 	}
 	}
+	if (union_type->Union.maybe) {
+		if (variants.count != 1) {
+			error(ut->align, "A union with #maybe must have at 1 variant, got %lld", cast(long long)variants.count);
+		}
+	}
 
 
 	if (ut->align != nullptr) {
 	if (ut->align != nullptr) {
 		i64 custom_align = 1;
 		i64 custom_align = 1;

+ 44 - 10
src/ir.cpp

@@ -1222,7 +1222,11 @@ irValue *ir_instr_union_tag_ptr(irProcedure *p, irValue *address) {
 
 
 	// i->UnionTagPtr.type = alloc_type_pointer(t_type_info_ptr);
 	// i->UnionTagPtr.type = alloc_type_pointer(t_type_info_ptr);
 	Type *u = type_deref(ir_type(address));
 	Type *u = type_deref(ir_type(address));
+	if (is_type_union_maybe_pointer(u)) {
+		GB_PANIC("union #maybe UnionTagPtr");
+	}
 	i->UnionTagPtr.type = alloc_type_pointer(union_tag_type(u));
 	i->UnionTagPtr.type = alloc_type_pointer(union_tag_type(u));
+
 	return v;
 	return v;
 }
 }
 
 
@@ -1234,6 +1238,9 @@ irValue *ir_instr_union_tag_value(irProcedure *p, irValue *address) {
 	if (address) address->uses += 1;
 	if (address) address->uses += 1;
 
 
 	Type *u = type_deref(ir_type(address));
 	Type *u = type_deref(ir_type(address));
+	if (is_type_union_maybe_pointer(u)) {
+		GB_PANIC("union #maybe UnionTagValue");
+	}
 	i->UnionTagPtr.type = union_tag_type(u);
 	i->UnionTagPtr.type = union_tag_type(u);
 	return v;
 	return v;
 }
 }
@@ -4351,7 +4358,9 @@ irValue *ir_emit_union_tag_value(irProcedure *proc, irValue *u) {
 
 
 irValue *ir_emit_comp_against_nil(irProcedure *proc, TokenKind op_kind, irValue *x) {
 irValue *ir_emit_comp_against_nil(irProcedure *proc, TokenKind op_kind, irValue *x) {
 	Type *t = ir_type(x);
 	Type *t = ir_type(x);
-	if (is_type_cstring(t)) {
+	if (is_type_pointer(t)) {
+		return ir_emit_comp(proc, op_kind, x, v_raw_nil);
+	} else if (is_type_cstring(t)) {
 		irValue *ptr = ir_emit_conv(proc, x, t_u8_ptr);
 		irValue *ptr = ir_emit_conv(proc, x, t_u8_ptr);
 		return ir_emit_comp(proc, op_kind, ptr, v_raw_nil);
 		return ir_emit_comp(proc, op_kind, ptr, v_raw_nil);
 	} else if (is_type_any(t)) {
 	} else if (is_type_any(t)) {
@@ -5232,8 +5241,12 @@ void ir_emit_store_union_variant(irProcedure *proc, irValue *parent, irValue *va
 
 
 	Type *t = type_deref(ir_type(parent));
 	Type *t = type_deref(ir_type(parent));
 
 
-	irValue *tag_ptr = ir_emit_union_tag_ptr(proc, parent);
-	ir_emit_store(proc, tag_ptr, ir_const_union_tag(t, variant_type));
+	if (is_type_union_maybe_pointer(t)) {
+		// No tag needed!
+	} else {
+		irValue *tag_ptr = ir_emit_union_tag_ptr(proc, parent);
+		ir_emit_store(proc, tag_ptr, ir_const_union_tag(t, variant_type));
+	}
 }
 }
 
 
 irValue *ir_emit_conv(irProcedure *proc, irValue *value, Type *t) {
 irValue *ir_emit_conv(irProcedure *proc, irValue *value, Type *t) {
@@ -5728,22 +5741,41 @@ irValue *ir_emit_union_cast(irProcedure *proc, irValue *value, Type *type, Token
 	GB_ASSERT_MSG(is_type_union(src), "%s", type_to_string(src_type));
 	GB_ASSERT_MSG(is_type_union(src), "%s", type_to_string(src_type));
 	Type *dst = tuple->Tuple.variables[0]->type;
 	Type *dst = tuple->Tuple.variables[0]->type;
 
 
-
 	irValue *value_  = ir_address_from_load_or_generate_local(proc, value);
 	irValue *value_  = ir_address_from_load_or_generate_local(proc, value);
-	irValue *tag     = ir_emit_load(proc, ir_emit_union_tag_ptr(proc, value_));
-	irValue *dst_tag = ir_const_union_tag(src, dst);
 
 
+	irValue *tag = nullptr;
+	irValue *dst_tag = nullptr;
+	irValue *cond = nullptr;
+	irValue *data = nullptr;
+
+	irValue *gep0 = ir_emit_struct_ep(proc, v, 0);
+	irValue *gep1 = ir_emit_struct_ep(proc, v, 1);
+
+	if (is_type_union_maybe_pointer(src)) {
+		data = ir_emit_load(proc, ir_emit_conv(proc, value_, ir_type(gep0)));
+	} else {
+		tag     = ir_emit_load(proc, ir_emit_union_tag_ptr(proc, value_));
+		dst_tag = ir_const_union_tag(src, dst);
+	}
 
 
 	irBlock *ok_block = ir_new_block(proc, nullptr, "union_cast.ok");
 	irBlock *ok_block = ir_new_block(proc, nullptr, "union_cast.ok");
 	irBlock *end_block = ir_new_block(proc, nullptr, "union_cast.end");
 	irBlock *end_block = ir_new_block(proc, nullptr, "union_cast.end");
-	irValue *cond = ir_emit_comp(proc, Token_CmpEq, tag, dst_tag);
+
+	if (data != nullptr) {
+		GB_ASSERT(is_type_union_maybe_pointer(src));
+		cond = ir_emit_comp_against_nil(proc, Token_NotEq, data);
+	} else {
+		cond = ir_emit_comp(proc, Token_CmpEq, tag, dst_tag);
+	}
+
 	ir_emit_if(proc, cond, ok_block, end_block);
 	ir_emit_if(proc, cond, ok_block, end_block);
 	ir_start_block(proc, ok_block);
 	ir_start_block(proc, ok_block);
 
 
-	irValue *gep0 = ir_emit_struct_ep(proc, v, 0);
-	irValue *gep1 = ir_emit_struct_ep(proc, v, 1);
 
 
-	irValue *data = ir_emit_load(proc, ir_emit_conv(proc, value_, ir_type(gep0)));
+
+	if (data == nullptr) {
+		data = ir_emit_load(proc, ir_emit_conv(proc, value_, ir_type(gep0)));
+	}
 	ir_emit_store(proc, gep0, data);
 	ir_emit_store(proc, gep0, data);
 	ir_emit_store(proc, gep1, v_true);
 	ir_emit_store(proc, gep1, v_true);
 
 
@@ -11465,6 +11497,7 @@ void ir_setup_type_info_data(irProcedure *proc) { // NOTE(bill): Setup type_info
 				irValue *tag_type_ptr     = ir_emit_struct_ep(proc, tag, 2);
 				irValue *tag_type_ptr     = ir_emit_struct_ep(proc, tag, 2);
 				irValue *custom_align_ptr = ir_emit_struct_ep(proc, tag, 3);
 				irValue *custom_align_ptr = ir_emit_struct_ep(proc, tag, 3);
 				irValue *no_nil_ptr       = ir_emit_struct_ep(proc, tag, 4);
 				irValue *no_nil_ptr       = ir_emit_struct_ep(proc, tag, 4);
+				irValue *maybe_ptr        = ir_emit_struct_ep(proc, tag, 5);
 
 
 				isize variant_count = gb_max(0, t->Union.variants.count);
 				isize variant_count = gb_max(0, t->Union.variants.count);
 				irValue *memory_types = ir_type_info_member_types_offset(proc, variant_count);
 				irValue *memory_types = ir_type_info_member_types_offset(proc, variant_count);
@@ -11494,6 +11527,7 @@ void ir_setup_type_info_data(irProcedure *proc) { // NOTE(bill): Setup type_info
 				ir_emit_store(proc, custom_align_ptr, is_custom_align);
 				ir_emit_store(proc, custom_align_ptr, is_custom_align);
 
 
 				ir_emit_store(proc, no_nil_ptr, ir_const_bool(t->Union.no_nil));
 				ir_emit_store(proc, no_nil_ptr, ir_const_bool(t->Union.no_nil));
+				ir_emit_store(proc, maybe_ptr, ir_const_bool(t->Union.maybe));
 			}
 			}
 
 
 			break;
 			break;

+ 28 - 4
src/ir_print.cpp

@@ -508,13 +508,25 @@ void ir_print_type(irFileBuffer *f, irModule *m, Type *t, bool in_struct) {
 			// NOTE(bill): The zero size array is used to fix the alignment used in a structure as
 			// NOTE(bill): The zero size array is used to fix the alignment used in a structure as
 			// LLVM takes the first element's alignment as the entire alignment (like C)
 			// LLVM takes the first element's alignment as the entire alignment (like C)
 			i64 align = type_align_of(t);
 			i64 align = type_align_of(t);
+
+			if (is_type_union_maybe_pointer_original_alignment(t)) {
+				ir_write_byte(f, '{');
+				ir_print_type(f, m, t->Union.variants[0]);
+				ir_write_byte(f, '}');
+				return;
+			}
+
 			i64 block_size =  t->Union.variant_block_size;
 			i64 block_size =  t->Union.variant_block_size;
 
 
 			ir_write_byte(f, '{');
 			ir_write_byte(f, '{');
 			ir_print_alignment_prefix_hack(f, align);
 			ir_print_alignment_prefix_hack(f, align);
-			ir_fprintf(f, ", [%lld x i8], ", block_size);
-			// ir_print_type(f, m, t_type_info_ptr);
-			ir_print_type(f, m, union_tag_type(t));
+			if (is_type_union_maybe_pointer(t)) {
+				ir_fprintf(f, ", ");
+				ir_print_type(f, m, t->Union.variants[0]);
+			} else {
+				ir_fprintf(f, ", [%lld x i8], ", block_size);
+				ir_print_type(f, m, union_tag_type(t));
+			}
 			ir_write_byte(f, '}');
 			ir_write_byte(f, '}');
 		}
 		}
 		return;
 		return;
@@ -1850,6 +1862,12 @@ void ir_print_instr(irFileBuffer *f, irModule *m, irValue *value) {
 
 
 	case irInstr_UnionTagPtr: {
 	case irInstr_UnionTagPtr: {
 		Type *et = ir_type(instr->UnionTagPtr.address);
 		Type *et = ir_type(instr->UnionTagPtr.address);
+
+		Type *ut = type_deref(et);
+		if (is_type_union_maybe_pointer(ut)) {
+			GB_PANIC("union #maybe UnionTagPtr");
+		}
+
 		ir_fprintf(f, "%%%d = getelementptr inbounds ", value->index);
 		ir_fprintf(f, "%%%d = getelementptr inbounds ", value->index);
 		Type *t = base_type(type_deref(et));
 		Type *t = base_type(type_deref(et));
 		GB_ASSERT(is_type_union(t));
 		GB_ASSERT(is_type_union(t));
@@ -1869,10 +1887,16 @@ void ir_print_instr(irFileBuffer *f, irModule *m, irValue *value) {
 
 
 	case irInstr_UnionTagValue: {
 	case irInstr_UnionTagValue: {
 		Type *et = ir_type(instr->UnionTagValue.address);
 		Type *et = ir_type(instr->UnionTagValue.address);
-		ir_fprintf(f, "%%%d = extractvalue ", value->index);
 		Type *t = base_type(et);
 		Type *t = base_type(et);
+
+		if (is_type_union_maybe_pointer(t)) {
+			GB_PANIC("union #maybe UnionTagValue");
+		}
+
+		ir_fprintf(f, "%%%d = extractvalue ", value->index);
 		GB_ASSERT(is_type_union(t));
 		GB_ASSERT(is_type_union(t));
 
 
+
 		ir_print_type(f, m, et);
 		ir_print_type(f, m, et);
 		ir_write_byte(f, ' ');
 		ir_write_byte(f, ' ');
 		ir_print_value(f, m, instr->UnionTagValue.address, et);
 		ir_print_value(f, m, instr->UnionTagValue.address, et);

+ 20 - 3
src/parser.cpp

@@ -929,7 +929,7 @@ Ast *ast_struct_type(AstFile *f, Token token, Array<Ast *> fields, isize field_c
 }
 }
 
 
 
 
-Ast *ast_union_type(AstFile *f, Token token, Array<Ast *> variants, Ast *polymorphic_params, Ast *align, bool no_nil,
+Ast *ast_union_type(AstFile *f, Token token, Array<Ast *> variants, Ast *polymorphic_params, Ast *align, bool no_nil, bool maybe,
                     Token where_token, Array<Ast *> const &where_clauses) {
                     Token where_token, Array<Ast *> const &where_clauses) {
 	Ast *result = alloc_ast_node(f, Ast_UnionType);
 	Ast *result = alloc_ast_node(f, Ast_UnionType);
 	result->UnionType.token              = token;
 	result->UnionType.token              = token;
@@ -937,6 +937,7 @@ Ast *ast_union_type(AstFile *f, Token token, Array<Ast *> variants, Ast *polymor
 	result->UnionType.polymorphic_params = polymorphic_params;
 	result->UnionType.polymorphic_params = polymorphic_params;
 	result->UnionType.align              = align;
 	result->UnionType.align              = align;
 	result->UnionType.no_nil             = no_nil;
 	result->UnionType.no_nil             = no_nil;
+	result->UnionType.maybe             = maybe;
 	result->UnionType.where_token        = where_token;
 	result->UnionType.where_token        = where_token;
 	result->UnionType.where_clauses      = where_clauses;
 	result->UnionType.where_clauses      = where_clauses;
 	return result;
 	return result;
@@ -2091,6 +2092,7 @@ Ast *parse_operand(AstFile *f, bool lhs) {
 		Ast *polymorphic_params = nullptr;
 		Ast *polymorphic_params = nullptr;
 		Ast *align = nullptr;
 		Ast *align = nullptr;
 		bool no_nil = false;
 		bool no_nil = false;
+		bool maybe = false;
 
 
 		CommentGroup *docs = f->lead_comment;
 		CommentGroup *docs = f->lead_comment;
 		Token start_token = f->curr_token;
 		Token start_token = f->curr_token;
@@ -2118,10 +2120,19 @@ Ast *parse_operand(AstFile *f, bool lhs) {
 					syntax_error(tag, "Duplicate union tag '#%.*s'", LIT(tag.string));
 					syntax_error(tag, "Duplicate union tag '#%.*s'", LIT(tag.string));
 				}
 				}
 				no_nil = true;
 				no_nil = true;
-			} else {
+			} else if (tag.string == "maybe") {
+				if (maybe) {
+					syntax_error(tag, "Duplicate union tag '#%.*s'", LIT(tag.string));
+				}
+				maybe = true;
+			}else {
 				syntax_error(tag, "Invalid union tag '#%.*s'", LIT(tag.string));
 				syntax_error(tag, "Invalid union tag '#%.*s'", LIT(tag.string));
 			}
 			}
 		}
 		}
+		if (no_nil && maybe) {
+			syntax_error(f->curr_token, "#maybe and #no_nil cannot be applied together");
+		}
+
 
 
 		Token where_token = {};
 		Token where_token = {};
 		Array<Ast *> where_clauses = {};
 		Array<Ast *> where_clauses = {};
@@ -2150,7 +2161,7 @@ Ast *parse_operand(AstFile *f, bool lhs) {
 
 
 		Token close = expect_token(f, Token_CloseBrace);
 		Token close = expect_token(f, Token_CloseBrace);
 
 
-		return ast_union_type(f, token, variants, polymorphic_params, align, no_nil, where_token, where_clauses);
+		return ast_union_type(f, token, variants, polymorphic_params, align, no_nil, maybe, where_token, where_clauses);
 	} break;
 	} break;
 
 
 	case Token_enum: {
 	case Token_enum: {
@@ -2350,6 +2361,12 @@ Ast *parse_atom_expr(AstFile *f, Ast *operand, bool lhs) {
 				operand = ast_type_assertion(f, operand, token, type);
 				operand = ast_type_assertion(f, operand, token, type);
 			} break;
 			} break;
 
 
+			case Token_Question: {
+				Token question = expect_token(f, Token_Question);
+				Ast *type = ast_unary_expr(f, question, nullptr);
+				operand = ast_type_assertion(f, operand, token, type);
+			} break;
+
 			default:
 			default:
 				syntax_error(f->curr_token, "Expected a selector");
 				syntax_error(f->curr_token, "Expected a selector");
 				advance_token(f);
 				advance_token(f);

+ 1 - 0
src/parser.hpp

@@ -515,6 +515,7 @@ AST_KIND(_TypeBegin, "", bool) \
 		Array<Ast *> variants;      \
 		Array<Ast *> variants;      \
 		Ast *polymorphic_params;    \
 		Ast *polymorphic_params;    \
 		Ast *        align;         \
 		Ast *        align;         \
+		bool         maybe;         \
 		bool         no_nil;        \
 		bool         no_nil;        \
 		Token where_token;          \
 		Token where_token;          \
 		Array<Ast *> where_clauses; \
 		Array<Ast *> where_clauses; \

+ 44 - 7
src/types.cpp

@@ -152,6 +152,7 @@ struct TypeUnion {
 	Type *        polymorphic_params; // Type_Tuple
 	Type *        polymorphic_params; // Type_Tuple
 	Type *        polymorphic_parent;
 	Type *        polymorphic_parent;
 	bool          no_nil;
 	bool          no_nil;
+	bool          maybe;
 	bool          is_polymorphic;
 	bool          is_polymorphic;
 	bool          is_poly_specialized;
 	bool          is_poly_specialized;
 };
 };
@@ -1219,6 +1220,32 @@ bool is_type_map(Type *t) {
 	return t->kind == Type_Map;
 	return t->kind == Type_Map;
 }
 }
 
 
+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]);
+		}
+	}
+	return false;
+}
+
+
+bool is_type_union_maybe_pointer_original_alignment(Type *t) {
+	t = base_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)) {
+				return type_align_of(v) == type_align_of(t);
+			}
+		}
+	}
+	return false;
+}
+
+
+
 
 
 bool is_type_integer_endian_big(Type *t) {
 bool is_type_integer_endian_big(Type *t) {
 	t = core_type(t);
 	t = core_type(t);
@@ -2024,6 +2051,7 @@ i64 union_tag_size(Type *u) {
 Type *union_tag_type(Type *u) {
 Type *union_tag_type(Type *u) {
 	i64 s = union_tag_size(u);
 	i64 s = union_tag_size(u);
 	switch (s) {
 	switch (s) {
+	case  0: return  t_u8;
 	case  1: return  t_u8;
 	case  1: return  t_u8;
 	case  2: return  t_u16;
 	case  2: return  t_u16;
 	case  4: return  t_u32;
 	case  4: return  t_u32;
@@ -2934,14 +2962,23 @@ i64 type_size_of_internal(Type *t, TypePath *path) {
 			}
 			}
 		}
 		}
 
 
-		// NOTE(bill): Align to tag
-		i64 tag_size = union_tag_size(t);
-		i64 size = align_formula(max, tag_size);
-		// NOTE(bill): Calculate the padding between the common fields and the tag
-		t->Union.tag_size = tag_size;
-		t->Union.variant_block_size = size - field_size;
+		i64 size = 0;
+
+		if (is_type_union_maybe_pointer(t)) {
+			size = max;
+			t->Union.tag_size = 0;
+			t->Union.variant_block_size = size;
+		} else {
+			// NOTE(bill): Align to tag
+			i64 tag_size = union_tag_size(t);
+			size = align_formula(max, tag_size);
+			// NOTE(bill): Calculate the padding between the common fields and the tag
+			t->Union.tag_size = tag_size;
+			t->Union.variant_block_size = size - field_size;
 
 
-		return align_formula(size + tag_size, align);
+			size += tag_size;
+		}
+		return align_formula(size, align);
 	} break;
 	} break;