Browse Source

Implement `#complete switch` by default, replace with `#partial switch` #511

gingerBill 5 years ago
parent
commit
d1c9fd4e01

+ 105 - 105
core/encoding/cel/cel.odin

@@ -106,11 +106,11 @@ create_from_tokenizer :: proc(t: ^Tokenizer) -> (^Parser, bool) {
 	p := new(Parser);
 	for {
 		tok := scan(t);
-		if tok.kind == Kind.Illegal {
+		if tok.kind == .Illegal {
 			return p, false;
 		}
 		append(&p.tokens, tok);
-		if tok.kind == Kind.EOF {
+		if tok.kind == .EOF {
 			break;
 		}
 	}
@@ -120,7 +120,7 @@ create_from_tokenizer :: proc(t: ^Tokenizer) -> (^Parser, bool) {
 	}
 
 	if len(p.tokens) == 0 {
-		tok := Token{kind = Kind.EOF};
+		tok := Token{kind = .EOF};
 		tok.line, tok.column = 1, 1;
 		append(&p.tokens, tok);
 		return p, true;
@@ -134,8 +134,8 @@ create_from_tokenizer :: proc(t: ^Tokenizer) -> (^Parser, bool) {
 	p.dict_stack = make([dynamic]^Dict, 0, 4);
 	append(&p.dict_stack, &p.root);
 
-	for p.curr_token.kind != Kind.EOF &&
-	    p.curr_token.kind != Kind.Illegal &&
+	for p.curr_token.kind != .EOF &&
+	    p.curr_token.kind != .Illegal &&
 	    p.curr_token_index < len(p.tokens) {
 		if !parse_assignment(p) {
 			break;
@@ -147,7 +147,7 @@ create_from_tokenizer :: proc(t: ^Tokenizer) -> (^Parser, bool) {
 
 destroy :: proc(p: ^Parser) {
 	destroy_value :: proc(value: Value) {
-		switch v in value {
+		#partial switch v in value {
 		case Array:
 			for elem in v do destroy_value(elem);
 			delete(v);
@@ -287,7 +287,7 @@ unquote_char :: proc(str: string, quote: byte) -> (r: rune, multiple_bytes: bool
 
 
 unquote_string :: proc(p: ^Parser, t: Token) -> (string, bool) {
-	if t.kind != Kind.String {
+	if t.kind != .String {
 		return t.lit, true;
 	}
 	s := t.lit;
@@ -368,8 +368,8 @@ expect_operator :: proc(p: ^Parser) -> Token {
 
 fix_advance :: proc(p: ^Parser) {
 	for {
-		switch t := p.curr_token; t.kind {
-		case Kind.EOF, Kind.Semicolon:
+		#partial switch t := p.curr_token; t.kind {
+		case .EOF, .Semicolon:
 			return;
 		}
 		next_token(p);
@@ -377,7 +377,7 @@ fix_advance :: proc(p: ^Parser) {
 }
 
 copy_value :: proc(value: Value) -> Value {
-	switch v in value {
+	#partial switch v in value {
 	case Array:
 		a := make(Array, len(v));
 		for elem, idx in v {
@@ -407,79 +407,79 @@ lookup_value :: proc(p: ^Parser, name: string) -> (Value, bool) {
 
 parse_operand :: proc(p: ^Parser) -> (Value, Pos) {
 	tok := p.curr_token;
-	switch p.curr_token.kind {
-	case Kind.Ident:
+	#partial switch p.curr_token.kind {
+	case .Ident:
 		next_token(p);
 		v, ok := lookup_value(p, tok.lit);
 		if !ok do error(p, tok.pos, "Undeclared identifier %s", tok.lit);
 		return v, tok.pos;
 
-	case Kind.True:
+	case .True:
 		next_token(p);
 		return true, tok.pos;
-	case Kind.False:
+	case .False:
 		next_token(p);
 		return false, tok.pos;
 
-	case Kind.Nil:
+	case .Nil:
 		next_token(p);
 		return Nil_Value{}, tok.pos;
 
-	case Kind.Integer:
+	case .Integer:
 		next_token(p);
 		return strconv.parse_i64(tok.lit), tok.pos;
 
-	case Kind.Float:
+	case .Float:
 		next_token(p);
 		return strconv.parse_f64(tok.lit), tok.pos;
 
-	case Kind.String:
+	case .String:
 		next_token(p);
 		str, ok := unquote_string(p, tok);
 		if !ok do error(p, tok.pos, "Unable to unquote string");
 		return string(str), tok.pos;
 
-	case Kind.Open_Paren:
-		expect_token(p, Kind.Open_Paren);
+	case .Open_Paren:
+		expect_token(p, .Open_Paren);
 		expr, _ := parse_expr(p);
-		expect_token(p, Kind.Close_Paren);
+		expect_token(p, .Close_Paren);
 		return expr, tok.pos;
 
-	case Kind.Open_Bracket:
-		expect_token(p, Kind.Open_Bracket);
+	case .Open_Bracket:
+		expect_token(p, .Open_Bracket);
 		elems := make([dynamic]Value, 0, 4);
-		for p.curr_token.kind != Kind.Close_Bracket &&
-		    p.curr_token.kind != Kind.EOF {
+		for p.curr_token.kind != .Close_Bracket &&
+		    p.curr_token.kind != .EOF {
 			elem, _ := parse_expr(p);
 			append(&elems, elem);
 
-			if p.curr_token.kind == Kind.Semicolon && p.curr_token.lit == "\n" {
+			if p.curr_token.kind == .Semicolon && p.curr_token.lit == "\n" {
 				next_token(p);
-			} else if !allow_token(p, Kind.Comma) {
+			} else if !allow_token(p, .Comma) {
 				break;
 			}
 
 		}
-		expect_token(p, Kind.Close_Bracket);
+		expect_token(p, .Close_Bracket);
 		return Array(elems[:]), tok.pos;
 
-	case Kind.Open_Brace:
-		expect_token(p, Kind.Open_Brace);
+	case .Open_Brace:
+		expect_token(p, .Open_Brace);
 
     	dict := Dict{};
     	append(&p.dict_stack, &dict);
     	defer pop(&p.dict_stack);
 
-		for p.curr_token.kind != Kind.Close_Brace &&
-		    p.curr_token.kind != Kind.EOF {
+		for p.curr_token.kind != .Close_Brace &&
+		    p.curr_token.kind != .EOF {
 		    name_tok := p.curr_token;
-		    if !allow_token(p, Kind.Ident) && !allow_token(p, Kind.String) {
-		    	name_tok = expect_token(p, Kind.Ident);
+		    if !allow_token(p, .Ident) && !allow_token(p, .String) {
+		    	name_tok = expect_token(p, .Ident);
 		    }
 
 			name, ok := unquote_string(p, name_tok);
 			if !ok do error(p, tok.pos, "Unable to unquote string");
-		    expect_token(p, Kind.Assign);
+		    expect_token(p, .Assign);
 			elem, _ := parse_expr(p);
 
 			if _, ok2 := dict[name]; ok2 {
@@ -488,13 +488,13 @@ parse_operand :: proc(p: ^Parser) -> (Value, Pos) {
 				dict[name] = elem;
 			}
 
-			if p.curr_token.kind == Kind.Semicolon && p.curr_token.lit == "\n" {
+			if p.curr_token.kind == .Semicolon && p.curr_token.lit == "\n" {
 				next_token(p);
-			} else if !allow_token(p, Kind.Comma) {
+			} else if !allow_token(p, .Comma) {
 				break;
 			}
 		}
-		expect_token(p, Kind.Close_Brace);
+		expect_token(p, .Close_Brace);
 		return dict, tok.pos;
 
 	}
@@ -504,13 +504,13 @@ parse_operand :: proc(p: ^Parser) -> (Value, Pos) {
 parse_atom_expr :: proc(p: ^Parser, operand: Value, pos: Pos) -> (Value, Pos) {
 	loop := true;
 	for operand := operand; loop;  {
-		switch p.curr_token.kind {
-		case Kind.Period:
+		#partial switch p.curr_token.kind {
+		case .Period:
 			next_token(p);
 			tok := next_token(p);
 
-			switch tok.kind {
-			case Kind.Ident:
+			#partial switch tok.kind {
+			case .Ident:
 				d, ok := operand.(Dict);
 				if !ok || d == nil {
 					error(p, tok.pos, "Expected a dictionary");
@@ -531,13 +531,13 @@ parse_atom_expr :: proc(p: ^Parser, operand: Value, pos: Pos) -> (Value, Pos) {
 				operand = nil;
 			}
 
-		case Kind.Open_Bracket:
-			expect_token(p, Kind.Open_Bracket);
+		case .Open_Bracket:
+			expect_token(p, .Open_Bracket);
 			index, index_pos := parse_expr(p);
-			expect_token(p, Kind.Close_Bracket);
+			expect_token(p, .Close_Bracket);
 
 
-			switch a in operand {
+			#partial switch a in operand {
 			case Array:
 				i, ok := index.(i64);
 				if !ok {
@@ -587,22 +587,22 @@ parse_atom_expr :: proc(p: ^Parser, operand: Value, pos: Pos) -> (Value, Pos) {
 
 parse_unary_expr :: proc(p: ^Parser) -> (Value, Pos) {
 	op := p.curr_token;
-	switch p.curr_token.kind {
-	case Kind.At:
+	#partial switch p.curr_token.kind {
+	case .At:
 		next_token(p);
-		tok := expect_token(p, Kind.String);
+		tok := expect_token(p, .String);
 		v, ok := lookup_value(p, tok.lit);
 		if !ok do error(p, tok.pos, "Undeclared identifier %s", tok.lit);
 		return parse_atom_expr(p, v, tok.pos);
 
-	case Kind.Add, Kind.Sub:
+	case .Add, .Sub:
 		next_token(p);
 		// TODO(bill): Calcuate values as you go!
 		expr, pos := parse_unary_expr(p);
 
-		switch e in expr {
-		case i64: if op.kind == Kind.Sub do return -e, pos;
-		case f64: if op.kind == Kind.Sub do return -e, pos;
+		#partial switch e in expr {
+		case i64: if op.kind == .Sub do return -e, pos;
+		case f64: if op.kind == .Sub do return -e, pos;
 		case:
 			error(p, op.pos, "Unary operator %s can only be used on integers or floats", op.lit);
 			return nil, op.pos;
@@ -610,7 +610,7 @@ parse_unary_expr :: proc(p: ^Parser) -> (Value, Pos) {
 
 		return expr, op.pos;
 
-	case Kind.Not:
+	case .Not:
 		next_token(p);
 		expr, _ := parse_unary_expr(p);
 		if v, ok := expr.(bool); ok {
@@ -625,7 +625,7 @@ parse_unary_expr :: proc(p: ^Parser) -> (Value, Pos) {
 
 
 value_order :: proc(v: Value) -> int {
-	switch _ in v {
+	#partial switch _ in v {
 	case bool, string:
 		return 1;
 	case i64:
@@ -641,13 +641,13 @@ match_values :: proc(left, right: ^Value) -> bool {
 		return match_values(right, left);
 	}
 
-	switch x in left^ {
+	#partial switch x in left^ {
 	case:
 		right^ = left^;
 	case bool, string:
 		return true;
 	case i64:
-		switch y in right^ {
+		#partial switch y in right^ {
 		case i64:
 			return true;
 		case f64:
@@ -656,7 +656,7 @@ match_values :: proc(left, right: ^Value) -> bool {
 		}
 
 	case f64:
-		switch y in right {
+		#partial switch y in right {
 		case f64:
 			return true;
 		}
@@ -671,59 +671,59 @@ calculate_binary_value :: proc(p: ^Parser, op: Kind, a, b: Value) -> (Value, boo
 	match_values(&x, &y);
 
 
-	switch a in x {
+	#partial switch a in x {
 	case: return x, true;
 
 	case bool:
 		b, ok := y.(bool);
 		if !ok do return nil, false;
-		switch op {
-		case Kind.Eq:    return a == b, true;
-		case Kind.NotEq: return a != b, true;
-		case Kind.And:   return a && b, true;
-		case Kind.Or:    return a || b, true;
+		#partial switch op {
+		case .Eq:    return a == b, true;
+		case .NotEq: return a != b, true;
+		case .And:   return a && b, true;
+		case .Or:    return a || b, true;
 		}
 
 	case i64:
 		b, ok := y.(i64);
 		if !ok do return nil, false;
-		switch op {
-		case Kind.Add:   return a + b, true;
-		case Kind.Sub:   return a - b, true;
-		case Kind.Mul:   return a * b, true;
-		case Kind.Quo:   return a / b, true;
-		case Kind.Rem:   return a % b, true;
-		case Kind.Eq:    return a == b, true;
-		case Kind.NotEq: return a != b, true;
-		case Kind.Lt:    return a <  b, true;
-		case Kind.Gt:    return a >  b, true;
-		case Kind.LtEq:  return a <= b, true;
-		case Kind.GtEq:  return a >= b, true;
+		#partial switch op {
+		case .Add:   return a + b, true;
+		case .Sub:   return a - b, true;
+		case .Mul:   return a * b, true;
+		case .Quo:   return a / b, true;
+		case .Rem:   return a % b, true;
+		case .Eq:    return a == b, true;
+		case .NotEq: return a != b, true;
+		case .Lt:    return a <  b, true;
+		case .Gt:    return a >  b, true;
+		case .LtEq:  return a <= b, true;
+		case .GtEq:  return a >= b, true;
 		}
 
 	case f64:
 		b, ok := y.(f64);
 		if !ok do return nil, false;
 
-		switch op {
-		case Kind.Add:   return a + b, true;
-		case Kind.Sub:   return a - b, true;
-		case Kind.Mul:   return a * b, true;
-		case Kind.Quo:   return a / b, true;
-		case Kind.Eq:    return a == b, true;
-		case Kind.NotEq: return a != b, true;
-		case Kind.Lt:    return a <  b, true;
-		case Kind.Gt:    return a >  b, true;
-		case Kind.LtEq:  return a <= b, true;
-		case Kind.GtEq:  return a >= b, true;
+		#partial switch op {
+		case .Add:   return a + b, true;
+		case .Sub:   return a - b, true;
+		case .Mul:   return a * b, true;
+		case .Quo:   return a / b, true;
+		case .Eq:    return a == b, true;
+		case .NotEq: return a != b, true;
+		case .Lt:    return a <  b, true;
+		case .Gt:    return a >  b, true;
+		case .LtEq:  return a <= b, true;
+		case .GtEq:  return a >= b, true;
 		}
 
 	case string:
 		b, ok := y.(string);
 		if !ok do return nil, false;
 
-		switch op {
-		case Kind.Add:
+		#partial switch op {
+		case .Add:
 			n := len(a) + len(b);
 			data := make([]byte, n);
 			copy(data[:], a);
@@ -732,12 +732,12 @@ calculate_binary_value :: proc(p: ^Parser, op: Kind, a, b: Value) -> (Value, boo
 			append(&p.allocated_strings, s);
 			return s, true;
 
-		case Kind.Eq:    return a == b, true;
-		case Kind.NotEq: return a != b, true;
-		case Kind.Lt:    return a <  b, true;
-		case Kind.Gt:    return a >  b, true;
-		case Kind.LtEq:  return a <= b, true;
-		case Kind.GtEq:  return a >= b, true;
+		case .Eq:    return a == b, true;
+		case .NotEq: return a != b, true;
+		case .Lt:    return a <  b, true;
+		case .Gt:    return a >  b, true;
+		case .LtEq:  return a <= b, true;
+		case .GtEq:  return a >= b, true;
 		}
 	}
 
@@ -755,10 +755,10 @@ parse_binary_expr :: proc(p: ^Parser, prec_in: int) -> (Value, Pos) {
 			}
 			expect_operator(p);
 
-			if op.kind == Kind.Question {
+			if op.kind == .Question {
 				cond := expr;
 				x, _ := parse_expr(p);
-				expect_token(p, Kind.Colon);
+				expect_token(p, .Colon);
 				y, _ := parse_expr(p);
 
 				if t, ok := cond.(bool); ok {
@@ -791,13 +791,13 @@ parse_expr :: proc(p: ^Parser) -> (Value, Pos) {
 expect_semicolon :: proc(p: ^Parser) {
 	kind := p.curr_token.kind;
 
-	switch kind {
-	case Kind.Comma:
+	#partial switch kind {
+	case .Comma:
 		error(p, p.curr_token.pos, "Expected ';', got ','");
 		next_token(p);
-	case Kind.Semicolon:
+	case .Semicolon:
 		next_token(p);
-	case Kind.EOF:
+	case .EOF:
 		// okay
 	case:
 		error(p, p.curr_token.pos, "Expected ';', got %s", p.curr_token.lit);
@@ -811,17 +811,17 @@ parse_assignment :: proc(p: ^Parser) -> bool {
 		return p.dict_stack[len(p.dict_stack)-1];
 	}
 
-	if p.curr_token.kind == Kind.Semicolon {
+	if p.curr_token.kind == .Semicolon {
 		next_token(p);
 		return true;
 	}
-	if p.curr_token.kind == Kind.EOF {
+	if p.curr_token.kind == .EOF {
 		return false;
 	}
 
 	tok := p.curr_token;
-	if allow_token(p, Kind.Ident) || allow_token(p, Kind.String) {
-		expect_token(p, Kind.Assign);
+	if allow_token(p, .Ident) || allow_token(p, .String) {
+		expect_token(p, .Assign);
 		name, ok := unquote_string(p, tok);
 		if !ok do error(p, tok.pos, "Unable to unquote string");
 		expr, _ := parse_expr(p);

+ 1 - 1
core/encoding/cel/token.odin

@@ -137,7 +137,7 @@ kind_to_string := [len(Kind)]string{
 };
 
 precedence :: proc(op: Kind) -> int {
-	switch op {
+	#partial switch op {
 	case Question:
 		return 1;
 	case Or:

+ 2 - 2
core/encoding/json/marshal.odin

@@ -40,7 +40,7 @@ marshal_arg :: proc(b: ^strings.Builder, v: any) -> Marshal_Error {
 	ti := type_info_base(type_info_of(v.id));
 	a := any{v.data, ti.id};
 
-	switch info in ti.variant {
+	#partial switch info in ti.variant {
 	case Type_Info_Named:
 		panic("Unreachable");
 
@@ -282,7 +282,7 @@ marshal_arg :: proc(b: ^strings.Builder, v: any) -> Marshal_Error {
 				return false;
 			}
 			t := runtime.type_info_base(ti);
-			switch info in t.variant {
+			#partial switch info in t.variant {
 			case runtime.Type_Info_Integer:
 				switch info.endianness {
 				case .Platform: return false;

+ 2 - 2
core/encoding/json/parser.odin

@@ -70,7 +70,7 @@ parse_value :: proc(p: ^Parser) -> (value: Value, err: Error) {
 	defer value.end = token_end_pos(p.prev_token);
 
 	token := p.curr_token;
-	switch token.kind {
+	#partial switch token.kind {
 	case Kind.Null:
 		value.value = Null{};
 		advance_token(p);
@@ -105,7 +105,7 @@ parse_value :: proc(p: ^Parser) -> (value: Value, err: Error) {
 
 	case:
 		if p.spec == Specification.JSON5 {
-			switch token.kind {
+			#partial switch token.kind {
 			case Kind.Infinity:
 				inf: u64 = 0x7ff0000000000000;
 				if token.text[0] == '-' {

+ 1 - 1
core/encoding/json/types.odin

@@ -56,7 +56,7 @@ Error :: enum {
 
 
 destroy_value :: proc(value: Value) {
-	switch v in value.value {
+	#partial switch v in value.value {
 	case Object:
 		for key, elem in v {
 			delete(key);

+ 8 - 9
core/encoding/json/validator.odin

@@ -91,28 +91,27 @@ validate_array :: proc(p: ^Parser) -> bool {
 validate_value :: proc(p: ^Parser) -> bool {
 	token := p.curr_token;
 
-	using Kind;
-	switch token.kind {
-	case Null, False, True:
+	#partial switch token.kind {
+	case .Null, .False, .True:
 		advance_token(p);
 		return true;
-	case Integer, Float:
+	case .Integer, .Float:
 		advance_token(p);
 		return true;
-	case String:
+	case .String:
 		advance_token(p);
 		return is_valid_string_literal(token.text, p.spec);
 
-	case Open_Brace:
+	case .Open_Brace:
 		return validate_object(p);
 
-	case Open_Bracket:
+	case .Open_Bracket:
 		return validate_array(p);
 
 	case:
 		if p.spec == Specification.JSON5 {
-			switch token.kind {
-			case Infinity, NaN:
+			#partial switch token.kind {
+			case .Infinity, .NaN:
 				advance_token(p);
 				return true;
 			}

+ 11 - 8
core/fmt/fmt.odin

@@ -794,7 +794,7 @@ enum_value_to_string :: proc(val: any) -> (string, bool) {
 	v.id = runtime.typeid_base(v.id);
 	type_info := type_info_of(v.id);
 
-	switch e in type_info.variant {
+	#partial switch e in type_info.variant {
 	case: return "", false;
 	case runtime.Type_Info_Enum:
 		get_str :: proc(i: $T, e: runtime.Type_Info_Enum) -> (string, bool) {
@@ -857,7 +857,7 @@ fmt_enum :: proc(fi: ^Info, v: any, verb: rune) {
 	}
 
 	type_info := type_info_of(v.id);
-	switch e in type_info.variant {
+	#partial switch e in type_info.variant {
 	case: fmt_bad_verb(fi, verb);
 	case runtime.Type_Info_Enum:
 		switch verb {
@@ -898,7 +898,7 @@ fmt_bit_set :: proc(fi: ^Info, v: any, name: string = "") {
 			return false;
 		}
 		t := runtime.type_info_base(ti);
-		switch info in t.variant {
+		#partial switch info in t.variant {
 		case runtime.Type_Info_Integer:
 			switch info.endianness {
 			case .Platform: return false;
@@ -912,7 +912,7 @@ fmt_bit_set :: proc(fi: ^Info, v: any, name: string = "") {
 	byte_swap :: bits.byte_swap;
 
 	type_info := type_info_of(v.id);
-	switch info in type_info.variant {
+	#partial switch info in type_info.variant {
 	case runtime.Type_Info_Named:
 		val := v;
 		val.id = info.base.id;
@@ -982,7 +982,7 @@ fmt_bit_set :: proc(fi: ^Info, v: any, name: string = "") {
 }
 fmt_bit_field :: proc(fi: ^Info, v: any, bit_field_name: string = "") {
 	type_info := type_info_of(v.id);
-	switch info in type_info.variant {
+	#partial switch info in type_info.variant {
 	case runtime.Type_Info_Named:
 		val := v;
 		val.id = info.base.id;
@@ -1052,7 +1052,7 @@ fmt_opaque :: proc(fi: ^Info, v: any) {
 		strings.write_byte(fi.buf, '{');
 		defer strings.write_byte(fi.buf, '}');
 
-		switch in elem.variant {
+		#partial switch in elem.variant {
 		case rt.Type_Info_Integer, rt.Type_Info_Pointer, rt.Type_Info_Float:
 			fmt_value(fi, any{v.data, elem.id}, 'v');
 		case:
@@ -1073,8 +1073,11 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) {
 
 	type_info := type_info_of(v.id);
 	switch info in type_info.variant {
+	case runtime.Type_Info_Any:   // Ignore
+	case runtime.Type_Info_Tuple: // Ignore
+
 	case runtime.Type_Info_Named:
-		switch b in info.base.variant {
+		#partial switch b in info.base.variant {
 		case runtime.Type_Info_Struct:
 			if verb != 'v' {
 				fmt_bad_verb(fi, verb);
@@ -1193,7 +1196,7 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) {
 				a := any{ptr, info.elem.id};
 
 				elem := runtime.type_info_base(info.elem);
-				if elem != nil do switch e in elem.variant {
+				if elem != nil do #partial switch e in elem.variant {
 				case runtime.Type_Info_Array,
 				     runtime.Type_Info_Slice,
 				     runtime.Type_Info_Dynamic_Array,

+ 2 - 2
core/odin/ast/ast.odin

@@ -331,7 +331,7 @@ Switch_Stmt :: struct {
 	init:       ^Stmt,
 	cond:       ^Expr,
 	body:       ^Stmt,
-	complete:   bool,
+	partial:    bool,
 }
 
 Type_Switch_Stmt :: struct {
@@ -341,7 +341,7 @@ Type_Switch_Stmt :: struct {
 	tag:        ^Stmt,
 	expr:       ^Expr,
 	body:       ^Stmt,
-	complete:   bool,
+	partial:    bool,
 }
 
 Branch_Stmt :: struct {

+ 25 - 25
core/odin/parser/parser.odin

@@ -390,7 +390,7 @@ expect_semicolon :: proc(p: ^Parser, node: ^ast.Node) -> bool {
 				return true;
 			}
 		} else {
-			switch p.curr_tok.kind {
+			#partial switch p.curr_tok.kind {
 			case .Close_Brace:
 			case .Close_Paren:
 			case .Else:
@@ -475,7 +475,7 @@ parse_when_stmt :: proc(p: ^Parser) -> ^ast.When_Stmt {
 	}
 
 	if allow_token(p, .Else) {
-		switch p.curr_tok.kind {
+		#partial switch p.curr_tok.kind {
 		case .When:
 			else_stmt = parse_when_stmt(p);
 		case .Open_Brace:
@@ -550,7 +550,7 @@ parse_if_stmt :: proc(p: ^Parser) -> ^ast.If_Stmt {
 	}
 
 	if allow_token(p, .Else) {
-		switch p.curr_tok.kind {
+		#partial switch p.curr_tok.kind {
 		case .If:
 			else_stmt = parse_if_stmt(p);
 		case .Open_Brace:
@@ -859,7 +859,7 @@ parse_foreign_block :: proc(p: ^Parser, tok: tokenizer.Token) -> ^ast.Foreign_Bl
 	docs := p.lead_comment;
 
 	foreign_library: ^ast.Expr;
-	switch p.curr_tok.kind {
+	#partial switch p.curr_tok.kind {
 	case .Open_Brace:
 		i := ast.new(ast.Ident, tok.pos, end_pos(tok));
 		i.name = "_";
@@ -901,7 +901,7 @@ parse_foreign_decl :: proc(p: ^Parser) -> ^ast.Decl {
 	docs := p.lead_comment;
 	tok := expect_token(p, .Foreign);
 
-	switch p.curr_tok.kind {
+	#partial switch p.curr_tok.kind {
 	case .Ident, .Open_Brace:
 		return parse_foreign_block(p, tok);
 
@@ -955,7 +955,7 @@ parse_foreign_decl :: proc(p: ^Parser) -> ^ast.Decl {
 
 
 parse_stmt :: proc(p: ^Parser) -> ^ast.Stmt {
-	switch p.curr_tok.kind {
+	#partial switch p.curr_tok.kind {
 	// Operands
 	case .Context, // Also allows for 'context = '
 	     .Proc,
@@ -1086,12 +1086,12 @@ parse_stmt :: proc(p: ^Parser) -> ^ast.Stmt {
 				stmt.state_flags |= {.No_Bounds_Check};
 			}
 			return stmt;
-		case "complete":
+		case "partial":
 			stmt := parse_stmt(p);
 			switch s in &stmt.derived {
-			case ast.Switch_Stmt:      s.complete = true;
-			case ast.Type_Switch_Stmt: s.complete = true;
-			case: error(p, stmt.pos, "#complete can only be applied to a switch statement");
+			case ast.Switch_Stmt:      s.partial = true;
+			case ast.Type_Switch_Stmt: s.partial = true;
+			case: error(p, stmt.pos, "#partial can only be applied to a switch statement");
 			}
 			return stmt;
 		case "assert", "panic":
@@ -1130,7 +1130,7 @@ parse_stmt :: proc(p: ^Parser) -> ^ast.Stmt {
 
 
 token_precedence :: proc(p: ^Parser, kind: tokenizer.Token_Kind) -> int {
-	switch kind {
+	#partial switch kind {
 	case .Question:
 		return 1;
 	case .Ellipsis, .Range_Half:
@@ -1308,7 +1308,7 @@ convert_to_ident_list :: proc(p: ^Parser, list: []Expr_And_Flags, ignore_flags,
 
 is_token_field_prefix :: proc(p: ^Parser) -> Field_Prefix {
 	using Field_Prefix;
-	switch p.curr_tok.kind {
+	#partial switch p.curr_tok.kind {
 	case .EOF:
 		return Invalid;
 	case .Using:
@@ -1323,7 +1323,7 @@ is_token_field_prefix :: proc(p: ^Parser) -> Field_Prefix {
 	case .Hash:
 		advance_token(p);
 		defer advance_token(p);
-		switch p.curr_tok.kind {
+		#partial switch p.curr_tok.kind {
 		case .Ident:
 			switch p.curr_tok.text {
 			case "no_alias":
@@ -1359,7 +1359,7 @@ parse_field_prefixes :: proc(p: ^Parser) -> ast.Field_Flags {
 	for kind in Field_Prefix {
 		count := counts[kind];
 		using Field_Prefix;
-		#complete switch kind {
+		switch kind {
 		case Invalid, Unknown: // Ignore
 		case Using:
 			if count > 1 do error(p, p.curr_tok.pos, "multiple 'using' in this field list");
@@ -1391,7 +1391,7 @@ check_field_flag_prefixes :: proc(p: ^Parser, name_count: int, allowed_flags, se
 
 	for flag in ast.Field_Flag {
 		if flag notin allowed_flags && flag in flags {
-			#complete switch flag {
+			switch flag {
 			case .Using:
 				error(p, p.curr_tok.pos, "'using' is not allowed within this field list");
 			case .No_Alias:
@@ -1832,7 +1832,7 @@ check_poly_params_for_type :: proc(p: ^Parser, poly_params: ^ast.Field_List, tok
 
 
 parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
-	switch p.curr_tok.kind {
+	#partial switch p.curr_tok.kind {
 	case .Ident:
 		return parse_ident(p);
 
@@ -1945,7 +1945,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
 		expr := parse_unary_expr(p, lhs);
 
 		pi := ast.Proc_Inlining.None;
-		switch tok.kind {
+		#partial switch tok.kind {
 		case .Inline:
 			pi = ast.Proc_Inlining.Inline;
 		case .No_Inline:
@@ -2537,7 +2537,7 @@ parse_atom_expr :: proc(p: ^Parser, value: ^ast.Expr, lhs: bool) -> (operand: ^a
 	loop := true;
 	is_lhs := lhs;
 	for loop {
-		switch p.curr_tok.kind {
+		#partial switch p.curr_tok.kind {
 		case:
 			loop = false;
 
@@ -2556,7 +2556,7 @@ parse_atom_expr :: proc(p: ^Parser, value: ^ast.Expr, lhs: bool) -> (operand: ^a
 			p.expr_level += 1;
 			open := expect_token(p, .Open_Bracket);
 
-			switch p.curr_tok.kind {
+			#partial switch p.curr_tok.kind {
 			case .Colon, .Ellipsis, .Range_Half:
 				// NOTE(bill): Do not err yet
 				break;
@@ -2564,7 +2564,7 @@ parse_atom_expr :: proc(p: ^Parser, value: ^ast.Expr, lhs: bool) -> (operand: ^a
 				indicies[0] = parse_expr(p, false);
 			}
 
-			switch p.curr_tok.kind {
+			#partial switch p.curr_tok.kind {
 			case .Ellipsis, .Range_Half:
 				error(p, p.curr_tok.pos, "expected a colon, not a range");
 				fallthrough;
@@ -2602,7 +2602,7 @@ parse_atom_expr :: proc(p: ^Parser, value: ^ast.Expr, lhs: bool) -> (operand: ^a
 
 		case .Period:
 			tok := expect_token(p, .Period);
-			switch p.curr_tok.kind {
+			#partial switch p.curr_tok.kind {
 			case .Ident:
 				field := parse_ident(p);
 
@@ -2659,7 +2659,7 @@ parse_expr :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
 	return parse_binary_expr(p, lhs, 0+1);
 }
 parse_unary_expr :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
-	switch p.curr_tok.kind {
+	#partial switch p.curr_tok.kind {
 	case .Transmute, .Cast:
 		tok := advance_token(p);
 		open := expect_token(p, .Open_Paren);
@@ -2812,7 +2812,7 @@ parse_simple_stmt :: proc(p: ^Parser, flags: Stmt_Allow_Flags) -> ^ast.Stmt {
 	case op.kind == .Colon:
 		expect_token_after(p, .Colon, "identifier list");
 		if .Label in flags && len(lhs) == 1 {
-			switch p.curr_tok.kind {
+			#partial switch p.curr_tok.kind {
 			case .Open_Brace, .If, .For, .Switch:
 				label := lhs[0];
 				stmt := parse_stmt(p);
@@ -2847,7 +2847,7 @@ parse_value_decl :: proc(p: ^Parser, names: []^ast.Expr, docs: ^ast.Comment_Grou
 	values: []^ast.Expr;
 	type := parse_type_or_ident(p);
 
-	switch p.curr_tok.kind {
+	#partial switch p.curr_tok.kind {
 	case .Eq, .Colon:
 		sep := advance_token(p);
 		is_mutable = sep.kind != .Colon;
@@ -2910,7 +2910,7 @@ parse_import_decl :: proc(p: ^Parser, kind := Import_Decl_Kind.Standard) -> ^ast
 	import_name: tokenizer.Token;
 	is_using := kind != Import_Decl_Kind.Standard;
 
-	switch p.curr_tok.kind {
+	#partial switch p.curr_tok.kind {
 	case .Ident:
 		import_name = advance_token(p);
 	case:

+ 1 - 1
core/odin/tokenizer/token.odin

@@ -313,7 +313,7 @@ is_literal  :: proc(kind: Token_Kind) -> bool {
 	return Token_Kind.B_Literal_Begin  < kind && kind < Token_Kind.B_Literal_End;
 }
 is_operator :: proc(kind: Token_Kind) -> bool {
-	switch kind {
+	#partial switch kind {
 	case .B_Operator_Begin .. .B_Operator_End:
 		return true;
 	case .In, .Notin:

+ 1 - 1
core/reflect/reflect.odin

@@ -37,7 +37,7 @@ Type_Kind :: enum {
 type_kind :: proc(T: typeid) -> Type_Kind {
 	ti := type_info_of(T);
 	if ti != nil {
-		#complete switch _ in ti.variant {
+		switch _ in ti.variant {
 		case runtime.Type_Info_Named:         return .Named;
 		case runtime.Type_Info_Integer:       return .Integer;
 		case runtime.Type_Info_Rune:          return .Rune;

+ 14 - 2
core/reflect/types.odin

@@ -40,6 +40,14 @@ are_types_identical :: proc(a, b: ^rt.Type_Info) -> bool {
 		_, ok := b.variant.(rt.Type_Info_Complex);
 		return ok;
 
+	case rt.Type_Info_Quaternion:
+		_, ok := b.variant.(rt.Type_Info_Quaternion);
+		return ok;
+
+	case rt.Type_Info_Type_Id:
+		_, ok := b.variant.(rt.Type_Info_Type_Id);
+		return ok;
+
 	case rt.Type_Info_String:
 		_, ok := b.variant.(rt.Type_Info_String);
 		return ok;
@@ -174,7 +182,7 @@ are_types_identical :: proc(a, b: ^rt.Type_Info) -> bool {
 
 is_signed :: proc(info: ^rt.Type_Info) -> bool {
 	if info == nil do return false;
-	switch i in rt.type_info_base(info).variant {
+	#partial switch i in rt.type_info_base(info).variant {
 	case rt.Type_Info_Integer: return i.signed;
 	case rt.Type_Info_Float:   return true;
 	}
@@ -309,6 +317,7 @@ write_type :: proc(buf: ^strings.Builder, ti: ^rt.Type_Info) {
 			write_byte(buf, info.signed ? 'i' : 'u');
 			write_i64(buf, i64(8*ti.size), 10);
 			switch info.endianness {
+			case .Platform: // Okay
 			case .Little: write_string(buf, "le");
 			case .Big:    write_string(buf, "be");
 			}
@@ -321,6 +330,9 @@ write_type :: proc(buf: ^strings.Builder, ti: ^rt.Type_Info) {
 	case rt.Type_Info_Complex:
 		write_string(buf, "complex");
 		write_i64(buf, i64(8*ti.size), 10);
+	case rt.Type_Info_Quaternion:
+		write_string(buf, "quaternion");
+		write_i64(buf, i64(8*ti.size), 10);
 	case rt.Type_Info_String:
 		if info.is_cstring {
 			write_string(buf, "cstring");
@@ -399,7 +411,7 @@ write_type :: proc(buf: ^strings.Builder, ti: ^rt.Type_Info) {
 		write_type(buf, info.value);
 
 	case rt.Type_Info_Struct:
-		#complete switch info.soa_kind {
+		switch info.soa_kind {
 		case .None: // Ignore
 		case .Fixed:
 			write_string(buf, "#soa[");

+ 2 - 2
core/runtime/core.odin

@@ -305,7 +305,7 @@ type_info_base :: proc "contextless" (info: ^Type_Info) -> ^Type_Info {
 
 	base := info;
 	loop: for {
-		switch i in base.variant {
+		#partial switch i in base.variant {
 		case Type_Info_Named: base = i.base;
 		case: break loop;
 		}
@@ -319,7 +319,7 @@ type_info_core :: proc "contextless" (info: ^Type_Info) -> ^Type_Info {
 
 	base := info;
 	loop: for {
-		switch i in base.variant {
+		#partial switch i in base.variant {
 		case Type_Info_Named:  base = i.base;
 		case Type_Info_Enum:   base = i.base;
 		case Type_Info_Opaque: base = i.elem;

+ 5 - 2
core/runtime/internal.odin

@@ -110,6 +110,9 @@ print_type :: proc(fd: os.Handle, ti: ^Type_Info) {
 	case Type_Info_Complex:
 		os.write_string(fd, "complex");
 		print_u64(fd, u64(8*ti.size));
+	case Type_Info_Quaternion:
+		os.write_string(fd, "quaternion");
+		print_u64(fd, u64(8*ti.size));
 	case Type_Info_String:
 		os.write_string(fd, "string");
 	case Type_Info_Boolean:
@@ -183,7 +186,7 @@ print_type :: proc(fd: os.Handle, ti: ^Type_Info) {
 		print_type(fd, info.value);
 
 	case Type_Info_Struct:
-		#complete switch info.soa_kind {
+		switch info.soa_kind {
 		case .None: // Ignore
 		case .Fixed:
 			os.write_string(fd, "#soa[");
@@ -263,7 +266,7 @@ print_type :: proc(fd: os.Handle, ti: ^Type_Info) {
 	case Type_Info_Bit_Set:
 		os.write_string(fd, "bit_set[");
 
-		switch elem in type_info_base(info.elem).variant {
+		#partial switch elem in type_info_base(info.elem).variant {
 		case Type_Info_Enum:
 			print_type(fd, info.elem);
 		case Type_Info_Rune:

+ 11 - 11
core/sync/atomic.odin

@@ -11,7 +11,7 @@ Ordering :: enum {
 }
 
 strongest_failure_ordering :: inline proc "contextless" (order: Ordering) -> Ordering {
-	#complete switch order {
+	switch order {
 	case .Relaxed:                 return .Relaxed;
 	case .Release:                 return .Relaxed;
 	case .Acquire:                 return .Acquire;
@@ -22,7 +22,7 @@ strongest_failure_ordering :: inline proc "contextless" (order: Ordering) -> Ord
 }
 
 fence :: inline proc "contextless" ($order: Ordering) {
-	#complete switch order {
+	switch order {
 	case .Relaxed:                 panic("there is no such thing as a relaxed fence");
 	case .Release:                 intrinsics.atomic_fence_rel();
 	case .Acquire:                 intrinsics.atomic_fence_acq();
@@ -34,7 +34,7 @@ fence :: inline proc "contextless" ($order: Ordering) {
 
 
 atomic_store :: inline proc "contextless" (dst: ^$T, val: T, $order: Ordering) {
-	#complete switch order {
+	switch order {
 	case .Relaxed:                 intrinsics.atomic_store_relaxed(dst, val);
 	case .Release:                 intrinsics.atomic_store_rel(dst, val);
 	case .Sequentially_Consistent: intrinsics.atomic_store(dst, val);
@@ -45,7 +45,7 @@ atomic_store :: inline proc "contextless" (dst: ^$T, val: T, $order: Ordering) {
 }
 
 atomic_load :: inline proc "contextless" (dst: ^$T, $order: Ordering) -> T {
-	#complete switch order {
+	switch order {
 	case .Relaxed:                 return intrinsics.atomic_load_relaxed(dst);
 	case .Acquire:                 return intrinsics.atomic_load_acq(dst);
 	case .Sequentially_Consistent: return intrinsics.atomic_load(dst);
@@ -57,7 +57,7 @@ atomic_load :: inline proc "contextless" (dst: ^$T, $order: Ordering) -> T {
 }
 
 atomic_swap :: inline proc "contextless" (dst: ^$T, val: T, $order: Ordering) -> T {
-	#complete switch order {
+	switch order {
 	case .Relaxed:                 return intrinsics.atomic_xchg_relaxed(dst, val);
 	case .Release:                 return intrinsics.atomic_xchg_rel(dst, val);
 	case .Acquire:                 return intrinsics.atomic_xchg_acq(dst, val);
@@ -138,7 +138,7 @@ atomic_compare_exchange_weak :: inline proc "contextless" (dst: ^$T, old, new: T
 
 
 atomic_add :: inline proc "contextless" (dst: ^$T, val: T, $order: Ordering) -> T {
-	#complete switch order {
+	switch order {
 	case .Relaxed:                 return intrinsics.atomic_add_relaxed(dst, val);
 	case .Release:                 return intrinsics.atomic_add_rel(dst, val);
 	case .Acquire:                 return intrinsics.atomic_add_acq(dst, val);
@@ -150,7 +150,7 @@ atomic_add :: inline proc "contextless" (dst: ^$T, val: T, $order: Ordering) ->
 }
 
 atomic_sub :: inline proc "contextless" (dst: ^$T, val: T, $order: Ordering) -> T {
-	#complete switch order {
+	switch order {
 	case .Relaxed:                 return intrinsics.atomic_sub_relaxed(dst, val);
 	case .Release:                 return intrinsics.atomic_sub_rel(dst, val);
 	case .Acquire:                 return intrinsics.atomic_sub_acq(dst, val);
@@ -162,7 +162,7 @@ atomic_sub :: inline proc "contextless" (dst: ^$T, val: T, $order: Ordering) ->
 }
 
 atomic_and :: inline proc "contextless" (dst: ^$T, val: T, $order: Ordering) -> T {
-	#complete switch order {
+	switch order {
 	case .Relaxed:                 return intrinsics.atomic_and_relaxed(dst, val);
 	case .Release:                 return intrinsics.atomic_and_rel(dst, val);
 	case .Acquire:                 return intrinsics.atomic_and_acq(dst, val);
@@ -174,7 +174,7 @@ atomic_and :: inline proc "contextless" (dst: ^$T, val: T, $order: Ordering) ->
 }
 
 atomic_nand :: inline proc "contextless" (dst: ^$T, val: T, $order: Ordering) -> T {
-	#complete switch order {
+	switch order {
 	case .Relaxed:                 return intrinsics.atomic_nand_relaxed(dst, val);
 	case .Release:                 return intrinsics.atomic_nand_rel(dst, val);
 	case .Acquire:                 return intrinsics.atomic_nand_acq(dst, val);
@@ -186,7 +186,7 @@ atomic_nand :: inline proc "contextless" (dst: ^$T, val: T, $order: Ordering) ->
 }
 
 atomic_or :: inline proc "contextless" (dst: ^$T, val: T, $order: Ordering) -> T {
-	#complete switch order {
+	switch order {
 	case .Relaxed:                 return intrinsics.atomic_or_relaxed(dst, val);
 	case .Release:                 return intrinsics.atomic_or_rel(dst, val);
 	case .Acquire:                 return intrinsics.atomic_or_acq(dst, val);
@@ -198,7 +198,7 @@ atomic_or :: inline proc "contextless" (dst: ^$T, val: T, $order: Ordering) -> T
 }
 
 atomic_xor :: inline proc "contextless" (dst: ^$T, val: T, $order: Ordering) -> T {
-	#complete switch order {
+	switch order {
 	case .Relaxed:                 return intrinsics.atomic_xor_relaxed(dst, val);
 	case .Release:                 return intrinsics.atomic_xor_rel(dst, val);
 	case .Acquire:                 return intrinsics.atomic_xor_acq(dst, val);

+ 15 - 5
examples/demo/demo.odin

@@ -7,6 +7,7 @@ import "core:thread"
 import "core:reflect"
 import "intrinsics"
 
+
 /*
 	The Odin programming language is fast, concise, readable, pragmatic and open sourced.
 	It is designed with the intent of replacing C with the following goals:
@@ -1233,8 +1234,8 @@ implicit_selector_expression :: proc() {
 }
 
 
-complete_switch :: proc() {
-	fmt.println("\n# complete_switch");
+partial_switch :: proc() {
+	fmt.println("\n# partial_switch");
 	{ // enum
 		Foo :: enum {
 			A,
@@ -1244,22 +1245,31 @@ complete_switch :: proc() {
 		};
 
 		f := Foo.A;
-		#complete switch f {
+		switch f {
 		case .A: fmt.println("A");
 		case .B: fmt.println("B");
 		case .C: fmt.println("C");
 		case .D: fmt.println("D");
 		case:    fmt.println("?");
 		}
+
+		#partial switch f {
+		case .A: fmt.println("A");
+		case .D: fmt.println("D");
+		}
 	}
 	{ // union
 		Foo :: union {int, bool};
 		f: Foo = 123;
-		#complete switch in f {
+		switch in f {
 		case int:  fmt.println("int");
 		case bool: fmt.println("bool");
 		case:
 		}
+
+		#partial switch in f {
+		case bool: fmt.println("bool");
+		}
 	}
 }
 
@@ -1820,7 +1830,7 @@ main :: proc() {
 		array_programming();
 		map_type();
 		implicit_selector_expression();
-		complete_switch();
+		partial_switch();
 		cstring_example();
 		bit_set_type();
 		deferred_procedure_associations();

+ 24 - 33
src/check_stmt.cpp

@@ -807,12 +807,11 @@ void check_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) {
 		}
 	}
 
-	bool complete = ss->complete;
+	bool is_partial = ss->partial;
 
-	if (complete) {
+	if (is_partial) {
 		if (!is_type_enum(x.type)) {
-			error(x.expr, "#complete switch statement can be only used with an enum type");
-			complete = false;
+			error(x.expr, "#partial switch statement can be only used with an enum type");
 		}
 	}
 
@@ -877,9 +876,6 @@ void check_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) {
 				Operand a1 = lhs;
 				Operand b1 = rhs;
 				check_comparison(ctx, &a1, &b1, Token_LtEq);
-				if (complete) {
-					error(lhs.expr, "#complete switch statement does not allow ranges");
-				}
 
 				add_constant_switch_case(ctx, &seen, lhs);
 				if (upper_op == Token_GtEq) {
@@ -926,9 +922,6 @@ void check_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) {
 						continue;
 					}
 					if (y.mode != Addressing_Constant) {
-						if (complete) {
-							error(y.expr, "#complete switch statement only allows constant case clauses");
-						}
 						continue;
 					}
 
@@ -942,7 +935,7 @@ void check_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) {
 		check_close_scope(ctx);
 	}
 
-	if (complete) {
+	if (!is_partial && is_type_enum(x.type)) {
 		Type *et = base_type(x.type);
 		GB_ASSERT(is_type_enum(et));
 		auto fields = et->Enum.fields;
@@ -968,18 +961,17 @@ void check_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) {
 			defer (begin_error_block());
 
 			if (unhandled.count == 1) {
-				error_no_newline(node, "Unhandled switch case: ");
+				error_no_newline(node, "Unhandled switch case: %.*s", LIT(unhandled[0]->token.string));
 			} else {
 				error_no_newline(node, "Unhandled switch cases: ");
-			}
-			for_array(i, unhandled) {
-				Entity *f = unhandled[i];
-				if (i > 0)  {
-					error_line(", ");
+				for_array(i, unhandled) {
+					Entity *f = unhandled[i];
+					error_line("\t%.*s\n", LIT(f->token.string));
 				}
-				error_line("%.*s", LIT(f->token.string));
 			}
 			error_line("\n");
+
+			error_line("\tSuggestion: Was '#partial switch' wanted? This replaces the previous '#complete switch'.\n");
 		}
 	}
 }
@@ -1042,11 +1034,10 @@ void check_type_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) {
 		return;
 	}
 
-	bool complete = ss->complete;
-	if (complete) {
+	bool is_partial = ss->partial;
+	if (is_partial) {
 		if (switch_kind != TypeSwitch_Union) {
-			error(node, "#complete switch statement may only be used with a union");
-			complete = false;
+			error(node, "#partial switch statement may only be used with a union");
 		}
 	}
 
@@ -1174,7 +1165,7 @@ void check_type_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) {
 		check_close_scope(ctx);
 	}
 
-	if (complete) {
+	if (!is_partial && is_type_union(type_deref(x.type))) {
 		Type *ut = base_type(type_deref(x.type));
 		GB_ASSERT(is_type_union(ut));
 		auto variants = ut->Union.variants;
@@ -1191,20 +1182,20 @@ void check_type_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) {
 
 		if (unhandled.count > 0) {
 			if (unhandled.count == 1) {
-				error_no_newline(node, "Unhandled switch case: ");
+				gbString s = type_to_string(unhandled[0]);
+				error_no_newline(node, "Unhandled switch case: %s", s);
+				gb_string_free(s);
 			} else {
-				error_no_newline(node, "Unhandled switch cases: ");
-			}
-			for_array(i, unhandled) {
-				Type *t = unhandled[i];
-				if (i > 0)  {
-					error_line(", ");
+				error_no_newline(node, "Unhandled switch cases:\n");
+				for_array(i, unhandled) {
+					Type *t = unhandled[i];
+					gbString s = type_to_string(t);
+					error_line("\t%s\n", s);
+					gb_string_free(s);
 				}
-				gbString s = type_to_string(t);
-				error_line("%s", s);
-				gb_string_free(s);
 			}
 			error_line("\n");
+			error_line("\tSuggestion: Was '#partial switch' wanted? This replaces the previous '#complete switch'.\n");
 		}
 	}
 }

+ 20 - 2
src/parser.cpp

@@ -766,6 +766,7 @@ Ast *ast_switch_stmt(AstFile *f, Token token, Ast *init, Ast *tag, Ast *body) {
 	result->SwitchStmt.init  = init;
 	result->SwitchStmt.tag   = tag;
 	result->SwitchStmt.body  = body;
+	result->SwitchStmt.partial = false;
 	return result;
 }
 
@@ -775,6 +776,7 @@ Ast *ast_type_switch_stmt(AstFile *f, Token token, Ast *tag, Ast *body) {
 	result->TypeSwitchStmt.token = token;
 	result->TypeSwitchStmt.tag   = tag;
 	result->TypeSwitchStmt.body  = body;
+	result->TypeSwitchStmt.partial = false;
 	return result;
 }
 
@@ -4060,16 +4062,32 @@ Ast *parse_stmt(AstFile *f) {
 			s = parse_stmt(f);
 			switch (s->kind) {
 			case Ast_SwitchStmt:
-				s->SwitchStmt.complete = true;
+				s->SwitchStmt.partial = false;
+				syntax_warning(token, "#complete is now the default and has been replaced with its opposite: #partial");
 				break;
 			case Ast_TypeSwitchStmt:
-				s->TypeSwitchStmt.complete = true;
+				s->TypeSwitchStmt.partial = false;
+				syntax_warning(token, "#complete is now the default and has been replaced with its opposite: #partial");
 				break;
 			default:
 				syntax_error(token, "#complete can only be applied to a switch statement");
 				break;
 			}
 			return s;
+		} else if (tag == "partial") {
+			s = parse_stmt(f);
+			switch (s->kind) {
+			case Ast_SwitchStmt:
+				s->SwitchStmt.partial = true;
+				break;
+			case Ast_TypeSwitchStmt:
+				s->TypeSwitchStmt.partial = true;
+				break;
+			default:
+				syntax_error(token, "#partial can only be applied to a switch statement");
+				break;
+			}
+			return s;
 		} else if (tag == "assert") {
 			Ast *t = ast_basic_directive(f, hash_token, tag);
 			return ast_expr_stmt(f, parse_call_expr(f, t));

+ 12 - 12
src/parser.hpp

@@ -363,20 +363,20 @@ AST_KIND(_ComplexStmtBegin, "", bool) \
 		Entity *implicit_entity; \
 	}) \
 	AST_KIND(SwitchStmt, "switch statement", struct { \
-		Token token;    \
-		Ast *label;    \
-		Ast *init;     \
-		Ast *tag;      \
-		Ast *body;     \
-		bool complete; \
+		Token token;  \
+		Ast *label;   \
+		Ast *init;    \
+		Ast *tag;     \
+		Ast *body;    \
+		bool partial; \
 	}) \
 	AST_KIND(TypeSwitchStmt, "type switch statement", struct { \
-		Token token;   \
-		Ast *label;    \
-		Ast *tag;      \
-		Ast *body;     \
-		bool complete; \
-	}) \
+		Token token; \
+		Ast *label;  \
+		Ast *tag;    \
+		Ast *body;   \
+		bool partial; \
+}) \
 	AST_KIND(DeferStmt,  "defer statement",  struct { Token token; Ast *stmt; }) \
 	AST_KIND(BranchStmt, "branch statement", struct { Token token; Ast *label; }) \
 	AST_KIND(UsingStmt,  "using statement",  struct { \