Commit 1520e084cb

Vexu <git@vexu.eu>
2020-08-23 19:19:05
stage2: implement accessing error values
1 parent e9b15ac
Changed files (4)
src-self-hosted/astgen.zig
@@ -247,7 +247,7 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr
         .Return => return ret(mod, scope, node.castTag(.Return).?),
         .If => return ifExpr(mod, scope, rl, node.castTag(.If).?),
         .While => return whileExpr(mod, scope, rl, node.castTag(.While).?),
-        .Period => return rlWrap(mod, scope, rl, try field(mod, scope, node.castTag(.Period).?)),
+        .Period => return field(mod, scope, rl, node.castTag(.Period).?),
         .Deref => return rlWrap(mod, scope, rl, try deref(mod, scope, node.castTag(.Deref).?)),
         .AddressOf => return rlWrap(mod, scope, rl, try addressOf(mod, scope, node.castTag(.AddressOf).?)),
         .FloatLiteral => return rlWrap(mod, scope, rl, try floatLiteral(mod, scope, node.castTag(.FloatLiteral).?)),
@@ -270,7 +270,8 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr
         .ErrorUnion => return rlWrap(mod, scope, rl, try typeInixOp(mod, scope, node.castTag(.ErrorUnion).?, .error_union_type)),
         .MergeErrorSets => return rlWrap(mod, scope, rl, try typeInixOp(mod, scope, node.castTag(.MergeErrorSets).?, .merge_error_sets)),
         .AnyFrameType => return rlWrap(mod, scope, rl, try anyFrameType(mod, scope, node.castTag(.AnyFrameType).?)),
-        .ErrorSetDecl => return rlWrap(mod, scope, rl, try errorSetDecl(mod, scope, node.castTag(.ErrorSetDecl).?)),
+        .ErrorSetDecl => return errorSetDecl(mod, scope, rl, node.castTag(.ErrorSetDecl).?),
+        .ErrorType => return rlWrap(mod, scope, rl, try errorType(mod, scope, node.castTag(.ErrorType).?)),
 
         .Defer => return mod.failNode(scope, node, "TODO implement astgen.expr for .Defer", .{}),
         .Catch => return mod.failNode(scope, node, "TODO implement astgen.expr for .Catch", .{}),
@@ -290,7 +291,6 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr
         .Suspend => return mod.failNode(scope, node, "TODO implement astgen.expr for .Suspend", .{}),
         .Continue => return mod.failNode(scope, node, "TODO implement astgen.expr for .Continue", .{}),
         .AnyType => return mod.failNode(scope, node, "TODO implement astgen.expr for .AnyType", .{}),
-        .ErrorType => return mod.failNode(scope, node, "TODO implement astgen.expr for .ErrorType", .{}),
         .FnProto => return mod.failNode(scope, node, "TODO implement astgen.expr for .FnProto", .{}),
         .ContainerDecl => return mod.failNode(scope, node, "TODO implement astgen.expr for .ContainerDecl", .{}),
         .Comptime => return mod.failNode(scope, node, "TODO implement astgen.expr for .Comptime", .{}),
@@ -722,13 +722,10 @@ fn unwrapOptional(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Si
     const src = tree.token_locs[node.rtoken].start;
 
     const operand = try expr(mod, scope, .ref, node.lhs);
-    const unwrapped_ptr = try addZIRUnOp(mod, scope, src, .unwrap_optional_safe, operand);
-    if (rl == .lvalue or rl == .ref) return unwrapped_ptr;
-
-    return rlWrap(mod, scope, rl, try addZIRUnOp(mod, scope, src, .deref, unwrapped_ptr));
+    return rlWrapPtr(mod, scope, rl, try addZIRUnOp(mod, scope, src, .unwrap_optional_safe, operand));
 }
 
