diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b3a7eb25a..a44a9c7780 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,6 +56,7 @@ set(ZIG_SOURCES "${CMAKE_SOURCE_DIR}/src/main.cpp" "${CMAKE_SOURCE_DIR}/src/os.cpp" "${CMAKE_SOURCE_DIR}/src/parser.cpp" + "${CMAKE_SOURCE_DIR}/src/range_set.cpp" "${CMAKE_SOURCE_DIR}/src/target.cpp" "${CMAKE_SOURCE_DIR}/src/tokenizer.cpp" "${CMAKE_SOURCE_DIR}/src/util.cpp" diff --git a/src/analyze.cpp b/src/analyze.cpp index e1d8ca2869..f397f441c8 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -3851,25 +3851,30 @@ int64_t min_signed_val(TypeTableEntry *type_entry) { } } +void eval_min_max_value_int(CodeGen *g, TypeTableEntry *int_type, BigNum *bignum, bool is_max) { + assert(int_type->id == TypeTableEntryIdInt); + if (is_max) { + if (int_type->data.integral.is_signed) { + int64_t val = max_signed_val(int_type); + bignum_init_signed(bignum, val); + } else { + uint64_t val = max_unsigned_val(int_type); + bignum_init_unsigned(bignum, val); + } + } else { + if (int_type->data.integral.is_signed) { + int64_t val = min_signed_val(int_type); + bignum_init_signed(bignum, val); + } else { + bignum_init_unsigned(bignum, 0); + } + } +} + void eval_min_max_value(CodeGen *g, TypeTableEntry *type_entry, ConstExprValue *const_val, bool is_max) { if (type_entry->id == TypeTableEntryIdInt) { const_val->special = ConstValSpecialStatic; - if (is_max) { - if (type_entry->data.integral.is_signed) { - int64_t val = max_signed_val(type_entry); - bignum_init_signed(&const_val->data.x_bignum, val); - } else { - uint64_t val = max_unsigned_val(type_entry); - bignum_init_unsigned(&const_val->data.x_bignum, val); - } - } else { - if (type_entry->data.integral.is_signed) { - int64_t val = min_signed_val(type_entry); - bignum_init_signed(&const_val->data.x_bignum, val); - } else { - bignum_init_unsigned(&const_val->data.x_bignum, 0); - } - } + eval_min_max_value_int(g, type_entry, &const_val->data.x_bignum, is_max); } else if (type_entry->id == TypeTableEntryIdFloat) { zig_panic("TODO analyze_min_max_value float"); } else if (type_entry->id == TypeTableEntryIdBool) { diff --git a/src/analyze.hpp b/src/analyze.hpp index e9cc1560ab..02c7a5dda0 100644 --- a/src/analyze.hpp +++ b/src/analyze.hpp @@ -84,6 +84,7 @@ void complete_enum(CodeGen *g, TypeTableEntry *enum_type); bool ir_get_var_is_comptime(VariableTableEntry *var); bool const_values_equal(ConstExprValue *a, ConstExprValue *b); void eval_min_max_value(CodeGen *g, TypeTableEntry *type_entry, ConstExprValue *const_val, bool is_max); +void eval_min_max_value_int(CodeGen *g, TypeTableEntry *int_type, BigNum *bignum, bool is_max); int64_t min_signed_val(TypeTableEntry *type_entry); uint64_t max_unsigned_val(TypeTableEntry *type_entry); diff --git a/src/ir.cpp b/src/ir.cpp index b24fe3b9ea..6f8322c9d9 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -12,6 +12,7 @@ #include "ir_print.hpp" #include "os.hpp" #include "parseh.hpp" +#include "range_set.hpp" struct IrExecContext { ConstExprValue *mem_slot_list; @@ -12981,7 +12982,7 @@ static TypeTableEntry *ir_analyze_instruction_check_switch_prongs(IrAnalyze *ira if (switch_type->id == TypeTableEntryIdEnumTag) { TypeTableEntry *enum_type = switch_type->data.enum_tag.enum_type; - size_t *field_use_counts = allocate(enum_type->data.enumeration.src_field_count); + AstNode **field_prev_uses = allocate(enum_type->data.enumeration.src_field_count); for (size_t range_i = 0; range_i < instruction->range_count; range_i += 1) { IrInstructionCheckSwitchProngsRange *range = &instruction->ranges[range_i]; @@ -13011,24 +13012,65 @@ static TypeTableEntry *ir_analyze_instruction_check_switch_prongs(IrAnalyze *ira } for (size_t field_index = start_index; field_index <= end_index; field_index += 1) { - field_use_counts[field_index] += 1; - if (field_use_counts[field_index] > 1) { + AstNode *prev_node = field_prev_uses[field_index]; + if (prev_node != nullptr) { TypeEnumField *type_enum_field = &enum_type->data.enumeration.fields[field_index]; - ir_add_error(ira, start_value, + ErrorMsg *msg = ir_add_error(ira, start_value, buf_sprintf("duplicate switch value: '%s.%s'", buf_ptr(&enum_type->name), buf_ptr(type_enum_field->name))); + add_error_note(ira->codegen, msg, prev_node, buf_sprintf("other value is here")); } + field_prev_uses[field_index] = start_value->source_node; } } for (uint32_t i = 0; i < enum_type->data.enumeration.src_field_count; i += 1) { - if (field_use_counts[i] == 0) { + if (field_prev_uses[i] == nullptr) { ir_add_error(ira, &instruction->base, buf_sprintf("enumeration value '%s.%s' not handled in switch", buf_ptr(&enum_type->name), buf_ptr(enum_type->data.enumeration.fields[i].name))); } } + } else if (switch_type->id == TypeTableEntryIdInt) { + RangeSet rs = {0}; + for (size_t range_i = 0; range_i < instruction->range_count; range_i += 1) { + IrInstructionCheckSwitchProngsRange *range = &instruction->ranges[range_i]; + + IrInstruction *start_value = range->start->other; + if (type_is_invalid(start_value->value.type)) + return ira->codegen->builtin_types.entry_invalid; + + IrInstruction *end_value = range->end->other; + if (type_is_invalid(end_value->value.type)) + return ira->codegen->builtin_types.entry_invalid; + + ConstExprValue *start_val = ir_resolve_const(ira, start_value, UndefBad); + if (!start_val) + return ira->codegen->builtin_types.entry_invalid; + + ConstExprValue *end_val = ir_resolve_const(ira, end_value, UndefBad); + if (!end_val) + return ira->codegen->builtin_types.entry_invalid; + + AstNode *prev_node = rangeset_add_range(&rs, &start_val->data.x_bignum, &end_val->data.x_bignum, + start_value->source_node); + if (prev_node != nullptr) { + ErrorMsg *msg = ir_add_error(ira, start_value, buf_sprintf("duplicate switch value")); + add_error_note(ira->codegen, msg, prev_node, buf_sprintf("previous value is here")); + return ira->codegen->builtin_types.entry_invalid; + } + } + BigNum min_val; + eval_min_max_value_int(ira->codegen, switch_type, &min_val, false); + BigNum max_val; + eval_min_max_value_int(ira->codegen, switch_type, &max_val, true); + if (!rangeset_spans(&rs, &min_val, &max_val)) { + ir_add_error(ira, &instruction->base, buf_sprintf("switch must handle all possibilities")); + return ira->codegen->builtin_types.entry_invalid; + } } else { - // TODO check prongs of types other than enumtag + ir_add_error(ira, &instruction->base, + buf_sprintf("else prong required when switching on type '%s'", buf_ptr(&switch_type->name))); + return ira->codegen->builtin_types.entry_invalid; } ir_build_const_from(ira, &instruction->base); return ira->codegen->builtin_types.entry_void; diff --git a/src/range_set.cpp b/src/range_set.cpp new file mode 100644 index 0000000000..6158f52a00 --- /dev/null +++ b/src/range_set.cpp @@ -0,0 +1,81 @@ +#include "range_set.hpp" + +AstNode *rangeset_add_range(RangeSet *rs, BigNum *first, BigNum *last, AstNode *source_node) { + for (size_t i = 0; i < rs->src_range_list.length; i += 1) { + RangeWithSrc *range_with_src = &rs->src_range_list.at(i); + Range *range = &range_with_src->range; + if ((bignum_cmp_gte(first, &range->first) && bignum_cmp_lte(first, &range->last)) || + (bignum_cmp_gte(last, &range->first) && bignum_cmp_lte(last, &range->last))) + { + return range_with_src->source_node; + } + } + rs->src_range_list.append({{*first, *last}, source_node}); + + return nullptr; + +} + +static bool add_range(ZigList *list, Range *new_range, BigNum *one) { + for (size_t i = 0; i < list->length; i += 1) { + Range *range = &list->at(i); + + BigNum first_minus_one; + if (bignum_sub(&first_minus_one, &range->first, one)) + zig_unreachable(); + + if (bignum_cmp_eq(&new_range->last, &first_minus_one)) { + range->first = new_range->first; + return true; + } + + BigNum last_plus_one; + if (bignum_add(&last_plus_one, &range->last, one)) + zig_unreachable(); + + if (bignum_cmp_eq(&new_range->first, &last_plus_one)) { + range->last = new_range->last; + return true; + } + } + list->append({new_range->first, new_range->last}); + return false; +} + +bool rangeset_spans(RangeSet *rs, BigNum *first, BigNum *last) { + ZigList cur_list_value = {0}; + ZigList other_list_value = {0}; + ZigList *cur_list = &cur_list_value; + ZigList *other_list = &other_list_value; + + for (size_t i = 0; i < rs->src_range_list.length; i += 1) { + RangeWithSrc *range_with_src = &rs->src_range_list.at(i); + Range *range = &range_with_src->range; + cur_list->append({range->first, range->last}); + } + + BigNum one; + bignum_init_unsigned(&one, 1); + + bool changes_made = true; + while (changes_made) { + changes_made = false; + for (size_t cur_i = 0; cur_i < cur_list->length; cur_i += 1) { + Range *range = &cur_list->at(cur_i); + changes_made = add_range(other_list, range, &one) || changes_made; + } + ZigList *tmp = cur_list; + cur_list = other_list; + other_list = tmp; + other_list->resize(0); + } + + if (cur_list->length != 1) + return false; + Range *range = &cur_list->at(0); + if (bignum_cmp_neq(&range->first, first)) + return false; + if (bignum_cmp_neq(&range->last, last)) + return false; + return true; +} diff --git a/src/range_set.hpp b/src/range_set.hpp new file mode 100644 index 0000000000..f3ae4a189f --- /dev/null +++ b/src/range_set.hpp @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2017 Andrew Kelley + * + * This file is part of zig, which is MIT licensed. + * See http://opensource.org/licenses/MIT + */ + +#ifndef ZIG_RANGE_SET_HPP +#define ZIG_RANGE_SET_HPP + +#include "all_types.hpp" + +struct Range { + BigNum first; + BigNum last; +}; + +struct RangeWithSrc { + Range range; + AstNode *source_node; +}; + +struct RangeSet { + ZigList src_range_list; +}; + +AstNode *rangeset_add_range(RangeSet *rs, BigNum *first, BigNum *last, AstNode *source_node); +bool rangeset_spans(RangeSet *rs, BigNum *first, BigNum *last); + +#endif diff --git a/test/cases/switch.zig b/test/cases/switch.zig index d1acfe5dbe..ccacbd1c78 100644 --- a/test/cases/switch.zig +++ b/test/cases/switch.zig @@ -1,6 +1,6 @@ const assert = @import("std").debug.assert; -test "switchWithNumbers" { +test "switch with numbers" { testSwitchWithNumbers(13); } @@ -13,7 +13,7 @@ fn testSwitchWithNumbers(x: u32) { assert(result); } -test "switchWithAllRanges" { +test "switch with all ranges" { assert(testSwitchWithAllRanges(50, 3) == 1); assert(testSwitchWithAllRanges(101, 0) == 2); assert(testSwitchWithAllRanges(300, 5) == 3); @@ -29,7 +29,7 @@ fn testSwitchWithAllRanges(x: u32, y: u32) -> u32 { } } -test "implicitComptimeSwitch" { +test "implicit comptime switch" { const x = 3 + 4; const result = switch (x) { 3 => 10, @@ -44,7 +44,7 @@ test "implicitComptimeSwitch" { } } -test "switchOnEnum" { +test "switch on enum" { const fruit = Fruit.Orange; nonConstSwitchOnEnum(fruit); } @@ -62,7 +62,7 @@ fn nonConstSwitchOnEnum(fruit: Fruit) { } -test "switchStatement" { +test "switch statement" { nonConstSwitch(SwitchStatmentFoo.C); } fn nonConstSwitch(foo: SwitchStatmentFoo) { @@ -82,7 +82,7 @@ const SwitchStatmentFoo = enum { }; -test "switchProngWithVar" { +test "switch prong with variable" { switchProngWithVarFn(SwitchProngWithVarEnum.One {13}); switchProngWithVarFn(SwitchProngWithVarEnum.Two {13.0}); switchProngWithVarFn(SwitchProngWithVarEnum.Meh); @@ -107,7 +107,7 @@ fn switchProngWithVarFn(a: &const SwitchProngWithVarEnum) { } -test "switchWithMultipleExpressions" { +test "switch with multiple expressions" { const x = switch (returnsFive()) { 1, 2, 3 => 1, 4, 5, 6 => 2, @@ -135,7 +135,7 @@ fn returnsFalse() -> bool { Number.Three => |x| return x > 12.34, } } -test "switchOnConstEnumWithVar" { +test "switch on const enum with var" { assert(!returnsFalse()); } @@ -150,3 +150,41 @@ fn trueIfBoolFalseOtherwise(comptime T: type) -> bool { else => false, } } + +test "switch handles all cases of number" { + testSwitchHandleAllCases(); + comptime testSwitchHandleAllCases(); +} + +const u2 = @IntType(false, 2); +fn testSwitchHandleAllCases() { + assert(testSwitchHandleAllCasesExhaustive(0) == 3); + assert(testSwitchHandleAllCasesExhaustive(1) == 2); + assert(testSwitchHandleAllCasesExhaustive(2) == 1); + assert(testSwitchHandleAllCasesExhaustive(3) == 0); + + assert(testSwitchHandleAllCasesRange(100) == 0); + assert(testSwitchHandleAllCasesRange(200) == 1); + assert(testSwitchHandleAllCasesRange(201) == 2); + assert(testSwitchHandleAllCasesRange(202) == 4); + assert(testSwitchHandleAllCasesRange(230) == 3); +} + +fn testSwitchHandleAllCasesExhaustive(x: u2) -> u2 { + switch (x) { + 0 => u2(3), + 1 => 2, + 2 => 1, + 3 => 0, + } +} + +fn testSwitchHandleAllCasesRange(x: u8) -> u8 { + switch (x) { + 0 ... 100 => u8(0), + 101 ... 200 => 1, + 201, 203 => 2, + 202 => 4, + 204 ... 255 => 3, + } +} diff --git a/test/cases/try.zig b/test/cases/try.zig index 4ed716d7d2..2f63836d6f 100644 --- a/test/cases/try.zig +++ b/test/cases/try.zig @@ -12,6 +12,7 @@ fn tryOnErrorUnionImpl() { } else |err| switch (err) { error.ItBroke, error.NoMem => 1, error.CrappedOut => i32(2), + else => unreachable, }; assert(x == 11); } diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 4d6877267d..54ab8444b8 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -542,7 +542,46 @@ pub fn addCases(cases: &tests.CompileErrorContext) { ".tmp_source.zig:5:5: error: redefinition of 'Bar'", ".tmp_source.zig:2:1: note: previous definition is here"); - cases.add("multiple else prongs in a switch", + cases.add("switch expression - missing enumeration prong", + \\const Number = enum { + \\ One, + \\ Two, + \\ Three, + \\ Four, + \\}; + \\fn f(n: Number) -> i32 { + \\ switch (n) { + \\ Number.One => 1, + \\ Number.Two => 2, + \\ Number.Three => i32(3), + \\ } + \\} + \\ + \\export fn entry() -> usize { @sizeOf(@typeOf(f)) } + , ".tmp_source.zig:8:5: error: enumeration value 'Number.Four' not handled in switch"); + + cases.add("switch expression - duplicate enumeration prong", + \\const Number = enum { + \\ One, + \\ Two, + \\ Three, + \\ Four, + \\}; + \\fn f(n: Number) -> i32 { + \\ switch (n) { + \\ Number.One => 1, + \\ Number.Two => 2, + \\ Number.Three => i32(3), + \\ Number.Four => 4, + \\ Number.Two => 2, + \\ } + \\} + \\ + \\export fn entry() -> usize { @sizeOf(@typeOf(f)) } + , ".tmp_source.zig:13:15: error: duplicate switch value", + ".tmp_source.zig:10:15: note: other value is here"); + + cases.add("switch expression - multiple else prongs", \\fn f(x: u32) { \\ const value: bool = switch (x) { \\ 1234 => false, @@ -555,6 +594,41 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\} , ".tmp_source.zig:5:9: error: multiple else prongs in switch expression"); + cases.add("switch expression - non exhaustive integer prongs", + \\fn foo(x: u8) { + \\ switch (x) { + \\ 0 => {}, + \\ } + \\} + \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) } + , + ".tmp_source.zig:2:5: error: switch must handle all possibilities"); + + cases.add("switch expression - duplicate or overlapping integer value", + \\fn foo(x: u8) -> u8 { + \\ switch (x) { + \\ 0 ... 100 => u8(0), + \\ 101 ... 200 => 1, + \\ 201, 203 ... 207 => 2, + \\ 206 ... 255 => 3, + \\ } + \\} + \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) } + , + ".tmp_source.zig:6:9: error: duplicate switch value", + ".tmp_source.zig:5:14: note: previous value is here"); + + cases.add("switch expression - switch on pointer type with no else", + \\fn foo(x: &u8) { + \\ switch (x) { + \\ &y => {}, + \\ } + \\} + \\const y: u8 = 100; + \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) } + , + ".tmp_source.zig:2:5: error: else prong required when switching on type '&u8'"); + cases.add("global variable initializer must be constant expression", \\extern fn foo() -> i32; \\const x = foo(); @@ -716,24 +790,6 @@ pub fn addCases(cases: &tests.CompileErrorContext) { ".tmp_source.zig:4:26: error: division by zero is undefined"); - cases.add("missing switch prong", - \\const Number = enum { - \\ One, - \\ Two, - \\ Three, - \\ Four, - \\}; - \\fn f(n: Number) -> i32 { - \\ switch (n) { - \\ Number.One => 1, - \\ Number.Two => 2, - \\ Number.Three => i32(3), - \\ } - \\} - \\ - \\export fn entry() -> usize { @sizeOf(@typeOf(f)) } - , ".tmp_source.zig:8:5: error: enumeration value 'Number.Four' not handled in switch"); - cases.add("normal string with newline", \\const foo = "a \\b";