Fix some long-standing tech debt

7bfd64dd51d19a55f370068c8d96a85af244b559392a6ec148b1cf8321c9fb47
Alexis Sellier committed ago 1 parent e0e2e01d
compiler/radiance.rad +3 -3
542 542
        funcPath[j - 1] = desc.modPath[j];
543 543
    }
544 544
    funcPath[desc.modPath.len - 1] = desc.fnName;
545 545
    let funcArg = synthScopeAccess(arena, &funcPath[..desc.modPath.len]);
546 546
547 -
    let a = ast::nodeAllocator(arena);
547 +
    let a = alloc::arenaAllocator(&mut arena.arena);
548 548
    let args = ast::nodeSlice(arena, 3)
549 549
        .append(modArg, a)
550 550
        .append(nameArg, a)
551 551
        .append(funcArg, a);
552 552
599 599
}
600 600
601 601
/// Synthesize the test entry point.
602 602
fn synthTestMainFn(arena: *mut ast::NodeArena, tests: *[TestDesc]) -> *ast::Node {
603 603
    // Build array literal: `[testing::test(...), ...]`.
604 -
    let a = ast::nodeAllocator(arena);
604 +
    let a = alloc::arenaAllocator(&mut arena.arena);
605 605
    let mut elements = ast::nodeSlice(arena, tests.len as u32);
606 606
    for i in 0..tests.len {
607 607
        elements.append(synthTestCall(arena, &tests[i]), a);
608 608
    }
609 609
    let arrayLit = ast::synthNode(arena, ast::NodeValue::ArrayLit(elements));
653 653
    decl: *ast::Node
654 654
) {
655 655
    let case ast::NodeValue::Block(block) = blockNode.value else {
656 656
        panic "injectIntoBlock: expected Block node";
657 657
    };
658 -
    let stmts = block.statements.append(decl, ast::nodeAllocator(arena));
658 +
    let stmts = block.statements.append(decl, alloc::arenaAllocator(&mut arena.arena));
659 659
    blockNode.value = ast::NodeValue::Block(ast::Block { statements: stmts });
660 660
}
661 661
662 662
/// Write code buffer to file as raw bytes.
663 663
fn writeCode(code: *[u32], path: *[u8]) -> bool {
lib/std/arch/rv64.rad +0 -1
131 131
132 132
/// Get target configuration for register allocation.
133 133
// TODO: This should be a constant variable.
134 134
pub fn targetConfig() -> regalloc::TargetConfig {
135 135
    return regalloc::TargetConfig {
136 -
        // TODO: Use inline slice when we move to self-hosted compiler.
137 136
        allocatable: &ALLOCATABLE_REGS[..],
138 137
        argRegs: &ARG_REG_NUMS[..],
139 138
        calleeSaved: &CALLEE_SAVED_NUMS[..],
140 139
        slotSize: DWORD_SIZE,
141 140
    };
lib/std/lang/ast.rad +1 -7
33 33
    let ptr = try! alloc::allocSlice(&mut arena.arena, @sizeOf(*Node), @alignOf(*Node), capacity);
34 34
35 35
    return @sliceOf(ptr.ptr as *mut *Node, 0, capacity);
36 36
}
37 37
38 -
/// Return an allocator backed by the node arena.
39 -
// TODO: Why do we need this?
40 -
pub fn nodeAllocator(arena: *mut NodeArena) -> alloc::Allocator {
41 -
    return alloc::arenaAllocator(&mut arena.arena);
42 -
}
43 -
44 38
/// Attribute bit set applied to declarations or fields.
45 39
pub union Attribute {
46 40
    /// Public visibility attribute.
47 41
    Pub = 0b1,
48 42
    /// Default implementation attribute.
848 842
849 843
/// Synthesize a module with a function in it with the given name and statements.
850 844
pub fn synthFnModule(
851 845
    arena: *mut NodeArena, name: *[u8], bodyStmts: *mut [*Node]
852 846
) -> SynthFnMod {
853 -
    let a = nodeAllocator(arena);
847 +
    let a = alloc::arenaAllocator(&mut arena.arena);
854 848
    let fnName = synthNode(arena, NodeValue::Ident(name));
855 849
    let params: *mut [*Node] = &mut [];
856 850
    let throwList: *mut [*Node] = &mut [];
857 851
    let fnSig = FnSig { params, returnType: nil, throwList };
858 852
    let fnBody = synthNode(arena, NodeValue::Block(Block { statements: bodyStmts }));
lib/std/lang/gen/data.rad +6 -15
97 97
                        },
98 98
                        case il::DataItem::Sym(name) => {
99 99
                            let addr = lookupAddr(dataSymMap, name) else {
100 100
                                panic "emitSection: data symbol not found";
101 101
                            };
102 -
                            // Write 8-byte pointer: low 4 bytes are the
103 -
                            // address, high 4 bytes are zero.
104 -
                            // TODO: Use `u64` once it's supported.
105 -
                            let lo: u32 = addr;
106 -
                            let hi: u32 = 0;
107 -
                            let loPtr = &lo as *u8;
108 -
                            let hiPtr = &hi as *u8;
102 +
                            let addr64: u64 = addr as u64;
103 +
                            let addrPtr = &addr64 as *u8;
109 104
110 -
                            try! mem::copy(&mut buf[offset..], @sliceOf(loPtr, 4));
111 -
                            try! mem::copy(&mut buf[(offset + 4)..], @sliceOf(hiPtr, 4));
105 +
                            try! mem::copy(&mut buf[offset..], @sliceOf(addrPtr, 8));
112 106
113 107
                            offset += 8;
114 108
                        },
115 109
                        case il::DataItem::Fn(name) => {
116 110
                            let addr = codeBase + labels::funcOffset(fnLabels, name) as u32;
117 -
                            let lo: u32 = addr;
118 -
                            let hi: u32 = 0;
119 -
                            let loPtr = &lo as *u8;
120 -
                            let hiPtr = &hi as *u8;
111 +
                            let addr64: u64 = addr as u64;
112 +
                            let addrPtr = &addr64 as *u8;
121 113
122 -
                            try! mem::copy(&mut buf[offset..], @sliceOf(loPtr, 4));
123 -
                            try! mem::copy(&mut buf[(offset + 4)..], @sliceOf(hiPtr, 4));
114 +
                            try! mem::copy(&mut buf[offset..], @sliceOf(addrPtr, 8));
124 115
125 116
                            offset += 8;
126 117
                        },
127 118
                        case il::DataItem::Str(s) => {
128 119
                            try! mem::copy(&mut buf[offset..], s);
lib/std/lang/gen/regalloc/liveness.rad +0 -1
27 27
use std::lang::il;
28 28
use std::lang::alloc;
29 29
use std::lang::gen::bitset;
30 30
31 31
/// Maximum number of SSA registers supported.
32 -
// TODO: Why so many?
33 32
pub const MAX_SSA_REGS: u32 = 8192;
34 33
35 34
/// Liveness information for a function.
36 35
pub record LiveInfo {
37 36
    /// Per-block live-in sets (indexed by block index).
lib/std/lang/lower.rad +35 -51
973 973
    let mut pos: u32 = 0;
974 974
975 975
    pos += try! mem::copy(&mut buf[pos..], typeName);
976 976
    pos += try! mem::copy(&mut buf[pos..], "::");
977 977
    pos += try! mem::copy(&mut buf[pos..], methodName);
978 -
979 -
    // TODO: Assert that `pos` equals `totalLen`.
978 +
    assert pos == totalLen;
980 979
981 980
    return qualifyName(self, modId, &buf[..totalLen]);
982 981
}
983 982
984 983
/// Build a v-table data name of the form "vtable::Type::Trait".
991 990
992 991
    pos += try! mem::copy(&mut buf[pos..], prefix);
993 992
    pos += try! mem::copy(&mut buf[pos..], typeName);
994 993
    pos += try! mem::copy(&mut buf[pos..], "::");
995 994
    pos += try! mem::copy(&mut buf[pos..], traitName);
996 -
997 -
    // TODO: Assert that `pos` equals `totalLen`.
995 +
    assert pos == totalLen;
998 996
999 997
    return qualifyName(self, modId, &buf[..totalLen]);
1000 998
}
1001 999
1002 1000
/// Lower an instance declaration (`instance Trait for Type { ... }`).
3359 3357
                defaultIdx = i;
3360 3358
            }
3361 3359
            case ast::ProngArm::Case(pats) => {
3362 3360
                blocks[i] = try createBlock(self, "case");
3363 3361
                for pat in pats {
3364 -
                    let cv = resolver::constValueFor(self.low.resolver, pat)
3362 +
                    let cv = resolver::constValueEntry(self.low.resolver, pat)
3365 3363
                        else throw LowerError::MissingConst(pat);
3366 3364
3367 3365
                    cases.append(il::SwitchCase {
3368 3366
                        value: constToScalar(cv),
3369 3367
                        target: blocks[i].n,
4246 4244
    let lenEq = try emitEqAtOffset(self, a, b, offset + SLICE_LEN_OFFSET, resolver::Type::U32);
4247 4245
4248 4246
    return emitTypedBinOp(self, il::BinOp::And, il::Type::W32, ptrEq, lenEq);
4249 4247
}
4250 4248
4251 -
/// Check if a type may contain uninitialized payload bytes when used as the
4252 -
/// inner type of a `nil` optional. Unions with non-void variants and nested
4253 -
/// optionals fall into this category; records and primitives do not.
4254 -
fn needsGuardedPayloadCmp(inner: resolver::Type) -> bool {
4255 -
    if let _ = unionInfoFromType(inner) {
4256 -
        return true;
4257 -
    }
4258 -
    match inner {
4259 -
        case resolver::Type::Optional(_) => return true,
4260 -
        else => return false,
4261 -
    }
4262 -
}
4263 -
4264 4249
/// Compare two optional aggregate values for equality.
4265 4250
///
4266 4251
/// Two optionals are equal when their tags match and either both are `nil` or
4267 4252
/// their payloads are equal.
4268 4253
///
4284 4269
    // Load tags.
4285 4270
    let tagA = loadTag(self, a, offset + TVAL_TAG_OFFSET, il::Type::W8);
4286 4271
    let tagB = loadTag(self, b, offset + TVAL_TAG_OFFSET, il::Type::W8);
4287 4272
4288 4273
    // For simple inner types (no unions/nested optionals), use branchless comparison.
4289 -
    // TODO: Inline this function.
4290 -
    if not needsGuardedPayloadCmp(inner) {
4274 +
    // Unions and nested optionals may contain uninitialized payload bytes
4275 +
    // when nil, so they need a guarded comparison.
4276 +
    let isUnion = unionInfoFromType(inner) != nil;
4277 +
    let mut isOptional = false;
4278 +
    if let case resolver::Type::Optional(_) = inner {
4279 +
        isOptional = true;
4280 +
    }
4281 +
    if not isUnion and not isOptional {
4291 4282
        let tagEq = emitTypedBinOp(self, il::BinOp::Eq, il::Type::W8, tagA, tagB);
4292 4283
        let tagNil = emitTypedBinOp(self, il::BinOp::Eq, il::Type::W8, tagA, il::Val::Imm(0));
4293 4284
        let payloadEq = try emitEqAtOffset(self, a, b, offset + valOffset, inner);
4294 4285
4295 4286
        return emitTypedBinOp(self, il::BinOp::And, il::Type::W32, tagEq,
4531 4522
            try lowerRecordFields(self, dst, &recInfo, lit.fields, 0);
4532 4523
4533 4524
            return il::Val::Reg(dst);
4534 4525
        }
4535 4526
        case resolver::Type::Nominal(resolver::NominalType::Union(_)) => {
4536 -
            // TODO: This can be inlined once we have a real register allocator.
4537 -
            return try lowerUnionRecordLit(self, typ, lit);
4527 +
            let typeName = lit.typeName else {
4528 +
                throw LowerError::ExpectedVariant;
4529 +
            };
4530 +
            let sym = try symOf(self, typeName);
4531 +
            let case resolver::SymbolData::Variant { type: payloadType, index, .. } = sym.data else {
4532 +
                throw LowerError::ExpectedVariant;
4533 +
            };
4534 +
            let recInfo = recordInfoFromType(payloadType) else {
4535 +
                throw LowerError::ExpectedRecord;
4536 +
            };
4537 +
            let unionInfo = unionInfoFromType(typ) else {
4538 +
                throw LowerError::MissingMetadata;
4539 +
            };
4540 +
            let valOffset = unionInfo.valOffset as i32;
4541 +
            let dst = try emitReserve(self, typ);
4542 +
4543 +
            emitStoreW8At(self, il::Val::Imm(index as i64), dst, TVAL_TAG_OFFSET);
4544 +
            try lowerRecordFields(self, dst, &recInfo, lit.fields, valOffset);
4545 +
4546 +
            return il::Val::Reg(dst);
4538 4547
        }
4539 4548
        else => throw LowerError::UnexpectedType(&typ),
4540 4549
    }
4541 4550
}
4542 4551
4543 -
/// Lower a union variant record literal like `Union::Variant { field: value }`.
4544 -
fn lowerUnionRecordLit(self: *mut FnLowerer, typ: resolver::Type, lit: ast::RecordLit) -> il::Val throws (LowerError) {
4545 -
    let typeName = lit.typeName else {
4546 -
        throw LowerError::ExpectedVariant;
4547 -
    };
4548 -
    let sym = try symOf(self, typeName);
4549 -
    let case resolver::SymbolData::Variant { type: payloadType, index, .. } = sym.data else {
4550 -
        throw LowerError::ExpectedVariant;
4551 -
    };
4552 -
    let recInfo = recordInfoFromType(payloadType) else {
4553 -
        throw LowerError::ExpectedRecord;
4554 -
    };
4555 -
    let unionInfo = unionInfoFromType(typ) else {
4556 -
        throw LowerError::MissingMetadata;
4557 -
    };
4558 -
    let valOffset = unionInfo.valOffset as i32;
4559 -
    let dst = try emitReserve(self, typ);
4560 -
4561 -
    emitStoreW8At(self, il::Val::Imm(index as i64), dst, TVAL_TAG_OFFSET);
4562 -
    try lowerRecordFields(self, dst, &recInfo, lit.fields, valOffset);
4563 -
4564 -
    return il::Val::Reg(dst);
4565 -
}
4566 -
4567 4552
/// Lower fields of a record literal into a destination register.
4568 4553
/// The `offset` is added to each field's offset when storing.
4569 4554
fn lowerRecordFields(
4570 4555
    self: *mut FnLowerer,
4571 4556
    dst: il::Reg,
5851 5836
    let dst = nextReg(self);
5852 5837
    let mut needsExt: bool = false;
5853 5838
5854 5839
    match unop.op {
5855 5840
        case ast::UnaryOp::Not => {
5856 -
            // TODO: Is this the best way?
5857 5841
            emit(self, il::Instr::BinOp { op: il::BinOp::Eq, typ, dst, a: val, b: il::Val::Imm(0) });
5858 5842
        }
5859 5843
        case ast::UnaryOp::Neg => {
5860 5844
            emit(self, il::Instr::UnOp { op: il::UnOp::Neg, typ, dst, a: val });
5861 5845
            needsExt = true;
5923 5907
/// Lower a builtin call expression.
5924 5908
fn lowerBuiltinCall(self: *mut FnLowerer, node: *ast::Node, kind: ast::Builtin, args: *mut [*ast::Node]) -> il::Val throws (LowerError) {
5925 5909
    match kind {
5926 5910
        case ast::Builtin::SliceOf => return try lowerSliceOf(self, node, args),
5927 5911
        case ast::Builtin::SizeOf, ast::Builtin::AlignOf => {
5928 -
            let constVal = resolver::constValueFor(self.low.resolver, node) else {
5912 +
            let constVal = resolver::constValueEntry(self.low.resolver, node) else {
5929 5913
                throw LowerError::MissingConst(node);
5930 5914
            };
5931 5915
            return try constValueToVal(self, constVal, node);
5932 5916
        }
5933 5917
    }
6731 6715
}
6732 6716
6733 6717
/// Lower an identifier that refers to a global symbol.
6734 6718
fn lowerGlobalSymbol(self: *mut FnLowerer, node: *ast::Node) -> il::Val throws (LowerError) {
6735 6719
    // First try to get a compile-time constant value.
6736 -
    if let constVal = resolver::constValueFor(self.low.resolver, node) {
6720 +
    if let constVal = resolver::constValueEntry(self.low.resolver, node) {
6737 6721
        return try constValueToVal(self, constVal, node);
6738 6722
    }
6739 6723
    // Otherwise get the symbol.
6740 6724
    let sym = try symOf(self, node);
6741 6725
    let mut ty: resolver::Type = undefined;
6772 6756
6773 6757
/// Lower a scope access expression like `Module::Const` or `Union::Variant`.
6774 6758
/// This doesn't handle record literal variants.
6775 6759
fn lowerScopeAccess(self: *mut FnLowerer, node: *ast::Node) -> il::Val throws (LowerError) {
6776 6760
    // First try to get a compile-time constant value.
6777 -
    if let constVal = resolver::constValueFor(self.low.resolver, node) {
6761 +
    if let constVal = resolver::constValueEntry(self.low.resolver, node) {
6778 6762
        return try constValueToVal(self, constVal, node);
6779 6763
    }
6780 6764
    // Otherwise get the associated symbol.
6781 6765
    let data = resolver::nodeData(self.low.resolver, node);
6782 6766
    let sym = data.sym else {
6902 6886
        case ast::NodeValue::Try(t) => {
6903 6887
            val = try lowerTry(self, node, t);
6904 6888
        }
6905 6889
        case ast::NodeValue::FieldAccess(access) => {
6906 6890
            // Check for compile-time constant (e.g., `arr.len` on fixed-size arrays).
6907 -
            if let constVal = resolver::constValueFor(self.low.resolver, node) {
6891 +
            if let constVal = resolver::constValueEntry(self.low.resolver, node) {
6908 6892
                match constVal {
6909 6893
                    // TODO: Handle `u32` values that don't fit in an `i32`.
6910 6894
                    //       Perhaps just store the `ConstInt`.
6911 6895
                    case resolver::ConstValue::Int(i) => val = il::Val::Imm(constIntToI64(i)),
6912 6896
                    else => val = try lowerFieldAccess(self, access),
lib/std/lang/module.rad +1 -1
302 302
    }
303 303
    let childName = childPath[childPath.len - 1];
304 304
305 305
    // Navigate through all but the last segment to find the parent.
306 306
    let mut parentId = root;
307 -
    for part in &childPath[..(childPath.len - 1)] {
307 +
    for part in &childPath[..childPath.len - 1] {
308 308
        let child = findChild(graph, part, parentId) else {
309 309
            throw ModuleError::MissingParent;
310 310
        };
311 311
        parentId = child.id;
312 312
    }
lib/std/lang/parser.rad +1 -3
159 159
        scanner: scanner::scanner(sourceLoc, source, pool),
160 160
        current: scanner::invalid(0, ""),
161 161
        previous: scanner::invalid(0, ""),
162 162
        errors: ErrorList { list: undefined, count: 0 },
163 163
        arena,
164 -
        allocator: ast::nodeAllocator(arena),
164 +
        allocator: alloc::arenaAllocator(&mut arena.arena),
165 165
        context: Context::Normal,
166 166
    };
167 167
}
168 168
169 169
/// Emit a `true` or `false` literal node.
2268 2268
2269 2269
    let mut blk = mkBlock(p, 512);
2270 2270
    try parseStmtsUntil(p, scanner::TokenKind::Eof, &mut blk);
2271 2271
    consume(p, scanner::TokenKind::Eof);
2272 2272
2273 -
    // TODO: Check that there are no further tokens remaining to parse.
2274 -
2275 2273
    return node(p, ast::NodeValue::Block(blk));
2276 2274
}
2277 2275
2278 2276
/// Consume a token of the given kind if present.
2279 2277
pub fn consume(p: *mut Parser, kind: scanner::TokenKind) -> bool {
lib/std/lang/resolver.rad +60 -37
759 759
    /// Diagnostics recorded so far.
760 760
    errors: *mut [Error],
761 761
    /// Module graph for the current package.
762 762
    moduleGraph: *module::ModuleGraph,
763 763
    /// Cache of module scopes indexed by module ID.
764 -
    // TODO: Why is this optional?
765 764
    moduleScopes: [?*mut Scope; module::MAX_MODULES],
766 765
    /// Trait instance registry.
767 766
    instances: [InstanceEntry; MAX_INSTANCES],
768 767
    /// Number of registered instances.
769 768
    instancesLen: u32,
1174 1173
/// Retrieve the constant value associated with a node, if any.
1175 1174
pub fn constValueEntry(self: *Resolver, node: *ast::Node) -> ?ConstValue {
1176 1175
    return self.nodeData.entries[node.id].constValue;
1177 1176
}
1178 1177
1179 -
/// Get the constant value bound to a node, if present.
1180 -
// TODO: Use `constValueEntry`
1181 -
pub fn constValueFor(self: *Resolver, node: *ast::Node) -> ?ConstValue {
1182 -
    return self.nodeData.entries[node.id].constValue;
1183 -
}
1184 -
1185 1178
/// Get the resolved record field index for a record literal field node.
1186 1179
pub fn recordFieldIndexFor(self: *Resolver, node: *ast::Node) -> ?u32 {
1187 1180
    if let case NodeExtra::RecordField { index } = self.nodeData.entries[node.id].extra {
1188 1181
        return index;
1189 1182
    }
1333 1326
             Type::Int => return true,
1334 1327
        else => return false,
1335 1328
    }
1336 1329
}
1337 1330
1331 +
/// Check if a type is an unsigned integer type.
1332 +
pub fn isUnsignedIntegerType(ty: Type) -> bool {
1333 +
    match ty {
1334 +
        case Type::U8, Type::U16, Type::U32, Type::U64 => return true,
1335 +
        else => return false,
1336 +
    }
1337 +
}
1338 +
1338 1339
/// Return the maximum of two u32 values.
1339 1340
fn max(a: u32, b: u32) -> u32 {
1340 1341
    if a > b {
1341 1342
        return a;
1342 1343
    }
3034 3035
            expected: MAX_FN_PARAMS,
3035 3036
            actual: decl.sig.params.len,
3036 3037
        }));
3037 3038
    }
3038 3039
    for paramNode in decl.sig.params {
3039 -
        let paramTy = try infer(self, paramNode) catch {
3040 +
        let paramTy = try infer(self, paramNode) catch e {
3040 3041
            exitFn(self);
3041 -
            throw ResolveError::Failure;
3042 +
            throw e;
3042 3043
        };
3043 3044
        paramTypes.append(allocType(self, paramTy), a);
3044 3045
    }
3045 3046
3046 3047
    if decl.sig.throwList.len > MAX_FN_THROWS {
3049 3050
            expected: MAX_FN_THROWS,
3050 3051
            actual: decl.sig.throwList.len,
3051 3052
        }));
3052 3053
    }
3053 3054
    for throwNode in decl.sig.throwList {
3054 -
        let throwTy = try infer(self, throwNode) catch {
3055 +
        let throwTy = try infer(self, throwNode) catch e {
3055 3056
            exitFn(self);
3056 -
            throw ResolveError::Failure;
3057 +
            throw e;
3057 3058
        };
3058 3059
        throwList.append(allocType(self, throwTy), a);
3059 3060
    }
3060 3061
    exitFn(self);
3061 3062
    fnType.paramTypes = &paramTypes[..];
3090 3091
        if isExtern {
3091 3092
            throw emitError(self, node, ErrorKind::FnUnexpectedBody);
3092 3093
        }
3093 3094
        enterFn(self, node, fnType); // Enter function scope for body analysis.
3094 3095
3095 -
        let bodyTy = try checkAssignable(self, body, Type::Void) catch {
3096 +
        let bodyTy = try checkAssignable(self, body, Type::Void) catch e {
3096 3097
            exitFn(self);
3097 -
            // TODO: Propagate error.
3098 -
            throw emitError(self, body, ErrorKind::Internal);
3098 +
            throw e;
3099 3099
        };
3100 3100
        if retTy != Type::Void and bodyTy != Type::Never {
3101 3101
            exitFn(self);
3102 3102
            throw emitError(self, body, ErrorKind::FnMissingReturn);
3103 3103
        }
3615 3615
    // Enter function scope.
3616 3616
    enterFn(self, node, fnType);
3617 3617
3618 3618
    // Bind the receiver parameter.
3619 3619
    let receiverTy = *fnType.paramTypes[0];
3620 -
    try bindValueIdent(self, receiverName, receiverName, receiverTy, false, 0, 0) catch {
3620 +
    try bindValueIdent(self, receiverName, receiverName, receiverTy, false, 0, 0) catch e {
3621 3621
        exitFn(self);
3622 -
        throw ResolveError::Failure;
3622 +
        throw e;
3623 3623
    };
3624 3624
    // Bind the remaining parameters from the signature.
3625 3625
    for paramNode in sig.params {
3626 -
        let paramTy = try infer(self, paramNode) catch {
3626 +
        let paramTy = try infer(self, paramNode) catch e {
3627 3627
            exitFn(self);
3628 -
            throw ResolveError::Failure;
3628 +
            throw e;
3629 3629
        };
3630 3630
    }
3631 3631
3632 3632
    // Resolve the body.
3633 3633
    let retTy = *fnType.returnType;
3634 -
    let bodyTy = try checkAssignable(self, body, Type::Void) catch {
3634 +
    let bodyTy = try checkAssignable(self, body, Type::Void) catch e {
3635 3635
        exitFn(self);
3636 -
        throw ResolveError::Failure;
3636 +
        throw e;
3637 3637
    };
3638 3638
    if retTy != Type::Void and bodyTy != Type::Never {
3639 3639
        exitFn(self);
3640 3640
        throw emitError(self, body, ErrorKind::FnMissingReturn);
3641 3641
    }
4579 4579
    subjectTy: Type,
4580 4580
    matchType: Type,
4581 4581
    matchBy: MatchBy
4582 4582
) -> Type throws (ResolveError) {
4583 4583
    enterScope(self, node);
4584 -
    let prongTy = try resolveMatchProngBody(self, prongNode, subjectTy, matchBy) catch {
4584 +
    let prongTy = try resolveMatchProngBody(self, prongNode, subjectTy, matchBy) catch e {
4585 4585
        exitScope(self);
4586 -
        throw ResolveError::Failure; // TODO: Rethrow same error.
4586 +
        throw e;
4587 4587
    };
4588 4588
    exitScope(self);
4589 4589
    setNodeType(self, node, prongTy);
4590 4590
4591 4591
    return unifyBranches(matchType, prongTy);
5028 5028
                return setNodeType(self, node, *method.fnType.returnType);
5029 5029
            }
5030 5030
        }
5031 5031
    }
5032 5032
    let case Type::Fn(info) = calleeTy else {
5033 -
        // TODO: Emit type error.
5034 -
        panic;
5033 +
        throw emitError(self, call.callee, ErrorKind::TypeMismatch(TypeMismatch {
5034 +
            expected: Type::Unknown,
5035 +
            actual: calleeTy,
5036 +
        }));
5035 5037
    };
5036 5038
    try checkCallArgs(self, node, call, info, ctx);
5037 5039
    // Associate function type to callee.
5038 5040
    setNodeType(self, call.callee, calleeTy);
5039 5041
5152 5154
}
5153 5155
5154 5156
/// Ensure slice range bounds are valid `u32` values.
5155 5157
fn checkSliceRangeIndices(self: *mut Resolver, range: ast::Range) throws (ResolveError) {
5156 5158
    if let start = range.start {
5157 -
        let _ = try checkAssignable(self, start, Type::U32);
5159 +
        try checkIndex(self, start);
5158 5160
    }
5159 5161
    if let end = range.end {
5160 -
        let _ = try checkAssignable(self, end, Type::U32);
5162 +
        try checkIndex(self, end);
5161 5163
    }
5162 5164
}
5163 5165
5164 5166
/// Emit an error when a slice range with compile-tyime values exceeds the array length.
5165 5167
fn validateArraySliceBounds(self: *mut Resolver, range: ast::Range, length: u32, site: *ast::Node) throws (ResolveError) {
5187 5189
            throw emitError(self, site, ErrorKind::SliceRangeOutOfBounds);
5188 5190
        }
5189 5191
    }
5190 5192
}
5191 5193
5194 +
/// Check that an index expression has an unsigned integer type.
5195 +
/// Accepts `u8`, `u16`, `u32` and unsuffixed integer literals.
5196 +
/// Smaller types are widened to `u32` via a numeric cast coercion.
5197 +
fn checkIndex(self: *mut Resolver, indexNode: *ast::Node) throws (ResolveError) {
5198 +
    let indexTy = try visit(self, indexNode, Type::U32);
5199 +
    if indexTy == Type::Int or indexTy == Type::U32 {
5200 +
        let _ = try expectAssignable(self, Type::U32, indexTy, indexNode);
5201 +
        return;
5202 +
    }
5203 +
    match indexTy {
5204 +
        case Type::U8, Type::U16 => {
5205 +
            setNodeCoercion(self, indexNode, Coercion::NumericCast {
5206 +
                from: indexTy, to: Type::U32,
5207 +
            });
5208 +
        }
5209 +
        else => {
5210 +
            throw emitTypeMismatch(self, indexNode, TypeMismatch {
5211 +
                expected: Type::U32,
5212 +
                actual: indexTy,
5213 +
            });
5214 +
        }
5215 +
    }
5216 +
}
5217 +
5192 5218
/// Analyze an array or slice subscript expression.
5193 5219
fn resolveSubscript(self: *mut Resolver, node: *ast::Node, container: *ast::Node, indexNode: *ast::Node) -> Type
5194 5220
    throws (ResolveError)
5195 5221
{
5196 5222
    // Range subscripts always require `&` to form a slice.
5199 5225
        let _ = try infer(self, container);
5200 5226
        try checkSliceRangeIndices(self, range);
5201 5227
        throw emitError(self, node, ErrorKind::SliceRequiresAddress);
5202 5228
    }
5203 5229
    let containerTy = try infer(self, container);
5204 -
    let _ = try checkAssignable(self, indexNode, Type::U32);
5230 +
    try checkIndex(self, indexNode);
5205 5231
    let subjectTy = autoDeref(containerTy);
5206 5232
5207 5233
    match subjectTy {
5208 5234
        case Type::Array(arrayInfo) => {
5209 5235
            return setNodeType(self, node, *arrayInfo.item);
6233 6259
}
6234 6260
6235 6261
/// Try to constant-fold a binary operation on two resolved operands.
6236 6262
/// Only folds when the result type is concrete.
6237 6263
fn tryFoldBinOp(self: *mut Resolver, node: *ast::Node, binop: ast::BinOp, resultTy: Type) {
6238 -
    let leftVal = constValueFor(self, binop.left)
6264 +
    let leftVal = constValueEntry(self, binop.left)
6239 6265
        else return;
6240 -
    let rightVal = constValueFor(self, binop.right)
6266 +
    let rightVal = constValueEntry(self, binop.right)
6241 6267
        else return;
6242 6268
6243 6269
    // Fold integer binary ops.
6244 6270
    if let case ConstValue::Int(leftInt) = leftVal {
6245 6271
        if let case ConstValue::Int(rightInt) = rightVal {
6371 6397
    let mut resultTy = Type::Unknown;
6372 6398
6373 6399
    match unop.op {
6374 6400
        case ast::UnaryOp::Not => {
6375 6401
            resultTy = try checkBoolean(self, unop.value);
6376 -
            if let value = constValueFor(self, unop.value) {
6402 +
            if let value = constValueEntry(self, unop.value) {
6377 6403
                if let case ConstValue::Bool(val) = value {
6378 6404
                    setNodeConstValue(self, node, ConstValue::Bool(not val));
6379 6405
                }
6380 6406
            }
6381 6407
        },
6382 6408
        case ast::UnaryOp::Neg => {
6383 6409
            // TODO: Check that we're allowed to use `-` here? Should negation
6384 6410
            // only be valid for signed integers?
6385 6411
            resultTy = try checkNumeric(self, unop.value);
6386 -
            if let value = constValueFor(self, unop.value) {
6412 +
            if let value = constValueEntry(self, unop.value) {
6387 6413
                // Get the constant expression for the value, flip the sign,
6388 6414
                // and store that new expression on the unary op node.
6389 6415
                if let case ConstValue::Int(intVal) = value {
6390 6416
                    setNodeConstValue(
6391 6417
                        self,
6395 6421
                }
6396 6422
            }
6397 6423
        },
6398 6424
        case ast::UnaryOp::BitNot => {
6399 6425
            resultTy = try checkNumeric(self, unop.value);
6400 -
            if let value = constValueFor(self, unop.value) {
6426 +
            if let value = constValueEntry(self, unop.value) {
6401 6427
                if let case ConstValue::Int(intVal) = value {
6402 6428
                    let signed = constIntToSigned(intVal);
6403 6429
                    let inverted = constIntFromSigned(-(signed + 1), intVal.bits, intVal.signed);
6404 6430
                    setNodeConstValue(self, node, ConstValue::Int(inverted));
6405 6431
                }
6539 6565
6540 6566
/// Analyze a standalone expression by wrapping it in a synthetic function.
6541 6567
pub fn resolveExpr(
6542 6568
    self: *mut Resolver, expr: *ast::Node, arena: *mut ast::NodeArena
6543 6569
) -> Diagnostics throws (ResolveError) {
6544 -
    let a = ast::nodeAllocator(arena);
6570 +
    let a = alloc::arenaAllocator(&mut arena.arena);
6545 6571
    let exprStmt = ast::synthNode(arena, ast::NodeValue::ExprStmt(expr));
6546 6572
    let bodyStmts = ast::nodeSlice(arena, 1).append(exprStmt, a);
6547 6573
    let module = ast::synthFnModule(arena, ANALYZE_EXPR_FN_NAME, bodyStmts);
6548 6574
6549 6575
    let case ast::NodeValue::Block(block) = module.modBody.value
6696 6722
    }
6697 6723
    // Phase 6: Resolve type bodies (record fields, union variants).
6698 6724
    try resolveTypeBodies(res, block);
6699 6725
    // Phase 7: Process all other declarations (statics, etc.).
6700 6726
    for stmt in block.statements {
6701 -
        // TODO: This error should propagate, since we catch errors in the `resolveModule` entry point.
6702 -
        try visitDecl(res, stmt) catch {
6703 -
            break;
6704 -
        };
6727 +
        try visitDecl(res, stmt);
6705 6728
    }
6706 6729
}
6707 6730
6708 6731
/// Analyze module definitions. This pass analyzes function bodies, recursing into sub-modules.
6709 6732
fn resolveModuleDefs(self: *mut Resolver, block: *ast::Block) throws (ResolveError) {
lib/std/lang/resolver/tests.rad +2 -2
419 419
420 420
/// Verify that a node has a constant integer value with the expected magnitude.
421 421
fn expectConstInt(a: *super::Resolver, node: *ast::Node, expected: u32)
422 422
    throws (testing::TestError)
423 423
{
424 -
    let constVal = super::constValueFor(a, node)
424 +
    let constVal = super::constValueEntry(a, node)
425 425
        else throw testing::TestError::Failed;
426 426
427 427
    let case super::ConstValue::Int(int) = constVal
428 428
        else throw testing::TestError::Failed;
429 429
986 986
    try expectNoErrors(&result);
987 987
988 988
    let constStmt = try getBlockStmt(result.root, 1);
989 989
    let case ast::NodeValue::ConstDecl(decl) = constStmt.value
990 990
        else throw testing::TestError::Failed;
991 -
    let valueConst = super::constValueFor(&a, decl.value)
991 +
    let valueConst = super::constValueEntry(&a, decl.value)
992 992
        else throw testing::TestError::Failed;
993 993
    let case super::ConstValue::Int(lenVal) = valueConst
994 994
        else throw testing::TestError::Failed;
995 995
    try testing::expect(lenVal.magnitude == 3);
996 996
    try testing::expect(not lenVal.negative);
lib/std/sys/unix.rad +2 -2
1 1
//! Unix-specific system calls and utilities.
2 2
use std::intrinsics;
3 3
4 4
/// File access modes.
5 -
// TODO: Use unlabeled struct.
5 +
// TODO: Use unlabeled record (blocked on positional field access syntax).
6 6
pub record OpenFlags {
7 7
    value: i64,
8 8
}
9 9
10 10
/// Open file for reading only.
95 95
        return nil;
96 96
    }
97 97
    if n < 0 {
98 98
        return nil;
99 99
    }
100 -
    return &buf[..(n as u32)];
100 +
    return &buf[..n as u32];
101 101
}
102 102
103 103
/// Exit the current process with the given status code.
104 104
pub fn exit(status: i64) {
105 105
    intrinsics::ecall(93, status, 0, 0, 0);
test/runner.rad +1 -2
118 118
    let len = sourcePath.len;
119 119
    if len < 4 {
120 120
        return nil;
121 121
    }
122 122
    // Check for .rad extension.
123 -
    let extStart = len - 4; // TODO: language support for additions in ranges.
124 -
    if not mem::eq(&sourcePath[extStart..len], ".rad") {
123 +
    if not mem::eq(&sourcePath[len - 4..len], ".rad") {
125 124
        return nil;
126 125
    }
127 126
    if len + 1 > buf.len {
128 127
        return nil;
129 128
    }
test/tests/index.u8.rad added +32 -0
1 +
//! returns: 0
2 +
3 +
@default fn main() -> i32 {
4 +
    let arr: [i32; 4] = [10, 20, 42, 30];
5 +
6 +
    // u8 index.
7 +
    let idx8: u8 = 2;
8 +
    if arr[idx8] != 42 {
9 +
        return 1;
10 +
    }
11 +
    // u16 index.
12 +
    let idx16: u16 = 3;
13 +
    if arr[idx16] != 30 {
14 +
        return 2;
15 +
    }
16 +
    // u32 index (already worked).
17 +
    let idx32: u32 = 0;
18 +
    if arr[idx32] != 10 {
19 +
        return 3;
20 +
    }
21 +
    // Unsuffixed integer literal index.
22 +
    if arr[1] != 20 {
23 +
        return 4;
24 +
    }
25 +
    // u8 index into slice.
26 +
    let s = &arr[..];
27 +
    let si: u8 = 1;
28 +
    if s[si] != 20 {
29 +
        return 5;
30 +
    }
31 +
    return 0;
32 +
}
test/tests/range.arithmetic.rad added +29 -0
1 +
//! returns: 0
2 +
3 +
@default fn main() -> i32 {
4 +
    let arr: [i32; 6] = [10, 20, 30, 40, 50, 60];
5 +
    let len: u32 = 6;
6 +
7 +
    // Arithmetic in range start (no parens needed).
8 +
    let s = &arr[len - 3..len];
9 +
    if s.len != 3 { return 1; }
10 +
    if s[0] != 40 { return 2; }
11 +
    if s[2] != 60 { return 3; }
12 +
13 +
    // Arithmetic in range end.
14 +
    let s2 = &arr[0..len - 2];
15 +
    if s2.len != 4 { return 4; }
16 +
    if s2[3] != 40 { return 5; }
17 +
18 +
    // Arithmetic in both.
19 +
    let s3 = &arr[len - 4..len - 1];
20 +
    if s3.len != 3 { return 6; }
21 +
    if s3[0] != 30 { return 7; }
22 +
    if s3[2] != 50 { return 8; }
23 +
24 +
    // Arithmetic in subscript.
25 +
    if arr[len - 1] != 60 { return 9; }
26 +
    if arr[len - 6] != 10 { return 10; }
27 +
28 +
    return 0;
29 +
}