Skip to content

Commit 996b8e9

Browse files
committed
Don't duplicate match checks for inherited trait bodies
Due to how default method bodies are implemented, we only want to do match checks within the context of the trait, not in the context of the class that has inherited the method. Closes #4612
1 parent 8560170 commit 996b8e9

File tree

3 files changed

+174
-104
lines changed

3 files changed

+174
-104
lines changed

.release-notes/4628.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
## Don't duplicate match checks for inherited trait bodies
2+
3+
Due to how we were doing match checks, some usages of match in default method bodies of traits and interfaces could end up failing checking once they were "inherited" by the implementing class.
4+
5+
For example, the following failed to compile:
6+
7+
```pony
8+
primitive Prim
9+
fun ignore() => None
10+
11+
trait A
12+
fun union(): (Prim | None)
13+
14+
fun match_it(): Bool =>
15+
match union()
16+
| let p: Prim =>
17+
p.ignore()
18+
true
19+
| None =>
20+
false
21+
end
22+
23+
class B is A
24+
fun union(): Prim =>
25+
Prim
26+
```
27+
28+
We've updated to only do the match checks checking the trait's default method bodies.

src/libponyc/expr/match.c

Lines changed: 144 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -187,48 +187,59 @@ bool expr_match(pass_opt_t* opt, ast_t* ast)
187187
type = control_type_add_branch(opt, type, cases);
188188
}
189189

190-
// analyze exhaustiveness
191-
ast_t* exhaustive_at = is_match_exhaustive(opt, expr_type, cases);
192-
193-
if(exhaustive_at == NULL)
190+
// If the method definition containing the match site had its body inherited
191+
// from a trait, we don't want to check exhaustiveness here -
192+
// it should only be checked in the context of the original trait.
193+
bool skip_exhaustive = false;
194+
ast_t* body_donor = (ast_t*)ast_data(opt->check.frame->method);
195+
if ((body_donor != NULL) && (ast_id(body_donor) == TK_TRAIT) && (ast_id(opt->check.frame->type) != TK_TRAIT))
196+
skip_exhaustive = true;
197+
198+
if(!skip_exhaustive)
194199
{
195-
// match might not be exhaustive
196-
if ((ast_id(else_clause) == TK_NONE))
197-
{
198-
// If we have no else clause, and the match is not found to be exhaustive,
199-
// we must generate an implicit else clause that returns None as the value.
200-
ast_scope(else_clause);
201-
ast_setid(else_clause, TK_SEQ);
202-
203-
BUILD(ref, else_clause,
204-
NODE(TK_TYPEREF,
205-
NONE
206-
ID("None")
207-
NONE));
208-
ast_add(else_clause, ref);
209-
210-
if(!expr_typeref(opt, &ref) || !expr_seq(opt, else_clause))
211-
return false;
212-
}
213-
}
214-
else
215-
{
216-
// match is exhaustive
217-
if(ast_sibling(exhaustive_at) != NULL)
200+
// analyze exhaustiveness
201+
ast_t* exhaustive_at = is_match_exhaustive(opt, expr_type, cases);
202+
203+
if(exhaustive_at == NULL)
218204
{
219-
// we have unreachable cases
220-
ast_error(opt->check.errors, ast, "match contains unreachable cases");
221-
ast_error_continue(opt->check.errors, ast_sibling(exhaustive_at),
222-
"first unreachable case expression");
223-
return false;
205+
// match might not be exhaustive
206+
if ((ast_id(else_clause) == TK_NONE))
207+
{
208+
// If we have no else clause, and the match is not found to be exhaustive,
209+
// we must generate an implicit else clause that returns None as the value.
210+
ast_scope(else_clause);
211+
ast_setid(else_clause, TK_SEQ);
212+
213+
BUILD(ref, else_clause,
214+
NODE(TK_TYPEREF,
215+
NONE
216+
ID("None")
217+
NONE));
218+
ast_add(else_clause, ref);
219+
220+
if(!expr_typeref(opt, &ref) || !expr_seq(opt, else_clause))
221+
return false;
222+
}
224223
}
225-
else if((ast_id(else_clause) != TK_NONE))
224+
else
226225
{
227-
ast_error(opt->check.errors, ast,
228-
"match is exhaustive, the else clause is unreachable");
229-
ast_error_continue(opt->check.errors, else_clause,
230-
"unreachable code");
231-
return false;
226+
// match is exhaustive
227+
if(ast_sibling(exhaustive_at) != NULL)
228+
{
229+
// we have unreachable cases
230+
ast_error(opt->check.errors, ast, "match contains unreachable cases");
231+
ast_error_continue(opt->check.errors, ast_sibling(exhaustive_at),
232+
"first unreachable case expression");
233+
return false;
234+
}
235+
else if((ast_id(else_clause) != TK_NONE))
236+
{
237+
ast_error(opt->check.errors, ast,
238+
"match is exhaustive, the else clause is unreachable");
239+
ast_error_continue(opt->check.errors, else_clause,
240+
"unreachable code");
241+
return false;
242+
}
232243
}
233244
}
234245

