compiler: Fix initialization of optional constants
9b9efcd0064af631db73a629576a9a3586e7c87a812d28a456799e3f8827b85f
Lower optional constant coercions with their resolved tag, padding, and payload layout. Handle nil constants and repeated optional elements through the same payload path. This produces initialized static data for nullable and tagged optional representations.
1 parent
7f92c2e1
lib/std/lang/lower.rad
+74 -61
| 1419 | 1419 | } |
|
| 1420 | 1420 | dataSliceHeader(b, dataName, arrInfo.length); |
|
| 1421 | 1421 | } |
|
| 1422 | 1422 | ||
| 1423 | 1423 | /// Lower a constant expression payload into a builder without slot padding. |
|
| 1424 | - | /// Compute the type layout only when undefined data needs a byte count. |
|
| 1424 | + | /// Return the number of appended data entries, not bytes or array elements. |
|
| 1425 | + | /// Compute the type layout when nil or undefined data needs a byte count. |
|
| 1425 | 1426 | unsafe fn lowerConstDataPayloadInto( |
|
| 1426 | 1427 | self: &mut Lowerer, |
|
| 1427 | 1428 | node: *ast::Node, |
|
| 1428 | 1429 | ty: resolver::Type, |
|
| 1429 | 1430 | dataPrefix: *[u8], |
|
| 1430 | 1431 | b: &mut DataValueBuilder |
|
| 1431 | - | ) throws (LowerError) { |
|
| 1432 | + | ) -> u32 throws (LowerError) { |
|
| 1433 | + | let start = b.values.len; |
|
| 1434 | + | // Optional coercions define the tag and payload layout of constant values. |
|
| 1435 | + | if let case resolver::Type::Optional(inner) = ty { |
|
| 1436 | + | if let coercion = resolver::coercionFor(self.resolver, node) { |
|
| 1437 | + | if let case resolver::Coercion::OptionalLift(target) = coercion; resolver::typesEqual(target, ty) { |
|
| 1438 | + | let layout = resolver::getTypeLayout(ty); |
|
| 1439 | + | let mut offset: u32 = 0; |
|
| 1440 | + | if resolver::isOptionalAggregate(ty) { |
|
| 1441 | + | set offset = resolver::getOptionalValOffset(*inner); |
|
| 1442 | + | dataBuilderPush(b, il::DataValue { |
|
| 1443 | + | item: il::DataItem::Val { typ: il::Type::W8, val: 1 }, count: 1, |
|
| 1444 | + | }); |
|
| 1445 | + | if offset > 1 { |
|
| 1446 | + | dataBuilderPush(b, il::DataValue { item: il::DataItem::Undef, count: offset - 1 }); |
|
| 1447 | + | } |
|
| 1448 | + | } |
|
| 1449 | + | try lowerConstDataInto(self, node, *inner, layout.size - offset, dataPrefix, b); |
|
| 1450 | + | return b.values.len - start; |
|
| 1451 | + | } |
|
| 1452 | + | } |
|
| 1453 | + | } |
|
| 1432 | 1454 | // Function pointer references in constant data. |
|
| 1433 | 1455 | if let case resolver::Type::Fn(_) = ty { |
|
| 1434 | 1456 | let sym = resolver::nodeData(self.resolver, node).sym |
|
| 1435 | 1457 | else throw LowerError::MissingSymbol(node); |
|
| 1436 | 1458 | let modId = resolver::moduleIdForSymbol(self.resolver, sym); |
|
| 1437 | 1459 | let qualName = qualifyName(self, modId, sym.name); |
|
| 1438 | 1460 | dataBuilderPush(b, il::DataValue { |
|
| 1439 | 1461 | item: il::DataItem::Fn(qualName), count: 1, |
|
| 1440 | 1462 | }); |
|
| 1441 | - | return; |
|
| 1463 | + | return b.values.len - start; |
|
| 1442 | 1464 | } |
|
| 1443 | 1465 | // In constant data, a void variant of a mixed union still occupies the |
|
| 1444 | 1466 | // full tagged-union slot. Emitting only the tag corrupts following fields. |
|
| 1445 | 1467 | if let sym = resolver::nodeData(self.resolver, node).sym { |
|
| 1446 | 1468 | if let case resolver::SymbolData::Variant { type: resolver::Type::Void, .. } = sym.data { |
|
| 1447 | 1469 | if let case resolver::Type::Nominal(resolver::NominalType::Union(_)) = ty { |
|
| 1448 | 1470 | try lowerConstUnionVariantInto(self, node, sym, ty, &mut [], dataPrefix, b); |
|
| 1449 | - | return; |
|
| 1471 | + | return b.values.len - start; |
|
| 1450 | 1472 | } |
|
| 1451 | 1473 | } |
|
| 1452 | 1474 | } |
|
| 1453 | 1475 | match node.value { |
|
| 1476 | + | case ast::NodeValue::Nil => { |
|
| 1477 | + | let case resolver::Type::Optional(_) = ty else throw LowerError::NilInNonOptional; |
|
| 1478 | + | let layout = resolver::getTypeLayout(ty); |
|
| 1479 | + | dataBuilderPush(b, il::DataValue { |
|
| 1480 | + | item: il::DataItem::Val { typ: il::Type::W8, val: 0 }, count: layout.size, |
|
| 1481 | + | }); |
|
| 1482 | + | }, |
|
| 1454 | 1483 | case ast::NodeValue::Undef => { |
|
| 1455 | 1484 | let layout = resolver::getTypeLayout(ty); |
|
| 1456 | 1485 | dataBuilderPush(b, il::DataValue { |
|
| 1457 | 1486 | item: il::DataItem::Undef, |
|
| 1458 | 1487 | count: layout.size |
| 1500 | 1529 | else => { |
|
| 1501 | 1530 | // Scalar values: integers, bools, strings, void union variants, etc. |
|
| 1502 | 1531 | try lowerConstScalarDataInto(self, node, ty, dataPrefix, b); |
|
| 1503 | 1532 | } |
|
| 1504 | 1533 | } |
|
| 1534 | + | return b.values.len - start; |
|
| 1505 | 1535 | } |
|
| 1506 | 1536 | ||
| 1507 | 1537 | /// Lower a constant expression into a builder, padding to the given slot size. |
|
| 1508 | 1538 | unsafe fn lowerConstDataInto( |
|
| 1509 | 1539 | self: &mut Lowerer, |
| 1539 | 1569 | try lowerConstDataPayloadInto(self, elem, elemTy, dataPrefix, b); |
|
| 1540 | 1570 | } |
|
| 1541 | 1571 | } |
|
| 1542 | 1572 | ||
| 1543 | 1573 | /// Build data values for a constant array repeat literal `[item; count]`. |
|
| 1544 | - | /// Repeat element payloads without extra slot padding. Undefined data uses |
|
| 1545 | - | /// the element layout to compute the total byte count. |
|
| 1574 | + | /// Repeat element payloads without extra slot padding. Undefined payloads |
|
| 1575 | + | /// already carry their element size in bytes. |
|
| 1546 | 1576 | unsafe fn lowerConstArrayRepeatInto( |
|
| 1547 | 1577 | self: &mut Lowerer, |
|
| 1548 | 1578 | repeat: ast::ArrayRepeatLit, |
|
| 1549 | 1579 | ty: resolver::Type, |
|
| 1550 | 1580 | dataPrefix: *[u8], |
| 1553 | 1583 | let case resolver::Type::Array(arrInfo) = ty |
|
| 1554 | 1584 | else throw LowerError::ExpectedArray; |
|
| 1555 | 1585 | let length = arrInfo.length; |
|
| 1556 | 1586 | let elemTy = *arrInfo.item; |
|
| 1557 | 1587 | ||
| 1558 | - | if let case ast::NodeValue::Undef = repeat.item.value { |
|
| 1559 | - | let elemLayout = resolver::getTypeLayout(elemTy); |
|
| 1560 | - | dataBuilderPush(b, il::DataValue { |
|
| 1561 | - | item: il::DataItem::Undef, |
|
| 1562 | - | count: elemLayout.size * length |
|
| 1563 | - | }); |
|
| 1564 | - | } else if let val = resolver::constValueEntry(self.resolver, repeat.item) { |
|
| 1565 | - | if let case resolver::ConstValue::String(_) = val { |
|
| 1566 | - | // A string used as a slice is represented by a three-word slice |
|
| 1567 | - | // header, not by the bytes of the string itself. |
|
| 1568 | - | for _ in 0..length { |
|
| 1569 | - | try lowerConstDataPayloadInto(self, repeat.item, elemTy, dataPrefix, b); |
|
| 1570 | - | } |
|
| 1571 | - | } else { |
|
| 1572 | - | dataBuilderPush(b, il::DataValue { |
|
| 1573 | - | item: constValueToDataItem(self, val, elemTy), |
|
| 1574 | - | count: length |
|
| 1575 | - | }); |
|
| 1576 | - | } |
|
| 1588 | + | if length == 0 { |
|
| 1589 | + | return; |
|
| 1590 | + | } |
|
| 1591 | + | let elementStart = b.values.len; |
|
| 1592 | + | let entryCount = try lowerConstDataPayloadInto(self, repeat.item, elemTy, dataPrefix, b); |
|
| 1593 | + | if entryCount == 1 { |
|
| 1594 | + | set b.values[elementStart].count *= length; |
|
| 1577 | 1595 | } else { |
|
| 1578 | - | for _ in 0..length { |
|
| 1579 | - | try lowerConstDataPayloadInto(self, repeat.item, elemTy, dataPrefix, b); |
|
| 1596 | + | // A string used as a slice is represented by a slice header, not by |
|
| 1597 | + | // the bytes of the string itself. Repeat complete payloads in order. |
|
| 1598 | + | for _ in 1..length { |
|
| 1599 | + | for i in 0..entryCount { |
|
| 1600 | + | let value = b.values[elementStart + i]; |
|
| 1601 | + | dataBuilderPush(b, value); |
|
| 1602 | + | } |
|
| 1580 | 1603 | } |
|
| 1581 | 1604 | } |
|
| 1582 | 1605 | } |
|
| 1583 | 1606 | ||
| 1584 | 1607 | /// Build data values for a constant record literal. |
| 4233 | 4256 | } |
|
| 4234 | 4257 | } |
|
| 4235 | 4258 | return il::Val::Reg(dst); |
|
| 4236 | 4259 | } |
|
| 4237 | 4260 | ||
| 4238 | - | /// Wrap a value in an optional type. |
|
| 4261 | + | /// Build a present or absent optional value. |
|
| 4239 | 4262 | /// |
|
| 4240 | - | /// For optional pointers (`?*T`), the value is returned as-is since pointers |
|
| 4241 | - | /// use zero to represent `nil`. For other optionals, builds a tagged aggregate. |
|
| 4242 | - | /// with the tag set to `1`, and the value as payload. |
|
| 4243 | - | unsafe fn wrapInOptional(self: &mut FnLowerer, val: il::Val, optType: resolver::Type) -> il::Val throws (LowerError) { |
|
| 4263 | + | /// Optional pointers and slices use present values as-is, since valid values |
|
| 4264 | + | /// have non-null data pointers. Absent values use a null pointer or zero slice. |
|
| 4265 | + | /// Other optionals use a tagged aggregate with tag `1` and a payload when |
|
| 4266 | + | /// present, or tag `0` with no payload when absent. |
|
| 4267 | + | unsafe fn buildOptional(self: &mut FnLowerer, val: ?il::Val, optType: resolver::Type) -> il::Val throws (LowerError) { |
|
| 4244 | 4268 | let case resolver::Type::Optional(inner) = optType else { |
|
| 4245 | 4269 | throw LowerError::ExpectedOptional; |
|
| 4246 | 4270 | }; |
|
| 4247 | 4271 | // Null-pointer-optimized (NPO) types are used as-is -- valid values are never null. |
|
| 4248 | - | if resolver::isNullableType(*inner) { |
|
| 4249 | - | return val; |
|
| 4272 | + | if resolver::isOptionalPointer(optType) { |
|
| 4273 | + | if let val = val { |
|
| 4274 | + | return val; |
|
| 4275 | + | } |
|
| 4276 | + | if let case resolver::Type::Slice { item, mutable, .. } = *inner { |
|
| 4277 | + | return try buildSliceValue( |
|
| 4278 | + | self, item, mutable, il::Val::Imm(0), il::Val::Imm(0), il::Val::Imm(0) |
|
| 4279 | + | ); |
|
| 4280 | + | } |
|
| 4281 | + | return il::Val::Imm(0); |
|
| 4250 | 4282 | } |
|
| 4251 | 4283 | let layout = resolver::getTypeLayout(optType); |
|
| 4252 | 4284 | let valOffset = resolver::getOptionalValOffset(*inner) as i32; |
|
| 4253 | 4285 | ||
| 4254 | - | return try buildTagged(self, layout, 1, val, *inner, 1, valOffset); |
|
| 4255 | - | } |
|
| 4256 | - | ||
| 4257 | - | /// Build a `nil` value for an optional type. |
|
| 4258 | - | /// |
|
| 4259 | - | /// For optional pointers (`?*T`), returns an immediate `0` (null pointer). |
|
| 4260 | - | /// For other optionals, builds a tagged aggregate with tag set to `0` (absent). |
|
| 4261 | - | unsafe fn buildNilOptional(self: &mut FnLowerer, optType: resolver::Type) -> il::Val throws (LowerError) { |
|
| 4262 | - | let case resolver::Type::Optional(inner) = optType |
|
| 4263 | - | else throw LowerError::ExpectedOptional; |
|
| 4264 | - | if let case resolver::Type::Pointer { .. } = *inner { |
|
| 4265 | - | return il::Val::Imm(0); |
|
| 4266 | - | } |
|
| 4267 | - | if let case resolver::Type::Slice { item, mutable, .. } = *inner { |
|
| 4268 | - | return try buildSliceValue( |
|
| 4269 | - | self, item, mutable, il::Val::Imm(0), il::Val::Imm(0), il::Val::Imm(0) |
|
| 4270 | - | ); |
|
| 4271 | - | } |
|
| 4272 | - | let valOffset = resolver::getOptionalValOffset(*inner) as i32; |
|
| 4273 | - | return try buildTagged(self, resolver::getTypeLayout(optType), 0, nil, *inner, 1, valOffset); |
|
| 4286 | + | return try buildTagged(self, layout, 1 if val <> nil else 0, val, *inner, 1, valOffset); |
|
| 4274 | 4287 | } |
|
| 4275 | 4288 | ||
| 4276 | 4289 | /// Build a result value for throwing functions. |
|
| 4277 | 4290 | unsafe fn buildResult( |
|
| 4278 | 4291 | self: &mut FnLowerer, |
| 6025 | 6038 | // Aggregate types require element-wise comparison. |
|
| 6026 | 6039 | // When comparing `?T` with `T`, wrap the scalar side. |
|
| 6027 | 6040 | if isAggregateType(leftTy) { |
|
| 6028 | 6041 | let mut rhs = b; |
|
| 6029 | 6042 | if not isAggregateType(rightTy) { |
|
| 6030 | - | set rhs = try wrapInOptional(self, rhs, leftTy); |
|
| 6043 | + | set rhs = try buildOptional(self, rhs, leftTy); |
|
| 6031 | 6044 | } |
|
| 6032 | 6045 | return try emitAggregateEqOp(self, binop.op, leftTy, a, rhs); |
|
| 6033 | 6046 | } |
|
| 6034 | 6047 | if isAggregateType(rightTy) { |
|
| 6035 | - | let lhs = try wrapInOptional(self, a, rightTy); |
|
| 6048 | + | let lhs = try buildOptional(self, a, rightTy); |
|
| 6036 | 6049 | return try emitAggregateEqOp(self, binop.op, rightTy, lhs, b); |
|
| 6037 | 6050 | } |
|
| 6038 | 6051 | set resultTy = scalarComparisonType(leftTy, rightTy); |
|
| 6039 | 6052 | } |
|
| 6040 | 6053 | return emitScalarBinOp(self, binop.op, ilType(self.low, resultTy), a, b, isUnsignedType(resultTy)); |
| 6326 | 6339 | // type (e.g. `try?` wrapping `T` into `?T`), wrap the value. |
|
| 6327 | 6340 | let payloadVal = tvalPayloadVal(self, base, okValueTy, RESULT_VAL_OFFSET); |
|
| 6328 | 6341 | let mut okVal = payloadVal; |
|
| 6329 | 6342 | ||
| 6330 | 6343 | if t.returnsOptional and tryExprTy <> okValueTy { |
|
| 6331 | - | set okVal = try wrapInOptional(self, payloadVal, tryExprTy); |
|
| 6344 | + | set okVal = try buildOptional(self, payloadVal, tryExprTy); |
|
| 6332 | 6345 | } |
|
| 6333 | 6346 | try emitStore(self, slot, 0, tryExprTy, okVal); |
|
| 6334 | 6347 | } |
|
| 6335 | 6348 | // Jump to merge block if unterminated. |
|
| 6336 | 6349 | try emitMergeIfUnterminated(self, &mut mergeBlock); |
| 6339 | 6352 | switchToBlock(self, errBlock); |
|
| 6340 | 6353 | ||
| 6341 | 6354 | if t.returnsOptional { |
|
| 6342 | 6355 | // `try?` converts errors to `nil` -- store the `nil` and continue. |
|
| 6343 | 6356 | if let slot = resultSlot { |
|
| 6344 | - | let errVal = try buildNilOptional(self, tryExprTy); |
|
| 6357 | + | let errVal = try buildOptional(self, nil, tryExprTy); |
|
| 6345 | 6358 | try emitStore(self, slot, 0, tryExprTy, errVal); |
|
| 6346 | 6359 | } |
|
| 6347 | 6360 | try emitMergeIfUnterminated(self, &mut mergeBlock); |
|
| 6348 | 6361 | } else if t.catches.len > 0 { |
|
| 6349 | 6362 | // `try ... catch` -- handle the error. |
| 7073 | 7086 | return val; |
|
| 7074 | 7087 | }; |
|
| 7075 | 7088 | match coerce { |
|
| 7076 | 7089 | case resolver::Coercion::OptionalLift(optType) => { |
|
| 7077 | 7090 | if let case ast::NodeValue::Nil = node.value { |
|
| 7078 | - | return try buildNilOptional(self, optType); |
|
| 7091 | + | return try buildOptional(self, nil, optType); |
|
| 7079 | 7092 | } |
|
| 7080 | - | return try wrapInOptional(self, val, optType); |
|
| 7093 | + | return try buildOptional(self, val, optType); |
|
| 7081 | 7094 | } |
|
| 7082 | 7095 | case resolver::Coercion::NumericCast { from, to } => { |
|
| 7083 | 7096 | return lowerNumericCast(self, val, from, to); |
|
| 7084 | 7097 | } |
|
| 7085 | 7098 | case resolver::Coercion::ResultWrap => { |
| 7257 | 7270 | set val = il::Val::Imm(c as i64); |
|
| 7258 | 7271 | } |
|
| 7259 | 7272 | case ast::NodeValue::Nil => { |
|
| 7260 | 7273 | let typ = try typeOf(self, node); |
|
| 7261 | 7274 | if let case resolver::Type::Optional(_) = typ { |
|
| 7262 | - | set val = try buildNilOptional(self, typ); |
|
| 7275 | + | set val = try buildOptional(self, nil, typ); |
|
| 7263 | 7276 | } else if let case resolver::Type::Nil = typ { |
|
| 7264 | 7277 | // Standalone `nil` without a concrete optional type. We can't |
|
| 7265 | 7278 | // generate a proper value representation. |
|
| 7266 | 7279 | throw LowerError::MissingType(node); |
|
| 7267 | 7280 | } else { |
test/tests/binding.storage.rad
added
+35 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | ||
| 3 | + | /// Aggregate whose fields need initialized storage. |
|
| 4 | + | record Pair: Copy { |
|
| 5 | + | /// First summand. |
|
| 6 | + | first: u32, |
|
| 7 | + | /// Second summand. |
|
| 8 | + | second: u64, |
|
| 9 | + | } |
|
| 10 | + | ||
| 11 | + | /// Check initialized values and storage layouts. |
|
| 12 | + | @default unsafe fn main() -> u32 { |
|
| 13 | + | let mut pair: Pair = undefined; |
|
| 14 | + | set pair.first = 40; |
|
| 15 | + | set pair.second = 0x1122334455667788; |
|
| 16 | + | let numbers: [u32; 2] = [40, 2]; |
|
| 17 | + | assert pair.first == numbers[0]; |
|
| 18 | + | assert pair.second == 0x1122334455667788; |
|
| 19 | + | let wrapped: ?Pair = pair; |
|
| 20 | + | let value = wrapped else panic; |
|
| 21 | + | assert value.first == 40; |
|
| 22 | + | assert value.second == 0x1122334455667788; |
|
| 23 | + | let scalar: u32 = 42; |
|
| 24 | + | let mut optional: ?u32 = scalar; |
|
| 25 | + | assert readOptional(&optional) == 42; |
|
| 26 | + | set optional = nil; |
|
| 27 | + | assert optional == nil; |
|
| 28 | + | return 0; |
|
| 29 | + | } |
|
| 30 | + | ||
| 31 | + | /// Read a coerced optional through its stored representation. |
|
| 32 | + | fn readOptional(value: &?u32) -> u32 { |
|
| 33 | + | let present = *value else return 0; |
|
| 34 | + | return present; |
|
| 35 | + | } |
test/tests/data.optional.nil.rad
added
+33 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | ||
| 3 | + | /// Payload with alignment padding inside a tagged optional. |
|
| 4 | + | record Payload: Copy { |
|
| 5 | + | /// Small prefix. |
|
| 6 | + | prefix: u8, |
|
| 7 | + | /// Aligned value. |
|
| 8 | + | value: u64, |
|
| 9 | + | } |
|
| 10 | + | ||
| 11 | + | /// Initialized nullable pointer table. |
|
| 12 | + | unsafe static POINTERS: [?*u32; 3] = [nil; 3]; |
|
| 13 | + | /// Initialized nullable slice. |
|
| 14 | + | unsafe static SLICE: ?*[u8] = nil; |
|
| 15 | + | /// Initialized tagged record optionals. |
|
| 16 | + | unsafe static RECORDS: [?Payload; 2] = [nil, nil]; |
|
| 17 | + | /// Initialized nested optional. |
|
| 18 | + | unsafe static NESTED: ??u32 = nil; |
|
| 19 | + | ||
| 20 | + | /// Check initialized values and storage layouts. |
|
| 21 | + | @default unsafe fn main() -> u32 { |
|
| 22 | + | for pointer in &POINTERS[..] { |
|
| 23 | + | assert pointer == nil; |
|
| 24 | + | } |
|
| 25 | + | assert SLICE == nil; |
|
| 26 | + | assert RECORDS[0] == nil; |
|
| 27 | + | assert RECORDS[1] == nil; |
|
| 28 | + | assert NESTED == nil; |
|
| 29 | + | set RECORDS[1] = Payload { prefix: 2, value: 40 }; |
|
| 30 | + | let result = RECORDS[1] else panic; |
|
| 31 | + | assert result.prefix as u32 + result.value as u32 == 42; |
|
| 32 | + | return 0; |
|
| 33 | + | } |
test/tests/data.optional.value.rad
added
+67 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | ||
| 3 | + | /// Aligned record payload in a present optional. |
|
| 4 | + | record Payload: Copy { |
|
| 5 | + | /// Small prefix before the aligned value. |
|
| 6 | + | prefix: u8, |
|
| 7 | + | /// Value used to check payload alignment. |
|
| 8 | + | number: u64, |
|
| 9 | + | } |
|
| 10 | + | ||
| 11 | + | /// Tagged payload with both empty and initialized variants. |
|
| 12 | + | union Value: Copy { Empty, Number(i64), Pair { left: u8, right: u64 } } |
|
| 13 | + | ||
| 14 | + | /// Optional payload in a tagged instruction. |
|
| 15 | + | union Instruction: Copy { Return { value: ?Value } } |
|
| 16 | + | ||
| 17 | + | /// Optional constant reused as a nested optional payload. |
|
| 18 | + | constant PRESENT: ?u32 = 42; |
|
| 19 | + | /// Absent inner payload used in a present outer optional. |
|
| 20 | + | constant ABSENT: ?u32 = nil; |
|
| 21 | + | /// Present aligned record initializer. |
|
| 22 | + | unsafe static RECORD: ?Payload = Payload { prefix: 2, number: 40 }; |
|
| 23 | + | /// Present outer optional that contains a present value. |
|
| 24 | + | unsafe static NESTED: ??u32 = PRESENT; |
|
| 25 | + | /// Present outer optional that contains an absent value. |
|
| 26 | + | unsafe static NESTED_NIL: ??u32 = ABSENT; |
|
| 27 | + | /// Repeated present optional values. |
|
| 28 | + | unsafe static REPEATED: [?u32; 2] = [PRESENT; 2]; |
|
| 29 | + | /// Present nullable slice initializer. |
|
| 30 | + | unsafe static TEXT: ?*[u8] = "answer"; |
|
| 31 | + | ||
| 32 | + | /// Check initialized values and storage layouts. |
|
| 33 | + | @default unsafe fn main() -> u32 { |
|
| 34 | + | let instructions = &[ |
|
| 35 | + | Instruction::Return { value: Value::Number(42) }, |
|
| 36 | + | Instruction::Return { value: Value::Pair { left: 2, right: 40 } }, |
|
| 37 | + | Instruction::Return { value: Value::Empty } |
|
| 38 | + | ]; |
|
| 39 | + | let case Instruction::Return { value } = instructions[0] else panic; |
|
| 40 | + | let present = value else panic; |
|
| 41 | + | let case Value::Number(number) = present else panic; |
|
| 42 | + | assert number == 42; |
|
| 43 | + | let case Instruction::Return { value: second } = instructions[1] else panic; |
|
| 44 | + | let pair = second else panic; |
|
| 45 | + | let case Value::Pair { left, right } = pair else panic; |
|
| 46 | + | assert left as u64 + right == 42; |
|
| 47 | + | let case Instruction::Return { value: third } = instructions[2] else panic; |
|
| 48 | + | let empty = third else panic; |
|
| 49 | + | let case Value::Empty = empty else panic; |
|
| 50 | + | let rec = RECORD else panic; |
|
| 51 | + | assert rec.prefix as u64 + rec.number == 42; |
|
| 52 | + | let inner = NESTED else panic; |
|
| 53 | + | let answer = inner else panic; |
|
| 54 | + | assert answer == 42; |
|
| 55 | + | let missing = NESTED_NIL else panic; |
|
| 56 | + | assert missing == nil; |
|
| 57 | + | for item in &REPEATED[..] { |
|
| 58 | + | let value = item else panic; |
|
| 59 | + | assert value == 42; |
|
| 60 | + | } |
|
| 61 | + | let text = TEXT else panic; |
|
| 62 | + | assert text.len == 6; |
|
| 63 | + | for byte, i in "answer" { |
|
| 64 | + | assert text[i] == byte; |
|
| 65 | + | } |
|
| 66 | + | return 0; |
|
| 67 | + | } |