diff options
| -rw-r--r-- | CHANGES.md | 3 | ||||
| -rw-r--r-- | src/ast.h | 6 | ||||
| -rw-r--r-- | src/compile/enums.c | 6 | ||||
| -rw-r--r-- | src/compile/optionals.c | 56 | ||||
| -rw-r--r-- | src/compile/statements.c | 4 | ||||
| -rw-r--r-- | src/typecheck.c | 12 | ||||
| -rw-r--r-- | src/types.c | 4 |
7 files changed, 71 insertions, 20 deletions
@@ -3,6 +3,9 @@ ## v2025-12-06 - You can now discard Empty values. +- 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. ## v2025-11-30 @@ -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)); |
