diff --git a/releasenotes.md b/releasenotes.md index fd48147fe..eb25bcdb4 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -69,6 +69,7 @@ - Fix thread tests. - Detect recursion errors on non-recursive mutexes in safe mode. - Foreach over distinct pointer failed to be caught as error #1506. +- Foreach over distinct iterable would ignore operator(len). ### Stdlib changes - Additional init functions for hashmap. diff --git a/src/compiler/sema_stmts.c b/src/compiler/sema_stmts.c index 9cc877650..3754efdec 100644 --- a/src/compiler/sema_stmts.c +++ b/src/compiler/sema_stmts.c @@ -1463,10 +1463,6 @@ static inline bool sema_analyse_foreach_stmt(SemaContext *context, Ast *statemen // Check that we can even index this expression. Type *value_type = type_get_indexed_type(enumerator->type); - // Special case, we might have "distinct Foo = char*" in which case the - // deref didn't happen above, but it will get a value_type. - // However, that would make the code later assume it's possible - // To use foreach on it. if (canonical->type_kind == TYPE_DISTINCT && type_flatten(canonical)->type_kind == TYPE_POINTER) { value_type = NULL; @@ -1478,20 +1474,19 @@ static inline bool sema_analyse_foreach_stmt(SemaContext *context, Ast *statemen Decl *index_macro = NULL; Type *index_type = type_usz; - if (!value_type) + if (!value_type || canonical->type_kind == TYPE_DISTINCT) { len = sema_find_operator(context, enumerator->type, OVERLOAD_LEN); Decl *by_val = sema_find_operator(context, enumerator->type, OVERLOAD_ELEMENT_AT); Decl *by_ref = sema_find_operator(context, enumerator->type, OVERLOAD_ELEMENT_REF); if (!len || (!by_val && !by_ref)) { - SEMA_ERROR(enumerator, "It's not possible to enumerate an expression of type %s.", type_quoted_error_string(enumerator->type)); - return false; + if (value_type) goto SKIP_OVERLOAD; + RETURN_SEMA_ERROR(enumerator, "It's not possible to enumerate an expression of type %s.", type_quoted_error_string(enumerator->type)); } if (!by_ref && value_by_ref) { - SEMA_ERROR(enumerator, "%s does not support 'foreach' by reference, but you iterate by value.", type_quoted_error_string(enumerator->type)); - return false; + RETURN_SEMA_ERROR(enumerator, "%s does not support 'foreach' by reference, but you iterate by value.", type_quoted_error_string(enumerator->type)); } if (!decl_ok(len) || !decl_ok(by_val) || !decl_ok(by_ref)) return false; index_macro = value_by_ref ? by_ref : by_val; @@ -1499,13 +1494,13 @@ static inline bool sema_analyse_foreach_stmt(SemaContext *context, Ast *statemen index_type = index_macro->func_decl.signature.params[1]->type; if (!type_is_integer(index_type)) { - SEMA_ERROR(enumerator, "Only integer indexed types may be used with foreach."); - return false; + RETURN_SEMA_ERROR(enumerator, "Only integer indexed types may be used with foreach."); } TypeInfoId rtype = index_macro->func_decl.signature.rtype; value_type = rtype ? type_infoptr(rtype)->type : NULL; } +SKIP_OVERLOAD:; TypeInfo *type_info = vartype(var); // Set up the value, assigning the type as needed. @@ -1531,15 +1526,13 @@ static inline bool sema_analyse_foreach_stmt(SemaContext *context, Ast *statemen index_var_type = idx_type_info->type; if (type_is_optional(index_var_type)) { - SEMA_ERROR(idx_type_info, "The index may not be an optional."); - return false; + RETURN_SEMA_ERROR(idx_type_info, "The index may not be an optional."); } if (!type_is_integer(type_flatten(index_var_type))) { - SEMA_ERROR(idx_type_info, - "Index must be an integer type, '%s' is not valid.", - type_to_error_string(index_var_type)); - return false; + RETURN_SEMA_ERROR(idx_type_info, + "Index must be an integer type, '%s' is not valid.", + type_to_error_string(index_var_type)); } } diff --git a/test/test_suite/statements/foreach_distinct_iterable.c3t b/test/test_suite/statements/foreach_distinct_iterable.c3t new file mode 100644 index 000000000..8cad8f6be --- /dev/null +++ b/test/test_suite/statements/foreach_distinct_iterable.c3t @@ -0,0 +1,67 @@ +// #target: macos-x64 +module test; +import std; +distinct TypeA = char[]; + +fn char TypeA.get(self, usz i) @operator([]) { + return ((char[])self)[i]; +} + +fn usz TypeA.len(self) @operator(len) +{ + return self.len; +} + +fn int main() { + TypeA x = "AAAAA"; + foreach(y : x) + { + int z = y; + } + return 0; +} + +/* #expect: test.ll + +et i64 %2 +} + +define i32 @main() #0 { +entry: + %x = alloca %"char[]", align 8 + %.anon = alloca i64, align 8 + %.anon1 = alloca i64, align 8 + %y = alloca i8, align 1 + %z = alloca i32, align 4 + store %"char[]" { ptr @.str, i64 5 }, ptr %x, align 8 + %lo = load ptr, ptr %x, align 8 + %ptradd = getelementptr inbounds i8, ptr %x, i64 8 + %hi = load i64, ptr %ptradd, align 8 + %0 = call i64 @test.TypeA.len(ptr %lo, i64 %hi) + store i64 %0, ptr %.anon, align 8 + store i64 0, ptr %.anon1, align 8 + br label %loop.cond + +loop.cond: ; preds = %loop.body, %entry + %1 = load i64, ptr %.anon1, align 8 + %2 = load i64, ptr %.anon, align 8 + %lt = icmp ult i64 %1, %2 + br i1 %lt, label %loop.body, label %loop.exit + +loop.body: ; preds = %loop.cond + %lo2 = load ptr, ptr %x, align 8 + %ptradd3 = getelementptr inbounds i8, ptr %x, i64 8 + %hi4 = load i64, ptr %ptradd3, align 8 + %3 = load i64, ptr %.anon1, align 8 + %4 = call i8 @test.TypeA.get(ptr %lo2, i64 %hi4, i64 %3) + store i8 %4, ptr %y, align 1 + %5 = load i8, ptr %y, align 1 + %zext = zext i8 %5 to i32 + store i32 %zext, ptr %z, align 4 + %6 = load i64, ptr %.anon1, align 8 + %addnuw = add nuw i64 %6, 1 + store i64 %addnuw, ptr %.anon1, align 8 + br label %loop.cond + +loop.exit: ; preds = %loop.cond + ret i32 0 \ No newline at end of file