compiler: Classify match catch-all arms in checked code
81619cc0d7eac899f25ce7e8bc7768e390a77561adf407e89267c8172ae22637
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 | + | } |