Skip to content

Commit 10b48a3

Browse files
authored
Fix match exhaustiveness for tuples of aliased unions (#5193)
PR #5145 stopped expanding type aliases during name resolution, so a type alias now persists as TK_TYPEALIASREF in places where it used to be replaced by its underlying form. Every subtype dispatch site was updated to unfold TK_TYPEALIASREF on demand, except for expand_type_alternatives, which is how tuple-of-union subtype checks distribute a tuple over its element unions for match exhaustiveness. Without the unfold, a tuple element that is a type alias was treated as a single opaque alternative instead of the union it wraps, so (PRF, PRF) over a four-case match (where PRF is (String | U64)) no longer looked exhaustive. Add the TK_TYPEALIASREF case to expand_type_alternatives and a regression test pair covering both the exhaustive and non-exhaustive shapes. This is a regression against unreleased code, so no changelog or release notes entry.
1 parent 527c200 commit 10b48a3

File tree

2 files changed

+62
-0
lines changed

2 files changed

+62
-0
lines changed

src/libponyc/type/subtype.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,7 @@ static bool is_x_sub_isect(ast_t* sub, ast_t* super, check_cap_t check_cap,
556556
//
557557
// For TK_UNIONTYPE: collects alternatives from all members.
558558
// For TK_TUPLETYPE: computes the cross-product of each element's alternatives.
559+
// For TK_TYPEALIASREF: unfolds the alias and recurses.
559560
// For anything else: returns a 1-element union containing a copy of the type.
560561
static ast_t* expand_type_alternatives(ast_t* type, size_t max_alternatives)
561562
{
@@ -644,6 +645,22 @@ static ast_t* expand_type_alternatives(ast_t* type, size_t max_alternatives)
644645
return result;
645646
}
646647

648+
case TK_TYPEALIASREF:
649+
{
650+
ast_t* unfolded = typealias_unfold(type);
651+
652+
if(unfolded == NULL)
653+
{
654+
ast_t* result = ast_from(type, TK_UNIONTYPE);
655+
ast_append(result, ast_dup(type));
656+
return result;
657+
}
658+
659+
ast_t* result = expand_type_alternatives(unfolded, max_alternatives);
660+
ast_free_unattached(unfolded);
661+
return result;
662+
}
663+
647664
default:
648665
{
649666
ast_t* result = ast_from(type, TK_UNIONTYPE);

test/libponyc/sugar_expr.cc

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -755,6 +755,51 @@ TEST_F(SugarExprTest, MatchExhaustiveAllCasesIncludingDontCareAndTuple)
755755
}
756756

757757

758+
TEST_F(SugarExprTest, MatchExhaustiveTupleOverAliasedUnion)
759+
{
760+
// Regression test for the interaction with PR #5145, which stopped
761+
// expanding type aliases during name resolution. Tuple match exhaustiveness
762+
// distributes the tuple over its element unions by computing a cross-
763+
// product of the element alternatives, and must unfold TK_TYPEALIASREF
764+
// elements to see the underlying union. Without the unfold, each element
765+
// is treated as a single opaque alternative and the cases do not look
766+
// exhaustive. This mirrors the failing pattern in corral's
767+
// CompareVersions._compare_pr_Fields.
768+
const char* src =
769+
"type PRF is (String | U64)\n"
770+
771+
"primitive Foo\n"
772+
" fun apply(p1: PRF, p2: PRF): Bool =>\n"
773+
" match (p1, p2)\n"
774+
" | (let u1: U64, let s2: String) => true\n"
775+
" | (let s1: String, let u2: U64) => false\n"
776+
" | (let u1: U64, let u2: U64) => true\n"
777+
" | (let s1: String, let s2: String) => false\n"
778+
" end";
779+
780+
TEST_COMPILE(src);
781+
}
782+
783+
784+
TEST_F(SugarExprTest, MatchNonExhaustiveTupleOverAliasedUnion)
785+
{
786+
// Paired negative for MatchExhaustiveTupleOverAliasedUnion: dropping one
787+
// cross-product case must still be detected as non-exhaustive after the
788+
// TK_TYPEALIASREF unfold in expand_type_alternatives.
789+
const char* src =
790+
"type PRF is (String | U64)\n"
791+
792+
"primitive Foo\n"
793+
" fun apply(p1: PRF, p2: PRF): Bool =>\n"
794+
" match (p1, p2)\n"
795+
" | (let u1: U64, let s2: String) => true\n"
796+
" | (let s1: String, let u2: U64) => false\n"
797+
" | (let u1: U64, let u2: U64) => true\n"
798+
" end";
799+
800+
TEST_ERRORS_1(src, "function body isn't the result type");
801+
}
802+
758803

759804
TEST_F(SugarExprTest, MatchNonExhaustiveSomeTupleCaseElementsMatchOnValue)
760805
{

0 commit comments

Comments
 (0)