From 85d1507d8b7e0139135e040b7b4b23c02097a155 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 7 Dec 2025 18:44:58 -0500 Subject: Consolidate logic for enums with and without tags with fields. --- CHANGES.md | 2 ++ src/compile/enums.c | 48 +++++++------------------------------- src/compile/headers.c | 61 +++++++++++++++---------------------------------- src/compile/optionals.c | 30 ++++++++++-------------- src/compile/whens.c | 8 ++----- src/typecheck.c | 17 ++------------ src/types.c | 10 +------- src/types.h | 1 - test/enums.tm | 6 ++--- 9 files changed, 48 insertions(+), 135 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 8468e4a7..3e94c85d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,8 @@ - For an enum `Foo(A,B,C)`, the syntax `f!` now desugars to `f.A!` using the first tag defined in the enum. - Error messages are more helpful for `foo.Whatever!` enum field accessing. +- Simplified logic for enums so there is less difference between enums that + have tags with member fields and those without. ## v2025-11-30 diff --git a/src/compile/enums.c b/src/compile/enums.c index 853625ca..24da79b8 100644 --- a/src/compile/enums.c +++ b/src/compile/enums.c @@ -12,8 +12,6 @@ Text_t compile_enum_typeinfo(env_t *env, const char *name, tag_ast_t *tags) { // Compile member types and constructors: Text_t member_typeinfos = EMPTY_TEXT; for (tag_ast_t *tag = tags; tag; tag = tag->next) { - if (!tag->fields) continue; - const char *tag_name = String(name, "$", tag->name); type_t *tag_type = Table$str_get(*env->types, tag_name); assert(tag_type && tag_type->tag == StructType); @@ -38,9 +36,7 @@ Text_t compile_enum_typeinfo(env_t *env, const char *name, tag_ast_t *tags) { for (tag_ast_t *tag = tags; tag; tag = tag->next) { const char *tag_type_name = String(name, "$", tag->name); type_t *tag_type = Table$str_get(*env->types, tag_type_name); - if (tag_type && Match(tag_type, StructType)->fields) - typeinfo = Texts(typeinfo, "{\"", tag->name, "\", ", compile_type_info(tag_type), "}, "); - else typeinfo = Texts(typeinfo, "{\"", tag->name, "\"}, "); + typeinfo = Texts(typeinfo, "{\"", tag->name, "\", ", compile_type_info(tag_type), "}, "); } typeinfo = Texts(typeinfo, "}}}};\n"); return Texts(member_typeinfos, typeinfo); @@ -49,8 +45,6 @@ Text_t compile_enum_typeinfo(env_t *env, const char *name, tag_ast_t *tags) { Text_t compile_enum_constructors(env_t *env, const char *name, tag_ast_t *tags) { Text_t constructors = EMPTY_TEXT; for (tag_ast_t *tag = tags; tag; tag = tag->next) { - if (!tag->fields) continue; - Text_t arg_sig = EMPTY_TEXT; for (arg_ast_t *field = tag->fields; field; field = field->next) { type_t *field_t = get_arg_ast_type(env, field); @@ -76,25 +70,16 @@ Text_t compile_enum_constructors(env_t *env, const char *name, tag_ast_t *tags) Text_t compile_enum_header(env_t *env, const char *name, tag_ast_t *tags) { Text_t all_defs = EMPTY_TEXT; Text_t none_name = namespace_name(env, env->namespace, Texts(name, "$none")); - Text_t enum_name = namespace_name(env, env->namespace, Texts(name, "$$enum")); Text_t enum_tags = Texts("{ ", none_name, "=0, "); assert(Table$str_get(*env->types, name)); - bool has_any_tags_with_fields = false; for (tag_ast_t *tag = tags; tag; tag = tag->next) { Text_t tag_name = namespace_name(env, env->namespace, Texts(name, "$tag$", tag->name)); enum_tags = Texts(enum_tags, tag_name); if (tag->next) enum_tags = Texts(enum_tags, ", "); - has_any_tags_with_fields = has_any_tags_with_fields || (tag->fields != NULL); } enum_tags = Texts(enum_tags, " }"); - if (!has_any_tags_with_fields) { - Text_t enum_def = Texts("enum ", enum_name, " ", enum_tags, ";\n"); - Text_t info = namespace_name(env, env->namespace, Texts(name, "$$info")); - return Texts(enum_def, "extern const TypeInfo_t ", info, ";\n"); - } - Text_t struct_name = namespace_name(env, env->namespace, Texts(name, "$$struct")); Text_t enum_def = Texts("struct ", struct_name, " {\n" @@ -103,7 +88,6 @@ Text_t compile_enum_header(env_t *env, const char *name, tag_ast_t *tags) { " $tag;\n" "union {\n"); for (tag_ast_t *tag = tags; tag; tag = tag->next) { - if (!tag->fields) continue; Text_t field_def = compile_struct_header(env, NewAST(tag->file, tag->start, tag->end, StructDef, .name = Text$as_c_string(Texts(name, "$", tag->name)), .fields = tag->fields)); @@ -117,8 +101,6 @@ Text_t compile_enum_header(env_t *env, const char *name, tag_ast_t *tags) { Text_t info = namespace_name(env, env->namespace, Texts(name, "$$info")); all_defs = Texts(all_defs, "extern const TypeInfo_t ", info, ";\n"); for (tag_ast_t *tag = tags; tag; tag = tag->next) { - if (!tag->fields) continue; - Text_t arg_sig = EMPTY_TEXT; for (arg_ast_t *field = tag->fields; field; field = field->next) { type_t *field_t = get_arg_ast_type(env, field); @@ -140,11 +122,8 @@ Text_t compile_empty_enum(type_t *t) { tag_t *tag = enum_->tags; assert(tag); assert(tag->type); - if (Match(tag->type, StructType)->fields) - return Texts("((", compile_type(t), "){.$tag=", tag->tag_value, ", .", tag->name, "=", compile_empty(tag->type), - "})"); - else if (enum_has_fields(t)) return Texts("((", compile_type(t), "){.$tag=", tag->tag_value, "})"); - else return Texts("((", compile_type(t), ")", tag->tag_value, ")"); + return Texts("((", compile_type(t), "){.$tag=", tag->tag_value, ", .", tag->name, "=", compile_empty(tag->type), + "})"); } public @@ -156,22 +135,11 @@ Text_t compile_enum_field_access(env_t *env, ast_t *ast) { for (tag_t *tag = e->tags; tag; tag = tag->next) { if (streq(f->field, tag->name)) { Text_t tag_name = namespace_name(e->env, e->env->namespace, Texts("tag$", tag->name)); - if (tag->type != NULL && Match(tag->type, StructType)->fields) { - Text_t member = compile_maybe_incref( - env, WrapLiteralCode(ast, Texts("_e.", tag->name), .type = tag->type), tag->type); - return Texts("({ ", compile_declaration(value_t, Text("_e")), " = ", - compile_to_pointer_depth(env, f->fielded, 0, false), "; ", "_e.$tag == ", tag_name, " ? ", - promote_to_optional(tag->type, member), " : ", compile_none(tag->type), "; })"); - } else if (fielded_t->tag == PointerType) { - Text_t fielded = compile_to_pointer_depth(env, f->fielded, 1, false); - return Texts("((", fielded, ")->$tag == ", tag_name, " ? OPTIONAL_EMPTY_STRUCT : NONE_EMPTY_STRUCT)"); - } else if (enum_has_fields(value_t)) { - Text_t fielded = compile(env, f->fielded); - return Texts("((", fielded, ").$tag == ", tag_name, " ? OPTIONAL_EMPTY_STRUCT : NONE_EMPTY_STRUCT)"); - } else { - Text_t fielded = compile(env, f->fielded); - return Texts("((", fielded, ") == ", tag_name, " ? OPTIONAL_EMPTY_STRUCT : NONE_EMPTY_STRUCT)"); - } + Text_t member = + compile_maybe_incref(env, WrapLiteralCode(ast, Texts("_e.", tag->name), .type = tag->type), tag->type); + return Texts("({ ", compile_declaration(value_t, Text("_e")), " = ", + compile_to_pointer_depth(env, f->fielded, 0, false), "; ", "_e.$tag == ", tag_name, " ? ", + promote_to_optional(tag->type, member), " : ", compile_none(tag->type), "; })"); } } code_err(ast, "The field '", f->field, "' is not a valid tag name of ", type_to_text(value_t)); diff --git a/src/compile/headers.c b/src/compile/headers.c index 1dcf7abb..dc57da77 100644 --- a/src/compile/headers.c +++ b/src/compile/headers.c @@ -79,28 +79,16 @@ static void _make_typedefs(compile_typedef_info_t *info, ast_t *ast) { *info->header = Texts(*info->header, "typedef struct ", struct_name, " ", type_name, ";\n"); } else if (ast->tag == EnumDef) { DeclareMatch(def, ast, EnumDef); - bool has_any_tags_with_fields = false; - for (tag_ast_t *tag = def->tags; tag; tag = tag->next) { - has_any_tags_with_fields = has_any_tags_with_fields || (tag->fields != NULL); - } + Text_t struct_name = namespace_name(info->env, info->env->namespace, Texts(def->name, "$$struct")); + Text_t type_name = namespace_name(info->env, info->env->namespace, Texts(def->name, "$$type")); + *info->header = Texts(*info->header, "typedef struct ", struct_name, " ", type_name, ";\n"); - if (has_any_tags_with_fields) { - Text_t struct_name = namespace_name(info->env, info->env->namespace, Texts(def->name, "$$struct")); - Text_t type_name = namespace_name(info->env, info->env->namespace, Texts(def->name, "$$type")); - *info->header = Texts(*info->header, "typedef struct ", struct_name, " ", type_name, ";\n"); - - for (tag_ast_t *tag = def->tags; tag; tag = tag->next) { - if (!tag->fields) continue; - Text_t tag_struct = - namespace_name(info->env, info->env->namespace, Texts(def->name, "$", tag->name, "$$struct")); - Text_t tag_type = - namespace_name(info->env, info->env->namespace, Texts(def->name, "$", tag->name, "$$type")); - *info->header = Texts(*info->header, "typedef struct ", tag_struct, " ", tag_type, ";\n"); - } - } else { - Text_t enum_name = namespace_name(info->env, info->env->namespace, Texts(def->name, "$$enum")); - Text_t type_name = namespace_name(info->env, info->env->namespace, Texts(def->name, "$$type")); - *info->header = Texts(*info->header, "typedef enum ", enum_name, " ", type_name, ";\n"); + for (tag_ast_t *tag = def->tags; tag; tag = tag->next) { + Text_t tag_struct = + namespace_name(info->env, info->env->namespace, Texts(def->name, "$", tag->name, "$$struct")); + Text_t tag_type = + namespace_name(info->env, info->env->namespace, Texts(def->name, "$", tag->name, "$$type")); + *info->header = Texts(*info->header, "typedef struct ", tag_struct, " ", tag_type, ";\n"); } } else if (ast->tag == LangDef) { DeclareMatch(def, ast, LangDef); @@ -124,29 +112,16 @@ static void add_type_headers(type_ast_t *type_ast, void *userdata) { // Force the type to get defined: (void)parse_type_ast(info->env, type_ast); DeclareMatch(enum_, type_ast, EnumTypeAST); - bool has_any_tags_with_fields = false; - for (tag_ast_t *tag = enum_->tags; tag; tag = tag->next) { - has_any_tags_with_fields = has_any_tags_with_fields || (tag->fields != NULL); - } - const char *name = String("enum$", (int64_t)(type_ast->start - type_ast->file->text)); - if (has_any_tags_with_fields) { - Text_t struct_name = namespace_name(info->env, info->env->namespace, Texts(name, "$$struct")); - Text_t type_name = namespace_name(info->env, info->env->namespace, Texts(name, "$$type")); - *info->header = Texts(*info->header, "typedef struct ", struct_name, " ", type_name, ";\n"); - - for (tag_ast_t *tag = enum_->tags; tag; tag = tag->next) { - if (!tag->fields) continue; - Text_t tag_struct = - namespace_name(info->env, info->env->namespace, Texts(name, "$", tag->name, "$$struct")); - Text_t tag_type = - namespace_name(info->env, info->env->namespace, Texts(name, "$", tag->name, "$$type")); - *info->header = Texts(*info->header, "typedef struct ", tag_struct, " ", tag_type, ";\n"); - } - } else { - Text_t enum_name = namespace_name(info->env, info->env->namespace, Texts(name, "$$enum")); - Text_t type_name = namespace_name(info->env, info->env->namespace, Texts(name, "$$type")); - *info->header = Texts(*info->header, "typedef enum ", enum_name, " ", type_name, ";\n"); + Text_t struct_name = namespace_name(info->env, info->env->namespace, Texts(name, "$$struct")); + Text_t type_name = namespace_name(info->env, info->env->namespace, Texts(name, "$$type")); + *info->header = Texts(*info->header, "typedef struct ", struct_name, " ", type_name, ";\n"); + + for (tag_ast_t *tag = enum_->tags; tag; tag = tag->next) { + Text_t tag_struct = + namespace_name(info->env, info->env->namespace, Texts(name, "$", tag->name, "$$struct")); + Text_t tag_type = namespace_name(info->env, info->env->namespace, Texts(name, "$", tag->name, "$$type")); + *info->header = Texts(*info->header, "typedef struct ", tag_struct, " ", tag_type, ";\n"); } *info->header = Texts(*info->header, compile_enum_header(info->env, name, enum_->tags)); diff --git a/src/compile/optionals.c b/src/compile/optionals.c index 5ad67602..b9a3742e 100644 --- a/src/compile/optionals.c +++ b/src/compile/optionals.c @@ -103,10 +103,7 @@ Text_t check_none(type_t *t, Text_t value) { else if (t->tag == BoolType) return Texts("((", value, ") == NONE_BOOL)"); else if (t->tag == TextType) return Texts("((", value, ").tag == TEXT_NONE)"); else if (t->tag == IntType || t->tag == ByteType || t->tag == StructType) return Texts("!(", value, ").has_value"); - else if (t->tag == EnumType) { - if (enum_has_fields(t)) return Texts("((", value, ").$tag == 0)"); - else return Texts("((", value, ") == 0)"); - } + else if (t->tag == EnumType) return Texts("((", value, ").$tag == 0)"); print_err("Optional check not implemented for: ", type_to_text(t)); return EMPTY_TEXT; } @@ -139,20 +136,17 @@ Text_t compile_non_optional(env_t *env, ast_t *ast) { for (tag_t *tag = e->tags; tag; tag = tag->next) { if (streq(f->field, tag->name)) { Text_t tag_name = namespace_name(e->env, e->env->namespace, Texts("tag$", tag->name)); - return Texts("({ ", compile_declaration(enum_t, Text("_test_enum")), " = ", - compile_to_pointer_depth(env, f->fielded, 0, true), ";", - "if unlikely (_test_enum.$tag != ", tag_name, ") {\n", "#line ", line, "\n", - "fail_source(", quoted_str(f->fielded->file->filename), ", ", - (int64_t)(f->fielded->start - f->fielded->file->text), ", ", - (int64_t)(f->fielded->end - f->fielded->file->text), ", ", "\"This was expected to be ", - tag->name, ", but it was: \", ", expr_as_text(Text("_test_enum"), enum_t, Text("false")), - ", \"\\n\");\n}\n", - Match(tag->type, StructType)->fields - ? compile_maybe_incref( - env, WrapLiteralCode(value, Texts("_test_enum.", tag->name), .type = tag->type), - tag->type) - : Text("EMPTY_STRUCT"), - "; })"); + return Texts( + "({ ", compile_declaration(enum_t, Text("_test_enum")), " = ", + compile_to_pointer_depth(env, f->fielded, 0, true), ";", + "if unlikely (_test_enum.$tag != ", tag_name, ") {\n", "#line ", line, "\n", "fail_source(", + quoted_str(f->fielded->file->filename), ", ", (int64_t)(f->fielded->start - f->fielded->file->text), + ", ", (int64_t)(f->fielded->end - f->fielded->file->text), ", ", "\"This was expected to be ", + tag->name, ", but it was: \", ", expr_as_text(Text("_test_enum"), enum_t, Text("false")), + ", \"\\n\");\n}\n", + compile_maybe_incref( + env, WrapLiteralCode(value, Texts("_test_enum.", tag->name), .type = tag->type), tag->type), + "; })"); } } code_err(ast, "The field '", f->field, "' is not a valid tag name of ", type_to_text(enum_t)); diff --git a/src/compile/whens.c b/src/compile/whens.c index 122c581c..618a667c 100644 --- a/src/compile/whens.c +++ b/src/compile/whens.c @@ -43,11 +43,7 @@ Text_t compile_when_statement(env_t *env, ast_t *ast) { DeclareMatch(enum_t, subject_t, EnumType); - Text_t code; - if (enum_has_fields(subject_t)) - code = Texts("WHEN(", compile_type(subject_t), ", ", compile(env, when->subject), ", _when_subject, {\n"); - else code = Texts("switch(", compile(env, when->subject), ") {\n"); - + Text_t code = Texts("WHEN(", compile_type(subject_t), ", ", compile(env, when->subject), ", _when_subject, {\n"); for (when_clause_t *clause = when->clauses; clause; clause = clause->next) { if (clause->pattern->tag == Var) { const char *clause_tag_name = Match(clause->pattern, Var)->name; @@ -132,7 +128,7 @@ Text_t compile_when_statement(env_t *env, ast_t *ast) { } else { code = Texts(code, "default: errx(1, \"Invalid tag!\");\n"); } - code = Texts(code, "\n}", enum_has_fields(subject_t) ? Text(")") : EMPTY_TEXT, "\n"); + code = Texts(code, "\n}", Text(")"), "\n"); return code; } diff --git a/src/typecheck.c b/src/typecheck.c index 41de1a6b..af726a14 100644 --- a/src/typecheck.c +++ b/src/typecheck.c @@ -125,11 +125,6 @@ type_t *parse_type_ast(env_t *env, type_ast_t *ast) { Table$str_set(env->types, enum_name, enum_type); - bool has_any_tags_with_fields = false; - for (tag_ast_t *tag_ast = tag_asts; tag_ast; tag_ast = tag_ast->next) { - has_any_tags_with_fields = has_any_tags_with_fields || tag_ast->fields; - } - for (tag_ast_t *tag_ast = tag_asts; tag_ast; tag_ast = tag_ast->next) { arg_t *fields = NULL; for (arg_ast_t *field_ast = tag_ast->fields; field_ast; field_ast = field_ast->next) { @@ -151,14 +146,11 @@ type_t *parse_type_ast(env_t *env, type_ast_t *ast) { set_binding(ns_env, tag_ast->name, constructor_t, tagged_name); binding_t binding = {.type = constructor_t, .code = tagged_name}; List$insert(&ns_env->namespace->constructors, &binding, I(1), sizeof(binding)); - } else if (has_any_tags_with_fields) { // Empty singleton value: + } else { // Empty singleton value: Text_t code = Texts("((", namespace_name(env, env->namespace, Texts(enum_name, "$$type")), "){", namespace_name(env, env->namespace, Texts(enum_name, "$tag$", tag_ast->name)), "})"); set_binding(ns_env, tag_ast->name, enum_type, code); - } else { - Text_t code = namespace_name(env, env->namespace, Texts(enum_name, "$tag$", tag_ast->name)); - set_binding(ns_env, tag_ast->name, enum_type, code); } Table$str_set(env->types, String(enum_name, "$", tag_ast->name), tag_type); @@ -441,9 +433,7 @@ void bind_statement(env_t *env, ast_t *statement) { ns_env->current_type = type; tag_t *tags = NULL; int64_t next_tag = 1; - bool has_any_tags_with_fields = false; for (tag_ast_t *tag_ast = def->tags; tag_ast; tag_ast = tag_ast->next) { - has_any_tags_with_fields = has_any_tags_with_fields || tag_ast->fields; arg_t *fields = NULL; for (arg_ast_t *field_ast = tag_ast->fields; field_ast; field_ast = field_ast->next) { type_t *field_t = get_arg_ast_type(env, field_ast); @@ -496,13 +486,10 @@ void bind_statement(env_t *env, ast_t *statement) { set_binding(ns_env, tag->name, constructor_t, tagged_name); binding_t binding = {.type = constructor_t, .code = tagged_name}; List$insert(&ns_env->namespace->constructors, &binding, I(1), sizeof(binding)); - } else if (has_any_tags_with_fields) { // Empty singleton value: + } else { // Empty singleton value: Text_t code = Texts("((", namespace_name(env, env->namespace, Texts(def->name, "$$type")), "){", namespace_name(env, env->namespace, Texts(def->name, "$tag$", tag->name)), "})"); set_binding(ns_env, tag->name, type, code); - } else { - Text_t code = namespace_name(env, env->namespace, Texts(def->name, "$tag$", tag->name)); - set_binding(ns_env, tag->name, type, code); } Table$str_set(env->types, String(def->name, "$", tag->name), tag->type); } diff --git a/src/types.c b/src/types.c index b34d8bf9..e3bf326c 100644 --- a/src/types.c +++ b/src/types.c @@ -701,8 +701,7 @@ type_t *get_field_type(type_t *t, const char *field_name) { DeclareMatch(e, t, EnumType); for (tag_t *tag = e->tags; tag; tag = tag->next) { if (!streq(field_name, tag->name)) continue; - if (tag->type != NULL && Match(tag->type, StructType)->fields) return Type(OptionalType, tag->type); - else return Type(OptionalType, EMPTY_TYPE); + return Type(OptionalType, tag->type); } return NULL; } @@ -835,10 +834,3 @@ type_t *_make_function_type(type_t *ret, int n, arg_t args[n]) { } return Type(FunctionType, .ret = ret, .args = &arg_pointers[0]); } - -PUREFUNC bool enum_has_fields(type_t *t) { - for (tag_t *e_tag = Match(t, EnumType)->tags; e_tag; e_tag = e_tag->next) { - if (e_tag->type != NULL && Match(e_tag->type, StructType)->fields) return true; - } - return false; -} diff --git a/src/types.h b/src/types.h index 76632c66..66e6ba12 100644 --- a/src/types.h +++ b/src/types.h @@ -166,4 +166,3 @@ PUREFUNC type_t *non_optional(type_t *t); type_t *get_field_type(type_t *t, const char *field_name); PUREFUNC type_t *get_iterated_type(type_t *t); type_t *_make_function_type(type_t *ret, int n, arg_t args[n]); -PUREFUNC bool enum_has_fields(type_t *t); diff --git a/test/enums.tm b/test/enums.tm index 80f66ed8..aab1b234 100644 --- a/test/enums.tm +++ b/test/enums.tm @@ -98,17 +98,17 @@ func main() do e := OnlyTags.A - assert e.A == EMPTY + assert e.A == OnlyTags.A.A assert e.B == none do e := Foo.Zero - assert e.Zero == EMPTY + assert e.Zero == Foo.Zero.Zero assert e.One == none assert e.Two == none ep := @Foo.Zero - assert ep.Zero == EMPTY + assert ep.Zero == Foo.Zero.Zero assert ep.One == none assert ep.Two == none -- cgit v1.2.3