- Deprecate @compact use for comparison. Old behaviour is enabled using --use-old-compact-eq.

- Switch available for types implementing `@operator(==)`.
- `Type.is_eq` is now true for types with `==` overload.
- Functions being tested for overload are now always checked before test.
- Compile time indexing at compile time in a $typeof was no considered compile time.
This commit is contained in:
Christoffer Lerno
2025-08-14 15:53:35 +02:00
parent 076ef187cb
commit 85dc9c45ab
12 changed files with 117 additions and 24 deletions

View File

@@ -8,6 +8,9 @@
- Add compile-time `@clz` builtin. #2367
- Add `bitsizeof` macro builtins. #2376
- Add compile-time `@min` and `@max` builtins. #2378
- Deprecate `@compact` use for comparison. Old behaviour is enabled using `--use-old-compact-eq`.
- Switch available for types implementing `@operator(==)`.
- `Type.is_eq` is now true for types with `==` overload.
### Fixes
- List.remove_at would incorrectly trigger ASAN.
@@ -29,6 +32,8 @@
- Fix `native_cpus` functionality for OpenBSD systems. #2387
- Assert triggered when trying to slice a struct.
- Improve codegen for stack allocated large non-zero arrays.
- Functions being tested for overload are now always checked before test.
- Compile time indexing at compile time in a $typeof was no considered compile time.
### Stdlib changes
- Add `==` to `Pair`, `Triple` and TzDateTime. Add print to `Pair` and `Triple`.

View File

@@ -555,6 +555,7 @@ typedef struct BuildOptions_
bool suppress_run;
bool old_slice_copy;
bool old_enums;
bool old_compact_eq;
int verbosity_level;
const char *panicfn;
const char *benchfn;
@@ -698,6 +699,7 @@ typedef struct
bool print_stats;
bool old_slice_copy;
bool old_enums;
bool old_compact_eq;
bool single_threaded;
int build_threads;
TrustLevel trust_level;

View File

