diff --git a/doc/langref.md b/doc/langref.md index 61131d20e7..3607a3a1ab 100644 --- a/doc/langref.md +++ b/doc/langref.md @@ -94,7 +94,7 @@ BlockExpression : IfExpression | Block | WhileExpression | ForExpression | Switc SwitchExpression : "switch" "(" Expression ")" "{" many(SwitchProng) "}" -SwitchProng : (list(SwitchItem, ",") | "else") option("," "(" "Symbol" ")") "=>" Expression "," +SwitchProng : (list(SwitchItem, ",") | "else") option(":" "(" "Symbol" ")") "=>" Expression "," SwitchItem : Expression | (Expression "..." Expression) @@ -197,8 +197,8 @@ x{} | Example | Characters | Escapes | Null Term | Type ----------------|----------|-------------|----------------|-----------|---------- Byte | 'H' | All ASCII | Byte | No | u8 - UTF-8 Bytes | "hello" | All Unicode | Byte & Unicode | No | [5; u8] - UTF-8 C string | c"hello" | All Unicode | Byte & Unicode | Yes | *const u8 + UTF-8 Bytes | "hello" | All Unicode | Byte & Unicode | No | [5]u8 + UTF-8 C string | c"hello" | All Unicode | Byte & Unicode | Yes | &const u8 ### Byte Escapes diff --git a/example/list/list.zig b/example/list/list.zig index 5fc6ed6464..fc42889d0d 100644 --- a/example/list/list.zig +++ b/example/list/list.zig @@ -68,37 +68,35 @@ pub fn free#(T: type)(ptr: ?&T) { ////////////////// alternate -// previously proposed, but with : instead of -> -// `:` means "parser should expect a type now" -fn max#(T :type)(a :T, b :T) :T { +// previously proposed but without -> +fn max#(T: type)(a: T, b: T) T { if (a > b) a else b } // andy's new idea -// parameters can talk about @typeof() for previous parameters. -// using :T here is equivalent to @child_type(@typeof(T)) -fn max(T :type, a :T, b :T) :T { +// parameters can reference other inline parameters. +fn max(inline T: type, a: T, b: T) T { if (a > b) a else b } fn f() { - const x :i32 = 1234; - const y :i32 = 5678; + const x: i32 = 1234; + const y: i32 = 5678; const z = max(@typeof(x), x, y); } // So, type-generic functions don't need any fancy syntax. type-generic // containers still do, though: -pub struct List(T :type) { - items :?&T, - length :isize, - capacity :isize, +pub struct List(T: type) { + items: ?&T, + length: isize, + capacity: isize, } -// Types are always marked with ':' so we don't need '#' to indicate type generic parameters. +// we don't need '#' to indicate type generic parameters. fn f() { - var list :List(:u8); + var list: List(u8); } diff --git a/src/all_types.hpp b/src/all_types.hpp index 351b1847c5..2d61309e9f 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -26,6 +26,7 @@ struct BuiltinFnEntry; struct LabelTableEntry; struct TypeStructField; struct CodeGen; +struct ConstExprValue; enum OutType { OutTypeUnknown, @@ -57,6 +58,11 @@ struct Cast { AstNode *source_node; }; +struct ConstEnumValue { + uint64_t tag; + ConstExprValue *payload; +}; + struct ConstExprValue { bool ok; // true if constant expression evalution worked bool depends_on_compile_var; @@ -69,6 +75,7 @@ struct ConstExprValue { FnTableEntry *x_fn; TypeTableEntry *x_type; ConstExprValue *x_maybe; + ConstEnumValue x_enum; } data; }; @@ -426,6 +433,10 @@ struct AstNodeSwitchProng { ZigList items; AstNode *var_symbol; AstNode *expr; + + // populated by semantic analyzer + BlockContext *block_context; + VariableTableEntry *var; }; struct AstNodeSwitchRange { @@ -933,6 +944,7 @@ struct CodeGen { OutType out_type; FnTableEntry *cur_fn; + // TODO remove this in favor of get_resolved_expr(expr_node)->context BlockContext *cur_block_context; ZigList break_block_stack; ZigList continue_block_stack; diff --git a/src/analyze.cpp b/src/analyze.cpp index b7ec217c17..ef07c4e650 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -1350,6 +1350,11 @@ static TypeTableEntry *analyze_enum_value_expr(CodeGen *g, ImportTableEntry *imp buf_ptr(&enum_type->name), buf_ptr(field_name), buf_ptr(&type_enum_field->type_entry->name))); + } else { + Expr *expr = get_resolved_expr(field_access_node); + expr->const_val.ok = true; + expr->const_val.data.x_enum.tag = type_enum_field->value; + expr->const_val.data.x_enum.payload = nullptr; } } else { add_node_error(g, field_access_node, @@ -1945,6 +1950,25 @@ static TypeTableEntry *analyze_bool_bin_op_expr(CodeGen *g, ImportTableEntry *im } } else if (resolved_type->id == TypeTableEntryIdFloat) { answer = eval_bool_bin_op_float(op1_val->data.x_float, bin_op_type, op2_val->data.x_float); + } else if (resolved_type->id == TypeTableEntryIdEnum) { + ConstEnumValue *enum1 = &op1_val->data.x_enum; + ConstEnumValue *enum2 = &op2_val->data.x_enum; + bool are_equal = false; + if (enum1->tag == enum2->tag) { + TypeEnumField *enum_field = &op1_type->data.enumeration.fields[enum1->tag]; + if (enum_field->type_entry->size_in_bits > 0) { + zig_panic("TODO const expr analyze enum special value for equality"); + } else { + are_equal = true; + } + } + if (bin_op_type == BinOpTypeCmpEq) { + answer = are_equal; + } else if (bin_op_type == BinOpTypeCmpNotEq) { + answer = !are_equal; + } else { + zig_unreachable(); + } } else { zig_unreachable(); } @@ -3017,7 +3041,62 @@ static TypeTableEntry *analyze_prefix_op_expr(CodeGen *g, ImportTableEntry *impo static TypeTableEntry *analyze_switch_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context, TypeTableEntry *expected_type, AstNode *node) { - zig_panic("TODO analyze_switch_expr"); + AstNode *expr_node = node->data.switch_expr.expr; + TypeTableEntry *expr_type = analyze_expression(g, import, context, nullptr, expr_node); + + if (expected_type == nullptr) { + zig_panic("TODO resolve peer compatibility of switch prongs"); + } + + if (expr_type->id == TypeTableEntryIdInvalid) { + return expr_type; + } else if (expr_type->id == TypeTableEntryIdUnreachable) { + add_node_error(g, first_executing_node(expr_node), + buf_sprintf("switch on unreachable expression not allowed")); + return g->builtin_types.entry_invalid; + } else { + AstNode *else_prong = nullptr; + for (int prong_i = 0; prong_i < node->data.switch_expr.prongs.length; prong_i += 1) { + AstNode *prong_node = node->data.switch_expr.prongs.at(prong_i); + + TypeTableEntry *var_type; + if (prong_node->data.switch_prong.items.length == 0) { + if (else_prong) { + add_node_error(g, prong_node, buf_sprintf("multiple else prongs in switch expression")); + } else { + else_prong = prong_node; + } + var_type = expr_type; + } else { + for (int item_i = 0; item_i < prong_node->data.switch_prong.items.length; item_i += 1) { + AstNode *item_node = prong_node->data.switch_prong.items.at(item_i); + if (item_node->type == NodeTypeSwitchRange) { + zig_panic("TODO range in switch statement"); + } + analyze_expression(g, import, context, expr_type, item_node); + ConstExprValue *const_val = &get_resolved_expr(item_node)->const_val; + if (!const_val->ok) { + add_node_error(g, item_node, buf_sprintf("unable to resolve constant expression")); + } + } + var_type = expr_type; + } + + BlockContext *child_context = new_block_context(node, context); + prong_node->data.switch_prong.block_context = child_context; + AstNode *var_node = prong_node->data.switch_prong.var_symbol; + if (var_node) { + assert(var_node->type == NodeTypeSymbol); + Buf *var_name = &var_node->data.symbol_expr.symbol; + prong_node->data.switch_prong.var = add_local_var(g, var_node, child_context, var_name, + var_type, true); + } + + analyze_expression(g, import, child_context, expected_type, + prong_node->data.switch_prong.expr); + } + } + return expected_type; } static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import, BlockContext *context, diff --git a/src/codegen.cpp b/src/codegen.cpp index 2d0caf8440..07c69ecc90 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -1968,7 +1968,69 @@ static LLVMValueRef gen_symbol(CodeGen *g, AstNode *node) { static LLVMValueRef gen_switch_expr(CodeGen *g, AstNode *node) { assert(node->type == NodeTypeSwitchExpr); - zig_panic("TODO gen_switch_expr"); + LLVMValueRef target_value = gen_expr(g, node->data.switch_expr.expr); + + bool end_unreachable = (get_expr_type(node)->id == TypeTableEntryIdUnreachable); + + LLVMBasicBlockRef end_block = end_unreachable ? + nullptr : LLVMAppendBasicBlock(g->cur_fn->fn_value, "SwitchEnd"); + LLVMBasicBlockRef else_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "SwitchElse"); + int prong_count = node->data.switch_expr.prongs.length; + + add_debug_source_node(g, node); + LLVMValueRef switch_instr = LLVMBuildSwitch(g->builder, target_value, else_block, prong_count); + + ZigList incoming_values = {0}; + ZigList incoming_blocks = {0}; + + AstNode *else_prong = nullptr; + for (int prong_i = 0; prong_i < prong_count; prong_i += 1) { + AstNode *prong_node = node->data.switch_expr.prongs.at(prong_i); + LLVMBasicBlockRef prong_block; + if (prong_node->data.switch_prong.items.length == 0) { + assert(!else_prong); + else_prong = prong_node; + prong_block = else_block; + } else { + prong_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "SwitchProng"); + for (int item_i = 0; item_i < prong_node->data.switch_prong.items.length; item_i += 1) { + AstNode *item_node = prong_node->data.switch_prong.items.at(item_i); + assert(item_node->type != NodeTypeSwitchRange); + assert(get_resolved_expr(item_node)->const_val.ok); + LLVMValueRef val = gen_expr(g, item_node); + LLVMAddCase(switch_instr, val, prong_block); + } + } + assert(!prong_node->data.switch_prong.var_symbol); + LLVMPositionBuilderAtEnd(g->builder, prong_block); + AstNode *prong_expr = prong_node->data.switch_prong.expr; + LLVMValueRef prong_val = gen_expr(g, prong_expr); + + if (get_expr_type(prong_expr)->id != TypeTableEntryIdUnreachable) { + add_debug_source_node(g, prong_expr); + LLVMBuildBr(g->builder, end_block); + incoming_values.append(prong_val); + incoming_blocks.append(prong_block); + } + } + + if (!else_prong) { + LLVMPositionBuilderAtEnd(g->builder, else_block); + add_debug_source_node(g, node); + LLVMBuildUnreachable(g->builder); + } + + if (end_unreachable) { + return nullptr; + } + + LLVMPositionBuilderAtEnd(g->builder, end_block); + + add_debug_source_node(g, node); + LLVMValueRef phi = LLVMBuildPhi(g->builder, get_expr_type(node)->type_ref, ""); + LLVMAddIncoming(phi, incoming_values.items, incoming_blocks.items, incoming_values.length); + + return phi; } static LLVMValueRef gen_expr_no_cast(CodeGen *g, AstNode *node) { diff --git a/src/parser.cpp b/src/parser.cpp index 32ecc9885a..0dfb60a47a 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -2255,8 +2255,8 @@ static AstNode *ast_parse_switch_expr(ParseContext *pc, int *token_index, bool m break; } - Token *arrow_or_comma = &pc->tokens->at(*token_index); - if (arrow_or_comma->id == TokenIdComma) { + Token *arrow_or_colon = &pc->tokens->at(*token_index); + if (arrow_or_colon->id == TokenIdColon) { *token_index += 1; ast_eat_token(pc, token_index, TokenIdLParen); prong_node->data.switch_prong.var_symbol = ast_parse_symbol(pc, token_index); diff --git a/test/run_tests.cpp b/test/run_tests.cpp index 91cfd66317..5c4c04fd1c 100644 --- a/test/run_tests.cpp +++ b/test/run_tests.cpp @@ -1180,6 +1180,33 @@ fn fn2() u32 => {6} fn fn3() u32 => {7} fn fn4() u32 => {8} )SOURCE", "5\n6\n7\n8\n"); + + add_simple_case("switch statement", R"SOURCE( +import "std.zig"; + +enum Foo { + A, + B, + C, + D, +} + +pub fn main(args: [][]u8) i32 => { + const foo = Foo.C; + const val: i32 = switch (foo) { + Foo.A => 1, + Foo.B => 2, + Foo.C => 3, + Foo.D => 4, + }; + if (val != 3) { + print_str("BAD\n"); + } + + print_str("OK\n"); + return 0; +} + )SOURCE", "OK\n"); } @@ -1511,6 +1538,16 @@ fn f(Foo: i32) => { } )SOURCE", 2, ".tmp_source.zig:5:6: error: variable shadows type 'Foo'", ".tmp_source.zig:6:5: error: variable shadows type 'Bar'"); + + add_compile_fail_case("multiple else prongs in a switch", R"SOURCE( +fn f() => { + const value: bool = switch (u32(111)) { + 1234 => false, + else => true, + else => true, + }; +} + )SOURCE", 1, ".tmp_source.zig:6:9: error: multiple else prongs in switch expression"); } static void print_compiler_invocation(TestCase *test_case) {