compiler: Initialize pattern binding destinations
f04dd59a4402f0b117c0778333921a74a3727eabeb1d9c37fd4128689ddc2bc6
1 parent
37ceac34
lib/std/lang/lower.rad
+16 -17
| 3926 | 3926 | ||
| 3927 | 3927 | /// Lower an `if let` statement. |
|
| 3928 | 3928 | unsafe fn lowerIfLet 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, cond: ast::IfLet) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
|
| 3929 | 3929 | let savedVarsLen = enterVarScope(&self.vars); |
|
| 3930 | 3930 | let subject = try lowerMatchSubject(self, cond.pattern.scrutinee); |
|
| 3931 | - | let mut thenBlock: BlockId = undefined; |
|
| 3931 | + | let mut thenBlock: ?BlockId = nil; |
|
| 3932 | 3932 | if cond.pattern.guard == nil { |
|
| 3933 | 3933 | set thenBlock = try createBlock(self, "then"); |
|
| 3934 | 3934 | } |
|
| 3935 | 3935 | let elseBlock = try createBlock(self, "else"); |
|
| 3936 | 3936 | let mut mergeBlock: ?BlockId = nil; |
|
| 3937 | 3937 | ||
| 3938 | 3938 | // Pattern match: jump to @then on success, @else on failure. |
|
| 3939 | - | try lowerPatternMatch(self, &subject, &cond.pattern, &mut thenBlock, "then", elseBlock); |
|
| 3939 | + | try lowerPatternMatch(self, &subject, &cond.pattern, thenBlock, "then", elseBlock); |
|
| 3940 | 3940 | ||
| 3941 | 3941 | // Lower then branch. |
|
| 3942 | 3942 | try lowerNode(self, cond.thenBranch); |
|
| 3943 | 3943 | try emitMergeIfUnterminated(self, &mut mergeBlock); |
|
| 3944 | 3944 | // Pattern bindings are visible only in the success branch. Restore the |
| 3965 | 3965 | /// to ensure block indices are in RPO. |
|
| 3966 | 3966 | unsafe fn lowerPatternMatch 'arena 'phase 'function ( |
|
| 3967 | 3967 | self: &mut FnLowerer 'arena 'phase 'function, |
|
| 3968 | 3968 | subject: &MatchSubject, |
|
| 3969 | 3969 | pat: &ast::PatternMatch, |
|
| 3970 | - | successBlock: &mut BlockId, |
|
| 3970 | + | initialSuccess: ?BlockId, |
|
| 3971 | 3971 | successLabel: *[u8], |
|
| 3972 | 3972 | failBlock: BlockId |
|
| 3973 | 3973 | ) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
|
| 3974 | 3974 | // If guard present, pattern match jumps to @guard, then guard evaluation |
|
| 3975 | 3975 | // jumps to `successBlock` or `failBlock`. Otherwise, jump directly to |
|
| 3976 | 3976 | // `successBlock`. |
|
| 3977 | - | let mut targetBlock: BlockId = undefined; |
|
| 3977 | + | let mut guardBlock: ?BlockId = nil; |
|
| 3978 | 3978 | if pat.guard <> nil { |
|
| 3979 | - | set targetBlock = try createBlock(self, "guard"); |
|
| 3980 | - | set *successBlock = try createBlock(self, successLabel); |
|
| 3981 | - | } else { |
|
| 3982 | - | set targetBlock = *successBlock; |
|
| 3979 | + | set guardBlock = try createBlock(self, "guard"); |
|
| 3983 | 3980 | } |
|
| 3981 | + | let successBlock = initialSuccess else try createBlock(self, successLabel); |
|
| 3982 | + | let targetBlock = guardBlock else successBlock; |
|
| 3984 | 3983 | match pat.kind { |
|
| 3985 | 3984 | case ast::PatternKind::Case => { |
|
| 3986 | 3985 | let patterns = [pat.pattern]; |
|
| 3987 | 3986 | // Jump to `targetBlock` if the pattern matches, `failBlock` otherwise. |
|
| 3988 | 3987 | try emitPatternMatches(self, subject, &patterns[..], targetBlock, failBlock); |
| 4000 | 3999 | try bindMatchVariable(self, subject, pat.pattern, pat.mutable); |
|
| 4001 | 4000 | } |
|
| 4002 | 4001 | } |
|
| 4003 | 4002 | // Handle guard: on success jump to `successBlock`, on failure jump to `failBlock`. |
|
| 4004 | 4003 | if let g = pat.guard { |
|
| 4005 | - | try emitCondBranch(self, g, *successBlock, failBlock); |
|
| 4006 | - | try switchToAndSeal(self, *successBlock); |
|
| 4004 | + | try emitCondBranch(self, g, successBlock, failBlock); |
|
| 4005 | + | try switchToAndSeal(self, successBlock); |
|
| 4007 | 4006 | } else if *currentBlock(self) <> *targetBlock { |
|
| 4008 | 4007 | // Nested tests changed the current block. Create a new success block |
|
| 4009 | 4008 | // after the nest blocks to maintain RPO ordering, and jump to it. |
|
| 4010 | - | set *successBlock = try createBlock(self, successLabel); |
|
| 4009 | + | let nestedSuccess = try createBlock(self, successLabel); |
|
| 4011 | 4010 | ||
| 4012 | - | try emitJmp(self, *successBlock); |
|
| 4013 | - | try switchToAndSeal(self, *successBlock); |
|
| 4011 | + | try emitJmp(self, nestedSuccess); |
|
| 4012 | + | try switchToAndSeal(self, nestedSuccess); |
|
| 4014 | 4013 | } |
|
| 4015 | 4014 | } |
|
| 4016 | 4015 | ||
| 4017 | 4016 | /// Lower a `let-else` statement. |
|
| 4018 | 4017 | unsafe fn lowerLetElse 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, letElse: ast::LetElse) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
|
| 4019 | 4018 | let subject = try lowerMatchSubject(self, letElse.pattern.scrutinee); |
|
| 4020 | - | let mut successBlock: BlockId = undefined; |
|
| 4019 | + | let mut successBlock: ?BlockId = nil; |
|
| 4021 | 4020 | if letElse.pattern.guard == nil { |
|
| 4022 | 4021 | set successBlock = try createBlock(self, "success"); |
|
| 4023 | 4022 | } |
|
| 4024 | 4023 | // The else branch executes when the pattern fails to match. |
|
| 4025 | 4024 | let elseBlock = try createBlock(self, "else"); |
| 4027 | 4026 | // Evaluate the pattern and jump to @success or @else. |
|
| 4028 | 4027 | try lowerPatternMatch( |
|
| 4029 | 4028 | self, |
|
| 4030 | 4029 | &subject, |
|
| 4031 | 4030 | &letElse.pattern, |
|
| 4032 | - | &mut successBlock, |
|
| 4031 | + | successBlock, |
|
| 4033 | 4032 | "success", |
|
| 4034 | 4033 | elseBlock, |
|
| 4035 | 4034 | ); |
|
| 4036 | 4035 | let mut bindingVar: ?Var = nil; |
|
| 4037 | 4036 | if let case ast::PatternKind::Binding = letElse.pattern.kind { |
| 4059 | 4058 | unsafe fn lowerWhileLet 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, w: ast::WhileLet) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
|
| 4060 | 4059 | let savedVarsLen = enterVarScope(&self.vars); |
|
| 4061 | 4060 | // Create control flow blocks: loop header, body (created lazily when |
|
| 4062 | 4061 | // there's a guard), and exit. |
|
| 4063 | 4062 | let whileBlock = try createBlock(self, "while"); |
|
| 4064 | - | let mut bodyBlock: BlockId = undefined; |
|
| 4063 | + | let mut bodyBlock: ?BlockId = nil; |
|
| 4065 | 4064 | if w.pattern.guard == nil { |
|
| 4066 | 4065 | set bodyBlock = try createBlock(self, "body"); |
|
| 4067 | 4066 | } |
|
| 4068 | 4067 | let endBlock = try createBlock(self, "merge"); |
|
| 4069 | 4068 |
| 4071 | 4070 | enterLoop(self, endBlock, whileBlock); |
|
| 4072 | 4071 | try switchAndJumpTo(self, whileBlock); |
|
| 4073 | 4072 | let subject = try lowerMatchSubject(self, w.pattern.scrutinee); |
|
| 4074 | 4073 | ||
| 4075 | 4074 | // Evaluate pattern and jump to loop body or loop end. |
|
| 4076 | - | try lowerPatternMatch(self, &subject, &w.pattern, &mut bodyBlock, "body", endBlock); |
|
| 4075 | + | try lowerPatternMatch(self, &subject, &w.pattern, bodyBlock, "body", endBlock); |
|
| 4077 | 4076 | ||
| 4078 | 4077 | // Lower loop body, jump back to loop header, and exit loop context. |
|
| 4079 | 4078 | try lowerBlock(self, w.body); |
|
| 4080 | 4079 | try emitJmpAndSeal(self, whileBlock); |
|
| 4081 | 4080 |
test/tests/pattern.guard.flow.rad
added
+68 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | ||
| 3 | + | /// Count guard evaluations. |
|
| 4 | + | fn guard(calls: &mut u32, accepted: bool) -> bool { |
|
| 5 | + | set *calls += 1; |
|
| 6 | + | return accepted; |
|
| 7 | + | } |
|
| 8 | + | ||
| 9 | + | /// Check guarded conditional binding. |
|
| 10 | + | fn conditional(value: u32, accepted: bool, calls: &mut u32) -> u32 { |
|
| 11 | + | if let case 1 = value; guard(calls, accepted) { |
|
| 12 | + | return 10; |
|
| 13 | + | } else { |
|
| 14 | + | return 20; |
|
| 15 | + | } |
|
| 16 | + | } |
|
| 17 | + | ||
| 18 | + | /// Check guarded binding with a diverging fallback. |
|
| 19 | + | fn binding(value: u32, accepted: bool, calls: &mut u32) -> u32 { |
|
| 20 | + | let case 1 = value if guard(calls, accepted) else { return 20; }; |
|
| 21 | + | return 10; |
|
| 22 | + | } |
|
| 23 | + | ||
| 24 | + | /// Check guard failure and pattern failure at loop exit. |
|
| 25 | + | fn looping(value: u32, limit: u32, calls: &mut u32) -> u32 { |
|
| 26 | + | let mut steps: u32 = 0; |
|
| 27 | + | while let case 1 = value; guard(calls, steps < limit) { |
|
| 28 | + | set steps += 1; |
|
| 29 | + | } |
|
| 30 | + | return steps; |
|
| 31 | + | } |
|
| 32 | + | ||
| 33 | + | /// Check preallocated success blocks without guards. |
|
| 34 | + | fn unguarded(value: u32) -> u32 { |
|
| 35 | + | let case 1 = value else { return 0; }; |
|
| 36 | + | let mut current = value; |
|
| 37 | + | let mut result: u32 = 0; |
|
| 38 | + | while let case 1 = current { |
|
| 39 | + | if let case 1 = current { |
|
| 40 | + | set result += 1; |
|
| 41 | + | } |
|
| 42 | + | set current = 0; |
|
| 43 | + | } |
|
| 44 | + | return result; |
|
| 45 | + | } |
|
| 46 | + | ||
| 47 | + | /// Exercise both pattern outcomes and guard outcomes for every binding form. |
|
| 48 | + | @default fn main() -> u32 { |
|
| 49 | + | for value in 0..3 { |
|
| 50 | + | assert unguarded(value) == (1 if value == 1 else 0); |
|
| 51 | + | for flag in 0..2 { |
|
| 52 | + | let accepted = flag == 1; |
|
| 53 | + | let expected: u32 = 10 if value == 1 and accepted else 20; |
|
| 54 | + | let mut calls: u32 = 0; |
|
| 55 | + | assert conditional(value, accepted, &mut calls) == expected; |
|
| 56 | + | assert calls == (1 if value == 1 else 0); |
|
| 57 | + | set calls = 0; |
|
| 58 | + | assert binding(value, accepted, &mut calls) == expected; |
|
| 59 | + | assert calls == (1 if value == 1 else 0); |
|
| 60 | + | } |
|
| 61 | + | for limit in 0..4 { |
|
| 62 | + | let mut calls: u32 = 0; |
|
| 63 | + | assert looping(value, limit, &mut calls) == (limit if value == 1 else 0); |
|
| 64 | + | assert calls == (limit + 1 if value == 1 else 0); |
|
| 65 | + | } |
|
| 66 | + | } |
|
| 67 | + | return 0; |
|
| 68 | + | } |