Ver Fonte

`using` `immutable` `thread_local` on variable declarations

Ginger Bill há 8 anos atrás
pai
commit
3f023509a7
10 ficheiros alterados com 165 adições e 194 exclusões
  1. 6 6
      code/demo.odin
  2. 1 1
      core/_preload.odin
  3. 2 72
      src/check_decl.c
  4. 8 7
      src/check_expr.c
  5. 64 60
      src/check_stmt.c
  6. 7 3
      src/checker.c
  7. 7 6
      src/entity.c
  8. 10 10
      src/ir.c
  9. 59 29
      src/parser.c
  10. 1 0
      src/tokenizer.c

+ 6 - 6
code/demo.odin

@@ -9,13 +9,13 @@
 #import "sync.odin";
 #import "utf8.odin";
 
+T :: struct { x, y: int };
+thread_local t: T;
+
 main :: proc() {
-	T :: struct { x, y: int }
-	foo :: proc(using immutable t: T) {
-		a0 := t.x;
-		a1 := x;
-		x = 123; // Error: Cannot assign to an immutable: `x`
-	}
+	immutable using t := T{123, 321};
+	fmt.println(t);
+
 
 	// foo :: proc(x: ^i32) -> (int, int) {
 	// 	fmt.println("^int");

+ 1 - 1
core/_preload.odin

@@ -150,7 +150,7 @@ Context :: struct #ordered {
 	user_index: int,
 }
 
-#thread_local __context: Context;
+thread_local __context: Context;
 
 
 DEFAULT_ALIGNMENT :: align_of([vector 4]f32);

+ 2 - 72
src/check_decl.c

@@ -107,78 +107,6 @@ void check_init_variables(Checker *c, Entity **lhs, isize lhs_count, AstNodeArra
 	gb_temp_arena_memory_end(tmp);
 }
 
