From de09a19a48665fae775d2d808b667dac50b4c910 Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Tue, 19 Aug 2025 00:40:56 +0200 Subject: [PATCH] - Incorrect type checking when &[] and [] return optional values. - Failed to find subscript overloading on optional values. - Added `&[]` overload to HashMap. --- lib/std/collections/hashmap.c3 | 18 ++++++++++++++++++ releasenotes.md | 3 +++ src/compiler/sema_decls.c | 6 +++--- src/compiler/sema_expr.c | 4 ++-- test/unit/stdlib/collections/map.c3 | 13 +++++++++++++ 5 files changed, 39 insertions(+), 5 deletions(-) diff --git a/lib/std/collections/hashmap.c3 b/lib/std/collections/hashmap.c3 index fecb10869..81f01341a 100644 --- a/lib/std/collections/hashmap.c3 +++ b/lib/std/collections/hashmap.c3 @@ -182,6 +182,24 @@ fn Value*? HashMap.get_ref(&map, Key key) return NOT_FOUND?; } +fn Value* HashMap.get_or_create_ref(&map, Key key) @operator(&[]) +{ + uint hash = rehash(key.hash()); + if (map.count) + { + for (Entry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next) + { + if (e.hash == hash && equals(key, e.key)) return &e.value; + } + } + map.set(key, {}); + for (Entry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next) + { + if (e.hash == hash && equals(key, e.key)) return &e.value; + } + unreachable(); +} + fn Entry*? HashMap.get_entry(&map, Key key) { if (!map.count) return NOT_FOUND?; diff --git a/releasenotes.md b/releasenotes.md index 6cfac76e5..d1609048e 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -51,6 +51,8 @@ - Compiler assert when calling unassigned CT functions #2418. - Fixed crash in header generation when exporting functions with const enums (#2384). - Fix incorrect panic message when slicing with negative size. +- Incorrect type checking when &[] and [] return optional values. +- Failed to find subscript overloading on optional values. ### Stdlib changes - Add `==` to `Pair`, `Triple` and TzDateTime. Add print to `Pair` and `Triple`. @@ -65,6 +67,7 @@ - Add Freestanding OS types to runtime `env::` booleans. - Added libloaderapi to `std::os::win32`. - Added `HashSet.values` and `String.contains_char` #2386 +- Added `&[]` overload to HashMap. ## 0.7.4 Change list diff --git a/src/compiler/sema_decls.c b/src/compiler/sema_decls.c index e17efd3f6..0bb43e589 100755 --- a/src/compiler/sema_decls.c +++ b/src/compiler/sema_decls.c @@ -1851,7 +1851,7 @@ static bool sema_analyse_operator_common(SemaContext *context, Decl *method, Typ Decl *sema_find_untyped_operator(Type *type, OperatorOverload operator_overload, Decl *skipped) { - type = type->canonical; + type = type_no_optional(type)->canonical; assert(operator_overload < OVERLOAD_TYPED_START); if (!type_may_have_sub_elements(type)) return NULL; Decl *def = type->decl; @@ -2098,7 +2098,7 @@ static inline bool sema_analyse_operator_element_at(SemaContext *context, Decl * RETURN_SEMA_ERROR(rtype, "%s has unknown size and cannot be used as a return type.", type_quoted_error_string(rtype->type)); } - if (method->func_decl.operator == OVERLOAD_ELEMENT_REF && !type_is_pointer(rtype->type)) + if (method->func_decl.operator == OVERLOAD_ELEMENT_REF && !type_is_pointer(type_no_optional(rtype->type))) { RETURN_SEMA_ERROR(rtype, "The return type must be a pointer, but it is returning %s, did you mean to overload [] instead?", type_quoted_error_string(rtype->type)); @@ -2224,7 +2224,7 @@ static inline void sema_get_overload_arguments(Decl *method, Type **value_ref, T *index_ref = method->func_decl.signature.params[1]->type->canonical; return; case OVERLOAD_ELEMENT_REF: - *value_ref = type_no_optional(typeget(method->func_decl.signature.rtype)->canonical->pointer); + *value_ref = type_no_optional(typeget(method->func_decl.signature.rtype))->canonical->pointer; *index_ref = method->func_decl.signature.params[1]->type->canonical; return; case OVERLOAD_ELEMENT_SET: diff --git a/src/compiler/sema_expr.c b/src/compiler/sema_expr.c index 4e96d38f3..e56068677 100644 --- a/src/compiler/sema_expr.c +++ b/src/compiler/sema_expr.c @@ -11781,7 +11781,7 @@ bool sema_insert_method_call(SemaContext *context, Expr *method_call, Decl *meth .call_expr.is_func_ref = true, .call_expr.is_type_method = true, }; - Type *type = parent->type->canonical; + Type *type = type_no_optional(parent->type)->canonical; Decl *first_param = method_decl->func_decl.signature.params[0]; Type *first = first_param->type->canonical; // Deref / addr as needed. @@ -11796,7 +11796,7 @@ bool sema_insert_method_call(SemaContext *context, Expr *method_call, Decl *meth if (!sema_expr_rewrite_insert_deref(context, parent)) return false; } } - ASSERT_SPAN(method_call, parent && parent->type && first == parent->type->canonical); + ASSERT_SPAN(method_call, first == type_no_optional(parent->type)->canonical); unit_register_external_symbol(context, method_decl); if (!sema_expr_analyse_general_call(context, method_call, method_decl, parent, false, NULL)) return expr_poison(method_call); diff --git a/test/unit/stdlib/collections/map.c3 b/test/unit/stdlib/collections/map.c3 index 524c7c906..b8394d72c 100644 --- a/test/unit/stdlib/collections/map.c3 +++ b/test/unit/stdlib/collections/map.c3 @@ -103,3 +103,16 @@ fn void map_copy() assert(hash_map_copy.len() == hash_map.len()); } + +alias Test = HashMap{String, HashMap {String, String}}; +fn void test_ref() +{ + Test t; + t.init(tmem); + (&t["a"]).init(tmem); + + (*&t["a"])["b"] = "ab"; + test::eq("ab", t["a"]["b"]!!); +} + +