Support mutable optional pattern bindings

ff5b37ff058c768c87916e8408c8084bad7abacfaf7aa390c88adefffac9f29c
Eg. `if let mut ...` and `let mut ... else`
Alexis Sellier committed ago 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);