aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBruce Hill <bruce@bruce-hill.com>2025-12-06 14:55:00 -0500
committerBruce Hill <bruce@bruce-hill.com>2025-12-06 14:55:00 -0500
commit48491f94c96615e8055bcf72ed9009b1d921467f (patch)
treeb30b934d71ca14a7f9602282c35d2f2c137d902a /src
parenta13b39f1e1ea220a868d99508796d06492a40611 (diff)
Use `foo!` as sugar for `foo.FirstTag!` for enum values. Also, give
better error messages for this kind of `!` assertion.
Diffstat (limited to 'src')
-rw-r--r--src/ast.h6
-rw-r--r--src/compile/enums.c6
-rw-r--r--src/compile/optionals.c56
-rw-r--r--src/compile/statements.c4
-rw-r--r--src/typecheck.c12
-rw-r--r--src/types.c4
6 files changed, 68 insertions, 20 deletions
diff --git a/src/ast.h b/src/ast.h
index 5307fc2c..b852da2a 100644
--- a/src/ast.h
+++ b/src/ast.h
@@ -23,9 +23,9 @@
#define LiteralCode(code, ...) \
new (ast_t, .tag = InlineCCode, \
.__data.InlineCCode = {.chunks = new (ast_list_t, .ast = FakeAST(TextLiteral, code)), __VA_ARGS__})
-#define WrapLiteralCode(ast, code, ...) \
- new (ast_t, .tag = InlineCCode, .file = (ast)->file, .start = (ast)->start, .end = (ast)->end, \
- .__data.InlineCCode = {.chunks = new (ast_list_t, .ast = WrapAST(ast, TextLiteral, code)), __VA_ARGS__})
+#define WrapLiteralCode(_ast, code, ...) \
+ new (ast_t, .tag = InlineCCode, .file = (_ast)->file, .start = (_ast)->start, .end = (_ast)->end, \
+ .__data.InlineCCode = {.chunks = new (ast_list_t, .ast = WrapAST(_ast, TextLiteral, code)), __VA_ARGS__})
#define Match(x, _tag) \
((x)->tag == _tag ? &(x)->__data._tag \
: (errx(1, __FILE__ ":%d This was supposed to be a " #_tag "\n", __LINE__), &(x)->__data._tag))
diff --git a/src/compile/enums.c b/src/compile/enums.c
index 56d6432a..853625ca 100644
--- a/src/compile/enums.c
+++ b/src/compile/enums.c
@@ -158,11 +158,7 @@ Text_t compile_enum_field_access(env_t *env, ast_t *ast) {
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,
- WrapAST(ast, InlineCCode,
- .chunks = new (ast_list_t, WrapAST(ast, TextLiteral, Texts("_e.", tag->name))),
- .type = tag->type),
- tag->type);
+ 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), "; })");
diff --git a/src/compile/optionals.c b/src/compile/optionals.c
index ffe16248..5ad67602 100644
--- a/src/compile/optionals.c
+++ b/src/compile/optionals.c
@@ -115,12 +115,54 @@ public
Text_t compile_non_optional(env_t *env, ast_t *ast) {
ast_t *value = Match(ast, NonOptional)->value;
if (value->tag == Index && Match(value, Index)->index != NULL) return compile_indexing(env, value, true);
- type_t *t = get_type(env, value);
- Text_t value_code = compile(env, value);
+ type_t *value_t = get_type(env, value);
+ if (value_t->tag == PointerType) {
+ // Dereference pointers automatically
+ return compile_non_optional(env, WrapAST(ast, NonOptional, WrapAST(ast, Index, .indexed = value)));
+ }
int64_t line = get_line_number(ast->file, ast->start);
- return Texts(
- "({ ", compile_declaration(t, Text("opt")), " = ", value_code, "; ", "if unlikely (",
- check_none(t, Text("opt")), ")\n", "#line ", line, "\n", "fail_source(", quoted_str(ast->file->filename), ", ",
- (int64_t)(value->start - value->file->text), ", ", (int64_t)(value->end - value->file->text), ", ",
- "\"This was expected to be a value, but it's `none`\\n\");\n", optional_into_nonnone(t, Text("opt")), "; })");
+ if (value_t->tag == EnumType) {
+ // For this case:
+ // enum Foo(FirstField, SecondField(msg:Text))
+ // e := ...
+ // e!
+ // We desugar into `e.FirstField!` using the first enum field
+ tag_t *first_tag = Match(value_t, EnumType)->tags;
+ if (!first_tag) code_err(ast, "'!' cannot be used on an empty enum");
+ return compile_non_optional(
+ env, WrapAST(ast, NonOptional, WrapAST(value, FieldAccess, .fielded = value, .field = first_tag->name)));
+ } else if (value->tag == FieldAccess
+ && value_type(get_type(env, Match(value, FieldAccess)->fielded))->tag == EnumType) {
+ type_t *enum_t = value_type(get_type(env, Match(value, FieldAccess)->fielded));
+ DeclareMatch(e, enum_t, EnumType);
+ DeclareMatch(f, value, FieldAccess);
+ 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"),
+ "; })");
+ }
+ }
+ code_err(ast, "The field '", f->field, "' is not a valid tag name of ", type_to_text(enum_t));
+ } else {
+ Text_t value_code = compile(env, value);
+ return Texts("({ ", compile_declaration(value_t, Text("opt")), " = ", value_code, "; ", "if unlikely (",
+ check_none(value_t, Text("opt")), ")\n", "#line ", line, "\n", "fail_source(",
+ quoted_str(value->file->filename), ", ", (int64_t)(value->start - value->file->text), ", ",
+ (int64_t)(value->end - value->file->text), ", ",
+ "\"This was expected to be a value, but it's `none`\\n\");\n",
+ optional_into_nonnone(value_t, Text("opt")), "; })");
+ }
}
diff --git a/src/compile/statements.c b/src/compile/statements.c
index 4bb8b432..01fb1a0b 100644
--- a/src/compile/statements.c
+++ b/src/compile/statements.c
@@ -219,7 +219,9 @@ static Text_t _compile_statement(env_t *env, ast_t *ast) {
case Metadata: return EMPTY_TEXT;
default:
if (!is_discardable(env, ast))
- code_err(ast, "The ", type_to_text(get_type(env, ast)), " result of this statement cannot be discarded");
+ code_err(
+ ast, "The ", type_to_text(get_type(env, ast)),
+ " value of this statement is implicitly ignored. \n Use `_ := ` if you want to explicitly discard it.");
return Texts("(void)", compile(env, ast), ";");
}
}
diff --git a/src/typecheck.c b/src/typecheck.c
index 6f8fa0ba..41de1a6b 100644
--- a/src/typecheck.c
+++ b/src/typecheck.c
@@ -755,7 +755,12 @@ type_t *get_type(env_t *env, ast_t *ast) {
}
case NonOptional: {
ast_t *value = Match(ast, NonOptional)->value;
- type_t *t = get_type(env, value);
+ type_t *t = value_type(get_type(env, value));
+ if (t->tag == EnumType) {
+ tag_t *first_tag = Match(t, EnumType)->tags;
+ if (!first_tag) code_err(ast, "'!' cannot be used on an empty enum");
+ return first_tag->type;
+ }
if (t->tag != OptionalType)
code_err(value, "This value is not optional. Only optional values can use the '!' operator.");
return Match(t, OptionalType)->type;
@@ -1539,7 +1544,10 @@ PUREFUNC bool is_discardable(env_t *env, ast_t *ast) {
default: break;
}
type_t *t = get_type(env, ast);
- return (t->tag == VoidType || t->tag == AbortType || t->tag == ReturnType || t == EMPTY_TYPE);
+ if (t->tag == StructType) {
+ return (Match(t, StructType)->fields == NULL);
+ }
+ return (t->tag == VoidType || t->tag == AbortType || t->tag == ReturnType);
}
type_t *get_arg_ast_type(env_t *env, arg_ast_t *arg) {
diff --git a/src/types.c b/src/types.c
index 6ed24f6c..b34d8bf9 100644
--- a/src/types.c
+++ b/src/types.c
@@ -63,7 +63,7 @@ Text_t type_to_text(type_t *t) {
}
case StructType: {
DeclareMatch(struct_, t, StructType);
- return Text$from_str(struct_->name);
+ return Text$replace(Text$from_str(struct_->name), Text("$"), Text("."));
}
case PointerType: {
DeclareMatch(ptr, t, PointerType);
@@ -73,7 +73,7 @@ Text_t type_to_text(type_t *t) {
case EnumType: {
DeclareMatch(enum_, t, EnumType);
if (enum_->name != NULL && strncmp(enum_->name, "enum$", strlen("enum$")) != 0)
- return Text$from_str(enum_->name);
+ return Text$replace(Text$from_str(enum_->name), Text("$"), Text("."));
Text_t text = Text("enum(");
for (tag_t *tag = enum_->tags; tag; tag = tag->next) {
text = Texts(text, Text$from_str(tag->name));