@@ -465,10 +476,20 @@ static bool infer_pattern_type(ast_t* pattern, ast_t* match_expr_type,
465476
return coerce_literals(&pattern, match_expr_type, opt);
466477
}
467478

479+
typedef struct foo
480+
{
481+
ast_t* body_donor; // Type body was defined in. NULL if we have no body.
482+
ast_t* trait_ref;
483+
bool local_define;
484+
bool failed;
485+
} foo;
486+
468487
bool expr_case(pass_opt_t* opt, ast_t* ast)
469488
{
470489
pony_assert(opt != NULL);
471490
pony_assert(ast_id(ast) == TK_CASE);
491+
bool ok = true;
492+
472493
AST_GET_CHILDREN(ast, pattern, guard, body);
473494

474495
if((ast_id(pattern) == TK_NONE) && (ast_id(guard) == TK_NONE))
@@ -504,83 +525,102 @@ bool expr_case(pass_opt_t* opt, ast_t* ast)
504525

505526
ast_settype(ast, pattern_type);
506527

507-
bool ok = true;
508-
errorframe_t info = NULL;
528+
// If the method definition containing the match site had its body inherited
529+
// from a trait, we don't want to check it here -
530+
// it should only be checked in the context of the original trait.
531+
bool skip = false;
532+
ast_t* body_donor = (ast_t*)ast_data(opt->check.frame->method);
533+
if ((body_donor != NULL) && (ast_id(body_donor) == TK_TRAIT) && (ast_id(opt->check.frame->type) != TK_TRAIT))
534+
{
535+
skip = true;
536+
}
537+
538+
// if (ast_id(body_donor) == TK_TRAIT)
539+
// {
540+
// printf("%d %d %d\n", ast_id(opt->check.frame->method), ast_id(body_donor), TK_TRAIT);
541+
// ast_print(opt->check.frame->method
542+
// }
509543

510-
switch(is_matchtype_with_consumed_pattern(match_type, pattern_type, &info, opt))
544+
if (!skip)
511545
{
512-
case MATCHTYPE_ACCEPT:
513-
break;
546+
errorframe_t info = NULL;
514547

515-
case MATCHTYPE_REJECT:
548+
switch(is_matchtype_with_consumed_pattern(match_type, pattern_type, &info, opt))
516549
{
517-
errorframe_t frame = NULL;
518-
ast_error_frame(&frame, pattern, "this pattern can never match");
519-
ast_error_frame(&frame, match_type, "match type: %s",
520-
ast_print_type(match_type));
521-
// TODO report unaliased type when body is consume !
522-
ast_error_frame(&frame, pattern, "pattern type: %s",
523-
ast_print_type(pattern_type));
524-
errorframe_append(&frame, &info);
525-
errorframe_report(&frame, opt->check.errors);
526-
527-
ok = false;
528-
break;
529-
}
550+
case MATCHTYPE_ACCEPT:
551+
break;
530552

531-
case MATCHTYPE_DENY_CAP:
532-
{
533-
errorframe_t frame = NULL;
534-
ast_error_frame(&frame, pattern,
535-
"this capture violates capabilities, because the match would "
536-
"need to differentiate by capability at runtime instead of matching "
537-
"on type alone");
538-
ast_error_frame(&frame, match_type, "the match type allows for more than "
539-
"one possibility with the same type as pattern type, but different "
540-
"capabilities. match type: %s",
541-
ast_print_type(match_type));
542-
ast_error_frame(&frame, pattern, "pattern type: %s",
543-
ast_print_type(pattern_type));
544-
errorframe_append(&frame, &info);
545-
ast_error_frame(&frame, match_expr,
546-
"the match expression with the inadequate capability is here");
547-
errorframe_report(&frame, opt->check.errors);
548-
549-
ok = false;
550-
break;
551-
}
553+
case MATCHTYPE_REJECT:
554+
{
555+
errorframe_t frame = NULL;
556+
ast_error_frame(&frame, pattern, "this pattern can never match");
557+
ast_error_frame(&frame, match_type, "match type: %s",
558+
ast_print_type(match_type));
559+
// TODO report unaliased type when body is consume !
560+
ast_error_frame(&frame, pattern, "pattern type: %s",
561+
ast_print_type(pattern_type));
562+
errorframe_append(&frame, &info);
563+
errorframe_report(&frame, opt->check.errors);
564+
565+
ok = false;
566+
break;
567+
}
552568

553-
case MATCHTYPE_DENY_NODESC:
554-
{
555-
errorframe_t frame = NULL;
556-
ast_error_frame(&frame, pattern,
557-
"this capture cannot match, since the type %s "
558-
"is a struct and lacks a type descriptor",
559-
ast_print_type(pattern_type));
560-
ast_error_frame(&frame, match_type,
561-
"a struct cannot be part of a union type. match type: %s",
562-
ast_print_type(match_type));
563-
ast_error_frame(&frame, pattern, "pattern type: %s",
564-
ast_print_type(pattern_type));
565-
errorframe_append(&frame, &info);
566-
errorframe_report(&frame, opt->check.errors);
567-
ok = false;
568-
break;
569-
}
570-
}
569+
case MATCHTYPE_DENY_CAP:
570+
{
571+
errorframe_t frame = NULL;
572+
ast_error_frame(&frame, pattern,
573+
"this capture violates capabilities, because the match would "
574+
"need to differentiate by capability at runtime instead of matching "
575+
"on type alone");
576+
ast_error_frame(&frame, match_type, "the match type allows for more than "
577+
"one possibility with the same type as pattern type, but different "
578+
"capabilities. match type: %s",
579+
ast_print_type(match_type));
580+
ast_error_frame(&frame, pattern, "pattern type: %s",
581+
ast_print_type(pattern_type));
582+
errorframe_append(&frame, &info);
583+
ast_error_frame(&frame, match_expr,
584+
"the match expression with the inadequate capability is here");
585+
errorframe_report(&frame, opt->check.errors);
586+
587+
ok = false;
588+
break;
589+
}
571590

572-
if(ast_id(guard) != TK_NONE)
573-
{
574-
ast_t* guard_type = ast_type(guard);
591+
case MATCHTYPE_DENY_NODESC:
592+
{
593+
errorframe_t frame = NULL;
594+
ast_error_frame(&frame, pattern,
595+
"this capture cannot match, since the type %s "
596+
"is a struct and lacks a type descriptor",
597+
ast_print_type(pattern_type));
598+
ast_error_frame(&frame, match_type,
599+
"a struct cannot be part of a union type. match type: %s",
600+
ast_print_type(match_type));
601+
ast_error_frame(&frame, pattern, "pattern type: %s",
602+
ast_print_type(pattern_type));
603+
errorframe_append(&frame, &info);
604+
errorframe_report(&frame, opt->check.errors);
605+
ok = false;
606+
break;
607+
}
608+
}
575609

576-
if(is_typecheck_error(guard_type))
610+
if(ast_id(guard) != TK_NONE)
577611
{
578-
ok = false;
579-
} else if(!is_bool(guard_type)) {
580-
ast_error(opt->check.errors, guard,
581-
"guard must be a boolean expression");
612+
ast_t* guard_type = ast_type(guard);
613+
614+
if(is_typecheck_error(guard_type))
615+
{
616+
ok = false;
617+
} else if(!is_bool(guard_type)) {
618+
ast_error(opt->check.errors, guard,
619+
"guard must be a boolean expression");
620+
}
582621
}
583622
}
623+
584624
return ok;
585625
}
586626

src/libponyc/pass/traits.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,8 @@ static void tidy_up(ast_t* entity)
133133
body_donor = entity;
134134

135135
ast_setdata(p, body_donor);
136+
if (!info->local_define)
137+
ast_setdata(body_donor, body_donor);
136138
}
137139
}
138140
}

0 commit comments

Comments
 (0)