authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2017-05-07 12:07:35-04:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2017-05-07 12:07:35-04:00
log818a0a26291cf456cfaa955401b1aa8219737d6c
tree9eac0aacdcc41c718a18c98f3513beffaaeac2d3
parent29beb603b752928184f5f5e9f7479412de1e1951

switch expression - add compile errors

* for duplicate integer value * for missing integer values * for missing else prong see #43

9 files changed, 304 insertions(+), 49 deletions(-)

CMakeLists.txt+1
......@@ -56,6 +56,7 @@ set(ZIG_SOURCES
5656 "${CMAKE_SOURCE_DIR}/src/main.cpp"
5757 "${CMAKE_SOURCE_DIR}/src/os.cpp"
5858 "${CMAKE_SOURCE_DIR}/src/parser.cpp"
59 "${CMAKE_SOURCE_DIR}/src/range_set.cpp"
5960 "${CMAKE_SOURCE_DIR}/src/target.cpp"
6061 "${CMAKE_SOURCE_DIR}/src/tokenizer.cpp"
6162 "${CMAKE_SOURCE_DIR}/src/util.cpp"
src/analyze.cpp+21-16
......@@ -3851,25 +3851,30 @@ int64_t min_signed_val(TypeTableEntry *type_entry) {
38513851 }
38523852}
38533853
3854void eval_min_max_value_int(CodeGen *g, TypeTableEntry *int_type, BigNum *bignum, bool is_max) {
3855 assert(int_type->id == TypeTableEntryIdInt);
3856 if (is_max) {
3857 if (int_type->data.integral.is_signed) {
3858 int64_t val = max_signed_val(int_type);
3859 bignum_init_signed(bignum, val);
3860 } else {
3861 uint64_t val = max_unsigned_val(int_type);
3862 bignum_init_unsigned(bignum, val);
3863 }
3864 } else {
3865 if (int_type->data.integral.is_signed) {
3866 int64_t val = min_signed_val(int_type);
3867 bignum_init_signed(bignum, val);
3868 } else {
3869 bignum_init_unsigned(bignum, 0);
3870 }
3871 }
3872}
3873
38543874void eval_min_max_value(CodeGen *g, TypeTableEntry *type_entry, ConstExprValue *const_val, bool is_max) {
38553875 if (type_entry->id == TypeTableEntryIdInt) {
38563876 const_val->special = ConstValSpecialStatic;
3857 if (is_max) {
3858 if (type_entry->data.integral.is_signed) {
3859 int64_t val = max_signed_val(type_entry);
3860 bignum_init_signed(&const_val->data.x_bignum, val);
3861 } else {
3862 uint64_t val = max_unsigned_val(type_entry);
3863 bignum_init_unsigned(&const_val->data.x_bignum, val);
3864 }
3865 } else {
3866 if (type_entry->data.integral.is_signed) {
3867 int64_t val = min_signed_val(type_entry);
3868 bignum_init_signed(&const_val->data.x_bignum, val);
3869 } else {
3870 bignum_init_unsigned(&const_val->data.x_bignum, 0);
3871 }
3872 }
3877 eval_min_max_value_int(g, type_entry, &const_val->data.x_bignum, is_max);
38733878 } else if (type_entry->id == TypeTableEntryIdFloat) {
38743879 zig_panic("TODO analyze_min_max_value float");
38753880 } else if (type_entry->id == TypeTableEntryIdBool) {
src/analyze.hpp+1
......@@ -84,6 +84,7 @@ void complete_enum(CodeGen *g, TypeTableEntry *enum_type);
8484bool ir_get_var_is_comptime(VariableTableEntry *var);
8585bool const_values_equal(ConstExprValue *a, ConstExprValue *b);
8686void eval_min_max_value(CodeGen *g, TypeTableEntry *type_entry, ConstExprValue *const_val, bool is_max);
87void eval_min_max_value_int(CodeGen *g, TypeTableEntry *int_type, BigNum *bignum, bool is_max);
8788int64_t min_signed_val(TypeTableEntry *type_entry);
8889uint64_t max_unsigned_val(TypeTableEntry *type_entry);
8990
src/ir.cpp+48-6
......@@ -12,6 +12,7 @@
1212#include "ir_print.hpp"
1313#include "os.hpp"
1414#include "parseh.hpp"
15#include "range_set.hpp"
1516
1617struct IrExecContext {
1718 ConstExprValue *mem_slot_list;
......@@ -12981,7 +12982,7 @@ static TypeTableEntry *ir_analyze_instruction_check_switch_prongs(IrAnalyze *ira
1298112982
1298212983 if (switch_type->id == TypeTableEntryIdEnumTag) {
1298312984 TypeTableEntry *enum_type = switch_type->data.enum_tag.enum_type;
12984 size_t *field_use_counts = allocate<size_t>(enum_type->data.enumeration.src_field_count);
12985 AstNode **field_prev_uses = allocate<AstNode *>(enum_type->data.enumeration.src_field_count);
1298512986 for (size_t range_i = 0; range_i < instruction->range_count; range_i += 1) {
1298612987 IrInstructionCheckSwitchProngsRange *range = &instruction->ranges[range_i];
1298712988
......@@ -13011,24 +13012,65 @@ static TypeTableEntry *ir_analyze_instruction_check_switch_prongs(IrAnalyze *ira
1301113012 }
1301213013
1301313014 for (size_t field_index = start_index; field_index <= end_index; field_index += 1) {
13014 field_use_counts[field_index] += 1;
13015 if (field_use_counts[field_index] > 1) {
13015 AstNode *prev_node = field_prev_uses[field_index];
13016 if (prev_node != nullptr) {
1301613017 TypeEnumField *type_enum_field = &enum_type->data.enumeration.fields[field_index];
13017 ir_add_error(ira, start_value,
13018 ErrorMsg *msg = ir_add_error(ira, start_value,
1301813019 buf_sprintf("duplicate switch value: '%s.%s'", buf_ptr(&enum_type->name),
1301913020 buf_ptr(type_enum_field->name)));
13021 add_error_note(ira->codegen, msg, prev_node, buf_sprintf("other value is here"));
1302013022 }
13023 field_prev_uses[field_index] = start_value->source_node;
1302113024 }
1302213025 }
1302313026 for (uint32_t i = 0; i < enum_type->data.enumeration.src_field_count; i += 1) {
13024 if (field_use_counts[i] == 0) {
13027 if (field_prev_uses[i] == nullptr) {
1302513028 ir_add_error(ira, &instruction->base,
1302613029 buf_sprintf("enumeration value '%s.%s' not handled in switch", buf_ptr(&enum_type->name),
1302713030 buf_ptr(enum_type->data.enumeration.fields[i].name)));
1302813031 }
1302913032 }
13033 } else if (switch_type->id == TypeTableEntryIdInt) {
13034 RangeSet rs = {0};
13035 for (size_t range_i = 0; range_i < instruction->range_count; range_i += 1) {
13036 IrInstructionCheckSwitchProngsRange *range = &instruction->ranges[range_i];
13037
13038 IrInstruction *start_value = range->start->other;
13039 if (type_is_invalid(start_value->value.type))
13040 return ira->codegen->builtin_types.entry_invalid;
13041
13042 IrInstruction *end_value = range->end->other;
13043 if (type_is_invalid(end_value->value.type))
13044 return ira->codegen->builtin_types.entry_invalid;
13045
13046 ConstExprValue *start_val = ir_resolve_const(ira, start_value, UndefBad);
13047 if (!start_val)
13048 return ira->codegen->builtin_types.entry_invalid;
13049
13050 ConstExprValue *end_val = ir_resolve_const(ira, end_value, UndefBad);
13051 if (!end_val)
13052 return ira->codegen->builtin_types.entry_invalid;
13053
13054 AstNode *prev_node = rangeset_add_range(&rs, &start_val->data.x_bignum, &end_val->data.x_bignum,
13055 start_value->source_node);
13056 if (prev_node != nullptr) {
13057 ErrorMsg *msg = ir_add_error(ira, start_value, buf_sprintf("duplicate switch value"));
13058 add_error_note(ira->codegen, msg, prev_node, buf_sprintf("previous value is here"));
13059 return ira->codegen->builtin_types.entry_invalid;
13060 }
13061 }
13062 BigNum min_val;
13063 eval_min_max_value_int(ira->codegen, switch_type, &min_val, false);
13064 BigNum max_val;
13065 eval_min_max_value_int(ira->codegen, switch_type, &max_val, true);
13066 if (!rangeset_spans(&rs, &min_val, &max_val)) {
13067 ir_add_error(ira, &instruction->base, buf_sprintf("switch must handle all possibilities"));
13068 return ira->codegen->builtin_types.entry_invalid;
13069 }
1303013070 } else {
13031 // TODO check prongs of types other than enumtag
13071 ir_add_error(ira, &instruction->base,
13072 buf_sprintf("else prong required when switching on type '%s'", buf_ptr(&switch_type->name)));
13073 return ira->codegen->builtin_types.entry_invalid;
1303213074 }
1303313075 ir_build_const_from(ira, &instruction->base);
1303413076 return ira->codegen->builtin_types.entry_void;
src/range_set.cpp created+81
......@@ -0,0 +1,81 @@
1#include "range_set.hpp"
2
3AstNode *rangeset_add_range(RangeSet *rs, BigNum *first, BigNum *last, AstNode *source_node) {
4 for (size_t i = 0; i < rs->src_range_list.length; i += 1) {
5 RangeWithSrc *range_with_src = &rs->src_range_list.at(i);
6 Range *range = &range_with_src->range;
7 if ((bignum_cmp_gte(first, &range->first) && bignum_cmp_lte(first, &range->last)) ||
8 (bignum_cmp_gte(last, &range->first) && bignum_cmp_lte(last, &range->last)))
9 {
10 return range_with_src->source_node;
11 }
12 }
13 rs->src_range_list.append({{*first, *last}, source_node});
14
15 return nullptr;
16
17}
18
19static bool add_range(ZigList<Range> *list, Range *new_range, BigNum *one) {
20 for (size_t i = 0; i < list->length; i += 1) {
21 Range *range = &list->at(i);
22
23 BigNum first_minus_one;
24 if (bignum_sub(&first_minus_one, &range->first, one))
25 zig_unreachable();
26
27 if (bignum_cmp_eq(&new_range->last, &first_minus_one)) {
28 range->first = new_range->first;
29 return true;
30 }
31
32 BigNum last_plus_one;
33 if (bignum_add(&last_plus_one, &range->last, one))
34 zig_unreachable();
35
36 if (bignum_cmp_eq(&new_range->first, &last_plus_one)) {
37 range->last = new_range->last;
38 return true;
39 }
40 }
41 list->append({new_range->first, new_range->last});
42 return false;
43}
44
45bool rangeset_spans(RangeSet *rs, BigNum *first, BigNum *last) {
46 ZigList<Range> cur_list_value = {0};
47 ZigList<Range> other_list_value = {0};
48 ZigList<Range> *cur_list = &cur_list_value;
49 ZigList<Range> *other_list = &other_list_value;
50
51 for (size_t i = 0; i < rs->src_range_list.length; i += 1) {
52 RangeWithSrc *range_with_src = &rs->src_range_list.at(i);
53 Range *range = &range_with_src->range;
54 cur_list->append({range->first, range->last});
55 }
56
57 BigNum one;
58 bignum_init_unsigned(&one, 1);
59
60 bool changes_made = true;
61 while (changes_made) {
62 changes_made = false;
63 for (size_t cur_i = 0; cur_i < cur_list->length; cur_i += 1) {
64 Range *range = &cur_list->at(cur_i);
65 changes_made = add_range(other_list, range, &one) || changes_made;
66 }
67 ZigList<Range> *tmp = cur_list;
68 cur_list = other_list;
69 other_list = tmp;
70 other_list->resize(0);
71 }
72
73 if (cur_list->length != 1)
74 return false;
75 Range *range = &cur_list->at(0);
76 if (bignum_cmp_neq(&range->first, first))
77 return false;
78 if (bignum_cmp_neq(&range->last, last))
79 return false;
80 return true;
81}
src/range_set.hpp created+30
......@@ -0,0 +1,30 @@
1/*
2 * Copyright (c) 2017 Andrew Kelley
3 *
4 * This file is part of zig, which is MIT licensed.
5 * See http://opensource.org/licenses/MIT
6 */
7
8#ifndef ZIG_RANGE_SET_HPP
9#define ZIG_RANGE_SET_HPP
10
11#include "all_types.hpp"
12
13struct Range {
14 BigNum first;
15 BigNum last;
16};
17
18struct RangeWithSrc {
19 Range range;
20 AstNode *source_node;
21};
22
23struct RangeSet {
24 ZigList<RangeWithSrc> src_range_list;
25};
26
27AstNode *rangeset_add_range(RangeSet *rs, BigNum *first, BigNum *last, AstNode *source_node);
28bool rangeset_spans(RangeSet *rs, BigNum *first, BigNum *last);
29
30#endif
test/cases/switch.zig+46-8
......@@ -1,6 +1,6 @@
11const assert = @import("std").debug.assert;
22
3test "switchWithNumbers" {
3test "switch with numbers" {
44 testSwitchWithNumbers(13);
55}
66
......@@ -13,7 +13,7 @@ fn testSwitchWithNumbers(x: u32) {
1313 assert(result);
1414}
1515
16test "switchWithAllRanges" {
16test "switch with all ranges" {
1717 assert(testSwitchWithAllRanges(50, 3) == 1);
1818 assert(testSwitchWithAllRanges(101, 0) == 2);
1919 assert(testSwitchWithAllRanges(300, 5) == 3);
......@@ -29,7 +29,7 @@ fn testSwitchWithAllRanges(x: u32, y: u32) -> u32 {
2929 }
3030}
3131
32test "implicitComptimeSwitch" {
32test "implicit comptime switch" {
3333 const x = 3 + 4;
3434 const result = switch (x) {
3535 3 => 10,
......@@ -44,7 +44,7 @@ test "implicitComptimeSwitch" {
4444 }
4545}
4646
47test "switchOnEnum" {
47test "switch on enum" {
4848 const fruit = Fruit.Orange;
4949 nonConstSwitchOnEnum(fruit);
5050}
......@@ -62,7 +62,7 @@ fn nonConstSwitchOnEnum(fruit: Fruit) {
6262}
6363
6464
65test "switchStatement" {
65test "switch statement" {
6666 nonConstSwitch(SwitchStatmentFoo.C);
6767}
6868fn nonConstSwitch(foo: SwitchStatmentFoo) {
......@@ -82,7 +82,7 @@ const SwitchStatmentFoo = enum {
8282};
8383
8484
85test "switchProngWithVar" {
85test "switch prong with variable" {
8686 switchProngWithVarFn(SwitchProngWithVarEnum.One {13});
8787 switchProngWithVarFn(SwitchProngWithVarEnum.Two {13.0});
8888 switchProngWithVarFn(SwitchProngWithVarEnum.Meh);
......@@ -107,7 +107,7 @@ fn switchProngWithVarFn(a: &const SwitchProngWithVarEnum) {
107107}
108108
109109
110test "switchWithMultipleExpressions" {
110test "switch with multiple expressions" {
111111 const x = switch (returnsFive()) {
112112 1, 2, 3 => 1,
113113 4, 5, 6 => 2,
......@@ -135,7 +135,7 @@ fn returnsFalse() -> bool {
135135 Number.Three => |x| return x > 12.34,
136136 }
137137}
138test "switchOnConstEnumWithVar" {
138test "switch on const enum with var" {
139139 assert(!returnsFalse());
140140}
141141
......@@ -150,3 +150,41 @@ fn trueIfBoolFalseOtherwise(comptime T: type) -> bool {
150150 else => false,
151151 }
152152}
153
154test "switch handles all cases of number" {
155 testSwitchHandleAllCases();
156 comptime testSwitchHandleAllCases();
157}
158
159const u2 = @IntType(false, 2);
160fn testSwitchHandleAllCases() {
161 assert(testSwitchHandleAllCasesExhaustive(0) == 3);
162 assert(testSwitchHandleAllCasesExhaustive(1) == 2);
163 assert(testSwitchHandleAllCasesExhaustive(2) == 1);
164 assert(testSwitchHandleAllCasesExhaustive(3) == 0);
165
166 assert(testSwitchHandleAllCasesRange(100) == 0);
167 assert(testSwitchHandleAllCasesRange(200) == 1);
168 assert(testSwitchHandleAllCasesRange(201) == 2);
169 assert(testSwitchHandleAllCasesRange(202) == 4);
170 assert(testSwitchHandleAllCasesRange(230) == 3);
171}
172
173fn testSwitchHandleAllCasesExhaustive(x: u2) -> u2 {
174 switch (x) {
175 0 => u2(3),
176 1 => 2,
177 2 => 1,
178 3 => 0,
179 }
180}
181
182fn testSwitchHandleAllCasesRange(x: u8) -> u8 {
183 switch (x) {
184 0 ... 100 => u8(0),
185 101 ... 200 => 1,
186 201, 203 => 2,
187 202 => 4,
188 204 ... 255 => 3,
189 }
190}
test/cases/try.zig+1
......@@ -12,6 +12,7 @@ fn tryOnErrorUnionImpl() {
1212 } else |err| switch (err) {
1313 error.ItBroke, error.NoMem => 1,
1414 error.CrappedOut => i32(2),
15 else => unreachable,
1516 };
1617 assert(x == 11);
1718}
test/compile_errors.zig+75-19
......@@ -542,7 +542,46 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
542542 ".tmp_source.zig:5:5: error: redefinition of 'Bar'",
543543 ".tmp_source.zig:2:1: note: previous definition is here");
544544
545 cases.add("multiple else prongs in a switch",
545 cases.add("switch expression - missing enumeration prong",
546 \\const Number = enum {
547 \\ One,
548 \\ Two,
549 \\ Three,
550 \\ Four,
551 \\};
552 \\fn f(n: Number) -> i32 {
553 \\ switch (n) {
554 \\ Number.One => 1,
555 \\ Number.Two => 2,
556 \\ Number.Three => i32(3),
557 \\ }
558 \\}
559 \\
560 \\export fn entry() -> usize { @sizeOf(@typeOf(f)) }
561 , ".tmp_source.zig:8:5: error: enumeration value 'Number.Four' not handled in switch");
562
563 cases.add("switch expression - duplicate enumeration prong",
564 \\const Number = enum {
565 \\ One,
566 \\ Two,
567 \\ Three,
568 \\ Four,
569 \\};
570 \\fn f(n: Number) -> i32 {
571 \\ switch (n) {
572 \\ Number.One => 1,
573 \\ Number.Two => 2,
574 \\ Number.Three => i32(3),
575 \\ Number.Four => 4,
576 \\ Number.Two => 2,
577 \\ }
578 \\}
579 \\
580 \\export fn entry() -> usize { @sizeOf(@typeOf(f)) }
581 , ".tmp_source.zig:13:15: error: duplicate switch value",
582 ".tmp_source.zig:10:15: note: other value is here");
583
584 cases.add("switch expression - multiple else prongs",
546585 \\fn f(x: u32) {
547586 \\ const value: bool = switch (x) {
548587 \\ 1234 => false,
......@@ -555,6 +594,41 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
555594 \\}
556595 , ".tmp_source.zig:5:9: error: multiple else prongs in switch expression");
557596
597 cases.add("switch expression - non exhaustive integer prongs",
598 \\fn foo(x: u8) {
599 \\ switch (x) {
600 \\ 0 => {},
601 \\ }
602 \\}
603 \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) }
604 ,
605 ".tmp_source.zig:2:5: error: switch must handle all possibilities");
606
607 cases.add("switch expression - duplicate or overlapping integer value",
608 \\fn foo(x: u8) -> u8 {
609 \\ switch (x) {
610 \\ 0 ... 100 => u8(0),
611 \\ 101 ... 200 => 1,
612 \\ 201, 203 ... 207 => 2,
613 \\ 206 ... 255 => 3,
614 \\ }
615 \\}
616 \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) }
617 ,
618 ".tmp_source.zig:6:9: error: duplicate switch value",
619 ".tmp_source.zig:5:14: note: previous value is here");
620
621 cases.add("switch expression - switch on pointer type with no else",
622 \\fn foo(x: &u8) {
623 \\ switch (x) {
624 \\ &y => {},
625 \\ }
626 \\}
627 \\const y: u8 = 100;
628 \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) }
629 ,
630 ".tmp_source.zig:2:5: error: else prong required when switching on type '&u8'");
631
558632 cases.add("global variable initializer must be constant expression",
559633 \\extern fn foo() -> i32;
560634 \\const x = foo();
......@@ -716,24 +790,6 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
716790 ".tmp_source.zig:4:26: error: division by zero is undefined");
717791
718792
719 cases.add("missing switch prong",
720 \\const Number = enum {
721 \\ One,
722 \\ Two,
723 \\ Three,
724 \\ Four,
725 \\};
726 \\fn f(n: Number) -> i32 {
727 \\ switch (n) {
728 \\ Number.One => 1,
729 \\ Number.Two => 2,
730 \\ Number.Three => i32(3),
731 \\ }
732 \\}
733 \\
734 \\export fn entry() -> usize { @sizeOf(@typeOf(f)) }
735 , ".tmp_source.zig:8:5: error: enumeration value 'Number.Four' not handled in switch");
736
737793 cases.add("normal string with newline",
738794 \\const foo = "a
739795 \\b";