Commit 5c18826240

Andrew Kelley <superjoe30@gmail.com>
2016-01-24 09:34:48
introduce the error keyword and type
See #23
1 parent 37aae53
doc/vim/syntax/zig.vim
@@ -15,7 +15,7 @@ syn keyword zigRepeat while for
 
 syn keyword zigConstant null undefined
 syn keyword zigKeyword fn import
-syn keyword zigType bool i8 u8 i16 u16 i32 u32 i64 u64 isize usize f32 f64 f128 void unreachable type
+syn keyword zigType bool i8 u8 i16 u16 i32 u32 i64 u64 isize usize f32 f64 f128 void unreachable type error
 
 syn keyword zigBoolean true false
 
doc/langref.md
@@ -7,7 +7,7 @@ Root : many(TopLevelDecl) "EOF"
 
 TopLevelDecl : FnDef | ExternBlock | RootExportDecl | Import | ContainerDecl | VariableDeclaration | ErrorValueDecl
 
-ErrorValueDecl : option(FnVisibleMod) "%." "Symbol"
+ErrorValueDecl : option(FnVisibleMod) "error" "Symbol"
 
 VariableDeclaration : option(FnVisibleMod) ("var" | "const") "Symbol" ("=" Expression | ":" PrefixOpExpression option("=" Expression))
 
@@ -133,9 +133,9 @@ ContainerInitBody : list(StructLiteralField, ",") | list(Expression, ",")
 
 StructLiteralField : "." "Symbol" "=" Expression
 
-PrefixOp : "!" | "-" | "~" | "*" | ("&" option("const")) | "?"
+PrefixOp : "!" | "-" | "~" | "*" | ("&" option("const")) | "?" | "%"
 
-PrimaryExpression : "Number" | "String" | "CharLiteral" | KeywordLiteral | GroupedExpression | GotoExpression | BlockExpression | "Symbol" | ("@" "Symbol" FnCallExpression) | ArrayType | AsmExpression | ("%." "Symbol")
+PrimaryExpression : "Number" | "String" | "CharLiteral" | KeywordLiteral | GroupedExpression | GotoExpression | BlockExpression | "Symbol" | ("@" "Symbol" FnCallExpression) | ArrayType | AsmExpression | ("error" "." "Symbol")
 
 ArrayType : "[" option(Expression) "]" option("const") PrefixOpExpression
 
@@ -143,7 +143,7 @@ GotoExpression: "goto" "Symbol"
 
 GroupedExpression : "(" Expression ")"
 