-fn errorSetDecl(mod: *Module, scope: *Scope, node: *ast.Node.ErrorSetDecl) InnerError!*zir.Inst {
+fn errorSetDecl(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.ErrorSetDecl) InnerError!*zir.Inst {
     const tree = scope.tree();
     const src = tree.token_locs[node.error_token].start;
     const decls = node.decls();
@@ -739,7 +736,17 @@ fn errorSetDecl(mod: *Module, scope: *Scope, node: *ast.Node.ErrorSetDecl) Inner
         fields[i] = try identifierTokenString(mod, scope, tag.name_token);
     }
 
-    return addZIRInst(mod, scope, src, zir.Inst.ErrorSet, .{ .fields = fields }, .{});
+    // analyzing the error set results in a decl ref, so we might need to dereference it
+    return rlWrapPtr(mod, scope, rl, try addZIRInst(mod, scope, src, zir.Inst.ErrorSet, .{ .fields = fields }, .{}));
+}
+
+fn errorType(mod: *Module, scope: *Scope, node: *ast.Node.OneToken) InnerError!*zir.Inst {
+    const tree = scope.tree();
+    const src = tree.token_locs[node.token].start;
+    return addZIRInstConst(mod, scope, src, .{
+        .ty = Type.initTag(.type),
+        .val = Value.initTag(.anyerror_type),
+    });
 }
 
 /// Return whether the identifier names of two tokens are equal. Resolves @"" tokens without allocating.
@@ -779,16 +786,16 @@ pub fn identifierStringInst(mod: *Module, scope: *Scope, node: *ast.Node.OneToke
     return addZIRInst(mod, scope, src, zir.Inst.Str, .{ .bytes = ident_name }, .{});
 }
 
-fn field(mod: *Module, scope: *Scope, node: *ast.Node.SimpleInfixOp) InnerError!*zir.Inst {
-    // TODO introduce lvalues
+fn field(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.SimpleInfixOp) InnerError!*zir.Inst {
     const tree = scope.tree();
     const src = tree.token_locs[node.op_token].start;
 
-    const lhs = try expr(mod, scope, .none, node.lhs);
+    const lhs = try expr(mod, scope, .ref, node.lhs);
     const field_name = try identifierStringInst(mod, scope, node.rhs.castTag(.Identifier).?);
 
     const pointer = try addZIRInst(mod, scope, src, zir.Inst.FieldPtr, .{ .object_ptr = lhs, .field_name = field_name }, .{});
-    return addZIRUnOp(mod, scope, src, .deref, pointer);
+    if (rl == .ref or rl == .lvalue) return pointer;
+    return rlWrap(mod, scope, rl, try addZIRUnOp(mod, scope, src, .deref, pointer));
 }
 
 fn deref(mod: *Module, scope: *Scope, node: *ast.Node.SimpleSuffixOp) InnerError!*zir.Inst {
@@ -1274,12 +1281,7 @@ fn identifier(mod: *Module, scope: *Scope, rl: ResultLoc, ident: *ast.Node.OneTo
             .local_ptr => {
                 const local_ptr = s.cast(Scope.LocalPtr).?;
                 if (mem.eql(u8, local_ptr.name, ident_name)) {
-                    if (rl == .lvalue or rl == .ref) {
-                        return local_ptr.ptr;
-                    } else {
-                        const result = try addZIRUnOp(mod, scope, src, .deref, local_ptr.ptr);
-                        return rlWrap(mod, scope, rl, result);
-                    }
+                    return rlWrapPtr(mod, scope, rl, local_ptr.ptr);
                 }
                 s = local_ptr.parent;
             },
@@ -1289,10 +1291,7 @@ fn identifier(mod: *Module, scope: *Scope, rl: ResultLoc, ident: *ast.Node.OneTo
     }
 
     if (mod.lookupDeclName(scope, ident_name)) |decl| {
-        const result = try addZIRInst(mod, scope, src, zir.Inst.DeclValInModule, .{ .decl = decl }, .{});
-        if (rl == .lvalue or rl == .ref)
-            return result;
-        return rlWrap(mod, scope, rl, try addZIRUnOp(mod, scope, src, .deref, result));
+        return rlWrapPtr(mod, scope, rl, try addZIRInst(mod, scope, src, zir.Inst.DeclValInModule, .{ .decl = decl }, .{}));
     }
 
     return mod.failNode(scope, &ident.base, "use of undeclared identifier '{}'", .{ident_name});
@@ -1886,6 +1885,12 @@ fn rlWrapVoid(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node, resul
     return rlWrap(mod, scope, rl, void_inst);
 }
 
+fn rlWrapPtr(mod: *Module, scope: *Scope, rl: ResultLoc, ptr: *zir.Inst) InnerError!*zir.Inst {
+    if (rl == .lvalue or rl == .ref) return ptr;
+
+    return rlWrap(mod, scope, rl, try addZIRUnOp(mod, scope, ptr.src, .deref, ptr));
+}
+
 pub fn addZIRInstSpecial(
     mod: *Module,
     scope: *Scope,
src-self-hosted/Module.zig
@@ -2081,12 +2081,15 @@ fn createNewDecl(
 }
 
 /// Get error value for error tag `name`.
-pub fn getErrorValue(self: *Module, name: []const u8) !u16 {
+pub fn getErrorValue(self: *Module, name: []const u8) !std.StringHashMapUnmanaged(u16).Entry {
     const new_val = @intCast(u16, self.global_error_set.items().len);
-    if (self.global_error_set.get(name)) |some| return some;
+    if (self.global_error_set.getEntry(name)) |some| return some.*;
 
-    try self.global_error_set.put(self.gpa, try self.gpa.dupe(u8, name), new_val);
-    return new_val;
+    const duped = try self.gpa.dupe(u8, name);
+    errdefer self.gpa.free(duped);
+
+    try self.global_error_set.put(self.gpa, duped, new_val);
+    return self.global_error_set.getEntry(duped).?.*;
 }
 
 /// TODO split this into `requireRuntimeBlock` and `requireFunctionBlock` and audit callsites.
src-self-hosted/value.zig
@@ -92,6 +92,7 @@ pub const Value = extern union {
         float_128,
         enum_literal,
         error_set,
+        @"error",
 
         pub const last_no_payload_tag = Tag.bool_false;
         pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1;
@@ -244,9 +245,10 @@ pub const Value = extern union {
                 };
                 return Value{ .ptr_otherwise = &new_payload.base };
             },
+            .@"error" => return self.copyPayloadShallow(allocator, Payload.Error),
 
             // memory is managed by the declaration
-            .error_set => return self,
+            .error_set => return self.copyPayloadShallow(allocator, Payload.ErrorSet),
         }
     }
 
@@ -358,6 +360,7 @@ pub const Value = extern union {
                 }
                 return out_stream.writeAll("}");
             },
+            .@"error" => return out_stream.print("error.{}", .{val.cast(Payload.Error).?.name}),
         };
     }
 
