resolver: require unguarded match catch-all last
aa5d2a53ea403febbf0a8b2e912437b30d017991d841acdf37f0498ea2675bb2
1 parent
14b282d4
lib/std/lang/resolver.rad
+8 -3
| 628 | 628 | BoolMatchMissing(bool), |
|
| 629 | 629 | /// `match` on a non-union type is missing a catch-all. |
|
| 630 | 630 | MatchNonExhaustive, |
|
| 631 | 631 | /// `match` has more than one catch-all prongs. |
|
| 632 | 632 | DuplicateCatchAll, |
|
| 633 | + | /// `match` has a prong after an unguarded catch-all. |
|
| 634 | + | CatchAllMustBeLast, |
|
| 633 | 635 | /// `match` has a duplicate case pattern. |
|
| 634 | 636 | DuplicateMatchPattern, |
|
| 635 | 637 | /// `match` has an unreachable `else`: all cases are already handled. |
|
| 636 | 638 | UnreachableElse, |
|
| 637 | 639 | /// Builtin called with wrong number of arguments. |
| 6311 | 6313 | } |
|
| 6312 | 6314 | } |
|
| 6313 | 6315 | return true; |
|
| 6314 | 6316 | } |
|
| 6315 | 6317 | ||
| 6316 | - | /// Classify a match prong and reject duplicate catch-alls. |
|
| 6318 | + | /// Classify a match prong and reject unreachable prongs after a catch-all. |
|
| 6317 | 6319 | /// Record whether lowering can omit the prong's pattern test. |
|
| 6318 | 6320 | fn checkMatchProng 'arena ( |
|
| 6319 | 6321 | self: &mut Resolver 'arena, |
|
| 6320 | 6322 | prongNode: *ast::Node, |
|
| 6321 | 6323 | prong: ast::MatchProng, |
| 6335 | 6337 | }, |
|
| 6336 | 6338 | case ast::ProngArm::Else => set isCatchAll = true, |
|
| 6337 | 6339 | case ast::ProngArm::Case(patterns) => set isCatchAll = hasWildcardPattern(patterns), |
|
| 6338 | 6340 | } |
|
| 6339 | 6341 | } |
|
| 6340 | - | if isCatchAll { |
|
| 6341 | - | if state.catchAll { |
|
| 6342 | + | if state.catchAll { |
|
| 6343 | + | if isCatchAll { |
|
| 6342 | 6344 | throw emitError(self, prongNode, ErrorKind::DuplicateCatchAll); |
|
| 6343 | 6345 | } |
|
| 6346 | + | throw emitError(self, prongNode, ErrorKind::CatchAllMustBeLast); |
|
| 6347 | + | } |
|
| 6348 | + | if isCatchAll { |
|
| 6344 | 6349 | set state.catchAll = true; |
|
| 6345 | 6350 | } |
|
| 6346 | 6351 | setProngCatchAll(self, prongNode, isCatchAll); |
|
| 6347 | 6352 | ||
| 6348 | 6353 | } |
lib/std/lang/resolver/printer.rad
+3 -0
| 370 | 370 | io::print("match non-exhaustive: requires `else` or binding catch-all"); |
|
| 371 | 371 | } |
|
| 372 | 372 | case super::ErrorKind::DuplicateCatchAll => { |
|
| 373 | 373 | io::print("match has multiple catch-all prongs"); |
|
| 374 | 374 | } |
|
| 375 | + | case super::ErrorKind::CatchAllMustBeLast => { |
|
| 376 | + | io::print("match catch-all prong must be last"); |
|
| 377 | + | } |
|
| 375 | 378 | case super::ErrorKind::DuplicateMatchPattern => { |
|
| 376 | 379 | io::print("match has duplicate pattern"); |
|
| 377 | 380 | } |
|
| 378 | 381 | case super::ErrorKind::UnreachableElse => { |
|
| 379 | 382 | io::print("match has unreachable `else`, all cases are already handled"); |
lib/std/lang/resolver/tests.rad
+28 -6
| 2001 | 2001 | try expectTypeMismatch(err, super::Type::Nominal(optionTy), super::Type::Bool); |
|
| 2002 | 2002 | } |
|
| 2003 | 2003 | } |
|
| 2004 | 2004 | ||
| 2005 | 2005 | @test unsafe fn testResolveMatchGuardForms() throws (testing::TestError) { |
|
| 2006 | - | let mut testArena90 = testArena(); |
|
| 2007 | - | let testStorage90: 'test90 = &mut testArena90 in { |
|
| 2008 | - | let mut a = testResolver(testStorage90); |
|
| 2009 | - | let program = "fn first(value: i32) { match value { case _ if true => {}, else => {} } }"; |
|
| 2010 | - | let result = try resolveProgramStr(&mut a, program); |
|
| 2011 | - | try expectNoErrors(&result); |
|
| 2006 | + | for program in [ |
|
| 2007 | + | "fn first(value: i32) { match value { case _ if true => {}, else => {} } }", |
|
| 2008 | + | "fn f(x: u32) { match x { case _ if x > 0 => {}, case 1 => {}, else => {} } }", |
|
| 2009 | + | "union U: Copy { A, B } fn f(x: U) { match x { value if false => {}, case U::A => {}, case U::B => {} } }", |
|
| 2010 | + | ] { |
|
| 2011 | + | let mut testArena90 = testArena(); |
|
| 2012 | + | let testStorage90: 'test90 = &mut testArena90 in { |
|
| 2013 | + | let mut a = testResolver(testStorage90); |
|
| 2014 | + | let result = try resolveProgramStr(&mut a, program); |
|
| 2015 | + | try expectNoErrors(&result); |
|
| 2016 | + | } |
|
| 2012 | 2017 | } |
|
| 2013 | 2018 | } |
|
| 2014 | 2019 | ||
| 2015 | 2020 | /// Expected lowering metadata for a resolved match. |
|
| 2016 | 2021 | record MatchMetadataCase: Copy { |
| 2067 | 2072 | let mut res = testResolver(storage); |
|
| 2068 | 2073 | let result = try resolveProgramStr(&mut res, source); |
|
| 2069 | 2074 | try expectErrorKind(&result, super::ErrorKind::DuplicateCatchAll); |
|
| 2070 | 2075 | } |
|
| 2071 | 2076 | } |
|
| 2077 | + | ||
| 2078 | + | // Any prong after an unguarded catch-all is unreachable. |
|
| 2079 | + | for source in [ |
|
| 2080 | + | "fn f(x: u32) { match x { else => {}, case 1 => {} } }", |
|
| 2081 | + | "fn f(x: u32) { match x { case _ => {}, case 1 if true => {} } }", |
|
| 2082 | + | "fn f(x: [u32; 2]) { match x { case [a, b] => {}, case [1, 2] => {} } }", |
|
| 2083 | + | "union U: Copy { A, B } fn f(x: U) { match x { value => {}, case U::A => {} } }", |
|
| 2084 | + | "fn f(x: ?u32) { match x { else => {}, case nil => {} } }", |
|
| 2085 | + | "fn f(x: ?u32) { match x { case _ => {}, value => {} } }", |
|
| 2086 | + | ] { |
|
| 2087 | + | let mut arena = testArena(); |
|
| 2088 | + | let storage: 'test = &mut arena in { |
|
| 2089 | + | let mut res = testResolver(storage); |
|
| 2090 | + | let result = try resolveProgramStr(&mut res, source); |
|
| 2091 | + | try expectErrorKind(&result, super::ErrorKind::CatchAllMustBeLast); |
|
| 2092 | + | } |
|
| 2093 | + | } |
|
| 2072 | 2094 | } |
|
| 2073 | 2095 | ||
| 2074 | 2096 | /// Test that a binding prong binds the subject to the identifier. |
|
| 2075 | 2097 | @test unsafe fn testResolveMatchBindingProng() throws (testing::TestError) { |
|
| 2076 | 2098 | let mut testArena91 = testArena(); |