-KeywordLiteral : "true" | "false" | "null" | "break" | "continue" | "undefined"
+KeywordLiteral : "true" | "false" | "null" | "break" | "continue" | "undefined" | "error"
 ```
 
 ## Operator Precedence
example/cat/main.zig
@@ -10,6 +10,7 @@ import "std.zig";
 // * %% binary operator
 // * %% prefix operator
 // * cast err type to string
+// * string equality
 
 pub fn main(args: [][]u8) %void => {
     const exe = args[0];
@@ -39,7 +40,7 @@ pub fn main(args: [][]u8) %void => {
 
 fn usage(exe: []u8) %void => {
     %%stderr.print("Usage: {} [FILE]...\n", exe);
-    return %.Invalid;
+    return error.Invalid;
 }
 
 fn cat_stream(is: InputStream) %void => {
example/guess_number/main.zig
@@ -3,8 +3,8 @@ export executable "guess_number";
 import "std.zig";
 import "rand.zig";
 
-%.GetRandomFail;
-%.ReadInputFail;
+error GetRandomFail;
+error ReadInputFail;
 
 pub fn main(args: [][]u8) %void => {
     print_str("Welcome to the Guess Number Game in Zig.\n");
@@ -15,7 +15,7 @@ pub fn main(args: [][]u8) %void => {
     if (err != @sizeof(u32)) {
         // TODO full error message
         fprint_str(stderr_fileno, "unable to get random bytes\n");
-        return %.GetRandomFail;
+        return error.GetRandomFail;
     }
 
     var rand : Rand;
@@ -31,7 +31,7 @@ pub fn main(args: [][]u8) %void => {
         if (readline(line_buf, &line_len) || line_len == line_buf.len) {
             // TODO full error message
             fprint_str(stderr_fileno, "unable to read input\n");
-            return %.ReadInputFail;
+            return error.ReadInputFail;
         }
 
         var guess : u64;
src/all_types.hpp
@@ -133,7 +133,6 @@ enum NodeType {
     NodeTypeNumberLiteral,
     NodeTypeStringLiteral,
     NodeTypeCharLiteral,
-    NodeTypeErrorLiteral,
     NodeTypeSymbol,
     NodeTypePrefixOpExpr,
     NodeTypeFnCallExpr,
@@ -161,6 +160,7 @@ enum NodeType {
     NodeTypeContainerInitExpr,
     NodeTypeStructValueField,
     NodeTypeArrayType,
+    NodeTypeErrorType,
 };
 
 struct AstNodeRoot {
@@ -315,6 +315,7 @@ enum CastOp {
     CastOpToUnknownSizeArray,
     CastOpMaybeWrap,
     CastOpErrorWrap,
+    CastOpPureErrorWrap,
     CastOpPointerReinterpret,
     CastOpErrToInt,
 };
@@ -584,13 +585,6 @@ struct AstNodeNumberLiteral {
     Expr resolved_expr;
 };
 
-struct AstNodeErrorLiteral {
-    Buf symbol;
-
-    // populated by semantic analyzer
-    Expr resolved_expr;
-};
-
 struct AstNodeStructValueField {
     Buf name;
     AstNode *expr;
@@ -663,6 +657,11 @@ struct AstNodeArrayType {
     Expr resolved_expr;
 };
 
+struct AstNodeErrorType {
+    // populated by semantic analyzer
+    Expr resolved_expr;
+};
+
 struct AstNode {
     enum NodeType type;
     int line;
@@ -705,7 +704,6 @@ struct AstNode {
         AstNodeStringLiteral string_literal;
         AstNodeCharLiteral char_literal;
         AstNodeNumberLiteral number_literal;
-        AstNodeErrorLiteral error_literal;
         AstNodeContainerInitExpr container_init_expr;
         AstNodeStructValueField struct_val_field;
         AstNodeNullLiteral null_literal;
@@ -715,6 +713,7 @@ struct AstNode {
         AstNodeBreakExpr break_expr;
         AstNodeContinueExpr continue_expr;
         AstNodeArrayType array_type;
+        AstNodeErrorType error_type;
     } data;
 };
 
@@ -820,7 +819,8 @@ enum TypeTableEntryId {
     TypeTableEntryIdNumLitInt,
     TypeTableEntryIdUndefLit,
     TypeTableEntryIdMaybe,
-    TypeTableEntryIdError,
+    TypeTableEntryIdErrorUnion,
+    TypeTableEntryIdPureError,
     TypeTableEntryIdEnum,
     TypeTableEntryIdFn,
 };
@@ -961,6 +961,7 @@ struct CodeGen {
         TypeTableEntry *entry_num_lit_int;
         TypeTableEntry *entry_num_lit_float;
         TypeTableEntry *entry_undef;
+        TypeTableEntry *entry_pure_error;
     } builtin_types;
 
     LLVMTargetDataRef target_data_ref;
src/analyze.cpp
@@ -19,6 +19,8 @@ static void resolve_struct_type(CodeGen *g, ImportTableEntry *import, TypeTableE
 static TypeTableEntry *unwrapped_node_type(AstNode *node);
 static TypeTableEntry *analyze_cast_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
         AstNode *node);
+static TypeTableEntry *analyze_error_literal_expr(CodeGen *g, ImportTableEntry *import,
+        BlockContext *context, AstNode *node, Buf *err_name);
 
 static AstNode *first_executing_node(AstNode *node) {
     switch (node->type) {
@@ -47,7 +49,6 @@ static AstNode *first_executing_node(AstNode *node) {
         case NodeTypeVariableDeclaration:
         case NodeTypeErrorValueDecl:
         case NodeTypeNumberLiteral:
-        case NodeTypeErrorLiteral:
         case NodeTypeStringLiteral:
         case NodeTypeCharLiteral:
         case NodeTypeSymbol:
@@ -71,6 +72,7 @@ static AstNode *first_executing_node(AstNode *node) {
         case NodeTypeSwitchExpr:
         case NodeTypeSwitchProng:
         case NodeTypeArrayType:
+        case NodeTypeErrorType:
         case NodeTypeContainerInitExpr:
             return node;
     }
@@ -110,7 +112,8 @@ TypeTableEntry *new_type_table_entry(TypeTableEntryId id) {
         case TypeTableEntryIdNumLitInt:
         case TypeTableEntryIdMaybe:
         case TypeTableEntryIdFn:
-        case TypeTableEntryIdError:
+        case TypeTableEntryIdErrorUnion:
+        case TypeTableEntryIdPureError:
         case TypeTableEntryIdUndefLit:
             // nothing to init
             break;
@@ -200,7 +203,7 @@ static TypeTableEntry *get_maybe_type(CodeGen *g, TypeTableEntry *child_type) {
                     "val", di_file, line, child_type->size_in_bits, child_type->align_in_bits, 0, 0,
                     child_type->di_type),
             LLVMZigCreateDebugMemberType(g->dbuilder, LLVMZigTypeToScope(entry->di_type),
-                    "maybe", di_file, line, 8, 8, 8, 0,
+                    "maybe", di_file, line, 8, 8, child_type->size_in_bits, 0,
                     child_type->di_type),
         };
         LLVMZigDIType *replacement_di_type = LLVMZigCreateDebugStructType(g->dbuilder,
@@ -223,7 +226,7 @@ static TypeTableEntry *get_error_type(CodeGen *g, TypeTableEntry *child_type) {
     if (child_type->error_parent) {
         return child_type->error_parent;
     } else {
-        TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdError);
+        TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdErrorUnion);
         assert(child_type->type_ref);
         assert(child_type->di_type);
 
@@ -239,7 +242,38 @@ static TypeTableEntry *get_error_type(CodeGen *g, TypeTableEntry *child_type) {
             entry->di_type = g->err_tag_type->di_type;
 
         } else {
-            zig_panic("TODO get_error_type non-void");
+            LLVMTypeRef elem_types[] = {
+                g->err_tag_type->type_ref,
+                child_type->type_ref,
+            };
+            entry->type_ref = LLVMStructType(elem_types, 2, false);
+            entry->size_in_bits = g->err_tag_type->size_in_bits + child_type->size_in_bits;
+            entry->align_in_bits = g->err_tag_type->align_in_bits;
+
+            LLVMZigDIScope *compile_unit_scope = LLVMZigCompileUnitToScope(g->compile_unit);
+            LLVMZigDIFile *di_file = nullptr;
+            unsigned line = 0;
+            entry->di_type = LLVMZigCreateReplaceableCompositeType(g->dbuilder,
+                LLVMZigTag_DW_structure_type(), buf_ptr(&entry->name),
+                compile_unit_scope, di_file, line);
+
+            LLVMZigDIType *di_element_types[] = {
+                LLVMZigCreateDebugMemberType(g->dbuilder, LLVMZigTypeToScope(entry->di_type),
+                        "tag", di_file, line, g->err_tag_type->size_in_bits, g->err_tag_type->align_in_bits,
+                        0, 0, child_type->di_type),
+                LLVMZigCreateDebugMemberType(g->dbuilder, LLVMZigTypeToScope(entry->di_type),
+                        "value", di_file, line, child_type->size_in_bits, child_type->align_in_bits,
+                        g->err_tag_type->size_in_bits, 0, child_type->di_type),
+            };
+
+            LLVMZigDIType *replacement_di_type = LLVMZigCreateDebugStructType(g->dbuilder,
+                    compile_unit_scope,
+                    buf_ptr(&entry->name),
+                    di_file, line, entry->size_in_bits, entry->align_in_bits, 0,
+                    nullptr, di_element_types, 2, 0, nullptr, "");
+
+            LLVMZigReplaceTemporary(g->dbuilder, entry->di_type, replacement_di_type);
+            entry->di_type = replacement_di_type;
         }
 
         child_type->error_parent = entry;
@@ -1012,7 +1046,6 @@ static void resolve_top_level_decl(CodeGen *g, ImportTableEntry *import, AstNode
         case NodeTypeArrayAccessExpr:
         case NodeTypeSliceExpr:
         case NodeTypeNumberLiteral:
-        case NodeTypeErrorLiteral:
         case NodeTypeStringLiteral:
         case NodeTypeCharLiteral:
         case NodeTypeBoolLiteral:
@@ -1037,6 +1070,7 @@ static void resolve_top_level_decl(CodeGen *g, ImportTableEntry *import, AstNode
         case NodeTypeStructValueField:
         case NodeTypeContainerInitExpr:
         case NodeTypeArrayType:
+        case NodeTypeErrorType:
             zig_unreachable();
     }
 }
@@ -1083,7 +1117,8 @@ static bool type_has_codegen_value(TypeTableEntryId id) {
         case TypeTableEntryIdArray:
         case TypeTableEntryIdStruct:
         case TypeTableEntryIdMaybe:
-        case TypeTableEntryIdError:
+        case TypeTableEntryIdErrorUnion:
+        case TypeTableEntryIdPureError:
         case TypeTableEntryIdEnum:
         case TypeTableEntryIdFn:
             return true;
@@ -1166,8 +1201,8 @@ static bool types_match_const_cast_only(TypeTableEntry *expected_type, TypeTable
     }
 
     // error
-    if (expected_type->id == TypeTableEntryIdError &&
-        actual_type->id == TypeTableEntryIdError)
+    if (expected_type->id == TypeTableEntryIdErrorUnion &&
+        actual_type->id == TypeTableEntryIdErrorUnion)
     {
         return types_match_const_cast_only(
                 expected_type->data.error.child_type,
@@ -1224,11 +1259,11 @@ static TypeTableEntry *determine_peer_type_compatibility(CodeGen *g, AstNode *pa
                 prev_type = cur_type;
                 prev_node = cur_node;
             }
-        } else if (prev_type->id == TypeTableEntryIdError &&
+        } else if (prev_type->id == TypeTableEntryIdErrorUnion &&
                    types_match_const_cast_only(prev_type->data.error.child_type, cur_type))
         {
             continue;
-        } else if (cur_type->id == TypeTableEntryIdError &&
+        } else if (cur_type->id == TypeTableEntryIdErrorUnion &&
                    types_match_const_cast_only(cur_type->data.error.child_type, prev_type))
         {
             prev_type = cur_type;
@@ -1287,13 +1322,20 @@ static bool types_match_with_implicit_cast(CodeGen *g, TypeTableEntry *expected_
     }
 
     // implicit conversion from error child type to error type
-    if (expected_type->id == TypeTableEntryIdError &&
+    if (expected_type->id == TypeTableEntryIdErrorUnion &&
         types_match_with_implicit_cast(g, expected_type->data.error.child_type, actual_type,
             literal_node, reported_err))
     {
         return true;
     }
 
+    // implicit conversion from pure error to error union type
+    if (expected_type->id == TypeTableEntryIdErrorUnion &&
+        actual_type->id == TypeTableEntryIdPureError)
+    {
+        return true;
+    }
+
     // implicit widening conversion
     if (expected_type->id == TypeTableEntryIdInt &&
         actual_type->id == TypeTableEntryIdInt &&
@@ -1695,12 +1737,11 @@ static TypeTableEntry *analyze_field_access_expr(CodeGen *g, ImportTableEntry *i
 
     AstNode *struct_expr_node = node->data.field_access_expr.struct_expr;
     TypeTableEntry *struct_type = analyze_expression(g, import, context, nullptr, struct_expr_node);
+    Buf *field_name = &node->data.field_access_expr.field_name;
 
     if (struct_type->id == TypeTableEntryIdStruct || (struct_type->id == TypeTableEntryIdPointer &&
          struct_type->data.pointer.child_type->id == TypeTableEntryIdStruct))
     {
-        Buf *field_name = &node->data.field_access_expr.field_name;
-
         TypeTableEntry *bare_struct_type = (struct_type->id == TypeTableEntryIdStruct) ?
             struct_type : struct_type->data.pointer.child_type;
 
@@ -1713,15 +1754,14 @@ static TypeTableEntry *analyze_field_access_expr(CodeGen *g, ImportTableEntry *i
             return g->builtin_types.entry_invalid;
         }
     } else if (struct_type->id == TypeTableEntryIdArray) {
-        Buf *name = &node->data.field_access_expr.field_name;
-        if (buf_eql_str(name, "len")) {
+        if (buf_eql_str(field_name, "len")) {
             return g->builtin_types.entry_isize;
-        } else if (buf_eql_str(name, "ptr")) {
+        } else if (buf_eql_str(field_name, "ptr")) {
             // TODO determine whether the pointer should be const
             return get_pointer_to_type(g, struct_type->data.array.child_type, false);
         } else {
             add_node_error(g, node,
-                buf_sprintf("no member named '%s' in '%s'", buf_ptr(name),
+                buf_sprintf("no member named '%s' in '%s'", buf_ptr(field_name),
                     buf_ptr(&struct_type->name)));
             return g->builtin_types.entry_invalid;
         }
@@ -1731,8 +1771,9 @@ static TypeTableEntry *analyze_field_access_expr(CodeGen *g, ImportTableEntry *i
         if (enum_type->id == TypeTableEntryIdInvalid) {
             return g->builtin_types.entry_invalid;
         } else if (enum_type->id == TypeTableEntryIdEnum) {
-            Buf *field_name = &node->data.field_access_expr.field_name;
             return analyze_enum_value_expr(g, import, context, node, nullptr, enum_type, field_name);
+        } else if (enum_type->id == TypeTableEntryIdPureError) {
+            return analyze_error_literal_expr(g, import, context, node, field_name);
         } else {
             add_node_error(g, node,
                 buf_sprintf("type '%s' does not support field access", buf_ptr(&struct_type->name)));
@@ -1846,7 +1887,7 @@ static TypeTableEntry *resolve_expr_const_val_as_err(CodeGen *g, AstNode *node,
     Expr *expr = get_resolved_expr(node);
     expr->const_val.ok = true;
     expr->const_val.data.x_err.err = err;
-    return get_error_type(g, g->builtin_types.entry_void);
+    return g->builtin_types.entry_pure_error;
 }
 
 static TypeTableEntry *resolve_expr_const_val_as_bool(CodeGen *g, AstNode *node, bool value) {
@@ -1959,6 +2000,21 @@ static TypeTableEntry *resolve_expr_const_val_as_bignum_op(CodeGen *g, AstNode *
     return resolved_type;
 }
 
+static TypeTableEntry *analyze_error_literal_expr(CodeGen *g, ImportTableEntry *import,
+        BlockContext *context, AstNode *node, Buf *err_name)
+{
+    auto err_table_entry = import->block_context->error_table.maybe_get(err_name);
+
+    if (err_table_entry) {
+        return resolve_expr_const_val_as_err(g, node, err_table_entry->value);
+    }
+
+    add_node_error(g, node,
+            buf_sprintf("use of undeclared error value '%s'", buf_ptr(err_name)));
+
+    return get_error_type(g, g->builtin_types.entry_void);
+}
+
 
 static TypeTableEntry *analyze_symbol_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
         TypeTableEntry *expected_type, AstNode *node)
@@ -2564,23 +2620,6 @@ static TypeTableEntry *analyze_number_literal_expr(CodeGen *g, ImportTableEntry
     }
 }
 
-static TypeTableEntry *analyze_error_literal_expr(CodeGen *g, ImportTableEntry *import,
-        BlockContext *block_context, TypeTableEntry *expected_type, AstNode *node)
-{
-    Buf *err_name = &node->data.error_literal.symbol;
-
-    auto err_table_entry = import->block_context->error_table.maybe_get(err_name);
-
-    if (err_table_entry) {
-        return resolve_expr_const_val_as_err(g, node, err_table_entry->value);
-    }
-
-    add_node_error(g, node,
-            buf_sprintf("use of undeclared error value '%s'", buf_ptr(err_name)));
-
-    return get_error_type(g, g->builtin_types.entry_void);
-}
-
 static TypeTableEntry *analyze_array_type(CodeGen *g, ImportTableEntry *import, BlockContext *context,
         TypeTableEntry *expected_type, AstNode *node)
 {
@@ -2906,6 +2945,10 @@ static void eval_const_expr_implicit_cast(CodeGen *g, AstNode *node, AstNode *ex
             const_val->data.x_err.payload = other_val;
             const_val->ok = true;
             break;
+        case CastOpPureErrorWrap:
+            const_val->data.x_err.err = other_val->data.x_err.err;
+            const_val->ok = true;
+            break;
         case CastOpErrToInt:
             {
                 uint64_t value = other_val->data.x_err.err ? other_val->data.x_err.err->value : 0;
@@ -3007,7 +3050,7 @@ static TypeTableEntry *analyze_cast_expr(CodeGen *g, ImportTableEntry *import, B
     }
 
     // explicit cast from child type of error type to error type
-    if (wanted_type->id == TypeTableEntryIdError) {
+    if (wanted_type->id == TypeTableEntryIdErrorUnion) {
         if (types_match_const_cast_only(wanted_type->data.error.child_type, actual_type)) {
             node->data.fn_call_expr.cast_op = CastOpErrorWrap;
             eval_const_expr_implicit_cast(g, node, expr_node);
@@ -3025,6 +3068,15 @@ static TypeTableEntry *analyze_cast_expr(CodeGen *g, ImportTableEntry *import, B
         }
     }
 
+    // explicit cast from pure error to error union type
+    if (wanted_type->id == TypeTableEntryIdErrorUnion &&
+        actual_type->id == TypeTableEntryIdPureError)
+    {
+        node->data.fn_call_expr.cast_op = CastOpPureErrorWrap;
+        eval_const_expr_implicit_cast(g, node, expr_node);
+        return wanted_type;
+    }
+
     // explicit cast from number literal to another type
     if (actual_type->id == TypeTableEntryIdNumLitFloat ||
         actual_type->id == TypeTableEntryIdNumLitInt)
@@ -3039,8 +3091,10 @@ static TypeTableEntry *analyze_cast_expr(CodeGen *g, ImportTableEntry *import, B
     }
 
     // explicit cast from %void to integer type which can fit it
-    if (actual_type->id == TypeTableEntryIdError &&
-        actual_type->data.error.child_type->size_in_bits == 0 &&
+    bool actual_type_is_void_err = actual_type->id == TypeTableEntryIdErrorUnion &&
+        actual_type->data.error.child_type->size_in_bits == 0;
+    bool actual_type_is_pure_err = actual_type->id == TypeTableEntryIdPureError;
+    if ((actual_type_is_void_err || actual_type_is_pure_err) &&
         wanted_type->id == TypeTableEntryIdInt)
     {
         BigNum bn;
@@ -3754,9 +3808,6 @@ static TypeTableEntry *analyze_expression(CodeGen *g, ImportTableEntry *import,
         case NodeTypeNumberLiteral:
             return_type = analyze_number_literal_expr(g, import, context, expected_type, node);
             break;
-        case NodeTypeErrorLiteral:
-            return_type = analyze_error_literal_expr(g, import, context, expected_type, node);
-            break;
         case NodeTypeStringLiteral:
             return_type = analyze_string_literal_expr(g, import, context, expected_type, node);
             break;
@@ -3794,6 +3845,9 @@ static TypeTableEntry *analyze_expression(CodeGen *g, ImportTableEntry *import,
         case NodeTypeArrayType:
             return_type = analyze_array_type(g, import, context, expected_type, node);
             break;
+        case NodeTypeErrorType:
+            return_type = resolve_expr_const_val_as_type(g, node, g->builtin_types.entry_pure_error);
+            break;
         case NodeTypeSwitchExpr:
             return_type = analyze_switch_expr(g, import, context, expected_type, node);
             break;
@@ -3933,7 +3987,6 @@ static void analyze_top_level_decl(CodeGen *g, ImportTableEntry *import, AstNode
         case NodeTypeArrayAccessExpr:
         case NodeTypeSliceExpr:
         case NodeTypeNumberLiteral:
-        case NodeTypeErrorLiteral:
         case NodeTypeStringLiteral:
         case NodeTypeCharLiteral:
         case NodeTypeBoolLiteral:
@@ -3958,6 +4011,7 @@ static void analyze_top_level_decl(CodeGen *g, ImportTableEntry *import, AstNode
         case NodeTypeStructValueField:
         case NodeTypeContainerInitExpr:
         case NodeTypeArrayType:
+        case NodeTypeErrorType:
             zig_unreachable();
     }
 }
@@ -3967,7 +4021,6 @@ static void collect_expr_decl_deps(CodeGen *g, ImportTableEntry *import, AstNode
 {
     switch (node->type) {
         case NodeTypeNumberLiteral:
-        case NodeTypeErrorLiteral:
         case NodeTypeStringLiteral:
         case NodeTypeCharLiteral:
         case NodeTypeBoolLiteral:
@@ -3977,6 +4030,7 @@ static void collect_expr_decl_deps(CodeGen *g, ImportTableEntry *import, AstNode
         case NodeTypeBreak:
         case NodeTypeContinue:
         case NodeTypeErrorValueDecl:
+        case NodeTypeErrorType:
             // no dependencies on other top level declarations
             break;
         case NodeTypeSymbol:
@@ -4286,7 +4340,6 @@ static void detect_top_level_decl_deps(CodeGen *g, ImportTableEntry *import, Ast
         case NodeTypeArrayAccessExpr:
         case NodeTypeSliceExpr:
         case NodeTypeNumberLiteral:
-        case NodeTypeErrorLiteral:
         case NodeTypeStringLiteral:
         case NodeTypeCharLiteral:
         case NodeTypeBoolLiteral:
@@ -4311,6 +4364,7 @@ static void detect_top_level_decl_deps(CodeGen *g, ImportTableEntry *import, Ast
         case NodeTypeContainerInitExpr:
         case NodeTypeStructValueField:
         case NodeTypeArrayType:
+        case NodeTypeErrorType:
             zig_unreachable();
     }
 }
@@ -4419,7 +4473,14 @@ void semantic_analyze(CodeGen *g) {
         }
     }
 
-    g->err_tag_type = get_smallest_unsigned_int_type(g, g->error_value_count);
+    {
+        g->err_tag_type = get_smallest_unsigned_int_type(g, g->error_value_count);
+
+        g->builtin_types.entry_pure_error->type_ref = g->err_tag_type->type_ref;
+        g->builtin_types.entry_pure_error->size_in_bits = g->err_tag_type->size_in_bits;
+        g->builtin_types.entry_pure_error->align_in_bits = g->err_tag_type->align_in_bits;
+        g->builtin_types.entry_pure_error->di_type = g->err_tag_type->di_type;
+    }
 
     {
         auto it = g->import_table.entry_iterator();
@@ -4493,8 +4554,6 @@ Expr *get_resolved_expr(AstNode *node) {
             return &node->data.container_init_expr.resolved_expr;
         case NodeTypeNumberLiteral:
             return &node->data.number_literal.resolved_expr;
-        case NodeTypeErrorLiteral:
-            return &node->data.error_literal.resolved_expr;
         case NodeTypeStringLiteral:
             return &node->data.string_literal.resolved_expr;
         case NodeTypeBlock:
@@ -4521,6 +4580,8 @@ Expr *get_resolved_expr(AstNode *node) {
             return &node->data.label.resolved_expr;
         case NodeTypeArrayType:
             return &node->data.array_type.resolved_expr;
+        case NodeTypeErrorType:
+            return &node->data.error_type.resolved_expr;
         case NodeTypeSwitchExpr:
             return &node->data.switch_expr.resolved_expr;
         case NodeTypeSwitchProng:
@@ -4554,7 +4615,6 @@ TopLevelDecl *get_resolved_top_level_decl(AstNode *node) {
         case NodeTypeErrorValueDecl:
             return &node->data.error_value_decl.top_level_decl;
         case NodeTypeNumberLiteral:
-        case NodeTypeErrorLiteral:
         case NodeTypeReturnExpr:
         case NodeTypeBinOpExpr:
         case NodeTypePrefixOpExpr:
@@ -4593,6 +4653,7 @@ TopLevelDecl *get_resolved_top_level_decl(AstNode *node) {
         case NodeTypeStructField:
         case NodeTypeStructValueField:
         case NodeTypeArrayType:
+        case NodeTypeErrorType:
             zig_unreachable();
     }
     zig_unreachable();
src/codegen.cpp
@@ -306,7 +306,7 @@ static LLVMValueRef gen_cast_expr(CodeGen *g, AstNode *node) {
         case CastOpNoop:
             return expr_val;
         case CastOpErrToInt:
-            assert(actual_type->id == TypeTableEntryIdError);
+            assert(actual_type->id == TypeTableEntryIdErrorUnion);
             if (actual_type->data.error.child_type->size_in_bits == 0) {
                 return gen_widen_or_shorten(g, node, g->err_tag_type, wanted_type, expr_val);
             } else {
@@ -330,12 +330,19 @@ static LLVMValueRef gen_cast_expr(CodeGen *g, AstNode *node) {
                 return cast_expr->tmp_ptr;
             }
         case CastOpErrorWrap:
-            assert(wanted_type->id == TypeTableEntryIdError);
+            assert(wanted_type->id == TypeTableEntryIdErrorUnion);
             if (wanted_type->data.error.child_type->size_in_bits == 0) {
                 return LLVMConstNull(g->err_tag_type->type_ref);
             } else {
                 zig_panic("TODO");
             }
+        case CastOpPureErrorWrap:
+            assert(wanted_type->id == TypeTableEntryIdErrorUnion);
+            if (wanted_type->data.error.child_type->size_in_bits == 0) {
+                return expr_val;
+            } else {
+                zig_panic("TODO");
+            }
         case CastOpPtrToInt:
             add_debug_source_node(g, node);
             return LLVMBuildPtrToInt(g->builder, expr_val, wanted_type->type_ref, "");
@@ -1292,7 +1299,7 @@ static LLVMValueRef gen_if_bool_expr_raw(CodeGen *g, AstNode *source_node, LLVMV
         return nullptr;
     }
 
-    assert(!use_expr_value || then_type->id == TypeTableEntryIdError);
+    assert(!use_expr_value || then_type->id == TypeTableEntryIdErrorUnion);
 
     LLVMBasicBlockRef then_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "Then");
     LLVMBasicBlockRef endif_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "EndIf");
@@ -2011,7 +2018,6 @@ static LLVMValueRef gen_expr(CodeGen *g, AstNode *node) {
             return gen_container_init_expr(g, node);
         case NodeTypeSwitchExpr:
             return gen_switch_expr(g, node);
-        case NodeTypeErrorLiteral:
         case NodeTypeNumberLiteral:
         case NodeTypeBoolLiteral:
         case NodeTypeStringLiteral:
@@ -2033,6 +2039,7 @@ static LLVMValueRef gen_expr(CodeGen *g, AstNode *node) {
         case NodeTypeStructField:
         case NodeTypeStructValueField:
         case NodeTypeArrayType:
+        case NodeTypeErrorType:
         case NodeTypeSwitchProng:
         case NodeTypeSwitchRange:
         case NodeTypeErrorValueDecl:
@@ -2063,6 +2070,9 @@ static LLVMValueRef gen_const_val(CodeGen *g, TypeTableEntry *type_entry, ConstE
 
     if (type_entry->id == TypeTableEntryIdInt) {
         return LLVMConstInt(type_entry->type_ref, bignum_to_twos_complement(&const_val->data.x_bignum), false);
+    } else if (type_entry->id == TypeTableEntryIdPureError) {
+        assert(const_val->data.x_err.err);
+        return LLVMConstInt(g->builtin_types.entry_pure_error->type_ref, const_val->data.x_err.err->value, false);
     } else if (type_entry->id == TypeTableEntryIdFloat) {
         if (const_val->data.x_bignum.kind == BigNumKindFloat) {
             return LLVMConstReal(type_entry->type_ref, const_val->data.x_bignum.data.x_float);
@@ -2148,12 +2158,26 @@ static LLVMValueRef gen_const_val(CodeGen *g, TypeTableEntry *type_entry, ConstE
         } else {
             return global_value;
         }
-    } else if (type_entry->id == TypeTableEntryIdError) {
-        if (type_entry->data.error.child_type->size_in_bits == 0) {
+    } else if (type_entry->id == TypeTableEntryIdErrorUnion) {
+        TypeTableEntry *child_type = type_entry->data.error.child_type;
+        if (child_type->size_in_bits == 0) {
             uint64_t value = const_val->data.x_err.err ? const_val->data.x_err.err->value : 0;
             return LLVMConstInt(g->err_tag_type->type_ref, value, false);
         } else {
-            zig_panic("TODO");
+            LLVMValueRef err_tag_value;
+            LLVMValueRef err_payload_value;
+            if (const_val->data.x_err.err) {
+                err_tag_value = LLVMConstInt(g->err_tag_type->type_ref, const_val->data.x_err.err->value, false);
+                err_payload_value = LLVMConstNull(child_type->type_ref);
+            } else {
+                err_tag_value = LLVMConstNull(g->err_tag_type->type_ref);
+                err_payload_value = gen_const_val(g, child_type, const_val->data.x_err.payload);
+            }
+            LLVMValueRef fields[] = {
+                err_tag_value,
+                err_payload_value,
+            };
+            return LLVMConstStruct(fields, 2, false);
         }
     } else {
         zig_unreachable();
@@ -2557,6 +2581,14 @@ static void define_builtin_types(CodeGen *g) {
         g->builtin_types.entry_type = entry;
         g->primitive_type_table.put(&entry->name, entry);
     }
+    {
+        // partially complete the error type. we complete it later after we know
+        // error_value_count.
+        TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdPureError);
+        buf_init_from_str(&entry->name, "error");
+        g->builtin_types.entry_pure_error = entry;
+        g->primitive_type_table.put(&entry->name, entry);
+    }
 
     g->builtin_types.entry_u8 = get_int_type(g, false, 8);
     g->builtin_types.entry_u16 = get_int_type(g, false, 16);
src/parser.cpp
@@ -113,8 +113,6 @@ const char *node_type_str(NodeType node_type) {
             return "ErrorValueDecl";
         case NodeTypeNumberLiteral:
             return "NumberLiteral";
-        case NodeTypeErrorLiteral:
-            return "ErrorLiteral";
         case NodeTypeStringLiteral:
             return "StringLiteral";
         case NodeTypeCharLiteral:
@@ -167,6 +165,8 @@ const char *node_type_str(NodeType node_type) {
             return "ContainerInitExpr";
         case NodeTypeArrayType:
             return "ArrayType";
+        case NodeTypeErrorType:
+            return "ErrorType";
     }
     zig_unreachable();
 }
@@ -313,11 +313,6 @@ void ast_print(AstNode *node, int indent) {
                 }
                 break;
             }
-        case NodeTypeErrorLiteral:
-            {
-                fprintf(stderr, "%s '%s'", node_type_str(node->type), buf_ptr(&node->data.error_literal.symbol));
-                break;
-            }
         case NodeTypeStringLiteral:
             {
                 const char *c = node->data.string_literal.c ? "c" : "";
@@ -465,6 +460,9 @@ void ast_print(AstNode *node, int indent) {
                 ast_print(node->data.array_type.child_type, indent + 2);
                 break;
             }
+        case NodeTypeErrorType:
+            fprintf(stderr, "%s\n", node_type_str(node->type));
+            break;
     }
 }
 
@@ -1372,8 +1370,8 @@ static AstNode *ast_parse_asm_expr(ParseContext *pc, int *token_index, bool mand
 }
 
 /*
-PrimaryExpression : "Number" | "String" | "CharLiteral" | KeywordLiteral | GroupedExpression | GotoExpression | BlockExpression | "Symbol" | ("@" "Symbol" FnCallExpression) | ArrayType | AsmExpression | ("%." "Symbol")
-KeywordLiteral : "true" | "false" | "null" | "break" | "continue" | "undefined"
+PrimaryExpression : "Number" | "String" | "CharLiteral" | KeywordLiteral | GroupedExpression | GotoExpression | BlockExpression | "Symbol" | ("@" "Symbol" FnCallExpression) | ArrayType | AsmExpression | ("error" "." "Symbol")
+KeywordLiteral : "true" | "false" | "null" | "break" | "continue" | "undefined" | "error"
 */
 static AstNode *ast_parse_primary_expr(ParseContext *pc, int *token_index, bool mandatory) {
     Token *token = &pc->tokens->at(*token_index);
@@ -1419,6 +1417,10 @@ static AstNode *ast_parse_primary_expr(ParseContext *pc, int *token_index, bool
         AstNode *node = ast_create_node(pc, NodeTypeUndefinedLiteral, token);
         *token_index += 1;
         return node;
+    } else if (token->id == TokenIdKeywordError) {
+        AstNode *node = ast_create_node(pc, NodeTypeErrorType, token);
+        *token_index += 1;
+        return node;
     } else if (token->id == TokenIdAtSign) {
         *token_index += 1;
         Token *name_tok = ast_eat_token(pc, token_index, TokenIdSymbol);
@@ -1448,12 +1450,6 @@ static AstNode *ast_parse_primary_expr(ParseContext *pc, int *token_index, bool
 
         ast_buf_from_token(pc, dest_symbol, &node->data.goto_expr.name);
         return node;
-    } else if (token->id == TokenIdPercentDot) {
-        *token_index += 1;
-        Token *symbol_tok = ast_eat_token(pc, token_index, TokenIdSymbol);
-        AstNode *node = ast_create_node(pc, NodeTypeErrorLiteral, token);
-        ast_buf_from_token(pc, symbol_tok, &node->data.error_literal.symbol);
-        return node;
     }
 
     AstNode *grouped_expr_node = ast_parse_grouped_expr(pc, token_index, false);
@@ -2969,7 +2965,7 @@ static AstNode *ast_parse_struct_decl(ParseContext *pc, int *token_index) {
 }
 
 /*
-ErrorValueDecl : option(FnVisibleMod) "%." "Symbol"
+ErrorValueDecl : option(FnVisibleMod) "error" "Symbol"
 */
 static AstNode *ast_parse_error_value_decl(ParseContext *pc, int *token_index, bool mandatory) {
     Token *first_token = &pc->tokens->at(*token_index);
@@ -2978,7 +2974,7 @@ static AstNode *ast_parse_error_value_decl(ParseContext *pc, int *token_index, b
 
     if (first_token->id == TokenIdKeywordPub) {
         Token *next_token = &pc->tokens->at(*token_index + 1);
-        if (next_token->id == TokenIdPercentDot) {
+        if (next_token->id == TokenIdKeywordError) {
             visib_mod = VisibModPub;
             *token_index += 2;
         } else if (mandatory) {
@@ -2988,7 +2984,7 @@ static AstNode *ast_parse_error_value_decl(ParseContext *pc, int *token_index, b
         }
     } else if (first_token->id == TokenIdKeywordExport) {
         Token *next_token = &pc->tokens->at(*token_index + 1);
-        if (next_token->id == TokenIdPercentDot) {
+        if (next_token->id == TokenIdKeywordError) {
             visib_mod = VisibModExport;
             *token_index += 2;
         } else if (mandatory) {
@@ -2996,7 +2992,7 @@ static AstNode *ast_parse_error_value_decl(ParseContext *pc, int *token_index, b
         } else {
             return nullptr;
         }
-    } else if (first_token->id == TokenIdPercentDot) {
+    } else if (first_token->id == TokenIdKeywordError) {
         visib_mod = VisibModPrivate;
         *token_index += 1;
     } else if (mandatory) {
@@ -3177,9 +3173,6 @@ void normalize_parent_ptrs(AstNode *node) {
         case NodeTypeCharLiteral:
             // none
             break;
-        case NodeTypeErrorLiteral:
-            // none
-            break;
         case NodeTypeSymbol:
             // none
             break;
@@ -3290,5 +3283,8 @@ void normalize_parent_ptrs(AstNode *node) {
             set_field(&node->data.array_type.size);
             set_field(&node->data.array_type.child_type);
             break;
+        case NodeTypeErrorType:
+            // none
+            break;
     }
 }
src/tokenizer.cpp
@@ -247,6 +247,8 @@ static void end_token(Tokenize *t) {
         t->cur_tok->id = TokenIdKeywordSwitch;
     } else if (mem_eql_str(token_mem, token_len, "undefined")) {
         t->cur_tok->id = TokenIdKeywordUndefined;
+    } else if (mem_eql_str(token_mem, token_len, "error")) {
+        t->cur_tok->id = TokenIdKeywordError;
     }
 
     t->cur_tok = nullptr;
@@ -1046,6 +1048,7 @@ const char * token_name(TokenId id) {
         case TokenIdKeywordNoAlias: return "noalias";
         case TokenIdKeywordSwitch: return "switch";
         case TokenIdKeywordUndefined: return "undefined";
+        case TokenIdKeywordError: return "error";
         case TokenIdLParen: return "(";
         case TokenIdRParen: return ")";
         case TokenIdComma: return ",";
src/tokenizer.hpp
@@ -38,6 +38,7 @@ enum TokenId {
     TokenIdKeywordNoAlias,
     TokenIdKeywordSwitch,
     TokenIdKeywordUndefined,
+    TokenIdKeywordError,
     TokenIdLParen,
     TokenIdRParen,
     TokenIdComma,
std/std.zig
@@ -25,15 +25,15 @@ pub var stderr = OutStream {
 };
 */
 
-pub %.Unexpected;
-pub %.DiskQuota;
-pub %.FileTooBig;
-pub %.SigInterrupt;
-pub %.Io;
-pub %.NoSpaceLeft;
-pub %.BadPerm;
-pub %.PipeFail;
-pub %.Invalid;
+pub error Unexpected;
+pub error DiskQuota;
+pub error FileTooBig;
+pub error SigInterrupt;
+pub error Io;
+pub error NoSpaceLeft;
+pub error BadPerm;
+pub error PipeFail;
+pub error Invalid;
 
 const buffer_size = 4 * 1024;
 const max_u64_base10_digits = 20;
@@ -100,14 +100,14 @@ pub struct OutStream {
         os.index = 0;
         switch (write(os.fd, os.buffer.ptr, amt_to_write)) {
             EINVAL => unreachable{},
-            EDQUOT => %.DiskQuota,
-            EFBIG  => %.FileTooBig,
-            EINTR  => %.SigInterrupt,
-            EIO    => %.Io,
-            ENOSPC => %.NoSpaceLeft,
-            EPERM  => %.BadPerm,
-            EPIPE  => %.PipeFail,
-            else   => %.Unexpected,
+            EDQUOT => error.DiskQuota,
+            EFBIG  => error.FileTooBig,
+            EINTR  => error.SigInterrupt,
+            EIO    => error.Io,
+            ENOSPC => error.NoSpaceLeft,
+            EPERM  => error.BadPerm,
+            EPIPE  => error.PipeFail,
+            else   => error.Unexpected,
         }
     }
 }
@@ -121,10 +121,10 @@ pub struct InStream {
             switch (-amt_read) {
                 EINVAL => unreachable{},
                 EFAULT => unreachable{},
-                EBADF  => %.BadFd,
-                EINTR  => %.SigInterrupt,
-                EIO    => %.Io,
-                else   => %.Unexpected,
+                EBADF  => error.BadFd,
+                EINTR  => error.SigInterrupt,
+                EIO    => error.Io,
+                else   => error.Unexpected,
             }
         }
         return amt_read;
@@ -136,8 +136,8 @@ pub fn os_get_random_bytes(buf: []u8) %void => {
     switch (getrandom(buf.ptr, buf.len, 0)) {
         EINVAL => unreachable{},
         EFAULT => unreachable{},
-        EINTR  => %.SigInterrupt,
-        else   => %.Unexpected,
+        EINTR  => error.SigInterrupt,
+        else   => error.Unexpected,
     }
 }
 */
test/run_tests.cpp
@@ -1213,11 +1213,11 @@ pub fn main(args: [][]u8) %void => {
 
     add_simple_case("error values", R"SOURCE(
 import "std.zig";
-%.err1;
-%.err2;
+error err1;
+error err2;
 pub fn main(args: [][]u8) %void => {
-    const a = i32(%.err1);
-    const b = i32(%.err2);
+    const a = i32(error.err1);
+    const b = i32(error.err2);
     if (a == b) {
         print_str("BAD\n");
     }
@@ -1467,8 +1467,8 @@ enum A {}
     )SOURCE", 1, ".tmp_source.zig:3:1: error: redefinition of 'A'");
 
     add_compile_fail_case("redefinition of error values", R"SOURCE(
-%.A;
-%.A;
+error A;
+error A;
     )SOURCE", 1, ".tmp_source.zig:3:1: error: redefinition of error 'A'");
 
     add_compile_fail_case("redefinition of global variables", R"SOURCE(