-void check_var_decl_node(Checker *c, AstNodeValueDecl *vd) {
-	GB_ASSERT(vd->is_var == true);
-	isize entity_count = vd->names.count;
-	isize entity_index = 0;
-	Entity **entities = gb_alloc_array(c->allocator, Entity *, entity_count);
-
-	for_array(i, vd->names) {
-		AstNode *name = vd->names.e[i];
-		Entity *entity = NULL;
-		if (name->kind == AstNode_Ident) {
-			Token token = name->Ident;
-			String str = token.string;
-			Entity *found = NULL;
-			// NOTE(bill): Ignore assignments to `_`
-			if (str_ne(str, str_lit("_"))) {
-				found = current_scope_lookup_entity(c->context.scope, str);
-			}
-			if (found == NULL) {
-				entity = make_entity_variable(c->allocator, c->context.scope, token, NULL);
-				add_entity_definition(&c->info, name, entity);
-			} else {
-				TokenPos pos = found->token.pos;
-				error(token,
-				      "Redeclaration of `%.*s` in this scope\n"
-				      "\tat %.*s(%td:%td)",
-				      LIT(str), LIT(pos.file), pos.line, pos.column);
-				entity = found;
-			}
-		} else {
-			error_node(name, "A variable declaration must be an identifier");
-		}
-		if (entity == NULL) {
-			entity = make_entity_dummy_variable(c->allocator, c->global_scope, ast_node_token(name));
-		}
-		entities[entity_index++] = entity;
-	}
-
-	Type *init_type = NULL;
-	if (vd->type) {
-		init_type = check_type_extra(c, vd->type, NULL);
-		if (init_type == NULL) {
-			init_type = t_invalid;
-		}
-	}
-
-	for (isize i = 0; i < entity_count; i++) {
-		Entity *e = entities[i];
-		GB_ASSERT(e != NULL);
-		if (e->flags & EntityFlag_Visited) {
-			e->type = t_invalid;
-			continue;
-		}
-		e->flags |= EntityFlag_Visited;
-
-		if (e->type == NULL) {
-			e->type = init_type;
-		}
-	}
-
-	check_arity_match(c, vd);
-	check_init_variables(c, entities, entity_count, vd->values, str_lit("variable declaration"));
-
-	for_array(i, vd->names) {
-		if (entities[i] != NULL) {
-			add_entity(c, c->context.scope, vd->names.e[i], entities[i]);
-		}
-	}
-
-}
-
-
-
 void check_init_constant(Checker *c, Entity *e, Operand *operand) {
 	if (operand->mode == Addressing_Invalid ||
 	    operand->type == t_invalid ||
@@ -443,6 +371,8 @@ void check_var_decl(Checker *c, Entity *e, Entity **entities, isize entity_count
 	GB_ASSERT(e->type == NULL);
 	GB_ASSERT(e->kind == Entity_Variable);
 
+	u32 flags = c->context.decl->var_decl_flags;
+
 	if (e->flags & EntityFlag_Visited) {
 		e->type = t_invalid;
 		return;

+ 8 - 7
src/check_expr.c

@@ -778,7 +778,8 @@ Type *check_get_params(Checker *c, Scope *scope, AstNodeArray params, bool *is_v
 			for_array(j, p->names) {
 				AstNode *name = p->names.e[j];
 				if (ast_node_expect(name, AstNode_Ident)) {
-					Entity *param = make_entity_param(c->allocator, scope, name->Ident, type, p->flags&FieldFlag_using);
+					Entity *param = make_entity_param(c->allocator, scope, name->Ident, type,
+					                                  p->flags&FieldFlag_using, p->flags&FieldFlag_immutable);
 					if (p->flags&FieldFlag_no_alias) {
 						param->flags |= EntityFlag_NoAlias;
 					}
@@ -826,7 +827,7 @@ Type *check_get_results(Checker *c, Scope *scope, AstNodeArray results) {
 		Token token = ast_node_token(item);
 		token.string = str_lit(""); // NOTE(bill): results are not named
 		// TODO(bill): Should I have named results?
-		Entity *param = make_entity_param(c->allocator, scope, token, type, false);
+		Entity *param = make_entity_param(c->allocator, scope, token, type, false, false);
 		// NOTE(bill): No need to record
 		variables[variable_index++] = param;
 	}
@@ -2939,8 +2940,8 @@ bool check_builtin_procedure(Checker *c, Operand *operand, AstNode *call, i32 id
 		}
 
 		Entity **variables = gb_alloc_array(c->allocator, Entity *, 2);
-		variables[0] = make_entity_param(c->allocator, NULL, empty_token, type, false);
-		variables[1] = make_entity_param(c->allocator, NULL, empty_token, t_bool, false);
+		variables[0] = make_entity_param(c->allocator, NULL, empty_token, type, false, true);
+		variables[1] = make_entity_param(c->allocator, NULL, empty_token, t_bool, false, true);
 
 		Type *tuple = make_type_tuple(c->allocator);
 		tuple->Tuple.variables = variables;
@@ -4128,7 +4129,7 @@ ExprKind check__expr_base(Checker *c, Operand *o, AstNode *node, Type *type_hint
 					variables[variable_index++] = make_entity_constant(c->allocator, NULL, empty_token, type, operand.value);
 					break;
 				default:
-					variables[variable_index++] = make_entity_param(c->allocator, NULL, empty_token, type, false);
+					variables[variable_index++] = make_entity_param(c->allocator, NULL, empty_token, type, false, true);
 					break;
 				}
 			}
@@ -4745,8 +4746,8 @@ ExprKind check__expr_base(Checker *c, Operand *o, AstNode *node, Type *type_hint
 				Entity **variables = gb_alloc_array(c->allocator, Entity *, 2);
 				Type *elem = t->Maybe.elem;
 				Token tok = make_token_ident(str_lit(""));
-				variables[0] = make_entity_param(c->allocator, NULL, tok, elem, false);
-				variables[1] = make_entity_param(c->allocator, NULL, tok, t_bool, false);
+				variables[0] = make_entity_param(c->allocator, NULL, tok, elem, false, true);
+				variables[1] = make_entity_param(c->allocator, NULL, tok, t_bool, false, true);
 
 				Type *tuple = make_type_tuple(c->allocator);
 				tuple->Tuple.variables = variables;

+ 64 - 60
src/check_stmt.c

@@ -720,8 +720,7 @@ void check_stmt_internal(Checker *c, AstNode *node, u32 flags) {
 					found = current_scope_lookup_entity(c->context.scope, str);
 				}
 				if (found == NULL) {
-					entity = make_entity_variable(c->allocator, c->context.scope, token, type);
-					entity->Variable.is_immutable = true;
+					entity = make_entity_variable(c->allocator, c->context.scope, token, type, true);
 					add_entity_definition(&c->info, name, entity);
 				} else {
 					TokenPos pos = found->token.pos;
@@ -1020,9 +1019,8 @@ void check_stmt_internal(Checker *c, AstNode *node, u32 flags) {
 					tt = make_type_pointer(c->allocator, case_type);
 					add_type_info_type(c, tt);
 				}
-				Entity *tag_var = make_entity_variable(c->allocator, c->context.scope, ms->var->Ident, tt);
+				Entity *tag_var = make_entity_variable(c->allocator, c->context.scope, ms->var->Ident, tt, true);
 				tag_var->flags |= EntityFlag_Used;
-				tag_var->Variable.is_immutable = true;
 				add_entity(c, c->context.scope, ms->var, tag_var);
 				add_entity_use(c, ms->var, tag_var);
 			}
@@ -1072,6 +1070,10 @@ void check_stmt_internal(Checker *c, AstNode *node, u32 flags) {
 
 	case_ast_node(us, UsingStmt, node);
 		switch (us->node->kind) {
+		default:
+			// TODO(bill): Better error message for invalid using statement
+			error(us->token, "Invalid `using` statement");
+			break;
 		case_ast_node(es, ExprStmt, us->node);
 			// TODO(bill): Allow for just a LHS expression list rather than this silly code
 			Entity *e = NULL;
@@ -1199,52 +1201,6 @@ void check_stmt_internal(Checker *c, AstNode *node, u32 flags) {
 			}
 		case_end;
 
-		case_ast_node(vd, ValueDecl, us->node);
-			if (!vd->is_var) {
-				error_node(us->node, "`using` can only be applied to a variable declaration");
-				return;
-			}
-
-			if (vd->names.count > 1 && vd->type != NULL) {
-				error(us->token, "`using` can only be applied to one variable of the same type");
-			}
-
-			check_var_decl_node(c, vd);
-
-			for_array(name_index, vd->names) {
-				AstNode *item = vd->names.e[name_index];
-				if (item->kind != AstNode_Ident) {
-					// TODO(bill): Handle error here???
-					continue;
-				}
-				ast_node(i, Ident, item);
-				String name = i->string;
-				Entity *e = scope_lookup_entity(c->context.scope, name);
-				Type *t = base_type(type_deref(e->type));
-				if (is_type_struct(t) || is_type_raw_union(t)) {
-					Scope **found = map_scope_get(&c->info.scopes, hash_pointer(t->Record.node));
-					GB_ASSERT(found != NULL);
-					for_array(i, (*found)->elements.entries) {
-						Entity *f = (*found)->elements.entries.e[i].value;
-						if (f->kind == Entity_Variable) {
-							Entity *uvar = make_entity_using_variable(c->allocator, e, f->token, f->type);
-							Entity *prev = scope_insert_entity(c->context.scope, uvar);
-							if (prev != NULL) {
-								error(us->token, "Namespace collision while `using` `%.*s` of: %.*s", LIT(name), LIT(prev->token.string));
-								return;
-							}
-						}
-					}
-				} else {
-					error(us->token, "`using` can only be applied to variables of type struct or raw_union");
-					return;
-				}
-			}
-		case_end;
-
-		default:
-			error(us->token, "Invalid AST: Using Statement");
-			break;
 		}
 	case_end;
 
@@ -1268,14 +1224,20 @@ void check_stmt_internal(Checker *c, AstNode *node, u32 flags) {
 
 	case_ast_node(vd, ValueDecl, node);
 		if (vd->is_var) {
-			isize entity_count = vd->names.count;
-			isize entity_index = 0;
-			Entity **entities = gb_alloc_array(c->allocator, Entity *, entity_count);
+			Entity **entities = gb_alloc_array(c->allocator, Entity *, vd->names.count);
+			isize entity_count = 0;
+
+			if (vd->flags&VarDeclFlag_thread_local &&
+			    !c->context.scope->is_file) {
+				error_node(node, "`thread_local` may only be applied to a variable declaration");
+			}
 
 			for_array(i, vd->names) {
 				AstNode *name = vd->names.e[i];
 				Entity *entity = NULL;
-				if (name->kind == AstNode_Ident) {
+				if (name->kind != AstNode_Ident) {
+					error_node(name, "A variable declaration must be an identifier");
+				} else {
 					Token token = name->Ident;
 					String str = token.string;
 					Entity *found = NULL;
@@ -1284,7 +1246,7 @@ void check_stmt_internal(Checker *c, AstNode *node, u32 flags) {
 						found = current_scope_lookup_entity(c->context.scope, str);
 					}
 					if (found == NULL) {
-						entity = make_entity_variable(c->allocator, c->context.scope, token, NULL);
+						entity = make_entity_variable(c->allocator, c->context.scope, token, NULL, vd->flags&VarDeclFlag_immutable);
 						add_entity_definition(&c->info, name, entity);
 					} else {
 						TokenPos pos = found->token.pos;
@@ -1294,13 +1256,11 @@ void check_stmt_internal(Checker *c, AstNode *node, u32 flags) {
 						      LIT(str), LIT(pos.file), pos.line, pos.column);
 						entity = found;
 					}
-				} else {
-					error_node(name, "A variable declaration must be an identifier");
 				}
 				if (entity == NULL) {
 					entity = make_entity_dummy_variable(c->allocator, c->global_scope, ast_node_token(name));
 				}
-				entities[entity_index++] = entity;
+				entities[entity_count++] = entity;
 			}
 
 			Type *init_type = NULL;
@@ -1320,11 +1280,12 @@ void check_stmt_internal(Checker *c, AstNode *node, u32 flags) {
 				}
 				e->flags |= EntityFlag_Visited;
 
-				if (e->type == NULL)
+				if (e->type == NULL) {
 					e->type = init_type;
+				}
 			}
-			check_arity_match(c, vd);
 
+			check_arity_match(c, vd);
 			check_init_variables(c, entities, entity_count, vd->values, str_lit("variable declaration"));
 
 			for_array(i, vd->names) {
@@ -1332,6 +1293,49 @@ void check_stmt_internal(Checker *c, AstNode *node, u32 flags) {
 					add_entity(c, c->context.scope, vd->names.e[i], entities[i]);
 				}
 			}
+
+			if ((vd->flags & VarDeclFlag_using) != 0) {
+				Token token = ast_node_token(node);
+				if (vd->type != NULL && entity_count > 1) {
+					error(token, "`using` can only be applied to one variable of the same type");
+					// TODO(bill): Should a `continue` happen here?
+				}
+
+				for (isize entity_index = 0; entity_index < entity_count; entity_index++) {
+					Entity *e = entities[entity_index];
+					if (e == NULL) {
+						continue;
+					}
+					bool is_immutable = false;
+					if (e->kind == Entity_Variable) {
+						is_immutable = e->Variable.is_immutable;
+					}
+
+					String name = e->token.string;
+					Type *t = base_type(type_deref(e->type));
+					if (is_type_struct(t) || is_type_raw_union(t)) {
+						Scope **found = map_scope_get(&c->info.scopes, hash_pointer(t->Record.node));
+						GB_ASSERT(found != NULL);
+						for_array(i, (*found)->elements.entries) {
+							Entity *f = (*found)->elements.entries.e[i].value;
+							if (f->kind == Entity_Variable) {
+								Entity *uvar = make_entity_using_variable(c->allocator, e, f->token, f->type);
+								uvar->Variable.is_immutable = is_immutable;
+								Entity *prev = scope_insert_entity(c->context.scope, uvar);
+								if (prev != NULL) {
+									error(token, "Namespace collision while `using` `%.*s` of: %.*s", LIT(name), LIT(prev->token.string));
+									return;
+								}
+							}
+						}
+					} else {
+						// NOTE(bill): skip the rest to remove extra errors
+						error(token, "`using` can only be applied to variables of type struct or raw_union");
+						return;
+					}
+				}
+			}
+
 		} else {
 			// NOTE(bill): Handled elsewhere
 		}

+ 7 - 3
src/checker.c

@@ -61,7 +61,7 @@ typedef struct DeclInfo {
 	AstNode *type_expr;
 	AstNode *init_expr;
 	AstNode *proc_lit; // AstNode_ProcLit
-	u32      var_decl_tags;
+	u32      var_decl_flags;
 
 	MapBool deps; // Key: Entity *
 } DeclInfo;
@@ -1395,8 +1395,12 @@ void check_collect_entities(Checker *c, AstNodeArray nodes, bool is_file_scope)
 						error_node(name, "A declaration's name must be an identifier, got %.*s", LIT(ast_node_strings[name->kind]));
 						continue;
 					}
-					Entity *e = make_entity_variable(c->allocator, c->context.scope, name->Ident, NULL);
+					Entity *e = make_entity_variable(c->allocator, c->context.scope, name->Ident, NULL, vd->flags&VarDeclFlag_immutable);
 					e->identifier = name;
+					if (vd->flags&VarDeclFlag_using) {
+						vd->flags &= ~VarDeclFlag_using; // NOTE(bill): This error will be only caught once
+						error_node(name, "`using` is not allowed at the file scope");
+					}
 					entities[entity_index++] = e;
 
 					DeclInfo *d = di;
@@ -1405,7 +1409,7 @@ void check_collect_entities(Checker *c, AstNodeArray nodes, bool is_file_scope)
 						d = make_declaration_info(heap_allocator(), e->scope);
 						d->type_expr = vd->type;
 						d->init_expr = init_expr;
-						d->var_decl_tags = vd->tags;
+						d->var_decl_flags = vd->flags;
 					}
 
 					add_entity_and_decl_info(c, name, e, d);

+ 7 - 6
src/entity.c

@@ -103,8 +103,9 @@ Entity *alloc_entity(gbAllocator a, EntityKind kind, Scope *scope, Token token,
 	return entity;
 }
 
-Entity *make_entity_variable(gbAllocator a, Scope *scope, Token token, Type *type) {
+Entity *make_entity_variable(gbAllocator a, Scope *scope, Token token, Type *type, bool is_immutable) {
 	Entity *entity = alloc_entity(a, Entity_Variable, scope, token, type);
+	entity->Variable.is_immutable = is_immutable;
 	return entity;
 }
 
@@ -128,8 +129,8 @@ Entity *make_entity_type_name(gbAllocator a, Scope *scope, Token token, Type *ty
 	return entity;
 }
 
-Entity *make_entity_param(gbAllocator a, Scope *scope, Token token, Type *type, bool anonymous) {
-	Entity *entity = make_entity_variable(a, scope, token, type);
+Entity *make_entity_param(gbAllocator a, Scope *scope, Token token, Type *type, bool anonymous, bool is_immutable) {
+	Entity *entity = make_entity_variable(a, scope, token, type, is_immutable);
 	entity->flags |= EntityFlag_Used;
 	entity->flags |= EntityFlag_Anonymous*(anonymous != 0);
 	entity->flags |= EntityFlag_Param;
@@ -137,7 +138,7 @@ Entity *make_entity_param(gbAllocator a, Scope *scope, Token token, Type *type,
 }
 
 Entity *make_entity_field(gbAllocator a, Scope *scope, Token token, Type *type, bool anonymous, i32 field_src_index) {
-	Entity *entity = make_entity_variable(a, scope, token, type);
+	Entity *entity = make_entity_variable(a, scope, token, type, false);
 	entity->Variable.field_src_index = field_src_index;
 	entity->Variable.field_index = field_src_index;
 	entity->flags |= EntityFlag_Field;
@@ -146,7 +147,7 @@ Entity *make_entity_field(gbAllocator a, Scope *scope, Token token, Type *type,
 }
 
 Entity *make_entity_vector_elem(gbAllocator a, Scope *scope, Token token, Type *type, i32 field_src_index) {
-	Entity *entity = make_entity_variable(a, scope, token, type);
+	Entity *entity = make_entity_variable(a, scope, token, type, false);
 	entity->Variable.field_src_index = field_src_index;
 	entity->Variable.field_index = field_src_index;
 	entity->flags |= EntityFlag_Field;
@@ -191,6 +192,6 @@ Entity *make_entity_implicit_value(gbAllocator a, String name, Type *type, Impli
 
 Entity *make_entity_dummy_variable(gbAllocator a, Scope *scope, Token token) {
 	token.string = str_lit("_");
-	return make_entity_variable(a, scope, token, NULL);
+	return make_entity_variable(a, scope, token, NULL, false);
 }
 

+ 10 - 10
src/ir.c

@@ -1230,7 +1230,7 @@ irValue *ir_add_local_generated(irProcedure *proc, Type *type) {
 	Entity *e = make_entity_variable(proc->module->allocator,
 	                                 scope,
 	                                 empty_token,
-	                                 type);
+	                                 type, false);
 	return ir_add_local(proc, e);
 }
 
@@ -5064,7 +5064,7 @@ void ir_init_module(irModule *m, Checker *c, BuildContext *build_context) {
 		{
 			String name = str_lit(IR_TYPE_INFO_DATA_NAME);
 			isize count = c->info.type_info_map.entries.count;
-			Entity *e = make_entity_variable(m->allocator, NULL, make_token_ident(name), make_type_array(m->allocator, t_type_info, count));
+			Entity *e = make_entity_variable(m->allocator, NULL, make_token_ident(name), make_type_array(m->allocator, t_type_info, count), false);
 			irValue *g = ir_make_value_global(m->allocator, e, NULL);
 			g->Global.is_private  = true;
 			ir_module_add_value(m, e, g);
@@ -5096,7 +5096,7 @@ void ir_init_module(irModule *m, Checker *c, BuildContext *build_context) {
 
 			String name = str_lit(IR_TYPE_INFO_DATA_MEMBER_NAME);
 			Entity *e = make_entity_variable(m->allocator, NULL, make_token_ident(name),
-			                                 make_type_array(m->allocator, t_type_info_member, count));
+			                                 make_type_array(m->allocator, t_type_info_member, count), false);
 			irValue *g = ir_make_value_global(m->allocator, e, NULL);
 			ir_module_add_value(m, e, g);
 			map_ir_value_set(&m->members, hash_string(name), g);
@@ -5303,7 +5303,7 @@ void ir_gen_tree(irGen *s) {
 
 		case Entity_Variable: {
 			irValue *g = ir_make_value_global(a, e, NULL);
-			if (decl->var_decl_tags & VarDeclTag_thread_local) {
+			if (decl->var_decl_flags & VarDeclFlag_thread_local) {
 				g->Global.is_thread_local = true;
 			}
 			irGlobalVariable var = {0};
@@ -5406,11 +5406,11 @@ void ir_gen_tree(irGen *s) {
 		proc_results->Tuple.variables = gb_alloc_array(a, Entity *, 1);
 		proc_results->Tuple.variable_count = 1;
 
-		proc_params->Tuple.variables[0] = make_entity_param(a, proc_scope, blank_token, t_rawptr, false);
-		proc_params->Tuple.variables[1] = make_entity_param(a, proc_scope, make_token_ident(str_lit("reason")), t_i32, false);
-		proc_params->Tuple.variables[2] = make_entity_param(a, proc_scope, blank_token, t_rawptr, false);
+		proc_params->Tuple.variables[0] = make_entity_param(a, proc_scope, blank_token, t_rawptr, false, false);
+		proc_params->Tuple.variables[1] = make_entity_param(a, proc_scope, make_token_ident(str_lit("reason")), t_i32, false, false);
+		proc_params->Tuple.variables[2] = make_entity_param(a, proc_scope, blank_token, t_rawptr, false, false);
 
-		proc_results->Tuple.variables[0] = make_entity_param(a, proc_scope, empty_token, t_i32, false);
+		proc_results->Tuple.variables[0] = make_entity_param(a, proc_scope, empty_token, t_i32, false, false);
 
 
 		Type *proc_type = make_type_proc(a, proc_scope,
@@ -5793,7 +5793,7 @@ void ir_gen_tree(irGen *s) {
 									token.string.text = gb_alloc_array(a, u8, name_len);
 									token.string.len = gb_snprintf(cast(char *)token.string.text, name_len,
 									                               "%s-%d", name_base, id)-1;
-									Entity *e = make_entity_variable(a, NULL, token, make_type_array(a, t_string, count));
+									Entity *e = make_entity_variable(a, NULL, token, make_type_array(a, t_string, count), false);
 									name_array = ir_make_value_global(a, e, NULL);
 									name_array->Global.is_private = true;
 									ir_module_add_value(m, e, name_array);
@@ -5808,7 +5808,7 @@ void ir_gen_tree(irGen *s) {
 									token.string.text = gb_alloc_array(a, u8, name_len);
 									token.string.len = gb_snprintf(cast(char *)token.string.text, name_len,
 									                               "%s-%d", name_base, id)-1;
-									Entity *e = make_entity_variable(a, NULL, token, make_type_array(a, t_type_info_enum_value, count));
+									Entity *e = make_entity_variable(a, NULL, token, make_type_array(a, t_type_info_enum_value, count), false);
 									value_array = ir_make_value_global(a, e, NULL);
 									value_array->Global.is_private = true;
 									ir_module_add_value(m, e, value_array);

+ 59 - 29
src/parser.c

@@ -83,9 +83,11 @@ typedef enum ProcCallingConvention {
 	ProcCC_Invalid,
 } ProcCallingConvention;
 
-typedef enum VarDeclTag {
-	VarDeclTag_thread_local = 1<<0,
-} VarDeclTag;
+typedef enum VarDeclFlag {
+	VarDeclFlag_thread_local = 1<<0,
+	VarDeclFlag_using        = 1<<1,
+	VarDeclFlag_immutable    = 1<<2,
+} VarDeclFlag;
 
 typedef enum StmtStateFlag {
 	StmtStateFlag_bounds_check    = 1<<0,
@@ -274,7 +276,7 @@ AST_NODE_KIND(_DeclBegin,      "", i32) \
 		AstNodeArray names;  \
 		AstNode *    type;   \
 		AstNodeArray values; \
-		u64          tags;   \
+		u32          flags;  \
 	}) \
 	AST_NODE_KIND(ImportDecl, "import declaration", struct { \
 		Token     token;        \
@@ -1209,6 +1211,7 @@ void fix_advance_to_next_stmt(AstFile *f) {
 		case Token_defer:
 		case Token_asm:
 		case Token_using:
+		case Token_immutable:
 
 		case Token_break:
 		case Token_continue:
@@ -3087,31 +3090,73 @@ AstNode *parse_stmt(AstFile *f) {
 		// TODO(bill): Make using statements better
 		Token token = expect_token(f, Token_using);
 		AstNode *node = parse_stmt(f);
-		bool valid = false;
 
 		switch (node->kind) {
+		case AstNode_ValueDecl:
+			if (!node->ValueDecl.is_var) {
+				syntax_error(token, "`using` may not be applied to constant declarations");
+				return make_bad_stmt(f, token, f->curr_token);
+			} else {
+				if (f->curr_proc == NULL) {
+					syntax_error(token, "`using` is not allowed at the file scope");
+				} else {
+					node->ValueDecl.flags |= VarDeclFlag_using;
+				}
+				return node;
+			}
+			break;
 		case AstNode_ExprStmt: {
 			AstNode *e = unparen_expr(node->ExprStmt.expr);
 			while (e->kind == AstNode_SelectorExpr) {
 				e = unparen_expr(e->SelectorExpr.selector);
 			}
 			if (e->kind == AstNode_Ident) {
-				valid = true;
+				return make_using_stmt(f, token, node);
 			}
 		} break;
-		case AstNode_ValueDecl:
-			valid = node->ValueDecl.is_var;
-			break;
 		}
 
-		if (!valid) {
-			syntax_error(token, "Illegal use of `using` statement.");
-			return make_bad_stmt(f, token, f->curr_token);
-		}
+		syntax_error(token, "Illegal use of `using` statement");
+		return make_bad_stmt(f, token, f->curr_token);
+	} break;
+
+	case Token_immutable: {
+		Token token = expect_token(f, Token_immutable);
+		AstNode *node = parse_stmt(f);
 
-		return make_using_stmt(f, token, node);
+		if (node->kind == AstNode_ValueDecl) {
+			if (!node->ValueDecl.is_var) {
+				syntax_error(token, "`immutable` may not be applied to constant declarations");
+				return make_bad_stmt(f, token, f->curr_token);
+			} else {
+				node->ValueDecl.flags |= VarDeclFlag_immutable;
+				return node;
+			}
+		}
+		syntax_error(token, "`immutable` may only be applied to a variable declaration");
+		return make_bad_stmt(f, token, f->curr_token);
 	} break;
 
+	case Token_thread_local: {
+		Token token = expect_token(f, Token_thread_local);
+		AstNode *node = parse_stmt(f);
+
+		if (node->kind == AstNode_ValueDecl) {
+			if (!node->ValueDecl.is_var) {
+				syntax_error(token, "`thread_local` may not be applied to constant declarations");
+				return make_bad_stmt(f, token, f->curr_token);
+			}
+			if (f->curr_proc != NULL) {
+				syntax_error(token, "`thread_local` is only allowed at the file scope");
+			} else {
+				node->ValueDecl.flags |= VarDeclFlag_thread_local;
+			}
+			return node;
+		}
+		syntax_error(token, "`thread_local` may only be applied to a variable declaration");
+		return make_bad_stmt(f, token, f->curr_token);
+	}
+
 	case Token_push_allocator: {
 		next_token(f);
 		isize prev_level = f->expr_level;
@@ -3237,21 +3282,6 @@ AstNode *parse_stmt(AstFile *f) {
 			}
 			expect_semicolon(f, s);
 			return s;
-		} else if (str_eq(tag, str_lit("thread_local"))) {
-			AstNode *decl = parse_simple_stmt(f);
-			if (decl->kind != AstNode_ValueDecl &&
-			    !decl->ValueDecl.is_var) {
-				syntax_error(token, "#thread_local may only be applied to variable declarations");
-				return make_bad_decl(f, token, ast_node_token(decl));
-			}
-			if (f->curr_proc != NULL) {
-				syntax_error(token, "#thread_local is only allowed at the file scope");
-				return make_bad_decl(f, token, ast_node_token(decl));
-			}
-
-			GB_ASSERT(decl->kind == AstNode_ValueDecl);
-			decl->ValueDecl.tags |= VarDeclTag_thread_local;
-			return decl;
 		} else if (str_eq(tag, str_lit("bounds_check"))) {
 			s = parse_stmt(f);
 			s->stmt_state_flags |= StmtStateFlag_bounds_check;

+ 1 - 0
src/tokenizer.c

@@ -107,6 +107,7 @@ TOKEN_KIND(Token__KeywordBegin, "_KeywordBegin"), \
 	TOKEN_KIND(Token_using,          "using"), \
 	TOKEN_KIND(Token_no_alias,       "no_alias"), \
 	TOKEN_KIND(Token_immutable,      "immutable"), \
+	TOKEN_KIND(Token_thread_local,   "thread_local"), \
 	TOKEN_KIND(Token_asm,            "asm"), \
 	TOKEN_KIND(Token_push_allocator, "push_allocator"), \
 	TOKEN_KIND(Token_push_context,   "push_context"), \