Skip to content

Commit

Permalink
Changes to how match statements interact with enums (#728)
Browse files Browse the repository at this point in the history
  • Loading branch information
Akuli authored Feb 5, 2025
1 parent 5b215b2 commit 446da9b
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 15 deletions.
17 changes: 16 additions & 1 deletion compiler/builders/ast_to_builder.jou
Original file line number Diff line number Diff line change
Expand Up @@ -508,7 +508,22 @@ class AstToBuilder:

if match_stmt->case_underscore != NULL:
self->build_body(match_stmt->case_underscore)
self->builder->jump(done)

if (
match_stmt->case_underscore == NULL
and match_stmt->match_obj.types.implicit_cast_type->kind == TypeKind.Enum
):
# The one corner case where match statement invokes UB:
# - User is matching over an enum
# - All enum members are handled (otherwise error in typecheck)
# - The value stored in the enum is not a valid value of the enum
# - There is no "case _" to catch the invalid value
#
# See also: doc/match.md
self->builder->unreachable()
else:
self->builder->jump(done)

self->builder->set_current_block(done)

def build_assert(self, assert_location: Location, assertion: AstAssertion*) -> None:
Expand Down
8 changes: 4 additions & 4 deletions compiler/typecheck/step3_function_and_method_bodies.jou
Original file line number Diff line number Diff line change
Expand Up @@ -1146,11 +1146,11 @@ def typecheck_match_statement(state: State*, match_stmt: AstMatchStatement*) ->
snprintf(msg, sizeof(msg), "case value cannot be <from> when matching with %s", sig_string)
typecheck_expression_with_implicit_cast(state, case_obj, case_type, msg)

# The special casing only applies to simple enum member lookups (TheEnum.TheMember syntax)
if case_obj->kind != AstExpressionKind.GetEnumMember:
nremaining = -1

if nremaining != -1:
if case_obj->kind != AstExpressionKind.GetEnumMember:
# Matching an enum but it's too dynamic, not simply TheEnum.Member
snprintf(msg, sizeof(msg), "'case' value must be %s.something when matching a value of enum %s", case_type->name, case_type->name)
fail(case_obj->location, msg)
# We are matching against TheEnum.member. Try to find and remove it from remaining members.
member = case_obj->enum_member.member_name
found = False
Expand Down
11 changes: 11 additions & 0 deletions tests/other_errors/match_enum_too_dynamic.jou
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
enum Thing:
Foo
Bar
Baz

def do_stuff(t1: Thing, t2: Thing) -> None:
match t1:
# This is forbidden because it would be hard for the compiler to know
# which values have been handled and which haven't
case t2: # Error: 'case' value must be Thing.something when matching a value of enum Thing
pass
24 changes: 14 additions & 10 deletions tests/should_succeed/match.jou
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ enum Foo:
Wut


def show_evaluation(foo: Foo, msg: byte*) -> Foo:
def show_evaluation(value: int, msg: byte*) -> int:
puts(msg)
return foo
return value


def main() -> int:
Expand Down Expand Up @@ -80,6 +80,8 @@ def main() -> int:
printf("Hey! :)\n") # Output: Hey! :)
case Foo.Wut:
printf("nope\n")
case _:
printf("Other!!!\n")

f = 12345 as Foo
match f:
Expand All @@ -91,6 +93,8 @@ def main() -> int:
printf("nope\n")
case Foo.Wut:
printf("nope\n")
case _:
printf("Other!!!\n") # Output: Other!!!

# Test evaluation order.
#
Expand All @@ -99,25 +103,25 @@ def main() -> int:
# Output: case 2
# Output: case 3
# Output: ye
match show_evaluation(Foo.Lol, "match obj"):
case show_evaluation(Foo.Bar, "case 1"):
match show_evaluation(3, "match obj"):
case show_evaluation(1, "case 1"):
printf("nope\n")
case show_evaluation(Foo.Baz, "case 2"):
case show_evaluation(2, "case 2"):
printf("nope\n")
case show_evaluation(Foo.Lol, "case 3"):
case show_evaluation(3, "case 3"):
printf("ye\n")
case show_evaluation(Foo.Wut, "case 4"):
case show_evaluation(4, "case 4"):
printf("nope\n")

# Output: match obj
# Output: case 1
# Output: case 2
# Output: case 3
# Output: ye
match show_evaluation(Foo.Lol, "match obj"):
case show_evaluation(Foo.Bar, "case 1") | show_evaluation(Foo.Baz, "case 2"):
match show_evaluation(3, "match obj"):
case show_evaluation(1, "case 1") | show_evaluation(2, "case 2"):
printf("nope\n")
case show_evaluation(Foo.Lol, "case 3") | show_evaluation(Foo.Wut, "case 4"):
case show_evaluation(3, "case 3") | show_evaluation(4, "case 4"):
printf("ye\n")

# Make a string that is surely not == "Hello", to make sure strcmp() is called below
Expand Down

0 comments on commit 446da9b

Please sign in to comment.