From a36f4ee290fa9f3f1515e8aa9bd2bb0f0117c505 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 23 Mar 2022 09:40:29 -0700 Subject: [PATCH] stage2: able to slice to sentinel index at comptime The runtime behavior allowed this in both stage1 and stage2, but stage1 fails with index out of bounds during comptime. This behavior makes sense to support, and comptime behavior should match runtime behavior. I implement this fix only in stage2. --- src/Sema.zig | 39 ++++++++++++++++++++++++++++++++----- test/behavior/slice.zig | 43 +++++++++++++++++++++++++++++++++++++++++ test/compile_errors.zig | 14 ++++++++++---- 3 files changed, 87 insertions(+), 9 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index fe10810d57..7e87ebbf33 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -19713,17 +19713,31 @@ fn analyzeSlice( if (!end_is_len) { const end = try sema.coerce(block, Type.usize, uncasted_end_opt, end_src); if (try sema.resolveMaybeUndefVal(block, end_src, end)) |end_val| { - if (end_val.compare(.gt, len_val, Type.usize, target)) { + const len_s_val = try Value.Tag.int_u64.create( + sema.arena, + array_ty.arrayLenIncludingSentinel(), + ); + if (end_val.compare(.gt, len_s_val, Type.usize, target)) { + const sentinel_label: []const u8 = if (array_ty.sentinel() != null) + " +1 (sentinel)" + else + ""; + return sema.fail( block, end_src, - "end index {} out of bounds for array of length {}", + "end index {} out of bounds for array of length {}{s}", .{ end_val.fmtValue(Type.usize, target), len_val.fmtValue(Type.usize, target), + sentinel_label, }, ); } + + // end_is_len is only true if we are NOT using the sentinel + // length. For sentinel-length, we don't want the type to + // contain the sentinel. if (end_val.eql(len_val, Type.usize, target)) { end_is_len = true; } @@ -19737,22 +19751,37 @@ fn analyzeSlice( const end = try sema.coerce(block, Type.usize, uncasted_end_opt, end_src); if (try sema.resolveDefinedValue(block, end_src, end)) |end_val| { if (try sema.resolveDefinedValue(block, src, ptr_or_slice)) |slice_val| { + const has_sentinel = slice_ty.sentinel() != null; var int_payload: Value.Payload.U64 = .{ .base = .{ .tag = .int_u64 }, - .data = slice_val.sliceLen(target), + .data = slice_val.sliceLen(target) + @boolToInt(has_sentinel), }; const slice_len_val = Value.initPayload(&int_payload.base); if (end_val.compare(.gt, slice_len_val, Type.usize, target)) { + const sentinel_label: []const u8 = if (has_sentinel) + " +1 (sentinel)" + else + ""; + return sema.fail( block, end_src, - "end index {} out of bounds for slice of length {}", + "end index {} out of bounds for slice of length {d}{s}", .{ end_val.fmtValue(Type.usize, target), - slice_len_val.fmtValue(Type.usize, target), + slice_val.sliceLen(target), + sentinel_label, }, ); } + + // If the slice has a sentinel, we subtract one so that + // end_is_len is only true if it equals the length WITHOUT + // the sentinel, so we don't add a sentinel type. + if (has_sentinel) { + int_payload.data -= 1; + } + if (end_val.eql(slice_len_val, Type.usize, target)) { end_is_len = true; } diff --git a/test/behavior/slice.zig b/test/behavior/slice.zig index 432524ebf7..9f3ba001cf 100644 --- a/test/behavior/slice.zig +++ b/test/behavior/slice.zig @@ -640,3 +640,46 @@ test "slice sentinel access at comptime" { try expect(slice0[slice0.len] == 0); } } + +test "slicing array with sentinel as end index" { + // Doesn't work in stage1 + if (builtin.zig_backend == .stage1) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; + + const S = struct { + fn do() !void { + var array = [_:0]u8{ 1, 2, 3, 4 }; + var slice = array[4..5]; + try expect(slice.len == 1); + try expect(slice[0] == 0); + try expect(@TypeOf(slice) == *[1]u8); + } + }; + + try S.do(); + comptime try S.do(); +} + +test "slicing slice with sentinel as end index" { + // Doesn't work in stage1 + if (builtin.zig_backend == .stage1) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; + + const S = struct { + fn do() !void { + var array = [_:0]u8{ 1, 2, 3, 4 }; + var src_slice: [:0]u8 = &array; + var slice = src_slice[4..5]; + try expect(slice.len == 1); + try expect(slice[0] == 0); + try expect(@TypeOf(slice) == *[1]u8); + } + }; + + try S.do(); + comptime try S.do(); +} diff --git a/test/compile_errors.zig b/test/compile_errors.zig index c479dd9c88..5008bdd7b8 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -26,11 +26,16 @@ pub fn addCases(ctx: *TestContext) !void { \\comptime { \\ var array = [_:0]u8{ 1, 2, 3, 4 }; \\ var src_slice: [:0]u8 = &array; - \\ var slice = src_slice[2..5]; + \\ var slice = src_slice[2..6]; \\ _ = slice; \\} \\comptime { \\ var array = [_:0]u8{ 1, 2, 3, 4 }; + \\ var slice = array[2..6]; + \\ _ = slice; + \\} + \\comptime { + \\ var array = [_]u8{ 1, 2, 3, 4 }; \\ var slice = array[2..5]; \\ _ = slice; \\} @@ -40,9 +45,10 @@ pub fn addCases(ctx: *TestContext) !void { \\ _ = slice; \\} , &[_][]const u8{ - ":4:26: error: end index 5 out of bounds for slice of length 4", - ":9:22: error: end index 5 out of bounds for array of length 4", - ":14:22: error: start index 3 is larger than end index 2", + ":4:26: error: end index 6 out of bounds for slice of length 4 +1 (sentinel)", + ":9:22: error: end index 6 out of bounds for array of length 4 +1 (sentinel)", + ":14:22: error: end index 5 out of bounds for array of length 4", + ":19:22: error: start index 3 is larger than end index 2", }); }