From 85dc9c45aba5bcbdfa2193a104ac12273a82670e Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Thu, 14 Aug 2025 15:53:35 +0200 Subject: [PATCH] - 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. --- releasenotes.md | 5 ++ src/build/build.h | 2 + src/build/build_options.c | 6 +++ src/build/builder.c | 1 + src/compiler/enums.h | 3 +- src/compiler/llvm_codegen_expr.c | 1 + src/compiler/sema_decls.c | 31 +++++++++--- src/compiler/sema_expr.c | 50 ++++++++++++++++++- src/compiler/sema_internal.h | 2 + src/compiler/sema_stmts.c | 35 +++++++++---- src/compiler/types.c | 2 +- .../statements/switch_const_non_int.c3t | 3 +- 12 files changed, 117 insertions(+), 24 deletions(-) diff --git a/releasenotes.md b/releasenotes.md index 10abc7864..8a0bcbeed 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -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`. diff --git a/src/build/build.h b/src/build/build.h index 66142f04b..89707b3ff 100644 --- a/src/build/build.h +++ b/src/build/build.h @@ -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; diff --git a/src/build/build_options.c b/src/build/build_options.c index b03830082..0e0ca32da 100644 --- a/src/build/build_options.c +++ b/src/build/build_options.c @@ -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."); diff --git a/src/build/builder.c b/src/build/builder.c index e0e7310b9..866c652d9 100644 --- a/src/build/builder.c +++ b/src/build/builder.c @@ -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) { diff --git a/src/compiler/enums.h b/src/compiler/enums.h index 6c64132fb..44e6b3aac 100644 --- a/src/compiler/enums.h +++ b/src/compiler/enums.h @@ -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, diff --git a/src/compiler/llvm_codegen_expr.c b/src/compiler/llvm_codegen_expr.c index ee953cca2..da2498b68 100644 --- a/src/compiler/llvm_codegen_expr.c +++ b/src/compiler/llvm_codegen_expr.c @@ -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: diff --git a/src/compiler/sema_decls.c b/src/compiler/sema_decls.c index 12ac08ae8..00fbd29cb 100755 --- a/src/compiler/sema_decls.c +++ b/src/compiler/sema_decls.c @@ -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; diff --git a/src/compiler/sema_expr.c b/src/compiler/sema_expr.c index 45fe342a1..b885f1608 100644 --- a/src/compiler/sema_expr.c +++ b/src/compiler/sema_expr.c @@ -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 diff --git a/src/compiler/sema_internal.h b/src/compiler/sema_internal.h index 8fdca151d..6484717e4 100644 --- a/src/compiler/sema_internal.h +++ b/src/compiler/sema_internal.h @@ -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); diff --git a/src/compiler/sema_stmts.c b/src/compiler/sema_stmts.c index b74582d5a..797b08ff0 100644 --- a/src/compiler/sema_stmts.c +++ b/src/compiler/sema_stmts.c @@ -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) { diff --git a/src/compiler/types.c b/src/compiler/types.c index 7121aa581..28d3ae3ef 100644 --- a/src/compiler/types.c +++ b/src/compiler/types.c @@ -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; diff --git a/test/test_suite/statements/switch_const_non_int.c3t b/test/test_suite/statements/switch_const_non_int.c3t index 8a4a39357..1d29f00f0 100644 --- a/test/test_suite/statements/switch_const_non_int.c3t +++ b/test/test_suite/statements/switch_const_non_int.c3t @@ -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 };