Commit 321ccbdc52

Andrew Kelley <andrew@ziglang.org>
2023-02-18 03:54:26
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.
1 parent 5029e53
Changed files (4)
lib/std/builtin.zig
@@ -975,6 +975,7 @@ pub const panic_messages = struct {
     pub const unwrap_error = "attempt to unwrap error";
     pub const index_out_of_bounds = "index out of bounds";
     pub const start_index_greater_than_end = "start index is larger than end index";
+    pub const for_len_mismatch = "for loop over objects with non-equal lengths";
 };
 
 pub noinline fn returnError(st: *StackTrace) void {
src/AstGen.zig
@@ -6381,11 +6381,10 @@ fn forExpr(
                 lens[i] = range_len;
             } else {
                 const indexable = try expr(parent_gz, scope, .{ .rl = .none }, input);
-                const indexable_len = try parent_gz.addUnNode(.indexable_ptr_len, indexable, input);
 
                 any_len_checks = true;
                 indexables[i] = indexable;
-                lens[i] = indexable_len;
+                lens[i] = indexable;
             }
         }
     }
src/Sema.zig
@@ -3378,26 +3378,7 @@ fn zirIndexablePtrLen(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileE
     else
         object_ty;
 
-    if (!array_ty.isIndexable()) {
-        const msg = msg: {
-            const msg = try sema.errMsg(
-                block,
-                src,
-                "type '{}' does not support indexing",
-                .{array_ty.fmt(sema.mod)},
-            );
-            errdefer msg.destroy(sema.gpa);
-            try sema.errNote(
-                block,
-                src,
-                msg,
-                "for loop operand must be an array, slice, tuple, or vector",
-                .{},
-            );
-            break :msg msg;
-        };
-        return sema.failWithOwnedErrorMsg(msg);
-    }
+    try checkIndexable(sema, block, src, array_ty);
 
     return sema.fieldVal(block, src, object, "len", src);
 }
