Support mutable optional pattern bindings
ff5b37ff058c768c87916e8408c8084bad7abacfaf7aa390c88adefffac9f29c
Eg. `if let mut ...` and `let mut ... else`
1 parent
43baa252
lib/std/arch/rv64/tests/if-let-mut.rad
added
+75 -0
| 1 | + | //! Test `if let mut` and `let mut ... else` bindings. |
|
| 2 | + | ||
| 3 | + | fn getOptional(x: i32) -> ?i32 { |
|
| 4 | + | if x > 0 { |
|
| 5 | + | return x; |
|
| 6 | + | } |
|
| 7 | + | return nil; |
|
| 8 | + | } |
|
| 9 | + | ||
| 10 | + | fn testIfLetMut() -> i32 { |
|
| 11 | + | let opt = getOptional(10); |
|
| 12 | + | ||
| 13 | + | if let mut v = opt { |
|
| 14 | + | v = v + 5; |
|
| 15 | + | if v != 15 { |
|
| 16 | + | return 1; |
|
| 17 | + | } |
|
| 18 | + | } else { |
|
| 19 | + | return 2; |
|
| 20 | + | } |
|
| 21 | + | return 0; |
|
| 22 | + | } |
|
| 23 | + | ||
| 24 | + | fn testIfLetMutNil() -> i32 { |
|
| 25 | + | let opt = getOptional(0); |
|
| 26 | + | ||
| 27 | + | if let mut v = opt { |
|
| 28 | + | v = v + 1; |
|
| 29 | + | return 3; |
|
| 30 | + | } |
|
| 31 | + | return 0; |
|
| 32 | + | } |
|
| 33 | + | ||
| 34 | + | fn testLetMutElse() -> i32 { |
|
| 35 | + | let opt = getOptional(42); |
|
| 36 | + | ||
| 37 | + | let mut v = opt else { |
|
| 38 | + | return 4; |
|
| 39 | + | }; |
|
| 40 | + | v = v + 8; |
|
| 41 | + | if v != 50 { |
|
| 42 | + | return 5; |
|
| 43 | + | } |
|
| 44 | + | return 0; |
|
| 45 | + | } |
|
| 46 | + | ||
| 47 | + | fn testLetMutElseNil() -> i32 { |
|
| 48 | + | let opt = getOptional(0); |
|
| 49 | + | ||
| 50 | + | let mut v = opt else { |
|
| 51 | + | return 0; |
|
| 52 | + | }; |
|
| 53 | + | v = v + 1; |
|
| 54 | + | return 6; |
|
| 55 | + | } |
|
| 56 | + | ||
| 57 | + | @default fn main() -> i32 { |
|
| 58 | + | let r1 = testIfLetMut(); |
|
| 59 | + | if r1 != 0 { |
|
| 60 | + | return r1; |
|
| 61 | + | } |
|
| 62 | + | let r2 = testIfLetMutNil(); |
|
| 63 | + | if r2 != 0 { |
|
| 64 | + | return r2; |
|
| 65 | + | } |
|
| 66 | + | let r3 = testLetMutElse(); |
|
| 67 | + | if r3 != 0 { |
|
| 68 | + | return r3; |
|
| 69 | + | } |
|
| 70 | + | let r4 = testLetMutElseNil(); |
|
| 71 | + | if r4 != 0 { |
|
| 72 | + | return r4; |
|
| 73 | + | } |
|
| 74 | + | return 0; |
|
| 75 | + | } |
lib/std/lang/ast.rad
+2 -0
| 393 | 393 | scrutinee: *Node, |
|
| 394 | 394 | /// Optional guard that must evaluate to `true`. |
|
| 395 | 395 | guard: ?*Node, |
|
| 396 | 396 | /// Whether this is a case pattern or binding. |
|
| 397 | 397 | kind: PatternKind, |
|
| 398 | + | /// Whether the binding is mutable. |
|
| 399 | + | mutable: bool, |
|
| 398 | 400 | } |
|
| 399 | 401 | ||
| 400 | 402 | /// `if let` conditional binding metadata. |
|
| 401 | 403 | pub record IfLet { |
|
| 402 | 404 | /// Pattern matching structure. |
lib/std/lang/ast/printer.rad
+15 -9
| 362 | 362 | case super::NodeValue::Assert { condition, message } => |
|
| 363 | 363 | return sexpr::list(a, "assert", &[toExpr(a, condition), toExprOrNull(a, message)]), |
|
| 364 | 364 | case super::NodeValue::If(c) => |
|
| 365 | 365 | return sexpr::block(a, "if", &[toExpr(a, c.condition)], |
|
| 366 | 366 | &[toExpr(a, c.thenBranch), toExprOrNull(a, c.elseBranch)]), |
|
| 367 | - | case super::NodeValue::IfLet(c) => |
|
| 368 | - | return sexpr::block(a, "if-let", &[ |
|
| 367 | + | case super::NodeValue::IfLet(c) => { |
|
| 368 | + | let label = "if-let-mut" if c.pattern.mutable else "if-let"; |
|
| 369 | + | return sexpr::block(a, label, &[ |
|
| 369 | 370 | toExpr(a, c.pattern.pattern), |
|
| 370 | 371 | toExpr(a, c.pattern.scrutinee), |
|
| 371 | 372 | guardExpr(a, c.pattern.guard) |
|
| 372 | 373 | ], &[ |
|
| 373 | 374 | toExpr(a, c.thenBranch), |
|
| 374 | 375 | toExprOrNull(a, c.elseBranch) |
|
| 375 | - | ]), |
|
| 376 | - | case super::NodeValue::LetElse(l) => |
|
| 377 | - | return sexpr::block(a, "let-else", &[ |
|
| 376 | + | ]); |
|
| 377 | + | } |
|
| 378 | + | case super::NodeValue::LetElse(l) => { |
|
| 379 | + | let label = "let-mut-else" if l.pattern.mutable else "let-else"; |
|
| 380 | + | return sexpr::block(a, label, &[ |
|
| 378 | 381 | toExpr(a, l.pattern.pattern), |
|
| 379 | 382 | toExpr(a, l.pattern.scrutinee), |
|
| 380 | 383 | guardExpr(a, l.pattern.guard) |
|
| 381 | - | ], &[toExpr(a, l.elseBranch)]), |
|
| 384 | + | ], &[toExpr(a, l.elseBranch)]); |
|
| 385 | + | } |
|
| 382 | 386 | case super::NodeValue::While(w) => |
|
| 383 | 387 | return sexpr::block(a, "while", &[ |
|
| 384 | 388 | toExpr(a, w.condition) |
|
| 385 | 389 | ], &[ |
|
| 386 | 390 | toExpr(a, w.body), |
|
| 387 | 391 | toExprOrNull(a, w.elseBranch) |
|
| 388 | 392 | ]), |
|
| 389 | - | case super::NodeValue::WhileLet(w) => |
|
| 390 | - | return sexpr::block(a, "while-let", &[ |
|
| 393 | + | case super::NodeValue::WhileLet(w) => { |
|
| 394 | + | let label = "while-let-mut" if w.pattern.mutable else "while-let"; |
|
| 395 | + | return sexpr::block(a, label, &[ |
|
| 391 | 396 | toExpr(a, w.pattern.pattern), |
|
| 392 | 397 | toExpr(a, w.pattern.scrutinee), |
|
| 393 | 398 | guardExpr(a, w.pattern.guard) |
|
| 394 | 399 | ], &[ |
|
| 395 | 400 | toExpr(a, w.body), |
|
| 396 | 401 | toExprOrNull(a, w.elseBranch) |
|
| 397 | - | ]), |
|
| 402 | + | ]); |
|
| 403 | + | } |
|
| 398 | 404 | case super::NodeValue::For(f) => |
|
| 399 | 405 | return sexpr::block(a, "for", &[ |
|
| 400 | 406 | toExpr(a, f.binding), |
|
| 401 | 407 | toExprOrNull(a, f.index), |
|
| 402 | 408 | toExpr(a, f.iterable) |
lib/std/lang/lower.rad
+10 -8
| 3006 | 3006 | self: *mut FnLowerer, |
|
| 3007 | 3007 | name: *[u8], |
|
| 3008 | 3008 | subjectVal: il::Val, |
|
| 3009 | 3009 | bindType: resolver::Type, |
|
| 3010 | 3010 | matchBy: resolver::MatchBy, |
|
| 3011 | - | valOffset: i32 |
|
| 3011 | + | valOffset: i32, |
|
| 3012 | + | mutable: bool |
|
| 3012 | 3013 | ) -> Var throws (LowerError) { |
|
| 3013 | 3014 | let base = emitValToReg(self, subjectVal); |
|
| 3014 | 3015 | let mut payload: il::Val = undefined; |
|
| 3015 | 3016 | ||
| 3016 | 3017 | match matchBy { |
|
| 3017 | 3018 | case resolver::MatchBy::Value => |
|
| 3018 | 3019 | payload = tvalPayloadVal(self, base, bindType, valOffset), |
|
| 3019 | 3020 | case resolver::MatchBy::Ref, resolver::MatchBy::MutRef => |
|
| 3020 | 3021 | payload = tvalPayloadAddr(self, base, valOffset), |
|
| 3021 | 3022 | }; |
|
| 3022 | - | return newVar(self, name, ilType(self.low, bindType), false, payload); |
|
| 3023 | + | return newVar(self, name, ilType(self.low, bindType), mutable, payload); |
|
| 3023 | 3024 | } |
|
| 3024 | 3025 | ||
| 3025 | 3026 | fn bindMatchVariable( |
|
| 3026 | 3027 | self: *mut FnLowerer, |
|
| 3027 | 3028 | subject: *MatchSubject, |
|
| 3028 | - | binding: *ast::Node |
|
| 3029 | + | binding: *ast::Node, |
|
| 3030 | + | mutable: bool |
|
| 3029 | 3031 | ) -> ?Var throws (LowerError) { |
|
| 3030 | 3032 | // Only bind if the pattern is an identifier. |
|
| 3031 | 3033 | let case ast::NodeValue::Ident(name) = binding.value else { |
|
| 3032 | 3034 | return nil; |
|
| 3033 | 3035 | }; |
|
| 3034 | 3036 | // For optional aggregates, extract the payload from the tagged value. |
|
| 3035 | 3037 | // The tag check already passed, so we know the payload is valid. |
|
| 3036 | 3038 | if let case MatchSubjectKind::OptionalAggregate = subject.kind { |
|
| 3037 | 3039 | let valOffset = resolver::getOptionalValOffset(subject.bindType) as i32; |
|
| 3038 | - | return try bindPayloadVariable(self, name, subject.val, subject.bindType, subject.by, valOffset); |
|
| 3040 | + | return try bindPayloadVariable(self, name, subject.val, subject.bindType, subject.by, valOffset, mutable); |
|
| 3039 | 3041 | } |
|
| 3040 | 3042 | // Declare the variable in the current block's scope. |
|
| 3041 | - | return newVar(self, name, ilType(self.low, subject.bindType), false, subject.val); |
|
| 3043 | + | return newVar(self, name, ilType(self.low, subject.bindType), mutable, subject.val); |
|
| 3042 | 3044 | } |
|
| 3043 | 3045 | ||
| 3044 | 3046 | /// Bind variables from inside case patterns (union variants, records, slices). |
|
| 3045 | 3047 | fn bindPatternVariables(self: *mut FnLowerer, subject: *MatchSubject, patterns: *mut [*ast::Node]) throws (LowerError) { |
|
| 3046 | 3048 | for pattern in patterns { |
| 3052 | 3054 | if let bindType = resolver::typeFor(self.low.resolver, arg) { |
|
| 3053 | 3055 | let case MatchSubjectKind::Union(unionInfo) = subject.kind |
|
| 3054 | 3056 | else panic "bindPatternVariables: expected union subject"; |
|
| 3055 | 3057 | let valOffset = unionInfo.valOffset as i32; |
|
| 3056 | 3058 | ||
| 3057 | - | try bindPayloadVariable(self, name, subject.val, bindType, subject.by, valOffset); |
|
| 3059 | + | try bindPayloadVariable(self, name, subject.val, bindType, subject.by, valOffset, false); |
|
| 3058 | 3060 | } |
|
| 3059 | 3061 | } |
|
| 3060 | 3062 | } |
|
| 3061 | 3063 | match pattern.value { |
|
| 3062 | 3064 | // Compound variant patterns like `Variant { a, b }`. |
| 3336 | 3338 | ||
| 3337 | 3339 | // Bind pattern variables after successful match. Note that the guard |
|
| 3338 | 3340 | // has not been evaluated yet. |
|
| 3339 | 3341 | match prong.arm { |
|
| 3340 | 3342 | case ast::ProngArm::Binding(pat) => |
|
| 3341 | - | try bindMatchVariable(self, &subject, pat), |
|
| 3343 | + | try bindMatchVariable(self, &subject, pat, false), |
|
| 3342 | 3344 | case ast::ProngArm::Case(patterns) => |
|
| 3343 | 3345 | try bindPatternVariables(self, &subject, patterns), |
|
| 3344 | 3346 | else => {}, |
|
| 3345 | 3347 | } |
|
| 3346 | 3348 |
| 3436 | 3438 | case ast::PatternKind::Binding => { |
|
| 3437 | 3439 | // Jump to `targetBlock` if there is a value present, `failBlock` otherwise. |
|
| 3438 | 3440 | try emitBindingTest(self, subject, targetBlock, failBlock); |
|
| 3439 | 3441 | try switchToAndSeal(self, targetBlock); |
|
| 3440 | 3442 | // Bind the matched value to the pattern variable. |
|
| 3441 | - | try bindMatchVariable(self, subject, pat.pattern); |
|
| 3443 | + | try bindMatchVariable(self, subject, pat.pattern, pat.mutable); |
|
| 3442 | 3444 | } |
|
| 3443 | 3445 | } |
|
| 3444 | 3446 | // Handle guard: on success jump to `successBlock`, on failure jump to `failBlock`. |
|
| 3445 | 3447 | if let g = pat.guard { |
|
| 3446 | 3448 | try emitCondBranch(self, g, *successBlock, failBlock); |
lib/std/lang/lower/tests/cond.iflet.mut.rad
added
+9 -0
| 1 | + | //! `if let mut` binding with mutation of the bound variable. |
|
| 2 | + | fn ifLetMut(p: ?i32) -> i32 { |
|
| 3 | + | if let mut x = p { |
|
| 4 | + | x = x + 1; |
|
| 5 | + | return x; |
|
| 6 | + | } else { |
|
| 7 | + | return 0; |
|
| 8 | + | } |
|
| 9 | + | } |
lib/std/lang/lower/tests/cond.iflet.mut.ril
added
+13 -0
| 1 | + | fn w32 $ifLetMut(w64 %0) { |
|
| 2 | + | @entry0 |
|
| 3 | + | reserve %1 8 4; |
|
| 4 | + | blit %1 %0 8; |
|
| 5 | + | load w8 %2 %1 0; |
|
| 6 | + | br.ne w32 %2 0 @then1 @else2; |
|
| 7 | + | @then1 |
|
| 8 | + | sload w32 %3 %1 4; |
|
| 9 | + | add w32 %4 %3 1; |
|
| 10 | + | ret %4; |
|
| 11 | + | @else2 |
|
| 12 | + | ret 0; |
|
| 13 | + | } |
lib/std/lang/lower/tests/cond.letelse.mut.rad
added
+8 -0
| 1 | + | //! `let mut ... else` binding with mutation of the bound variable. |
|
| 2 | + | fn letMutElse(p: ?i32) -> i32 { |
|
| 3 | + | let mut x = p else { |
|
| 4 | + | return 0; |
|
| 5 | + | }; |
|
| 6 | + | x = x + 1; |
|
| 7 | + | return x; |
|
| 8 | + | } |
lib/std/lang/lower/tests/cond.letelse.mut.ril
added
+13 -0
| 1 | + | fn w32 $letMutElse(w64 %0) { |
|
| 2 | + | @entry0 |
|
| 3 | + | reserve %1 8 4; |
|
| 4 | + | blit %1 %0 8; |
|
| 5 | + | load w8 %2 %1 0; |
|
| 6 | + | br.ne w32 %2 0 @merge1 @else2; |
|
| 7 | + | @merge1 |
|
| 8 | + | sload w32 %3 %1 4; |
|
| 9 | + | add w32 %4 %3 1; |
|
| 10 | + | ret %4; |
|
| 11 | + | @else2 |
|
| 12 | + | ret 0; |
|
| 13 | + | } |
lib/std/lang/parser.rad
+14 -11
| 1205 | 1205 | } |
|
| 1206 | 1206 | ||
| 1207 | 1207 | /// Parse an `if let` pattern matching statement. |
|
| 1208 | 1208 | /// |
|
| 1209 | 1209 | /// Syntax: `if let binding = scrutinee { ... }` |
|
| 1210 | + | /// Syntax: `if let mut binding = scrutinee { ... }` |
|
| 1210 | 1211 | fn parseIfLet(p: *mut Parser) -> *ast::Node throws (ParseError) { |
|
| 1211 | 1212 | try expect(p, scanner::TokenKind::Let, "expected `let`"); |
|
| 1212 | 1213 | ||
| 1213 | - | // Parse pattern: either `case <pattern>` or simple `<ident>`. |
|
| 1214 | + | // Parse pattern: either `case <pattern>`, `mut <ident>`, or simple `<ident>`. |
|
| 1214 | 1215 | let mut pattern: *ast::Node = undefined; |
|
| 1215 | 1216 | let mut kind = ast::PatternKind::Binding; |
|
| 1217 | + | let mut mutable = false; |
|
| 1216 | 1218 | ||
| 1217 | 1219 | if consume(p, scanner::TokenKind::Case) { |
|
| 1218 | 1220 | pattern = try parseMatchPattern(p); |
|
| 1219 | 1221 | kind = ast::PatternKind::Case; |
|
| 1220 | 1222 | } else { |
|
| 1221 | - | pattern = try parseIdentOrPlaceholder(p, "expected `case` or identifier after `let`"); |
|
| 1223 | + | mutable = consume(p, scanner::TokenKind::Mut); |
|
| 1224 | + | pattern = try parseIdentOrPlaceholder(p, "expected `case`, `mut`, or identifier after `let`"); |
|
| 1222 | 1225 | } |
|
| 1223 | 1226 | try expect(p, scanner::TokenKind::Equal, "expected `=` after pattern"); |
|
| 1224 | 1227 | ||
| 1225 | 1228 | let scrutinee = try parseCond(p); |
|
| 1226 | 1229 | let mut guard: ?*ast::Node = nil; |
| 1240 | 1243 | elseBranch = try parseBlock(p); |
|
| 1241 | 1244 | } |
|
| 1242 | 1245 | } |
|
| 1243 | 1246 | ||
| 1244 | 1247 | return node(p, ast::NodeValue::IfLet(ast::IfLet { |
|
| 1245 | - | pattern: ast::PatternMatch { pattern, scrutinee, guard, kind }, |
|
| 1248 | + | pattern: ast::PatternMatch { pattern, scrutinee, guard, kind, mutable }, |
|
| 1246 | 1249 | thenBranch, |
|
| 1247 | 1250 | elseBranch, |
|
| 1248 | 1251 | })); |
|
| 1249 | 1252 | } |
|
| 1250 | 1253 |
| 1252 | 1255 | fn parseWhileLet(p: *mut Parser) -> *ast::Node |
|
| 1253 | 1256 | throws (ParseError) |
|
| 1254 | 1257 | { |
|
| 1255 | 1258 | try expect(p, scanner::TokenKind::Let, "expected `let`"); |
|
| 1256 | 1259 | ||
| 1257 | - | // Parse pattern: either `case <pattern>` or simple `<ident>`. |
|
| 1260 | + | // Parse pattern: either `case <pattern>`, `mut <ident>`, or simple `<ident>`. |
|
| 1258 | 1261 | let mut pattern: *ast::Node = undefined; |
|
| 1259 | 1262 | let mut kind = ast::PatternKind::Binding; |
|
| 1263 | + | let mut mutable = false; |
|
| 1260 | 1264 | if consume(p, scanner::TokenKind::Case) { |
|
| 1261 | 1265 | pattern = try parseMatchPattern(p); |
|
| 1262 | 1266 | kind = ast::PatternKind::Case; |
|
| 1263 | 1267 | } else { |
|
| 1264 | - | pattern = try parseIdentOrPlaceholder(p, "expected `case` or identifier after `let`"); |
|
| 1268 | + | mutable = consume(p, scanner::TokenKind::Mut); |
|
| 1269 | + | pattern = try parseIdentOrPlaceholder(p, "expected `case`, `mut`, or identifier after `let`"); |
|
| 1265 | 1270 | } |
|
| 1266 | 1271 | try expect(p, scanner::TokenKind::Equal, "expected `=` after pattern"); |
|
| 1267 | 1272 | ||
| 1268 | 1273 | let scrutinee = try parseCond(p); |
|
| 1269 | 1274 | let mut guard: ?*ast::Node = nil; |
| 1276 | 1281 | ||
| 1277 | 1282 | if consume(p, scanner::TokenKind::Else) { |
|
| 1278 | 1283 | elseBranch = try parseBlock(p); |
|
| 1279 | 1284 | } |
|
| 1280 | 1285 | return node(p, ast::NodeValue::WhileLet(ast::WhileLet { |
|
| 1281 | - | pattern: ast::PatternMatch { pattern, scrutinee, guard, kind }, |
|
| 1286 | + | pattern: ast::PatternMatch { pattern, scrutinee, guard, kind, mutable }, |
|
| 1282 | 1287 | body, |
|
| 1283 | 1288 | elseBranch, |
|
| 1284 | 1289 | })); |
|
| 1285 | 1290 | } |
|
| 1286 | 1291 |
| 2219 | 2224 | ||
| 2220 | 2225 | try expect(p, scanner::TokenKind::Else, "expected `else` after pattern"); |
|
| 2221 | 2226 | let elseBranch = try parseLetElseBranch(p); |
|
| 2222 | 2227 | ||
| 2223 | 2228 | return node(p, ast::NodeValue::LetElse(ast::LetElse { |
|
| 2224 | - | pattern: ast::PatternMatch { pattern, scrutinee: expr, guard, kind: ast::PatternKind::Case }, |
|
| 2229 | + | pattern: ast::PatternMatch { pattern, scrutinee: expr, guard, kind: ast::PatternKind::Case, mutable: false }, |
|
| 2225 | 2230 | elseBranch, |
|
| 2226 | 2231 | })); |
|
| 2227 | 2232 | } |
|
| 2228 | 2233 | ||
| 2229 | 2234 | /// Parse a `let` binding statement. |
|
| 2230 | 2235 | /// |
|
| 2231 | 2236 | /// Eg. `let <ident> = <expr>;` |
|
| 2232 | 2237 | /// Eg. `let <ident> = <expr> else { ... };` |
|
| 2238 | + | /// Eg. `let mut <ident> = <expr> else { ... };` |
|
| 2233 | 2239 | /// Eg. `let <ident> = <expr> if <guard> else { ... };` |
|
| 2234 | 2240 | /// Eg. `mut <ident> = <expr>;` |
|
| 2235 | 2241 | /// |
|
| 2236 | 2242 | /// Expects `let` or `mut` token to have already been consumed. |
|
| 2237 | 2243 | fn parseLet(p: *mut Parser, mutable: bool) -> *ast::Node throws (ParseError) { |
| 2239 | 2245 | let value = binding.value |
|
| 2240 | 2246 | else throw failParsing(p, "expected value initializer"); |
|
| 2241 | 2247 | ||
| 2242 | 2248 | // Check for optional `else` clause (let-else). |
|
| 2243 | 2249 | if consume(p, scanner::TokenKind::Else) { |
|
| 2244 | - | if mutable { |
|
| 2245 | - | throw failParsing(p, "let-else bindings cannot be mutable"); |
|
| 2246 | - | } |
|
| 2247 | 2250 | let elseBranch = try parseLetElseBranch(p); |
|
| 2248 | 2251 | ||
| 2249 | 2252 | return node(p, ast::NodeValue::LetElse(ast::LetElse { |
|
| 2250 | - | pattern: ast::PatternMatch { pattern: binding.name, scrutinee: value, guard: nil, kind: ast::PatternKind::Binding }, |
|
| 2253 | + | pattern: ast::PatternMatch { pattern: binding.name, scrutinee: value, guard: nil, kind: ast::PatternKind::Binding, mutable }, |
|
| 2251 | 2254 | elseBranch, |
|
| 2252 | 2255 | })); |
|
| 2253 | 2256 | } |
|
| 2254 | 2257 | return node(p, ast::NodeValue::Let(ast::Let { |
|
| 2255 | 2258 | ident: binding.name, type: binding.type, value, alignment: binding.alignment, mutable, |
lib/std/lang/parser/tests.rad
+42 -0
| 1221 | 1221 | try expectIdent(inner.condition, "cond"); |
|
| 1222 | 1222 | try expectBlockExprStmt(inner.thenBranch, ast::NodeValue::Ident("alt")); |
|
| 1223 | 1223 | try testing::expect(inner.elseBranch == nil); |
|
| 1224 | 1224 | } |
|
| 1225 | 1225 | ||
| 1226 | + | /// Test parsing `if let mut` binding. |
|
| 1227 | + | @test fn testParseIfLetMut() throws (testing::TestError) { |
|
| 1228 | + | let root = try! parseStmtStr("if let mut value = opt { body; }"); |
|
| 1229 | + | let case ast::NodeValue::IfLet(node) = root.value |
|
| 1230 | + | else throw testing::TestError::Failed; |
|
| 1231 | + | ||
| 1232 | + | try expectIdent(node.pattern.pattern, "value"); |
|
| 1233 | + | try expectIdent(node.pattern.scrutinee, "opt"); |
|
| 1234 | + | try testing::expect(node.pattern.mutable); |
|
| 1235 | + | try testing::expect(node.pattern.guard == nil); |
|
| 1236 | + | try expectBlockExprStmt(node.thenBranch, ast::NodeValue::Ident("body")); |
|
| 1237 | + | try testing::expect(node.elseBranch == nil); |
|
| 1238 | + | } |
|
| 1239 | + | ||
| 1240 | + | /// Test parsing `let mut ... else` binding. |
|
| 1241 | + | @test fn testParseLetMutElse() throws (testing::TestError) { |
|
| 1242 | + | let root = try! parseStmtStr("let mut x = opt else { return };"); |
|
| 1243 | + | let case ast::NodeValue::LetElse(letElse) = root.value |
|
| 1244 | + | else throw testing::TestError::Failed; |
|
| 1245 | + | ||
| 1246 | + | try expectIdent(letElse.pattern.pattern, "x"); |
|
| 1247 | + | try testing::expect(letElse.pattern.mutable); |
|
| 1248 | + | } |
|
| 1249 | + | ||
| 1250 | + | /// Test that `if let` without `mut` is not mutable. |
|
| 1251 | + | @test fn testParseIfLetNotMutable() throws (testing::TestError) { |
|
| 1252 | + | let root = try! parseStmtStr("if let value = opt { body; }"); |
|
| 1253 | + | let case ast::NodeValue::IfLet(node) = root.value |
|
| 1254 | + | else throw testing::TestError::Failed; |
|
| 1255 | + | ||
| 1256 | + | try testing::expect(not node.pattern.mutable); |
|
| 1257 | + | } |
|
| 1258 | + | ||
| 1259 | + | /// Test that `let ... else` without `mut` is not mutable. |
|
| 1260 | + | @test fn testParseLetElseNotMutable() throws (testing::TestError) { |
|
| 1261 | + | let root = try! parseStmtStr("let x = opt else { return };"); |
|
| 1262 | + | let case ast::NodeValue::LetElse(letElse) = root.value |
|
| 1263 | + | else throw testing::TestError::Failed; |
|
| 1264 | + | ||
| 1265 | + | try testing::expect(not letElse.pattern.mutable); |
|
| 1266 | + | } |
|
| 1267 | + | ||
| 1226 | 1268 | /// Test parsing a simple `if let case` statement. |
|
| 1227 | 1269 | @test fn testParseIfCase() throws (testing::TestError) { |
|
| 1228 | 1270 | let root = try! parseStmtStr("if let case pat = value { body; }"); |
|
| 1229 | 1271 | let case ast::NodeValue::IfLet(node) = root.value |
|
| 1230 | 1272 | else throw testing::TestError::Failed; |
lib/std/lang/resolver.rad
+11 -15
| 2037 | 2037 | let sym = try bindIdent(self, name, owner, data, attrs, self.scope); |
|
| 2038 | 2038 | setNodeType(self, owner, type); |
|
| 2039 | 2039 | setNodeType(self, ident, type); |
|
| 2040 | 2040 | ||
| 2041 | 2041 | // Track number of local bindings for lowering stage. |
|
| 2042 | - | // TODO: Support `if let mut`. |
|
| 2043 | 2042 | if let fnType = self.currentFn { |
|
| 2044 | 2043 | let mut ty = fnType; |
|
| 2045 | 2044 | ty.localCount = fnType.localCount + 1; |
|
| 2046 | 2045 | } |
|
| 2047 | 2046 | return sym; |
| 3838 | 3837 | case ast::PatternKind::Binding => { |
|
| 3839 | 3838 | // Scrutinee must be optional, bind the payload. |
|
| 3840 | 3839 | let scrutineeTy = try checkOptional(self, pat.scrutinee); |
|
| 3841 | 3840 | let payloadTy = *scrutineeTy; |
|
| 3842 | 3841 | ||
| 3843 | - | try bindValueIdent(self, pat.pattern, node, payloadTy, false, 0, 0); |
|
| 3842 | + | try bindValueIdent(self, pat.pattern, node, payloadTy, pat.mutable, 0, 0); |
|
| 3844 | 3843 | setNodeType(self, pat.pattern, payloadTy); |
|
| 3845 | 3844 | } |
|
| 3846 | 3845 | } |
|
| 3847 | 3846 | if let guard = pat.guard { |
|
| 3848 | 3847 | try checkBoolean(self, guard); |
| 4548 | 4547 | let pat = &letElse.pattern; |
|
| 4549 | 4548 | let exprTy = try infer(self, pat.scrutinee); |
|
| 4550 | 4549 | ||
| 4551 | 4550 | match pat.kind { |
|
| 4552 | 4551 | case ast::PatternKind::Binding => { |
|
| 4553 | - | // Simple binding only works when expression is optional. |
|
| 4554 | - | if let case Type::Optional(inner) = exprTy { |
|
| 4555 | - | // Simple binding, unwrap optional payload. |
|
| 4556 | - | let payloadTy = *inner; |
|
| 4557 | - | let _ = try bindValueIdent(self, pat.pattern, node, payloadTy, false, 0, 0); |
|
| 4558 | - | // The `else` branch must be assignable to the payload type. |
|
| 4559 | - | try checkAssignable(self, letElse.elseBranch, payloadTy); |
|
| 4560 | - | ||
| 4561 | - | return setNodeType(self, node, Type::Void); |
|
| 4562 | - | } |
|
| 4563 | - | // FIXME: This shouldn't be valid, should it? What scenario needs this? |
|
| 4564 | - | // Fall through to pattern matching if not optional. |
|
| 4565 | - | try resolveCasePattern(self, pat.pattern, exprTy, IdentMode::Compare, MatchBy::Value); |
|
| 4552 | + | // Simple binding requires an optional expression. |
|
| 4553 | + | let case Type::Optional(inner) = exprTy else { |
|
| 4554 | + | throw emitError(self, pat.scrutinee, ErrorKind::ExpectedOptional); |
|
| 4555 | + | }; |
|
| 4556 | + | let payloadTy = *inner; |
|
| 4557 | + | let _ = try bindValueIdent(self, pat.pattern, node, payloadTy, pat.mutable, 0, 0); |
|
| 4558 | + | // The `else` branch must be assignable to the payload type. |
|
| 4559 | + | try checkAssignable(self, letElse.elseBranch, payloadTy); |
|
| 4560 | + | ||
| 4561 | + | return setNodeType(self, node, Type::Void); |
|
| 4566 | 4562 | } |
|
| 4567 | 4563 | case ast::PatternKind::Case => { |
|
| 4568 | 4564 | // Analyze pattern against expression type. |
|
| 4569 | 4565 | try resolveCasePattern(self, pat.pattern, exprTy, IdentMode::Compare, MatchBy::Value); |
|
| 4570 | 4566 | } |
lib/std/lang/resolver/tests.rad
+41 -0
| 2204 | 2204 | let result = try resolveProgramStr(&mut a, program); |
|
| 2205 | 2205 | let err = try expectError(&result); |
|
| 2206 | 2206 | try expectTypeMismatch(err, super::Type::I32, super::Type::Void); |
|
| 2207 | 2207 | } |
|
| 2208 | 2208 | ||
| 2209 | + | @test fn testResolveLetElseRequiresOptional() throws (testing::TestError) { |
|
| 2210 | + | let mut a = testResolver(); |
|
| 2211 | + | let program = "let x: i32 = 42; let value = x else panic;"; |
|
| 2212 | + | let result = try resolveProgramStr(&mut a, program); |
|
| 2213 | + | try expectErrorKind(&result, super::ErrorKind::ExpectedOptional); |
|
| 2214 | + | } |
|
| 2215 | + | ||
| 2216 | + | /// Test that `if let mut` produces a mutable binding. |
|
| 2217 | + | @test fn testResolveIfLetMut() throws (testing::TestError) { |
|
| 2218 | + | let mut a = testResolver(); |
|
| 2219 | + | let program = "let opt: ?i32 = 42; if let mut v = opt { v = v + 1; }"; |
|
| 2220 | + | let result = try resolveProgramStr(&mut a, program); |
|
| 2221 | + | try expectNoErrors(&result); |
|
| 2222 | + | } |
|
| 2223 | + | ||
| 2224 | + | /// Test that `if let` (without mut) rejects assignment. |
|
| 2225 | + | @test fn testResolveIfLetImmutable() throws (testing::TestError) { |
|
| 2226 | + | let mut a = testResolver(); |
|
| 2227 | + | let program = "let opt: ?i32 = 42; if let v = opt { v = 1; }"; |
|
| 2228 | + | let result = try resolveProgramStr(&mut a, program); |
|
| 2229 | + | let err = try expectError(&result); |
|
| 2230 | + | try expectErrorKind(&result, super::ErrorKind::ImmutableBinding); |
|
| 2231 | + | } |
|
| 2232 | + | ||
| 2233 | + | /// Test that `let mut ... else` produces a mutable binding. |
|
| 2234 | + | @test fn testResolveLetMutElse() throws (testing::TestError) { |
|
| 2235 | + | let mut a = testResolver(); |
|
| 2236 | + | let program = "let opt: ?i32 = 42; let mut v = opt else panic; v = v + 1;"; |
|
| 2237 | + | let result = try resolveProgramStr(&mut a, program); |
|
| 2238 | + | try expectNoErrors(&result); |
|
| 2239 | + | } |
|
| 2240 | + | ||
| 2241 | + | /// Test that `let ... else` (without mut) rejects assignment. |
|
| 2242 | + | @test fn testResolveLetElseImmutable() throws (testing::TestError) { |
|
| 2243 | + | let mut a = testResolver(); |
|
| 2244 | + | let program = "let opt: ?i32 = 42; let v = opt else panic; v = 1;"; |
|
| 2245 | + | let result = try resolveProgramStr(&mut a, program); |
|
| 2246 | + | let err = try expectError(&result); |
|
| 2247 | + | try expectErrorKind(&result, super::ErrorKind::ImmutableBinding); |
|
| 2248 | + | } |
|
| 2249 | + | ||
| 2209 | 2250 | @test fn testResolveLetCaseElse() throws (testing::TestError) { |
|
| 2210 | 2251 | { |
|
| 2211 | 2252 | let mut a = testResolver(); |
|
| 2212 | 2253 | let program = "let case _ = 1 else panic;"; |
|
| 2213 | 2254 | let result = try resolveProgramStr(&mut a, program); |