@@ -133,6 +133,7 @@ static void usage(bool full)
print_opt("--lsp", "Emit data about errors suitable for a LSP.");
print_opt("--use-old-slice-copy", "Use the old slice copy semantics.");
print_opt("--use-old-enums", "Use the old enum syntax and semantics.");
print_opt("--use-old-compact-eq", "Enable the old ability to use '@compact' to make a struct comparable.");
}
PRINTF("");
print_opt("-g", "Emit debug info.");
@@ -764,6 +765,11 @@ static void parse_option(BuildOptions *options)
options->old_enums = true;
return;
}
if (match_longopt("use-old-compact-eq"))
{
options->old_compact_eq = true;
return;
}
if (match_longopt("test-filter"))
{
if (at_end() || next_is_opt()) FAIL_WITH_ERR_LONG("error: --test-filter needs an argument.");

View File

@@ -367,6 +367,7 @@ static void update_build_target_from_options(BuildTarget *target, BuildOptions *
target->backend = options->backend;
target->old_slice_copy = options->old_slice_copy;
target->old_enums = options->old_enums;
target->old_compact_eq = options->old_compact_eq;
// Remove feature flags
FOREACH(const char *, remove_feature, options->removed_feature_names)
{

View File

@@ -924,7 +924,8 @@ typedef enum
typedef enum
{
OVERLOAD_MATCH_NONE,
OVERLOAD_MATCH_ERROR = -1,
OVERLOAD_MATCH_NONE = 0,
OVERLOAD_MATCH_EXACT,
OVERLOAD_MATCH_CONVERSION,
OVERLOAD_MATCH_WILDCARD,

View File

@@ -3573,6 +3573,7 @@ MEMCMP:
case TYPE_UNION:
case TYPE_STRUCT:
case TYPE_BITSTRUCT:
assert(compiler.build.old_compact_eq);
if (array_base->decl->attr_compact) goto MEMCMP;
break;
case TYPE_POISONED:

View File

@@ -159,6 +159,8 @@ static inline bool sema_resolve_implemented_interfaces(SemaContext *context, Dec
/**
* Analyse a struct or union field.
*
* This will also push the member to the current declaration stack.
*/
static inline bool sema_analyse_struct_member(SemaContext *context, Decl *parent, Decl *decl, bool *erase_decl)
{
@@ -174,7 +176,7 @@ static inline bool sema_analyse_struct_member(SemaContext *context, Decl *parent
if (decl->resolve_status == RESOLVE_RUNNING) RETURN_SEMA_ERROR(decl, "Circular dependency resolving member.");
// Mark the unit, it should not have been assigned at this point.
ASSERT(!decl->unit || decl->unit->module->is_generic || decl->unit == parent->unit);
ASSERT_SPAN(decl, !decl->unit || decl->unit->module->is_generic || decl->unit == parent->unit);
decl->unit = parent->unit;
// Pick the domain for attribute analysis.
@@ -216,17 +218,23 @@ static inline bool sema_analyse_struct_member(SemaContext *context, Decl *parent
}
bool is_export = parent->is_export;
// Analysis depends on the underlying type.
switch (decl->decl_kind)
{
// A regular member
case DECL_VAR:
{
ASSERT(decl->var.kind == VARDECL_MEMBER);
ASSERT_SPAN(decl, decl->var.kind == VARDECL_MEMBER);
decl->resolve_status = RESOLVE_RUNNING;
// Resolve the type
// Inferred types are not strictly allowed, but we use the int[*] for the flexible array member.
ASSERT(type_infoptrzero(decl->var.type_info));
ASSERT_SPAN(decl, decl->var.type_info);
TypeInfo *type_info = type_infoptr(decl->var.type_info);
if (!sema_resolve_type_info(context, type_info, RESOLVE_TYPE_ALLOW_FLEXIBLE)) return decl_poison(decl);
if (!sema_resolve_type_info(context, type_info, RESOLVE_TYPE_ALLOW_FLEXIBLE)) return false;
// Check the resulting storage type:
Type *type = type_info->type;
switch (sema_resolve_storage_type(context, type))
{
@@ -246,16 +254,19 @@ static inline bool sema_analyse_struct_member(SemaContext *context, Decl *parent
decl->resolve_status = RESOLVE_DONE;
return true;
}
// It might be a struct of union
case DECL_STRUCT:
case DECL_UNION:
// Extend the nopadding attributes to nested structs.
if (parent->attr_nopadding) decl->attr_nopadding = true;
if (parent->attr_compact) decl->attr_compact = true;
FALLTHROUGH;
// The common case for all inline types:
case DECL_BITSTRUCT:
// Set the nested type as export if this one is exported.
decl->is_export = is_export;
if (!sema_analyse_decl(context, decl)) return false;
return true;
// Perform the analysis
return sema_analyse_decl(context, decl);
default:
UNREACHABLE
}
@@ -1851,6 +1862,7 @@ INLINE bool decl_matches_overload(Decl *method, Type *type, OperatorOverload ove
static inline OverloadMatch operator_in_module_typed(SemaContext *c, Module *module, OperatorOverload operator_overload, OverloadType overload_type, Type *method_type, Expr *binary_arg, Type *binary_type, Decl **candidate_ref, OverloadMatch match, Decl **ambiguous_ref)
{
if (match == OVERLOAD_MATCH_ERROR) return match;
if (module->is_generic) return match;
match = sema_find_typed_operator_in_list(c, module->private_method_extensions, operator_overload, OVERLOAD_TYPE_SYMMETRIC, method_type, binary_arg, binary_type, candidate_ref, match, ambiguous_ref);
FOREACH(Module *, sub_module, module->sub_modules)
@@ -1940,10 +1952,12 @@ static Decl *sema_find_exact_typed_operator_in_list(Decl **methods, OperatorOver
static OverloadMatch sema_find_typed_operator_in_list(SemaContext *context, Decl **methods, OperatorOverload operator_overload, OverloadType overload_type, Type *parent_type, Expr *binary_arg, Type *binary_type, Decl **candidate_ref, OverloadMatch last_match, Decl **ambiguous_ref)
{
if (last_match == OVERLOAD_MATCH_AMBIGUOUS_EXACT) return last_match;
if (last_match == OVERLOAD_MATCH_AMBIGUOUS_EXACT || last_match == OVERLOAD_MATCH_ERROR) return last_match;
Decl *candidate = *candidate_ref;
FOREACH(Decl *, func, methods)
{
if (!sema_analyse_decl(context, func)) return OVERLOAD_MATCH_ERROR;
ASSERT_SPAN(func, func->resolve_status == RESOLVE_DONE);
if (func->func_decl.operator != operator_overload) continue;
if (parent_type && parent_type != typeget(func->func_decl.type_parent)) continue;
if ((overload_type & func->func_decl.overload_type) == 0) continue;
@@ -2031,7 +2045,7 @@ static Decl *sema_find_exact_typed_operator(SemaContext *context, Type *type, Op
return NULL;
}
static OverloadMatch sema_find_typed_operator_type(SemaContext *context, OperatorOverload operator_overload, OverloadType overloat_type, Type *lhs_type, Type *rhs_type, Expr *rhs, Decl **candidate_ref, OverloadMatch last_match, Decl **ambiguous_ref)
OverloadMatch sema_find_typed_operator_type(SemaContext *context, OperatorOverload operator_overload, OverloadType overloat_type, Type *lhs_type, Type *rhs_type, Expr *rhs, Decl **candidate_ref, OverloadMatch last_match, Decl **ambiguous_ref)
{
// Can we find the overload directly on the method?
last_match = sema_find_typed_operator_in_list(
@@ -2120,6 +2134,7 @@ Decl *sema_find_typed_operator(SemaContext *context, OperatorOverload operator_o
return poisoned_decl;
}
if (current_match == OVERLOAD_MATCH_ERROR) return poisoned_decl;
if (candidate != first) *reverse = true;
return candidate;

View File

@@ -3719,7 +3719,7 @@ static inline bool sema_expr_resolve_subscript_index(SemaContext *context, Expr
ArrayIndex size;
bool check_len = !context->call_env.in_no_eval || current_type == type_untypedlist;
Expr *len_expr = current_expr->expr_kind == EXPR_CT_IDENT ? current_expr->ct_ident_expr.decl->var.init_expr : current_expr;
if (check_len && expr_is_const_int(index) && (size = sema_len_from_expr(len_expr)) >= 0)
if (expr_is_const_int(index) && (size = sema_len_from_expr(len_expr)) >= 0)
{
// 4c. And that it's in range.
if (int_is_neg(index->const_expr.ixx))
@@ -3729,6 +3729,11 @@ static inline bool sema_expr_resolve_subscript_index(SemaContext *context, Expr
}
if (!int_fits(index->const_expr.ixx, TYPE_I64) || size == 0)
{
if (!check_len)
{
index_value = -1;
goto SKIP;
}
if (check_valid) return false;
RETURN_SEMA_ERROR(index, "The index is out of range.", size);
}
@@ -3739,6 +3744,11 @@ static inline bool sema_expr_resolve_subscript_index(SemaContext *context, Expr
}
if (index_value < 0 || index_value >= size)
{
if (!check_len)
{
index_value = -1;
goto SKIP;
}
if (check_valid) return false;
if (start_from_end)
{
@@ -3757,6 +3767,7 @@ static inline bool sema_expr_resolve_subscript_index(SemaContext *context, Expr
(long long) size - 1);
}
}
SKIP:
*index_ref = index_value;
*current_type_ref = current_type;
*current_expr_ref = current_expr;
@@ -5598,7 +5609,19 @@ static bool sema_expr_rewrite_to_type_property(SemaContext *context, Expr *expr,
expr_rewrite_const_bool(expr, type_bool, type_is_ordered(flat));
return true;
case TYPE_PROPERTY_IS_EQ:
expr_rewrite_const_bool(expr, type_bool, type_is_comparable(flat));
if (type_is_comparable(flat))
{
expr_rewrite_const_bool(expr, type_bool, type_is_comparable(flat));
return true;
}
if (type_is_user_defined(type))
{
BoolErr res = sema_type_has_equality_overload(context, type);
if (res == BOOL_ERR) return false;
expr_rewrite_const_bool(expr, type_bool, res == BOOL_TRUE);
return true;
}
expr_rewrite_const_bool(expr, type_bool, false);
return true;
case TYPE_PROPERTY_IS_SUBSTRUCT:
expr_rewrite_const_bool(expr, type_bool, type_is_substruct(flat));
@@ -8045,6 +8068,29 @@ static bool sema_binary_is_unsigned_always_same_comparison(SemaContext *context,
}
BoolErr sema_type_has_equality_overload(SemaContext *context, CanonicalType *type)
{
assert(type->canonical == type);
Decl *candidate = NULL;
Decl *ambiguous = NULL;
OverloadMatch match = sema_find_typed_operator_type(context, OVERLOAD_EQUAL, OVERLOAD_TYPE_LEFT, type, type, NULL, &candidate, OVERLOAD_MATCH_NONE, &ambiguous);
if (match == OVERLOAD_MATCH_NONE) match = sema_find_typed_operator_type(context, OVERLOAD_NOT_EQUAL, OVERLOAD_TYPE_LEFT, type, type, NULL, &candidate, OVERLOAD_MATCH_NONE, &ambiguous);
switch (match)
{
case OVERLOAD_MATCH_ERROR:
return BOOL_ERR;
case OVERLOAD_MATCH_EXACT:
case OVERLOAD_MATCH_WILDCARD:
case OVERLOAD_MATCH_CONVERSION:
return BOOL_TRUE;
case OVERLOAD_MATCH_NONE:
case OVERLOAD_MATCH_AMBIGUOUS_WILDCARD:
case OVERLOAD_MATCH_AMBIGUOUS_CONVERSION:
case OVERLOAD_MATCH_AMBIGUOUS_EXACT:
return BOOL_FALSE;
}
UNREACHABLE
}
/**
* Analyze a == b, a != b, a > b, a < b, a >= b, a <= b
* @return

View File

@@ -98,6 +98,8 @@ bool sema_analyse_expr_value(SemaContext *context, Expr *expr);
Expr *expr_access_inline_member(Expr *parent, Decl *parent_decl);
bool sema_analyse_ct_expr(SemaContext *context, Expr *expr);
Decl *sema_find_typed_operator(SemaContext *context, OperatorOverload operator_overload, SourceSpan span, Expr *lhs, Expr *rhs, bool *reverse);
OverloadMatch sema_find_typed_operator_type(SemaContext *context, OperatorOverload operator_overload, OverloadType overloat_type, Type *lhs_type, Type *rhs_type, Expr *rhs, Decl **candidate_ref, OverloadMatch last_match, Decl **ambiguous_ref);
BoolErr sema_type_has_equality_overload(SemaContext *context, Type *type);
Decl *sema_find_untyped_operator(SemaContext *context, Type *type, OperatorOverload operator_overload, Decl *skipped);
bool sema_insert_method_call(SemaContext *context, Expr *method_call, Decl *method_decl, Expr *parent, Expr **arguments, bool reverse_overload);
bool sema_expr_analyse_builtin_call(SemaContext *context, Expr *expr);

View File

@@ -46,7 +46,7 @@ static inline bool sema_check_type_case(SemaContext *context, Ast *case_stmt, As
static inline bool sema_check_value_case(SemaContext *context, Type *switch_type, Ast *case_stmt, Ast **cases,
unsigned index, bool *if_chained, bool *max_ranged, uint64_t *actual_cases_ref,
Int *low, Int *high);
static bool sema_analyse_switch_body(SemaContext *context, Ast *statement, SourceSpan expr_span, Type *switch_type, Ast **cases);
static bool sema_analyse_switch_body(SemaContext *context, Ast *statement, SourceSpan expr_span, CanonicalType *switch_type, Ast **cases);
static inline bool sema_analyse_statement_inner(SemaContext *context, Ast *statement);
static bool sema_analyse_require(SemaContext *context, Ast *directive, AstId **asserts, SourceSpan source);
@@ -2484,18 +2484,31 @@ DONE:;
scratch_buffer_append(" - either add them or use 'default'.");
return scratch_buffer_to_string();
}
static bool sema_analyse_switch_body(SemaContext *context, Ast *statement, SourceSpan expr_span, Type *switch_type, Ast **cases)
static bool sema_analyse_switch_body(SemaContext *context, Ast *statement, SourceSpan expr_span, CanonicalType *switch_type, Ast **cases)
{
if (!type_is_comparable(switch_type))
bool is_enum_switch = false;
bool if_chain = true;
Decl **enum_values = NULL;
if (type_is_user_defined(switch_type) && switch_type->type_kind != TYPE_ENUM)
{
BoolErr res = sema_type_has_equality_overload(context, switch_type);
if (res == BOOL_ERR) return false;
if (res == BOOL_TRUE) goto FOUND;
}
if (type_is_comparable(switch_type))
{
Type *flat = type_flatten(switch_type);
TypeKind flat_switch_type_kind = flat->type_kind;
is_enum_switch = flat_switch_type_kind == TYPE_ENUM;
if (is_enum_switch) enum_values = flat->decl->enums.values;
// We need an if-chain if this isn't an enum/integer type.
if_chain = !is_enum_switch && !type_kind_is_any_integer(flat_switch_type_kind);
}
else
{
RETURN_SEMA_ERROR_AT(expr_span, "You cannot test '%s' for equality, and only values that supports '==' for comparison can be used in a switch.", type_to_error_string(switch_type));
}
// We need an if-chain if this isn't an enum/integer type.
Type *flat = type_flatten(switch_type);
TypeKind flat_switch_type_kind = flat->type_kind;
bool is_enum_switch = flat_switch_type_kind == TYPE_ENUM;
bool if_chain = !is_enum_switch && !type_kind_is_any_integer(flat_switch_type_kind);
FOUND:;
Ast *default_case = NULL;
ASSERT(context->active_scope.defer_start == context->active_scope.defer_last);
@@ -2549,7 +2562,7 @@ static bool sema_analyse_switch_body(SemaContext *context, Ast *statement, Sourc
POP_NEXT();
}
if (!exhaustive && is_enum_switch) exhaustive = actual_enum_cases == vec_size(flat->decl->enums.values);
if (!exhaustive && is_enum_switch) exhaustive = actual_enum_cases == vec_size(enum_values);
bool all_jump_end = exhaustive;
for (unsigned i = 0; i < case_count; i++)
{
@@ -2568,7 +2581,7 @@ static bool sema_analyse_switch_body(SemaContext *context, Ast *statement, Sourc
}
if (is_enum_switch && !exhaustive && success)
{
RETURN_SEMA_ERROR(statement, create_missing_enums_in_switch_error(cases, actual_enum_cases, flat->decl->enums.values));
RETURN_SEMA_ERROR(statement, create_missing_enums_in_switch_error(cases, actual_enum_cases, enum_values));
}
if (statement->flow.jump)
{

View File

@@ -520,7 +520,7 @@ bool type_is_comparable(Type *type)
return false;
case TYPE_UNION:
case TYPE_STRUCT:
return type->decl->attr_compact;
return type->decl->attr_compact && compiler.build.old_compact_eq;
case TYPE_BITSTRUCT:
type = type->decl->strukt.container_type->type;
goto RETRY;

View File

@@ -2,11 +2,12 @@
module test;
import std;
struct Foo @compact
struct Foo
{
int value;
}
fn bool Foo.eq(&self, Foo other) @operator(==) => self.value == other.value;
const Foo FOO1 = { 15 };
const Foo FOO2 = { 20 };