diff --git a/releasenotes.md b/releasenotes.md index b16344429..d2b8bee28 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -7,6 +7,7 @@ - Add splat defaults for designated initialization #2441. - Add new builtins `$$str_snakecase` `$$str_replace` and `$$str_pascalcase`. - `"build-dir"` option now available for `project.json`, added to project. #2323 +- Allow `..` ranges to use "a..a-1" in order to express zero length. ### Fixes - Bug in `io::write_using_write_byte`. diff --git a/src/compiler/llvm_codegen_expr.c b/src/compiler/llvm_codegen_expr.c index 0743e292e..2ecc2abdf 100644 --- a/src/compiler/llvm_codegen_expr.c +++ b/src/compiler/llvm_codegen_expr.c @@ -2845,8 +2845,13 @@ static void llvm_emit_slice_values(GenContext *c, Expr *slice, BEValue *parent_r } else { - llvm_emit_int_comp(c, &excess, &start_index, &end_index, BINARYOP_GT); - llvm_emit_panic_if_true(c, &excess, "Negative size", slice->span, "Negative size (start %d is less than end %d)", &start_index, &end_index); + llvm_value_rvalue(c, &start_index); + llvm_value_rvalue(c, &end_index); + LLVMValueRef val = llvm_emit_add_int(c, end_index.type, end_index.value, llvm_const_int(c, end_index.type, 1), slice->span); + BEValue plus_one_end_index; + llvm_value_set(&plus_one_end_index, val, end_index.type); + llvm_emit_int_comp(c, &excess, &start_index, &plus_one_end_index, BINARYOP_GT); + llvm_emit_panic_if_true(c, &excess, "Negative size", slice->span, "Negative size (slice was: [%d..%d])", &start_index, &end_index); if (len.value) { diff --git a/src/compiler/sema_expr.c b/src/compiler/sema_expr.c index 1009b22f6..3dce12e03 100644 --- a/src/compiler/sema_expr.c +++ b/src/compiler/sema_expr.c @@ -4258,6 +4258,7 @@ INLINE bool sema_expr_analyse_range_internal(SemaContext *context, Range *range, if (!sema_analyse_expr_rvalue(context, start)) return false; if (end && !sema_analyse_expr_rvalue(context, end)) return false; + ArrayIndex lowest = range->is_len ? 0 : -1; if (!cast_to_index_len(context, start, false)) return false; if (end && !cast_to_index_len(context, end, false)) return false; Type *end_type = end ? type_no_optional(end->type) : NULL; @@ -4307,14 +4308,14 @@ INLINE bool sema_expr_analyse_range_internal(SemaContext *context, Range *range, // Something like 1 .. ^4 with an unknown length. if (len < 0) return true; // Otherwise we fold the "from end" - if (end_index > len) + if (end_index + lowest > len) { RETURN_SEMA_ERROR(end, "An index may only be negative for pointers (it was: %lld).", len - end_index); } end_index = len - end_index; range->end_from_end = false; } - if (end_index < 0 && env != RANGE_PTR) + if (end_index < lowest && env != RANGE_PTR) { RETURN_SEMA_ERROR(end, "An index may only be negative for pointers (it was: %lld).", end_index); } @@ -4363,7 +4364,7 @@ INLINE bool sema_expr_analyse_range_internal(SemaContext *context, Range *range, if (range->range_type == RANGE_CONST_END) { ArrayIndex end_index = range->const_end; - if (end_index < start_index) RETURN_SEMA_ERROR(start, "The start index (%lld) should not be greater than the end index (%lld).", + if (end_index - lowest < start_index) RETURN_SEMA_ERROR(start, "The start index (%lld) should not be greater than the end index (%lld).", start_index, end_index); range->const_end = end_index + 1 - start_index; range->range_type = RANGE_CONST_LEN; diff --git a/test/test_suite/arrays/slice_negative.c3t b/test/test_suite/arrays/slice_negative.c3t new file mode 100644 index 000000000..a4dcfed6e --- /dev/null +++ b/test/test_suite/arrays/slice_negative.c3t @@ -0,0 +1,137 @@ +// #target: macos-x64 +// #safe: yes +module test; + +fn void main() +{ + int[2] x = { 1, 2 }; + int[] z = x[0..-1]; + int y = 1; + int yy = -1; + int[] w = x[y..yy]; +} + +/* #expect: test.ll + + +define void @test.main() #0 { +entry: + %x = alloca [2 x i32], align 4 + %z = alloca %"int[]", align 8 + %y = alloca i32, align 4 + %yy = alloca i32, align 4 + %w = alloca %"int[]", align 8 + %taddr = alloca i64, align 8 + %taddr1 = alloca i64, align 8 + %varargslots = alloca [2 x %any], align 16 + %indirectarg = alloca %"any[]", align 8 + %taddr3 = alloca i64, align 8 + %varargslots4 = alloca [1 x %any], align 16 + %indirectarg6 = alloca %"any[]", align 8 + %taddr11 = alloca i64, align 8 + %taddr12 = alloca i64, align 8 + %varargslots13 = alloca [2 x %any], align 16 + %indirectarg16 = alloca %"any[]", align 8 + %taddr19 = alloca i64, align 8 + %taddr20 = alloca i64, align 8 + %varargslots21 = alloca [2 x %any], align 16 + %indirectarg24 = alloca %"any[]", align 8 + call void @llvm.memcpy.p0.p0.i32(ptr align 4 %x, ptr align 4 @.__const, i32 8, i1 false) + %0 = insertvalue %"int[]" undef, ptr %x, 0 + %1 = insertvalue %"int[]" %0, i64 0, 1 + store %"int[]" %1, ptr %z, align 8 + store i32 1, ptr %y, align 4 + store i32 -1, ptr %yy, align 4 + %2 = load i32, ptr %y, align 4 + %sext = sext i32 %2 to i64 + %gt = icmp sgt i64 %sext, 2 + %3 = call i1 @llvm.expect.i1(i1 %gt, i1 false) + br i1 %3, label %panic, label %checkok + +checkok: ; preds = %entry + %underflow = icmp slt i64 %sext, 0 + %4 = call i1 @llvm.expect.i1(i1 %underflow, i1 false) + br i1 %4, label %panic2, label %checkok7 + +checkok7: ; preds = %checkok + %5 = load i32, ptr %yy, align 4 + %sext8 = sext i32 %5 to i64 + %add = add i64 %sext8, 1 + %gt9 = icmp sgt i64 %sext, %add + %6 = call i1 @llvm.expect.i1(i1 %gt9, i1 false) + br i1 %6, label %panic10, label %checkok17 + +checkok17: ; preds = %checkok7 + %le = icmp sle i64 2, %sext8 + %7 = call i1 @llvm.expect.i1(i1 %le, i1 false) + br i1 %7, label %panic18, label %checkok25 + +checkok25: ; preds = %checkok17 + %8 = add i64 %sext8, 1 + %size = sub i64 %8, %sext + %ptroffset = getelementptr inbounds [4 x i8], ptr %x, i64 %sext + %9 = insertvalue %"int[]" undef, ptr %ptroffset, 0 + %10 = insertvalue %"int[]" %9, i64 %size, 1 + store %"int[]" %10, ptr %w, align 8 + ret void + +panic: ; preds = %entry + store i64 2, ptr %taddr, align 8 + %11 = insertvalue %any undef, ptr %taddr, 0 + %12 = insertvalue %any %11, i64 ptrtoint (ptr @"$ct.long" to i64), 1 + store i64 %sext, ptr %taddr1, align 8 + %13 = insertvalue %any undef, ptr %taddr1, 0 + %14 = insertvalue %any %13, i64 ptrtoint (ptr @"$ct.long" to i64), 1 + store %any %12, ptr %varargslots, align 16 + %ptradd = getelementptr inbounds i8, ptr %varargslots, i64 16 + store %any %14, ptr %ptradd, align 16 + %15 = insertvalue %"any[]" undef, ptr %varargslots, 0 + %"$$temp" = insertvalue %"any[]" %15, i64 2, 1 + store %"any[]" %"$$temp", ptr %indirectarg, align 8 + call void @std.core.builtin.panicf(ptr @.panic_msg, i64 61, ptr @.file, i64 17, ptr @.func, i64 4, i32 9, ptr byval(%"any[]") align 8 %indirectarg) #3 + unreachable + +panic2: ; preds = %checkok + store i64 %sext, ptr %taddr3, align 8 + %16 = insertvalue %any undef, ptr %taddr3, 0 + %17 = insertvalue %any %16, i64 ptrtoint (ptr @"$ct.long" to i64), 1 + store %any %17, ptr %varargslots4, align 16 + %18 = insertvalue %"any[]" undef, ptr %varargslots4, 0 + %"$$temp5" = insertvalue %"any[]" %18, i64 1, 1 + store %"any[]" %"$$temp5", ptr %indirectarg6, align 8 + call void @std.core.builtin.panicf(ptr @.panic_msg.1, i64 22, ptr @.file, i64 17, ptr @.func, i64 4, i32 9, ptr byval(%"any[]") align 8 %indirectarg6) #3 + unreachable + +panic10: ; preds = %checkok7 + store i64 %sext, ptr %taddr11, align 8 + %19 = insertvalue %any undef, ptr %taddr11, 0 + %20 = insertvalue %any %19, i64 ptrtoint (ptr @"$ct.long" to i64), 1 + store i64 %sext8, ptr %taddr12, align 8 + %21 = insertvalue %any undef, ptr %taddr12, 0 + %22 = insertvalue %any %21, i64 ptrtoint (ptr @"$ct.long" to i64), 1 + store %any %20, ptr %varargslots13, align 16 + %ptradd14 = getelementptr inbounds i8, ptr %varargslots13, i64 16 + store %any %22, ptr %ptradd14, align 16 + %23 = insertvalue %"any[]" undef, ptr %varargslots13, 0 + %"$$temp15" = insertvalue %"any[]" %23, i64 2, 1 + store %"any[]" %"$$temp15", ptr %indirectarg16, align 8 + call void @std.core.builtin.panicf(ptr @.panic_msg.2, i64 35, ptr @.file, i64 17, ptr @.func, i64 4, i32 9, ptr byval(%"any[]") align 8 %indirectarg16) #3 + unreachable + +panic18: ; preds = %checkok17 + store i64 %sext8, ptr %taddr19, align 8 + %24 = insertvalue %any undef, ptr %taddr19, 0 + %25 = insertvalue %any %24, i64 ptrtoint (ptr @"$ct.long" to i64), 1 + store i64 2, ptr %taddr20, align 8 + %26 = insertvalue %any undef, ptr %taddr20, 0 + %27 = insertvalue %any %26, i64 ptrtoint (ptr @"$ct.long" to i64), 1 + store %any %25, ptr %varargslots21, align 16 + %ptradd22 = getelementptr inbounds i8, ptr %varargslots21, i64 16 + store %any %27, ptr %ptradd22, align 16 + %28 = insertvalue %"any[]" undef, ptr %varargslots21, 0 + %"$$temp23" = insertvalue %"any[]" %28, i64 2, 1 + store %"any[]" %"$$temp23", ptr %indirectarg24, align 8 + call void @std.core.builtin.panicf(ptr @.panic_msg.3, i64 60, ptr @.file, i64 17, ptr @.func, i64 4, i32 9, ptr byval(%"any[]") align 8 %indirectarg24) #3 + unreachable +} + diff --git a/test/test_suite/slices/slice_negative_len.c3 b/test/test_suite/slices/slice_negative_len.c3 index 8e1baa8bf..cc15b9549 100644 --- a/test/test_suite/slices/slice_negative_len.c3 +++ b/test/test_suite/slices/slice_negative_len.c3 @@ -2,7 +2,7 @@ fn void test() { int[3] x = { 1, 2, 3}; int[] z = x[2..2]; - z = x[2..1]; // #error: greater than the end index + z = x[2..0]; // #error: greater than the end index } fn void test2() @@ -10,7 +10,7 @@ fn void test2() int[3] x = { 1, 2, 3}; int[] z = x[^2..^2]; z = x[^3..]; - z = x[^1..^2]; // #error: greater than the end index + z = x[^1..^3]; // #error: greater than the end index } fn void test3() @@ -29,7 +29,7 @@ fn void test4() fn void test5() { int[3] x = { 1, 2, 3 }; - int[] z = x[..^4]; // #error: An index may only be negative + int[] z = x[..^5]; // #error: An index may only be negative } fn void test6()