resolver: require unguarded match catch-all last

aa5d2a53ea403febbf0a8b2e912437b30d017991d841acdf37f0498ea2675bb2
Alexis Sellier committed ago 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();