@@ -3921,13 +3902,70 @@ fn zirFieldBasePtr(
 }
 
 fn zirForLen(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
+    const gpa = sema.gpa;
     const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
     const extra = sema.code.extraData(Zir.Inst.MultiOp, inst_data.payload_index);
     const args = sema.code.refSlice(extra.end, extra.data.operands_len);
     const src = inst_data.src();
 
-    _ = args;
-    return sema.fail(block, src, "TODO implement zirForCheckLens", .{});
+    var len: Air.Inst.Ref = .none;
+    var len_val: ?Value = null;
+    var len_idx: usize = undefined;
+    var any_runtime = false;
+
+    const runtime_arg_lens = try gpa.alloc(Air.Inst.Ref, args.len);
+    defer gpa.free(runtime_arg_lens);
+
+    // First pass to look for comptime values.
+    for (args) |zir_arg, i| {
+        runtime_arg_lens[i] = .none;
+        if (zir_arg == .none) continue;
+        const object = try sema.resolveInst(zir_arg);
+        const object_ty = sema.typeOf(object);
+        // Each arg could be an indexable, or a range, in which case the length
+        // is passed directly as an integer.
+        const arg_len = if (object_ty.zigTypeTag() == .Int) object else l: {
+            try checkIndexable(sema, block, src, object_ty);
+            if (!object_ty.indexableHasLen()) continue;
+
+            break :l try sema.fieldVal(block, src, object, "len", src);
+        };
+        if (len == .none) {
+            len = arg_len;
+            len_idx = i;
+        }
+        if (try sema.resolveDefinedValue(block, src, arg_len)) |arg_val| {
+            if (len_val) |v| {
+                if (!(try sema.valuesEqual(arg_val, v, Type.usize))) {
+                    // TODO error notes for each arg stating the differing values
+                    return sema.fail(block, src, "non-matching for loop lengths", .{});
+                }
+            } else {
+                len = arg_len;
+                len_val = arg_val;
+                len_idx = i;
+            }
+            continue;
+        }
+        runtime_arg_lens[i] = arg_len;
+        any_runtime = true;
+    }
+
+    if (len == .none) {
+        return sema.fail(block, src, "non-obvious infinite loop", .{});
+    }
+
+    // Now for the runtime checks.
+    if (any_runtime and block.wantSafety()) {
+        for (runtime_arg_lens) |arg_len, i| {
+            if (arg_len == .none) continue;
+            if (i == len_idx) continue;
+            const ok = try block.addBinOp(.cmp_eq, len, arg_len);
+            try sema.addSafetyCheck(block, ok, .for_len_mismatch);
+        }
+    }
+
+    return len;
 }
 
 fn validateArrayInitTy(
@@ -9655,7 +9693,7 @@ fn zirElemPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air
     const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
     const array_ptr = try sema.resolveInst(extra.lhs);
     const elem_index = try sema.resolveInst(extra.rhs);
-    return sema.elemPtr(block, src, array_ptr, elem_index, src, false);
+    return sema.elemPtrOneLayerOnly(block, src, array_ptr, elem_index, src, false);
 }
 
 fn zirElemPtrNode(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
@@ -22687,6 +22725,7 @@ pub const PanicId = enum {
     unwrap_error,
     index_out_of_bounds,
     start_index_greater_than_end,
+    for_len_mismatch,
 };
 
 fn addSafetyCheck(
@@ -24076,21 +24115,46 @@ fn elemPtr(
         .Pointer => indexable_ptr_ty.elemType(),
         else => return sema.fail(block, indexable_ptr_src, "expected pointer, found '{}'", .{indexable_ptr_ty.fmt(sema.mod)}),
     };
+    switch (indexable_ty.zigTypeTag()) {
+        .Array, .Vector => return sema.elemPtrArray(block, src, indexable_ptr_src, indexable_ptr, elem_index_src, elem_index, init),
+        .Struct => {
+            // Tuple field access.
+            const index_val = try sema.resolveConstValue(block, elem_index_src, elem_index, "tuple field access index must be comptime-known");
+            const index = @intCast(u32, index_val.toUnsignedInt(target));
+            return sema.tupleFieldPtr(block, src, indexable_ptr, elem_index_src, index, init);
+        },
+        else => {
+            const indexable = try sema.analyzeLoad(block, indexable_ptr_src, indexable_ptr, indexable_ptr_src);
+            return elemPtrOneLayerOnly(sema, block, src, indexable, elem_index, elem_index_src, init);
+        },
+    }
+}
+
+fn elemPtrOneLayerOnly(
+    sema: *Sema,
+    block: *Block,
+    src: LazySrcLoc,
+    indexable: Air.Inst.Ref,
+    elem_index: Air.Inst.Ref,
+    elem_index_src: LazySrcLoc,
+    init: bool,
+) CompileError!Air.Inst.Ref {
+    const indexable_src = src; // TODO better source location
+    const indexable_ty = sema.typeOf(indexable);
     if (!indexable_ty.isIndexable()) {
         return sema.fail(block, src, "element access of non-indexable type '{}'", .{indexable_ty.fmt(sema.mod)});
     }
+    const target = sema.mod.getTarget();
 
     switch (indexable_ty.zigTypeTag()) {
         .Pointer => {
-            // In all below cases, we have to deref the ptr operand to get the actual indexable pointer.
-            const indexable = try sema.analyzeLoad(block, indexable_ptr_src, indexable_ptr, indexable_ptr_src);
             switch (indexable_ty.ptrSize()) {
-                .Slice => return sema.elemPtrSlice(block, src, indexable_ptr_src, indexable, elem_index_src, elem_index),
+                .Slice => return sema.elemPtrSlice(block, src, indexable_src, indexable, elem_index_src, elem_index),
                 .Many, .C => {
-                    const maybe_ptr_val = try sema.resolveDefinedValue(block, indexable_ptr_src, indexable);
+                    const maybe_ptr_val = try sema.resolveDefinedValue(block, indexable_src, indexable);
                     const maybe_index_val = try sema.resolveDefinedValue(block, elem_index_src, elem_index);
                     const runtime_src = rs: {
-                        const ptr_val = maybe_ptr_val orelse break :rs indexable_ptr_src;
+                        const ptr_val = maybe_ptr_val orelse break :rs indexable_src;
                         const index_val = maybe_index_val orelse break :rs elem_index_src;
                         const index = @intCast(usize, index_val.toUnsignedInt(target));
                         const elem_ptr = try ptr_val.elemPtr(indexable_ty, sema.arena, index, sema.mod);
@@ -24104,18 +24168,16 @@ fn elemPtr(
                 },
                 .One => {
                     assert(indexable_ty.childType().zigTypeTag() == .Array); // Guaranteed by isIndexable
-                    return sema.elemPtrArray(block, src, indexable_ptr_src, indexable, elem_index_src, elem_index, init);
+                    return sema.elemPtrArray(block, src, indexable_src, indexable, elem_index_src, elem_index, init);
                 },
             }
         },
-        .Array, .Vector => return sema.elemPtrArray(block, src, indexable_ptr_src, indexable_ptr, elem_index_src, elem_index, init),
-        .Struct => {
-            // Tuple field access.
-            const index_val = try sema.resolveConstValue(block, elem_index_src, elem_index, "tuple field access index must be comptime-known");
-            const index = @intCast(u32, index_val.toUnsignedInt(target));
-            return sema.tupleFieldPtr(block, src, indexable_ptr, elem_index_src, index, init);
+        else => {
+            // TODO add note pointing at corresponding for loop input and suggest using '&'
+            return sema.fail(block, indexable_src, "pointer capture of non pointer type '{}'", .{
+                indexable_ty.fmt(sema.mod),
+            });
         },
-        else => unreachable,
     }
 }
 
@@ -30202,6 +30264,29 @@ fn checkBackingIntType(sema: *Sema, block: *Block, src: LazySrcLoc, backing_int_
     }
 }
 
+fn checkIndexable(sema: *Sema, block: *Block, src: LazySrcLoc, array_ty: Type) !void {
+    if (!array_ty.isIndexable()) {
+        const msg = msg: {
+            const msg = try sema.errMsg(
+                block,
+                src,
+                "type '{}' does not support indexing",
+                .{array_ty.fmt(sema.mod)},
+            );
+            errdefer msg.destroy(sema.gpa);
+            try sema.errNote(
+                block,
+                src,
+                msg,
+                "for loop operand must be an array, slice, tuple, or vector",
+                .{},
+            );
+            break :msg msg;
+        };
+        return sema.failWithOwnedErrorMsg(msg);
+    }
+}
+
 fn resolveUnionLayout(sema: *Sema, ty: Type) CompileError!void {
     const resolved_ty = try sema.resolveTypeFields(ty);
     const union_obj = resolved_ty.cast(Type.Payload.Union).?.data;
src/type.zig
@@ -5326,6 +5326,19 @@ pub const Type = extern union {
         };
     }
 
+    pub fn indexableHasLen(ty: Type) bool {
+        return switch (ty.zigTypeTag()) {
+            .Array, .Vector => true,
+            .Pointer => switch (ty.ptrSize()) {
+                .Many, .C => false,
+                .Slice => true,
+                .One => ty.elemType().zigTypeTag() == .Array,
+            },
+            .Struct => ty.isTuple(),
+            else => false,
+        };
+    }
+
     /// Returns null if the type has no namespace.
     pub fn getNamespace(self: Type) ?*Module.Namespace {
         return switch (self.tag()) {