Browse Source

Struct field tags

gingerBill 6 years ago
parent
commit
9c63212824
9 changed files with 127 additions and 32 deletions
  1. 12 8
      core/odin/ast/ast.odin
  2. 17 7
      core/odin/parser/parser.odin
  3. 3 2
      core/runtime/core.odin
  4. 25 0
      examples/demo/demo.odin
  5. 4 2
      src/check_type.cpp
  6. 33 4
      src/ir.cpp
  7. 29 8
      src/parser.cpp
  8. 3 1
      src/parser.hpp
  9. 1 0
      src/types.cpp

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

@@ -435,7 +435,9 @@ Field_Flag :: enum {
 	C_Vararg,
 	C_Vararg,
 	Auto_Cast,
 	Auto_Cast,
 	In,
 	In,
+
 	Results,
 	Results,
+	Tags,
 	Default_Parameters,
 	Default_Parameters,
 	Typeid_Token,
 	Typeid_Token,
 }
 }
@@ -443,18 +445,19 @@ Field_Flag :: enum {
 Field_Flags :: distinct bit_set[Field_Flag];
 Field_Flags :: distinct bit_set[Field_Flag];
 
 
 Field_Flags_Struct :: Field_Flags{
 Field_Flags_Struct :: Field_Flags{
-	Field_Flag.Using,
+	.Using,
+	.Tags,
 };
 };
 Field_Flags_Record_Poly_Params :: Field_Flags{
 Field_Flags_Record_Poly_Params :: Field_Flags{
-	Field_Flag.Typeid_Token,
+	.Typeid_Token,
 };
 };
 Field_Flags_Signature :: Field_Flags{
 Field_Flags_Signature :: Field_Flags{
-	Field_Flag.Ellipsis,
-	Field_Flag.Using,
-	Field_Flag.No_Alias,
-	Field_Flag.C_Vararg,
-	Field_Flag.Auto_Cast,
-	Field_Flag.Default_Parameters,
+	.Ellipsis,
+	.Using,
+	.No_Alias,
+	.C_Vararg,
+	.Auto_Cast,
+	.Default_Parameters,
 };
 };
 
 
 Field_Flags_Signature_Params  :: Field_Flags_Signature | {Field_Flag.Typeid_Token};
 Field_Flags_Signature_Params  :: Field_Flags_Signature | {Field_Flag.Typeid_Token};
@@ -483,6 +486,7 @@ Field :: struct {
 	names:         []^Expr, // Could be polymorphic
 	names:         []^Expr, // Could be polymorphic
 	type:          ^Expr,
 	type:          ^Expr,
 	default_value: ^Expr,
 	default_value: ^Expr,
+	tag:           token.Token,
 	flags:         Field_Flags,
 	flags:         Field_Flags,
 	comment:       ^Comment_Group,
 	comment:       ^Comment_Group,
 }
 }

+ 17 - 7
core/odin/parser/parser.odin

