aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBruce Hill <bruce@bruce-hill.com>2025-12-07 18:44:58 -0500
committerBruce Hill <bruce@bruce-hill.com>2025-12-07 18:44:58 -0500
commit85d1507d8b7e0139135e040b7b4b23c02097a155 (patch)
tree8eb7268aa29dd37846f16edbeb32c093efe1d63e
parent43b0a91664fc0b9a5222805c68c3505fd9634689 (diff)
Consolidate logic for enums with and without tags with fields.
-rw-r--r--CHANGES.md2
-rw-r--r--src/compile/enums.c48
-rw-r--r--src/compile/headers.c61
-rw-r--r--src/compile/optionals.c30
-rw-r--r--src/compile/whens.c8
-rw-r--r--src/typecheck.c17
-rw-r--r--src/types.c10
-rw-r--r--src/types.h1
-rw-r--r--test/enums.tm6
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