mirror of
https://github.com/ziglang/zig.git
synced 2026-02-15 13:58:27 +00:00
basic support for switch expression
This commit is contained in:
parent
3eca42c17b
commit
ad9759bc8e
@ -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
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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<AstNode *> 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<LLVMBasicBlockRef> break_block_stack;
|
||||
ZigList<LLVMBasicBlockRef> continue_block_stack;
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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<LLVMValueRef> incoming_values = {0};
|
||||
ZigList<LLVMBasicBlockRef> 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) {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user