Skip to content

Commit ed24dbc

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 62fb3af commit ed24dbc

File tree

3 files changed

+184
-101
lines changed

3 files changed

+184
-101
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: 130 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -187,48 +187,62 @@ 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)
196+
&& (ast_id(opt->check.frame->type) != TK_TRAIT))
194197
{
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-
}
198+
skip_exhaustive = true;
213199
}
214-
else
200+
201+
if(!skip_exhaustive)
215202
{
216-
// match is exhaustive
217-
if(ast_sibling(exhaustive_at) != NULL)
203+
// analyze exhaustiveness
204+
ast_t* exhaustive_at = is_match_exhaustive(opt, expr_type, cases);
205+
206+
if(exhaustive_at == NULL)
218207
{
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;
208+
// match might not be exhaustive
209+
if ((ast_id(else_clause) == TK_NONE))
210+
{
211+
// If we have no else clause, and the match is not found to be exhaustive,
212+
// we must generate an implicit else clause that returns None as the value.
213+
ast_scope(else_clause);
214+
ast_setid(else_clause, TK_SEQ);
215+
216+
BUILD(ref, else_clause,
217+
NODE(TK_TYPEREF,
218+
NONE
219+
ID("None")
220+
NONE));
221+
ast_add(else_clause, ref);
222+
223+
if(!expr_typeref(opt, &ref) || !expr_seq(opt, else_clause))
224+
return false;
225+
}
224226
}
225-
else if((ast_id(else_clause) != TK_NONE))
227+
else
226228
{
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;
229+
// match is exhaustive
230+
if(ast_sibling(exhaustive_at) != NULL)
231+
{
232+
// we have unreachable cases
233+
ast_error(opt->check.errors, ast, "match contains unreachable cases");
234+
ast_error_continue(opt->check.errors, ast_sibling(exhaustive_at),
235+
"first unreachable case expression");
236+
return false;
237+
}
238+
else if((ast_id(else_clause) != TK_NONE))
239+
{
240+
ast_error(opt->check.errors, ast,
241+
"match is exhaustive, the else clause is unreachable");
242+
ast_error_continue(opt->check.errors, else_clause,
243+
"unreachable code");
244+
return false;
245+
}
232246
}
233247
}
234248

@@ -504,81 +518,96 @@ bool expr_case(pass_opt_t* opt, ast_t* ast)
504518

505519
ast_settype(ast, pattern_type);
506520

521+
// If the method definition containing the match site had its body inherited
522+
// from a trait, we don't want to check it here -
523+
// it should only be checked in the context of the original trait.
524+
bool skip = false;
525+
ast_t* body_donor = (ast_t*)ast_data(opt->check.frame->method);
526+
if ((body_donor != NULL) && (ast_id(body_donor) == TK_TRAIT)
527+
&& (ast_id(opt->check.frame->type) != TK_TRAIT))
528+
{
529+
skip = true;
530+
}
531+
507532
bool ok = true;
508-
errorframe_t info = NULL;
509533

510-
switch(is_matchtype_with_consumed_pattern(match_type, pattern_type, &info, opt))
534+
if (!skip)
511535
{
512-
case MATCHTYPE_ACCEPT:
513-
break;
536+
errorframe_t info = NULL;
514537

515-
case MATCHTYPE_REJECT:
538+
switch(is_matchtype_with_consumed_pattern(match_type, pattern_type, &info, opt))
516539
{
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-
}
540+
case MATCHTYPE_ACCEPT:
541+
break;
530542

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-
}
543+
case MATCHTYPE_REJECT:
544+
{
545+
errorframe_t frame = NULL;
546+
ast_error_frame(&frame, pattern, "this pattern can never match");
547+
ast_error_frame(&frame, match_type, "match type: %s",
548+
ast_print_type(match_type));
549+
// TODO report unaliased type when body is consume !
550+
ast_error_frame(&frame, pattern, "pattern type: %s",
551+
ast_print_type(pattern_type));
552+
errorframe_append(&frame, &info);
553+
errorframe_report(&frame, opt->check.errors);
554+
555+
ok = false;
556+
break;
557+
}
552558

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-
}
559+
case MATCHTYPE_DENY_CAP:
560+
{
561+
errorframe_t frame = NULL;
562+
ast_error_frame(&frame, pattern,
563+
"this capture violates capabilities, because the match would "
564+
"need to differentiate by capability at runtime instead of matching "
565+
"on type alone");
566+
ast_error_frame(&frame, match_type, "the match type allows for more than "
567+
"one possibility with the same type as pattern type, but different "
568+
"capabilities. match type: %s",
569+
ast_print_type(match_type));
570+
ast_error_frame(&frame, pattern, "pattern type: %s",
571+
ast_print_type(pattern_type));
572+
errorframe_append(&frame, &info);
573+
ast_error_frame(&frame, match_expr,
574+
"the match expression with the inadequate capability is here");
575+
errorframe_report(&frame, opt->check.errors);
576+
577+
ok = false;
578+
break;
579+
}
571580

572-
if(ast_id(guard) != TK_NONE)
573-
{
574-
ast_t* guard_type = ast_type(guard);
581+
case MATCHTYPE_DENY_NODESC:
582+
{
583+
errorframe_t frame = NULL;
584+
ast_error_frame(&frame, pattern,
585+
"this capture cannot match, since the type %s "
586+
"is a struct and lacks a type descriptor",
587+
ast_print_type(pattern_type));
588+
ast_error_frame(&frame, match_type,
589+
"a struct cannot be part of a union type. match type: %s",
590+
ast_print_type(match_type));
591+
ast_error_frame(&frame, pattern, "pattern type: %s",
592+
ast_print_type(pattern_type));
593+
errorframe_append(&frame, &info);
594+
errorframe_report(&frame, opt->check.errors);
595+
ok = false;
596+
break;
597+
}
598+
}
575599

576-
if(is_typecheck_error(guard_type))
600+
if(ast_id(guard) != TK_NONE)
577601
{
578-
ok = false;
579-
} else if(!is_bool(guard_type)) {
580-
ast_error(opt->check.errors, guard,
581-
"guard must be a boolean expression");
602+
ast_t* guard_type = ast_type(guard);
603+
604+
if(is_typecheck_error(guard_type))
605+
{
606+
ok = false;
607+
} else if(!is_bool(guard_type)) {
608+
ast_error(opt->check.errors, guard,
609+
"guard must be a boolean expression");
610+
}
582611
}
583612
}
584613
return ok;
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
"""
2+
This won't compile if there is a regression for issue #4612.
3+
"""
4+
5+
actor Main
6+
new create(env: Env) =>
7+
env.out.print("streets ahead")
8+
9+
primitive Prim
10+
fun ignore() => None
11+
12+
trait A
13+
fun union(): (Prim | None)
14+
15+
fun match_it(): Bool =>
16+
match union()
17+
| let p: Prim =>
18+
p.ignore()
19+
true
20+
| None =>
21+
false
22+
end
23+
24+
class B is A
25+
fun union(): Prim =>
26+
Prim

0 commit comments

Comments
 (0)