From 24041ed80d424b101ec91a7089bb2fee40753278 Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Mon, 15 Jul 2024 02:01:26 +0200 Subject: [PATCH] Macro `$case` statements now pick the first match and does not evaluate the rest. Added countingsort tests #1234. --- lib/std/sort/countingsort.c3 | 23 ++--- releasenotes.md | 1 + src/compiler/compiler_internal.h | 1 + src/compiler/expr.c | 1 + src/compiler/parse_expr.c | 8 ++ src/compiler/parse_stmt.c | 14 ++- src/compiler/sema_expr.c | 16 +++ src/compiler/sema_initializers.c | 2 + src/compiler/sema_stmts.c | 15 ++- src/compiler/sema_types.c | 2 +- .../compile_time/ct_switch_errors.c3 | 4 +- .../compile_time/ct_switch_type_errors.c3 | 6 +- test/unit/stdlib/sort/countingsort.c3 | 98 +++++++++++++++++++ test/unit/stdlib/sort/sort.c3 | 21 +++- 14 files changed, 184 insertions(+), 28 deletions(-) create mode 100644 test/unit/stdlib/sort/countingsort.c3 diff --git a/lib/std/sort/countingsort.c3 b/lib/std/sort/countingsort.c3 index 53f350914..15507310d 100644 --- a/lib/std/sort/countingsort.c3 +++ b/lib/std/sort/countingsort.c3 @@ -31,28 +31,25 @@ def Ranges = ulong[257] @private; def Indexs = char[256] @private; def ElementType = $typeof(Type{}[0]); -const bool NO_KEY_FN @private = KeyFn.typeid == void*.typeid; +const bool NO_KEY_FN @private = types::is_same(KeyFn, EmptySlot); const bool KEY_BY_VALUE @private = $or(NO_KEY_FN, $assignable(Type{}[0], $typefrom(KeyFn.params[0]))); const bool LIST_HAS_REF @private = $defined(&Type{}[0]); def KeyFnReturnType = $typefrom(KeyFn.returns) @if(!NO_KEY_FN); def KeyFnReturnType = ElementType @if(NO_KEY_FN); -def CmpCallback = fn int(ElementType, ElementType, KeyFn) @if(KEY_BY_VALUE); -def CmpCallback = fn int(ElementType*, ElementType*, KeyFn) @if(!KEY_BY_VALUE); +def CmpCallback = fn int(ElementType, ElementType) @if(KEY_BY_VALUE && NO_KEY_FN); +def CmpCallback = fn int(ElementType*, ElementType*) @if(!KEY_BY_VALUE && NO_KEY_FN); +def CmpCallback = fn int(ElementType, ElementType, KeyFn) @if(KEY_BY_VALUE && !NO_KEY_FN); +def CmpCallback = fn int(ElementType*, ElementType*, KeyFn) @if(!KEY_BY_VALUE && !NO_KEY_FN); fn void csort(Type list, usz low, usz high, KeyFn key_fn, uint byte_idx) { if (high <= low) return; - CmpCallback compare_fn = fn (lhs, rhs, key_fn) { - $switch - $case NO_KEY_FN: - return compare_to(lhs, rhs); - $case KEY_BY_VALUE: - return compare_to(key_fn(lhs), key_fn(rhs)); - $default: - return compare_to(key_fn(lhs), key_fn(rhs)); - $endswitch; - }; + $if NO_KEY_FN: + CmpCallback compare_fn = fn (lhs, rhs) => compare_to(lhs, rhs); + $else + CmpCallback compare_fn = fn (lhs, rhs, key_fn) => compare_to(key_fn(lhs), key_fn(rhs)); + $endif; byte_idx = byte_idx >= KeyFnReturnType.sizeof ? KeyFnReturnType.sizeof - 1 : byte_idx; diff --git a/releasenotes.md b/releasenotes.md index 56a679f01..210f4760a 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -19,6 +19,7 @@ - The maximum number of parameters in a call is now 255, up from 127. - Array comparison now uses built-in memcmp on LLVM to enable optimizations. - Prevent implicit array casts to pointers with higher alignment #1237. +- Macro `$case` statements now pick the first match and does not evaluate the rest. ### Fixes - Error with unsigned compare in `@ensure` when early returning 0 #1207. diff --git a/src/compiler/compiler_internal.h b/src/compiler/compiler_internal.h index e7768bf20..b508f1dd0 100644 --- a/src/compiler/compiler_internal.h +++ b/src/compiler/compiler_internal.h @@ -3620,6 +3620,7 @@ INLINE void expr_rewrite_const_int(Expr *expr, Type *type, uint64_t v) { expr->expr_kind = EXPR_CONST; expr->type = type; + expr->resolve_status = RESOLVE_DONE; TypeKind kind = type_flatten(type)->type_kind; (&expr->const_expr)->ixx.i.high = 0; if (type_kind_is_signed(kind)) diff --git a/src/compiler/expr.c b/src/compiler/expr.c index 946791428..65df9bb72 100644 --- a/src/compiler/expr.c +++ b/src/compiler/expr.c @@ -604,6 +604,7 @@ void expr_rewrite_to_const_zero(Expr *expr, Type *type) expr->const_expr.const_kind = CONST_ENUM; assert(type->decl->resolve_status == RESOLVE_DONE); expr->const_expr.enum_err_val = type->decl->enums.values[0]; + expr->resolve_status = RESOLVE_DONE; break; case TYPE_FUNC_RAW: case TYPE_TYPEDEF: diff --git a/src/compiler/parse_expr.c b/src/compiler/parse_expr.c index 22a0c54cf..798f40f9b 100644 --- a/src/compiler/parse_expr.c +++ b/src/compiler/parse_expr.c @@ -593,6 +593,8 @@ static Expr *parse_type_expr(ParseContext *c, Expr *left) } expr->span = type->span; expr->type_expr = type; + expr->type = type_typeinfo; + if (type->resolve_status == RESOLVE_DONE) expr->resolve_status = RESOLVE_DONE; if (tok_is(c, TOKEN_SCOPE)) { PRINT_ERROR_HERE("A type is never followed by '::', did you mean '.'?"); @@ -625,6 +627,7 @@ static Expr *parse_ct_stringify(ParseContext *c, Expr *left) expr->const_expr.bytes.ptr = content; expr->const_expr.bytes.len = len; expr->type = type_string; + expr->resolve_status = RESOLVE_DONE; return expr; } @@ -1313,6 +1316,7 @@ Expr *parse_integer(ParseContext *c, Expr *left) { assert(!left && "Had left hand side"); Expr *expr_int = EXPR_NEW_TOKEN(EXPR_CONST); + expr_int->resolve_status = RESOLVE_DONE; size_t len = c->data.lex_len; const char *string = symstr(c); Int128 i = { 0, 0 }; @@ -1632,6 +1636,7 @@ static Expr *parse_bytes_expr(ParseContext *c, Expr *left) expr_bytes->const_expr.const_kind = CONST_BYTES; Type *type = type_get_array(type_char, len); expr_bytes->type = type; + expr_bytes->resolve_status = RESOLVE_DONE; return expr_bytes; } @@ -1643,6 +1648,7 @@ static Expr *parse_char_lit(ParseContext *c, Expr *left) assert(!left && "Had left hand side"); Expr *expr_int = EXPR_NEW_TOKEN(EXPR_CONST); expr_int->const_expr.is_character = true; + expr_int->resolve_status = RESOLVE_DONE; expr_int->const_expr.ixx.i = c->data.char_value; expr_int->const_expr.const_kind = CONST_INTEGER; switch (c->data.width) @@ -1836,6 +1842,8 @@ Expr *parse_type_expression_with_path(ParseContext *c, Path *path) } Expr *expr = expr_new(EXPR_TYPEINFO, type->span); expr->type_expr = type; + expr->type = type_typeinfo; + if (type->resolve_status == RESOLVE_DONE) expr->resolve_status = RESOLVE_DONE; if (tok_is(c, TOKEN_SCOPE)) { PRINT_ERROR_HERE("A type is never followed by '::', did you mean '.'?"); diff --git a/src/compiler/parse_stmt.c b/src/compiler/parse_stmt.c index 9d12c73e6..d1a521d17 100644 --- a/src/compiler/parse_stmt.c +++ b/src/compiler/parse_stmt.c @@ -624,7 +624,19 @@ static inline Ast *parse_case_stmt(ParseContext *c, TokenType case_type, TokenTy // Change type -> type.typeid if (expr->expr_kind == EXPR_TYPEINFO) { - expr->expr_kind = EXPR_TYPEID; + // Fold constant immediately + if (expr->type_expr->resolve_status == RESOLVE_DONE) + { + Type *cond_val = expr->type_expr->type; + expr->expr_kind = EXPR_CONST; + expr->const_expr.const_kind = CONST_TYPEID; + expr->const_expr.typeid = cond_val->canonical; + expr->type = type_typeid; + } + else + { + expr->expr_kind = EXPR_TYPEID; + } } if (try_consume(c, TOKEN_DOTDOT)) { diff --git a/src/compiler/sema_expr.c b/src/compiler/sema_expr.c index 77e488c12..be351cdf0 100644 --- a/src/compiler/sema_expr.c +++ b/src/compiler/sema_expr.c @@ -938,6 +938,7 @@ static inline bool sema_expr_analyse_enum_constant(SemaContext *context, Expr *e expr->expr_kind = EXPR_CONST; expr->const_expr.const_kind = enum_constant->decl_kind == DECL_ENUM_CONSTANT ? CONST_ENUM : CONST_ERR; expr->const_expr.enum_err_val = enum_constant; + expr->resolve_status = RESOLVE_DONE; return true; } @@ -3341,6 +3342,7 @@ static inline bool sema_expr_analyse_type_access(SemaContext *context, Expr *exp if (member->decl_kind == DECL_VAR || member->decl_kind == DECL_UNION || member->decl_kind == DECL_STRUCT || member->decl_kind == DECL_BITSTRUCT) { expr->expr_kind = EXPR_CONST; + expr->resolve_status = RESOLVE_DONE; AlignSize align; if (!sema_set_abi_alignment(context, decl->type, &align)) return false; expr->const_expr = (ExprConst) { @@ -3441,6 +3443,7 @@ static inline bool sema_expr_analyse_member_access(SemaContext *context, Expr *e } expr->expr_kind = EXPR_CONST; + expr->resolve_status = RESOLVE_DONE; expr->const_expr = (ExprConst) { .member.decl = member, .member.align = parent->const_expr.member.align, @@ -3482,6 +3485,7 @@ static inline bool sema_create_const_kind(SemaContext *context, Expr *expr, Type assert(vec_size(values) > val); expr->type = type_kind->type; expr->expr_kind = EXPR_CONST; + expr->resolve_status = RESOLVE_DONE; assert(type_kind->resolve_status == RESOLVE_DONE); expr->const_expr = (ExprConst) { .const_kind = CONST_ENUM, @@ -3683,6 +3687,7 @@ static inline void sema_create_const_membersof(SemaContext *context, Expr *expr, { Decl *decl = members[i]; Expr *expr_element = expr_new(EXPR_CONST, expr->span); + expr_element->resolve_status = RESOLVE_DONE; expr_element->type = type_member; expr_element->const_expr = (ExprConst) { .const_kind = CONST_MEMBER, @@ -5287,6 +5292,7 @@ static bool sema_expr_analyse_enum_add_sub(SemaContext *context, Expr *expr, Exp expr->const_expr.const_kind = CONST_INTEGER; expr->const_expr.is_character = false; expr->expr_kind = EXPR_CONST; + expr->resolve_status = RESOLVE_DONE; } return true; } @@ -5316,6 +5322,7 @@ static bool sema_expr_analyse_enum_add_sub(SemaContext *context, Expr *expr, Exp assert(left_type->decl->resolve_status == RESOLVE_DONE); expr->const_expr = (ExprConst) { .const_kind = CONST_ENUM, .enum_err_val = enums[int_to_i64(i)] }; expr->expr_kind = EXPR_CONST; + expr->resolve_status = RESOLVE_DONE; } return true; @@ -6116,6 +6123,7 @@ DONE: expr->const_expr.b = expr_const_compare(&left->const_expr, &right->const_expr, expr->binary_expr.operator); expr->const_expr.const_kind = CONST_BOOL; expr->expr_kind = EXPR_CONST; + expr->resolve_status = RESOLVE_DONE; } else { @@ -6508,6 +6516,7 @@ static inline bool sema_expr_analyse_not(SemaContext *context, Expr *expr) assert(inner->const_expr.const_kind == CONST_BOOL); expr->const_expr.const_kind = CONST_BOOL; expr->expr_kind = EXPR_CONST; + expr->resolve_status = RESOLVE_DONE; expr->const_expr.b = !inner->const_expr.b; } return true; @@ -6893,6 +6902,7 @@ static inline bool sema_expr_analyse_typeid(SemaContext *context, Expr *expr) if (!sema_resolve_type_info(context, expr->typeid_expr, RESOLVE_TYPE_DEFAULT)) return expr_poison(expr); Type *type = expr->type_expr->type; expr->expr_kind = EXPR_CONST; + expr->resolve_status = RESOLVE_DONE; expr->const_expr.const_kind = CONST_TYPEID; expr->const_expr.typeid = type->canonical; expr->type = type_typeid; @@ -7124,6 +7134,7 @@ static inline bool sema_expr_analyse_compiler_const(SemaContext *context, Expr * { expr->const_expr.const_kind = CONST_INITIALIZER; expr->expr_kind = EXPR_CONST; + expr->resolve_status = RESOLVE_DONE; ConstInitializer *init = expr->const_expr.initializer = CALLOCS(ConstInitializer); init->kind = CONST_INIT_ZERO; init->type = expr->type = type_get_slice(type_string); @@ -7138,6 +7149,7 @@ static inline bool sema_expr_analyse_compiler_const(SemaContext *context, Expr * { expr->const_expr.const_kind = CONST_INITIALIZER; expr->expr_kind = EXPR_CONST; + expr->resolve_status = RESOLVE_DONE; ConstInitializer *init = expr->const_expr.initializer = CALLOCS(ConstInitializer); init->kind = CONST_INIT_ZERO; init->type = expr->type = type_get_slice(type_voidptr); @@ -7152,6 +7164,7 @@ static inline bool sema_expr_analyse_compiler_const(SemaContext *context, Expr * { expr->const_expr.const_kind = CONST_INITIALIZER; expr->expr_kind = EXPR_CONST; + expr->resolve_status = RESOLVE_DONE; ConstInitializer *init = expr->const_expr.initializer = CALLOCS(ConstInitializer); init->kind = CONST_INIT_ZERO; init->type = expr->type = type_get_slice(type_string); @@ -7166,6 +7179,7 @@ static inline bool sema_expr_analyse_compiler_const(SemaContext *context, Expr * { expr->const_expr.const_kind = CONST_INITIALIZER; expr->expr_kind = EXPR_CONST; + expr->resolve_status = RESOLVE_DONE; ConstInitializer *init = expr->const_expr.initializer = CALLOCS(ConstInitializer); init->kind = CONST_INIT_ZERO; init->type = expr->type = type_get_slice(type_voidptr); @@ -7791,6 +7805,7 @@ static inline bool sema_expr_analyse_embed(SemaContext *context, Expr *expr, boo .bytes.len = len, }; expr->expr_kind = EXPR_CONST; + expr->resolve_status = RESOLVE_DONE; expr->type = type_get_slice(type_char); return true; } @@ -8558,6 +8573,7 @@ static inline bool sema_expr_analyse_ct_concat(SemaContext *context, Expr *conca } concat_expr->expr_kind = EXPR_CONST; concat_expr->type = type_untypedlist; + concat_expr->resolve_status = RESOLVE_DONE; concat_expr->const_expr = (ExprConst) { .const_kind = CONST_UNTYPED_LIST, .untyped_list = untyped_exprs diff --git a/src/compiler/sema_initializers.c b/src/compiler/sema_initializers.c index 0663694f0..d3da02fe0 100644 --- a/src/compiler/sema_initializers.c +++ b/src/compiler/sema_initializers.c @@ -394,6 +394,7 @@ static inline bool sema_expr_analyse_untyped_initializer(SemaContext *context, E initializer->expr_kind = EXPR_CONST; initializer->const_expr = (ExprConst) { .const_kind = CONST_UNTYPED_LIST, .untyped_list = init_list }; initializer->type = type_untypedlist; + initializer->resolve_status = RESOLVE_DONE; return true; } @@ -712,6 +713,7 @@ bool sema_expr_analyse_initializer_list(SemaContext *context, Type *to, Expr *ex expr->const_expr.const_kind = CONST_POINTER; expr->const_expr.ptr = 0; expr->type = to; + expr->resolve_status = RESOLVE_DONE; return true; } // Resolve this as an inferred array. diff --git a/src/compiler/sema_stmts.c b/src/compiler/sema_stmts.c index 510624a68..0e77f4c53 100644 --- a/src/compiler/sema_stmts.c +++ b/src/compiler/sema_stmts.c @@ -2522,7 +2522,7 @@ static inline bool sema_analyse_ct_switch_stmt(SemaContext *context, Ast *statem FALLTHROUGH; default: assert(cond); - SEMA_ERROR(cond, "Only types, strings, enums, integers, floats and booleans may be used with '$switch'."); + SEMA_ERROR(cond, "Only types, strings, enums, integers, floats and booleans may be used with '$switch'."); // NOLINT goto FAILED; } @@ -2549,6 +2549,11 @@ static inline bool sema_analyse_ct_switch_stmt(SemaContext *context, Ast *statem SEMA_ERROR(to_expr, "$case ranges are only allowed for integers."); goto FAILED; } + // Do not evaluate if found. + if (matched_case != case_count && expr && expr->resolve_status != RESOLVE_DONE) + { + continue; + } if (is_type) { if (!sema_analyse_ct_expr(context, expr)) goto FAILED; @@ -2560,6 +2565,8 @@ static inline bool sema_analyse_ct_switch_stmt(SemaContext *context, Ast *statem } else { + // Do not evaluate if found. + if (matched_case != case_count && expr->resolve_status != RESOLVE_DONE) continue; if (!sema_analyse_expr_rhs(context, type, expr, false, NULL)) goto FAILED; if (to_expr && !sema_analyse_expr_rhs(context, type, to_expr, false, NULL)) goto FAILED; } @@ -2591,6 +2598,7 @@ static inline bool sema_analyse_ct_switch_stmt(SemaContext *context, Ast *statem { Ast *other_stmt = cases[j]; if (other_stmt->ast_kind == AST_DEFAULT_STMT) continue; + if (exprptr(other_stmt->case_stmt.expr)->resolve_status != RESOLVE_DONE) continue; ExprConst *other_const = &exprptr(other_stmt->case_stmt.expr)->const_expr; ExprConst *other_const_to = other_stmt->case_stmt.to_expr ? &exprptr(other_stmt->case_stmt.to_expr)->const_expr : other_const; if (expr_const_in_range(const_expr, other_const, other_const_to)) @@ -2617,10 +2625,9 @@ static inline bool sema_analyse_ct_switch_stmt(SemaContext *context, Ast *statem break; } case AST_DEFAULT_STMT: - if (default_case < case_count) + if (i != case_count - 1) { - SEMA_ERROR(stmt, "More than one $default is not allowed."); - SEMA_NOTE(cases[default_case], "The previous $default was here."); + SEMA_ERROR(stmt, "$default must be last in a $switch."); goto FAILED; } default_case = (int)i; diff --git a/src/compiler/sema_types.c b/src/compiler/sema_types.c index 909cace76..c27a7022a 100644 --- a/src/compiler/sema_types.c +++ b/src/compiler/sema_types.c @@ -319,7 +319,7 @@ INLINE bool sema_resolve_typeof(SemaContext *context, TypeInfo *type_info) case STORAGE_WILDCARD: RETURN_SEMA_ERROR(expr, "This %sexpression lacks a concrete type.", type_is_optional(expr_type) ? "optional " : ""); case STORAGE_COMPILE_TIME: - RETURN_SEMA_ERROR(expr, "This expression has a compile time type."); + RETURN_SEMA_ERROR(expr, "This expression has a compile time type %s.", type_quoted_error_string(expr_type)); } UNREACHABLE } diff --git a/test/test_suite/compile_time/ct_switch_errors.c3 b/test/test_suite/compile_time/ct_switch_errors.c3 index e1c89f0cb..bbb85a1a7 100644 --- a/test/test_suite/compile_time/ct_switch_errors.c3 +++ b/test/test_suite/compile_time/ct_switch_errors.c3 @@ -4,15 +4,15 @@ fn void test() $switch (3) $case 2: return; + $default: // #error: $default must be last in a $switch $default: - $default: // #error: More than one $default is not allowed return; $endswitch } fn void test1() { - $switch (-1) + $switch (1) $case -1: return; $case -1: // #error: '-1' appears more than once diff --git a/test/test_suite/compile_time/ct_switch_type_errors.c3 b/test/test_suite/compile_time/ct_switch_type_errors.c3 index 82e98fbdd..27a3df32d 100644 --- a/test/test_suite/compile_time/ct_switch_type_errors.c3 +++ b/test/test_suite/compile_time/ct_switch_type_errors.c3 @@ -4,8 +4,8 @@ fn void test() $switch (int.typeid) $case int: return; + $default: // #error: must be last in a $switch $default: - $default: // #error: More than one $default is not allowed return; $endswitch } @@ -27,11 +27,11 @@ def Bar = double; fn void test2() { $switch (int.typeid) - $case int: + $case Foo: return; $case Bar: return; - $case Foo: // #error: 'int' appears more than once + $case int: // #error: 'int' appears more than once return; $default: return; diff --git a/test/unit/stdlib/sort/countingsort.c3 b/test/unit/stdlib/sort/countingsort.c3 new file mode 100644 index 000000000..8da53d9d1 --- /dev/null +++ b/test/unit/stdlib/sort/countingsort.c3 @@ -0,0 +1,98 @@ +module sort_test @test; + import std::math; + import std::sort; + import sort::check; + import std::collections::list; + + fn void countingsort() + { + int[][] tcases = { + {}, + {[0..128] = 10, [129..192] = 3}, + {[0..128] = 3, [129..192] = 2, [193..200] = 1}, + {[0..128] = 1, [129..192] = 2, [193..200] = 3}, + {[0..128] = 2, [129..192] = 1, [193..200] = 3}, + }; + + foreach (tc : tcases) + { + sort::countingsort(tc); + assert(check::int_ascending_sort(tc)); + } + } + + fn void countingsort_with_ref() + { + int[][] tcases = { + {}, + {[0..128] = 10, [129..192] = 3}, + {[0..128] = 3, [129..192] = 2, [193..200] = 1}, + {[0..128] = 1, [129..192] = 2, [193..200] = 3}, + {[0..128] = 2, [129..192] = 1, [193..200] = 3}, + }; + + foreach (tc : tcases) + { + sort::countingsort(tc, &sort::key_int_ref); + assert(check::int_ascending_sort(tc)); + } + } + + fn void countingsort_with_value() + { + int[][] tcases = { + {}, + {[0..128] = 10, [129..192] = 3}, + {[0..128] = 3, [129..192] = 2, [193..200] = 1}, + {[0..128] = 1, [129..192] = 2, [193..200] = 3}, + {[0..128] = 2, [129..192] = 1, [193..200] = 3}, + }; + + foreach (tc : tcases) + { + sort::countingsort(tc, &sort::key_int_value); + assert(check::int_ascending_sort(tc)); + } + } + + fn void countingsort_with_lambda() + { + int[][] tcases = { + {}, + {[0..128] = 10, [129..192] = 3}, + {[0..128] = 3, [129..192] = 2, [193..200] = 1}, + {[0..128] = 1, [129..192] = 2, [193..200] = 3}, + {[0..128] = 2, [129..192] = 1, [193..200] = 3}, + }; + + foreach (tc : tcases) + { + sort::countingsort(tc, fn uint(int a) => ((uint)(a + int.min))); + assert(check::int_ascending_sort(tc)); + } + } + + def CountingSortTestList = List(); + + fn void countingsort_list() + { + CountingSortTestList list; + list.temp_init(); + list.add_array({ 2, 1, 3}); + sort::countingsort(list, &sort::key_int_value); + assert(check::int_ascending_sort(list.array_view())); + } + + fn void countingsort_random_large_list() + { + Lcg128Random random; + random::seed_entropy(&random); + + CountingSortTestList list; + for (usz i = 0; i < 2048; i++) { + list.push(random.next_int()); + } + + sort::countingsort(list, &sort::key_int_value); + assert(check::int_ascending_sort(list.array_view())); +} \ No newline at end of file diff --git a/test/unit/stdlib/sort/sort.c3 b/test/unit/stdlib/sort/sort.c3 index 538a8b203..df7052cff 100644 --- a/test/unit/stdlib/sort/sort.c3 +++ b/test/unit/stdlib/sort/sort.c3 @@ -1,9 +1,22 @@ module std::sort; -fn int cmp_int_ref(int* x, int* y) { - return *x - *y; +fn int cmp_int_ref(int* x, int* y) +{ + return *x - *y; } -fn int cmp_int_value(int x, int y) { - return x - y; +fn int cmp_int_value(int x, int y) +{ + return x - y; +} + + +fn uint key_int_ref(int* x) +{ + return (uint)(*x + int.min); +} + +fn uint key_int_value(int x) +{ + return (uint)(x + int.min); } \ No newline at end of file