Added Enum.lookup and Enum.lookup_field.

This commit is contained in:
Christoffer Lerno
2025-04-25 14:44:00 +02:00
parent 8b29e4780d
commit 8b47673524
8 changed files with 274 additions and 33 deletions

View File

@@ -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);

View File

@@ -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.

View File

@@ -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;

View File

@@ -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,

View File

@@ -5,6 +5,8 @@
#include "sema_internal.h"
#include <math.h>
#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

View File

@@ -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");

View File

@@ -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
}

View File

@@ -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
}