@@ -1388,19 +1388,18 @@ check_field_flag_prefixes :: proc(p: ^Parser, name_count: int, allowed_flags, se
 
 
 	for flag in ast.Field_Flag {
 	for flag in ast.Field_Flag {
 		if flag notin allowed_flags && flag in flags {
 		if flag notin allowed_flags && flag in flags {
-			using ast.Field_Flag;
 			#complete switch flag {
 			#complete switch flag {
-			case Using:
+			case .Using:
 				error(p, p.curr_tok.pos, "'using' is not allowed within this field list");
 				error(p, p.curr_tok.pos, "'using' is not allowed within this field list");
-			case No_Alias:
+			case .No_Alias:
 				error(p, p.curr_tok.pos, "'#no_alias' is not allowed within this field list");
 				error(p, p.curr_tok.pos, "'#no_alias' is not allowed within this field list");
-			case C_Vararg:
+			case .C_Vararg:
 				error(p, p.curr_tok.pos, "'#c_vararg' is not allowed within this field list");
 				error(p, p.curr_tok.pos, "'#c_vararg' is not allowed within this field list");
-			case Auto_Cast:
+			case .Auto_Cast:
 				error(p, p.curr_tok.pos, "'auto_cast' is not allowed within this field list");
 				error(p, p.curr_tok.pos, "'auto_cast' is not allowed within this field list");
-			case In:
+			case .In:
 				error(p, p.curr_tok.pos, "'in' is not allowed within this field list");
 				error(p, p.curr_tok.pos, "'in' is not allowed within this field list");
-			case Ellipsis, Results, Default_Parameters, Typeid_Token:
+			case .Tags, .Ellipsis, .Results, .Default_Parameters, .Typeid_Token:
 				panic("Impossible prefixes");
 				panic("Impossible prefixes");
 			}
 			}
 			flags &~= {flag};
 			flags &~= {flag};
@@ -1540,6 +1539,7 @@ parse_field_list :: proc(p: ^Parser, follow: token.Kind, allowed_flags: ast.Fiel
 
 
 		type:          ^ast.Expr;
 		type:          ^ast.Expr;
 		default_value: ^ast.Expr;
 		default_value: ^ast.Expr;
+		tag: token.Token;
 
 
 		expect_token_after(p, token.Colon, "field list");
 		expect_token_after(p, token.Colon, "field list");
 		if p.curr_tok.kind != token.Eq {
 		if p.curr_tok.kind != token.Eq {
@@ -1579,9 +1579,19 @@ parse_field_list :: proc(p: ^Parser, follow: token.Kind, allowed_flags: ast.Fiel
 			error(p, p.curr_tok.pos, "extra parameter after ellipsis without a default value");
 			error(p, p.curr_tok.pos, "extra parameter after ellipsis without a default value");
 		}
 		}
 
 
+		if type != nil && default_value == nil {
+			if p.curr_tok.kind == token.String {
+				tag = expect_token(p, token.String);
+				if .Tags notin allowed_flags {
+					error(p, tag.pos, "Field tags are only allowed within structures");
+				}
+			}
+		}
+
 		ok := expect_field_separator(p, type);
 		ok := expect_field_separator(p, type);
 
 
 		field := new_ast_field(names, type, default_value);
 		field := new_ast_field(names, type, default_value);
+		field.tag     = tag;
 		field.docs    = docs;
 		field.docs    = docs;
 		field.flags   = flags;
 		field.flags   = flags;
 		field.comment = p.line_comment;
 		field.comment = p.line_comment;

+ 3 - 2
core/runtime/core.odin

@@ -79,8 +79,9 @@ Type_Info_Tuple :: struct { // Only really used for procedures
 Type_Info_Struct :: struct {
 Type_Info_Struct :: struct {
 	types:        []^Type_Info,
 	types:        []^Type_Info,
 	names:        []string,
 	names:        []string,
-	offsets:      []uintptr, // offsets may not be used in tuples
-	usings:       []bool,    // usings may not be used in tuples
+	offsets:      []uintptr,
+	usings:       []bool,
+	tags:         []string,
 	is_packed:    bool,
 	is_packed:    bool,
 	is_raw_union: bool,
 	is_raw_union: bool,
 	custom_align: bool,
 	custom_align: bool,

+ 25 - 0
examples/demo/demo.odin

@@ -3,6 +3,7 @@ package main
 import "core:fmt"
 import "core:fmt"
 import "core:mem"
 import "core:mem"
 import "core:os"
 import "core:os"
+import "core:runtime"
 
 
 when os.OS == "windows" {
 when os.OS == "windows" {
 	import "core:thread"
 	import "core:thread"
@@ -945,6 +946,29 @@ deferred_procedure_associations :: proc() {
 	}
 	}
 }
 }
 
 
+struct_field_tags :: proc() {
+	fmt.println("# struct_field_tags");
+
+	Foo :: struct {
+		x: int    `tag1`,
+		y: string `tag2`,
+		z: bool, // no tag
+	}
+
+	f: Foo;
+	ti := runtime.type_info_base(type_info_of(Foo));
+	s := ti.variant.(runtime.Type_Info_Struct);
+	fmt.println("Foo :: struct {");
+	for _, i in s.names {
+		if tag := s.tags[i]; tag != "" {
+			fmt.printf("\t%s: %T `%s`,\n", s.names[i], s.types[i], tag);
+		} else {
+			fmt.printf("\t%s: %T,\n", s.names[i], s.types[i]);
+		}
+	}
+	fmt.println("}");
+}
+
 main :: proc() {
 main :: proc() {
 	when true {
 	when true {
 		general_stuff();
 		general_stuff();
@@ -963,5 +987,6 @@ main :: proc() {
 		bit_set_type();
 		bit_set_type();
 		diverging_procedures();
 		diverging_procedures();
 		deferred_procedure_associations();
 		deferred_procedure_associations();
+		struct_field_tags();
 	}
 	}
 }
 }

+ 4 - 2
src/check_type.cpp

@@ -110,9 +110,10 @@ bool does_field_type_allow_using(Type *t) {
 	return false;
 	return false;
 }
 }
 
 
-void check_struct_fields(CheckerContext *ctx, Ast *node, Array<Entity *> *fields, Array<Ast *> const &params,
+void check_struct_fields(CheckerContext *ctx, Ast *node, Array<Entity *> *fields, Array<String> *tags, Array<Ast *> const &params,
                          isize init_field_capacity, Type *struct_type, String context) {
                          isize init_field_capacity, Type *struct_type, String context) {
 	*fields = array_make<Entity *>(heap_allocator(), 0, init_field_capacity);
 	*fields = array_make<Entity *>(heap_allocator(), 0, init_field_capacity);
+	*tags   = array_make<String>(heap_allocator(), 0, init_field_capacity);
 
 
 	GB_ASSERT(node->kind == Ast_StructType);
 	GB_ASSERT(node->kind == Ast_StructType);
 	GB_ASSERT(struct_type->kind == Type_Struct);
 	GB_ASSERT(struct_type->kind == Type_Struct);
@@ -171,6 +172,7 @@ void check_struct_fields(CheckerContext *ctx, Ast *node, Array<Entity *> *fields
 			Entity *field = alloc_entity_field(ctx->scope, name_token, type, is_using, field_src_index);
 			Entity *field = alloc_entity_field(ctx->scope, name_token, type, is_using, field_src_index);
 			add_entity(ctx->checker, ctx->scope, name, field);
 			add_entity(ctx->checker, ctx->scope, name, field);
 			array_add(fields, field);
 			array_add(fields, field);
+			array_add(tags, p->tag.string);
 
 
 			field_src_index += 1;
 			field_src_index += 1;
 		}
 		}
@@ -504,7 +506,7 @@ void check_struct_type(CheckerContext *ctx, Type *struct_type, Ast *node, Array<
 
 
 
 
 	if (!is_polymorphic) {
 	if (!is_polymorphic) {
-		check_struct_fields(ctx, node, &struct_type->Struct.fields, st->fields, min_field_count, struct_type, context);
+		check_struct_fields(ctx, node, &struct_type->Struct.fields, &struct_type->Struct.tags, st->fields, min_field_count, struct_type, context);
 	}
 	}
 
 
 	if (st->align != nullptr) {
 	if (st->align != nullptr) {

+ 33 - 4
src/ir.cpp

@@ -174,6 +174,7 @@ gbAllocator ir_allocator(void) {
 #define IR_TYPE_INFO_NAMES_NAME      "__$type_info_names_data"
 #define IR_TYPE_INFO_NAMES_NAME      "__$type_info_names_data"
 #define IR_TYPE_INFO_OFFSETS_NAME    "__$type_info_offsets_data"
 #define IR_TYPE_INFO_OFFSETS_NAME    "__$type_info_offsets_data"
 #define IR_TYPE_INFO_USINGS_NAME     "__$type_info_usings_data"
 #define IR_TYPE_INFO_USINGS_NAME     "__$type_info_usings_data"
+#define IR_TYPE_INFO_TAGS_NAME       "__$type_info_tags_data"
 
 
 
 
 #define IR_INSTR_KINDS \
 #define IR_INSTR_KINDS \
@@ -5271,12 +5272,14 @@ gb_global irValue *ir_global_type_info_member_types   = nullptr;
 gb_global irValue *ir_global_type_info_member_names   = nullptr;
 gb_global irValue *ir_global_type_info_member_names   = nullptr;
 gb_global irValue *ir_global_type_info_member_offsets = nullptr;
 gb_global irValue *ir_global_type_info_member_offsets = nullptr;
 gb_global irValue *ir_global_type_info_member_usings  = nullptr;
 gb_global irValue *ir_global_type_info_member_usings  = nullptr;
+gb_global irValue *ir_global_type_info_member_tags    = nullptr;
 
 
 gb_global i32      ir_global_type_info_data_index           = 0;
 gb_global i32      ir_global_type_info_data_index           = 0;
 gb_global i32      ir_global_type_info_member_types_index   = 0;
 gb_global i32      ir_global_type_info_member_types_index   = 0;
 gb_global i32      ir_global_type_info_member_names_index   = 0;
 gb_global i32      ir_global_type_info_member_names_index   = 0;
 gb_global i32      ir_global_type_info_member_offsets_index = 0;
 gb_global i32      ir_global_type_info_member_offsets_index = 0;
 gb_global i32      ir_global_type_info_member_usings_index  = 0;
 gb_global i32      ir_global_type_info_member_usings_index  = 0;
+gb_global i32      ir_global_type_info_member_tags_index    = 0;
 
 
 isize ir_type_info_count(CheckerInfo *info) {
 isize ir_type_info_count(CheckerInfo *info) {
 	return info->minimum_dependency_type_info_set.entries.count+1;
 	return info->minimum_dependency_type_info_set.entries.count+1;
@@ -9587,6 +9590,16 @@ void ir_init_module(irModule *m, Checker *c) {
 					map_set(&m->members, hash_string(name), g);
 					map_set(&m->members, hash_string(name), g);
 					ir_global_type_info_member_usings = g;
 					ir_global_type_info_member_usings = g;
 				}
 				}
+
+				{
+					String name = str_lit(IR_TYPE_INFO_TAGS_NAME);
+					Entity *e = alloc_entity_variable(nullptr, make_token_ident(name),
+					                                  alloc_type_array(t_string, count), false);
+					irValue *g = ir_value_global(e, nullptr);
+					ir_module_add_value(m, e, g);
+					map_set(&m->members, hash_string(name), g);
+					ir_global_type_info_member_tags = g;
+				}
 			}
 			}
 		}
 		}
 	}
 	}
@@ -9720,6 +9733,11 @@ irValue *ir_type_info_member_usings_offset(irProcedure *proc, isize count) {
 	ir_global_type_info_member_usings_index += cast(i32)count;
 	ir_global_type_info_member_usings_index += cast(i32)count;
 	return offset;
 	return offset;
 }
 }
+irValue *ir_type_info_member_tags_offset(irProcedure *proc, isize count) {
+	irValue *offset = ir_emit_array_epi(proc, ir_global_type_info_member_tags, ir_global_type_info_member_tags_index);
+	ir_global_type_info_member_tags_index += cast(i32)count;
+	return offset;
+}
 
 
 
 
 
 
@@ -10065,9 +10083,9 @@ void ir_setup_type_info_data(irProcedure *proc) { // NOTE(bill): Setup type_info
 				irValue *is_packed       = ir_const_bool(t->Struct.is_packed);
 				irValue *is_packed       = ir_const_bool(t->Struct.is_packed);
 				irValue *is_raw_union    = ir_const_bool(t->Struct.is_raw_union);
 				irValue *is_raw_union    = ir_const_bool(t->Struct.is_raw_union);
 				irValue *is_custom_align = ir_const_bool(t->Struct.custom_align != 0);
 				irValue *is_custom_align = ir_const_bool(t->Struct.custom_align != 0);
-				ir_emit_store(proc, ir_emit_struct_ep(proc, tag, 4), is_packed);
-				ir_emit_store(proc, ir_emit_struct_ep(proc, tag, 5), is_raw_union);
-				ir_emit_store(proc, ir_emit_struct_ep(proc, tag, 6), is_custom_align);
+				ir_emit_store(proc, ir_emit_struct_ep(proc, tag, 5), is_packed);
+				ir_emit_store(proc, ir_emit_struct_ep(proc, tag, 6), is_raw_union);
+				ir_emit_store(proc, ir_emit_struct_ep(proc, tag, 7), is_custom_align);
 			}
 			}
 
 
 			isize count = t->Struct.fields.count;
 			isize count = t->Struct.fields.count;
@@ -10076,6 +10094,7 @@ void ir_setup_type_info_data(irProcedure *proc) { // NOTE(bill): Setup type_info
 				irValue *memory_names   = ir_type_info_member_names_offset  (proc, count);
 				irValue *memory_names   = ir_type_info_member_names_offset  (proc, count);
 				irValue *memory_offsets = ir_type_info_member_offsets_offset(proc, count);
 				irValue *memory_offsets = ir_type_info_member_offsets_offset(proc, count);
 				irValue *memory_usings  = ir_type_info_member_usings_offset (proc, count);
 				irValue *memory_usings  = ir_type_info_member_usings_offset (proc, count);
+				irValue *memory_tags    = ir_type_info_member_tags_offset   (proc, count);
 
 
 				type_set_offsets(t); // NOTE(bill): Just incase the offsets have not been set yet
 				type_set_offsets(t); // NOTE(bill): Just incase the offsets have not been set yet
 				for (isize source_index = 0; source_index < count; source_index++) {
 				for (isize source_index = 0; source_index < count; source_index++) {
@@ -10091,7 +10110,7 @@ void ir_setup_type_info_data(irProcedure *proc) { // NOTE(bill): Setup type_info
 					irValue *index     = ir_const_int(source_index);
 					irValue *index     = ir_const_int(source_index);
 					irValue *type_info = ir_emit_ptr_offset(proc, memory_types,   index);
 					irValue *type_info = ir_emit_ptr_offset(proc, memory_types,   index);
 					irValue *offset    = ir_emit_ptr_offset(proc, memory_offsets, index);
 					irValue *offset    = ir_emit_ptr_offset(proc, memory_offsets, index);
-					irValue *is_using  = ir_emit_ptr_offset(proc, memory_usings, index);
+					irValue *is_using  = ir_emit_ptr_offset(proc, memory_usings,  index);
 
 
 					ir_emit_store(proc, type_info, ir_type_info(proc, f->type));
 					ir_emit_store(proc, type_info, ir_type_info(proc, f->type));
 					if (f->token.string.len > 0) {
 					if (f->token.string.len > 0) {
@@ -10100,6 +10119,15 @@ void ir_setup_type_info_data(irProcedure *proc) { // NOTE(bill): Setup type_info
 					}
 					}
 					ir_emit_store(proc, offset, ir_const_uintptr(foffset));
 					ir_emit_store(proc, offset, ir_const_uintptr(foffset));
 					ir_emit_store(proc, is_using, ir_const_bool((f->flags&EntityFlag_Using) != 0));
 					ir_emit_store(proc, is_using, ir_const_bool((f->flags&EntityFlag_Using) != 0));
+
+					if (t->Struct.tags.count > 0) {
+						String tag_string = t->Struct.tags[source_index];
+						if (tag_string.len > 0) {
+							irValue *tag_ptr = ir_emit_ptr_offset(proc, memory_tags, index);
+							ir_emit_store(proc, tag_ptr, ir_const_string(tag_string));
+						}
+					}
+
 				}
 				}
 
 
 				irValue *cv = ir_const_int(count);
 				irValue *cv = ir_const_int(count);
@@ -10107,6 +10135,7 @@ void ir_setup_type_info_data(irProcedure *proc) { // NOTE(bill): Setup type_info
 				ir_fill_slice(proc, ir_emit_struct_ep(proc, tag, 1), memory_names,   cv);
 				ir_fill_slice(proc, ir_emit_struct_ep(proc, tag, 1), memory_names,   cv);
 				ir_fill_slice(proc, ir_emit_struct_ep(proc, tag, 2), memory_offsets, cv);
 				ir_fill_slice(proc, ir_emit_struct_ep(proc, tag, 2), memory_offsets, cv);
 				ir_fill_slice(proc, ir_emit_struct_ep(proc, tag, 3), memory_usings,  cv);
 				ir_fill_slice(proc, ir_emit_struct_ep(proc, tag, 3), memory_usings,  cv);
+				ir_fill_slice(proc, ir_emit_struct_ep(proc, tag, 4), memory_tags,    cv);
 			}
 			}
 			break;
 			break;
 		}
 		}

+ 29 - 8
src/parser.cpp

@@ -805,13 +805,14 @@ Ast *ast_bad_decl(AstFile *f, Token begin, Token end) {
 	return result;
 	return result;
 }
 }
 
 
-Ast *ast_field(AstFile *f, Array<Ast *> names, Ast *type, Ast *default_value, u32 flags,
-                   CommentGroup *docs, CommentGroup *comment) {
+Ast *ast_field(AstFile *f, Array<Ast *> names, Ast *type, Ast *default_value, u32 flags, Token tag,
+               CommentGroup *docs, CommentGroup *comment) {
 	Ast *result = alloc_ast_node(f, Ast_Field);
 	Ast *result = alloc_ast_node(f, Ast_Field);
 	result->Field.names         = names;
 	result->Field.names         = names;
 	result->Field.type          = type;
 	result->Field.type          = type;
 	result->Field.default_value = default_value;
 	result->Field.default_value = default_value;
 	result->Field.flags         = flags;
 	result->Field.flags         = flags;
+	result->Field.tag           = tag;
 	result->Field.docs = docs;
 	result->Field.docs = docs;
 	result->Field.comment       = comment;
 	result->Field.comment       = comment;
 	return result;
 	return result;
@@ -2740,7 +2741,8 @@ Ast *parse_results(AstFile *f, bool *diverging) {
 		Array<Ast *> empty_names = {};
 		Array<Ast *> empty_names = {};
 		auto list = array_make<Ast *>(heap_allocator(), 0, 1);
 		auto list = array_make<Ast *>(heap_allocator(), 0, 1);
 		Ast *type = parse_type(f);
 		Ast *type = parse_type(f);
-		array_add(&list, ast_field(f, empty_names, type, nullptr, 0, nullptr, nullptr));
+		Token tag = {};
+		array_add(&list, ast_field(f, empty_names, type, nullptr, 0, tag, nullptr, nullptr));
 		return ast_field_list(f, begin_token, list);
 		return ast_field_list(f, begin_token, list);
 	}
 	}
 
 
@@ -3095,6 +3097,7 @@ Ast *parse_field_list(AstFile *f, isize *name_count_, u32 allowed_flags, TokenKi
 
 
 		Ast *type = nullptr;
 		Ast *type = nullptr;
 		Ast *default_value = nullptr;
 		Ast *default_value = nullptr;
+		Token tag = {};
 
 
 		expect_token_after(f, Token_Colon, "field list");
 		expect_token_after(f, Token_Colon, "field list");
 		if (f->curr_token.kind != Token_Eq) {
 		if (f->curr_token.kind != Token_Eq) {
@@ -3132,16 +3135,25 @@ Ast *parse_field_list(AstFile *f, isize *name_count_, u32 allowed_flags, TokenKi
 			syntax_error(f->curr_token, "Extra parameter after ellipsis without a default value");
 			syntax_error(f->curr_token, "Extra parameter after ellipsis without a default value");
 		}
 		}
 
 
+		if (type != nullptr && default_value == nullptr) {
+			if (f->curr_token.kind == Token_String) {
+				tag = expect_token(f, Token_String);
+				if ((allowed_flags & FieldFlag_Tags) == 0) {
+					syntax_error(tag, "Field tags are only allowed within structures");
+				}
+			}
+		}
+
 		parse_expect_field_separator(f, type);
 		parse_expect_field_separator(f, type);
-		Ast *param = ast_field(f, names, type, default_value, set_flags, docs, f->line_comment);
+		Ast *param = ast_field(f, names, type, default_value, set_flags, tag, docs, f->line_comment);
 		array_add(&params, param);
 		array_add(&params, param);
 
 
 
 
 		while (f->curr_token.kind != follow &&
 		while (f->curr_token.kind != follow &&
 		       f->curr_token.kind != Token_EOF) {
 		       f->curr_token.kind != Token_EOF) {
 			CommentGroup *docs = f->lead_comment;
 			CommentGroup *docs = f->lead_comment;
-
 			u32 set_flags = parse_field_prefixes(f);
 			u32 set_flags = parse_field_prefixes(f);
+			Token tag = {};
 			Array<Ast *> names = parse_ident_list(f, allow_poly_names);
 			Array<Ast *> names = parse_ident_list(f, allow_poly_names);
 			if (names.count == 0) {
 			if (names.count == 0) {
 				syntax_error(f->curr_token, "Empty field declaration");
 				syntax_error(f->curr_token, "Empty field declaration");
@@ -3184,9 +3196,18 @@ Ast *parse_field_list(AstFile *f, isize *name_count_, u32 allowed_flags, TokenKi
 				syntax_error(f->curr_token, "Extra parameter after ellipsis without a default value");
 				syntax_error(f->curr_token, "Extra parameter after ellipsis without a default value");
 			}
 			}
 
 
+			if (type != nullptr && default_value == nullptr) {
+				if (f->curr_token.kind == Token_String) {
+					tag = expect_token(f, Token_String);
+					if ((allowed_flags & FieldFlag_Tags) == 0) {
+						syntax_error(tag, "Field tags are only allowed within structures");
+					}
+				}
+			}
+
 
 
 			bool ok = parse_expect_field_separator(f, param);
 			bool ok = parse_expect_field_separator(f, param);
-			Ast *param = ast_field(f, names, type, default_value, set_flags, docs, f->line_comment);
+			Ast *param = ast_field(f, names, type, default_value, set_flags, tag, docs, f->line_comment);
 			array_add(&params, param);
 			array_add(&params, param);
 
 
 			if (!ok) {
 			if (!ok) {
@@ -3210,8 +3231,8 @@ Ast *parse_field_list(AstFile *f, isize *name_count_, u32 allowed_flags, TokenKi
 		token.pos = ast_token(type).pos;
 		token.pos = ast_token(type).pos;
 		names[0] = ast_ident(f, token);
 		names[0] = ast_ident(f, token);
 		u32 flags = check_field_prefixes(f, list.count, allowed_flags, list[i].flags);
 		u32 flags = check_field_prefixes(f, list.count, allowed_flags, list[i].flags);
-
-		Ast *param = ast_field(f, names, list[i].node, nullptr, flags, docs, f->line_comment);
+		Token tag = {};
+		Ast *param = ast_field(f, names, list[i].node, nullptr, flags, tag, docs, f->line_comment);
 		array_add(&params, param);
 		array_add(&params, param);
 	}
 	}
 
 

+ 3 - 1
src/parser.hpp

@@ -186,11 +186,12 @@ enum FieldFlag {
 	FieldFlag_c_vararg  = 1<<3,
 	FieldFlag_c_vararg  = 1<<3,
 	FieldFlag_auto_cast = 1<<4,
 	FieldFlag_auto_cast = 1<<4,
 
 
+	FieldFlag_Tags = 1<<10,
 
 
 	FieldFlag_Results   = 1<<16,
 	FieldFlag_Results   = 1<<16,
 
 
 	FieldFlag_Signature = FieldFlag_ellipsis|FieldFlag_using|FieldFlag_no_alias|FieldFlag_c_vararg|FieldFlag_auto_cast,
 	FieldFlag_Signature = FieldFlag_ellipsis|FieldFlag_using|FieldFlag_no_alias|FieldFlag_c_vararg|FieldFlag_auto_cast,
-	FieldFlag_Struct    = FieldFlag_using,
+	FieldFlag_Struct    = FieldFlag_using|FieldFlag_Tags,
 };
 };
 
 
 enum StmtAllowFlag {
 enum StmtAllowFlag {
@@ -412,6 +413,7 @@ AST_KIND(_DeclEnd,   "", bool) \
 		Array<Ast *> names;         \
 		Array<Ast *> names;         \
 		Ast *        type;          \
 		Ast *        type;          \
 		Ast *        default_value; \
 		Ast *        default_value; \
+		Token        tag;           \
 		u32              flags;     \
 		u32              flags;     \
 		CommentGroup *   docs;      \
 		CommentGroup *   docs;      \
 		CommentGroup *   comment;   \
 		CommentGroup *   comment;   \

+ 1 - 0
src/types.cpp

@@ -107,6 +107,7 @@ struct BasicType {
 
 
 struct TypeStruct {
 struct TypeStruct {
 	Array<Entity *> fields;
 	Array<Entity *> fields;
+	Array<String>   tags;
 	Ast *node;
 	Ast *node;
 	Scope *  scope;
 	Scope *  scope;