From 8b47673524cdaec0501b50e05b6c0fb797c586dd Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Fri, 25 Apr 2025 14:44:00 +0200 Subject: [PATCH] Added `Enum.lookup` and `Enum.lookup_field`. --- lib/std/core/runtime.c3 | 10 ++ releasenotes.md | 1 + src/compiler/compiler_internal.h | 2 + src/compiler/enums.h | 2 + src/compiler/sema_expr.c | 117 ++++++++++++---- src/compiler/symtab.c | 13 +- test/test_suite/enumerations/enum_lookup.c3t | 130 ++++++++++++++++++ test/test_suite/enumerations/lookup_errors.c3 | 32 +++++ 8 files changed, 274 insertions(+), 33 deletions(-) create mode 100644 test/test_suite/enumerations/enum_lookup.c3t create mode 100644 test/test_suite/enumerations/lookup_errors.c3 diff --git a/lib/std/core/runtime.c3 b/lib/std/core/runtime.c3 index 65852328a..5105961ac 100644 --- a/lib/std/core/runtime.c3 +++ b/lib/std/core/runtime.c3 @@ -22,6 +22,16 @@ struct SliceRaw usz len; } +macro @enum_lookup($Type, #value, value) +{ + var $elements = $Type.elements; + $for var $i = 0; $i < $elements; $i++: + var $val = $Type.from_ordinal($i); + if ($val.#value == value) return $val; + $endfor + return NOT_FOUND?; +} + module std::core::runtime @if(WASM_NOLIBC); diff --git a/releasenotes.md b/releasenotes.md index 480147bc5..c49d9f711 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -17,6 +17,7 @@ - Improved error messages on `Foo { 3, abc }` #2099. - `Foo[1..2] = { .baz = 123 }` inference now works. #2095 - Deprecated old inference with slice copy. Copying must now ensure a slicing operator at the end of the right hand side: `foo[1..2] = bar[..]` rather than the old `foo[1..2] = bar`. The old behaviour can be mostly retained with `--use-old-slice-copy`). +- Added `Enum.lookup` and `Enum.lookup_field`. ### Fixes - Trying to cast an enum to int and back caused the compiler to crash. diff --git a/src/compiler/compiler_internal.h b/src/compiler/compiler_internal.h index 5cc418656..37804792d 100644 --- a/src/compiler/compiler_internal.h +++ b/src/compiler/compiler_internal.h @@ -1903,6 +1903,7 @@ extern const char *builtin_defines[NUMBER_OF_BUILTIN_DEFINES]; extern const char *type_property_list[NUMBER_OF_TYPE_PROPERTIES]; extern const char *kw_std__core; extern const char *kw_std__core__types; +extern const char *kw_std__core__runtime; extern const char *kw_std__io; extern const char *kw_typekind; extern const char *kw_FILE_NOT_FOUND; @@ -1910,6 +1911,7 @@ extern const char *kw_IoError; extern const char *kw_at_deprecated; extern const char *kw_at_ensure; +extern const char *kw_at_enum_lookup; extern const char *kw_at_param; extern const char *kw_at_pure; extern const char *kw_at_require; diff --git a/src/compiler/enums.h b/src/compiler/enums.h index 87aedeb9b..0bb5f5089 100644 --- a/src/compiler/enums.h +++ b/src/compiler/enums.h @@ -1386,6 +1386,8 @@ typedef enum TYPE_PROPERTY_IS_ORDERED, TYPE_PROPERTY_IS_SUBSTRUCT, TYPE_PROPERTY_LEN, + TYPE_PROPERTY_LOOKUP, + TYPE_PROPERTY_LOOKUP_FIELD, TYPE_PROPERTY_MAX, TYPE_PROPERTY_MEMBERSOF, TYPE_PROPERTY_METHODSOF, diff --git a/src/compiler/sema_expr.c b/src/compiler/sema_expr.c index caa32e4b7..0964f8f0f 100644 --- a/src/compiler/sema_expr.c +++ b/src/compiler/sema_expr.c @@ -5,6 +5,8 @@ #include "sema_internal.h" #include +#include "parser_internal.h" + #define RETURN_SEMA_FUNC_ERROR(_decl, _node, ...) do { sema_error_at(context, (_node)->span, __VA_ARGS__); SEMA_NOTE(_decl, "The definition was here."); return false; } while (0) #define RETURN_NOTE_FUNC_DEFINITION do { SEMA_NOTE(callee->definition, "The definition was here."); return false; } while (0); #define RESOLVE(expr__, check__) \ @@ -116,8 +118,6 @@ static inline bool sema_expr_analyse_ct_nameof(SemaContext *context, Expr *expr) static inline bool sema_expr_analyse_ct_defined(SemaContext *context, Expr *expr); // -- returns -static inline void context_pop_returns(SemaContext *context, Ast **restore); -static inline Ast **context_push_returns(SemaContext *context); static inline Type *context_unify_returns(SemaContext *context); // -- addr helpers @@ -442,31 +442,6 @@ static void expr_binary_unify_failability(Expr *expr, Expr *left, Expr *right) expr->type = type_add_optional(left->type, IS_OPTIONAL(right)); } -static inline void context_pop_returns(SemaContext *context, Ast **restore) -{ - if (!context->returns_cache && context->returns) - { - context->returns_cache = context->returns; - } - context->returns = restore; -} - -static inline Ast **context_push_returns(SemaContext *context) -{ - Ast** old_returns = context->returns; - if (context->returns_cache) - { - context->returns = context->returns_cache; - context->returns_cache = NULL; - vec_resize(context->returns, 0); - } - else - { - context->returns = NULL; - } - return old_returns; -} - CondResult sema_check_comp_time_bool(SemaContext *context, Expr *expr) { CondResult result = COND_MISSING; @@ -2837,7 +2812,7 @@ INLINE bool sema_expr_analyse_from_ordinal(SemaContext *context, Expr *expr, Exp Expr **args = expr->call_expr.arguments; unsigned arg_count = vec_size(args); Decl *decl = tag->type_call_expr.type; - if (arg_count != 1) RETURN_SEMA_ERROR(expr, "Expected a single string argument to 'from_ordinal'."); + if (arg_count != 1) RETURN_SEMA_ERROR(expr, "Expected a single integer argument to 'from_ordinal'."); Expr *key = args[0]; if (!sema_analyse_expr(context, key)) return false; if (!type_is_integer(key->type)) @@ -2873,13 +2848,87 @@ INLINE bool sema_expr_analyse_from_ordinal(SemaContext *context, Expr *expr, Exp return true; } +INLINE bool sema_expr_analyse_lookup(SemaContext *context, Expr *expr, Expr *tag, bool inline_field) +{ + Expr **args = expr->call_expr.arguments; + unsigned arg_count = vec_size(args); + Decl *decl = tag->type_call_expr.type; + if (inline_field) + { + if (arg_count != 1) RETURN_SEMA_ERROR(expr, "Expected one (1) argument to 'lookup'."); + } + else + { + if (arg_count != 2) RETURN_SEMA_ERROR(expr, "'lookup_field' requires two arguments: the name of the field and the value to search for."); + } + Expr *key = inline_field ? args[0] : args[1]; + if (!sema_analyse_expr(context, key)) return false; + ArrayIndex index; + if (inline_field) + { + if (!decl->is_substruct || decl->enums.inline_value) + { + RETURN_SEMA_ERROR(expr, "'lookup' requires an inline associated value, use 'Enum.lookup_field(fieldname, value)' instead."); + } + } + else + { + Expr *ident = sema_expr_resolve_access_child(context, args[0], NULL); + if (!ident) return false; + const char *child = ident->unresolved_ident_expr.ident; + FOREACH_IDX(i, Decl *, param, decl->enums.parameters) + { + if (param->name && param->name == child) + { + index = i; + goto FOUND; + } + } + RETURN_SEMA_ERROR(args[0], "There is no associated value of %s with the name '%s'.", type_quoted_error_string(decl->type), child); + } + index = decl->enums.inline_index; +FOUND:; + Decl *match = decl->enums.parameters[index]; + if (!cast_implicit(context, key, match->type, false)) return false; + Decl *d = sema_find_symbol(context, kw_at_enum_lookup); + if (!d || d->unit->module->name->module != kw_std__core__runtime) + { + RETURN_SEMA_ERROR(expr, "Missing main enum lookup macro '%s' in '%s'.", kw_at_enum_lookup, kw_std__core__runtime); + } + Expr *type = expr_new_expr(EXPR_TYPEINFO, expr); + type->type_expr = type_info_new_base(decl->type, tag->span); + expr->expr_kind = EXPR_CALL; + while (vec_size(args) < 3) vec_add(args, NULL); + args[0] = type; + Expr *unresolved_ident = expr_new_expr(EXPR_UNRESOLVED_IDENTIFIER, expr); + unresolved_ident->unresolved_ident_expr.ident = match->name; + args[1] = unresolved_ident; + args[2] = key; + Expr *call = expr_new_expr(EXPR_UNRESOLVED_IDENTIFIER, expr); + Path *new_path = CALLOCS(Path); + new_path->module = kw_std__core__runtime; + new_path->span = expr->span; + new_path->len = strlen(kw_std__core__runtime); + call->unresolved_ident_expr = (ExprUnresolvedIdentifier) { .ident = kw_at_enum_lookup, .path = new_path }; + expr->call_expr = (ExprCall) { .arguments = args, .function = exprid(call) }; + expr->resolve_status = RESOLVE_NOT_DONE; + return sema_analyse_expr(context, expr); +} + static inline bool sema_expr_analyse_typecall(SemaContext *context, Expr *expr) { Expr *tag = exprptr(expr->call_expr.function); expr->call_expr.arguments = sema_expand_vasplat_exprs(context, expr->call_expr.arguments); - if (tag->type_call_expr.property == TYPE_PROPERTY_FROM_ORDINAL) + switch (tag->type_call_expr.property) { - return sema_expr_analyse_from_ordinal(context, expr, tag); + case TYPE_PROPERTY_FROM_ORDINAL: + return sema_expr_analyse_from_ordinal(context, expr, tag); + case TYPE_PROPERTY_LOOKUP: + return sema_expr_analyse_lookup(context, expr, tag, true); + case TYPE_PROPERTY_LOOKUP_FIELD: + return sema_expr_analyse_lookup(context, expr, tag, false); + default: + break; } Expr **args = expr->call_expr.arguments; unsigned arg_count = vec_size(args); @@ -4267,6 +4316,8 @@ static inline bool sema_expr_analyse_member_access(SemaContext *context, Expr *e case TYPE_PROPERTY_TAGOF: case TYPE_PROPERTY_HAS_TAGOF: case TYPE_PROPERTY_FROM_ORDINAL: + case TYPE_PROPERTY_LOOKUP: + case TYPE_PROPERTY_LOOKUP_FIELD: expr->expr_kind = EXPR_TYPECALL; expr->type_call_expr = (ExprTypeCall) { .type = decl, .property = type_property }; return true; @@ -4775,6 +4826,8 @@ static bool sema_expr_rewrite_to_typeid_property(SemaContext *context, Expr *exp case TYPE_PROPERTY_IS_EQ: case TYPE_PROPERTY_IS_ORDERED: case TYPE_PROPERTY_IS_SUBSTRUCT: + case TYPE_PROPERTY_LOOKUP: + case TYPE_PROPERTY_LOOKUP_FIELD: case TYPE_PROPERTY_MAX: case TYPE_PROPERTY_MEMBERSOF: case TYPE_PROPERTY_METHODSOF: @@ -4962,6 +5015,8 @@ static bool sema_type_property_is_valid_for_type(Type *original_type, TypeProper return false; } case TYPE_PROPERTY_FROM_ORDINAL: + case TYPE_PROPERTY_LOOKUP: + case TYPE_PROPERTY_LOOKUP_FIELD: return type->canonical->type_kind == TYPE_ENUM; case TYPE_PROPERTY_MIN: case TYPE_PROPERTY_MAX: @@ -5114,6 +5169,8 @@ static bool sema_expr_rewrite_to_type_property(SemaContext *context, Expr *expr, case TYPE_PROPERTY_TAGOF: case TYPE_PROPERTY_HAS_TAGOF: case TYPE_PROPERTY_FROM_ORDINAL: + case TYPE_PROPERTY_LOOKUP: + case TYPE_PROPERTY_LOOKUP_FIELD: expr->expr_kind = EXPR_TYPECALL; expr->type_call_expr = (ExprTypeCall) { .type = type->type_kind == TYPE_FUNC_PTR diff --git a/src/compiler/symtab.c b/src/compiler/symtab.c index b912b96d7..ee6d5b6e6 100644 --- a/src/compiler/symtab.c +++ b/src/compiler/symtab.c @@ -40,11 +40,12 @@ const char *builtin_defines[NUMBER_OF_BUILTIN_DEFINES]; const char *type_property_list[NUMBER_OF_TYPE_PROPERTIES]; const char *kw_at_deprecated; const char *kw_at_ensure; +const char *kw_at_enum_lookup; +const char *kw_at_jump; const char *kw_at_param; const char *kw_at_pure; const char *kw_at_require; const char *kw_at_return; -const char *kw_at_jump; const char *kw_in; const char *kw_inout; const char *kw_len; @@ -62,6 +63,7 @@ const char *kw_self; const char *kw_std; const char *kw_std__core; const char *kw_std__core__types; +const char *kw_std__core__runtime; const char *kw_std__io; const char *kw_type; const char *kw_typekind; @@ -146,6 +148,7 @@ void symtab_init(uint32_t capacity) kw_std = KW_DEF("std"); kw_std__core = KW_DEF("std::core"); kw_std__core__types = KW_DEF("std::core::types"); + kw_std__core__runtime = KW_DEF("std::core::runtime"); kw_std__io = KW_DEF("std::io"); kw_type = KW_DEF("type"); kw_winmain = KW_DEF("wWinMain"); @@ -161,12 +164,15 @@ void symtab_init(uint32_t capacity) type_property_list[TYPE_PROPERTY_ELEMENTS] = KW_DEF("elements"); type_property_list[TYPE_PROPERTY_EXTNAMEOF] = KW_DEF("extnameof"); type_property_list[TYPE_PROPERTY_FROM_ORDINAL] = KW_DEF("from_ordinal"); + type_property_list[TYPE_PROPERTY_GET] = KW_DEF("get"); type_property_list[TYPE_PROPERTY_INF] = KW_DEF("inf"); type_property_list[TYPE_PROPERTY_INNER] = KW_DEF("inner"); type_property_list[TYPE_PROPERTY_IS_EQ] = KW_DEF("is_eq"); type_property_list[TYPE_PROPERTY_IS_ORDERED] = KW_DEF("is_ordered"); type_property_list[TYPE_PROPERTY_IS_SUBSTRUCT] = KW_DEF("is_substruct"); + type_property_list[TYPE_PROPERTY_LOOKUP] = KW_DEF("lookup"); + type_property_list[TYPE_PROPERTY_LOOKUP_FIELD] = KW_DEF("lookup_field"); type_property_list[TYPE_PROPERTY_KINDOF] = KW_DEF("kindof"); type_property_list[TYPE_PROPERTY_MEMBERSOF] = KW_DEF("membersof"); type_property_list[TYPE_PROPERTY_METHODSOF] = KW_DEF("methodsof"); @@ -308,13 +314,14 @@ void symtab_init(uint32_t capacity) type = TOKEN_AT_IDENT; - kw_at_ensure = KW_DEF("@ensure"); kw_at_deprecated = KW_DEF("@deprecated"); + kw_at_ensure = KW_DEF("@ensure"); + kw_at_enum_lookup = KW_DEF("@enum_lookup"); + kw_at_jump = KW_DEF("@jump"); kw_at_param = KW_DEF("@param"); kw_at_pure = KW_DEF("@pure"); kw_at_require = KW_DEF("@require"); kw_at_return = KW_DEF("@return"); - kw_at_jump = KW_DEF("@jump"); attribute_list[ATTRIBUTE_ALIGN] = KW_DEF("@align"); attribute_list[ATTRIBUTE_BENCHMARK] = KW_DEF("@benchmark"); attribute_list[ATTRIBUTE_BIGENDIAN] = KW_DEF("@bigendian"); diff --git a/test/test_suite/enumerations/enum_lookup.c3t b/test/test_suite/enumerations/enum_lookup.c3t new file mode 100644 index 000000000..0de7dd618 --- /dev/null +++ b/test/test_suite/enumerations/enum_lookup.c3t @@ -0,0 +1,130 @@ +// #target: macos-x64 +module test; +import std; + +typedef Baz = int; +enum Foo : char (inline Baz x, String hello) +{ + ABC = { 123, "ugh" }, + DEF = { 444, "hello" } +} +fn int main(String[] a) +{ + Foo? x = Foo.lookup_field(hello, "hello"); + Foo? y = Foo.lookup(3); + return x.ordinal ?? 222; +} + +/* #expect: test.ll + +define i32 @test.main(ptr %0, i64 %1) #0 { +entry: + %a = alloca %"char[][]", align 8 + %x = alloca i8, align 1 + %x.f = alloca i64, align 8 + %blockret = alloca i8, align 1 + %cmp.idx = alloca i64, align 8 + %cmp.idx4 = alloca i64, align 8 + %y = alloca i8, align 1 + %y.f = alloca i64, align 8 + store ptr %0, ptr %a, align 8 + %ptradd = getelementptr inbounds i8, ptr %a, i64 8 + store i64 %1, ptr %ptradd, align 8 + br i1 false, label %slice_cmp_values, label %slice_cmp_exit + +slice_cmp_values: ; preds = %entry + store i64 0, ptr %cmp.idx, align 8 + br label %slice_loop_start + +slice_loop_start: ; preds = %slice_loop_comparison, %slice_cmp_values + %2 = load i64, ptr %cmp.idx, align 8 + %lt = icmp slt i64 %2, 3 + br i1 %lt, label %slice_loop_comparison, label %slice_cmp_exit + +slice_loop_comparison: ; preds = %slice_loop_start + %ptradd1 = getelementptr inbounds i8, ptr @.str.3, i64 %2 + %ptradd2 = getelementptr inbounds i8, ptr @.str.2, i64 %2 + %3 = load i8, ptr %ptradd1, align 1 + %4 = load i8, ptr %ptradd2, align 1 + %eq = icmp eq i8 %3, %4 + %5 = add i64 %2, 1 + store i64 %5, ptr %cmp.idx, align 8 + br i1 %eq, label %slice_loop_start, label %slice_cmp_exit + +slice_cmp_exit: ; preds = %slice_loop_comparison, %slice_loop_start, %entry + %slice_cmp_phi = phi i1 [ true, %slice_loop_start ], [ false, %entry ], [ false, %slice_loop_comparison ] + br i1 %slice_cmp_phi, label %if.then, label %if.exit + +if.then: ; preds = %slice_cmp_exit + store i8 0, ptr %blockret, align 1 + br label %expr_block.exit + +if.exit: ; preds = %slice_cmp_exit + br i1 true, label %slice_cmp_values3, label %slice_cmp_exit11 + +slice_cmp_values3: ; preds = %if.exit + store i64 0, ptr %cmp.idx4, align 8 + br label %slice_loop_start5 + +slice_loop_start5: ; preds = %slice_loop_comparison7, %slice_cmp_values3 + %6 = load i64, ptr %cmp.idx4, align 8 + %lt6 = icmp slt i64 %6, 5 + br i1 %lt6, label %slice_loop_comparison7, label %slice_cmp_exit11 + +slice_loop_comparison7: ; preds = %slice_loop_start5 + %ptradd8 = getelementptr inbounds i8, ptr @.str.4, i64 %6 + %ptradd9 = getelementptr inbounds i8, ptr @.str.2, i64 %6 + %7 = load i8, ptr %ptradd8, align 1 + %8 = load i8, ptr %ptradd9, align 1 + %eq10 = icmp eq i8 %7, %8 + %9 = add i64 %6, 1 + store i64 %9, ptr %cmp.idx4, align 8 + br i1 %eq10, label %slice_loop_start5, label %slice_cmp_exit11 + +slice_cmp_exit11: ; preds = %slice_loop_comparison7, %slice_loop_start5, %if.exit + %slice_cmp_phi12 = phi i1 [ true, %slice_loop_start5 ], [ false, %if.exit ], [ false, %slice_loop_comparison7 ] + br i1 %slice_cmp_phi12, label %if.then13, label %if.exit14 + +if.then13: ; preds = %slice_cmp_exit11 + store i8 1, ptr %blockret, align 1 + br label %expr_block.exit + +if.exit14: ; preds = %slice_cmp_exit11 + store i64 ptrtoint (ptr @std.core.builtin.NOT_FOUND to i64), ptr %x.f, align 8 + br label %after_assign + +expr_block.exit: ; preds = %if.then13, %if.then + %10 = load i8, ptr %blockret, align 1 + store i8 %10, ptr %x, align 1 + store i64 0, ptr %x.f, align 8 + br label %after_assign + +after_assign: ; preds = %expr_block.exit, %if.exit14 + br label %if.exit16 + +if.exit16: ; preds = %after_assign + br label %if.exit17 + +if.exit17: ; preds = %if.exit16 + store i64 ptrtoint (ptr @std.core.builtin.NOT_FOUND to i64), ptr %y.f, align 8 + br label %after_assign18 + +after_assign18: ; preds = %if.exit17 + %optval = load i64, ptr %x.f, align 8 + %not_err = icmp eq i64 %optval, 0 + %11 = call i1 @llvm.expect.i1(i1 %not_err, i1 true) + br i1 %11, label %after_check, label %else_block + +after_check: ; preds = %after_assign18 + %12 = load i8, ptr %x, align 1 + %zext = zext i8 %12 to i32 + br label %phi_block + +else_block: ; preds = %after_assign18 + br label %phi_block + +phi_block: ; preds = %else_block, %after_check + %val = phi i32 [ %zext, %after_check ], [ 222, %else_block ] + ret i32 %val +} + diff --git a/test/test_suite/enumerations/lookup_errors.c3 b/test/test_suite/enumerations/lookup_errors.c3 new file mode 100644 index 000000000..6d12f04ba --- /dev/null +++ b/test/test_suite/enumerations/lookup_errors.c3 @@ -0,0 +1,32 @@ + +typedef Baz = int; +enum Foo : char (inline Baz x, String hello) +{ + ABC = { 123, "ugh" }, + DEF = { 444, "hello" } +} +enum Bar : char (Baz x) +{ + HELLO = 123, +} + +fn void test1() +{ + (void)Bar.lookup(123); // #error: 'lookup' requires an inline associated value +} + +fn void test2() +{ + Foo.lookup(123, 322); // #error: Expected one (1) + Foo.lookup(); // #error: Expected one (1) + Foo.lookup_field(0); // #error: requires two arguments + Foo.lookup_field(33, 22, 44); // #error: requires two arguments +} + +fn void test3() +{ + Foo.lookup("hello"); // #error: possible to cast 'String' + Foo.lookup_field("hello", "hello"); // #error: identifier + Foo.lookup_field(hello, 2); // #error: possible to cast 'int' + Foo.lookup_field(err, 4); // #error: no associated value of +}