@@ -424,6 +427,7 @@ pub const Value = extern union {
             .const_slice_u8_type => Type.initTag(.const_slice_u8),
             .enum_literal_type => Type.initTag(.enum_literal),
             .anyframe_type => Type.initTag(.@"anyframe"),
+            .error_set => @panic("TODO error set to type"),
 
             .undef,
             .zero,
@@ -449,7 +453,7 @@ pub const Value = extern union {
             .float_64,
             .float_128,
             .enum_literal,
-            .error_set,
+            .@"error",
             => unreachable,
         };
     }
@@ -517,6 +521,7 @@ pub const Value = extern union {
             .empty_array,
             .enum_literal,
             .error_set,
+            .@"error",
             => unreachable,
 
             .undef => unreachable,
@@ -597,6 +602,7 @@ pub const Value = extern union {
             .empty_array,
             .enum_literal,
             .error_set,
+            .@"error",
             => unreachable,
 
             .undef => unreachable,
@@ -677,6 +683,7 @@ pub const Value = extern union {
             .empty_array,
             .enum_literal,
             .error_set,
+            .@"error",
             => unreachable,
 
             .undef => unreachable,
@@ -784,6 +791,7 @@ pub const Value = extern union {
             .empty_array,
             .enum_literal,
             .error_set,
+            .@"error",
             => unreachable,
 
             .zero,
@@ -868,6 +876,7 @@ pub const Value = extern union {
             .empty_array,
             .enum_literal,
             .error_set,
+            .@"error",
             => unreachable,
 
             .zero,
@@ -1036,6 +1045,7 @@ pub const Value = extern union {
             .unreachable_value,
             .enum_literal,
             .error_set,
+            .@"error",
             => unreachable,
 
             .zero => false,
@@ -1107,6 +1117,7 @@ pub const Value = extern union {
             .empty_array,
             .enum_literal,
             .error_set,
+            .@"error",
             => unreachable,
 
             .zero,
@@ -1251,6 +1262,7 @@ pub const Value = extern union {
             .empty_array,
             .enum_literal,
             .error_set,
+            .@"error",
             => unreachable,
 
             .ref_val => self.cast(Payload.RefVal).?.val,
@@ -1332,6 +1344,7 @@ pub const Value = extern union {
             .unreachable_value,
             .enum_literal,
             .error_set,
+            .@"error",
             => unreachable,
 
             .empty_array => unreachable, // out of bounds array index
@@ -1430,6 +1443,7 @@ pub const Value = extern union {
             .void_value,
             .enum_literal,
             .error_set,
+            .@"error",
             => false,
 
             .undef => unreachable,
@@ -1566,6 +1580,16 @@ pub const Value = extern union {
             // TODO revisit this when we have the concept of the error tag type
             fields: std.StringHashMapUnmanaged(u16),
         };
+
+        pub const Error = struct {
+            base: Payload = .{ .tag = .@"error" },
+
+            // TODO revisit this when we have the concept of the error tag type
+            /// `name` is owned by `Module` and will be valid for the entire
+            /// duration of the compilation.
+            name: []const u8,
+            value: u16,
+        };
     };
 
     /// Big enough to fit any non-BigInt value
src-self-hosted/zir_sema.zig
@@ -740,8 +740,7 @@ fn analyzeInstAnyframeType(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) In
 }
 
 fn analyzeInstErrorSet(mod: *Module, scope: *Scope, inst: *zir.Inst.ErrorSet) InnerError!*Inst {
-    // The bytes references memory inside the ZIR module, which can get deallocated
-    // after semantic analysis is complete. We need the memory to be in the new anonymous Decl's arena.
+    // The declarations arena will store the hashmap.
     var new_decl_arena = std.heap.ArenaAllocator.init(mod.gpa);
     errdefer new_decl_arena.deinit();
 
@@ -750,8 +749,8 @@ fn analyzeInstErrorSet(mod: *Module, scope: *Scope, inst: *zir.Inst.ErrorSet) In
     try payload.fields.ensureCapacity(&new_decl_arena.allocator, inst.positionals.fields.len);
 
     for (inst.positionals.fields) |field_name| {
-        const value = try mod.getErrorValue(field_name);
-        if (payload.fields.fetchPutAssumeCapacity(field_name, value)) |prev| {
+        const entry = try mod.getErrorValue(field_name);
+        if (payload.fields.fetchPutAssumeCapacity(entry.key, entry.value)) |prev| {
             return mod.fail(scope, inst.base.src, "duplicate error: '{}'", .{field_name});
         }
     }
@@ -909,6 +908,38 @@ fn analyzeInstFieldPtr(mod: *Module, scope: *Scope, fieldptr: *zir.Inst.FieldPtr
                 );
             }
         },
+        .Type => {
+            _ = try mod.resolveConstValue(scope, object_ptr);
+            const result = try mod.analyzeDeref(scope, fieldptr.base.src, object_ptr, object_ptr.src);
+            const val = result.value().?;
+            const child_type = val.toType();
+            switch (child_type.zigTypeTag()) {
+                .ErrorSet => {
+                    // TODO resolve inferred error sets
+                    const entry = if (val.cast(Value.Payload.ErrorSet)) |payload|
+                        (payload.fields.getEntry(field_name) orelse
+                            return mod.fail(scope, fieldptr.base.src, "no error named '{}' in '{}'", .{ field_name, child_type })).*
+                    else
+                        try mod.getErrorValue(field_name);
+
+                    const error_payload = try scope.arena().create(Value.Payload.Error);
+                    error_payload.* = .{ 
+                        .name = entry.key,
+                        .value = entry.value,
+                    };
+
+                    const ref_payload = try scope.arena().create(Value.Payload.RefVal);
+                    ref_payload.* = .{ .val = Value.initPayload(&error_payload.base) };
+
+                    // TODO if this is accessing the global error set create a `error{field_name}` type
+                    return mod.constInst(scope, fieldptr.base.src, .{
+                        .ty = try mod.simplePtrType(scope, fieldptr.base.src, child_type, false, .One),
+                        .val = Value.initPayload(&ref_payload.base),
+                    });
+                },
+                else => return mod.fail(scope, fieldptr.base.src, "type '{}' does not support field access", .{child_type}),
+            }
+        },
         else => return mod.fail(scope, fieldptr.base.src, "type '{}' does not support field access", .{elem_ty}),
     }
 }