compiler: Classify match catch-all arms in checked code

81619cc0d7eac899f25ce7e8bc7768e390a77561adf407e89267c8172ae22637
Alexis Sellier committed ago 1 parent 859cfb8c
lib/std/lang/resolver.rad +17 -33
6232 6232
        }
6233 6233
    }
6234 6234
    return true;
6235 6235
}
6236 6236
6237 -
/// Analyze a match prong, checking for duplicate catch-alls. Returns the
6238 -
/// unified match type.
6239 -
unsafe fn resolveMatchProng 'arena (
6237 +
/// Classify a match prong and reject duplicate catch-alls.
6238 +
/// Record whether lowering can omit the prong's pattern test.
6239 +
fn checkMatchProng 'arena (
6240 6240
    self: &mut Resolver 'arena,
6241 6241
    prongNode: *ast::Node,
6242 6242
    prong: ast::MatchProng,
6243 6243
    subjectTy: Type,
6244 -
    state: &mut MatchState,
6245 -
    matchType: Type,
6246 -
    matchBy: MatchBy
6247 -
) -> Type throws (ResolveError) {
6244 +
    state: &mut MatchState
6245 +
) throws (ResolveError) {
6248 6246
    // Whether this prong is catch-all.
6249 6247
    let mut isCatchAll = false;
6250 6248
6251 6249
    if prong.guard <> nil {
6252 6250
        set state.isConst = false;
6253 6251
    } else {
6254 6252
        match prong.arm {
6255 -
            case ast::ProngArm::Binding(_),
6256 -
                 ast::ProngArm::Else => set isCatchAll = true,
6253 +
            case ast::ProngArm::Binding(_) => {
6254 +
                // For optionals, a binding matches only a present value.
6255 +
                set isCatchAll = not isOptionalType(subjectTy);
6256 +
            },
6257 +
            case ast::ProngArm::Else => set isCatchAll = true,
6257 6258
            case ast::ProngArm::Case(patterns) => set isCatchAll = hasWildcardPattern(patterns),
6258 6259
        }
6259 6260
    }
6260 6261
    if isCatchAll {
6261 6262
        if state.catchAll {
6263 6264
        }
6264 6265
        set state.catchAll = true;
6265 6266
    }
6266 6267
    setProngCatchAll(self, prongNode, isCatchAll);
6267 6268
6268 -
    return try visitMatchProng(self, prongNode, prong, subjectTy, matchType, matchBy);
6269 6269
}
6270 6270
6271 6271
/// Analyze a `match` expression. Dispatches to specialized functions based on
6272 6272
/// the subject type.
6273 6273
unsafe fn resolveMatch 'arena (self: &mut Resolver 'arena, node: *ast::Node, sw: ast::Match) -> Type
6311 6311
{
6312 6312
    let subjectTy = Type::Optional(innerTy);
6313 6313
    let prongs = sw.prongs;
6314 6314
    let mut hasValue = false;
6315 6315
    let mut hasNil = false;
6316 -
    let mut catchAll = false;
6316 +
    let mut state = MatchState { catchAll: false, isConst: false };
6317 6317
    let mut matchType = Type::Never;
6318 6318
6319 6319
    for prongNode in prongs {
6320 6320
        let case ast::NodeValue::MatchProng(prong) = prongNode.value
6321 6321
            else panic "resolveMatchOptional: expected match prong";
6322 6322
6323 -
        let mut isCatchAll = false;
6324 -
        if prong.guard == nil {
6325 -
            match prong.arm {
6326 -
                case ast::ProngArm::Else => set isCatchAll = true,
6327 -
                case ast::ProngArm::Case(patterns) => set isCatchAll = hasWildcardPattern(patterns),
6328 -
                case ast::ProngArm::Binding(_) => {
6329 -
                    // For optionals, a binding does *not* always match.
6330 -
                }
6331 -
            }
6332 -
        }
6333 -
        if isCatchAll {
6334 -
            if catchAll {
6335 -
                throw emitError(self, prongNode, ErrorKind::DuplicateCatchAll);
6336 -
            }
6337 -
            set catchAll = true;
6338 -
        }
6339 -
        setProngCatchAll(self, prongNode, isCatchAll);
6323 +
        try checkMatchProng(self, prongNode, prong, subjectTy, &mut state);
6340 6324
        set matchType = try visitMatchProng(self, prongNode, prong, subjectTy, matchType, matchBy);
6341 6325
6342 6326
        // Track coverage. Guarded prongs don't count as covering a case.
6343 6327
        if prong.guard == nil {
6344 6328
            if let case ast::ProngArm::Binding(_) = prong.arm {
6358 6342
            }
6359 6343
        }
6360 6344
    }
6361 6345
6362 6346
    // Check exhaustiveness.
6363 -
    if not catchAll {
6347 +
    if not state.catchAll {
6364 6348
        if not hasValue {
6365 6349
            throw emitError(self, node, ErrorKind::OptionalMatchMissingValue);
6366 6350
        }
6367 6351
        if not hasNil {
6368 6352
            throw emitError(self, node, ErrorKind::OptionalMatchMissingNil);
6390 6374
6391 6375
    for prongNode in prongs {
6392 6376
        let case ast::NodeValue::MatchProng(prong) = prongNode.value
6393 6377
            else panic "resolveMatchUnion: expected match prong";
6394 6378
6395 -
        set matchType = try resolveMatchProng(self, prongNode, prong, subjectTy, &mut state, matchType, matchBy);
6379 +
        try checkMatchProng(self, prongNode, prong, subjectTy, &mut state);
6380 +
        set matchType = try visitMatchProng(self, prongNode, prong, subjectTy, matchType, matchBy);
6396 6381
6397 6382
        // Guarded prongs don't count as covering. Patterns with nested
6398 6383
        // refining sub-patterns (e.g. matching different inner union variants)
6399 6384
        // don't count as duplicates or as fully covering.
6400 6385
        if prong.guard == nil {
6442 6427
6443 6428
    for prongNode in prongs {
6444 6429
        let case ast::NodeValue::MatchProng(prong) = prongNode.value
6445 6430
            else panic "resolveMatchGeneric: expected match prong";
6446 6431
6447 -
        set matchType = try resolveMatchProng(
6448 -
            self, prongNode, prong, subjectTy, &mut state, matchType, matchBy
6449 -
        );
6432 +
        try checkMatchProng(self, prongNode, prong, subjectTy, &mut state);
6433 +
        set matchType = try visitMatchProng(self, prongNode, prong, subjectTy, matchType, matchBy);
6450 6434
        // Track boolean coverage. Guarded prongs don't count as covering.
6451 6435
        if let case ast::ProngArm::Case(patterns) = prong.arm {
6452 6436
            for p in patterns {
6453 6437
                if prong.guard == nil {
6454 6438
                    if let case ast::NodeValue::Bool(val) = p.value {
lib/std/lang/resolver/tests.rad +59 -0
1925 1925
        let result = try resolveProgramStr(&mut a, program);
1926 1926
        try expectNoErrors(&result);
1927 1927
    }
1928 1928
}
1929 1929
1930 +
/// Expected lowering metadata for a resolved match.
1931 +
record MatchMetadataCase: Copy {
1932 +
    /// Complete source program with a function named f.
1933 +
    source: *[u8],
1934 +
    /// Bit set when lowering can omit a prong's pattern test.
1935 +
    catchAllMask: u32,
1936 +
    /// Whether scalar cases can use switch lowering.
1937 +
    isConst: bool,
1938 +
}
1939 +
1940 +
/// Guards and subject types determine unconditional-prong metadata.
1941 +
@test unsafe fn testMatchCatchAllMetadata() throws (testing::TestError) {
1942 +
    let cases = [
1943 +
        MatchMetadataCase { source: "fn f(x: u32) { match x { case 1 => {}, else => {} } }", catchAllMask: 2, isConst: true },
1944 +
        MatchMetadataCase { source: "fn f(x: u32) { match x { n if n > 0 => {}, case _ => {} } }", catchAllMask: 2, isConst: false },
1945 +
        MatchMetadataCase { source: "fn f(x: u32) { match x { case _ if x > 0 => {}, n => {} } }", catchAllMask: 2, isConst: false },
1946 +
        MatchMetadataCase { source: "fn f(x: u32) { match x { n => {} } }", catchAllMask: 1, isConst: false },
1947 +
        MatchMetadataCase { source: "fn f(x: [[u32; 2]; 2]) { match x { case [[1, a], [b, c]] => {}, case [[a, b], [c, d]] => {} } }", catchAllMask: 2, isConst: false },
1948 +
        MatchMetadataCase { source: "union U: Copy { A, B } fn f(x: U) { match x { case U::A => {}, else => {} } }", catchAllMask: 2, isConst: false },
1949 +
        MatchMetadataCase { source: "fn f(x: ?u32) { match x { n => {}, case nil => {} } }", catchAllMask: 2, isConst: false },
1950 +
        MatchMetadataCase { source: "fn f(x: ?u32) { match x { case _ if false => {}, else => {} } }", catchAllMask: 2, isConst: false },
1951 +
        MatchMetadataCase { source: "fn f(x: ?u32) { match x { case _ => {} } }", catchAllMask: 1, isConst: false },
1952 +
        MatchMetadataCase { source: "fn f(x: bool) { match x { case true => {}, case false => {} } }", catchAllMask: 2, isConst: true },
1953 +
    ];
1954 +
    for item in cases {
1955 +
        let mut arena = testArena();
1956 +
        let storage: 'test = &mut arena in {
1957 +
            let mut res = testResolver(storage);
1958 +
            let result = try resolveProgramStr(&mut res, item.source);
1959 +
            try expectNoErrors(&result);
1960 +
            let body = try getFnBody(&res, result.root, "f");
1961 +
            let node = body.statements[0];
1962 +
            let case ast::NodeValue::Match(sw) = node.value else throw testing::TestError::Failed;
1963 +
            for prong, i in sw.prongs {
1964 +
                assert super::isProngCatchAll(&res, prong) == ((item.catchAllMask & (1 << i)) <> 0);
1965 +
            }
1966 +
            assert super::isMatchConst(&res, node) == item.isConst;
1967 +
        }
1968 +
    }
1969 +
}
1970 +
1971 +
/// Duplicate unconditional arms are rejected for each match subject category.
1972 +
@test unsafe fn testMatchDuplicateCatchAll() throws (testing::TestError) {
1973 +
    for source in [
1974 +
        "fn f(x: u32) { match x { case _ => {}, else => {} } }",
1975 +
        "fn f(x: u32) { match x { n => {}, case _ => {} } }",
1976 +
        "fn f(x: [[u32; 2]; 2]) { match x { case [[a, b], [c, d]] => {}, else => {} } }",
1977 +
        "union U: Copy { A, B } fn f(x: U) { match x { n => {}, else => {} } }",
1978 +
        "fn f(x: ?u32) { match x { case _ => {}, else => {} } }",
1979 +
    ] {
1980 +
        let mut arena = testArena();
1981 +
        let storage: 'test = &mut arena in {
1982 +
            let mut res = testResolver(storage);
1983 +
            let result = try resolveProgramStr(&mut res, source);
1984 +
            try expectErrorKind(&result, super::ErrorKind::DuplicateCatchAll);
1985 +
        }
1986 +
    }
1987 +
}
1988 +
1930 1989
/// Test that a binding prong binds the subject to the identifier.
1931 1990
@test unsafe fn testResolveMatchBindingProng() throws (testing::TestError) {
1932 1991
    let mut testArena91 = testArena();
1933 1992
    let testStorage91: 'test91 = &mut testArena91 in {
1934 1993
        let mut a = testResolver(testStorage91);
test/tests/match.catchall.metadata.rad added +41 -0
1 +
//! returns: 0
2 +
//! Guarded patterns retain their fallback paths.
3 +
4 +
/// Select a guarded nested array pattern or its unconditional fallback.
5 +
fn matrix(values: [[u32; 2]; 2]) -> u32 {
6 +
    match values {
7 +
        case [[a, b], [c, d]] if a == 1 => return b + c + d,
8 +
        case [[a, b], [c, d]] => return a,
9 +
    }
10 +
}
11 +
12 +
/// An optional binding selects only a present value.
13 +
fn optional(value: ?u32) -> u32 {
14 +
    match value {
15 +
        n if n > 10 => return 1,
16 +
        n => return n,
17 +
        case nil => return 20,
18 +
    }
19 +
}
20 +
21 +
/// A wildcard guard permits both optional values to reach the fallback.
22 +
fn guarded(value: ?u32, accept: bool) -> u32 {
23 +
    match value {
24 +
        case _ if accept => return 3,
25 +
        else => return 4,
26 +
    }
27 +
}
28 +
29 +
/// Match metadata selects the same paths for every guard outcome.
30 +
@default fn main() -> u32 {
31 +
    assert matrix([[1, 2], [3, 4]]) == 9;
32 +
    assert matrix([[5, 2], [3, 4]]) == 5;
33 +
    assert optional(nil) == 20;
34 +
    assert optional(7) == 7;
35 +
    assert optional(11) == 1;
36 +
    assert guarded(nil, false) == 4;
37 +
    assert guarded(nil, true) == 3;
38 +
    assert guarded(7, false) == 4;
39 +
    assert guarded(7, true) == 3;
40 +
    return 0;
41 +
}