authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-02-17 19:54:26-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-02-18 19:17:21-07:00
log321ccbdc525ab0f5862e42378b962c10ec54e4a1
treec3f7ddbd4e78b3aea1905f32c6c314f1933722c2
parent5029e5364caccac07f2296c1f30f2f238f13864d

Sema: implement for_len

This also makes another breaking change to for loops: in order to capture a pointer of an element, one must take the address of array values. This simplifies a lot of things, and makes more sense than how it was before semantically. It is still legal to use a for loop on an array value if the corresponding element capture is byval instead of byref.

4 files changed, 136 insertions(+), 38 deletions(-)

lib/std/builtin.zig+1
...@@ -975,6 +975,7 @@ pub const panic_messages = struct {...@@ -975,6 +975,7 @@ pub const panic_messages = struct {
975 pub const unwrap_error = "attempt to unwrap error";975 pub const unwrap_error = "attempt to unwrap error";
976 pub const index_out_of_bounds = "index out of bounds";976 pub const index_out_of_bounds = "index out of bounds";
977 pub const start_index_greater_than_end = "start index is larger than end index";977 pub const start_index_greater_than_end = "start index is larger than end index";
978 pub const for_len_mismatch = "for loop over objects with non-equal lengths";
978};979};
979980
980pub noinline fn returnError(st: *StackTrace) void {981pub noinline fn returnError(st: *StackTrace) void {
src/AstGen.zig+1-2
...@@ -6381,11 +6381,10 @@ fn forExpr(...@@ -6381,11 +6381,10 @@ fn forExpr(
6381 lens[i] = range_len;6381 lens[i] = range_len;
6382 } else {6382 } else {
6383 const indexable = try expr(parent_gz, scope, .{ .rl = .none }, input);6383 const indexable = try expr(parent_gz, scope, .{ .rl = .none }, input);
6384 const indexable_len = try parent_gz.addUnNode(.indexable_ptr_len, indexable, input);
63856384
6386 any_len_checks = true;6385 any_len_checks = true;
6387 indexables[i] = indexable;6386 indexables[i] = indexable;
6388 lens[i] = indexable_len;6387 lens[i] = indexable;
6389 }6388 }
6390 }6389 }
6391 }6390 }
src/Sema.zig+121-36
...@@ -3378,26 +3378,7 @@ fn zirIndexablePtrLen(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileE...@@ -3378,26 +3378,7 @@ fn zirIndexablePtrLen(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileE
3378 else3378 else
3379 object_ty;3379 object_ty;
33803380
3381 if (!array_ty.isIndexable()) {3381 try checkIndexable(sema, block, src, array_ty);
3382 const msg = msg: {
3383 const msg = try sema.errMsg(
3384 block,
3385 src,
3386 "type '{}' does not support indexing",
3387 .{array_ty.fmt(sema.mod)},
3388 );
3389 errdefer msg.destroy(sema.gpa);
3390 try sema.errNote(
3391 block,
3392 src,
3393 msg,
3394 "for loop operand must be an array, slice, tuple, or vector",
3395 .{},
3396 );
3397 break :msg msg;
3398 };
3399 return sema.failWithOwnedErrorMsg(msg);
3400 }
34013382
3402 return sema.fieldVal(block, src, object, "len", src);3383 return sema.fieldVal(block, src, object, "len", src);
3403}3384}
...@@ -3921,13 +3902,70 @@ fn zirFieldBasePtr(...@@ -3921,13 +3902,70 @@ fn zirFieldBasePtr(
3921}3902}
39223903
3923fn zirForLen(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {3904fn zirForLen(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
3905 const gpa = sema.gpa;
3924 const inst_data = sema.code.instructions.items(.data)[inst].pl_node;3906 const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
3925 const extra = sema.code.extraData(Zir.Inst.MultiOp, inst_data.payload_index);3907 const extra = sema.code.extraData(Zir.Inst.MultiOp, inst_data.payload_index);
3926 const args = sema.code.refSlice(extra.end, extra.data.operands_len);3908 const args = sema.code.refSlice(extra.end, extra.data.operands_len);
3927 const src = inst_data.src();3909 const src = inst_data.src();
39283910
3929 _ = args;3911 var len: Air.Inst.Ref = .none;
3930 return sema.fail(block, src, "TODO implement zirForCheckLens", .{});3912 var len_val: ?Value = null;
3913 var len_idx: usize = undefined;
3914 var any_runtime = false;
3915
3916 const runtime_arg_lens = try gpa.alloc(Air.Inst.Ref, args.len);
3917 defer gpa.free(runtime_arg_lens);
3918
3919 // First pass to look for comptime values.
3920 for (args) |zir_arg, i| {
3921 runtime_arg_lens[i] = .none;
3922 if (zir_arg == .none) continue;
3923 const object = try sema.resolveInst(zir_arg);
3924 const object_ty = sema.typeOf(object);
3925 // Each arg could be an indexable, or a range, in which case the length
3926 // is passed directly as an integer.
3927 const arg_len = if (object_ty.zigTypeTag() == .Int) object else l: {
3928 try checkIndexable(sema, block, src, object_ty);
3929 if (!object_ty.indexableHasLen()) continue;
3930
3931 break :l try sema.fieldVal(block, src, object, "len", src);
3932 };
3933 if (len == .none) {
3934 len = arg_len;
3935 len_idx = i;
3936 }
3937 if (try sema.resolveDefinedValue(block, src, arg_len)) |arg_val| {
3938 if (len_val) |v| {
3939 if (!(try sema.valuesEqual(arg_val, v, Type.usize))) {
3940 // TODO error notes for each arg stating the differing values
3941 return sema.fail(block, src, "non-matching for loop lengths", .{});
3942 }
3943 } else {
3944 len = arg_len;
3945 len_val = arg_val;
3946 len_idx = i;
3947 }
3948 continue;
3949 }
3950 runtime_arg_lens[i] = arg_len;
3951 any_runtime = true;
3952 }
3953
3954 if (len == .none) {
3955 return sema.fail(block, src, "non-obvious infinite loop", .{});
3956 }
3957
3958 // Now for the runtime checks.
3959 if (any_runtime and block.wantSafety()) {
3960 for (runtime_arg_lens) |arg_len, i| {
3961 if (arg_len == .none) continue;
3962 if (i == len_idx) continue;
3963 const ok = try block.addBinOp(.cmp_eq, len, arg_len);
3964 try sema.addSafetyCheck(block, ok, .for_len_mismatch);
3965 }
3966 }
3967
3968 return len;
3931}3969}
39323970
3933fn validateArrayInitTy(3971fn validateArrayInitTy(
...@@ -9655,7 +9693,7 @@ fn zirElemPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air...@@ -9655,7 +9693,7 @@ fn zirElemPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air
9655 const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;9693 const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
9656 const array_ptr = try sema.resolveInst(extra.lhs);9694 const array_ptr = try sema.resolveInst(extra.lhs);
9657 const elem_index = try sema.resolveInst(extra.rhs);9695 const elem_index = try sema.resolveInst(extra.rhs);
9658 return sema.elemPtr(block, src, array_ptr, elem_index, src, false);9696 return sema.elemPtrOneLayerOnly(block, src, array_ptr, elem_index, src, false);
9659}9697}
96609698
9661fn zirElemPtrNode(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {9699fn zirElemPtrNode(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
...@@ -22687,6 +22725,7 @@ pub const PanicId = enum {...@@ -22687,6 +22725,7 @@ pub const PanicId = enum {
22687 unwrap_error,22725 unwrap_error,
22688 index_out_of_bounds,22726 index_out_of_bounds,
22689 start_index_greater_than_end,22727 start_index_greater_than_end,
22728 for_len_mismatch,
22690};22729};
2269122730
22692fn addSafetyCheck(22731fn addSafetyCheck(
...@@ -24076,21 +24115,46 @@ fn elemPtr(...@@ -24076,21 +24115,46 @@ fn elemPtr(
24076 .Pointer => indexable_ptr_ty.elemType(),24115 .Pointer => indexable_ptr_ty.elemType(),
24077 else => return sema.fail(block, indexable_ptr_src, "expected pointer, found '{}'", .{indexable_ptr_ty.fmt(sema.mod)}),24116 else => return sema.fail(block, indexable_ptr_src, "expected pointer, found '{}'", .{indexable_ptr_ty.fmt(sema.mod)}),
24078 };24117 };
24118 switch (indexable_ty.zigTypeTag()) {
24119 .Array, .Vector => return sema.elemPtrArray(block, src, indexable_ptr_src, indexable_ptr, elem_index_src, elem_index, init),
24120 .Struct => {
24121 // Tuple field access.
24122 const index_val = try sema.resolveConstValue(block, elem_index_src, elem_index, "tuple field access index must be comptime-known");
24123 const index = @intCast(u32, index_val.toUnsignedInt(target));
24124 return sema.tupleFieldPtr(block, src, indexable_ptr, elem_index_src, index, init);
24125 },
24126 else => {
24127 const indexable = try sema.analyzeLoad(block, indexable_ptr_src, indexable_ptr, indexable_ptr_src);
24128 return elemPtrOneLayerOnly(sema, block, src, indexable, elem_index, elem_index_src, init);
24129 },
24130 }
24131}
24132
24133fn elemPtrOneLayerOnly(
24134 sema: *Sema,
24135 block: *Block,
24136 src: LazySrcLoc,
24137 indexable: Air.Inst.Ref,
24138 elem_index: Air.Inst.Ref,
24139 elem_index_src: LazySrcLoc,
24140 init: bool,
24141) CompileError!Air.Inst.Ref {
24142 const indexable_src = src; // TODO better source location
24143 const indexable_ty = sema.typeOf(indexable);
24079 if (!indexable_ty.isIndexable()) {24144 if (!indexable_ty.isIndexable()) {
24080 return sema.fail(block, src, "element access of non-indexable type '{}'", .{indexable_ty.fmt(sema.mod)});24145 return sema.fail(block, src, "element access of non-indexable type '{}'", .{indexable_ty.fmt(sema.mod)});
24081 }24146 }
24147 const target = sema.mod.getTarget();
2408224148
24083 switch (indexable_ty.zigTypeTag()) {24149 switch (indexable_ty.zigTypeTag()) {
24084 .Pointer => {24150 .Pointer => {
24085 // In all below cases, we have to deref the ptr operand to get the actual indexable pointer.
24086 const indexable = try sema.analyzeLoad(block, indexable_ptr_src, indexable_ptr, indexable_ptr_src);
24087 switch (indexable_ty.ptrSize()) {24151 switch (indexable_ty.ptrSize()) {
24088 .Slice => return sema.elemPtrSlice(block, src, indexable_ptr_src, indexable, elem_index_src, elem_index),24152 .Slice => return sema.elemPtrSlice(block, src, indexable_src, indexable, elem_index_src, elem_index),
24089 .Many, .C => {24153 .Many, .C => {
24090 const maybe_ptr_val = try sema.resolveDefinedValue(block, indexable_ptr_src, indexable);24154 const maybe_ptr_val = try sema.resolveDefinedValue(block, indexable_src, indexable);
24091 const maybe_index_val = try sema.resolveDefinedValue(block, elem_index_src, elem_index);24155 const maybe_index_val = try sema.resolveDefinedValue(block, elem_index_src, elem_index);
24092 const runtime_src = rs: {24156 const runtime_src = rs: {
24093 const ptr_val = maybe_ptr_val orelse break :rs indexable_ptr_src;24157 const ptr_val = maybe_ptr_val orelse break :rs indexable_src;
24094 const index_val = maybe_index_val orelse break :rs elem_index_src;24158 const index_val = maybe_index_val orelse break :rs elem_index_src;
24095 const index = @intCast(usize, index_val.toUnsignedInt(target));24159 const index = @intCast(usize, index_val.toUnsignedInt(target));
24096 const elem_ptr = try ptr_val.elemPtr(indexable_ty, sema.arena, index, sema.mod);24160 const elem_ptr = try ptr_val.elemPtr(indexable_ty, sema.arena, index, sema.mod);
...@@ -24104,18 +24168,16 @@ fn elemPtr(...@@ -24104,18 +24168,16 @@ fn elemPtr(
24104 },24168 },
24105 .One => {24169 .One => {
24106 assert(indexable_ty.childType().zigTypeTag() == .Array); // Guaranteed by isIndexable24170 assert(indexable_ty.childType().zigTypeTag() == .Array); // Guaranteed by isIndexable
24107 return sema.elemPtrArray(block, src, indexable_ptr_src, indexable, elem_index_src, elem_index, init);24171 return sema.elemPtrArray(block, src, indexable_src, indexable, elem_index_src, elem_index, init);
24108 },24172 },
24109 }24173 }
24110 },24174 },
24111 .Array, .Vector => return sema.elemPtrArray(block, src, indexable_ptr_src, indexable_ptr, elem_index_src, elem_index, init),24175 else => {
24112 .Struct => {24176 // TODO add note pointing at corresponding for loop input and suggest using '&'
24113 // Tuple field access.24177 return sema.fail(block, indexable_src, "pointer capture of non pointer type '{}'", .{
24114 const index_val = try sema.resolveConstValue(block, elem_index_src, elem_index, "tuple field access index must be comptime-known");24178 indexable_ty.fmt(sema.mod),
24115 const index = @intCast(u32, index_val.toUnsignedInt(target));24179 });
24116 return sema.tupleFieldPtr(block, src, indexable_ptr, elem_index_src, index, init);
24117 },24180 },
24118 else => unreachable,
24119 }24181 }
24120}24182}
2412124183
...@@ -30202,6 +30264,29 @@ fn checkBackingIntType(sema: *Sema, block: *Block, src: LazySrcLoc, backing_int_...@@ -30202,6 +30264,29 @@ fn checkBackingIntType(sema: *Sema, block: *Block, src: LazySrcLoc, backing_int_
30202 }30264 }
30203}30265}
3020430266
30267fn checkIndexable(sema: *Sema, block: *Block, src: LazySrcLoc, array_ty: Type) !void {
30268 if (!array_ty.isIndexable()) {
30269 const msg = msg: {
30270 const msg = try sema.errMsg(
30271 block,
30272 src,
30273 "type '{}' does not support indexing",
30274 .{array_ty.fmt(sema.mod)},
30275 );
30276 errdefer msg.destroy(sema.gpa);
30277 try sema.errNote(
30278 block,
30279 src,
30280 msg,
30281 "for loop operand must be an array, slice, tuple, or vector",
30282 .{},
30283 );
30284 break :msg msg;
30285 };
30286 return sema.failWithOwnedErrorMsg(msg);
30287 }
30288}
30289
30205fn resolveUnionLayout(sema: *Sema, ty: Type) CompileError!void {30290fn resolveUnionLayout(sema: *Sema, ty: Type) CompileError!void {
30206 const resolved_ty = try sema.resolveTypeFields(ty);30291 const resolved_ty = try sema.resolveTypeFields(ty);
30207 const union_obj = resolved_ty.cast(Type.Payload.Union).?.data;30292 const union_obj = resolved_ty.cast(Type.Payload.Union).?.data;
src/type.zig+13
...@@ -5326,6 +5326,19 @@ pub const Type = extern union {...@@ -5326,6 +5326,19 @@ pub const Type = extern union {
5326 };5326 };
5327 }5327 }
53285328
5329 pub fn indexableHasLen(ty: Type) bool {
5330 return switch (ty.zigTypeTag()) {
5331 .Array, .Vector => true,
5332 .Pointer => switch (ty.ptrSize()) {
5333 .Many, .C => false,
5334 .Slice => true,
5335 .One => ty.elemType().zigTypeTag() == .Array,
5336 },
5337 .Struct => ty.isTuple(),
5338 else => false,
5339 };
5340 }
5341
5329 /// Returns null if the type has no namespace.5342 /// Returns null if the type has no namespace.
5330 pub fn getNamespace(self: Type) ?*Module.Namespace {5343 pub fn getNamespace(self: Type) ?*Module.Namespace {
5331 return switch (self.tag()) {5344 return switch (self.tag()) {