lang: Make composites affine by default

702b8915c6f98686dbb3309fde047cbe8d2e83175202371578a12c861147611b
Alexis Sellier committed ago 1 parent 1e2fdea9
compiler/radiance.rad +9 -9
97 97
/// Usage string.
98 98
constant USAGE: *[u8] =
99 99
    "usage: radiance -pkg <name> [-start <input.ras>] -mod <input>.. [-pkg <name> -mod <input>..] -entry <pkg> -o <output>\n";
100 100
101 101
/// Compiler error.
102 -
union Error {
102 +
union Error: Copy {
103 103
    Other,
104 104
}
105 105
106 106
/// What to dump during compilation.
107 -
union Dump {
107 +
union Dump: Copy {
108 108
    /// Don't dump anything.
109 109
    None,
110 110
    /// Dump the parsed AST before semantic analysis.
111 111
    Ast,
112 112
    /// Dump the module graph.
116 116
    /// Dump the generated assembly.
117 117
    Asm,
118 118
}
119 119
120 120
/// A discovered test function.
121 -
record TestDesc {
121 +
record TestDesc: Copy {
122 122
    /// Full module qualified path segments (eg. ["std", "tests"]).
123 123
    modPath: *[*[u8]],
124 124
    /// Test function name (eg. "testFoo").
125 125
    fnName: *[u8],
126 126
}
127 127
128 128
/// Source inputs belonging to one command-line package.
129 -
record PackageInput {
129 +
record PackageInput: Copy {
130 130
    /// Package name from the `-pkg` argument.
131 131
    name: *[u8],
132 132
    /// Optional startup assembly emitted before generated text.
133 133
    startupPath: ?*[u8],
134 134
    /// Radiance source paths for this package.
140 140
    /// Number of assembly source paths.
141 141
    asmPathCount: u32,
142 142
}
143 143
144 144
/// Compilation context.
145 -
record CompileContext {
145 +
record CompileContext: Copy {
146 146
    /// Array of packages to compile.
147 147
    packages: [package::Package; MAX_PACKAGES],
148 148
    /// Driver inputs for each package slot.
149 149
    inputs: [PackageInput; MAX_PACKAGES],
150 150
    /// Number of packages.
162 162
    /// Whether to emit debug info (.debug file).
163 163
    debug: bool,
164 164
}
165 165
166 166
/// Root module info for a package.
167 -
record RootModule {
167 +
record RootModule: Copy {
168 168
    entry: *module::ModuleEntry,
169 169
    ast: *mut ast::Node,
170 170
}
171 171
172 172
/// State carried by the streaming lowerer/codegen callback.
173 -
record CodegenSinkContext {
173 +
record CodegenSinkContext: Copy {
174 174
    /// RV64 generator receiving lowered functions.
175 175
    generator: *mut rv64::Generator,
176 176
    /// Arena holding the current function's lowered IL.
177 177
    fnArena: *mut alloc::Arena,
178 178
}
179 179
180 180
/// Entry handling for streamed code generation.
181 -
union CodegenEntryMode {
181 +
union CodegenEntryMode: Copy {
182 182
    /// Do not reserve an entry jump.
183 183
    None,
184 184
    /// Reserve and patch an entry jump to the `@default` function.
185 185
    DefaultEntry,
186 186
}
187 187
188 188
/// Options controlling streamed lowering and code generation.
189 -
record CodegenOptions {
189 +
record CodegenOptions: Copy {
190 190
    /// Optional output path used for progress logging.
191 191
    logPath: ?*[u8],
192 192
    /// Whether to emit debug source locations.
193 193
    debug: bool,
194 194
    /// How the generated program should handle entry.
lib/std/arch/rv64.rad +5 -5
159 159
export fn imageHeader(codeBytes: u32, roDataBytes: u32, rwDataBytes: u32) -> [u32; 5] {
160 160
    return [IMAGE_MAGIC, IMAGE_VERSION, codeBytes, roDataBytes, rwDataBytes];
161 161
}
162 162
163 163
/// Storage buffers passed from driver for code generation.
164 -
export record Storage {
164 +
export record Storage: Copy {
165 165
    /// Buffer for data symbols.
166 166
    dataSyms: *mut [data::DataSym],
167 167
    /// Hash table entries for data symbol lookup.
168 168
    dataSymEntries: *mut [dict::Entry],
169 169
}
170 170
171 171
/// Result of code generation.
172 -
export record Program {
172 +
export record Program: Copy {
173 173
    /// Slice of emitted code.
174 174
    code: *[u32],
175 175
    /// Slice of function addresses (name + start index).
176 176
    funcs: *[types::FuncAddr],
177 177
    /// Number of read-only data bytes emitted.
181 181
    /// Debug entries mapping PCs to source locations. Empty when debug is off.
182 182
    debugEntries: *[types::DebugEntry],
183 183
}
184 184
185 185
/// Entry jump patching requested for the generated program.
186 -
export union EntryPatch {
186 +
export union EntryPatch: Copy {
187 187
    /// No entry jump is emitted.
188 188
    None,
189 189
    /// Reserve code slot zero for a jump to the default function.
190 190
    Reserved(?*[u8]),
191 191
}
192 192
193 193
/// Options controlling incremental RV64 program generation.
194 -
export record ProgramOptions {
194 +
export record ProgramOptions: Copy {
195 195
    /// Entry jump patching mode.
196 196
    entryPatch: EntryPatch,
197 197
    /// Whether to emit debug source locations.
198 198
    debug: bool,
199 199
}
201 201
/// State for incremental RV64 program generation.
202 202
///
203 203
/// The generator owns global codegen state that must survive across function
204 204
/// emission. Function-local scratch stays outside this record so callers can
205 205
/// reclaim it after each function.
206 -
export record Generator {
206 +
export record Generator: Copy {
207 207
    /// Binary emitter and relocation state.
208 208
    e: emit::Emitter,
209 209
    /// Entry jump patching state.
210 210
    entryPatch: EntryPatch,
211 211
}
lib/std/arch/rv64/asm.rad +15 -15
71 71
/// consumes the program, [`rv64::addAssembly`] appends the text words to the
72 72
/// generated text stream and registers text labels at their relocated offsets.
73 73
/// The driver copies `data` into the final read-only data prefix; the data
74 74
/// base supplied to [`assemble`] lets the assembler resolve data addresses as
75 75
/// they will appear in that final layout.
76 -
export record Program {
76 +
export record Program: Copy {
77 77
    /// Encoded instructions in the text section.
78 78
    text: *[u32],
79 79
    /// Raw bytes in the data section.
80 80
    data: *[u8],
81 81
    /// Symbols defined by the source.
83 83
    /// Text references resolved by the whole-program emitter.
84 84
    externalFixups: *[Fixup],
85 85
}
86 86
87 87
/// Errors reported while assembling source text.
88 -
export union Error {
88 +
export union Error: Copy {
89 89
    /// Invalid syntax or operand form at a source offset.
90 90
    Invalid { offset: u32, message: *[u8] },
91 91
    /// The source emitted more text words than the caller-provided buffer holds.
92 92
    TextOverflow,
93 93
    /// The source emitted more data bytes than the caller-provided buffer holds.
94 94
    DataOverflow,
95 95
}
96 96
97 97
/// Active output section.
98 -
export union Section {
98 +
export union Section: Copy {
99 99
    /// Instruction section.
100 100
    Text,
101 101
    /// Data byte section.
102 102
    Data,
103 103
}
104 104
105 105
/// Branch opcode that needs fixup.
106 -
export union BranchOp {
106 +
export union BranchOp: Copy {
107 107
    /// Branch if equal.
108 108
    Beq,
109 109
    /// Branch if not equal.
110 110
    Bne,
111 111
    /// Branch if less than, signed.
121 121
    /// Branch if greater than, signed pseudo-instruction.
122 122
    Bgt,
123 123
}
124 124
125 125
/// Parser and encoder behavior for one instruction mnemonic.
126 -
export union InstructionEncoder {
126 +
export union InstructionEncoder: Copy {
127 127
    /// No-operand instruction encoded by a fixed encoder.
128 128
    NoOperand { enc: fn() -> u32 },
129 129
    /// Load-immediate pseudo-instruction.
130 130
    Li,
131 131
    /// Load-address pseudo-instruction.
163 163
    /// Upper-immediate operand form.
164 164
    Upper { enc: fn(gen::Reg, i32) -> u32 },
165 165
}
166 166
167 167
/// Classified directive name.
168 -
export union DirectiveKind {
168 +
export union DirectiveKind: Copy {
169 169
    /// `.align` directive.
170 170
    Align,
171 171
    /// `.ascii` directive.
172 172
    Ascii,
173 173
    /// `.byte` directive.
187 187
    /// `.word` directive.
188 188
    Word,
189 189
}
190 190
191 191
/// Instruction descriptor table row.
192 -
record InstructionEntry {
192 +
record InstructionEntry: Copy {
193 193
    /// Assembly mnemonic text.
194 194
    name: *[u8],
195 195
    /// Operand parser and encoder behavior.
196 196
    encoder: InstructionEncoder,
197 197
}
198 198
199 199
/// Directive descriptor table row.
200 -
record DirectiveEntry {
200 +
record DirectiveEntry: Copy {
201 201
    /// Directive name without the leading `.`.
202 202
    name: *[u8],
203 203
    /// Parser behavior for the directive.
204 204
    kind: DirectiveKind,
205 205
}
206 206
207 207
/// Register descriptor table row.
208 -
record RegisterEntry {
208 +
record RegisterEntry: Copy {
209 209
    /// Register alias text without the leading `%`.
210 210
    name: *[u8],
211 211
    /// Numeric register selected by the alias.
212 212
    reg: gen::Reg,
213 213
}
214 214
215 215
/// CSR descriptor table row.
216 -
record CsrEntry {
216 +
record CsrEntry: Copy {
217 217
    /// CSR name text.
218 218
    name: *[u8],
219 219
    /// Numeric CSR address.
220 220
    csr: u32,
221 221
}
222 222
223 223
/// Width of an integer data directive.
224 -
export union DataWidth {
224 +
export union DataWidth: Copy {
225 225
    /// 32-bit data value.
226 226
    Word,
227 227
    /// 64-bit data value.
228 228
    Dword,
229 229
}
416 416
    { name: "mtval",    csr: 0x343 },
417 417
    { name: "mtvec",    csr: 0x305 },
418 418
];
419 419
420 420
/// Recorded symbol definition.
421 -
export record Symbol {
421 +
export record Symbol: Copy {
422 422
    /// Symbol name.
423 423
    name: *[u8],
424 424
    /// Section the symbol belongs to.
425 425
    section: Section,
426 426
    /// Byte offset within the section.
428 428
    /// Whether `.export` exported this symbol outside its assembly fragment.
429 429
    isExported: bool,
430 430
}
431 431
432 432
/// Information needed to resolve a pending symbol reference.
433 -
export union FixupInfo {
433 +
export union FixupInfo: Copy {
434 434
    /// Branch to a text label.
435 435
    Branch { op: BranchOp, rs1: gen::Reg, rs2: gen::Reg, index: u32 },
436 436
    /// JAL-like jump to a text label.
437 437
    Jal { rd: gen::Reg, index: u32 },
438 438
    /// Absolute address materialization into a register.
442 442
    /// A 64-bit data word referring to a symbol offset.
443 443
    Dword { offset: u32 },
444 444
}
445 445
446 446
/// Pending symbol reference.
447 -
export record Fixup {
447 +
export record Fixup: Copy {
448 448
    /// Referenced symbol.
449 449
    symbol: *[u8],
450 450
    /// Fixup payload.
451 451
    info: FixupInfo,
452 452
}
453 453
454 454
/// Parser and emission state.
455 -
export record Assembler {
455 +
export record Assembler: Copy {
456 456
    /// Allocation arena for temporary assembler state.
457 457
    arena: *mut alloc::Arena,
458 458
    /// Assembler lexical scanner.
459 459
    scan: scanner::Scanner,
460 460
    /// Output text buffer.
lib/std/arch/rv64/asm/parser.rad +1 -1
11 11
12 12
use super::emit;
13 13
use super::scanner;
14 14
15 15
/// Parsed memory operand with base register and signed byte offset.
16 -
record MemOperand {
16 +
record MemOperand: Copy {
17 17
    /// Base register inside the memory operand parentheses.
18 18
    base: gen::Reg,
19 19
    /// Signed byte offset preceding the base register.
20 20
    offset: i32,
21 21
}
lib/std/arch/rv64/asm/scanner.rad +4 -4
3 3
4 4
use std::char;
5 5
use std::lang::strings;
6 6
7 7
/// Token kinds recognized by the assembler scanner.
8 -
export union TokenKind {
8 +
export union TokenKind: Copy {
9 9
    /// Special end-of-file token generated when the input is exhausted.
10 10
    Eof,
11 11
    /// Special invalid token carrying an error message in [`Token::source`].
12 12
    Invalid,
13 13
40 40
    /// Integer literal token.
41 41
    Number,
42 42
}
43 43
44 44
/// Describes where assembler source originated from.
45 -
export union SourceKind {
45 +
export union SourceKind: Copy {
46 46
    /// Source loaded from a file at the given path.
47 47
    File { path: *[u8] },
48 48
    /// Source provided as an inline string.
49 49
    String,
50 50
}
51 51
52 52
/// Lexical scanner state for assembler source.
53 -
export record Scanner {
53 +
export record Scanner: Copy {
54 54
    /// Origin of the source being scanned.
55 55
    sourceKind: SourceKind,
56 56
    /// Source buffer.
57 57
    source: *[u8],
58 58
    /// Offset of the current token in `source`.
66 66
    /// Intern pool for identifier-shaped token text.
67 67
    pool: *mut strings::Pool,
68 68
}
69 69
70 70
/// Individual token with kind, source text, and byte offset.
71 -
export record Token {
71 +
export record Token: Copy {
72 72
    /// Token kind.
73 73
    kind: TokenKind,
74 74
    /// Token source text.
75 75
    source: *[u8],
76 76
    /// Byte offset of `source` in the input buffer.
lib/std/arch/rv64/decode.rad +1 -1
101 101
/////////////////////////
102 102
// Decoded Instruction //
103 103
/////////////////////////
104 104
105 105
/// Decoded RV64 instruction.
106 -
export union Instr {
106 +
export union Instr: Copy {
107 107
    // R-type ALU.
108 108
    Add  { rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg },
109 109
    Sub  { rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg },
110 110
    Sll  { rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg },
111 111
    Slt  { rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg },
lib/std/arch/rv64/emit.rad +10 -10
29 29
//////////////////////
30 30
// Emission Context //
31 31
//////////////////////
32 32
33 33
/// Branch/jump that needs offset patching after all blocks are emitted.
34 -
export record PendingBranch {
34 +
export record PendingBranch: Copy {
35 35
    /// Index into code buffer where the branch instruction is.
36 36
    index: u32,
37 37
    /// Target block index.
38 38
    target: u32,
39 39
    /// Type of branch for re-encoding.
40 40
    kind: BranchKind,
41 41
}
42 42
43 43
/// Type of branch instruction.
44 -
export union BranchKind {
44 +
export union BranchKind: Copy {
45 45
    /// Conditional branch (B-type encoding).
46 46
    Cond { op: il::CmpOp, rs1: gen::Reg, rs2: gen::Reg },
47 47
    /// Inverted conditional branch (B-type encoding with negated condition).
48 48
    InvertedCond { op: il::CmpOp, rs1: gen::Reg, rs2: gen::Reg },
49 49
    /// Unconditional jump (J-type encoding).
50 50
    Jump,
51 51
}
52 52
53 53
/// Function call that needs offset patching.
54 -
export record PendingCall {
54 +
export record PendingCall: Copy {
55 55
    /// Index in code buffer where the call was emitted.
56 56
    index: u32,
57 57
    /// Target function name.
58 58
    target: *[u8],
59 59
}
60 60
61 61
/// Assembly jump that needs offset patching after all text is emitted.
62 -
export record PendingJump {
62 +
export record PendingJump: Copy {
63 63
    /// Index in code buffer where the jump was emitted.
64 64
    index: u32,
65 65
    /// Target function name.
66 66
    target: *[u8],
67 67
    /// Destination register.
68 68
    rd: gen::Reg,
69 69
}
70 70
71 71
/// Address load that needs patching after layout is known.
72 -
export record PendingAddrLoad {
72 +
export record PendingAddrLoad: Copy {
73 73
    /// Index in code buffer where the load was emitted.
74 74
    index: u32,
75 75
    /// Target function or data symbol name.
76 76
    target: *[u8],
77 77
    /// Destination register.
79 79
    /// Whether this is an absolute data symbol address.
80 80
    isData: bool,
81 81
}
82 82
83 83
/// Adjusted base register and offset for addressing.
84 -
export record AdjustedOffset {
84 +
export record AdjustedOffset: Copy {
85 85
    /// Base register.
86 86
    base: gen::Reg,
87 87
    /// Byte offset from register.
88 88
    offset: i32,
89 89
}
90 90
91 91
/// Callee-saved register with its stack offset.
92 -
export record SavedReg {
92 +
export record SavedReg: Copy {
93 93
    /// Register to save/restore.
94 94
    reg: gen::Reg,
95 95
    /// Offset from SP.
96 96
    offset: i32,
97 97
}
98 98
99 99
/// Emission context. Tracks state during code generation.
100 -
export record Emitter {
100 +
export record Emitter: Copy {
101 101
    /// Allocator for growing append-backed emitter lists.
102 102
    allocator: alloc::Allocator,
103 103
    /// Emitted instructions storage.
104 104
    code: *mut [u32],
105 105
    /// Current number of emitted instructions.
121 121
    /// Number of debug entries recorded.
122 122
    debugEntriesLen: u32,
123 123
}
124 124
125 125
/// Computed stack frame layout for a function.
126 -
export record Frame {
126 +
export record Frame: Copy {
127 127
    /// Total frame size in bytes (aligned).
128 128
    totalSize: i32,
129 129
    /// Callee-saved registers and their offsets.
130 130
    // TODO: Use constant length when language supports it.
131 131
    savedRegs: [SavedReg; super::NUM_SAVED_REGISTERS],
453 453
/////////////////////////
454 454
// Immediate Handling  //
455 455
/////////////////////////
456 456
457 457
/// Split immediate into `hi` and `lo` bits.
458 -
export record SplitImm {
458 +
export record SplitImm: Copy {
459 459
    /// Upper 20 bits.
460 460
    hi: i32,
461 461
    /// Lower 12 bits.
462 462
    lo: i32,
463 463
}
lib/std/arch/rv64/isel.rad +6 -6
63 63
constant U8_MAX: i64 = 255;
64 64
constant U16_MAX: i64 = 65535;
65 65
constant U32_MAX: i64 = 4294967295;
66 66
67 67
/// Binary operation.
68 -
union BinOp { Add, And, Or, Xor }
68 +
union BinOp: Copy { Add, And, Or, Xor }
69 69
/// Shift operation.
70 -
union ShiftOp { Sll, Srl, Sra }
70 +
union ShiftOp: Copy { Sll, Srl, Sra }
71 71
/// Compare operation.
72 -
union CmpOp { Slt, Ult }
72 +
union CmpOp: Copy { Slt, Ult }
73 73
74 74
/// A pending spill store to be flushed after instruction selection.
75 -
record PendingSpill {
75 +
record PendingSpill: Copy {
76 76
    /// The SSA register that was spilled.
77 77
    ssa: il::Reg,
78 78
    /// The physical register holding the value to store.
79 79
    rd: gen::Reg,
80 80
}
82 82
////////////////////
83 83
// Selector State //
84 84
////////////////////
85 85
86 86
/// Instruction selector state.
87 -
export record Selector {
87 +
export record Selector: Copy {
88 88
    /// Emitter for outputting instructions.
89 89
    e: *mut emit::Emitter,
90 90
    /// Register allocation result.
91 91
    ralloc: *regalloc::AllocResult,
92 92
    /// Total stack frame size.
263 263
////////////////////////
264 264
// Instruction Select //
265 265
////////////////////////
266 266
267 267
/// Pre-scan result for reserve analysis.
268 -
record ReserveInfo {
268 +
record ReserveInfo: Copy {
269 269
    /// Total size needed for constant-sized reserves.
270 270
    size: i32,
271 271
    /// Whether any dynamic-sized reserves exist.
272 272
    isDynamic: bool,
273 273
}
lib/std/collections/dict.rad +2 -2
4 4
//! of two and is provided by the caller via arena-allocated storage.
5 5
6 6
use std::mem;
7 7
8 8
/// Hash map entry.
9 -
export record Entry {
9 +
export record Entry: Copy {
10 10
    /// Key.
11 11
    key: *[u8],
12 12
    /// Associated value.
13 13
    value: i32,
14 14
}
15 15
16 16
/// Open-addressed hash map with caller-provided storage.
17 -
export record Dict {
17 +
export record Dict: Copy {
18 18
    /// Hash table entries.
19 19
    entries: *mut [Entry],
20 20
    /// Number of occupied entries.
21 21
    count: u32,
22 22
}
lib/std/fmt.rad +3 -3
13 13
export constant I64_STR_LEN: u32 = 20;
14 14
/// Maximum string length for a formatted bool (eg. "false").
15 15
export constant BOOL_STR_LEN: u32 = 5;
16 16
17 17
/// Radix/base of a parsed integer literal.
18 -
export union Radix {
18 +
export union Radix: Copy {
19 19
    /// Binary literal (0b...).
20 20
    Binary,
21 21
    /// Decimal literal.
22 22
    Decimal,
23 23
    /// Hexadecimal literal (0x...).
24 24
    Hex,
25 25
}
26 26
27 27
/// Errors reported while parsing literal text.
28 -
export union ParseError {
28 +
export union ParseError: Copy {
29 29
    /// Literal text was empty or missing required digits.
30 30
    Invalid,
31 31
    /// Literal contained an invalid digit for its radix.
32 32
    InvalidDigit,
33 33
    /// Literal value exceeded the supported range.
34 34
    Overflow,
35 35
}
36 36
37 37
/// Parsed integer literal metadata.
38 -
export record IntLiteral {
38 +
export record IntLiteral: Copy {
39 39
    /// Raw characters that comprised the literal.
40 40
    text: *[u8],
41 41
    /// Magnitude parsed from the literal.
42 42
    magnitude: u64,
43 43
    /// Radix used by the literal.
lib/std/lang/alloc.rad +3 -3
7 7
@test mod tests;
8 8
9 9
use std::mem;
10 10
11 11
/// Error thrown by allocator.
12 -
export union AllocError {
12 +
export union AllocError: Copy {
13 13
    /// Allocator is out of memory.
14 14
    OutOfMemory,
15 15
}
16 16
17 17
/// Bump allocator backed by a byte slice.
18 18
///
19 19
/// Allocations are made by advancing an offset pointer. Individual allocations
20 20
/// cannot be freed; instead, the entire arena is reset at once via [`reset`].
21 -
export record Arena {
21 +
export record Arena: Copy {
22 22
    /// Backing storage.
23 23
    data: *mut [u8],
24 24
    /// Current allocation offset in bytes.
25 25
    offset: u32,
26 26
}
109 109
/// Bundles an allocation function with an opaque context pointer so that
110 110
/// any allocation strategy (arena, free-list, mmap, pool) can be used
111 111
/// through a uniform interface. The `func` field is called with the context
112 112
/// pointer, a byte size and an alignment, and must return a pointer to
113 113
/// the allocated memory or panic on failure.
114 -
export record Allocator {
114 +
export record Allocator: Copy {
115 115
    /// Allocation function. Returns a pointer to `size` bytes
116 116
    /// aligned to `alignment`, or panics on failure.
117 117
    func: fn(*mut opaque, u32, u32) -> *mut opaque,
118 118
    /// Opaque context pointer passed to `func`.
119 119
    ctx: *mut opaque,
lib/std/lang/ast.rad +49 -49
10 10
export constant MAX_TRAIT_METHODS: u32 = 8;
11 11
12 12
/// Arena for all parser allocations.
13 13
///
14 14
/// Uses a bump allocator for both AST nodes and node pointer arrays.
15 -
export record NodeArena {
15 +
export record NodeArena: Copy {
16 16
    /// Bump allocator for all allocations.
17 17
    arena: alloc::Arena,
18 18
    /// Next node ID to assign. Incremented on each node allocation.
19 19
    nextId: u32,
20 20
}
36 36
37 37
    return @sliceOf(ptr.ptr as *mut *Node, 0, capacity);
38 38
}
39 39
40 40
/// Attribute bit set applied to declarations or fields.
41 -
export union Attribute {
41 +
export union Attribute: Copy {
42 42
    /// Public visibility attribute.
43 43
    Export = 0b1,
44 44
    /// Default implementation attribute.
45 45
    Default = 0b10,
46 46
    /// Extern linkage attribute.
52 52
    /// Declaration may perform unsafe pointer operations.
53 53
    Unsafe = 0b100000,
54 54
}
55 55
56 56
/// Ordered collection of attribute nodes applied to a declaration.
57 -
export record Attributes {
57 +
export record Attributes: Copy {
58 58
    list: *mut [*Node],
59 59
}
60 60
61 61
/// Check if an attributes list contains an attribute.
62 62
export fn attributesContains(self: *Attributes, attr: Attribute) -> bool {
72 72
export fn hasAttribute(attrs: u32, attr: Attribute) -> bool {
73 73
    return (attrs & (attr as u32)) <> 0;
74 74
}
75 75
76 76
/// Signedness of an integer type.
77 -
export union Signedness {
77 +
export union Signedness: Copy {
78 78
    /// Signed, eg. `i8`.
79 79
    Signed,
80 80
    /// Unsigned, eg. `u32`.
81 81
    Unsigned,
82 82
}
83 83
84 84
/// Binary operator kinds used in numeric expressions.
85 -
export union BinaryOp {
85 +
export union BinaryOp: Copy {
86 86
    /// Addition (`+`).
87 87
    Add,
88 88
    /// Subtraction (`-`).
89 89
    Sub,
90 90
    /// Multiplication (`*`).
124 124
    /// Logical exclusive disjunction (`xor`).
125 125
    Xor,
126 126
}
127 127
128 128
/// Unary operator kinds used in expressions.
129 -
export union UnaryOp {
129 +
export union UnaryOp: Copy {
130 130
    /// Logical negation (`not`).
131 131
    Not,
132 132
    /// Arithmetic negation (`-`).
133 133
    Neg,
134 134
    /// Bitwise NOT (`~`).
135 135
    BitNot,
136 136
}
137 137
138 138
/// Builtin function kind.
139 -
export union Builtin {
139 +
export union Builtin: Copy {
140 140
    /// Size of type in bytes (`@sizeOf`).
141 141
    SizeOf,
142 142
    /// Alignment requirement of type (`@alignOf`).
143 143
    AlignOf,
144 144
    /// Construct a slice from pointer, length, and optional capacity (`@sliceOf`).
145 145
    SliceOf,
146 146
}
147 147
148 148
/// Source extent for a node measured in bytes.
149 -
export record Span {
149 +
export record Span: Copy {
150 150
    /// Byte offset from the start of the source file.
151 151
    offset: u32,
152 152
    /// Length of the node in bytes.
153 153
    length: u32,
154 154
}
155 155
156 156
/// Type signature node.
157 -
export union TypeSig {
157 +
export union TypeSig: Copy {
158 158
    /// Absence of type.
159 159
    Void,
160 160
    /// Opaque type.
161 161
    Opaque,
162 162
    /// Boolean type.
220 220
        mutable: bool,
221 221
    },
222 222
}
223 223
224 224
/// Function signature.
225 -
export record FnSig {
225 +
export record FnSig: Copy {
226 226
    /// Parameter type nodes in declaration order.
227 227
    params: *mut [*Node],
228 228
    /// Optional return type node.
229 229
    returnType: ?*Node,
230 230
    /// Throwable type nodes declared in the signature.
231 231
    throwList: *mut [*Node],
232 232
}
233 233
234 234
/// Address-of expression metadata.
235 -
export record AddressOf {
235 +
export record AddressOf: Copy {
236 236
    /// Target expression being referenced.
237 237
    target: *Node,
238 238
    /// Indicates whether the reference is mutable.
239 239
    mutable: bool,
240 240
}
241 241
242 242
/// Compound statement block with optional dedicated scope.
243 -
export record Block {
243 +
export record Block: Copy {
244 244
    /// Statements that belong to this block.
245 245
    statements: *mut [*Node],
246 246
}
247 247
248 248
/// Function call expression.
249 -
export record Call {
249 +
export record Call: Copy {
250 250
    /// Callee expression.
251 251
    callee: *Node,
252 252
    /// Argument expressions in source order.
253 253
    args: *mut [*Node],
254 254
}
255 255
256 256
/// Single argument to a function or record literal, optionally labeled.
257 -
export record Arg {
257 +
export record Arg: Copy {
258 258
    /// Optional label applied to the argument.
259 259
    label: ?*Node,
260 260
    /// Expression supplying the argument value.
261 261
    value: *Node,
262 262
}
263 263
264 264
/// Assignment expression connecting a target and value.
265 -
export record Assign {
265 +
export record Assign: Copy {
266 266
    /// Expression representing the assignment target.
267 267
    left: *Node,
268 268
    /// Expression providing the value being assigned.
269 269
    right: *Node,
270 270
}
271 271
272 272
/// While loop with an optional alternate branch.
273 -
export record While {
273 +
export record While: Copy {
274 274
    /// Condition evaluated before each iteration.
275 275
    condition: *Node,
276 276
    /// Loop body executed while `condition` is true.
277 277
    body: *Node,
278 278
    /// Optional branch executed when the condition is false at entry.
279 279
    elseBranch: ?*Node,
280 280
}
281 281
282 282
/// `while let` loop binding metadata.
283 -
export record WhileLet {
283 +
export record WhileLet: Copy {
284 284
    /// Pattern matching structure.
285 285
    pattern: PatternMatch,
286 286
    /// Loop body executed when the pattern matches.
287 287
    body: *Node,
288 288
    /// Optional branch executed when the match fails immediately.
289 289
    elseBranch: ?*Node,
290 290
}
291 291
292 292
/// Try expression metadata.
293 -
export record Try {
293 +
export record Try: Copy {
294 294
    /// Expression evaluated with implicit error propagation.
295 295
    expr: *Node,
296 296
    /// Catch clauses. Empty for propagation (`try`), `try!`, or `try?`.
297 297
    catches: *mut [*Node],
298 298
    /// Whether the try should panic instead of returning an error.
300 300
    /// Whether the try should return an optional instead of propagating error.
301 301
    returnsOptional: bool,
302 302
}
303 303
304 304
/// A single catch clause in a `try ... catch` expression.
305 -
export record CatchClause {
305 +
export record CatchClause: Copy {
306 306
    /// Optional identifier binding for the error value (eg. `e`).
307 307
    binding: ?*Node,
308 308
    /// Optional type annotation after `as` (eg. `IoError`).
309 309
    typeNode: ?*Node,
310 310
    /// Block body executed when this clause matches.
311 311
    body: *Node,
312 312
}
313 313
314 314
/// `for` loop metadata.
315 -
export record For {
315 +
export record For: Copy {
316 316
    /// Loop variable binding.
317 317
    binding: *Node,
318 318
    /// Optional index binding for enumeration loops.
319 319
    index: ?*Node,
320 320
    /// Expression producing the iterable value.
324 324
    /// Optional branch executed when the loop body never runs.
325 325
    elseBranch: ?*Node,
326 326
}
327 327
328 328
/// Conditional `if` statement metadata.
329 -
export record If {
329 +
export record If: Copy {
330 330
    /// Condition controlling the branch.
331 331
    condition: *Node,
332 332
    /// Branch executed when `condition` is true.
333 333
    thenBranch: *Node,
334 334
    /// Optional branch executed when `condition` is false.
335 335
    elseBranch: ?*Node,
336 336
}
337 337
338 338
/// Conditional expression (`<true> if <condition> else <false>`).
339 -
export record CondExpr {
339 +
export record CondExpr: Copy {
340 340
    /// Condition controlling which branch is evaluated.
341 341
    condition: *Node,
342 342
    /// Expression evaluated when `condition` is true.
343 343
    thenExpr: *Node,
344 344
    /// Expression evaluated when `condition` is false.
345 345
    elseExpr: *Node,
346 346
}
347 347
348 348
/// Classification of pattern matches (if-let, while-let, let-else).
349 -
export union PatternKind {
349 +
export union PatternKind: Copy {
350 350
    /// Case pattern match.
351 351
    Case,
352 352
    /// Binding pattern match.
353 353
    Binding,
354 354
}
355 355
356 356
/// Prong arm.
357 -
export union ProngArm {
357 +
export union ProngArm: Copy {
358 358
    /// Case arm with pattern list.
359 359
    Case(*mut [*Node]),
360 360
    /// Binding arm with single identifier or placeholder.
361 361
    Binding(*Node),
362 362
    /// Else arm.
363 363
    Else,
364 364
}
365 365
366 366
/// Common pattern matching structure used by `if let`, `while let`, and `let-else`.
367 -
export record PatternMatch {
367 +
export record PatternMatch: Copy {
368 368
    /// Pattern or binding to match against.
369 369
    pattern: *Node,
370 370
    /// Scrutinee expression to match against.
371 371
    scrutinee: *Node,
372 372
    /// Optional guard that must evaluate to `true`.
376 376
    /// Whether the binding is mutable.
377 377
    mutable: bool,
378 378
}
379 379
380 380
/// `if let` conditional binding metadata.
381 -
export record IfLet {
381 +
export record IfLet: Copy {
382 382
    /// Pattern matching structure.
383 383
    pattern: PatternMatch,
384 384
    /// Branch executed when the pattern matches.
385 385
    thenBranch: *Node,
386 386
    /// Optional branch executed when the match fails.
387 387
    elseBranch: ?*Node,
388 388
}
389 389
390 390
/// `let-else` statement metadata.
391 -
export record LetElse {
391 +
export record LetElse: Copy {
392 392
    /// Pattern matching structure.
393 393
    pattern: PatternMatch,
394 394
    /// Else branch executed if match fails (must diverge).
395 395
    elseBranch: *Node,
396 396
}
397 397
398 398
/// `match` statement metadata.
399 -
export record Match {
399 +
export record Match: Copy {
400 400
    /// Expression whose value controls the match.
401 401
    subject: *Node,
402 402
    /// Prong nodes evaluated in order.
403 403
    prongs: *mut [*Node],
404 404
}
405 405
406 406
/// `match` prong metadata.
407 -
export record MatchProng {
407 +
export record MatchProng: Copy {
408 408
    /// Prong arm.
409 409
    arm: ProngArm,
410 410
    /// Optional guard that must evaluate to `true`.
411 411
    guard: ?*Node,
412 412
    /// Body executed when patterns match and guard passes.
413 413
    body: *Node,
414 414
}
415 415
416 416
/// `let` binding.
417 -
export record Let {
417 +
export record Let: Copy {
418 418
    /// Identifier bound by the declaration.
419 419
    ident: *Node,
420 420
    /// Declared type annotation.
421 421
    type: ?*Node,
422 422
    /// Initializer expression.
426 426
    /// Whether the variable is mutable.
427 427
    mutable: bool,
428 428
}
429 429
430 430
/// Constant declaration.
431 -
export record ConstDecl {
431 +
export record ConstDecl: Copy {
432 432
    /// Identifier bound by the declaration.
433 433
    ident: *Node,
434 434
    /// Declared type annotation.
435 435
    type: *Node,
436 436
    /// Constant initializer expression.
438 438
    /// Optional attribute list applied to the constant.
439 439
    attrs: ?Attributes,
440 440
}
441 441
442 442
/// Static storage declaration.
443 -
export record StaticDecl {
443 +
export record StaticDecl: Copy {
444 444
    /// Identifier bound by the declaration.
445 445
    ident: *Node,
446 446
    /// Declared storage type.
447 447
    type: *Node,
448 448
    /// Initialization expression.
450 450
    /// Optional attribute list applied to the static.
451 451
    attrs: ?Attributes,
452 452
}
453 453
454 454
/// Function parameter declaration.
455 -
export record FnParam {
455 +
export record FnParam: Copy {
456 456
    /// Parameter identifier.
457 457
    name: *Node,
458 458
    /// Parameter type annotation.
459 459
    type: *Node,
460 460
}
461 461
462 462
/// Record literal expression metadata.
463 -
export record RecordLit {
463 +
export record RecordLit: Copy {
464 464
    /// Type name associated with the literal.
465 465
    /// If `nil`, it's an anonymous record literal.
466 466
    typeName: ?*Node,
467 467
    /// Field initializer nodes.
468 468
    fields: *mut [*Node],
469 469
    /// When true, remaining fields are discarded (`{ x, .. }`).
470 470
    ignoreRest: bool,
471 471
}
472 472
473 473
/// Record declaration.
474 -
export record RecordDecl {
474 +
export record RecordDecl: Copy {
475 475
    /// Identifier naming the record.
476 476
    name: *Node,
477 477
    /// Field declaration nodes.
478 478
    fields: *mut [*Node],
479 479
    /// Optional attribute list applied to the record.
483 483
    /// Whether this record has labeled fields.
484 484
    labeled: bool,
485 485
}
486 486
487 487
/// Union declarations.
488 -
export record UnionDecl {
488 +
export record UnionDecl: Copy {
489 489
    /// Identifier naming the union.
490 490
    name: *Node,
491 491
    /// Variant nodes making up the union.
492 492
    variants: *mut [*Node],
493 493
    /// Optional attribute list applied to the union.
495 495
    /// Trait derivations attached to the union.
496 496
    derives: *mut [*Node],
497 497
}
498 498
499 499
/// Union variant declaration.
500 -
export record UnionDeclVariant {
500 +
export record UnionDeclVariant: Copy {
501 501
    /// Identifier naming the variant.
502 502
    name: *Node,
503 503
    /// Variant index.
504 504
    index: u32,
505 505
    /// Explicit discriminant value, if provided.
507 507
    /// Optional payload type.
508 508
    type: ?*Node,
509 509
}
510 510
511 511
/// Function declaration.
512 -
export record FnDecl {
512 +
export record FnDecl: Copy {
513 513
    /// Identifier naming the function.
514 514
    name: *Node,
515 515
    /// Function type signature.
516 516
    sig: FnSig,
517 517
    /// Optional function body (`nil` for extern functions).
519 519
    /// Optional attribute list applied to the function.
520 520
    attrs: ?Attributes,
521 521
}
522 522
523 523
/// Array repeat literal metadata.
524 -
export record ArrayRepeatLit {
524 +
export record ArrayRepeatLit: Copy {
525 525
    /// Expression providing the repeated value.
526 526
    item: *Node,
527 527
    /// Expression providing the repetition count.
528 528
    count: *Node,
529 529
}
530 530
531 531
/// Module declaration.
532 -
export record Mod {
532 +
export record Mod: Copy {
533 533
    /// Identifier naming the module.
534 534
    name: *Node,
535 535
    /// Optional attribute list applied to the module.
536 536
    attrs: ?Attributes,
537 537
}
538 538
539 539
/// Use declaration for importing modules.
540 -
export record Use {
540 +
export record Use: Copy {
541 541
    /// Access node identifying the imported module.
542 542
    path: *Node,
543 543
    /// Whether this is a wildcard import (e.g. `use ast::*`).
544 544
    wildcard: bool,
545 545
    /// Optional attribute list applied to the use declaration.
546 546
    attrs: ?Attributes,
547 547
}
548 548
549 549
/// Access expression used for field, scope, and index lookups.
550 -
export record Access {
550 +
export record Access: Copy {
551 551
    /// Expression providing the container or namespace.
552 552
    parent: *Node,
553 553
    /// Expression identifying the member, scope element, or index.
554 554
    child: *Node,
555 555
}
556 556
557 557
/// `as` cast expression metadata.
558 -
export record As {
558 +
export record As: Copy {
559 559
    /// Expression being coerced.
560 560
    value: *Node,
561 561
    /// Target type annotation.
562 562
    type: *Node,
563 563
}
564 564
565 565
/// Range expression metadata.
566 -
export record Range {
566 +
export record Range: Copy {
567 567
    /// Optional inclusive start expression.
568 568
    start: ?*Node,
569 569
    /// Optional exclusive end expression.
570 570
    end: ?*Node,
571 571
}
572 572
573 573
/// Binary operation expression, eg. `x * y`.
574 -
export record BinOp {
574 +
export record BinOp: Copy {
575 575
    /// Operator applied to the operands.
576 576
    op: BinaryOp,
577 577
    /// Left-hand operand.
578 578
    left: *Node,
579 579
    /// Right-hand operand.
580 580
    right: *Node,
581 581
}
582 582
583 583
/// Unary operation expression, eg. `-x`.
584 -
export record UnOp {
584 +
export record UnOp: Copy {
585 585
    /// Operator applied to the operand.
586 586
    op: UnaryOp,
587 587
    /// Operand expression.
588 588
    value: *Node,
589 589
}
590 590
591 591
/// Tagged union describing every possible AST node payload.
592 -
export union NodeValue {
592 +
export union NodeValue: Copy {
593 593
    /// Placeholder `_` expression.
594 594
    Placeholder,
595 595
    /// Nil literal (`nil`).
596 596
    Nil,
597 597
    /// Undefined literal (`undefined`).
793 793
        attrs: ?Attributes,
794 794
    },
795 795
}
796 796
797 797
/// Full AST node with shared metadata and variant-specific payload.
798 -
export record Node {
798 +
export record Node: Copy {
799 799
    /// Unique identifier for this node.
800 800
    id: u32,
801 801
    /// Source span describing where the node originated.
802 802
    span: Span,
803 803
    /// Variant-specific payload for the node.
835 835
export fn synthNode(arena: *mut NodeArena, value: NodeValue) -> *mut Node {
836 836
    return allocNode(arena, Span { offset: 0, length: 0 }, value);
837 837
}
838 838
839 839
/// Synthetic module with a single function in it.
840 -
record SynthFnMod {
840 +
record SynthFnMod: Copy {
841 841
    /// The module block.
842 842
    modBody: *Node,
843 843
    /// The function block.
844 844
    fnBody: *Node
845 845
}
lib/std/lang/gen.rad +1 -1
13 13
/// Physical register.
14 14
///
15 15
/// Lightweight wrapper around a register number. Used both in the
16 16
/// target-independent register allocator and in architecture-specific
17 17
/// backends.
18 -
export record Reg(u8);
18 +
export record Reg: Copy(u8);
lib/std/lang/gen/bitset.rad +2 -2
18 18
export fn wordsFor(n: u32) -> u32 {
19 19
    return (n + 31) / 32;
20 20
}
21 21
22 22
/// A fixed-size bitset backed by an array of 32-bit words.
23 -
export record Bitset {
23 +
export record Bitset: Copy {
24 24
    /// Backing storage for bits, organized as 32-bit words.
25 25
    bits: *mut [u32],
26 26
    /// Number of bits this bitset can hold.
27 27
    len: u32,
28 28
}
172 172
        set bs.bits[i] = 0;
173 173
    }
174 174
}
175 175
176 176
/// Iterator state for iterating set bits.
177 -
export record BitIter {
177 +
export record BitIter: Copy {
178 178
    /// Bitset being iterated.
179 179
    bs: *Bitset,
180 180
    /// Current word index.
181 181
    wordIdx: u32,
182 182
    /// Remaining bits in the current word (visited bits cleared).
lib/std/lang/gen/data.rad +2 -2
14 14
/// Size of the data symbol hash table. Must be a power of two
15 15
/// and at least twice the size of [`MAX_DATA_SYMS`].
16 16
export constant DATA_SYM_TABLE_SIZE: u32 = MAX_DATA_SYMS * 2;
17 17
18 18
/// Data symbol entry mapping name to address.
19 -
export record DataSym {
19 +
export record DataSym: Copy {
20 20
    /// Symbol name.
21 21
    name: *[u8],
22 22
    /// Absolute address, including data base address.
23 23
    addr: u32,
24 24
}
25 25
26 26
/// Hash-indexed data symbol map.
27 -
export record DataSymMap {
27 +
export record DataSymMap: Copy {
28 28
    /// Underlying hash table.
29 29
    dict: dict::Dict,
30 30
    /// Fallback linear array for edge cases.
31 31
    syms: *[DataSym],
32 32
}
lib/std/lang/gen/labels.rad +1 -1
10 10
export constant MAX_FUNCS: u32 = 8192;
11 11
/// Size of the function hash table. Must be a power of two.
12 12
export constant FUNC_TABLE_SIZE: u32 = MAX_FUNCS * 2;
13 13
14 14
/// Label tracking for code emission.
15 -
export record Labels {
15 +
export record Labels: Copy {
16 16
    /// Block offsets indexed by block index.
17 17
    /// Per-function, reset each function.
18 18
    blockOffsets: *mut [i32],
19 19
    /// Number of blocks recorded in current function.
20 20
    blockCount: u32,
lib/std/lang/gen/regalloc.rad +2 -2
20 20
21 21
use std::lang::il;
22 22
use std::lang::alloc;
23 23
24 24
/// Target configuration for register allocation.
25 -
export record TargetConfig {
25 +
export record TargetConfig: Copy {
26 26
    /// List of allocatable physical registers.
27 27
    /// Order determines allocation preference.
28 28
    allocatable: *[super::Reg],
29 29
    /// Function argument registers.
30 30
    argRegs: *[super::Reg],
33 33
    /// Size of a spill slot in bytes.
34 34
    slotSize: u32,
35 35
}
36 36
37 37
/// Complete register allocation result.
38 -
export record AllocResult {
38 +
export record AllocResult: Copy {
39 39
    /// SSA register to physical register mapping.
40 40
    assignments: *[?super::Reg],
41 41
    /// Spill slot information.
42 42
    spill: spill::SpillInfo,
43 43
    /// Bitmask of used callee-saved registers.
lib/std/lang/gen/regalloc/assign.rad +3 -3
16 16
/// Maximum number of active register mappings.
17 17
constant MAX_ACTIVE: u32 = 64;
18 18
19 19
/// Register mapping at a program point.
20 20
/// Maps SSA registers to physical registers.
21 -
export record RegMap {
21 +
export record RegMap: Copy {
22 22
    /// SSA (virtual) registers that have mappings.
23 23
    virtRegs: *mut [u32],
24 24
    /// Physical register for each virtual register.
25 25
    physRegs: *mut [gen::Reg],
26 26
    /// Number of active mappings.
27 27
    n: u32,
28 28
}
29 29
30 30
/// Register assignment result, per function.
31 -
export record AssignInfo {
31 +
export record AssignInfo: Copy {
32 32
    /// SSA register -> physical register mapping.
33 33
    assignments: *mut [?gen::Reg],
34 34
    /// Bitmask of used callee-saved registers.
35 35
    usedCalleeSaved: u32,
36 36
}
37 37
38 38
/// Per-instruction context for freeing and allocating register uses.
39 -
record InstrCtx {
39 +
record InstrCtx: Copy {
40 40
    current: *mut RegMap,
41 41
    usedRegs: *mut bitset::Bitset,
42 42
    func: *il::Fn,
43 43
    live: *liveness::LiveInfo,
44 44
    blockIdx: u32,
lib/std/lang/gen/regalloc/liveness.rad +3 -3
30 30
31 31
/// Maximum number of SSA registers supported.
32 32
export constant MAX_SSA_REGS: u32 = 8192;
33 33
34 34
/// Liveness information for a function.
35 -
export record LiveInfo {
35 +
export record LiveInfo: Copy {
36 36
    /// Per-block live-in sets (indexed by block index).
37 37
    liveIn: *mut [bitset::Bitset],
38 38
    /// Per-block live-out sets (indexed by block index).
39 39
    liveOut: *mut [bitset::Bitset],
40 40
    /// Per-block defs sets (registers defined in block).
46 46
    /// Maximum register number used.
47 47
    maxReg: u32,
48 48
}
49 49
50 50
/// Context for collecting defs and uses during block analysis.
51 -
record DefsUses {
51 +
record DefsUses: Copy {
52 52
    defs: *bitset::Bitset,
53 53
    uses: *mut bitset::Bitset,
54 54
}
55 55
56 56
/// Context for searching for a specific register in an instruction.
57 -
record FindCtx {
57 +
record FindCtx: Copy {
58 58
    target: u32,
59 59
    found: bool,
60 60
}
61 61
62 62
/// Compute liveness information for a function.
lib/std/lang/gen/regalloc/spill.rad +5 -5
39 39
constant MAX_CANDIDATES: u32 = 256;
40 40
/// Maximum loop depth for cost weighting (2^10 = 1024).
41 41
constant MAX_LOOP_WEIGHT: u32 = 10;
42 42
43 43
/// Spill cost for a single SSA register.
44 -
record SpillCost {
44 +
record SpillCost: Copy {
45 45
    /// Number of definitions (weighted by loop depth).
46 46
    defs: u32,
47 47
    /// Number of uses (weighted by loop depth).
48 48
    uses: u32,
49 49
}
50 50
51 51
/// Spill decision for a function.
52 -
export record SpillInfo {
52 +
export record SpillInfo: Copy {
53 53
    /// SSA register mapped to stack slot offset. `-1` means not spilled.
54 54
    slots: *mut [i32],
55 55
    /// Total spill frame size needed in bytes.
56 56
    frameSize: i32,
57 57
    /// Values that must be allocated in callee-saved registers.
59 59
    /// Maximum SSA register number.
60 60
    maxReg: u32,
61 61
}
62 62
63 63
/// Candidate buffer for spill decisions.
64 -
record Candidates {
64 +
record Candidates: Copy {
65 65
    entries: [CostEntry; 256],
66 66
    n: u32,
67 67
}
68 68
69 69
/// Entry for cost sorting.
70 -
record CostEntry {
70 +
record CostEntry: Copy {
71 71
    reg: u32,
72 72
    cost: u32,
73 73
}
74 74
75 75
/// Context for counting register uses.
76 -
record CountCtx {
76 +
record CountCtx: Copy {
77 77
    costs: *mut [SpillCost],
78 78
    weight: u32,
79 79
}
80 80
81 81
/// Analyze a function and determine which values need spill slots.
lib/std/lang/gen/types.rad +2 -2
1 1
//! Target-independent code generation types.
2 2
//!
3 3
//! Defines common records used by all code generation backends.
4 4
5 5
/// Function address entry for printing.
6 -
export record FuncAddr {
6 +
export record FuncAddr: Copy {
7 7
    /// Function name.
8 8
    name: *[u8],
9 9
    /// Instruction index where this function starts.
10 10
    index: u32,
11 11
}
12 12
13 13
/// Debug entry mapping an instruction to a source location.
14 -
export record DebugEntry {
14 +
export record DebugEntry: Copy {
15 15
    /// Byte offset of the instruction from the start of the program.
16 16
    pc: u32,
17 17
    /// Module identifier.
18 18
    moduleId: u16,
19 19
    /// Byte offset into the module's source file.
lib/std/lang/il.rad +16 -16
65 65
66 66
/// Source location for debug info.
67 67
///
68 68
/// Associates an IL instruction with its originating source module and
69 69
/// byte offset.
70 -
export record SrcLoc {
70 +
export record SrcLoc: Copy {
71 71
    /// Module identifier.
72 72
    moduleId: u16,
73 73
    /// Byte offset into the module's source file.
74 74
    offset: u32,
75 75
}
102 102
///////////
103 103
// Types //
104 104
///////////
105 105
106 106
/// IL type representation. Integer signedness is encoded in operations, not types.
107 -
export union Type {
107 +
export union Type: Copy {
108 108
    /// 8-bit value.
109 109
    W8,
110 110
    /// 16-bit value.
111 111
    W16,
112 112
    /// 32-bit value.
124 124
        case Type::W64 => return 8,
125 125
    }
126 126
}
127 127
128 128
/// SSA register reference.
129 -
export record Reg { n: u32 }
129 +
export record Reg: Copy { n: u32 }
130 130
131 131
/// Instruction value.
132 -
export union Val {
132 +
export union Val: Copy {
133 133
    /// Register reference.
134 134
    Reg(Reg),
135 135
    /// Immediate integer value.
136 136
    Imm(i64),
137 137
    /// Data symbol address (globals, constants, string literals).
143 143
    /// eg. in void returns.
144 144
    Undef,
145 145
}
146 146
147 147
/// Comparison operation for compare-and-branch.
148 -
export union CmpOp { Eq, Ne, Slt, Ult }
148 +
export union CmpOp: Copy { Eq, Ne, Slt, Ult }
149 149
150 150
/// Binary ALU operation kind.
151 -
export union BinOp {
151 +
export union BinOp: Copy {
152 152
    // Arithmetic.
153 153
    Add, Sub, Mul, Sdiv, Udiv, Srem, Urem,
154 154
    // Comparison.
155 155
    Eq, Ne, Slt, Sge, Ult, Uge,
156 156
    // Bitwise.
157 157
    And, Or, Xor, Shl, Sshr, Ushr,
158 158
}
159 159
160 160
/// Unary ALU operation kind.
161 -
export union UnOp {
161 +
export union UnOp: Copy {
162 162
    Neg, Not,
163 163
}
164 164
165 165
//////////////////
166 166
// Instructions //
167 167
//////////////////
168 168
169 169
/// Block parameter.
170 -
export record Param {
170 +
export record Param: Copy {
171 171
    /// SSA register.
172 172
    value: Reg,
173 173
    /// Parameter type.
174 174
    type: Type,
175 175
}
176 176
177 177
/// Switch case mapping a constant value to a branch target.
178 -
export record SwitchCase {
178 +
export record SwitchCase: Copy {
179 179
    /// The constant value to match against.
180 180
    value: i64,
181 181
    /// The target block index.
182 182
    target: u32,
183 183
    /// Arguments to pass to the target block.
184 184
    args: *mut [Val],
185 185
}
186 186
187 187
/// IL instruction.
188 188
/// SSA registers are represented as `Reg`, values as `Val`.
189 -
export union Instr {
189 +
export union Instr: Copy {
190 190
    ///////////////////////
191 191
    // Memory operations //
192 192
    ///////////////////////
193 193
194 194
    /// Allocate space on the stack: `reserve %dst <size> <alignment>;`
307 307
///
308 308
/// Basic blocks are instruction sequences with a single entry point and no branch
309 309
/// instruction, except possibly at the end of the sequence, where a terminator
310 310
/// may be found, ie. an instruction that terminates the sequence by jumping
311 311
/// to another sequence.
312 -
export record Block {
312 +
export record Block: Copy {
313 313
    /// Block label.
314 314
    label: *[u8],
315 315
    /// Block parameters.
316 316
    params: *[Param],
317 317
    /// Instructions in the block. The last instruction must be a terminator.
325 325
    /// Used for spill cost weighting in register allocation.
326 326
    loopDepth: u32,
327 327
}
328 328
329 329
/// An IL function.
330 -
export record Fn {
330 +
export record Fn: Copy {
331 331
    /// Qualified function name (e.g. `$mod$path$func`).
332 332
    name: *[u8],
333 333
    /// Function parameters.
334 334
    params: *[Param],
335 335
    /// Return type.
345 345
/////////////
346 346
// Program //
347 347
/////////////
348 348
349 349
/// Data initializer item.
350 -
export union DataItem {
350 +
export union DataItem: Copy {
351 351
    /// Typed value: `w32 42;` or `w8 255;`
352 352
    Val { typ: Type, val: i64 },
353 353
    /// Symbol reference: `$symbol`
354 354
    Sym(*[u8]),
355 355
    /// Function reference: `$fnName`
359 359
    /// Undefined value. Used for padding and `void` returns.
360 360
    Undef,
361 361
}
362 362
363 363
/// Data initializer value with optional repeat count.
364 -
export record DataValue {
364 +
export record DataValue: Copy {
365 365
    /// The item contained in the value.
366 366
    item: DataItem,
367 367
    /// The number of times the item should be repeated.
368 368
    count: u32,
369 369
}
370 370
371 371
/// Global data definition.
372 -
export record Data {
372 +
export record Data: Copy {
373 373
    /// Data name.
374 374
    name: *[u8],
375 375
    /// Size in bytes.
376 376
    size: u32,
377 377
    /// Alignment requirement.
386 386
    /// Initializer values.
387 387
    values: *[DataValue],
388 388
}
389 389
390 390
/// An IL program (compilation unit).
391 -
export record Program {
391 +
export record Program: Copy {
392 392
    /// Global data.
393 393
    data: *[Data],
394 394
    /// Functions.
395 395
    fns: *[*Fn],
396 396
}
lib/std/lang/lower.rad +25 -25
107 107
// Error Handling //
108 108
////////////////////
109 109
110 110
/// Lowering errors are typically unrecoverable since they indicate bugs in
111 111
/// the resolver or malformed AST that should have been caught earlier.
112 -
export union LowerError {
112 +
export union LowerError: Copy {
113 113
    /// A node's symbol was not set before lowering.
114 114
    MissingSymbol(*ast::Node),
115 115
    /// A node's type was not set before lowering.
116 116
    MissingType(*ast::Node),
117 117
    /// A node's constant value was not set before lowering.
247 247
//////////////////////////
248 248
// Core Data Structures //
249 249
//////////////////////////
250 250
251 251
/// Options controlling the lowering pass.
252 -
export record LowerOptions {
252 +
export record LowerOptions: Copy {
253 253
    /// Whether to emit source location info.
254 254
    debug: bool,
255 255
    /// Whether to lower `@test` functions.
256 256
    buildTest: bool,
257 257
}
258 258
259 259
/// Role of a lowered function in the final program.
260 -
export union FnRole {
260 +
export union FnRole: Copy {
261 261
    /// Regular function.
262 262
    Normal,
263 263
    /// Program entry function marked with `@default`.
264 264
    Default,
265 265
}
266 266
267 267
/// Function sink used by lowerers that consume functions as they are produced.
268 -
export record FnSink {
268 +
export record FnSink: Copy {
269 269
    /// Opaque context passed to the sink callback.
270 270
    ctx: *mut opaque,
271 271
    /// Callback invoked for each lowered function.
272 272
    emitFn: fn(*mut opaque, *il::Fn, FnRole),
273 273
}
274 274
275 275
/// Destination for functions produced by the lowerer.
276 -
export union FnOutput {
276 +
export union FnOutput: Copy {
277 277
    /// Store lowered functions in the provided slice.
278 278
    Accumulate(*mut [*il::Fn]),
279 279
    /// Send lowered functions to an external consumer immediately.
280 280
    Stream(FnSink),
281 281
}
282 282
283 283
/// Module-level lowering context. Shared across all function lowerings.
284 284
/// Holds global state like the data section (strings, constants) and provides
285 285
/// access to the resolver for type queries.
286 -
export record Lowerer {
286 +
export record Lowerer: Copy {
287 287
    /// Arena for persistent lowering state, including data and symbol names.
288 288
    arena: *mut alloc::Arena,
289 289
    /// Arena for allocations owned by the function currently being lowered.
290 290
    fnArena: *mut alloc::Arena,
291 291
    /// Allocator backed by the arena.
313 313
    /// Lowering options.
314 314
    options: LowerOptions,
315 315
}
316 316
317 317
/// Entry mapping a function symbol to its qualified name.
318 -
record FnSymEntry {
318 +
record FnSymEntry: Copy {
319 319
    sym: *resolver::Symbol,
320 320
    qualName: *[u8],
321 321
}
322 322
323 323
/// Entry in the global error tag table.
324 -
record ErrTagEntry {
324 +
record ErrTagEntry: Copy {
325 325
    /// The type of this error, identified by its interned pointer.
326 326
    ty: resolver::Type,
327 327
    /// The globally unique tag assigned to this error type (non-zero).
328 328
    tag: u32,
329 329
}
377 377
    }
378 378
    return FnRole::Normal;
379 379
}
380 380
381 381
/// Builder for accumulating data values during constant lowering.
382 -
record DataValueBuilder {
382 +
record DataValueBuilder: Copy {
383 383
    allocator: alloc::Allocator,
384 384
    values: *mut [il::DataValue],
385 385
    /// Whether all pushed values can be represented by zero-filled memory.
386 386
    zeroInit: bool,
387 387
}
388 388
389 389
/// Result of lowering constant data.
390 -
record ConstDataResult {
390 +
record ConstDataResult: Copy {
391 391
    values: *[il::DataValue],
392 392
    zeroInit: bool,
393 393
}
394 394
395 395
/// Create a new builder.
442 442
// a mapping from [`Var`] to current SSA value. When control flow merges,
443 443
// block parameters are inserted to merge values from different control flow paths.
444 444
445 445
/// A variable handle. Represents a source-level variable during lowering.
446 446
/// The same [`Var`] can have different SSA values in different blocks.
447 -
export record Var(u32);
447 +
export record Var: Copy(u32);
448 448
449 449
/// Metadata for a source-level variable, stored once per function.
450 450
///
451 451
/// Each variable declaration in the source creates one [`VarData`] entry in the
452 452
/// function's `variables` array, indexed by `id`. This contains static
454 454
///
455 455
/// Per-block SSA values are tracked separately in [`BlockData::vars`] as [`?il::Val`],
456 456
/// where `nil` means "not yet assigned in this block". Together they implement
457 457
/// SSA construction.
458 458
///
459 -
record VarData {
459 +
record VarData: Copy {
460 460
    /// Variable name, used by [`lookupVarByName`] to resolve identifiers.
461 461
    /// Nil for anonymous variables (e.g., internal loop counters).
462 462
    name: ?*[u8],
463 463
    /// IL type of this variable. Set at declaration time and used when
464 464
    /// generating loads, stores, and type-checking assignments.
472 472
    addressTaken: bool,
473 473
}
474 474
475 475
/// Links a function parameter to its corresponding variable for the entry block.
476 476
/// After creating the entry block, we iterate through these to define initial values.
477 -
record FnParamBinding {
477 +
record FnParamBinding: Copy {
478 478
    /// The variable that receives this parameter's value.
479 479
    var: Var,
480 480
    /// SSA register containing the parameter value from the caller.
481 481
    reg: il::Reg,
482 482
}
489 489
// instructions until terminated by a jump, branch, return, or unreachable.
490 490
// Blocks can be created before they're filled (forward references for jumps).
491 491
492 492
/// A handle to a basic block within the current function.
493 493
/// Block handles are stable, they don't change as more blocks are added.
494 -
export record BlockId(u32);
494 +
export record BlockId: Copy(u32);
495 495
496 496
/// Internal block state during construction.
497 497
///
498 498
/// The key invariants:
499 499
///
500 500
/// - A block is "open" if it has no terminator; instructions can be added.
501 501
/// - A block is "sealed" when all predecessor edges are known.
502 502
/// - Sealing is required before SSA construction can insert block parameters.
503 503
///
504 504
/// This differs from the final [`il::Block`] which is immutable and fully formed.
505 -
record BlockData {
505 +
record BlockData: Copy {
506 506
    /// Block label for debugging and IL printing.
507 507
    label: *[u8],
508 508
    /// Block parameters for merging values at control flow joins. These
509 509
    /// receive values from predecessor edges when control flow merges.
510 510
    params: *mut [il::Param],
535 535
///
536 536
/// A block is "unsealed" while its predecessors are still being discovered.
537 537
/// During this time, variables used before being defined locally are tracked.
538 538
/// Once all predecessors are known, the block is sealed and those variables
539 539
/// are resolved via [`resolveBlockArgs`].
540 -
union Sealed {
540 +
union Sealed: Copy {
541 541
    /// Block is unsealed; predecessors may still be added.
542 542
    No { incompleteVars: *mut [u32] },
543 543
    /// Block is sealed; all predecessors are known.
544 544
    Yes,
545 545
}
548 548
// Loop and Control Flow Context //
549 549
///////////////////////////////////
550 550
551 551
/// Context for break/continue statements within a loop.
552 552
/// Each nested loop pushes a new context onto the loop stack.
553 -
export record LoopCtx {
553 +
export record LoopCtx: Copy {
554 554
    /// Where `break` should transfer control (the loop's exit block).
555 555
    breakTarget: BlockId,
556 556
    /// Where `continue` should transfer control.
557 557
    continueTarget: ?BlockId,
558 558
}
559 559
560 560
/// Logical operator.
561 -
union LogicalOp { And, Or }
561 +
union LogicalOp: Copy { And, Or }
562 562
563 563
/// Iterator state for for-loop lowering.
564 -
union ForIter {
564 +
union ForIter: Copy {
565 565
    /// Range iterator: `for i in 0..n`.
566 566
    Range {
567 567
        valVar: Var,
568 568
        indexVar: ?Var,
569 569
        endVal: il::Val,
592 592
// - Optional aggregates: tag checked then payload extracted.
593 593
// - Unions: tag compared against variant indices.
594 594
595 595
/// Cached information about a match subject. Computed once and reused across
596 596
/// all arms to avoid redundant lowering and type queries.
597 -
record MatchSubject {
597 +
record MatchSubject: Copy {
598 598
    /// The lowered subject value.
599 599
    val: il::Val,
600 600
    /// Source-level type from the resolver.
601 601
    type: resolver::Type,
602 602
    /// IL-level type for code generation.
609 609
    /// How bindings are created: by value, or by reference.
610 610
    by: resolver::MatchBy,
611 611
}
612 612
613 613
/// Classifies a match subject by how it should be compared and destructured.
614 -
union MatchSubjectKind {
614 +
union MatchSubjectKind: Copy {
615 615
    /// Regular value: direct equality comparison.
616 616
    Regular,
617 617
    /// Optional with null pointer optimization: `?*T`, `?*[T]`.
618 618
    OptionalPtr,
619 619
    /// Optional aggregate `?T`: tagged union with payload.
639 639
//////////////////////////
640 640
// Field Access Support //
641 641
//////////////////////////
642 642
643 643
/// Result of resolving a place expression to a memory location.
644 -
record FieldRef {
644 +
record FieldRef: Copy {
645 645
    /// Base pointer register (points to the container).
646 646
    base: il::Reg,
647 647
    /// Byte offset of the value within the container.
648 648
    offset: i32,
649 649
    /// Type of the value held at that location.
651 651
}
652 652
653 653
/// Result of computing an element pointer for array/slice subscript operations.
654 654
/// Used by [`lowerElemPtr`] to return both the element address register and
655 655
/// the element type for subsequent load or address-of operations.
656 -
record ElemPtrResult {
656 +
record ElemPtrResult: Copy {
657 657
    /// Register holding the computed element address.
658 658
    elemReg: il::Reg,
659 659
    /// Source-level type of the element.
660 660
    elemType: resolver::Type,
661 661
}
662 662
663 663
/// Result of resolving a slice range to a data pointer and element count.
664 -
record SliceRangeResult {
664 +
record SliceRangeResult: Copy {
665 665
    dataReg: il::Reg,
666 666
    count: il::Val,
667 667
}
668 668
669 669
/////////////////////////////
670 670
// Function Lowering State //
671 671
/////////////////////////////
672 672
673 673
/// Per-function lowering state. Created fresh for each function and contains
674 674
/// all the mutable state needed during function body lowering.
675 -
record FnLowerer {
675 +
record FnLowerer: Copy {
676 676
    /// Reference to the module-level lowerer.
677 677
    low: *mut Lowerer,
678 678
    /// Allocator for IL allocations.
679 679
    allocator: alloc::Allocator,
680 680
    /// Type signature of the function being lowered.
lib/std/lang/module.rad +4 -4
23 23
constant PATH_SEP: u8 = '/';
24 24
/// Source file extension handled by the loader.
25 25
constant SOURCE_EXT: *[u8] = ".rad";
26 26
27 27
/// Lifecycle state for modules in the dependency graph.
28 -
export union ModuleState {
28 +
export union ModuleState: Copy {
29 29
    /// Slot unused or yet to be initialized.
30 30
    Vacant,
31 31
    /// Module registered with a known path but not parsed yet.
32 32
    Registered,
33 33
    /// Module is currently being parsed (used for cycle detection).
39 39
    /// Module finished semantic analysis and is ready for codegen.
40 40
    Ready,
41 41
}
42 42
43 43
/// Error categories that can be produced by the module graph.
44 -
export union ModuleError {
44 +
export union ModuleError: Copy {
45 45
    /// Module not found.
46 46
    NotFound(u16),
47 47
    /// Adding the module would exceed the fixed storage.
48 48
    CapacityExceeded,
49 49
    /// Provided path is missing the required `.rad` extension or otherwise invalid.
53 53
    /// Attempt to register a module before its parent.
54 54
    MissingParent,
55 55
}
56 56
57 57
/// Module metadata recorded inside the dependency graph.
58 -
export record ModuleEntry {
58 +
export record ModuleEntry: Copy {
59 59
    /// Numeric identifier for the slot (index in the graph array).
60 60
    id: u16,
61 61
    /// Package identifier this module belongs to.
62 62
    packageId: u16,
63 63
    /// Parent module identifier, or `nil` for root modules.
83 83
    /// Source text for this module (for error reporting).
84 84
    source: ?*[u8],
85 85
}
86 86
87 87
/// Dense storage for all modules referenced by the compilation unit.
88 -
export record ModuleGraph {
88 +
export record ModuleGraph: Copy {
89 89
    entries: *mut [ModuleEntry],
90 90
    entriesLen: u32,
91 91
    pool: *mut strings::Pool,
92 92
    /// Arena used for all AST node allocations.
93 93
    arena: ?*ast::NodeArena,
lib/std/lang/package.rad +1 -1
7 7
8 8
/// Maximum number of packages processed in a single invocation.
9 9
export constant MAX_PACKAGES: u32 = 4;
10 10
11 11
/// A compilation unit.
12 -
export record Package {
12 +
export record Package: Copy {
13 13
    /// Package identifier (index in the package array).
14 14
    id: u16,
15 15
    /// Package name.
16 16
    name: *[u8],
17 17
    /// Root module identifier for this package, or `nil` if not yet registered.
lib/std/lang/parser.rad +9 -9
25 25
26 26
/// Maximum number of parser errors before aborting.
27 27
constant MAX_ERRORS: u32 = 8;
28 28
29 29
/// Parser error type.
30 -
export union ParseError {
30 +
export union ParseError: Copy {
31 31
    /// Encountered a token that was not expected in the current context.
32 32
    UnexpectedToken,
33 33
}
34 34
35 35
/// Represents a parsed name-type-value triple.
36 36
///
37 37
/// Used for record field declarations, variable declarations,
38 38
/// and record field initializations.
39 -
record NameTypeValue {
39 +
record NameTypeValue: Copy {
40 40
    /// The identifier name.
41 41
    name: *ast::Node,
42 42
    /// The optional type annotation.
43 43
    type: ?*ast::Node,
44 44
    /// The optional initialization value.
46 46
    /// The optional alignment specifier.
47 47
    alignment: ?*ast::Node,
48 48
}
49 49
50 50
/// Behavioural differences when parsing record field lists.
51 -
union RecordFieldMode {
51 +
union RecordFieldMode: Copy {
52 52
    /// Labeled fields, allows for default values.
53 53
    Labeled,
54 54
    /// Unlabeled fields.
55 55
    Unlabeled,
56 56
}
57 57
58 58
/// Parser context differentiates between regular expressions and
59 59
/// conditional contexts where `{` begins a block.
60 -
union Context {
60 +
union Context: Copy {
61 61
    /// Normal expression context where `{` may start a record literal
62 62
    /// and `if` may start a conditional expression.
63 63
    Normal,
64 64
    /// Pattern context where `{` may start a record literal
65 65
    /// but `if` is reserved for guards.
68 68
    /// and `if` is reserved for guards.
69 69
    Condition,
70 70
}
71 71
72 72
/// A single parser error with location information.
73 -
export record Error {
73 +
export record Error: Copy {
74 74
    /// Human-readable error message.
75 75
    message: *[u8],
76 76
    /// The token where the error occurred.
77 77
    token: scanner::Token,
78 78
}
79 79
80 80
/// List of parser errors encountered during parsing.
81 -
record ErrorList {
81 +
record ErrorList: Copy {
82 82
    /// Fixed-size array of error records.
83 83
    list: [Error; MAX_ERRORS],
84 84
    /// Number of errors currently in the list.
85 85
    count: u32,
86 86
}
87 87
88 88
/// Snapshot of parser state for speculative parsing.
89 -
record SavedState {
89 +
record SavedState: Copy {
90 90
    parser: Parser,
91 91
    arena: u32,
92 92
    nextId: u32,
93 93
}
94 94
95 95
/// Operator metadata for precedence climbing.
96 -
record OpInfo {
96 +
record OpInfo: Copy {
97 97
    op: ast::BinaryOp,
98 98
    prec: i32,
99 99
}
100 100
101 101
/// Get operator info for a token kind using match-based dispatch.
142 142
            return nil,
143 143
    }
144 144
}
145 145
146 146
/// Parser state.
147 -
export record Parser {
147 +
export record Parser: Copy {
148 148
    /// The scanner that provides tokens.
149 149
    scanner: scanner::Scanner,
150 150
    /// The current token being examined.
151 151
    current: scanner::Token,
152 152
    /// The most recently consumed token.
lib/std/lang/resolver.rad +231 -114
51 51
constant MAX_LINEAR_BINDINGS: u32 = 32;
52 52
/// Maximum nesting depth tracked for loops.
53 53
constant MAX_LINEAR_LOOP_DEPTH: u32 = 16;
54 54
55 55
/// Trait definition stored in the resolver.
56 -
export record TraitType {
56 +
export record TraitType: Copy {
57 57
    /// Trait name.
58 58
    name: *[u8],
59 59
    /// Method signatures, including from supertraits.
60 60
    methods: *mut [TraitMethod],
61 61
    /// Supertraits that must also be implemented.
62 62
    supertraits: *mut [*TraitType],
63 63
}
64 64
65 65
/// A single method signature within a trait.
66 -
export record TraitMethod {
66 +
export record TraitMethod: Copy {
67 67
    /// Method name.
68 68
    name: *[u8],
69 69
    /// Function type for the method, excluding the receiver.
70 70
    fnType: *FnType,
71 71
    /// Whether the receiver is mutable.
75 75
    /// V-table slot index.
76 76
    index: u32,
77 77
}
78 78
79 79
/// An entry in the trait instance registry.
80 -
export record InstanceEntry {
80 +
export record InstanceEntry: Copy {
81 81
    /// Trait type descriptor.
82 82
    traitType: *TraitType,
83 83
    /// Concrete type that implements the trait.
84 84
    concreteType: Type,
85 85
    /// Name of the concrete type.
89 89
    /// Method symbols for each trait method, in declaration order.
90 90
    methods: *mut [*mut Symbol],
91 91
}
92 92
93 93
/// An entry in the method registry.
94 -
export record MethodEntry {
94 +
export record MethodEntry: Copy {
95 95
    /// Concrete type that owns the method.
96 96
    concreteType: Type,
97 97
    /// Name of the concrete type.
98 98
    concreteTypeName: *[u8],
99 99
    /// Method name.
140 140
141 141
/// Size of a pointer in bytes.
142 142
export constant PTR_SIZE: u32 = 8;
143 143
144 144
/// Information about a record or tuple field.
145 -
export record RecordField {
145 +
export record RecordField: Copy {
146 146
    /// Field name, `nil` for positional fields.
147 147
    name: ?*[u8],
148 148
    /// Field type.
149 149
    fieldType: Type,
150 150
    /// Byte offset from the start of the record.
151 151
    offset: i32,
152 152
}
153 153
154 154
/// Information about a union variant.
155 -
record UnionVariant {
155 +
record UnionVariant: Copy {
156 156
    name: *[u8],
157 157
    valueType: Type,
158 158
    symbol: *mut Symbol,
159 159
}
160 160
161 161
/// Array type payload.
162 -
export record ArrayType {
162 +
export record ArrayType: Copy {
163 163
    item: *Type,
164 164
    length: u32,
165 165
}
166 166
167 167
/// Record nominal type.
168 -
export record RecordType {
168 +
export record RecordType: Copy {
169 169
    fields: *[RecordField],
170 170
    labeled: bool,
171 171
    /// Cached layout.
172 172
    layout: Layout,
173 173
    /// Whether the declaration explicitly carries the `Linear` marker.
174 174
    declaredLinear: bool,
175 +
    /// Whether the declaration explicitly carries the `Copy` marker.
176 +
    declaredCopy: bool,
175 177
}
176 178
177 179
/// Union nominal type.
178 -
export record UnionType {
180 +
export record UnionType: Copy {
179 181
    variants: *[UnionVariant],
180 182
    /// Cached layout.
181 183
    layout: Layout,
182 184
    /// Cached payload offset within the union aggregate.
183 185
    valOffset: u32,
184 186
    /// If all variants have void payloads.
185 187
    isAllVoid: bool,
186 188
    /// Whether the declaration explicitly carries the `Linear` marker.
187 189
    declaredLinear: bool,
190 +
    /// Whether the declaration explicitly carries the `Copy` marker.
191 +
    declaredCopy: bool,
188 192
}
189 193
190 194
/// Metadata for user-defined types.
191 -
export union NominalType {
195 +
export union NominalType: Copy {
192 196
    /// Placeholder for a type that hasn't been fully resolved yet.
193 197
    /// Stores the declaration node for lazy resolution.
194 198
    Placeholder(*ast::Node),
195 199
    Record(RecordType),
196 200
    Union(UnionType),
197 201
}
198 202
199 203
/// Coercion plan, when coercion from one type to another.
200 -
export union Coercion {
204 +
export union Coercion: Copy {
201 205
    /// No coercion, eg. `T -> T`.
202 206
    Identity,
203 207
    /// Eg. `u8 -> i32`. Stores both source and target types for lowering.
204 208
    NumericCast { from: Type, to: Type },
205 209
    /// Eg. `T -> ?T`. Stores the inner value type.
214 218
        inst: *InstanceEntry,
215 219
    },
216 220
}
217 221
218 222
/// Result of resolving a module path.
219 -
record ResolvedModule {
223 +
record ResolvedModule: Copy {
220 224
    /// Module entry in the graph.
221 225
    entry: *module::ModuleEntry,
222 226
    /// Scope containing the module's declarations.
223 227
    scope: *mut Scope,
224 228
}
225 229
226 230
/// Type layout.
227 -
export record Layout {
231 +
export record Layout: Copy {
228 232
    /// Size in bytes.
229 233
    size: u32,
230 234
    /// Alignment in bytes.
231 235
    alignment: u32,
232 236
}
233 237
234 238
/// Computed union layout parameters.
235 -
record UnionLayoutInfo {
239 +
record UnionLayoutInfo: Copy {
236 240
    layout: Layout,
237 241
    valOffset: u32,
238 242
    isAllVoid: bool,
239 243
}
240 244
241 245
/// Pre-computed metadata for slice range expressions.
242 246
/// Used by the lowerer.
243 -
export record SliceRangeInfo {
247 +
export record SliceRangeInfo: Copy {
244 248
    /// Element type of the resulting slice.
245 249
    itemType: *Type,
246 250
    /// Whether the resulting slice is mutable.
247 251
    mutable: bool,
248 252
    /// Static capacity if container is an array.
249 253
    capacity: ?u32,
250 254
}
251 255
252 256
/// Pre-computed metadata for `for` loop iteration.
253 257
/// Used by the lowerer to avoid re-analyzing the iterable type.
254 -
export union ForLoopInfo {
258 +
export union ForLoopInfo: Copy {
255 259
    /// Iterating over a range expression (e.g., `for i in 0..n`).
256 260
    Range {
257 261
        valType: *Type,
258 262
        range: ast::Range,
259 263
        bindingName: ?*[u8],
267 271
        indexName: ?*[u8]
268 272
    },
269 273
}
270 274
271 275
/// Resolved function signature details.
272 -
export record FnType {
276 +
export record FnType: Copy {
273 277
    paramTypes: *[*Type],
274 278
    returnType: *Type,
275 279
    throwList: *[*Type],
276 280
    /// Whether calling this function requires an unsafe context.
277 281
    isUnsafe: bool,
278 282
    localCount: u32,
279 283
}
280 284
281 285
/// Describes a type computed during semantic analysis.
282 -
export union Type {
286 +
export union Type: Copy {
283 287
    /// A type that couldn't be decided.
284 288
    Unknown,
285 289
    /// Types only used during inference.
286 290
    Nil, Undefined, Int,
287 291
    /// Primitive types.
323 327
        mutable: bool,
324 328
    },
325 329
}
326 330
327 331
/// Structured diagnostic payload for type mismatches.
328 -
export record TypeMismatch {
332 +
export record TypeMismatch: Copy {
329 333
    expected: Type,
330 334
    actual: Type,
331 335
}
332 336
333 337
/// Structured diagnostic payload for invalid `as` casts.
334 -
export record InvalidAsCast {
338 +
export record InvalidAsCast: Copy {
335 339
    from: Type,
336 340
    to: Type,
337 341
}
338 342
339 343
/// Diagnostic payload for argument count mismatches.
340 -
export record CountMismatch {
344 +
export record CountMismatch: Copy {
341 345
    expected: u32,
342 346
    actual: u32,
343 347
}
344 348
345 349
/// Detailed payload attached to a symbol, specialized per symbol kind.
346 -
export union SymbolData {
350 +
export union SymbolData: Copy {
347 351
    /// Payload describing mutable bindings like variables or functions.
348 352
    Value {
349 353
        /// Whether the binding permits mutation.
350 354
        mutable: bool,
351 355
        /// Custom alignment requirement, or 0 for default.
386 390
    /// Trait symbol.
387 391
    Trait(*mut TraitType),
388 392
}
389 393
390 394
/// Resolved symbol allocated during semantic analysis.
391 -
export record Symbol {
395 +
export record Symbol: Copy {
392 396
    /// Symbol name in source code.
393 397
    name: *[u8],
394 398
    /// Data associated with the symbol.
395 399
    data: SymbolData,
396 400
    /// Bitset of attributes applied to the declaration.
400 404
    /// Module ID this symbol belongs to. Only for module-level symbols.
401 405
    moduleId: ?u16,
402 406
}
403 407
404 408
/// Integer constant payload.
405 -
export record ConstInt {
409 +
export record ConstInt: Copy {
406 410
    /// Absolute magnitude of the value.
407 411
    magnitude: u64,
408 412
    /// Bit width of the integer.
409 413
    bits: u8,
410 414
    /// Whether the integer is signed.
412 416
    /// Whether the value is negative (only valid when `signed` is true).
413 417
    negative: bool,
414 418
}
415 419
416 420
/// Constant value recorded for literal nodes.
417 -
export union ConstValue {
421 +
export union ConstValue: Copy {
418 422
    Bool(bool),
419 423
    Char(u8),
420 424
    String(*[u8]),
421 425
    Int(ConstInt),
422 426
}
423 427
424 428
/// Integer range metadata for primitive integer types.
425 -
union IntegerRange {
429 +
union IntegerRange: Copy {
426 430
    Signed {
427 431
        bits: u8,
428 432
        min: i64,
429 433
        max: i64,
430 434
        lim: u64,
434 438
        max: u64,
435 439
    },
436 440
}
437 441
438 442
/// Diagnostic emitted by the analyzer.
439 -
export record Error {
443 +
export record Error: Copy {
440 444
    /// Error category.
441 445
    kind: ErrorKind,
442 446
    /// Node associated with the error, if known.
443 447
    node: ?*ast::Node,
444 448
    /// Module ID where this error occurred.
445 449
    moduleId: u16,
446 450
}
447 451
448 452
/// High-level classification for semantic diagnostics.
449 -
export union ErrorKind {
453 +
export union ErrorKind: Copy {
450 454
    /// Identifier declared more than once in the same scope.
451 455
    DuplicateBinding(*[u8]),
452 456
    /// Identifier referenced before it was declared.
453 457
    UnresolvedSymbol(*[u8]),
454 458
    /// Attempted to assign to an immutable binding.
593 597
    FnThrowOverflow(CountMismatch),
594 598
    /// Trait declaration has too many methods.
595 599
    TraitMethodOverflow(CountMismatch),
596 600
    /// Instance declaration is missing a required supertrait instance.
597 601
    MissingSupertraitInstance(*[u8]),
602 +
    /// An affine binding was used after it moved.
603 +
    AffineUseAfterMove(*[u8]),
598 604
    /// Linear binding was consumed more than once.
599 605
    LinearUseAfterConsume(*[u8]),
600 606
    /// Linear binding remains available at an exit.
601 607
    LinearNotConsumed(*[u8]),
602 608
    /// A case-pattern `let-else` fallback must terminate control flow.
609 615
    LinearDiscard,
610 616
    /// Assignment would overwrite a live linear value.
611 617
    LinearOverwrite,
612 618
    /// `undefined` cannot initialize a linear type.
613 619
    LinearUndefined,
620 +
    /// A `Copy` declaration contains a non-copy field or variant.
621 +
    CopyContainsNonCopy,
622 +
    /// A declaration carries both `Copy` and `Linear`.
623 +
    ConflictingOwnershipMarkers,
614 624
    /// A reference appears in a storable or escaping position.
615 625
    InvalidRefPosition,
616 626
    /// A reference cannot be bound to a local.
617 627
    RefBinding,
618 628
    /// Call arguments contain overlapping incompatible loans.
624 634
    /// Internal error.
625 635
    Internal,
626 636
}
627 637
628 638
/// Diagnostics returned by the analyzer.
629 -
export record Diagnostics {
639 +
export record Diagnostics: Copy {
630 640
    errors: *mut [Error],
631 641
}
632 642
633 643
/// Call context.
634 -
union CallCtx {
644 +
union CallCtx: Copy {
635 645
    /// Normal function call.
636 646
    Normal,
637 647
    /// Fallible function call, ie. `try f()`.
638 648
    Try,
639 649
}
640 650
641 651
/// Result of resolving a record literal's type name.
642 -
record ResolvedRecordLitType {
652 +
record ResolvedRecordLitType: Copy {
643 653
    /// The record nominal type to use for field checking.
644 654
    recordType: *NominalType,
645 655
    /// The result type of the literal (record type or union type for variants).
646 656
    resultType: Type,
647 657
}
648 658
649 659
/// Result of checking for a `super` path prefix.
650 -
record SuperAccessResult {
660 +
record SuperAccessResult: Copy {
651 661
    scope: *mut Scope,
652 662
    child: *ast::Node,
653 663
}
654 664
655 665
/// Node-specific resolver metadata.
656 -
export union NodeExtra {
666 +
export union NodeExtra: Copy {
657 667
    /// No extra data for this node.
658 668
    None,
659 669
    /// Resolved field index for record literal fields.
660 670
    RecordField { index: u32 },
661 671
    /// Slice range metadata for subscript expressions with ranges.
682 692
    /// Slice `.delete(index)` method call.
683 693
    SliceDelete { elemType: *Type },
684 694
}
685 695
686 696
/// Combined resolver metadata for a single AST node.
687 -
export record NodeData {
697 +
export record NodeData: Copy {
688 698
    /// Resolved type for this node.
689 699
    ty: Type,
690 700
    /// Coercion plan applied to this node.
691 701
    coercion: Coercion,
692 702
    /// Symbol associated with this node.
698 708
    /// Node-specific extra data.
699 709
    extra: NodeExtra,
700 710
}
701 711
702 712
/// Table storing all resolver metadata indexed by node ID.
703 -
record NodeDataTable {
713 +
record NodeDataTable: Copy {
704 714
    entries: *mut [NodeData],
705 715
}
706 716
707 717
/// Lexical scope.
708 -
export record Scope {
718 +
export record Scope: Copy {
709 719
    /// Owning AST node, or `nil` for the root scope.
710 720
    owner: ?*ast::Node,
711 721
    /// Parent/enclosing scope.
712 722
    parent: ?*mut Scope,
713 723
    /// Module ID if this is a module scope.
717 727
    /// Number of live symbols.
718 728
    symbolsLen: u32,
719 729
}
720 730
721 731
/// An object used by the enter and exit functions for module scopes.
722 -
record ModuleScope {
732 +
record ModuleScope: Copy {
723 733
    /// Module root node.
724 734
    root: *ast::Node,
725 735
    /// Module entry in graph.
726 736
    entry: *module::ModuleEntry,
727 737
    /// The newly entered scope.
731 741
    /// The previous module.
732 742
    prevMod: u16,
733 743
}
734 744
735 745
/// Loop context for tracking control flow within loops.
736 -
record LoopCtx {
746 +
record LoopCtx: Copy {
737 747
    /// Whether a reachable break was encountered in this loop.
738 748
    /// This is used to determine whether a loop diverges.
739 749
    hasBreak: bool,
740 750
}
741 751
742 752
/// Configuration for semantic analysis.
743 -
export record Config {
753 +
export record Config: Copy {
744 754
    /// Whether we're building in test mode.
745 755
    buildTest: bool,
746 756
}
747 757
748 758
/// How pattern bindings are created during match.
749 -
export union MatchBy {
759 +
export union MatchBy: Copy {
750 760
    /// Match by value.
751 761
    Value,
752 762
    /// Match by immutable reference.
753 763
    Ref,
754 764
    /// Match by mutable reference.
755 765
    MutRef,
756 766
}
757 767
758 768
/// State of a match statement being resolved.
759 769
// TODO: This is only used because of the maximum function param limitation.
760 -
record MatchState {
770 +
record MatchState: Copy {
761 771
    /// Is the match catch-all?
762 772
    catchAll: bool,
763 773
    /// Is the match constant?
764 774
    isConst: bool
765 775
}
766 776
767 777
/// Result of unwrapping a type for pattern matching.
768 -
export record MatchSubject {
778 +
export record MatchSubject: Copy {
769 779
    /// The effective type to match against.
770 780
    effectiveTy: Type,
771 781
    /// How bindings should be created.
772 782
    by: MatchBy,
773 783
}
774 784
775 785
/// How an expression uses a linear result.
776 -
union LinearUse {
786 +
union LinearUse: Copy {
777 787
    /// Consume the value and end its availability.
778 788
    Consume,
779 789
    /// Read the value without consuming it.
780 790
    Observe,
781 791
    /// Borrow the value through a reference.
785 795
    /// Use the value as an assignment target.
786 796
    Place,
787 797
}
788 798
789 799
/// Per-control-flow-path ownership state.
790 -
record LinearEnv {
800 +
record LinearEnv: Copy {
791 801
    /// Symbols tracked on this control-flow path.
792 802
    symbols: [?*mut Symbol; MAX_LINEAR_BINDINGS],
793 803
    /// Bit set for each binding that remains available.
794 804
    available: u64,
795 805
    /// Number of entries in `symbols`.
797 807
    /// Whether this control-flow path has terminated.
798 808
    terminated: bool,
799 809
}
800 810
801 811
/// Function-local exact-use checker state.
802 -
record LinearChecker {
812 +
record LinearChecker: Copy {
803 813
    /// Resolver that owns the symbols and diagnostics.
804 814
    resolver: *mut Resolver,
805 815
    /// Binding count at entry to each active loop.
806 816
    loopMarks: [u32; MAX_LINEAR_LOOP_DEPTH],
807 817
    /// Available bindings at entry to each active loop.
824 834
    }
825 835
    return MatchSubject { effectiveTy: ty, by: MatchBy::Value };
826 836
}
827 837
828 838
/// Global resolver state.
829 -
export record Resolver {
839 +
export record Resolver: Copy {
830 840
    /// Current scope.
831 841
    scope: *mut Scope,
832 842
    /// Package scope containing package roots and top-level symbols.
833 843
    pkgScope: *mut Scope,
834 844
    /// Stack of loop contexts for nested loops.
864 874
    /// Number of registered standalone methods.
865 875
    methodsLen: u32,
866 876
}
867 877
868 878
/// Internal error sentinel thrown when analysis cannot proceed.
869 -
export union ResolveError {
879 +
export union ResolveError: Copy {
870 880
    Failure,
871 881
}
872 882
873 883
/// Node in the type interning linked list.
874 -
record TypeNode {
884 +
record TypeNode: Copy {
875 885
    ty: Type,
876 886
    next: ?*TypeNode,
877 887
}
878 888
879 889
/// Allocate and intern a type in the arena, returning a pointer for deduplication.
932 942
    }
933 943
    return nil;
934 944
}
935 945
936 946
/// Storage buffers used by the analyzer.
937 -
export record ResolverStorage {
947 +
export record ResolverStorage: Copy {
938 948
    /// Unified arena for symbols, scopes, and nominal type.
939 949
    arena: alloc::Arena,
940 950
    /// Node semantic metadata indexed by node ID.
941 951
    nodeData: *mut [NodeData],
942 952
    /// Package scope.
944 954
    /// Error storage.
945 955
    errors: *mut [Error],
946 956
}
947 957
948 958
/// Input for resolving a single package.
949 -
export record Pkg {
959 +
export record Pkg: Copy {
950 960
    /// Root module entry.
951 961
    rootEntry: *module::ModuleEntry,
952 962
    /// Root AST node.
953 963
    rootAst: *ast::Node,
954 964
}
2025 2035
        case Type::Nominal(_) => return false,
2026 2036
        else => return false,
2027 2037
    }
2028 2038
}
2029 2039
2030 -
/// Return whether a type is exact-linear.
2031 -
export fn isLinear(ty: Type) -> bool {
2040 +
/// Return whether `ty` may be duplicated implicitly.
2041 +
export fn isCopy(ty: Type) -> bool {
2032 2042
    match ty {
2033 -
        case Type::Pointer { class: types::PointerClass::Owned, .. },
2034 -
             Type::Slice { class: types::PointerClass::Owned, .. },
2035 -
             Type::TraitObject { class: types::PointerClass::Owned, .. } => return true,
2036 -
        case Type::Pointer { class: types::PointerClass::Ref, .. },
2037 -
             Type::Pointer { class: types::PointerClass::Unsafe, .. },
2038 -
             Type::Slice { class: types::PointerClass::Ref, .. },
2039 -
             Type::Slice { class: types::PointerClass::Unsafe, .. },
2040 -
             Type::TraitObject { class: types::PointerClass::Ref, .. },
2041 -
             Type::TraitObject { class: types::PointerClass::Unsafe, .. } => return false,
2043 +
        case Type::Array(array) => return isCopy(*array.item),
2044 +
        case Type::Optional(inner) => return isCopy(*inner),
2045 +
        case Type::Nominal(NominalType::Record(recInfo)) => return recInfo.declaredCopy,
2046 +
        case Type::Nominal(NominalType::Union(unionType)) => return unionType.declaredCopy,
2047 +
        case Type::Nominal(NominalType::Placeholder(_)) => return false,
2048 +
        else => return true,
2049 +
    }
2050 +
}
2042 2051
2052 +
/// Return whether a type must be consumed exactly once.
2053 +
export fn isLinear(ty: Type) -> bool {
2054 +
    match ty {
2043 2055
        case Type::Array(array) => return isLinear(*array.item),
2044 2056
        case Type::Optional(inner) => return isLinear(*inner),
2045 2057
        case Type::Nominal(NominalType::Record(recInfo)) => {
2046 2058
            if recInfo.declaredLinear {
2047 2059
                return true;
2066 2078
        }
2067 2079
        else => return false,
2068 2080
    }
2069 2081
}
2070 2082
2083 +
/// Return whether a by-value use moves `ty`.
2084 +
fn isMoveOnly(ty: Type) -> bool {
2085 +
    return not isCopy(ty);
2086 +
}
2087 +
2071 2088
/// Return whether `ty` is a direct unsafe pointer-like value.
2072 2089
fn isUnsafePointerType(ty: Type) -> bool {
2073 2090
    match ty {
2074 2091
        case Type::Pointer { class: types::PointerClass::Unsafe, .. },
2075 2092
             Type::Slice { class: types::PointerClass::Unsafe, .. },
3406 3423
        if retTy <> Type::Void and bodyTy <> Type::Never {
3407 3424
            exitFn(self);
3408 3425
            if isUnsafe { set self.unsafeDepth -= 1; }
3409 3426
            throw emitError(self, body, ErrorKind::FnMissingReturn);
3410 3427
        }
3428 +
        try checkLinearFn(self, nil, decl.sig.params, body) catch e {
3429 +
            exitFn(self);
3430 +
            if isUnsafe { set self.unsafeDepth -= 1; }
3431 +
            throw e;
3432 +
        };
3411 3433
        exitFn(self);
3412 3434
        if isUnsafe {
3413 3435
            set self.unsafeDepth -= 1;
3414 3436
        }
3415 3437
    } else if not isExtern {
3425 3447
    let _ = try bindValueIdent(self, param.name, node, ty, false, 0, 0);
3426 3448
3427 3449
    return ty;
3428 3450
}
3429 3451
3430 -
/// Resolve the compiler-known `Linear` marker from a derive list.
3431 -
fn resolveLinearDerive(self: *mut Resolver, derives: *mut [*ast::Node]) -> bool
3452 +
/// Compiler-known ownership markers carried by a composite declaration.
3453 +
record OwnershipMarkers: Copy {
3454 +
    /// The declaration requires exact consumption.
3455 +
    linear: bool,
3456 +
    /// The declaration permits implicit copies.
3457 +
    copy: bool,
3458 +
}
3459 +
3460 +
/// Resolve compiler-known ownership markers from a derive list.
3461 +
fn resolveOwnershipMarkers(self: *mut Resolver, derives: *mut [*ast::Node]) -> OwnershipMarkers
3432 3462
    throws (ResolveError)
3433 3463
{
3434 -
    let mut linear = false;
3464 +
    let mut result = OwnershipMarkers { linear: false, copy: false };
3435 3465
    for derive in derives {
3436 3466
        let name = try nodeName(self, derive);
3437 3467
        if mem::eq(name, "Linear") {
3438 -
            if linear {
3468 +
            if result.linear {
3469 +
                throw emitError(self, derive, ErrorKind::DuplicateBinding(name));
3470 +
            }
3471 +
            if result.copy {
3472 +
                throw emitError(self, derive, ErrorKind::ConflictingOwnershipMarkers);
3473 +
            }
3474 +
            set result.linear = true;
3475 +
        } else if mem::eq(name, "Copy") {
3476 +
            if result.copy {
3439 3477
                throw emitError(self, derive, ErrorKind::DuplicateBinding(name));
3440 3478
            }
3441 -
            set linear = true;
3479 +
            if result.linear {
3480 +
                throw emitError(self, derive, ErrorKind::ConflictingOwnershipMarkers);
3481 +
            }
3482 +
            set result.copy = true;
3442 3483
        } else {
3443 3484
            // Resolve an ordinary trait derive.
3444 3485
            try infer(self, derive);
3445 3486
        }
3446 3487
    }
3447 -
    return linear;
3488 +
    return result;
3448 3489
}
3449 3490
3450 3491
/// Resolve record fields from a node list.
3451 3492
fn resolveRecordFields(self: *mut Resolver, node: *ast::Node, fields: *mut [*ast::Node], labeled: bool) -> RecordType
3452 3493
    throws (ResolveError)
3505 3546
    return RecordType {
3506 3547
        fields: &result[..],
3507 3548
        labeled,
3508 3549
        layout: recordLayout,
3509 3550
        declaredLinear: false,
3551 +
        declaredCopy: false,
3510 3552
    };
3511 3553
}
3512 3554
3513 3555
/// Resolve record field types for a named record declaration.
3514 3556
fn resolveRecordBody(self: *mut Resolver, node: *ast::Node, decl: ast::RecordDecl)
3523 3565
3524 3566
    // Skip if already resolved.
3525 3567
    if let case NominalType::Record(_) = *nominalTy {
3526 3568
        return;
3527 3569
    }
3528 -
    let declaredLinear = try resolveLinearDerive(self, decl.derives);
3570 +
    let markers = try resolveOwnershipMarkers(self, decl.derives);
3529 3571
    let mut recordType = try resolveRecordFields(self, node, decl.fields, decl.labeled);
3530 -
    set recordType.declaredLinear = declaredLinear;
3572 +
    if markers.copy {
3573 +
        for field in recordType.fields {
3574 +
            if not isCopy(field.fieldType) {
3575 +
                throw emitError(self, node, ErrorKind::CopyContainsNonCopy);
3576 +
            }
3577 +
        }
3578 +
    }
3579 +
    set recordType.declaredLinear = markers.linear;
3580 +
    set recordType.declaredCopy = markers.copy;
3531 3581
3532 3582
    set *nominalTy = NominalType::Record(recordType);
3533 3583
}
3534 3584
3535 3585
/// Bind a type name.
4011 4061
    if retTy <> Type::Void and bodyTy <> Type::Never {
4012 4062
        exitFn(self);
4013 4063
        if isUnsafe { set self.unsafeDepth -= 1; }
4014 4064
        throw emitError(self, body, ErrorKind::FnMissingReturn);
4015 4065
    }
4066 +
    try checkLinearFn(self, receiverName, sig.params, body) catch e {
4067 +
        exitFn(self);
4068 +
        if isUnsafe { set self.unsafeDepth -= 1; }
4069 +
        throw e;
4070 +
    };
4016 4071
    exitFn(self);
4017 4072
    if isUnsafe {
4018 4073
        set self.unsafeDepth -= 1;
4019 4074
    }
4020 4075
}
4203 4258
    let mut variants: *mut [UnionVariant] = &mut [];
4204 4259
4205 4260
    // Create a temporary nominal type to replace the placeholder.This prevents infinite recursion
4206 4261
    // when a variant references this union type (e.g. record payloads with `*[Self]`).
4207 4262
    // TODO: It would be best to have a resolving state eg. `Visiting` for this situation.
4208 -
    let declaredLinear = try resolveLinearDerive(self, decl.derives);
4263 +
    let markers = try resolveOwnershipMarkers(self, decl.derives);
4209 4264
    set *nominalTy = NominalType::Union(UnionType {
4210 4265
        variants: &[],
4211 4266
        layout: Layout { size: 0, alignment: 0 },
4212 4267
        valOffset: 0,
4213 4268
        isAllVoid: true,
4214 -
        declaredLinear,
4269 +
        declaredLinear: markers.linear,
4270 +
        declaredCopy: markers.copy,
4215 4271
    });
4216 4272
4217 4273
    assert decl.variants.len <= MAX_UNION_VARIANTS, "resolveUnionBody: maximum union variants exceeded";
4218 4274
    let mut iota: u32 = 0;
4219 4275
    for variantNode, i in decl.variants {
4237 4293
            name: variantName,
4238 4294
            valueType: variantType,
4239 4295
            symbol: variantSym,
4240 4296
        }, a);
4241 4297
    }
4298 +
    if markers.copy {
4299 +
        for variant in variants {
4300 +
            if not isCopy(variant.valueType) {
4301 +
                throw emitError(self, node, ErrorKind::CopyContainsNonCopy);
4302 +
            }
4303 +
        }
4304 +
    }
4242 4305
    let info = computeUnionLayout(&variants[..]);
4243 4306
4244 4307
    // Update the nominal type with the resolved variants.
4245 4308
    set *nominalTy = NominalType::Union(UnionType {
4246 4309
        variants: &variants[..],
4247 4310
        layout: info.layout,
4248 4311
        valOffset: info.valOffset,
4249 4312
        isAllVoid: info.isAllVoid,
4250 -
        declaredLinear,
4313 +
        declaredLinear: markers.linear,
4314 +
        declaredCopy: markers.copy,
4251 4315
    });
4252 4316
}
4253 4317
4254 4318
/// Check if a module should be analyzed based on its attributes and build configuration.
4255 4319
fn shouldAnalyzeModule(self: *Resolver, attrs: ?ast::Attributes) -> bool {
4400 4464
4401 4465
    return setNodeType(self, node, unifyBranches(thenTy, elseTy));
4402 4466
}
4403 4467
4404 4468
/// Controls how bare identifiers are handled in case patterns.
4405 -
union IdentMode {
4469 +
union IdentMode: Copy {
4406 4470
    /// Identifier is a value to compare against.
4407 4471
    Compare,
4408 4472
    /// Identifier introduces a new binding.
4409 4473
    Bind,
4410 4474
}
7131 7195
        case ast::TypeSig::Nominal(name) => {
7132 7196
            let ty = try resolveTypeName(self, name);
7133 7197
            return Type::Nominal(ty);
7134 7198
        }
7135 7199
        case ast::TypeSig::Record { fields, labeled } => {
7136 -
            let recordType = try resolveRecordFields(self, node, fields, labeled);
7200 +
            let mut recordType = try resolveRecordFields(self, node, fields, labeled);
7201 +
            set recordType.declaredCopy = true;
7202 +
            for field in recordType.fields {
7203 +
                if not isCopy(field.fieldType) {
7204 +
                    set recordType.declaredCopy = false;
7205 +
                }
7206 +
            }
7137 7207
            let nominalTy = allocNominalType(self, NominalType::Record(recordType));
7138 7208
            return Type::Nominal(nominalTy);
7139 7209
        }
7140 7210
        case ast::TypeSig::Fn(t) => {
7141 7211
            let a = alloc::arenaAllocator(&mut self.arena);
7380 7450
/// Return whether a tracked binding is still available.
7381 7451
fn linearBindingAvailable(env: *LinearEnv, index: u32) -> bool {
7382 7452
    return (env.available & ((1 as u64) << (index as u64))) <> 0;
7383 7453
}
7384 7454
7385 -
/// Add a local binding when its resolved type is linear.
7455 +
/// Add a local binding when its resolved type moves by value.
7386 7456
fn addLinearBinding(checker: *mut LinearChecker, env: *mut LinearEnv, node: *ast::Node)
7387 7457
    throws (ResolveError)
7388 7458
{
7389 7459
    let sym = symbolFor(checker.resolver, node) else return;
7390 7460
    let case SymbolData::Value { type: ty, .. } = sym.data else return;
7391 -
    if not isLinear(ty) {
7461 +
    if not isMoveOnly(ty) {
7392 7462
        return;
7393 7463
    }
7394 7464
    if env.len >= MAX_LINEAR_BINDINGS {
7395 7465
        throw emitError(checker.resolver, node, ErrorKind::Internal);
7396 7466
    }
7397 7467
    set env.symbols[env.len] = sym;
7398 7468
    set env.available |= (1 as u64) << (env.len as u64);
7399 7469
    set env.len += 1;
7400 7470
}
7401 7471
7402 -
/// Require all bindings introduced after `start` to have been consumed.
7472 +
/// Mark a tracked binding as uninitialized.
7473 +
fn markLinearBindingUnavailable(self: *mut Resolver, env: *mut LinearEnv, node: *ast::Node) {
7474 +
    let sym = symbolFor(self, node) else return;
7475 +
    let index = findLinearBinding(env, sym) else return;
7476 +
    set env.available &= ~((1 as u64) << (index as u64));
7477 +
}
7478 +
7479 +
/// Require exact-use bindings introduced after `start` to be consumed.
7403 7480
fn finishLinearScope(
7404 7481
    checker: *mut LinearChecker,
7405 7482
    env: *mut LinearEnv,
7406 7483
    start: u32,
7407 7484
) throws (ResolveError) {
7408 7485
    if not env.terminated {
7409 7486
        for i in start..env.len {
7410 7487
            if linearBindingAvailable(env, i) {
7411 7488
                let sym = env.symbols[i] else panic "finishLinearScope: missing symbol";
7412 -
                throw emitError(
7413 -
                    checker.resolver,
7414 -
                    sym.node,
7415 -
                    ErrorKind::LinearNotConsumed(sym.name),
7416 -
                );
7489 +
                let case SymbolData::Value { type: ty, .. } = sym.data
7490 +
                    else panic "finishLinearScope: expected value symbol";
7491 +
                if isLinear(ty) {
7492 +
                    throw emitError(
7493 +
                        checker.resolver,
7494 +
                        sym.node,
7495 +
                        ErrorKind::LinearNotConsumed(sym.name),
7496 +
                    );
7497 +
                }
7417 7498
            }
7418 7499
        }
7419 7500
    }
7420 7501
    set env.len = start;
7421 7502
}
7422 7503
7423 -
/// Consume a tracked identifier exactly once.
7504 +
/// Move or consume a tracked identifier once.
7424 7505
fn consumeLinearIdent(
7425 7506
    checker: *mut LinearChecker,
7426 7507
    env: *mut LinearEnv,
7427 7508
    node: *ast::Node,
7428 7509
) throws (ResolveError) {
7429 7510
    let sym = symbolFor(checker.resolver, node) else return;
7430 7511
    let index = findLinearBinding(env, sym) else return;
7431 7512
    if not linearBindingAvailable(env, index) {
7432 -
        throw emitError(
7433 -
            checker.resolver,
7434 -
            node,
7435 -
            ErrorKind::LinearUseAfterConsume(sym.name),
7436 -
        );
7513 +
        let case SymbolData::Value { type: ty, .. } = sym.data
7514 +
            else panic "consumeLinearIdent: expected value symbol";
7515 +
        let kind = ErrorKind::LinearUseAfterConsume(sym.name) if isLinear(ty)
7516 +
            else ErrorKind::AffineUseAfterMove(sym.name);
7517 +
        throw emitError(checker.resolver, node, kind);
7437 7518
    }
7438 7519
    set env.available &= ~((1 as u64) << (index as u64));
7439 7520
}
7440 7521
7441 -
/// Verify that two live branches agree on every outer binding.
7522 +
/// Merge ownership availability across two live branches.
7442 7523
fn joinLinearBranches(
7443 7524
    checker: *mut LinearChecker,
7444 7525
    env: *mut LinearEnv,
7445 7526
    left: LinearEnv,
7446 7527
    right: LinearEnv,
7458 7539
    if right.terminated {
7459 7540
        set *env = left;
7460 7541
        return;
7461 7542
    }
7462 7543
    assert left.len == right.len, "joinLinearBranches: scope mismatch";
7544 +
    let mut result = left;
7463 7545
    for i in 0..left.len {
7464 7546
        if linearBindingAvailable(&left, i) <> linearBindingAvailable(&right, i) {
7465 7547
            let sym = left.symbols[i] else panic "joinLinearBranches: missing symbol";
7466 -
            throw emitError(
7467 -
                checker.resolver,
7468 -
                node,
7469 -
                ErrorKind::LinearBranchMismatch(sym.name),
7470 -
            );
7548 +
            let case SymbolData::Value { type: ty, .. } = sym.data
7549 +
                else panic "joinLinearBranches: expected value symbol";
7550 +
            if isLinear(ty) {
7551 +
                throw emitError(
7552 +
                    checker.resolver,
7553 +
                    node,
7554 +
                    ErrorKind::LinearBranchMismatch(sym.name),
7555 +
                );
7556 +
            }
7557 +
            set result.available &= ~((1 as u64) << (i as u64));
7471 7558
        }
7472 7559
    }
7473 -
    set *env = left;
7560 +
    set *env = result;
7474 7561
}
7475 7562
7476 -
/// Require all current bindings to be consumed at a function exit.
7563 +
/// Require all available exact-use bindings to be consumed at a function exit.
7477 7564
fn finishLinearExit(
7478 7565
    checker: *mut LinearChecker,
7479 7566
    env: *mut LinearEnv,
7480 7567
) throws (ResolveError) {
7481 7568
    for i in 0..env.len {
7482 7569
        if linearBindingAvailable(env, i) {
7483 7570
            let sym = env.symbols[i] else panic "finishLinearExit: missing symbol";
7484 -
            throw emitError(
7485 -
                checker.resolver,
7486 -
                sym.node,
7487 -
                ErrorKind::LinearNotConsumed(sym.name),
7488 -
            );
7571 +
            let case SymbolData::Value { type: ty, .. } = sym.data
7572 +
                else panic "finishLinearExit: expected value symbol";
7573 +
            if isLinear(ty) {
7574 +
                throw emitError(
7575 +
                    checker.resolver,
7576 +
                    sym.node,
7577 +
                    ErrorKind::LinearNotConsumed(sym.name),
7578 +
                );
7579 +
            }
7489 7580
        }
7490 7581
    }
7491 7582
    set env.terminated = true;
7492 7583
}
7493 7584
7692 7783
            case ast::ProngArm::Binding(binding) => {
7693 7784
                try addLinearPatternBindings(checker, &mut branch, binding);
7694 7785
            }
7695 7786
            case ast::ProngArm::Else => {}
7696 7787
        }
7697 -
        if prong.guard <> nil and branch.len > bindingsStart {
7698 -
            throw emitError(checker.resolver, prongNode, ErrorKind::LinearDiscard);
7788 +
        if prong.guard <> nil {
7789 +
            for i in bindingsStart..branch.len {
7790 +
                let sym = branch.symbols[i] else panic "checkLinearMatch: missing symbol";
7791 +
                let case SymbolData::Value { type: ty, .. } = sym.data
7792 +
                    else panic "checkLinearMatch: expected value symbol";
7793 +
                if isLinear(ty) {
7794 +
                    throw emitError(checker.resolver, prongNode, ErrorKind::LinearDiscard);
7795 +
                }
7796 +
            }
7699 7797
        }
7700 7798
        if let guard = prong.guard {
7701 7799
            try checkLinearNode(checker, &mut branch, guard, LinearUse::Consume);
7702 7800
        }
7703 7801
        try checkLinearNode(checker, &mut branch, prong.body, LinearUse::Discard);
7720 7818
    env: *mut LinearEnv,
7721 7819
    node: *ast::Node,
7722 7820
    call: ast::Call,
7723 7821
) throws (ResolveError) {
7724 7822
    try checkLinearNode(checker, env, call.callee, LinearUse::Observe);
7725 -
    let calleeTy = typeFor(checker.resolver, call.callee) else {
7726 -
        throw emitError(checker.resolver, call.callee, ErrorKind::Internal);
7727 -
    };
7728 -
    let case Type::Fn(info) = calleeTy else {
7823 +
    let mut fnInfo: ?*FnType = nil;
7824 +
    match checker.resolver.nodeData.entries[node.id].extra {
7825 +
        case NodeExtra::TraitMethodCall { traitInfo, methodIndex } =>
7826 +
            set fnInfo = traitInfo.methods[methodIndex].fnType,
7827 +
        case NodeExtra::MethodCall { method } => set fnInfo = method.fnType,
7828 +
        else => {
7829 +
            if let calleeTy = typeFor(checker.resolver, call.callee) {
7830 +
                if let case Type::Fn(info) = calleeTy {
7831 +
                    set fnInfo = info;
7832 +
                }
7833 +
            }
7834 +
        }
7835 +
    }
7836 +
    let info = fnInfo else {
7729 7837
        for arg in call.args {
7730 7838
            try checkLinearNode(checker, env, arg, LinearUse::Consume);
7731 7839
        }
7732 7840
        return;
7733 7841
    };
7775 7883
    }
7776 7884
7777 7885
    for arg, i in call.args {
7778 7886
        let expected = *info.paramTypes[i];
7779 7887
        let root = linearRootSymbol(checker.resolver, arg);
7780 -
        let mut argExclusive = isLinear(expected);
7888 +
        let mut argExclusive = isMoveOnly(expected);
7781 7889
        if let case Type::Pointer { class: types::PointerClass::Ref, mutable, .. } = expected {
7782 7890
            set argExclusive = mutable;
7783 7891
        } else if let case Type::Slice { class: types::PointerClass::Ref, mutable, .. } = expected {
7784 7892
            set argExclusive = mutable;
7785 7893
        } else if let case Type::TraitObject {
7875 7983
            }
7876 7984
            try checkLinearNode(checker, env, expr, LinearUse::Consume);
7877 7985
        }
7878 7986
        case ast::NodeValue::Block(_) => try checkLinearBlock(checker, env, node),
7879 7987
        case ast::NodeValue::Let(binding) => {
7988 +
            let mut isUndefined = false;
7880 7989
            if let case ast::NodeValue::Undef = binding.value.value {
7990 +
                set isUndefined = true;
7991 +
            }
7992 +
            if isUndefined {
7881 7993
                if let bindingTy = typeFor(checker.resolver, binding.ident);
7882 7994
                    isLinear(bindingTy)
7883 7995
                {
7884 7996
                    throw emitError(
7885 7997
                        checker.resolver,
7888 8000
                    );
7889 8001
                }
7890 8002
            }
7891 8003
            try checkLinearNode(checker, env, binding.value, LinearUse::Consume);
7892 8004
            try addLinearBinding(checker, env, node);
8005 +
            if isUndefined {
8006 +
                markLinearBindingUnavailable(checker.resolver, env, node);
8007 +
            }
7893 8008
        }
7894 8009
        case ast::NodeValue::Assign(assign) => {
7895 8010
            let mut target: ?u32 = nil;
8011 +
            let mut targetLinear = false;
7896 8012
            if let leftTy = typeFor(checker.resolver, assign.left) {
7897 -
                if isLinear(leftTy) {
8013 +
                if isMoveOnly(leftTy) {
8014 +
                    set targetLinear = isLinear(leftTy);
7898 8015
                    if let case ast::NodeValue::Ident(_) = assign.left.value {
7899 8016
                        if let sym = symbolFor(checker.resolver, assign.left) {
7900 8017
                            set target = findLinearBinding(env, sym);
7901 8018
                        }
7902 8019
                    }
7910 8027
                }
7911 8028
            }
7912 8029
            try checkLinearNode(checker, env, assign.left, LinearUse::Place);
7913 8030
            try checkLinearNode(checker, env, assign.right, LinearUse::Consume);
7914 8031
            if let index = target {
7915 -
                if linearBindingAvailable(env, index) {
8032 +
                if targetLinear and linearBindingAvailable(env, index) {
7916 8033
                    throw emitError(
7917 8034
                        checker.resolver,
7918 8035
                        assign.left,
7919 8036
                        ErrorKind::LinearOverwrite,
7920 8037
                    );
7926 8043
        case ast::NodeValue::AddressOf(addr) => {
7927 8044
            try checkLinearNode(checker, env, addr.target, LinearUse::Borrow);
7928 8045
        }
7929 8046
        case ast::NodeValue::Deref(target) => {
7930 8047
            if let resultTy = typeFor(checker.resolver, node) {
7931 -
                if isLinear(resultTy) and usage == LinearUse::Consume {
8048 +
                if isMoveOnly(resultTy) and usage == LinearUse::Consume {
7932 8049
                    throw emitError(checker.resolver, node, ErrorKind::LinearPartialMove);
7933 8050
                }
7934 8051
            }
7935 8052
            try checkLinearNode(checker, env, target, LinearUse::Observe);
7936 8053
        }
7937 8054
        case ast::NodeValue::FieldAccess(access) => {
7938 8055
            if let resultTy = typeFor(checker.resolver, node) {
7939 -
                if isLinear(resultTy) and usage == LinearUse::Consume {
8056 +
                if isMoveOnly(resultTy) and usage == LinearUse::Consume {
7940 8057
                    throw emitError(checker.resolver, node, ErrorKind::LinearPartialMove);
7941 8058
                }
7942 8059
            }
7943 8060
            try checkLinearNode(checker, env, access.parent, LinearUse::Observe);
7944 8061
        }
7945 8062
        case ast::NodeValue::ScopeAccess(_) => {}
7946 8063
        case ast::NodeValue::Subscript { container, index } => {
7947 8064
            if let resultTy = typeFor(checker.resolver, node) {
7948 -
                if isLinear(resultTy) and usage == LinearUse::Consume {
8065 +
                if isMoveOnly(resultTy) and usage == LinearUse::Consume {
7949 8066
                    throw emitError(checker.resolver, node, ErrorKind::LinearPartialMove);
7950 8067
                }
7951 8068
            }
7952 8069
            try checkLinearNode(checker, env, container, LinearUse::Observe);
7953 8070
            try checkLinearNode(checker, env, index, LinearUse::Consume);
7964 8081
                try checkLinearNode(checker, env, item, LinearUse::Consume);
7965 8082
            }
7966 8083
        }
7967 8084
        case ast::NodeValue::ArrayRepeatLit(repeat) => {
7968 8085
            if let itemTy = typeFor(checker.resolver, repeat.item) {
7969 -
                if isLinear(itemTy) {
8086 +
                if not isCopy(itemTy) {
7970 8087
                    throw emitError(
7971 8088
                        checker.resolver,
7972 8089
                        repeat.item,
7973 8090
                        ErrorKind::LinearDiscard,
7974 8091
                    );
lib/std/lang/resolver/printer.rad +9 -0
494 494
        case super::ErrorKind::TraitMethodOverflow(m) =>
495 495
            printMismatch("too many trait methods", "maximum", m),
496 496
        case super::ErrorKind::MissingSupertraitInstance(name) => {
497 497
            printQuoted("missing instance for supertrait '", name);
498 498
        }
499 +
        case super::ErrorKind::AffineUseAfterMove(name) => {
500 +
            printQuoted("affine value used after move: '", name);
501 +
        }
499 502
        case super::ErrorKind::LinearUseAfterConsume(name) => {
500 503
            printQuoted("linear value used after consumption: '", name);
501 504
        }
502 505
        case super::ErrorKind::LinearNotConsumed(name) => {
503 506
            printQuoted("linear value is not consumed: '", name);
518 521
            io::print("assignment would overwrite a linear value");
519 522
        }
520 523
        case super::ErrorKind::LinearUndefined => {
521 524
            io::print("linear values cannot be undefined");
522 525
        }
526 +
        case super::ErrorKind::CopyContainsNonCopy => {
527 +
            io::print("`Copy` composite contains a non-copy value");
528 +
        }
529 +
        case super::ErrorKind::ConflictingOwnershipMarkers => {
530 +
            io::print("a composite cannot be both `Copy` and `Linear`");
531 +
        }
523 532
        case super::ErrorKind::InvalidRefPosition => {
524 533
            io::print("reference type is only allowed as a function parameter");
525 534
        }
526 535
        case super::ErrorKind::RefBinding => {
527 536
            io::print("references cannot be bound to locals");
lib/std/lang/resolver/tests.rad +57 -1
51 51
    "Second", "Opt", "x",
52 52
    "value", "idx"
53 53
];
54 54
55 55
/// Resolver result with AST, used by test helpers.
56 -
record TestResult {
56 +
record TestResult: Copy {
57 57
    diagnostics: super::Diagnostics,
58 58
    root: *ast::Node,
59 59
}
60 60
61 61
/// Create isolated storage for tests to avoid conflicts with global resolver storage.
279 279
        if let case super::ErrorKind::MissingSupertraitInstance(actualName) = *actual {
280 280
            return mem::eq(actualName, expectedName);
281 281
        }
282 282
        return false;
283 283
    }
284 +
    if let case super::ErrorKind::AffineUseAfterMove(expectedName) = expected {
285 +
        if let case super::ErrorKind::AffineUseAfterMove(actualName) = *actual {
286 +
            return mem::eq(actualName, expectedName);
287 +
        }
288 +
        return false;
289 +
    }
284 290
    if let case super::ErrorKind::LinearUseAfterConsume(expectedName) = expected {
285 291
        if let case super::ErrorKind::LinearUseAfterConsume(actualName) = *actual {
286 292
            return mem::eq(actualName, expectedName);
287 293
        }
288 294
        return false;
5238 5244
    let program = "record Value { number: i32 } trait Read { fn (&Read) get() -> i32; } instance Read for Value { fn (value: *Value) get() -> i32 { return value.number; } }";
5239 5245
    let result = try resolveProgramStr(&mut a, program);
5240 5246
    try expectErrorKind(&result, super::ErrorKind::TraitReceiverMismatch);
5241 5247
}
5242 5248
5249 +
/// Unmarked composite values may be discarded.
5250 +
@test fn testAffineCompositeMayBeDiscarded() throws (testing::TestError) {
5251 +
    let program = "record Value { number: u32 } fn run() { let value = Value { number: 1 }; }";
5252 +
    try expectAnalyzeOk(program);
5253 +
}
5254 +
5255 +
/// A by-value use moves an unmarked composite value.
5256 +
@test fn testAffineCompositeUseAfterMoveRejected() throws (testing::TestError) {
5257 +
    let mut a = testResolver();
5258 +
    let program = "record Value { number: u32 } fn take(value: Value) {} fn run() { let value = Value { number: 1 }; take(value); take(value); }";
5259 +
    let result = try resolveProgramStr(&mut a, program);
5260 +
    try expectErrorKind(&result, super::ErrorKind::AffineUseAfterMove("value"));
5261 +
}
5262 +
5263 +
/// Affine values may move on only one branch when not used later.
5264 +
@test fn testAffineConditionalMoveMayBeDiscarded() throws (testing::TestError) {
5265 +
    let program = "record Value { number: u32 } fn take(value: Value) {} fn run(condition: bool) { let value = Value { number: 1 }; if condition { take(value); } }";
5266 +
    try expectAnalyzeOk(program);
5267 +
}
5268 +
5269 +
/// A `Copy` composite remains available after a by-value use.
5270 +
@test fn testCopyCompositeMayBeReused() throws (testing::TestError) {
5271 +
    let program = "record Value: Copy { number: u32 } fn take(value: Value) {} fn run() { let value = Value { number: 1 }; take(value); take(value); }";
5272 +
    try expectAnalyzeOk(program);
5273 +
}
5274 +
5275 +
/// A `Copy` composite may contain only copy values.
5276 +
@test fn testCopyCompositeRejectsAffineField() throws (testing::TestError) {
5277 +
    let mut a = testResolver();
5278 +
    let program = "record Inner { number: u32 } record Outer: Copy { inner: Inner }";
5279 +
    let result = try resolveProgramStr(&mut a, program);
5280 +
    try expectErrorKind(&result, super::ErrorKind::CopyContainsNonCopy);
5281 +
}
5282 +
5283 +
/// A composite cannot carry conflicting ownership markers.
5284 +
@test fn testConflictingOwnershipMarkersRejected() throws (testing::TestError) {
5285 +
    let mut a = testResolver();
5286 +
    let program = "record Value: Copy + Linear { number: u32 }";
5287 +
    let result = try resolveProgramStr(&mut a, program);
5288 +
    try expectErrorKind(&result, super::ErrorKind::ConflictingOwnershipMarkers);
5289 +
}
5290 +
5291 +
/// Linear composites still require one consuming use.
5292 +
@test fn testLinearCompositeMustBeConsumed() throws (testing::TestError) {
5293 +
    let mut a = testResolver();
5294 +
    let program = "record Token: Linear { number: u32 } fn run() { let token = Token { number: 1 }; }";
5295 +
    let result = try resolveProgramStr(&mut a, program);
5296 +
    try expectErrorKind(&result, super::ErrorKind::LinearNotConsumed("token"));
5297 +
}
5298 +
5243 5299
/// The compiler-known marker cannot be derived more than once.
5244 5300
@test fn testDuplicateLinearMarkerRejected() throws (testing::TestError) {
5245 5301
    let mut a = testResolver();
5246 5302
    let program = "record Token: Linear + Linear { value: u32 }";
5247 5303
    let result = try resolveProgramStr(&mut a, program);
lib/std/lang/scanner.rad +6 -6
10 10
11 11
/// Token kinds representing all lexical elements in Radiance.
12 12
///
13 13
/// This enum covers operators, keywords, literals, and structural
14 14
/// elements used by the parser to build the AST.
15 -
export union TokenKind {
15 +
export union TokenKind: Copy {
16 16
    /// Special end of file token generated when the input is exhausted.
17 17
    Eof,
18 18
    /// Special invalid token.
19 19
    Invalid,
20 20
107 107
    I8, I16, I32, I64, U8, U16, U32, U64,
108 108
    Opaque, Fn, Bool, Union, Record, As, Unsafe
109 109
}
110 110
111 111
/// A reserved keyword.
112 -
record Keyword {
112 +
record Keyword: Copy {
113 113
    /// Keyword string.
114 114
    name: *[u8],
115 115
    /// Corresponding token.
116 116
    tok: TokenKind,
117 117
}
171 171
    { name: "use", tok: TokenKind::Use },
172 172
    { name: "while", tok: TokenKind::While },
173 173
];
174 174
175 175
/// Describes where source code originated from.
176 -
export union SourceLoc {
176 +
export union SourceLoc: Copy {
177 177
    /// Source loaded from a file at the given path.
178 178
    File(*[u8]),
179 179
    /// Source provided as an inline string (no file path).
180 180
    String,
181 181
}
182 182
183 183
/// Lexical scanner state for tokenizing Radiance source code.
184 184
///
185 185
/// Maintains position information and source buffer reference.
186 -
export record Scanner {
186 +
export record Scanner: Copy {
187 187
    /// Origin of the source being scanned.
188 188
    sourceLoc: SourceLoc,
189 189
    /// Source buffer.
190 190
    source: *[u8],
191 191
    /// Offset of current token into buffer.
198 198
199 199
/// Individual token with kind, source text, and position.
200 200
///
201 201
/// Represents a single lexical element extracted from source,
202 202
/// including its original text and byte offset for error reporting.
203 -
export record Token {
203 +
export record Token: Copy {
204 204
    /// Token kind.
205 205
    kind: TokenKind,
206 206
    /// Token source string.
207 207
    source: *[u8],
208 208
    /// Byte offset of `source` in input buffer.
210 210
}
211 211
212 212
/// Source code location with line/column information.
213 213
///
214 214
/// Used for error reporting and debugging.
215 -
export record Location {
215 +
export record Location: Copy {
216 216
    /// Origin of the source.
217 217
    source: SourceLoc,
218 218
    /// Line number.
219 219
    line: u16,
220 220
    /// Column number.
lib/std/lang/sexpr.rad +2 -2
5 5
6 6
use std::io;
7 7
use std::lang::alloc;
8 8
9 9
/// Output target for S-expression printing.
10 -
export union Output {
10 +
export union Output: Copy {
11 11
    /// Print to stdout.
12 12
    Stdout,
13 13
    /// Write to a buffer, tracking position.
14 14
    Buffer { buf: *mut [u8], pos: *mut u32 },
15 15
}
16 16
17 17
/// An S-expression element.
18 -
export union Expr {
18 +
export union Expr: Copy {
19 19
    /// An empty expression.
20 20
    Null,
21 21
    /// A symbol/identifier.
22 22
    Sym(*[u8]),
23 23
    /// A quoted string literal.
lib/std/lang/strings.rad +2 -2
14 14
15 15
/// String interning pool using open-addressed hash table.
16 16
///
17 17
/// Each unique string content is stored only once, allowing pointer equality
18 18
/// to be used instead of content comparison for symbol lookups and module names.
19 -
export record Pool {
19 +
export record Pool: Copy {
20 20
    /// Hash table slots.
21 21
    table: [*[u8]; TABLE_SIZE],
22 22
    /// Number of strings currently in the pool.
23 23
    count: u32,
24 24
}
25 25
26 26
/// Lookup result.
27 -
union Lookup {
27 +
union Lookup: Copy {
28 28
    /// Found.
29 29
    Found(*[u8]),
30 30
    /// Not found, contains empty slot index.
31 31
    Empty(u32),
32 32
}
lib/std/lang/types.rad +1 -1
1 1
//! Shared Radiance language types.
2 2
3 3
/// Ownership and safety class for pointer-like types.
4 -
export union PointerClass {
4 +
export union PointerClass: Copy {
5 5
    /// Owned pointer, eg. `*T`.
6 6
    Owned,
7 7
    /// Reference, borrowed pointer, eg. `&T`.
8 8
    Ref,
9 9
    /// Unsafe, raw pointer, eg. `*unsafe T`.
lib/std/mem.rad +1 -1
1 1
/// Memory error.
2 -
union MemoryError {
2 +
union MemoryError: Copy {
3 3
    /// Buffer is too small.
4 4
    BufferTooSmall,
5 5
}
6 6
7 7
/// Copy bytes between two slices. Returns the number of bytes copied.
lib/std/sys.rad +1 -1
1 1
//! System utilities.
2 2
export mod unix;
3 3
4 4
/// Process environment passed to the program entry point.
5 -
export record Env {
5 +
export record Env: Copy {
6 6
    /// Command-line arguments.
7 7
    args: *[*[u8]],
8 8
}
lib/std/sys/unix.rad +1 -1
1 1
//! Unix-specific system calls and utilities.
2 2
use std::intrinsics;
3 3
4 4
/// File access modes.
5 -
export record OpenFlags(i64);
5 +
export record OpenFlags: Copy(i64);
6 6
7 7
/// Open file for reading only.
8 8
export constant O_RDONLY: OpenFlags = OpenFlags(0);
9 9
10 10
/// Open file for writing only.
lib/std/testing.rad +3 -3
2 2
3 3
use std::mem;
4 4
use std::io;
5 5
6 6
/// Internal state for tracking test pass/fail counts.
7 -
record Ctx {
7 +
record Ctx: Copy {
8 8
    passed: u32,
9 9
    failed: u32,
10 10
}
11 11
12 12
/// Error thrown when a test assertion fails.
13 -
export union TestError {
13 +
export union TestError: Copy {
14 14
    Failed,
15 15
}
16 16
17 17
/// Descriptor for a single test case, holding its module path, name, and entry point.
18 -
export record TestInfo {
18 +
export record TestInfo: Copy {
19 19
    module: *[u8],
20 20
    name: *[u8],
21 21
    func: fn() throws (TestError),
22 22
}
23 23
lib/std/tests.rad +1 -1
6 6
use std::testing;
7 7
use std::sys::unix;
8 8
9 9
// Data types //////////////////////////////////////////////////////////////////
10 10
11 -
record Point {
11 +
record Point: Copy {
12 12
    x: i32,
13 13
    y: i32,
14 14
}
15 15
16 16
// fmt /////////////////////////////////////////////////////////////////////////
lib/std/vec.rad +1 -1
5 5
//! to the element type's requirements.
6 6
7 7
/// Raw vector metadata structure.
8 8
///
9 9
/// Does not own storage, points to user-provided arena.
10 -
export record RawVec {
10 +
export record RawVec: Copy {
11 11
    /// Pointer to user-provided byte arena.
12 12
    data: *mut [u8],
13 13
    /// Current number of elements stored.
14 14
    len: u32,
15 15
    /// Size of each element in bytes (stride between elements).
seed/radiance.rv64 +0 -0

Binary file changed.

seed/radiance.rv64.git +1 -1
1 -
d024ed30c27cc671d9371988d0155b3e08ff004582cf59f1b7eac8ffdf3e3467
1 +
1e2fdea952350036a4172f567442e608555094b9e18fa69f1fb615453d5352b6
test/tests/abi.sizes.rad +5 -5
1 1
//! returns: 0
2 2
//! Test size-based ABI: small aggregates (<= 8 bytes) are passed/returned
3 3
//! by value in registers, while larger aggregates use hidden pointers.
4 4
5 -
record Byte {
5 +
record Byte: Copy {
6 6
    x: u8,
7 7
}
8 8
9 -
record Short {
9 +
record Short: Copy {
10 10
    x: u16,
11 11
}
12 12
13 -
record Word {
13 +
record Word: Copy {
14 14
    x: u32,
15 15
}
16 16
17 -
record Pair {
17 +
record Pair: Copy {
18 18
    a: u32,
19 19
    b: u32,
20 20
}
21 21
22 -
record Triple {
22 +
record Triple: Copy {
23 23
    x: u32,
24 24
    y: u32,
25 25
    z: u32,
26 26
}
27 27
test/tests/aggregate.return.rad +2 -2
1 1
//! returns: 0
2 2
//! Test that returning aggregate types via hidden return buffer does not
3 3
//! corrupt the caller's stack. Modifying a returned aggregate must not affect
4 4
//! other locals or previously returned values.
5 5
6 -
record Pair {
6 +
record Pair: Copy {
7 7
    a: u32,
8 8
    b: u32,
9 9
}
10 10
11 -
record Triple {
11 +
record Triple: Copy {
12 12
    x: u32,
13 13
    y: u32,
14 14
    z: u32,
15 15
}
16 16
test/tests/array.aggregate.stride.rad +1 -1
1 1
/// Tests for correct stride calculation with aggregate array elements.
2 2
/// This is important because aggregate types (records, arrays) are larger
3 3
/// than a single word and require correct stride (not just 4 bytes).
4 4
5 -
record Point { x: i32, y: i32 }
5 +
record Point: Copy { x: i32, y: i32 }
6 6
7 7
/// Array of records (8 bytes per element).
8 8
fn arrayOfRecords(arr: [Point; 3], idx: u32) -> i32 {
9 9
    return arr[idx].x;
10 10
}
test/tests/array.record.elements.rad +1 -1
1 1
//! returns: 50
2 2
//! Test arrays with record elements.
3 3
4 -
record Point {
4 +
record Point: Copy {
5 5
    x: i32,
6 6
    y: i32,
7 7
}
8 8
9 9
fn calculateDistance(p1: Point, p2: Point) -> i32 {
test/tests/assign.mutable.rad +2 -2
1 1
//! returns: 0
2 2
//! Test suite for stack memory allocation and aliasing behavior.
3 3
//! Tests variable aliasing, mutations, and stack frame management.
4 4
5 -
record Person {
5 +
record Person: Copy {
6 6
    age: i32,
7 7
    score: i32,
8 8
}
9 9
10 -
record Container {
10 +
record Container: Copy {
11 11
    values: [i32; 3],
12 12
    count: i32,
13 13
}
14 14
15 15
/// Basic variable aliasing and mutation.
test/tests/bool.comparison.nested.gen.rad +2 -2
1 1
//! returns: 0
2 2
//! Test nested record equality.
3 3
4 -
record Point {
4 +
record Point: Copy {
5 5
    x: i32,
6 6
    y: i32,
7 7
}
8 8
9 -
record Circle {
9 +
record Circle: Copy {
10 10
    center: Point,
11 11
    radius: u32,
12 12
}
13 13
14 14
fn testNestedEqual() -> bool {
test/tests/bool.comparison.record.gen.rad +2 -2
1 1
//! returns: 0
2 2
3 -
record Point {
3 +
record Point: Copy {
4 4
    x: i32,
5 5
    y: i32,
6 6
}
7 7
8 -
record Rect {
8 +
record Rect: Copy {
9 9
    width: u32,
10 10
    height: u32,
11 11
}
12 12
13 13
fn testPointEqual() -> bool {
test/tests/bool.comparison.record.rad +2 -2
1 1
//! returns: 1
2 2
3 3
// A packed record. 8 bytes.
4 -
record Packed {
4 +
record Packed: Copy {
5 5
    x: i32,
6 6
    y: i32
7 7
}
8 8
9 9
// A record with padding. 17 bytes.
10 -
record Padded {
10 +
record Padded: Copy {
11 11
    a: u8,
12 12
    b: u32,
13 13
    c: u16,
14 14
    d: u32,
15 15
    e: u8,
test/tests/bool.comparison.slice.record.gen.rad +2 -2
1 1
//! returns: 0
2 2
3 -
record Point {
3 +
record Point: Copy {
4 4
    x: i32,
5 5
    y: i32,
6 6
}
7 7
8 -
record Line {
8 +
record Line: Copy {
9 9
    points: *[Point],
10 10
}
11 11
12 12
fn pointsEqual(a: *[Point], b: *[Point]) -> bool {
13 13
    if a.len <> b.len {
test/tests/bool.comparison.slice.union.gen.rad +3 -3
1 1
//! returns: 0
2 2
3 -
record Point {
3 +
record Point: Copy {
4 4
    x: i32,
5 5
    y: i32,
6 6
}
7 7
8 -
record Size {
8 +
record Size: Copy {
9 9
    width: u32,
10 10
    height: u32,
11 11
}
12 12
13 -
union Shape {
13 +
union Shape: Copy {
14 14
    points(*[Point]),
15 15
    sizes(*[Size]),
16 16
}
17 17
18 18
fn pointsEqual(a: *[Point], b: *[Point]) -> bool {
test/tests/bool.comparison.union.ctor.rad +1 -1
1 1
//! returns: 0
2 2
//! Test comparing a void union variable against a constructor literal.
3 3
4 -
union Op {
4 +
union Op: Copy {
5 5
    Add,
6 6
    Sub,
7 7
    Mul,
8 8
}
9 9
test/tests/bool.comparison.union.gen.rad +1 -1
1 1
//! returns: 0
2 2
3 -
union Status {
3 +
union Status: Copy {
4 4
    pending,
5 5
    running(u32),
6 6
    completed(bool),
7 7
}
8 8
test/tests/bool.comparison.union.record.gen.rad +3 -3
1 1
//! returns: 0
2 2
3 -
record Point {
3 +
record Point: Copy {
4 4
    x: i32,
5 5
    y: i32,
6 6
}
7 7
8 -
record Size {
8 +
record Size: Copy {
9 9
    width: u32,
10 10
    height: u32,
11 11
}
12 12
13 -
union Shape {
13 +
union Shape: Copy {
14 14
    circle(u32),
15 15
    point(Point),
16 16
    rect(Size),
17 17
}
18 18
test/tests/bool.comparison.union.simple.gen.rad +1 -1
1 1
//! returns: 0
2 2
//! Test equality on simple enum without payloads.
3 -
union Color {
3 +
union Color: Copy {
4 4
    red,
5 5
    green,
6 6
    blue,
7 7
}
8 8
test/tests/builtin.alignof.rad +1 -1
1 1
/// A simple record with mixed field sizes for alignment testing.
2 -
record Mixed {
2 +
record Mixed: Copy {
3 3
    a: u8,
4 4
    b: u32,
5 5
}
6 6
7 7
/// Returns the alignment of a u8.
test/tests/builtin.size.align.rad +1 -1
1 1
//! returns: 0
2 -
record Foo {
2 +
record Foo: Copy {
3 3
    x: u8,
4 4
    y: u32,
5 5
}
6 6
7 7
@default fn main() -> u8 {
test/tests/builtin.sizeof.rad +1 -1
1 1
/// A simple record with mixed field sizes for alignment testing.
2 -
record Mixed {
2 +
record Mixed: Copy {
3 3
    a: u8,
4 4
    b: u32,
5 5
}
6 6
7 7
/// Returns the size of a u8.
test/tests/call.aggregate.arg.snapshot.rad +1 -1
1 1
//! returns: 0
2 2
//! Aggregate call arguments must capture their value when evaluated, before
3 3
//! later arguments run and mutate the source object.
4 4
5 -
record Pair {
5 +
record Pair: Copy {
6 6
    x: i32,
7 7
    y: i32,
8 8
}
9 9
10 10
fn mutate(pair: *mut Pair) -> i32 {
test/tests/compound.assign.field.rad +1 -1
1 1
//! returns: 0
2 2
/// Test compound assignment on record fields.
3 -
record Point {
3 +
record Point: Copy {
4 4
    x: i32,
5 5
    y: i32,
6 6
}
7 7
8 8
@default fn main() -> i32 {
test/tests/cond.expr.aggregate.rad +1 -1
1 1
//! returns: 0
2 2
//! Test conditional expression with aggregate types (tagged unions, optionals).
3 3
4 -
union Val {
4 +
union Val: Copy {
5 5
    Reg(u32),
6 6
    Imm(i64),
7 7
}
8 8
9 9
fn pickVal(flag: bool, reg: u32) -> Val {
test/tests/cond.expr.rad +1 -1
7 7
8 8
fn pick(cond: bool, a: i32, b: i32) -> i32 {
9 9
    return a if cond else b;
10 10
}
11 11
12 -
union Color { Red, Green, Blue }
12 +
union Color: Copy { Red, Green, Blue }
13 13
14 14
fn colorPick(isRed: bool) -> Color {
15 15
    return Color::Red if isRed else Color::Blue;
16 16
}
17 17
test/tests/cond.if.case.rad +1 -1
1 1
//! returns: 1
2 2
//! Test if-let-case with union variants.
3 3
4 -
union Response {
4 +
union Response: Copy {
5 5
    success(i32),
6 6
    failure,
7 7
    offline,
8 8
}
9 9
test/tests/cond.match.guard.rad +1 -1
1 1
//! returns: 0
2 2
3 -
union OptionNum {
3 +
union OptionNum: Copy {
4 4
    Some(i32),
5 5
    Other(i32),
6 6
    None,
7 7
}
8 8
test/tests/cond.match.guard.regalloc.rad +2 -2
5 5
//! `case Kind::Name(name) if name.len > 0` creates a block ordering where
6 6
//! the case body has a lower index than its predecessor (the guard block).
7 7
//! Without RPO-based register allocation, the name payload pointer can be
8 8
//! overwritten by the bounds check in the case body.
9 9
10 -
union Kind {
10 +
union Kind: Copy {
11 11
    Num(i32),
12 12
    Name(*[u8]),
13 13
    Pair { a: i32, b: i32 },
14 14
}
15 15
16 -
record Node {
16 +
record Node: Copy {
17 17
    id: u32,
18 18
    span1: u32,
19 19
    span2: u32,
20 20
    kind: Kind,
21 21
}
test/tests/const.array.record.ident.rad +1 -1
1 1
/// Test constant array containing references to other record constants.
2 2
/// This tests that identifiers referencing record constants work in constant arrays.
3 3
4 -
record Point {
4 +
record Point: Copy {
5 5
    x: i32,
6 6
    y: i32,
7 7
}
8 8
9 9
constant P1: Point = Point { x: 1, y: 2 };
test/tests/const.array.repeat.record.rad +1 -1
1 1
/// Test constant array repeat with record elements.
2 -
record Point { x: i32, y: i32 }
2 +
record Point: Copy { x: i32, y: i32 }
3 3
4 4
constant POINTS: [Point; 3] = [Point { x: 1, y: 2 }; 3];
5 5
6 6
fn getSum() -> i32 {
7 7
    return POINTS[0].x + POINTS[1].y + POINTS[2].x;
test/tests/const.record.array.rad +1 -1
1 1
//! returns: 129
2 2
//! Test array of record constants.
3 3
4 -
record Point {
4 +
record Point: Copy {
5 5
    x: i32,
6 6
    y: i32,
7 7
}
8 8
9 9
// A constant array of structs
test/tests/const.record.array.simple.rad +1 -1
1 1
//! returns: 0
2 2
// Test array of record constants.
3 3
4 -
record Point {
4 +
record Point: Copy {
5 5
    x: i32,
6 6
    y: i32,
7 7
}
8 8
9 9
// Define an array of record constants
test/tests/const.record.ctor.rad +1 -1
1 1
//! returns: 0
2 2
//! Constant unlabeled record constructor.
3 -
record Pair(i32, i32);
3 +
record Pair: Copy(i32, i32);
4 4
5 5
constant P: Pair = Pair(40, 2);
6 6
7 7
@default fn main() -> i32 {
8 8
    assert P == Pair(40, 2);
test/tests/const.record.fn.rad +1 -1
1 1
//! returns: 7
2 2
//! Test record constants passed to functions.
3 3
4 -
record Point {
4 +
record Point: Copy {
5 5
    x: i32,
6 6
    y: i32,
7 7
}
8 8
9 9
constant ORIGIN: Point = Point { x: 3, y: 4 };
test/tests/const.record.mutcopy.rad +1 -1
1 1
/// Test that accessing an aggregate constant produces a mutable copy
2 2
/// (via Reserve + Blit) so that code can safely assign through the pointer.
3 3
4 -
record Reg { n: u8 }
4 +
record Reg: Copy { n: u8 }
5 5
6 6
constant SCRATCH1: Reg = { n: 30 };
7 7
constant SCRATCH2: Reg = { n: 31 };
8 8
9 9
/// Returns a scratch register, avoiding the one already used by `rs`.
test/tests/const.record.nested.rad +2 -2
1 1
/// Test constant nested record lowering with padding.
2 -
record Inner {
2 +
record Inner: Copy {
3 3
    a: u8,
4 4
    b: i32,
5 5
}
6 6
7 -
record Outer {
7 +
record Outer: Copy {
8 8
    x: u8,
9 9
    inner: Inner,
10 10
    y: u8,
11 11
}
12 12
test/tests/const.record.packed.rad +1 -1
1 1
/// Test constant record with multiple sub-word fields.
2 -
record Packed {
2 +
record Packed: Copy {
3 3
    a: u8,
4 4
    b: u8,
5 5
    c: u8,
6 6
}
7 7
test/tests/const.record.padded.rad +1 -1
1 1
/// Test constant record with padding between fields.
2 -
record Padded {
2 +
record Padded: Copy {
3 3
    a: u8,
4 4
    b: i32,
5 5
    c: u8,
6 6
}
7 7
test/tests/const.record.rad +1 -1
1 1
//! returns: 0
2 2
// Test record constants.
3 3
4 -
record Point {
4 +
record Point: Copy {
5 5
    x: i32,
6 6
    y: i32,
7 7
}
8 8
9 9
constant ORIGIN: Point = Point { x: 3, y: 4 };
test/tests/const.record.union.rad +2 -2
1 1
/// Test constant array of records containing union variant fields.
2 2
/// This is similar to scanner.rad's KEYWORDS constant.
3 -
union TokenKind {
3 +
union TokenKind: Copy {
4 4
    Fn,
5 5
    Let,
6 6
    If,
7 7
}
8 8
9 -
record Keyword {
9 +
record Keyword: Copy {
10 10
    name: *[u8],
11 11
    tok: TokenKind,
12 12
}
13 13
14 14
constant KEYWORDS: [Keyword; 3] = [
test/tests/const.union.payload.ctor.rad +1 -1
1 1
//! returns: 0
2 2
//! Constant union payload constructor call.
3 -
union Value {
3 +
union Value: Copy {
4 4
    Int(i32),
5 5
    Bool(bool),
6 6
    None,
7 7
}
8 8
test/tests/const.union.payload.fn.array.rad +3 -3
1 1
//! returns: 0
2 2
//! Constant array of records containing union variants with throwing function payloads.
3 3
4 -
union TestError {
4 +
union TestError: Copy {
5 5
    Fail,
6 6
}
7 7
8 8
fn add(a: i32, b: i32) -> i32 throws (TestError) {
9 9
    return a + b;
11 11
12 12
fn sub(a: i32, b: i32) -> i32 throws (TestError) {
13 13
    return a - b;
14 14
}
15 15
16 -
union Handler {
16 +
union Handler: Copy {
17 17
    Binary { op: fn(i32, i32) -> i32 throws (TestError) },
18 18
    None,
19 19
}
20 20
21 -
record Entry {
21 +
record Entry: Copy {
22 22
    handler: Handler,
23 23
}
24 24
25 25
constant ENTRIES: [Entry; 3] = [
26 26
    Entry { handler: Handler::None },
test/tests/const.union.payload.record.array.rad +2 -2
1 1
//! returns: 0
2 2
//! Constant array of records containing union variants with record payloads.
3 3
4 -
union InstrSpec {
4 +
union InstrSpec: Copy {
5 5
    Empty,
6 6
    Small { flag: bool },
7 7
    Imm { opcode: u32, funct3: u32, imm: i32 },
8 8
    Reg { opcode: u32, funct3: u32, funct7: u32 },
9 9
}
10 10
11 -
record Entry {
11 +
record Entry: Copy {
12 12
    name: *[u8],
13 13
    spec: InstrSpec,
14 14
}
15 15
16 16
constant ENTRIES: [Entry; 4] = [
test/tests/const.union.record.literal.rad +1 -1
1 1
//! returns: 0
2 2
//! Constant union record payload literal.
3 -
union Expr {
3 +
union Expr: Copy {
4 4
    Nil,
5 5
    Pair { first: i32, second: i32 },
6 6
}
7 7
8 8
constant E: Expr = Expr::Pair { first: 20, second: 22 };
test/tests/data.record.rad +2 -2
1 1
//! returns: 0
2 2
//! Test read-only data access for `record` types.
3 -
record Point {
3 +
record Point: Copy {
4 4
    x: i32,
5 5
    y: i32,
6 6
}
7 7
8 -
record Mixed {
8 +
record Mixed: Copy {
9 9
    a: u8,
10 10
    b: u16,
11 11
    c: u32,
12 12
    d: bool,
13 13
}
test/tests/data.union.rad +2 -2
1 1
//! returns: 0
2 2
//! Test read-only data access for `union` types.
3 -
union Color {
3 +
union Color: Copy {
4 4
    Red,
5 5
    Green,
6 6
    Blue,
7 7
}
8 8
9 -
union Value {
9 +
union Value: Copy {
10 10
    None,
11 11
    Small,
12 12
    Large,
13 13
}
14 14
test/tests/debug.tag.rad +1 -1
1 1
//! returns: 0
2 2
3 -
union Huge {
3 +
union Huge: Copy {
4 4
    Small(u32),
5 5
    Large {
6 6
        payload: [u32; 32],
7 7
        flag: u32,
8 8
    },
test/tests/edge.cases.2.rad +1 -1
1 1
//! returns: 0
2 2
//! Test scanner peek with optional return.
3 3
4 -
export record Scanner {
4 +
export record Scanner: Copy {
5 5
    source: *[u8],
6 6
}
7 7
8 8
fn peek(s: *Scanner) -> ?u8 {
9 9
    if 0 + 0 >= s.source.len {
test/tests/edge.cases.3.rad +1 -1
1 1
//! returns: 97
2 2
//! Test scanner current character access.
3 3
4 -
export record Scanner {
4 +
export record Scanner: Copy {
5 5
    source: *[u8],
6 6
    cursor: u32,
7 7
}
8 8
9 9
fn isEof(s: *Scanner) -> bool {
test/tests/edge.cases.4.rad +4 -4
1 1
//! returns: 0
2 2
3 -
union Signedness {
3 +
union Signedness: Copy {
4 4
    Signed,
5 5
    Unsigned,
6 6
}
7 7
8 -
union TypeSig {
8 +
union TypeSig: Copy {
9 9
    Integer {
10 10
        width: u8,
11 11
        sign: Signedness,
12 12
    },
13 13
}
14 14
15 -
union NodeValue {
15 +
union NodeValue: Copy {
16 16
    TypeSig(TypeSig),
17 17
}
18 18
19 -
record Node {
19 +
record Node: Copy {
20 20
    value: NodeValue,
21 21
}
22 22
23 23
fn node(nodes: *mut Node, count: *mut u32, value: NodeValue) -> *Node {
24 24
    let index = *count;
test/tests/edge.cases.5.rad +9 -9
1 1
//! returns: 0
2 2
3 -
union TestError {
3 +
union TestError: Copy {
4 4
    Fail,
5 5
}
6 6
7 -
record Derives {
7 +
record Derives: Copy {
8 8
    len: i32,
9 9
    list: [[u8; 8]; 2],
10 10
}
11 11
12 -
record StructField {
12 +
record StructField: Copy {
13 13
    typeSize: i32,
14 14
    isSigned: bool,
15 15
    value: ?i32,
16 16
}
17 17
18 -
record FieldNode {
18 +
record FieldNode: Copy {
19 19
    value: FieldValue,
20 20
}
21 21
22 -
record FieldList {
22 +
record FieldList: Copy {
23 23
    len: i32,
24 24
    list: [FieldNode; 2],
25 25
}
26 26
27 -
record StructDecl {
27 +
record StructDecl: Copy {
28 28
    derives: Derives,
29 29
    fields: FieldList,
30 30
}
31 31
32 -
record Node {
32 +
record Node: Copy {
33 33
    value: NodeValue,
34 34
}
35 35
36 -
union FieldValue {
36 +
union FieldValue: Copy {
37 37
    StructField(StructField),
38 38
    Other(i32),
39 39
}
40 40
41 -
union NodeValue {
41 +
union NodeValue: Copy {
42 42
    StructDecl(StructDecl),
43 43
    Other(i32),
44 44
}
45 45
46 46
fn expectIntType(a: i32, b: i32, c: bool, d: bool) throws (TestError) {}
test/tests/edge.cases.6.rad +2 -2
1 1
//! returns: 0
2 2
//! Standalone repro: writing a record into a static slice clobbers the slice header.
3 3
4 -
record Entry {
4 +
record Entry: Copy {
5 5
    a: u32,
6 6
    b: u32,
7 7
    c: u32,
8 8
    d: u32,
9 9
    e: u32,
22 22
    r: u32,
23 23
    s: u32,
24 24
    t: u32,
25 25
}
26 26
27 -
record Analyzer {
27 +
record Analyzer: Copy {
28 28
    pad0: u32,
29 29
    pad1: u32,
30 30
    entries: *mut [Entry],
31 31
    len: u32,
32 32
}
test/tests/edge.cases.7.addr.bug.rad +1 -1
1 1
//! returns: 42
2 2
3 -
record PtrHolder {
3 +
record PtrHolder: Copy {
4 4
    ptr: *mut i32,
5 5
}
6 6
7 7
static target: i32 = 10;
8 8
static holder: PtrHolder = undefined;
test/tests/edge.cases.8.bug.rad +2 -2
1 1
//! returns: 0
2 2
//! When a nested record field from a static variable is passed by value,
3 3
//! we only the base address and ignores the offset,
4 4
//! causing the function to receive the wrong data.
5 5
6 -
record Inner {
6 +
record Inner: Copy {
7 7
    value: i32,
8 8
}
9 9
10 -
record Outer {
10 +
record Outer: Copy {
11 11
    padding: i32,
12 12
    inner: Inner,
13 13
}
14 14
15 15
static global: Outer = undefined;
test/tests/edge.cases.rad +1 -1
1 1
//! returns: 0
2 2
3 -
export record Scanner {
3 +
export record Scanner: Copy {
4 4
    source: *[u8],
5 5
}
6 6
7 7
fn peek(s: *Scanner)  -> i32 {
8 8
    assert 0 + 0 <= s.source.len;
test/tests/error.basic.rad +1 -1
1 1
//! returns: 0
2 2
3 -
union MyError { A, B, C }
3 +
union MyError: Copy { A, B, C }
4 4
5 5
@default fn main() -> u32 {
6 6
    let err: MyError = MyError::B;
7 7
8 8
    if err == MyError::B {
test/tests/error.catch.rad +1 -1
1 1
//! returns: 0
2 2
3 -
union TestError { Fail }
3 +
union TestError: Copy { Fail }
4 4
static PTR_VALUE: i32 = 7;
5 5
6 6
@default fn main() -> u32 {
7 7
    // Catch block with early return
8 8
    let val1: u32 = catchWithReturn(true);
test/tests/error.catch.return.rad +1 -1
1 1
/// Error type for fallible functions.
2 -
union Error { Boom }
2 +
union Error: Copy { Boom }
3 3
4 4
/// Return a value or throw an error based on the flag.
5 5
fn fallible(flag: bool) -> i32 throws (Error) {
6 6
    if flag {
7 7
        throw Error::Boom();
test/tests/error.multi.basic.rad +2 -2
1 1
//! returns: 0
2 2
//! Test basic multi-error throw and catch.
3 3
4 -
union ErrA { A }
5 -
union ErrB { B }
4 +
union ErrA: Copy { A }
5 +
union ErrB: Copy { B }
6 6
7 7
fn failA() -> i32 throws (ErrA, ErrB) {
8 8
    throw ErrA::A();
9 9
}
10 10
test/tests/error.multi.catch.rad +2 -2
1 1
//! returns: 0
2 2
//! Test catch {} (no binding) with multi-error callee.
3 3
4 -
union ErrA { A }
5 -
union ErrB { B }
4 +
union ErrA: Copy { A }
5 +
union ErrB: Copy { B }
6 6
7 7
fn failA() -> i32 throws (ErrA, ErrB) {
8 8
    throw ErrA::A();
9 9
}
10 10
test/tests/error.multi.catch.typed.binding.rad +2 -2
1 1
//! returns: 0
2 2
//! Test typed multi-catch with payload extraction via bindings.
3 3
4 -
union ErrA { A(i32) }
5 -
union ErrB { B(i32) }
4 +
union ErrA: Copy { A(i32) }
5 +
union ErrB: Copy { B(i32) }
6 6
7 7
fn failA(v: i32) -> i32 throws (ErrA, ErrB) {
8 8
    throw ErrA::A(v);
9 9
}
10 10
test/tests/error.multi.catch.typed.catchall.rad +3 -3
1 1
//! returns: 0
2 2
//! Test typed catch with a catch-all fallback.
3 3
4 -
union ErrA { A(i32) }
5 -
union ErrB { B(i32) }
6 -
union ErrC { C }
4 +
union ErrA: Copy { A(i32) }
5 +
union ErrB: Copy { B(i32) }
6 +
union ErrC: Copy { C }
7 7
8 8
fn failA() -> i32 throws (ErrA, ErrB, ErrC) {
9 9
    throw ErrA::A(10);
10 10
}
11 11
test/tests/error.multi.catch.typed.rad +2 -2
1 1
//! returns: 0
2 2
//! Test typed multi-catch: catch e as ErrA {} catch e as ErrB {}.
3 3
4 -
union ErrA { A(i32) }
5 -
union ErrB { B(i32) }
4 +
union ErrA: Copy { A(i32) }
5 +
union ErrB: Copy { B(i32) }
6 6
7 7
fn failA() -> i32 throws (ErrA, ErrB) {
8 8
    throw ErrA::A(10);
9 9
}
10 10
test/tests/error.multi.propagate.multi.rad +2 -2
1 1
//! returns: 0
2 2
//! Test propagating a multi-throw callee via plain `try`.
3 3
4 -
union ErrA { A }
5 -
union ErrB { B }
4 +
union ErrA: Copy { A }
5 +
union ErrB: Copy { B }
6 6
7 7
fn inner(flag: i32) -> i32 throws (ErrA, ErrB) {
8 8
    if flag == 1 {
9 9
        throw ErrA::A();
10 10
    }
test/tests/error.multi.propagate.rad +2 -2
1 1
//! returns: 0
2 2
//! Test error propagation with multi-error and subset throw lists.
3 3
4 -
union ErrA { A }
5 -
union ErrB { B }
4 +
union ErrA: Copy { A }
5 +
union ErrB: Copy { B }
6 6
7 7
fn innerA() -> i32 throws (ErrA) {
8 8
    throw ErrA::A();
9 9
}
10 10
test/tests/error.multi.try.optional.rad +2 -2
1 1
//! returns: 0
2 2
//! Test try? with multi-error callee.
3 3
4 -
union ErrA { A }
5 -
union ErrB { B }
4 +
union ErrA: Copy { A }
5 +
union ErrB: Copy { B }
6 6
7 7
fn failA() -> i32 throws (ErrA, ErrB) {
8 8
    throw ErrA::A();
9 9
}
10 10
test/tests/error.try.bang.success.rad +1 -1
1 1
//! returns: 0
2 2
3 -
union PanicError { Boom }
3 +
union PanicError: Copy { Boom }
4 4
5 5
fn makeOk(value: u32) -> u32 throws (PanicError) {
6 6
    return value;
7 7
}
8 8
test/tests/error.try.catch.binding.rad +1 -1
1 1
//! returns: 0
2 2
3 -
union TestError { Boom, Bust }
3 +
union TestError: Copy { Boom, Bust }
4 4
5 5
@default fn main() -> u32 {
6 6
    // Test catching and using the error value.
7 7
    let mut caught: u32 = 0;
8 8
    try failWithBoom() catch e {
test/tests/error.try.optional.rad +2 -2
1 1
//! returns: 0
2 2
//! Test try? expressions converting errors to optionals.
3 3
4 -
union TestError { Boom, Bust }
4 +
union TestError: Copy { Boom, Bust }
5 5
6 6
fn returnsOk() -> u32 throws (TestError) {
7 7
    return 42;
8 8
}
9 9
10 10
fn returnsErr() -> u32 throws (TestError) {
11 11
    throw TestError::Boom;
12 12
}
13 13
14 -
record Point {
14 +
record Point: Copy {
15 15
    x: u32,
16 16
    y: u32,
17 17
}
18 18
19 19
fn returnsOkPoint() -> Point throws (TestError) {
test/tests/error.try.rad +2 -2
1 1
//! returns: 0
2 2
//! Test result-based error handling with try/throw.
3 3
4 -
union TestError { Boom, Bust }
4 +
union TestError: Copy { Boom, Bust }
5 5
6 -
record ResultSink {
6 +
record ResultSink: Copy {
7 7
    last: u32,
8 8
    count: u32,
9 9
}
10 10
11 11
fn returnsOk() -> u32 throws (TestError) {
test/tests/field.aggregate.rad +5 -5
1 1
/// Test field access where the field is an aggregate type.
2 2
/// Tests record, union, and slice fields.
3 3
4 -
record Inner { x: i32, y: i32 }
5 -
union MaybeInt { None, Some(i32) }
4 +
record Inner: Copy { x: i32, y: i32 }
5 +
union MaybeInt: Copy { None, Some(i32) }
6 6
7 -
record HasRecord { inner: Inner, z: i32 }
8 -
record HasUnion { maybe: MaybeInt, z: i32 }
9 -
record HasSlice { data: *[i32], z: i32 }
7 +
record HasRecord: Copy { inner: Inner, z: i32 }
8 +
record HasUnion: Copy { maybe: MaybeInt, z: i32 }
9 +
record HasSlice: Copy { data: *[i32], z: i32 }
10 10
11 11
/// Access a record field and read from it.
12 12
fn accessRecordField() -> i32 {
13 13
    let r = HasRecord { inner: Inner { x: 10, y: 20 }, z: 30 };
14 14
    return r.inner.y;
test/tests/fn.callback.nested.rad +2 -2
1 1
//! Test for callback with nested function call inside.
2 2
//! This exercises the register save/restore logic when a callback makes
3 3
//! a function call that uses caller-saved registers.
4 4
//! returns: 0
5 5
6 -
record Reg { n: u32 }
6 +
record Reg: Copy { n: u32 }
7 7
8 -
union Val {
8 +
union Val: Copy {
9 9
    Reg(Reg),
10 10
    Imm(i32),
11 11
}
12 12
13 13
fn maxRegNum(n: u32, current: u32) -> u32 {
test/tests/for.else.continue.rad +1 -1
1 1
//! returns: 0
2 2
//! Test `else continue` in a for loop with let-else.
3 3
4 -
record Field {
4 +
record Field: Copy {
5 5
    name: ?*[u8],
6 6
    value: i32,
7 7
}
8 8
9 9
fn findField(fields: *[Field], target: *[u8]) -> ?i32 {
test/tests/large.blit.store.rad +1 -1
11 11
//!
12 12
//! 2. Blit (memcpy) offset overflow: copying structs larger than ~2047 bytes
13 13
//!    caused the offset in the load/store loop to exceed the 12-bit immediate.
14 14
//!    Fixed by periodically advancing the base registers in the blit loop.
15 15
16 -
record Big {
16 +
record Big: Copy {
17 17
    a: [u8; 2200],
18 18
    tag: i32,
19 19
}
20 20
21 21
/// Store at offset 2200 (> MAX_IMM) into a record field.
test/tests/let.copy.semantics.rad +2 -2
1 1
/// Tests for copy semantics in let bindings.
2 2
/// Aggregates with storage (variables, field access, subscript, deref) need copying.
3 3
/// Temporaries (literals, call results) can be adopted directly.
4 4
5 -
record Point { x: i32, y: i32 }
6 -
record Outer { inner: Point, z: i32 }
5 +
record Point: Copy { x: i32, y: i32 }
6 +
record Outer: Copy { inner: Point, z: i32 }
7 7
8 8
/// Returns an aggregate - caller should NOT copy.
9 9
fn makePoint() -> Point {
10 10
    return Point { x: 1, y: 2 };
11 11
}
test/tests/let.guard.rad +1 -1
1 1
//! returns: 1
2 2
3 -
union Response {
3 +
union Response: Copy {
4 4
    success(i32),
5 5
    failure,
6 6
    other { message: *[u8] },
7 7
}
8 8
test/tests/literal.slice.record.rad +1 -1
1 1
/// Test slice literal with record elements.
2 -
record Point { x: i32, y: i32 }
2 +
record Point: Copy { x: i32, y: i32 }
3 3
4 4
fn localSliceOfRecords() -> i32 {
5 5
    let s: *[Point] = &[Point { x: 5, y: 6 }, Point { x: 7, y: 8 }];
6 6
    return s[0].x + s[1].y;
7 7
}
test/tests/load.u32.high.rad +1 -1
5 5
//! Regression test: `lw` sign-extends on RV64, producing a negative
6 6
//! 64-bit value for u32 values >= 0x80000000. The correct instruction
7 7
//! is `lwu` which zero-extends.
8 8
9 9
/// A u32 value with bit 31 set, stored in a struct to force a memory load.
10 -
record Pair {
10 +
record Pair: Copy {
11 11
    lo: u32,
12 12
    hi: u32,
13 13
}
14 14
15 15
/// Test that a u32 field with bit 31 set compares correctly.
test/tests/loc.addr.offset.bug.rad +2 -2
1 1
//! returns: 0
2 2
//! Test that LOC_ADDR values with non-zero offsets work correctly
3 3
//! for pass-by-ref types (structs).
4 4
5 -
record Inner {
5 +
record Inner: Copy {
6 6
    value: i32,
7 7
}
8 8
9 -
record Outer {
9 +
record Outer: Copy {
10 10
    pad: i32,      // offset 0, size 4
11 11
    inner: Inner,  // offset 4, size 4
12 12
}
13 13
14 14
static outer: Outer = undefined;
test/tests/loc.addr.opt.to.opt.rad +1 -1
1 1
//! returns: 0
2 2
//! Test assigning one optional to another when destination is LOC_ADDR
3 3
//! with offset.
4 4
5 -
record Container {
5 +
record Container: Copy {
6 6
    pad: i32,           // offset 0
7 7
    opt: ?i32,          // offset 4
8 8
}
9 9
10 10
static container: Container = undefined;
test/tests/loc.addr.optional.assign.rad +1 -1
1 1
//! returns: 0
2 2
//! Test assigning to an optional field stored at LOC_ADDR with offset.
3 3
4 -
record Container {
4 +
record Container: Copy {
5 5
    pad: i32,           // offset 0, size 4
6 6
    opt: ?i32,          // offset 4, size 8 (tag + value)
7 7
}
8 8
9 9
static container: Container = undefined;
test/tests/loc.addr.record.assign.rad +2 -2
1 1
//! returns: 0
2 2
//! Test assigning to a record field that is itself a record
3 3
//! when the parent is stored at LOC_ADDR (static).
4 4
5 -
record Inner {
5 +
record Inner: Copy {
6 6
    value: i32,
7 7
}
8 8
9 -
record Outer {
9 +
record Outer: Copy {
10 10
    pad: i32,      // offset 0, size 4
11 11
    inner: Inner,  // offset 4, size 4
12 12
}
13 13
14 14
static outer: Outer = undefined;
test/tests/loop.complex.flow.rad +1 -1
1 1
//! returns: 0
2 2
//! Test loop with complex control flow inside the body.
3 3
//! This mirrors patterns in the lowerer where loops contain
4 4
//! match statements, function calls, error handling, etc.
5 5
6 -
union Result {
6 +
union Result: Copy {
7 7
    Ok(i32),
8 8
    Err(i32),
9 9
}
10 10
11 11
fn maybeAdd(x: i32, y: i32) -> Result {
test/tests/loop.whilelet.union.rad +1 -1
1 1
/// Simple union for testing while-let with payload binding.
2 -
union Option { None, Some(u32) }
2 +
union Option: Copy { None, Some(u32) }
3 3
4 4
/// Sum values from a linked iteration using while-let case with binding.
5 5
fn sumWhileLet(x: Option) -> u32 {
6 6
    let mut opt = x;
7 7
    let mut sum: u32 = 0;
test/tests/lower.const.record.ident.rad +2 -2
1 -
record Reg(u8);
1 +
record Reg: Copy(u8);
2 2
3 3
constant A0: Reg = Reg(10);
4 4
constant A1: Reg = Reg(11);
5 5
6 -
record Entry {
6 +
record Entry: Copy {
7 7
    name: *[u8],
8 8
    reg: Reg,
9 9
}
10 10
11 11
constant ENTRIES: [Entry; 2] = [
test/tests/lower.private.union.const.rad +2 -2
1 -
union Kind {
1 +
union Kind: Copy {
2 2
    A,
3 3
    B,
4 4
}
5 5
6 -
record Entry {
6 +
record Entry: Copy {
7 7
    name: *[u8],
8 8
    kind: Kind,
9 9
}
10 10
11 11
constant ENTRIES: [Entry; 2] = [
test/tests/lower.record.scalar.record.const.rad +2 -2
1 -
record Reg(u8);
1 +
record Reg: Copy(u8);
2 2
3 -
record Entry {
3 +
record Entry: Copy {
4 4
    name: *[u8],
5 5
    reg: Reg,
6 6
}
7 7
8 8
constant ENTRIES: [Entry; 2] = [
test/tests/match.array.rad +1 -1
45 45
        }
46 46
    }
47 47
}
48 48
49 49
/// Array pattern nested inside union variant.
50 -
union Shape {
50 +
union Shape: Copy {
51 51
    Point { coords: [i32; 2] },
52 52
    Line { start: [i32; 2], end: [i32; 2] },
53 53
    None,
54 54
}
55 55
test/tests/match.char.rad +1 -1
11 11
        else => { return 0; }
12 12
    }
13 13
}
14 14
15 15
/// Character pattern nested inside a union variant field.
16 -
union Token {
16 +
union Token: Copy {
17 17
    Punct { ch: u8, pos: i32 },
18 18
    Eof,
19 19
}
20 20
21 21
fn tokenVal(t: Token) -> i32 {
test/tests/match.multi.seal.rad +1 -1
2 2
//! Regression test: intermediate arm blocks in multi-pattern matches must be
3 3
//! sealed so that SSA variables flow correctly through single-predecessor
4 4
//! optimization. Without sealing, block parameters are created but never
5 5
//! resolved, causing values to be read from wrong registers.
6 6
7 -
union Kind { A, B, C, D, E }
7 +
union Kind: Copy { A, B, C, D, E }
8 8
9 9
fn classify(k: Kind, y: i32) -> i32 {
10 10
    match k {
11 11
        case Kind::A, Kind::B, Kind::C => {
12 12
            return y + 10;
test/tests/match.multi.survive.rad +2 -2
7 7
//! the fall-through (else) path.
8 8
//!
9 9
//! This test catches the bug by requiring a value (`state`) to survive
10 10
//! through multiple match arms and into the `else` branch.
11 11
12 -
union Kind {
12 +
union Kind: Copy {
13 13
    Alpha,
14 14
    Beta,
15 15
    Gamma,
16 16
    Delta,
17 17
    Epsilon,
18 18
}
19 19
20 -
record State {
20 +
record State: Copy {
21 21
    value: i32,
22 22
    kind: Kind,
23 23
}
24 24
25 25
fn classify(s: State) -> i32 {
test/tests/match.mutref.push.rad +2 -2
1 1
//! returns: 0
2 2
//! Test pushing items through a mutable reference obtained via match.
3 3
4 -
record U32List {
4 +
record U32List: Copy {
5 5
    data: *mut [u32],
6 6
    len: u32,
7 7
}
8 8
9 -
union Sealed {
9 +
union Sealed: Copy {
10 10
    No { items: U32List },
11 11
    Yes,
12 12
}
13 13
14 14
fn pushItem(list: *mut U32List, value: u32) {
test/tests/match.mutref.union.rad +2 -2
1 1
//! returns: 0
2 2
//! Test matching on a mutable reference to a union field and mutating
3 3
//! the payload in place.
4 4
5 -
union State {
5 +
union State: Copy {
6 6
    A { count: u32 },
7 7
    B,
8 8
}
9 9
10 -
record Data {
10 +
record Data: Copy {
11 11
    state: State,
12 12
    value: u32,
13 13
}
14 14
15 15
fn process(d: *mut Data) -> u32 {
test/tests/match.nested.call.rad +3 -3
1 1
//! returns: 0
2 2
//! Test nested patterns with tuple-style (Call) union variants.
3 3
4 -
union Dir { North, South }
5 -
union Opt { Some(i32), None }
4 +
union Dir: Copy { North, South }
5 +
union Opt: Copy { Some(i32), None }
6 6
7 7
/// Tuple-style variant with integer literal nested pattern.
8 8
fn checkOpt(o: Opt) -> i32 {
9 9
    match o {
10 10
        case Opt::Some(42) => {
33 33
        else { return -1; };
34 34
    return x;
35 35
}
36 36
37 37
/// Nested union inside tuple-style variant.
38 -
union Wrapper { Wrap(Dir), Empty }
38 +
union Wrapper: Copy { Wrap(Dir), Empty }
39 39
40 40
fn extractDir(w: Wrapper) -> i32 {
41 41
    match w {
42 42
        case Wrapper::Wrap(Dir::North) => {
43 43
            return 10;
test/tests/match.nested.deep.rad +6 -6
1 1
//! returns: 0
2 2
//! Test deeply nested patterns: three levels of record nesting,
3 3
//! and nested unions inside records inside unions.
4 4
5 -
record Leaf {
5 +
record Leaf: Copy {
6 6
    val: i32,
7 7
}
8 8
9 -
record Branch {
9 +
record Branch: Copy {
10 10
    leaf: Leaf,
11 11
    extra: i32,
12 12
}
13 13
14 -
union Tree {
14 +
union Tree: Copy {
15 15
    Node { branch: Branch, tag: i32 },
16 16
    Empty,
17 17
}
18 18
19 19
/// Three levels deep: union -> record -> record -> record.
27 27
        }
28 28
    }
29 29
}
30 30
31 31
/// Nested union inside a record inside a union.
32 -
union Color { Red, Green, Blue }
32 +
union Color: Copy { Red, Green, Blue }
33 33
34 -
record Pixel {
34 +
record Pixel: Copy {
35 35
    color: Color,
36 36
    brightness: i32,
37 37
}
38 38
39 -
union Canvas {
39 +
union Canvas: Copy {
40 40
    Filled { pixel: Pixel },
41 41
    Blank,
42 42
}
43 43
44 44
fn getColor(cv: Canvas) -> i32 {
test/tests/match.nested.deref.rad +6 -6
1 1
//! returns: 0
2 2
//! Test nested patterns through pointer dereferences (auto-deref).
3 3
4 -
union Inner {
4 +
union Inner: Copy {
5 5
    A(i32),
6 6
    B,
7 7
}
8 8
9 -
union Outer {
9 +
union Outer: Copy {
10 10
    Some(*Inner),
11 11
    None,
12 12
}
13 13
14 14
/// Match nested union variant through pointer dereference in match/case.
40 40
        else { return -1; };
41 41
    return x;
42 42
}
43 43
44 44
/// Record with pointer field and nested pattern through deref.
45 -
record Container {
45 +
record Container: Copy {
46 46
    inner: *Inner,
47 47
    tag: i32,
48 48
}
49 49
50 -
union Boxed {
50 +
union Boxed: Copy {
51 51
    Some { c: Container },
52 52
    None,
53 53
}
54 54
55 55
/// Nested record with auto-deref on a pointer field.
87 87
    }
88 88
    return 0;
89 89
}
90 90
91 91
/// Auto-deref through pointer to record in a record field.
92 -
record Point {
92 +
record Point: Copy {
93 93
    x: i32,
94 94
    y: i32,
95 95
}
96 96
97 -
union Holder {
97 +
union Holder: Copy {
98 98
    Ptr { p: *Point, z: i32 },
99 99
    Empty,
100 100
}
101 101
102 102
fn derefRecordField(h: Holder) -> i32 {
test/tests/match.nested.guard.rad +4 -4
1 1
//! returns: 0
2 2
//! Test nested patterns combined with guards.
3 3
4 -
record Pair {
4 +
record Pair: Copy {
5 5
    a: i32,
6 6
    b: i32,
7 7
}
8 8
9 -
union Opt {
9 +
union Opt: Copy {
10 10
    Some { pair: Pair },
11 11
    None,
12 12
}
13 13
14 14
/// Nested pattern with guard on bound variable.
25 25
        }
26 26
    }
27 27
}
28 28
29 29
/// Nested union with guard.
30 -
union Tag { X, Y }
30 +
union Tag: Copy { X, Y }
31 31
32 -
union Box {
32 +
union Box: Copy {
33 33
    Item { tag: Tag, val: i32 },
34 34
    Empty,
35 35
}
36 36
37 37
fn guardedNested(bx: Box) -> i32 {
test/tests/match.nested.iflet.guard.rad +4 -4
1 1
//! returns: 0
2 2
//! Test nested patterns combined with guards in if-let-case.
3 3
4 -
record Pair {
4 +
record Pair: Copy {
5 5
    a: i32,
6 6
    b: i32,
7 7
}
8 8
9 -
union Opt {
9 +
union Opt: Copy {
10 10
    Some { pair: Pair },
11 11
    None,
12 12
}
13 13
14 14
/// Nested record pattern with guard on bound variable.
18 18
    }
19 19
    return -1;
20 20
}
21 21
22 22
/// Nested union variant with guard.
23 -
union Dir { North, South }
23 +
union Dir: Copy { North, South }
24 24
25 -
union Command {
25 +
union Command: Copy {
26 26
    Move { dir: Dir, speed: i32 },
27 27
    Stop,
28 28
}
29 29
30 30
fn fastNorth(c: Command) -> i32 {
test/tests/match.nested.iflet.rad +4 -4
1 1
//! returns: 0
2 2
//! Test nested patterns in if-let-case and let-else contexts.
3 3
4 -
record Inner {
4 +
record Inner: Copy {
5 5
    x: i32,
6 6
    y: i32,
7 7
}
8 8
9 -
union Wrapper {
9 +
union Wrapper: Copy {
10 10
    Some { inner: Inner, z: i32 },
11 11
    None,
12 12
}
13 13
14 14
/// Nested if-let-case.
18 18
    }
19 19
    return -1;
20 20
}
21 21
22 22
/// Nested union variant in if-let-case.
23 -
union Kind { A, B }
23 +
union Kind: Copy { A, B }
24 24
25 -
union Tagged {
25 +
union Tagged: Copy {
26 26
    Item { kind: Kind, val: i32 },
27 27
    Empty,
28 28
}
29 29
30 30
fn ifLetNestedUnion(t: Tagged) -> i32 {
test/tests/match.nested.letelse.rad +2 -2
1 1
//! returns: 0
2 2
//! Test nested patterns in let-else statements.
3 3
4 -
record Inner {
4 +
record Inner: Copy {
5 5
    x: i32,
6 6
    y: i32,
7 7
}
8 8
9 -
union Wrapper {
9 +
union Wrapper: Copy {
10 10
    Some { inner: Inner, z: i32 },
11 11
    None,
12 12
}
13 13
14 14
/// Nested record destructuring in let-else.
test/tests/match.nested.letelse.union.rad +5 -5
1 1
//! returns: 0
2 2
//! Test nested union variant patterns in let-else statements.
3 3
4 -
union Dir { North, South }
4 +
union Dir: Copy { North, South }
5 5
6 -
union Command {
6 +
union Command: Copy {
7 7
    Move { dir: Dir, speed: i32 },
8 8
    Stop,
9 9
}
10 10
11 11
/// Nested union variant in let-else.
14 14
        else { return -1; };
15 15
    return speed;
16 16
}
17 17
18 18
/// Nested union variant in let-else, deeper nesting.
19 -
record Inner {
19 +
record Inner: Copy {
20 20
    x: i32,
21 21
    y: i32,
22 22
}
23 23
24 -
union Wrapper {
24 +
union Wrapper: Copy {
25 25
    Some { inner: Inner, z: i32 },
26 26
    None,
27 27
}
28 28
29 -
union Box {
29 +
union Box: Copy {
30 30
    Filled { w: Wrapper, tag: i32 },
31 31
    Empty,
32 32
}
33 33
34 34
fn extractInner(b: Box) -> i32 {
test/tests/match.nested.literal.rad +2 -2
1 1
//! returns: 0
2 2
//! Test literal patterns (integers, booleans) nested inside union variant fields.
3 3
4 -
union Action {
4 +
union Action: Copy {
5 5
    Set { code: i32, value: i32 },
6 6
    Toggle { flag: bool },
7 7
    None,
8 8
}
9 9
53 53
    }
54 54
    return false;
55 55
}
56 56
57 57
/// Multiple literal fields in the same pattern.
58 -
union Pair {
58 +
union Pair: Copy {
59 59
    Both { a: i32, b: i32 },
60 60
    Empty,
61 61
}
62 62
63 63
fn matchBothLiterals(p: Pair) -> i32 {
test/tests/match.nested.multi.rad +3 -3
1 1
//! returns: 0
2 2
//! Test multiple nested refining fields in the same record pattern.
3 3
4 -
union Dir { North, South, East }
5 -
union Mode { Fast, Slow }
4 +
union Dir: Copy { North, South, East }
5 +
union Mode: Copy { Fast, Slow }
6 6
7 -
union Command {
7 +
union Command: Copy {
8 8
    Move { dir: Dir, mode: Mode, speed: i32 },
9 9
    Stop,
10 10
}
11 11
12 12
/// Two union-typed fields refined simultaneously.
test/tests/match.nested.pattern.rad +12 -12
1 1
//! returns: 0
2 2
//! Test nested patterns in match/case statements.
3 3
4 -
record Inner {
4 +
record Inner: Copy {
5 5
    x: i32,
6 6
    y: i32,
7 7
}
8 8
9 -
union Outer {
9 +
union Outer: Copy {
10 10
    A { inner: Inner, z: i32 },
11 11
    B,
12 12
}
13 13
14 14
/// Match a union variant and destructure its nested record field.
42 42
        }
43 43
    }
44 44
}
45 45
46 46
/// Multi-level nesting: three records deep.
47 -
record Deep {
47 +
record Deep: Copy {
48 48
    val: i32,
49 49
}
50 50
51 -
record Mid {
51 +
record Mid: Copy {
52 52
    deep: Deep,
53 53
    extra: i32,
54 54
}
55 55
56 -
union Wrapper {
56 +
union Wrapper: Copy {
57 57
    W { mid: Mid, tag: i32 },
58 58
    Empty,
59 59
}
60 60
61 61
fn testDeepNesting(w: Wrapper) -> i32 {
68 68
        }
69 69
    }
70 70
}
71 71
72 72
/// Nested union inside a union variant.
73 -
union Direction {
73 +
union Direction: Copy {
74 74
    North,
75 75
    South,
76 76
}
77 77
78 -
union Command {
78 +
union Command: Copy {
79 79
    Move { dir: Direction, speed: i32 },
80 80
    Stop,
81 81
}
82 82
83 83
fn testNestedUnionLiteral(c: Command) -> i32 {
93 93
        }
94 94
    }
95 95
}
96 96
97 97
/// Mixed record and union nesting with multiple fields.
98 -
record Point {
98 +
record Point: Copy {
99 99
    x: i32,
100 100
    y: i32,
101 101
}
102 102
103 -
union Shape {
103 +
union Shape: Copy {
104 104
    Circle { center: Point, radius: i32 },
105 105
    Rect { origin: Point, size: Point },
106 106
    None,
107 107
}
108 108
119 119
        }
120 120
    }
121 121
}
122 122
123 123
/// Nested union variant pattern inside record field.
124 -
union Pair {
124 +
union Pair: Copy {
125 125
    Val { a: i32, b: i32 },
126 126
    Empty,
127 127
}
128 128
129 -
record Container {
129 +
record Container: Copy {
130 130
    pair: Pair,
131 131
    label: i32,
132 132
}
133 133
134 -
union Boxed {
134 +
union Boxed: Copy {
135 135
    Some { c: Container },
136 136
    None,
137 137
}
138 138
139 139
fn testNestedUnionInRecord(bx: Boxed) -> i32 {
test/tests/match.nested.record.rad +3 -3
1 1
//! returns: 0
2 2
//! Test nested record destructuring inside union variant patterns.
3 3
4 -
record Point {
4 +
record Point: Copy {
5 5
    x: i32,
6 6
    y: i32,
7 7
}
8 8
9 -
record Rect {
9 +
record Rect: Copy {
10 10
    origin: Point,
11 11
    size: Point,
12 12
}
13 13
14 -
union Shape {
14 +
union Shape: Copy {
15 15
    Circle { center: Point, radius: i32 },
16 16
    Rectangle { rect: Rect },
17 17
    None,
18 18
}
19 19
test/tests/match.nested.union.rad +2 -2
1 1
//! returns: 0
2 2
//! Test nested union variant patterns inside record fields.
3 3
4 -
union Dir { North, South, East, West }
4 +
union Dir: Copy { North, South, East, West }
5 5
6 -
union Command {
6 +
union Command: Copy {
7 7
    Move { dir: Dir, speed: i32 },
8 8
    Turn { dir: Dir },
9 9
    Stop,
10 10
}
11 11
test/tests/match.nested.whilelet.rad +4 -4
1 1
//! returns: 0
2 2
//! Test nested patterns in while-let loops.
3 3
4 -
record Pair {
4 +
record Pair: Copy {
5 5
    a: i32,
6 6
    b: i32,
7 7
}
8 8
9 -
union Opt {
9 +
union Opt: Copy {
10 10
    Some { pair: Pair },
11 11
    None,
12 12
}
13 13
14 14
fn get(items: *[Opt], idx: u32) -> Opt {
38 38
        set i = i + 1;
39 39
    }
40 40
    return sum;
41 41
}
42 42
43 -
union Dir { North, South }
43 +
union Dir: Copy { North, South }
44 44
45 -
union Command {
45 +
union Command: Copy {
46 46
    Move { dir: Dir, speed: i32 },
47 47
    Stop,
48 48
}
49 49
50 50
fn getCmd(items: *[Command], idx: u32) -> Command {
test/tests/match.record.pattern.rad +1 -1
1 1
/// Match on union variant with record payload and bind fields.
2 -
union Expr {
2 +
union Expr: Copy {
3 3
    Null,
4 4
    Pair { first: i32, second: i32 },
5 5
}
6 6
7 7
/// Extract the first field from a Pair variant.
test/tests/match.string.rad +1 -1
18 18
        }
19 19
    }
20 20
}
21 21
22 22
/// String pattern nested inside a union variant field.
23 -
union Cmd {
23 +
union Cmd: Copy {
24 24
    Say { msg: *[u8], count: i32 },
25 25
    Quit,
26 26
}
27 27
28 28
fn dispatch(c: Cmd) -> i32 {
test/tests/match.value.copy.rad +3 -3
5 5
//!
6 6
//! The bug: when pattern-matching a union by value, the lowerer returns a
7 7
//! pointer into the original union's payload instead of copying it. If the
8 8
//! source is overwritten, the binding reads garbage.
9 9
10 -
record Pair {
10 +
record Pair: Copy {
11 11
    a: u32,
12 12
    b: u32,
13 13
}
14 14
15 -
union Data {
15 +
union Data: Copy {
16 16
    Some { pair: Pair },
17 17
    None,
18 18
}
19 19
20 -
record Box {
20 +
record Box: Copy {
21 21
    data: Data,
22 22
}
23 23
24 24
/// Test let-case on an aggregate union payload.
25 25
/// The Pair inside the union is an aggregate - its binding will be a pointer
test/tests/match.void.then.or.rad +2 -2
1 1
//! returns: 0
2 2
//! Regression test: match on void union followed by 'or' equality check
3 3
//! in the else branch.
4 4
5 -
union Op {
5 +
union Op: Copy {
6 6
    Add,
7 7
    Sub,
8 8
    Mul,
9 9
    Div,
10 10
    And,
12 12
    Xor,
13 13
    Eq,
14 14
    Ne,
15 15
}
16 16
17 -
record Expr {
17 +
record Expr: Copy {
18 18
    op: Op,
19 19
    left: i32,
20 20
    right: i32,
21 21
}
22 22
test/tests/memzero.result.bug.rad +1 -1
1 1
//! returns: 0
2 2
//! Test for result memzero overrun: success payload is tiny, error payload
3 3
//! is large, and a guard word sits immediately after the return slot.
4 4
5 -
union Err {
5 +
union Err: Copy {
6 6
    Big([u32; 3]), // 12-byte error payload, forces a large result container
7 7
}
8 8
9 9
fn fallible(ok: bool) throws (Err) {
10 10
    if ok {
test/tests/memzero.union.bug.rad +2 -2
1 1
//! returns: 0
2 2
//! Test enum with a large variant to force a payload capacity bigger than
3 3
//! the small variant we actually store.
4 -
union Payload {
4 +
union Payload: Copy {
5 5
    Big([u32; 3]), // 12-byte payload
6 6
    Small(u8),     // 1-byte payload
7 7
}
8 8
9 -
record Frame {
9 +
record Frame: Copy {
10 10
    guard1: u16,
11 11
    val: Payload,
12 12
    guard2: u32,
13 13
}
14 14
test/tests/method.basic.rad +1 -1
1 1
//! returns: 0
2 2
//! Basic standalone method test.
3 3
4 -
record Point {
4 +
record Point: Copy {
5 5
    x: i32,
6 6
    y: i32,
7 7
}
8 8
9 9
fn (p: *Point) sum() -> i32 {
test/tests/method.chain.rad +1 -1
1 1
//! returns: 0
2 2
//! Method chaining via mutable pointer returns.
3 3
4 -
record Builder {
4 +
record Builder: Copy {
5 5
    x: i32,
6 6
    y: i32,
7 7
}
8 8
9 9
fn (b: *mut Builder) setX(x: i32) -> *mut Builder {
test/tests/method.multiple.rad +1 -1
1 1
//! returns: 0
2 2
//! Multiple methods on the same type.
3 3
4 -
record Vec2 {
4 +
record Vec2: Copy {
5 5
    x: i32,
6 6
    y: i32,
7 7
}
8 8
9 9
fn (v: *Vec2) magnitudeSq() -> i32 {
test/tests/method.ptr.rad +1 -1
1 1
//! returns: 0
2 2
//! Methods called via various pointer indirections.
3 3
4 -
record Counter {
4 +
record Counter: Copy {
5 5
    value: i32,
6 6
}
7 7
8 8
fn (c: *Counter) get() -> i32 {
9 9
    return c.value;
test/tests/method.pub.rad +1 -1
1 1
//! returns: 0
2 2
//! Public attribute on standalone methods.
3 3
4 -
record Foo {
4 +
record Foo: Copy {
5 5
    x: i32,
6 6
}
7 7
8 8
export fn (f: *Foo) getX() -> i32 {
9 9
    return f.x;
test/tests/method.return.rad +1 -1
1 1
//! returns: 0
2 2
//! Methods returning various types and records.
3 3
4 -
record Pair {
4 +
record Pair: Copy {
5 5
    a: i32,
6 6
    b: i32,
7 7
}
8 8
9 9
fn (p: *Pair) sum() -> i32 {
test/tests/method.throws.rad +2 -2
1 1
//! returns: 0
2 2
//! Methods that throw errors.
3 3
4 -
union ParseError {
4 +
union ParseError: Copy {
5 5
    Invalid,
6 6
}
7 7
8 -
record Parser {
8 +
record Parser: Copy {
9 9
    pos: i32,
10 10
    len: i32,
11 11
}
12 12
13 13
fn (p: *mut Parser) advance() throws (ParseError) {
test/tests/method.union.rad +1 -1
1 1
//! returns: 0
2 2
//! Standalone methods on union types.
3 3
4 -
union Shape {
4 +
union Shape: Copy {
5 5
    Circle(i32),
6 6
    Rect { w: i32, h: i32 },
7 7
}
8 8
9 9
fn (s: *Shape) isCircle() -> bool {
test/tests/method.with.trait.rad +1 -1
1 1
//! returns: 0
2 2
//! Standalone methods coexisting with trait instances on the same type.
3 3
4 -
record Widget {
4 +
record Widget: Copy {
5 5
    x: i32,
6 6
    y: i32,
7 7
}
8 8
9 9
// Standalone method.
test/tests/multi.throw.basic.rad +2 -2
1 1
/// Multi-throw: function that can throw two different error types.
2 -
union ErrA { A }
3 -
union ErrB { B }
2 +
union ErrA: Copy { A }
3 +
union ErrB: Copy { B }
4 4
5 5
fn fallible(flag: i32) -> i32 throws (ErrA, ErrB) {
6 6
    if flag == 1 {
7 7
        throw ErrA::A();
8 8
    }
test/tests/multi.throw.catch.typed.rad +2 -2
1 1
/// Multi-error typed catch: switch dispatch on error tag.
2 -
union ErrA { A }
3 -
union ErrB { B }
2 +
union ErrA: Copy { A }
3 +
union ErrB: Copy { B }
4 4
5 5
fn fallible(flag: i32) -> i32 throws (ErrA, ErrB) {
6 6
    if flag == 1 {
7 7
        throw ErrA::A();
8 8
    }
test/tests/multi.throw.propagate.rad +2 -2
1 1
/// Multi-throw propagation: caller propagates errors from callee with subset throw list.
2 -
union ErrA { A }
3 -
union ErrB { B }
2 +
union ErrA: Copy { A }
3 +
union ErrB: Copy { B }
4 4
5 5
fn inner(flag: bool) -> i32 throws (ErrA) {
6 6
    if flag {
7 7
        throw ErrA::A();
8 8
    }
test/tests/mutref.call.result.rad +1 -1
1 1
//! returns: 0
2 2
//! Mutable borrow through a field access on a call result.
3 3
4 -
record Box {
4 +
record Box: Copy {
5 5
    x: i32,
6 6
}
7 7
8 8
fn idBox(b: *mut Box) -> *mut Box {
9 9
    return b;
test/tests/opt.record.eq.rad +2 -2
1 1
//! returns: 1
2 2
//! Test equality comparison of optional records.
3 3
4 -
record Inner {
4 +
record Inner: Copy {
5 5
    start: i32,
6 6
    end: i32,
7 7
}
8 8
9 -
record Outer {
9 +
record Outer: Copy {
10 10
    item: ?Inner,
11 11
}
12 12
13 13
@default fn main() -> bool {
14 14
    let outerA = Outer { item: Inner { start: 3, end: 9 } };
test/tests/opt.record.eq.rev.rad +1 -1
1 1
/// Tests equality between an optional record and a record (?T == T).
2 2
3 -
record Id { n: u32 }
3 +
record Id: Copy { n: u32 }
4 4
5 5
/// Wraps a record in an optional.
6 6
fn makeOpt(id: Id) -> ?Id {
7 7
    return id;
8 8
}
test/tests/opt.record.rad +2 -2
1 1
//! returns: 28
2 2
//! Test optional records with if-let.
3 3
4 -
record Vector {
4 +
record Vector: Copy {
5 5
    x: i32,
6 6
    y: i32,
7 7
    z: i32,
8 8
}
9 9
10 -
record Wrapper {
10 +
record Wrapper: Copy {
11 11
    opt: ?i32,
12 12
}
13 13
14 14
fn takeOptional(x: ?i32) -> i32 {
15 15
    if let value = x {
test/tests/opt.return.nested.rad +1 -1
1 1
//! returns: 99
2 2
3 -
record Pair {
3 +
record Pair: Copy {
4 4
    x: u32,
5 5
    y: u32,
6 6
}
7 7
8 8
fn pick(flag: bool) -> ?Pair {
test/tests/opt.return.record.rad +1 -1
1 1
//! returns: 99
2 2
//! Test returning optional records from functions.
3 3
4 -
record Vector {
4 +
record Vector: Copy {
5 5
    x: i32,
6 6
    y: i32,
7 7
    z: i32
8 8
}
9 9
test/tests/optional.aggregate.eq.rad +1 -1
1 1
// Test optional aggregate equality (non-pointer optionals)
2 2
3 -
record Pair {
3 +
record Pair: Copy {
4 4
    a: i32,
5 5
    b: i32,
6 6
}
7 7
8 8
fn optPairEq(a: ?Pair, b: ?Pair) -> bool {
test/tests/optional.record.value.match.rad +1 -1
1 1
//! returns: 0
2 2
//! Matching an optional record against a record value must compile and compare structurally.
3 3
4 -
record Pair {
4 +
record Pair: Copy {
5 5
    a: i32,
6 6
    b: i32,
7 7
}
8 8
9 9
fn classify(value: ?Pair) -> i32 {
test/tests/parser.call.record.argument.rad +1 -1
1 1
//! returns: 0
2 2
3 -
record Box { value: i32 }
3 +
record Box: Copy { value: i32 }
4 4
5 5
fn isOne(box: Box) -> bool {
6 6
    return box.value == 1;
7 7
}
8 8
test/tests/parser.condition.array.record.rad +1 -1
1 1
//! returns: 0
2 2
//! Bracketed array elements may contain record literals in a condition.
3 3
4 -
record Box { value: i32 }
4 +
record Box: Copy { value: i32 }
5 5
6 6
@default fn main() -> i32 {
7 7
    if [Box { value: 1 }][0].value == 1 {
8 8
        return 0;
9 9
    }
test/tests/parser.condition.subscript.record.rad +1 -1
1 1
//! returns: 0
2 2
//! Bracketed subscript expressions may contain record literals in a condition.
3 3
4 -
record Index { value: u32 }
4 +
record Index: Copy { value: u32 }
5 5
6 6
@default fn main() -> i32 {
7 7
    let values: [i32; 1] = [7];
8 8
    if values[Index { value: 0 }.value] == 7 {
9 9
        return 0;
test/tests/pointer.copy.edge.case.rad +4 -4
1 1
//! returns: 0
2 2
3 -
union NodeKind {
3 +
union NodeKind: Copy {
4 4
    Placeholder,
5 5
    Bool(bool),
6 6
}
7 7
8 -
record Span {
8 +
record Span: Copy {
9 9
    length: u32,
10 10
}
11 11
12 -
record Node {
12 +
record Node: Copy {
13 13
    span: Span,
14 14
    kind: NodeKind,
15 15
}
16 16
17 -
record Parser {
17 +
record Parser: Copy {
18 18
    nodes: [Node; 1],
19 19
    count: u32,
20 20
}
21 21
22 22
fn makeNode(p: *mut Parser, kind: NodeKind) -> *mut Node {
test/tests/pointer.slice.index.rad +1 -1
1 1
//! returns: 0
2 2
3 -
record Holder {
3 +
record Holder: Copy {
4 4
    ptr: *[i32],
5 5
    zero: u32,
6 6
}
7 7
8 8
// Test that pointer-to-slice indexing correctly dereferences the slice header.
test/tests/pointer.slice.store.rad +3 -3
1 1
//! returns: 0
2 2
//! Test storing through a pointer to a slice in static storage.
3 3
4 -
record Entry {
4 +
record Entry: Copy {
5 5
    a: u32,
6 6
    b: u32,
7 7
    c: u32,
8 8
    d: u32,
9 9
    e: u32,
10 10
}
11 11
12 -
record Table {
12 +
record Table: Copy {
13 13
    entries: *mut [Entry],
14 14
    len: u32,
15 15
}
16 16
17 -
record PtrBox {
17 +
record PtrBox: Copy {
18 18
    ptr: *mut *mut [Entry],
19 19
}
20 20
21 21
static STORAGE: [Entry; 2] = undefined;
22 22
static TABLE: Table = undefined;
test/tests/prog.cordic.rad +1 -1
20 20
21 21
/// Pi/2 in Q16.16.
22 22
constant HALF_PI: i32 = 102944;
23 23
24 24
/// Result record for cos and sin.
25 -
record CosSin {
25 +
record CosSin: Copy {
26 26
    cos: i32,
27 27
    sin: i32,
28 28
}
29 29
30 30
/// CORDIC rotation mode: compute cos(angle) and sin(angle).
test/tests/prog.dijkstra.rad +2 -2
4 4
//! and a min-heap priority queue. Reconstruct paths and verify distances.
5 5
6 6
constant MAX_NODES: u32 = 16;
7 7
constant INF: u32 = 0xFFFFFFFF;
8 8
9 -
record HeapEntry {
9 +
record HeapEntry: Copy {
10 10
    dist: u32,
11 11
    node: u32,
12 12
}
13 13
14 -
record Graph {
14 +
record Graph: Copy {
15 15
    adj: *mut [[u32; 16]],
16 16
    dist: *mut [u32],
17 17
    prev: *mut [i32],
18 18
    visited: *mut [bool],
19 19
    numNodes: u32,
test/tests/prog.eval.rad +4 -4
4 4
//! of tagged union nodes. Build expression trees, evaluate them recursively,
5 5
//! and verify results.
6 6
7 7
/// An expression node. Leaf nodes hold a number. Interior nodes hold an
8 8
/// operator and indices (into the nodes array) of their left and right children.
9 -
union Expr {
9 +
union Expr: Copy {
10 10
    Num(i32),
11 11
    Add(BinOp),
12 12
    Sub(BinOp),
13 13
    Mul(BinOp),
14 14
    Div(BinOp),
15 15
    Neg(u32),
16 16
}
17 17
18 18
/// Binary operator: indices of left and right child nodes.
19 -
record BinOp {
19 +
record BinOp: Copy {
20 20
    left: u32,
21 21
    right: u32,
22 22
}
23 23
24 24
/// A pool of expression nodes with a count of allocated nodes.
25 -
record Pool {
25 +
record Pool: Copy {
26 26
    nodes: [Expr; 64],
27 27
    count: u32,
28 28
}
29 29
30 30
/// Evaluation error.
31 -
union EvalError {
31 +
union EvalError: Copy {
32 32
    DivByZero
33 33
}
34 34
35 35
/// Allocate a new node, returning its index.
36 36
fn newNode(pool: *mut Pool, expr: Expr) -> u32 {
test/tests/prog.hanoi.rad +3 -3
6 6
constant NUM_DISKS: u32 = 6;
7 7
/// 2^6 - 1 = 63 moves.
8 8
constant MAX_MOVES: u32 = 63;
9 9
10 10
/// A single move: move a disk from one peg to another.
11 -
record Move {
11 +
record Move: Copy {
12 12
    disk: u32,
13 13
    from: u32,
14 14
    to: u32,
15 15
}
16 16
17 17
/// Log of all moves performed.
18 -
record MoveLog {
18 +
record MoveLog: Copy {
19 19
    moves: [Move; 63],
20 20
    count: u32,
21 21
}
22 22
23 23
/// Record a move.
65 65
    return 0;
66 66
}
67 67
68 68
/// Peg state: array of 3 pegs, each holding up to NUM_DISKS disks.
69 69
/// top[p] = number of disks currently on peg p.
70 -
record Pegs {
70 +
record Pegs: Copy {
71 71
    stacks: [[u32; 6]; 3],
72 72
    top: [u32; 3],
73 73
}
74 74
75 75
fn pegPush(pegs: *mut Pegs, peg: u32, disk: u32) -> bool {
test/tests/prog.huffman.rad +3 -3
6 6
constant MAX_SYMBOLS: u32 = 32;
7 7
constant MAX_NODES: u32 = 63;
8 8
constant MAX_BITS: u32 = 512;
9 9
10 10
/// A node in the Huffman tree.
11 -
union HNodeKind {
11 +
union HNodeKind: Copy {
12 12
    /// Leaf node with a symbol index.
13 13
    Leaf(u32),
14 14
    /// Interior node (no symbol).
15 15
    Interior,
16 16
}
17 17
18 -
record HNode {
18 +
record HNode: Copy {
19 19
    freq: u32,
20 20
    kind: HNodeKind,
21 21
    left: u32,
22 22
    right: u32,
23 23
}
24 24
25 25
constant NIL: u32 = 0xFFFFFFFF;
26 26
27 -
record HuffState {
27 +
record HuffState: Copy {
28 28
    nodes: *mut [HNode],
29 29
    nodeCount: u32,
30 30
    heap: *mut [u32],
31 31
    heapSize: u32,
32 32
    codeBits: *mut [u32],
test/tests/prog.linkedlist.rad +2 -2
2 2
//! Linked list via array pool.
3 3
//! Implement a singly-linked list using an array of node records
4 4
//! (pool allocator pattern). Operations: push, pop, find, reverse, length.
5 5
6 6
/// A node in the linked list.
7 -
record Node {
7 +
record Node: Copy {
8 8
    value: i32,
9 9
    next: ?u32,
10 10
}
11 11
12 12
/// A linked list backed by a pool of pre-allocated nodes.
13 -
record List {
13 +
record List: Copy {
14 14
    pool: [Node; 64],
15 15
    free: u32,
16 16
    head: ?u32,
17 17
}
18 18
test/tests/prog.lzw.rad +2 -2
6 6
constant MAX_DICT: u32 = 512;
7 7
constant INIT_DICT: u32 = 258;
8 8
constant CLEAR_CODE: u32 = 256;
9 9
constant EOI_CODE: u32 = 257;
10 10
11 -
record DictEntry {
11 +
record DictEntry: Copy {
12 12
    prefix: u32,
13 13
    suffix: u8,
14 14
}
15 15
16 -
record LzwState {
16 +
record LzwState: Copy {
17 17
    encDict: *mut [DictEntry],
18 18
    encDictSize: u32,
19 19
    decDict: *mut [DictEntry],
20 20
    decDictSize: u32,
21 21
    encoded: *mut [u32],
test/tests/prog.matmul.rad +1 -1
4 4
//! a known expected output. Classic benchmark kernel.
5 5
6 6
constant N: u32 = 4;
7 7
8 8
/// A 4x4 integer matrix (row-major).
9 -
record Mat4 {
9 +
record Mat4: Copy {
10 10
    rows: [[i32; 4]; 4],
11 11
}
12 12
13 13
/// Perform matrix multiplication: c = a * b.
14 14
fn matmul(c: *mut Mat4, a: *Mat4, b: *Mat4) {
test/tests/prog.mersenne.rad +1 -1
5 5
constant M: u32 = 397;
6 6
constant MATRIX_A: u32 = 0x9908B0DF;
7 7
constant UPPER_MASK: u32 = 0x80000000;
8 8
constant LOWER_MASK: u32 = 0x7FFFFFFF;
9 9
10 -
record MtState {
10 +
record MtState: Copy {
11 11
    mt: *mut [u32],
12 12
    mti: u32,
13 13
}
14 14
15 15
fn mtInit(s: *mut MtState, seed: u32) {
test/tests/prog.nqueens.rad +1 -1
3 3
//! Solve the N-Queens problem using backtracking. Count all valid placements
4 4
//! for boards of size 1 through 8 and verify against known solution counts.
5 5
6 6
constant MAX_N: u32 = 8;
7 7
8 -
record Board {
8 +
record Board: Copy {
9 9
    queens: *mut [i32],
10 10
    solutionCount: u32,
11 11
    boardSize: u32,
12 12
}
13 13
test/tests/prog.rbtree.rad +2 -2
6 6
constant NIL: u32 = 0;
7 7
8 8
constant RED: u32 = 0;
9 9
constant BLACK: u32 = 1;
10 10
11 -
record RBNode {
11 +
record RBNode: Copy {
12 12
    key: i32,
13 13
    color: u32,
14 14
    left: u32,
15 15
    right: u32,
16 16
    parent: u32,
17 17
}
18 18
19 -
record RBTree {
19 +
record RBTree: Copy {
20 20
    pool: *mut [RBNode],
21 21
    poolNext: u32,
22 22
    root: u32,
23 23
    inorder: *mut [i32],
24 24
    inorderCount: u32,
test/tests/prog.regex.rad +3 -3
10 10
11 11
constant TRANS_CHAR: u32 = 0;
12 12
constant TRANS_EPSILON: u32 = 1;
13 13
constant TRANS_DOT: u32 = 2;
14 14
15 -
record Trans {
15 +
record Trans: Copy {
16 16
    kind: u32,
17 17
    ch: u8,
18 18
    to: u32,
19 19
}
20 20
21 -
record Frag {
21 +
record Frag: Copy {
22 22
    start: u32,
23 23
    endState: u32,
24 24
}
25 25
26 -
record NfaState {
26 +
record NfaState: Copy {
27 27
    trans: *mut [Trans],
28 28
    transCount: u32,
29 29
    stateFirst: *mut [u32],
30 30
    transNext: *mut [u32],
31 31
    stateCount: u32,
test/tests/prog.sha256.rad +1 -1
2 2
//! SHA-256 (single block).
3 3
//! Implement the SHA-256 compression function for a single 512-bit block.
4 4
//! Verify against a known hash of a short message.
5 5
6 6
/// SHA-256 mutable state: hash values and message schedule.
7 -
record Sha256 {
7 +
record Sha256: Copy {
8 8
    h: [u32; 8],
9 9
    w: [u32; 64],
10 10
}
11 11
12 12
/// SHA-256 initial hash values (first 32 bits of fractional parts of square
test/tests/prog.symtab.rad +3 -3
9 9
constant MAX_SCOPES: u32 = 16;
10 10
constant HASH_SIZE: u32 = 64;
11 11
constant NIL: u32 = 0xFFFFFFFF;
12 12
13 13
/// A symbol entry in the table.
14 -
record Symbol {
14 +
record Symbol: Copy {
15 15
    /// Name of the symbol (hash for comparison).
16 16
    nameHash: u32,
17 17
    /// The value associated with this symbol.
18 18
    value: i32,
19 19
    /// Scope depth at which this symbol was defined.
23 23
    /// Previous symbol with the same name (for shadowing).
24 24
    shadow: u32,
25 25
}
26 26
27 27
/// A scope boundary marker.
28 -
record ScopeMarker {
28 +
record ScopeMarker: Copy {
29 29
    /// Number of symbols when scope was entered.
30 30
    symbolCount: u32,
31 31
}
32 32
33 33
/// The symbol table.
34 -
record SymTab {
34 +
record SymTab: Copy {
35 35
    symbols: *mut [Symbol],
36 36
    symbolCount: u32,
37 37
    scopes: *mut [ScopeMarker],
38 38
    scopeDepth: u32,
39 39
    buckets: *mut [u32],
test/tests/prog.tokenizer.rad +7 -7
4 4
//! then parses and evaluates them. Exercises: tagged unions with payloads,
5 5
//! match statements, optionals, if-let, while-let, for-in loops, records
6 6
//! with slice fields, and complex control flow.
7 7
8 8
/// Token types produced by the lexer.
9 -
union Token {
9 +
union Token: Copy {
10 10
    /// Integer literal with its value.
11 11
    Number(i32),
12 12
    /// +
13 13
    Plus,
14 14
    /// -
26 26
    /// Invalid character.
27 27
    Invalid(u8),
28 28
}
29 29
30 30
/// A lexer state over a byte slice.
31 -
record Lexer {
31 +
record Lexer: Copy {
32 32
    source: *[u8],
33 33
    pos: u32,
34 34
}
35 35
36 36
/// A list of tokens with a fixed-size backing store.
37 -
record TokenList {
37 +
record TokenList: Copy {
38 38
    tokens: *mut [Token],
39 39
    count: u32,
40 40
}
41 41
42 42
/// AST node for parsed expressions.
43 -
union Expr {
43 +
union Expr: Copy {
44 44
    Num(i32),
45 45
    BinOp(BinOpData),
46 46
    Neg(u32),
47 47
}
48 48
49 49
/// Binary operation data.
50 -
record BinOpData {
50 +
record BinOpData: Copy {
51 51
    op: u8,
52 52
    left: u32,
53 53
    right: u32,
54 54
}
55 55
56 56
/// Pool of AST nodes.
57 -
record ExprPool {
57 +
record ExprPool: Copy {
58 58
    nodes: *mut [Expr],
59 59
    count: u32,
60 60
}
61 61
62 62
/// Parser state.
63 -
record Parser {
63 +
record Parser: Copy {
64 64
    tokens: *[Token],
65 65
    tokenCount: u32,
66 66
    pos: u32,
67 67
    pool: *mut ExprPool,
68 68
}
test/tests/prog.vm.rad +4 -4
9 9
constant MAX_CODE: u32 = 256;
10 10
constant MAX_LOCALS: u32 = 16;
11 11
constant MAX_FRAMES: u32 = 8;
12 12
13 13
/// Bytecode instructions.
14 -
union Op {
14 +
union Op: Copy {
15 15
    /// Push an immediate value.
16 16
    Push(i32),
17 17
    /// Pop top two, push sum.
18 18
    Add,
19 19
    /// Pop top two, push difference (second - first).
49 49
    /// Halt execution, top of stack is result.
50 50
    Halt,
51 51
}
52 52
53 53
/// A call frame for function calls.
54 -
record Frame {
54 +
record Frame: Copy {
55 55
    returnAddr: u32,
56 56
    localBase: u32,
57 57
}
58 58
59 59
/// The VM state.
60 -
record VM {
60 +
record VM: Copy {
61 61
    code: *[Op],
62 62
    codeLen: u32,
63 63
    stack: *mut [i32],
64 64
    sp: u32,
65 65
    locals: *mut [i32],
67 67
    frameCount: u32,
68 68
    pc: u32,
69 69
}
70 70
71 71
/// VM error types.
72 -
union VmError {
72 +
union VmError: Copy {
73 73
    /// Stack underflow error.
74 74
    StackUnderflow,
75 75
    /// Stack overflow error.
76 76
    StackOverflow,
77 77
    /// Division by zero.
test/tests/ptr.addressof.field.rad +1 -1
1 -
record Point { x: i32, y: i32 }
1 +
record Point: Copy { x: i32, y: i32 }
2 2
3 3
/// Takes the address of a record field.
4 4
fn addressOfField(p: *Point) -> *i32 {
5 5
    return &p.y;
6 6
}
test/tests/ptr.addressof.local.rad +1 -1
1 -
record Point { x: i32, y: i32 }
1 +
record Point: Copy { x: i32, y: i32 }
2 2
3 3
/// Address of a record local (aggregate) - value is already a pointer.
4 4
fn addressOfRecordLocal() -> *Point {
5 5
    let p = Point { x: 1, y: 2 };
6 6
    return &p;
test/tests/ptr.deref.record.rad +1 -1
1 -
record Point { x: i32, y: i32 }
1 +
record Point: Copy { x: i32, y: i32 }
2 2
3 3
/// Dereferences a record pointer and accesses a field.
4 4
fn derefField(p: *Point) -> i32 {
5 5
    return (*p).x;
6 6
}
test/tests/ptr.eq.rad +1 -1
1 1
//! returns: 0
2 2
3 -
record Point {
3 +
record Point: Copy {
4 4
    x: i32,
5 5
    y: i32,
6 6
}
7 7
8 8
// Test that pointer equality uses address comparison, not value comparison.
test/tests/record.access.rad +1 -1
1 1
//! returns: 0
2 2
3 -
record Vector {
3 +
record Vector: Copy {
4 4
    x: i32,
5 5
    y: i32,
6 6
    z: i32,
7 7
    w: i32,
8 8
}
test/tests/record.alignment.rad +1 -1
1 1
//! returns: 0
2 2
//! Test record alignment computation.
3 3
4 -
record R {
4 +
record R: Copy {
5 5
    a: i32,
6 6
    b: i32,
7 7
}
8 8
9 9
@default fn main() -> i32 {
test/tests/record.array.elements.rad +2 -2
1 1
//! returns: 115
2 2
// Testing structs with array elements.
3 3
4 -
record Vector {
4 +
record Vector: Copy {
5 5
    data: [i32; 4],
6 6
}
7 7
8 -
record Matrix {
8 +
record Matrix: Copy {
9 9
    rows: [Vector; 3],
10 10
}
11 11
12 12
fn vectorSum(vec: Vector) -> i32 {
13 13
    let mut sum: i32 = 0;
test/tests/record.assign.blit.rad +1 -1
1 -
record Point { x: i32, y: i32 }
1 +
record Point: Copy { x: i32, y: i32 }
2 2
3 3
fn copy() {
4 4
    let p = Point { x: 1, y: 2 };
5 5
    let mut q = p;
6 6
    set q = p;
test/tests/record.copy.rad +1 -1
1 1
//! returns: 0
2 2
//! Test record value semantics through copying, mutation, and function calls.
3 -
record S {
3 +
record S: Copy {
4 4
    x: i16,
5 5
    y: i16,
6 6
    z: i16,
7 7
}
8 8
test/tests/record.ctor.tuple.rad +1 -1
1 -
record Pair(i32, i32);
1 +
record Pair: Copy(i32, i32);
2 2
3 3
fn make() -> Pair {
4 4
    return Pair(1, 2);
5 5
}
test/tests/record.empty.eq.rad +1 -1
1 1
// Test empty record equality
2 2
3 -
record Empty {}
3 +
record Empty: Copy {}
4 4
5 5
fn emptyEq(a: Empty, b: Empty) -> bool {
6 6
    return a == b;
7 7
}
8 8
test/tests/record.eq.rad +1 -1
1 1
// Holds two integer fields for equality comparisons.
2 -
record Pair {
2 +
record Pair: Copy {
3 3
    left: i32,
4 4
    right: i32,
5 5
}
6 6
7 7
// Returns true when the two records have identical fields.
test/tests/record.field.assign.rad +1 -1
1 1
//! returns: 0
2 2
3 -
record Vector { x: i32, y: i32 }
3 +
record Vector: Copy { x: i32, y: i32 }
4 4
5 5
@default fn main() -> i32 {
6 6
    let mut v: Vector = Vector { x: 3, y: 2 };
7 7
8 8
    set v.x = 40;
test/tests/record.literal.labeled.rad +1 -1
1 -
record Point { x: i32, y: i32 }
1 +
record Point: Copy { x: i32, y: i32 }
2 2
3 3
fn get() -> i32 {
4 4
    let p = Point { x: 1, y: 2 };
5 5
    return p.x;
6 6
}
test/tests/record.mixed.layout.rad +1 -1
1 -
record Mixed { a: i8, b: i32, c: i16 }
1 +
record Mixed: Copy { a: i8, b: i32, c: i16 }
2 2
3 3
fn get() -> i16 {
4 4
    let m = Mixed { a: 1, b: 2, c: 3 };
5 5
    return m.c;
6 6
}
test/tests/record.nested.calls.2.rad +1 -1
1 1
//! returns: 0
2 2
//! Test nested record function calls with scaling and addition.
3 3
4 -
record Point {
4 +
record Point: Copy {
5 5
    x: i32,
6 6
    y: i32,
7 7
}
8 8
9 9
fn add(p1: Point, p2: Point) -> Point {
test/tests/record.nested.calls.3.rad +1 -1
1 1
//! returns: 0
2 2
//! Test record passing through multiple nested function calls.
3 3
4 -
record Point {
4 +
record Point: Copy {
5 5
    x: i32,
6 6
    y: i32,
7 7
}
8 8
9 9
fn id(x: i32) -> i32 {
test/tests/record.nested.eq.rad +2 -2
1 1
// Test nested aggregate equality
2 2
3 -
record Inner {
3 +
record Inner: Copy {
4 4
    x: i32,
5 5
    y: i32,
6 6
}
7 7
8 -
record Outer {
8 +
record Outer: Copy {
9 9
    a: Inner,
10 10
    b: i32,
11 11
}
12 12
13 13
fn innerEq(a: Inner, b: Inner) -> bool {
test/tests/record.nested.lit.rad +2 -2
1 -
record Inner { a: i32, b: i32 }
2 -
record Outer { inner: Inner, c: i32 }
1 +
record Inner: Copy { a: i32, b: i32 }
2 +
record Outer: Copy { inner: Inner, c: i32 }
3 3
4 4
fn get() -> i32 {
5 5
    let o = Outer { inner: Inner { a: 1, b: 2 }, c: 3 };
6 6
    return o.inner.b;
7 7
}
test/tests/record.param.lit.rad +2 -2
1 1
//! returns: 0
2 2
// Test passing record literals directly into a function.
3 3
4 -
record Vector {
4 +
record Vector: Copy {
5 5
    x: i32,
6 6
    y: i32,
7 7
    z: i32
8 8
}
9 9
10 -
record Point {
10 +
record Point: Copy {
11 11
    x: i32,
12 12
    y: i32,
13 13
}
14 14
15 15
fn compute(v: Vector, p: Point) -> i32 {
test/tests/record.ptr.access.rad +1 -1
1 1
//! returns: 0
2 2
3 -
record Point {
3 +
record Point: Copy {
4 4
    x: i32,
5 5
    y: i32,
6 6
}
7 7
8 8
@default fn main() -> i32 {
test/tests/record.ptr.mutate.rad +1 -1
1 1
//! returns: 42
2 2
3 -
record Point {
3 +
record Point: Copy {
4 4
    x: i32,
5 5
    y: i32,
6 6
}
7 7
8 8
fn mutate(p: *mut Point) {
test/tests/record.shorthand.rad +2 -2
1 1
//! returns: 1
2 2
//! Test shorthand field syntax in record literals and patterns.
3 3
4 -
record Point {
4 +
record Point: Copy {
5 5
    x: i32,
6 6
    y: i32,
7 7
}
8 8
9 -
union Shape {
9 +
union Shape: Copy {
10 10
    circle(i32),
11 11
    rectangle { width: i32, height: i32 },
12 12
}
13 13
14 14
/// Test shorthand record literal construction.
test/tests/record.unlabeled.deref.rad +4 -4
1 1
//! returns: 0
2 2
// Test deref behavior for single-field unlabeled records.
3 3
4 -
record Wrap(i32);
5 -
record WrapBool(bool);
6 -
record WrapU8(u8);
7 -
record WrapU64(u64);
4 +
record Wrap: Copy(i32);
5 +
record WrapBool: Copy(bool);
6 +
record WrapU8: Copy(u8);
7 +
record WrapU64: Copy(u64);
8 8
9 9
fn getInner(w: Wrap) -> i32 {
10 10
    return *w;
11 11
}
12 12
test/tests/record.unlabeled.rad +2 -2
1 1
//! returns: 1
2 2
// TODO: Test destructuring.
3 3
4 -
record S(i32);
5 -
record R(bool, u8);
4 +
record S: Copy(i32);
5 +
record R: Copy(bool, u8);
6 6
7 7
fn passS(s: S) -> S {
8 8
    return s;
9 9
}
10 10
test/tests/reserve.loop.rad +1 -1
1 1
//! returns: 0
2 -
record Pair { a: i32, b: i32 }
2 +
record Pair: Copy { a: i32, b: i32 }
3 3
4 4
@default fn main() -> i32 {
5 5
    let mut total: i32 = 0;
6 6
    let mut i: i32 = 0;
7 7
    while i < 10000 {
test/tests/result.void.success.rad +1 -1
1 1
//! returns: 0
2 2
//! Test that no-result success returns work correctly.
3 3
4 -
union TestError {
4 +
union TestError: Copy {
5 5
    Failed,
6 6
}
7 7
8 8
fn voidSuccess() throws (TestError) {
9 9
    return;
test/tests/set.keyword.rad +1 -1
1 1
//! returns: 0
2 2
//! Test the `set` keyword for assignment statements.
3 3
4 -
record Point {
4 +
record Point: Copy {
5 5
    x: i32,
6 6
    y: i32,
7 7
}
8 8
9 9
@default fn main() -> i32 {
test/tests/slice.append.rad +2 -2
1 1
//! returns: 0
2 2
//! Test slice .append() method with inline allocator.
3 3
4 4
/// Simple bump allocator for testing.
5 -
record Arena {
5 +
record Arena: Copy {
6 6
    data: *mut [u8],
7 7
    offset: u32,
8 8
}
9 9
10 10
fn newArena(data: *mut [u8]) -> Arena {
22 22
23 23
    return base as *mut opaque;
24 24
}
25 25
26 26
/// Allocator record matching the compiler's expected layout.
27 -
record Allocator {
27 +
record Allocator: Copy {
28 28
    func: fn(*mut opaque, u32, u32) -> *mut opaque,
29 29
    ctx: *mut opaque,
30 30
}
31 31
32 32
fn arenaAllocFn(ctx: *mut opaque, size: u32, al: u32) -> *mut opaque {
test/tests/spill.blockarg.clobber.rad +1 -1
31 31
32 32
fn ssig1(x: u32) -> u32 {
33 33
    return rotr(x, 17) ^ rotr(x, 19) ^ (x >> 10);
34 34
}
35 35
36 -
record State {
36 +
record State: Copy {
37 37
    h: [u32; 8],
38 38
    w: [u32; 64],
39 39
}
40 40
41 41
fn prepareSchedule(s: *mut State, block: *[u32]) {
test/tests/spill.loop.rad +1 -1
1 1
//! returns: 0
2 2
//! Test register spilling in a loop with many live values.
3 3
//! Computes record layout from field tags, exercising alignment and offset
4 4
//! calculations that require multiple simultaneously live registers.
5 5
6 -
record Layout {
6 +
record Layout: Copy {
7 7
    size: u32,
8 8
    alignment: u32,
9 9
}
10 10
11 11
fn getLayout(tag: u8) -> Layout {
test/tests/static.record.array.rad +1 -1
1 1
//! returns: 0
2 2
//! Test mutating a static record containing an array.
3 3
4 -
record Record {
4 +
record Record: Copy {
5 5
    values: [i32; 4],
6 6
    bias: i32,
7 7
}
8 8
9 9
static TABLE: Record = Record {
test/tests/static.slice.index.assign.rad +2 -2
1 1
//! returns: 42
2 2
3 -
record Inner {
3 +
record Inner: Copy {
4 4
    value: i32,
5 5
}
6 6
7 -
record Container {
7 +
record Container: Copy {
8 8
    pad: i32,
9 9
    items: [Inner; 3],
10 10
}
11 11
12 12
static container: Container = undefined;
test/tests/static.slice.offset.rad +2 -2
1 1
//! returns: 0
2 2
//! Test slice indexing into a static table with offsets.
3 3
4 -
record Entry {
4 +
record Entry: Copy {
5 5
    a: i32,
6 6
    b: i32,
7 7
}
8 8
9 -
record Table {
9 +
record Table: Copy {
10 10
    scratch: *mut [Entry],
11 11
    entries: *mut [Entry],
12 12
    len: u32,
13 13
}
14 14
test/tests/static.zero.bss.rad +2 -2
1 1
//! returns: 0
2 2
//! rw-data-size: 0
3 3
4 4
/// Static pool used to verify that partially initialized zero data is BSS.
5 -
record Pool {
5 +
record Pool: Copy {
6 6
    /// Undefined slice table.
7 7
    table: [*[u8]; 32],
8 8
    /// Explicitly initialized count.
9 9
    count: u32,
10 10
}
11 11
12 12
/// Padded record used to verify that zero fields plus undefined padding are BSS.
13 -
record Padded {
13 +
record Padded: Copy {
14 14
    /// Leading byte.
15 15
    tag: u8,
16 16
    /// Field that forces padding after `tag`.
17 17
    value: u32,
18 18
}
test/tests/switch.blockargs.clobber.rad +1 -1
9 9
//! In the isel, `emitBlockArgs` for case 0 writes `1` (the merge arg) to the
10 10
//! merge block's parameter register.  If the register allocator assigned that
11 11
//! parameter register to the same physical register as the switch value, the
12 12
//! tag is clobbered before subsequent case comparisons run.
13 13
14 -
union Val {
14 +
union Val: Copy {
15 15
    none,
16 16
    empty,
17 17
    data(i32),
18 18
}
19 19
test/tests/trait.aggregate.ret.rad +3 -3
2 2
//! Test trait methods returning aggregate types.
3 3
//!
4 4
//! Exercises: trait methods that return structs (both small and larger
5 5
//! than pointer size) and optionals via v-table dispatch.
6 6
7 -
record Point {
7 +
record Point: Copy {
8 8
    x: i32,
9 9
    y: i32,
10 10
}
11 11
12 -
record Vec3 {
12 +
record Vec3: Copy {
13 13
    x: i32,
14 14
    y: i32,
15 15
    z: i32,
16 16
}
17 17
19 19
    fn (*Geometry) origin() -> Point;
20 20
    fn (*Geometry) center() -> Vec3;
21 21
    fn (*Geometry) maybe() -> ?i32;
22 22
}
23 23
24 -
record Circle {
24 +
record Circle: Copy {
25 25
    cx: i32,
26 26
    cy: i32,
27 27
    radius: i32,
28 28
}
29 29
test/tests/trait.array.optional.rad +2 -2
2 2
//! Test trait objects stored in arrays and used as optional values.
3 3
//!
4 4
//! Exercises: arrays of trait objects dispatched in loops,
5 5
//! and optional trait object values.
6 6
7 -
record Adder {
7 +
record Adder: Copy {
8 8
    n: i32,
9 9
}
10 10
11 -
record Multiplier {
11 +
record Multiplier: Copy {
12 12
    n: i32,
13 13
}
14 14
15 15
trait Transform {
16 16
    fn (*Transform) apply(x: i32) -> i32;
test/tests/trait.basic.rad +1 -1
1 1
//! returns: 0
2 2
//! Basic trait object dispatch test.
3 -
record Counter {
3 +
record Counter: Copy {
4 4
    value: i32,
5 5
}
6 6
7 7
trait Adder {
8 8
    fn (*mut Adder) add(n: i32) -> i32;
test/tests/trait.control.flow.rad +1 -1
3 3
//!
4 4
//! Exercises: trait method calls inside loops and conditionals,
5 5
//! verifying that vtable dispatch works correctly when interleaved
6 6
//! with branching and iteration.
7 7
8 -
record Counter {
8 +
record Counter: Copy {
9 9
    value: i32,
10 10
}
11 11
12 12
trait Stepper {
13 13
    fn (*mut Stepper) step() -> i32;
test/tests/trait.dispatch.rad +1 -1
1 -
record Acc {
1 +
record Acc: Copy {
2 2
    n: i32,
3 3
}
4 4
5 5
trait Ops {
6 6
    fn (*Ops) get() -> i32;
test/tests/trait.fn.param.rad +2 -2
2 2
//! Test passing trait objects as function parameters.
3 3
//!
4 4
//! Exercises: trait objects passed to non-method functions, including
5 5
//! both mutable and immutable trait object pointers as arguments.
6 6
7 -
record Circle {
7 +
record Circle: Copy {
8 8
    radius: i32,
9 9
}
10 10
11 -
record Square {
11 +
record Square: Copy {
12 12
    side: i32,
13 13
}
14 14
15 15
trait Shape {
16 16
    fn (*Shape) area() -> i32;
test/tests/trait.multiple.methods.rad +1 -1
3 3
//!
4 4
//! Exercises: a trait declaring multiple methods with void, bool, and
5 5
//! integer return types. Verifies vtable layout with more than one
6 6
//! entry and correct dispatch for each method.
7 7
8 -
record Accumulator {
8 +
record Accumulator: Copy {
9 9
    total: i32,
10 10
}
11 11
12 12
trait Collector {
13 13
    fn (*mut Collector) add(n: i32) -> i32;
test/tests/trait.multiple.traits.rad +1 -1
3 3
//!
4 4
//! Exercises: a single concrete type that provides instances for
5 5
//! two different traits. Each trait object dispatches independently
6 6
//! through its own vtable.
7 7
8 -
record Counter {
8 +
record Counter: Copy {
9 9
    value: i32,
10 10
}
11 11
12 12
trait Incrementable {
13 13
    fn (*mut Incrementable) inc() -> i32;
test/tests/trait.multiple.types.rad +2 -2
3 3
//!
4 4
//! Exercises: two different concrete types implementing the same trait,
5 5
//! coerced to the same trait object type. Also tests immutable `*Trait`
6 6
//! receivers. Verifies each type dispatches through its own vtable.
7 7
8 -
record Dog {
8 +
record Dog: Copy {
9 9
    age: i32,
10 10
}
11 11
12 -
record Cat {
12 +
record Cat: Copy {
13 13
    lives: i32,
14 14
}
15 15
16 16
trait Speaker {
17 17
    fn (*Speaker) speak() -> i32;
test/tests/trait.object.rad +1 -1
1 -
record Counter {
1 +
record Counter: Copy {
2 2
    value: i32,
3 3
}
4 4
5 5
trait Adder {
6 6
    fn (*mut Adder) add(n: i32) -> i32;
test/tests/trait.supertrait.forward.rad +1 -1
7 7
8 8
trait Parent {
9 9
    fn (*Parent) parent() -> i32;
10 10
}
11 11
12 -
record Value {
12 +
record Value: Copy {
13 13
    n: i32,
14 14
}
15 15
16 16
instance Parent for Value {
17 17
    fn (self: *Value) parent() -> i32 {
test/tests/trait.supertrait.rad +1 -1
18 18
19 19
trait ReadWriter: Reader + Writer {
20 20
    fn (*mut ReadWriter) flush() -> i32;
21 21
}
22 22
23 -
record Socket {
23 +
record Socket: Copy {
24 24
    rbuf: [u8; 32],
25 25
    rpos: i32,
26 26
    rlen: i32,
27 27
    wbuf: [u8; 32],
28 28
    wpos: i32,
test/tests/trait.throws.rad +2 -2
1 1
//! returns: 0
2 2
//! Test trait methods with throws clauses.
3 3
4 -
union ParseError {
4 +
union ParseError: Copy {
5 5
    InvalidInput,
6 6
    Overflow,
7 7
}
8 8
9 -
record StrictParser {
9 +
record StrictParser: Copy {
10 10
    limit: i32,
11 11
}
12 12
13 13
trait Parser {
14 14
    fn (*Parser) parse(n: i32) -> i32 throws (ParseError);
test/tests/trait.writer.rad +2 -2
10 10
    fn (*mut Writer) write(data: *[u8]) -> i32;
11 11
    fn (*Writer) total() -> i32;
12 12
}
13 13
14 14
/// Writes bytes into a fixed-size buffer.
15 -
record BufferWriter {
15 +
record BufferWriter: Copy {
16 16
    buf: [u8; 64],
17 17
    pos: i32,
18 18
}
19 19
20 20
instance Writer for BufferWriter {
35 35
        return w.pos;
36 36
    }
37 37
}
38 38
39 39
/// Counts bytes written through it, forwarding to an inner writer.
40 -
record CountingWriter {
40 +
record CountingWriter: Copy {
41 41
    inner: *mut opaque Writer,
42 42
    count: i32,
43 43
}
44 44
45 45
instance Writer for CountingWriter {
test/tests/try.basic.rad +1 -1
1 1
/// Error type for fallible functions.
2 -
union Error { Boom }
2 +
union Error: Copy { Boom }
3 3
4 4
/// Return a value or throw an error based on the flag.
5 5
fn fallible(flag: bool) -> i32 throws (Error) {
6 6
    if flag {
7 7
        throw Error::Boom();
test/tests/try.catch.rad +1 -1
1 1
/// Ensure `try` with catch block diverges from the error path.
2 -
union Error { Boom }
2 +
union Error: Copy { Boom }
3 3
4 4
fn fallible(flag: bool) -> i32 throws (Error) {
5 5
    if flag {
6 6
        throw Error::Boom();
7 7
    }
test/tests/try.optional.rad +1 -1
1 1
/// Error type for fallible functions.
2 -
union Error { Boom }
2 +
union Error: Copy { Boom }
3 3
4 4
/// Return a value or throw an error based on the flag.
5 5
fn fallible(flag: bool) -> i32 throws (Error) {
6 6
    if flag {
7 7
        throw Error::Boom();
test/tests/try.panic.rad +1 -1
1 1
/// Error type for fallible functions.
2 -
union Error { Boom }
2 +
union Error: Copy { Boom }
3 3
4 4
/// Return a value or throw an error based on the flag.
5 5
fn fallible(flag: bool) -> i32 throws (Error) {
6 6
    if flag {
7 7
        throw Error::Boom();
test/tests/undefined.aggregate.rad +1 -1
1 1
/// Test lowering of undefined for aggregate types.
2 -
record Point { x: i32, y: i32 }
2 +
record Point: Copy { x: i32, y: i32 }
3 3
4 4
fn test() -> i32 {
5 5
    let p: Point = undefined;
6 6
    return p.x;
7 7
}
test/tests/undefined.record.field.rad +3 -3
8 8
///
9 9
/// The fix skips stores for `undefined` fields entirely, leaving the
10 10
/// memory uninitialised.  Covers both named-field syntax (lowerRecordFields)
11 11
/// and positional constructor syntax (lowerRecordCtor).
12 12
13 -
record Small {
13 +
record Small: Copy {
14 14
    x: i32,
15 15
    y: i32,
16 16
}
17 17
18 -
record WithArray {
18 +
record WithArray: Copy {
19 19
    data: [i32; 4],
20 20
    len: i32,
21 21
}
22 22
23 23
/// Construct a record with one field `undefined` (named-field syntax).
38 38
fn fullInit() -> i32 {
39 39
    let s = Small { x: 10, y: 20 };
40 40
    return s.x + s.y;
41 41
}
42 42
43 -
union Tagged {
43 +
union Tagged: Copy {
44 44
    A { data: [i32; 4], tag: i32 },
45 45
    B,
46 46
}
47 47
48 48
/// Union variant with record payload containing an `undefined` field.
test/tests/union-tag.rad +1 -1
1 1
//! returns: 0
2 2
//! Test that union variant tags are stored correctly for unions with many variants.
3 3
4 -
union BigUnion {
4 +
union BigUnion: Copy {
5 5
    V0,
6 6
    V1,
7 7
    V2,
8 8
    V3,
9 9
    V4,
test/tests/union.bitfield.rad +1 -1
1 1
//! returns: 42
2 2
3 -
union Attribute {
3 +
union Attribute: Copy {
4 4
    None = 0b0,
5 5
    Export = 0b10,
6 6
    Default = 0b100,
7 7
    Extern = 0b1000,
8 8
    Test = 0b10000,
test/tests/union.ctor.rad +2 -2
1 1
/// Union with tag-only variants.
2 -
union Color { Red, Green, Blue }
2 +
union Color: Copy { Red, Green, Blue }
3 3
4 4
/// Union with and without payload variants.
5 -
union MaybeInt { None, Some(i32) }
5 +
union MaybeInt: Copy { None, Some(i32) }
6 6
7 7
/// Builds a tag-only union variant.
8 8
fn makeRed() -> Color {
9 9
    return Color::Red;
10 10
}
test/tests/union.discriminant.cast.rad +1 -1
1 1
//! returns: 0
2 2
//! Casting a void-union variant to `i32` should produce its explicit tag value.
3 3
4 -
union U {
4 +
union U: Copy {
5 5
    A = 7,
6 6
    B = 42,
7 7
    C = 255,
8 8
}
9 9
test/tests/union.edge.case.2.rad +1 -1
1 1
//! returns: 0
2 2
3 -
union NodeValue {
3 +
union NodeValue: Copy {
4 4
    Placeholder,
5 5
    Nil,
6 6
    Bool(bool),
7 7
}
8 8
test/tests/union.edge.case.3.rad +2 -2
1 1
//! returns: 0
2 2
//! Test union match through pointer dereference.
3 3
4 -
union Node {
4 +
union Node: Copy {
5 5
    Placeholder(u8),
6 6
    Nil(u8),
7 7
    Bool(bool),
8 8
}
9 9
10 -
record Parser {
10 +
record Parser: Copy {
11 11
    root: Node,
12 12
}
13 13
14 14
fn node(p: *mut Parser, value: Node) -> *Node {
15 15
    set p.root = value;
test/tests/union.eq.rad +4 -4
1 1
/// Void union - only tag comparison needed.
2 -
union Color { Red, Green, Blue }
2 +
union Color: Copy { Red, Green, Blue }
3 3
4 4
/// Union with one payload variant.
5 -
union MaybeInt { None, Some(i32) }
5 +
union MaybeInt: Copy { None, Some(i32) }
6 6
7 7
/// Union with multiple different payload types.
8 -
union Value { Empty, Int(i32), Byte(u8) }
8 +
union Value: Copy { Empty, Int(i32), Byte(u8) }
9 9
10 10
/// Union with multi-field record payload.
11 -
union Shape { None, Point { x: i32, y: i32 } }
11 +
union Shape: Copy { None, Point { x: i32, y: i32 } }
12 12
13 13
/// Compare void unions (tags only).
14 14
fn colorEq(a: Color, b: Color) -> bool {
15 15
    return a == b;
16 16
}
test/tests/union.eq.void.ctor.rad +1 -1
1 1
//! Regression test: comparing a void union variable against a constructor
2 2
//! literal must compare tag scalars directly, not load through a pointer.
3 3
4 -
union Op { Add, Sub, Mul }
4 +
union Op: Copy { Add, Sub, Mul }
5 5
6 6
/// Compare void union against constructor literal.
7 7
fn isAdd(op: Op) -> bool {
8 8
    return op == Op::Add;
9 9
}

16 more files not shown.