refactor(lang): unify core type representation

364e23465dec9c6886f404a4f1a71c7399f10485f207ea32830e57c4308cd5cb
Represent pointers, slices, arrays, and optionals as core type
applications. Share pointer shape and properties, and centralize argument
traversal, substitution, layout checks, equality, printing, and lowering.

Preserve nullable pointer representations and typed relocation checks while
reducing duplicated type-specific branches.
Alexis Sellier committed ago 1 parent b5fc83f3
lib/std/lang/lower.rad +223 -210
267 267
/// Trait object v-table pointer offset.
268 268
constant TRAIT_OBJ_VTABLE_OFFSET: i32 = resolver::PTR_SIZE as i32;
269 269
270 270
// Tagged Value Layout (optionals, tagged unions)
271 271
//
272 -
// Optionals and unions use 1-byte tags. Results use 8-byte tags.
272 +
// Tagged optionals and unions use 1-byte tags. Results use 8-byte tags.
273 273
//
274 274
// `{ tag: u8, [padding], payload: T }`
275 275
//
276 276
// Optionals use `tag: 0` for `nil` and `tag: 1` otherwise.
277 -
// When `T` is a pointer, the entire optional is stored as a single pointer.
277 +
// Nullable pointer-like payloads use their null data pointer as the nil niche.
278 278
//
279 279
// Tagged unions have a payload the size of the maximum variant size.
280 280
281 281
/// Offset of tag in tagged value data structure.
282 282
constant TVAL_TAG_OFFSET: i32 = 0;
676 676
677 677
/// Classifies a match subject by how it should be compared and destructured.
678 678
union MatchSubjectKind {
679 679
    /// Regular value: direct equality comparison.
680 680
    Regular,
681 -
    /// Optional with null pointer optimization: `?*T`, `?*[T]`.
681 +
    /// Optional with null pointer optimization.
682 682
    OptionalPtr,
683 -
    /// Optional aggregate `?T`: tagged union with payload.
683 +
    /// Tagged optional aggregate.
684 684
    OptionalAggregate,
685 685
    /// Union type: tag compared against variant indices.
686 686
    Union(resolver::UnionType),
687 687
}
688 688
689 689
/// Determine the kind of a match subject from its type.
690 690
fn matchSubjectKind(type: resolver::Type) -> MatchSubjectKind {
691 -
    if resolver::isOptionalPointer(type) {
692 -
        return MatchSubjectKind::OptionalPtr;
693 -
    }
694 -
    if resolver::isOptionalAggregate(type) {
695 -
        return MatchSubjectKind::OptionalAggregate;
691 +
    if let payload = resolver::optionalTypeArgument(type) {
692 +
        return MatchSubjectKind::OptionalPtr
693 +
            if resolver::isNullableType(*payload)
694 +
            else MatchSubjectKind::OptionalAggregate;
696 695
    }
697 696
    if let info = unionInfoFromType(type) {
698 697
        return MatchSubjectKind::Union(info);
699 698
    }
700 699
    return MatchSubjectKind::Regular;
1076 1075
        case resolver::Type::U64 => namePush(builder, "u64"),
1077 1076
        case resolver::Type::I8 => namePush(builder, "i8"),
1078 1077
        case resolver::Type::I16 => namePush(builder, "i16"),
1079 1078
        case resolver::Type::I32 => namePush(builder, "i32"),
1080 1079
        case resolver::Type::I64 => namePush(builder, "i64"),
1081 -
        case resolver::Type::Pointer(pointer) => {
1082 -
            match pointer.class {
1083 -
                case types::PointerClass::Owned => namePush(builder, "*"),
1084 -
                case types::PointerClass::Ref => namePush(builder, "&"),
1085 -
                case types::PointerClass::Unsafe => namePush(builder, "*unsafe "),
1086 -
            }
1087 -
            if pointer.mutable { namePush(builder, "mut "); }
1088 -
            namePushType(self, builder, *pointer.target);
1089 -
        }
1090 -
        case resolver::Type::Slice(slice) => {
1091 -
            match slice.class {
1092 -
                case types::PointerClass::Owned => namePush(builder, "*"),
1093 -
                case types::PointerClass::Ref => namePush(builder, "&"),
1094 -
                case types::PointerClass::Unsafe => namePush(builder, "*unsafe "),
1080 +
        case resolver::Type::Core(core) => {
1081 +
            match core {
1082 +
                case resolver::CoreType::Pointer { shape, properties, target } => {
1083 +
                    match properties.class {
1084 +
                        case types::PointerClass::Owned => namePush(builder, "*"),
1085 +
                        case types::PointerClass::Ref => namePush(builder, "&"),
1086 +
                        case types::PointerClass::Unsafe =>
1087 +
                            namePush(builder, "*unsafe "),
1088 +
                    }
1089 +
                    if properties.mutable { namePush(builder, "mut "); }
1090 +
                    match shape {
1091 +
                        case resolver::PointerShape::Thin =>
1092 +
                            namePushType(self, builder, *target),
1093 +
                        case resolver::PointerShape::Slice => {
1094 +
                            namePush(builder, "[");
1095 +
                            namePushType(self, builder, *target);
1096 +
                            namePush(builder, "]");
1097 +
                        }
1098 +
                    }
1099 +
                }
1100 +
                case resolver::CoreType::Array { item, length } => {
1101 +
                    namePush(builder, "[");
1102 +
                    namePushType(self, builder, *item);
1103 +
                    namePush(builder, "; ");
1104 +
                    namePushType(self, builder, *length);
1105 +
                    namePush(builder, "]");
1106 +
                }
1107 +
                case resolver::CoreType::Optional { payload } => {
1108 +
                    namePush(builder, "?");
1109 +
                    namePushType(self, builder, *payload);
1110 +
                }
1095 1111
            }
1096 -
            if slice.mutable { namePush(builder, "mut "); }
1097 -
            namePush(builder, "[");
1098 -
            namePushType(self, builder, *slice.item);
1099 -
            namePush(builder, "]");
1100 -
        }
1101 -
        case resolver::Type::Array(array) => {
1102 -
            namePush(builder, "[");
1103 -
            namePushType(self, builder, *array.item);
1104 -
            namePush(builder, "; ");
1105 -
            namePushType(self, builder, *array.length);
1106 -
            namePush(builder, "]");
1107 1112
        }
1108 1113
        case resolver::Type::ConstArgument { value, .. } => {
1109 1114
            if value.negative { namePush(builder, "-"); }
1110 1115
            namePushU64(builder, value.magnitude);
1111 1116
        }
1112 -
        case resolver::Type::Optional(inner) => {
1113 -
            namePush(builder, "?");
1114 -
            namePushType(self, builder, *inner);
1115 -
        }
1116 1117
        case resolver::Type::Fn(info) => {
1117 1118
            if info.isUnsafe { namePush(builder, "unsafe "); }
1118 1119
            namePush(builder, "fn(");
1119 1120
            for param, i in info.paramTypes {
1120 1121
                if i > 0 { namePush(builder, ", "); }
1167 1168
                case resolver::NominalType::Placeholder(_) =>
1168 1169
                    panic "namePushType: unresolved nominal type",
1169 1170
            }
1170 1171
        }
1171 1172
        case resolver::Type::TraitObject(object) => {
1172 -
            match object.class {
1173 +
            match object.properties.class {
1173 1174
                case types::PointerClass::Owned => namePush(builder, "*"),
1174 1175
                case types::PointerClass::Ref => namePush(builder, "&"),
1175 -
                case types::PointerClass::Unsafe => namePush(builder, "*unsafe "),
1176 +
                case types::PointerClass::Unsafe =>
1177 +
                    namePush(builder, "*unsafe "),
1176 1178
            }
1177 -
            if object.mutable { namePush(builder, "mut "); }
1179 +
            if object.properties.mutable { namePush(builder, "mut "); }
1178 1180
            namePush(builder, "opaque ");
1179 1181
            namePushTrait(self, builder, object.traitInfo);
1180 1182
        }
1181 1183
        else => panic "namePushType: non-concrete type",
1182 1184
    }
1667 1669
        }
1668 1670
        throw LowerError::MissingConst(node);
1669 1671
    };
1670 1672
1671 1673
    if let case resolver::ConstValue::String(s) = val {
1672 -
        if let case resolver::Type::Slice(_) = ty {
1674 +
        if let case resolver::Type::Core(resolver::CoreType::Pointer { shape: resolver::PointerShape::Slice, .. }) = ty {
1673 1675
            let strSym = try getOrCreateStringData(self, s, dataPrefix);
1674 1676
            dataSliceHeader(b, strSym, s.len);
1675 1677
            return;
1676 1678
        }
1677 1679
    }
1742 1744
    addr: ast::AddressOf,
1743 1745
    ty: resolver::Type,
1744 1746
    dataPrefix: *[u8],
1745 1747
    b: *mut DataValueBuilder
1746 1748
) throws (LowerError) {
1747 -
    let case resolver::Type::Slice(slice) = ty
1749 +
    let case resolver::Type::Core(resolver::CoreType::Pointer { shape: resolver::PointerShape::Slice, properties: slice, .. }) = ty
1748 1750
        else throw LowerError::ExpectedSliceOrArray;
1749 1751
    let targetTy = resolver::typeFor(self.resolver, addr.target)
1750 1752
        else throw LowerError::MissingType(addr.target);
1751 -
    let case resolver::Type::Array(arrInfo) = targetTy
1753 +
    let case resolver::Type::Core(resolver::CoreType::Array { length, .. }) = targetTy
1752 1754
        else throw LowerError::ExpectedArray;
1753 1755
1754 1756
    let mut nested = dataBuilder(self.allocator);
1755 1757
    let layout = resolver::getTypeLayout(targetTy);
1756 1758
    try lowerConstDataInto(self, addr.target, targetTy, layout.size, dataPrefix, &mut nested);
1765 1767
            set dataName = try pushDeclData(self, layout.size, layout.alignment, readOnly, backing.values, dataPrefix);
1766 1768
        }
1767 1769
    } else {
1768 1770
        set dataName = try pushDeclData(self, layout.size, layout.alignment, readOnly, backing.values, dataPrefix);
1769 1771
    }
1770 -
    let length = resolver::concreteArrayLength(arrInfo.length)
1772 +
    let concreteLength = resolver::concreteArrayLength(length)
1771 1773
        else throw LowerError::MissingMetadata;
1772 -
    dataSliceHeader(b, dataName, length);
1774 +
    dataSliceHeader(b, dataName, concreteLength);
1773 1775
}
1774 1776
1775 1777
/// Lower a constant expression payload into a builder without slot padding.
1776 1778
fn lowerConstDataPayloadInto(
1777 1779
    self: *mut Lowerer,
1880 1882
    elems: *mut [*ast::Node],
1881 1883
    ty: resolver::Type,
1882 1884
    dataPrefix: *[u8],
1883 1885
    b: *mut DataValueBuilder
1884 1886
) throws (LowerError) {
1885 -
    let case resolver::Type::Array(arrInfo) = ty
1887 +
    let case resolver::Type::Core(resolver::CoreType::Array { item, .. }) = ty
1886 1888
        else throw LowerError::ExpectedArray;
1887 -
    let elemTy = *arrInfo.item;
1889 +
    let elemTy = *item;
1888 1890
    let elemLayout = resolver::getTypeLayout(elemTy);
1889 1891
1890 1892
    for elem in elems {
1891 1893
        try lowerConstDataInto(self, elem, elemTy, elemLayout.size, dataPrefix, b);
1892 1894
    }
1898 1900
    repeat: ast::ArrayRepeatLit,
1899 1901
    ty: resolver::Type,
1900 1902
    dataPrefix: *[u8],
1901 1903
    b: *mut DataValueBuilder
1902 1904
) throws (LowerError) {
1903 -
    let case resolver::Type::Array(arrInfo) = ty
1905 +
    let case resolver::Type::Core(resolver::CoreType::Array { item, length }) = ty
1904 1906
        else throw LowerError::ExpectedArray;
1905 -
    let length = resolver::concreteArrayLength(arrInfo.length)
1907 +
    let concreteLength = resolver::concreteArrayLength(length)
1906 1908
        else throw LowerError::MissingMetadata;
1907 -
    let elemTy = *arrInfo.item;
1909 +
    let elemTy = *item;
1908 1910
    let elemLayout = resolver::getTypeLayout(elemTy);
1909 1911
1910 1912
    if let case ast::NodeValue::Undef = repeat.item.value {
1911 1913
        dataBuilderPush(b, il::DataValue {
1912 1914
            item: il::DataItem::Undef,
1913 -
            count: elemLayout.size * length
1915 +
            count: elemLayout.size * concreteLength
1914 1916
        });
1915 1917
    } else if let val = resolver::constValueEntry(self.resolver, repeat.item) {
1916 1918
        if let case resolver::ConstValue::String(_) = val {
1917 1919
            // A string used as a slice is represented by a three-word slice
1918 1920
            // header, not by the bytes of the string itself.
1919 -
            for _ in 0..length {
1921 +
            for _ in 0..concreteLength {
1920 1922
                try lowerConstDataInto(self, repeat.item, elemTy, elemLayout.size, dataPrefix, b);
1921 1923
            }
1922 1924
        } else {
1923 1925
            dataBuilderPush(b, il::DataValue {
1924 1926
                item: constValueToDataItem(self, val, elemTy),
1925 -
                count: length
1927 +
                count: concreteLength
1926 1928
            });
1927 1929
        }
1928 1930
    } else {
1929 -
        for _ in 0..length {
1931 +
        for _ in 0..concreteLength {
1930 1932
            try lowerConstDataInto(self, repeat.item, elemTy, elemLayout.size, dataPrefix, b);
1931 1933
        }
1932 1934
    }
1933 1935
}
1934 1936
2793 2795
    }
2794 2796
    let isNil = pattern.value == ast::NodeValue::Nil;
2795 2797
2796 2798
    match subject.kind {
2797 2799
        case MatchSubjectKind::OptionalPtr if isNil => {
2798 -
            // Null pointer optimization: branch on the data pointer being null.
2800 +
            // Null pointer optimization branches on the niche data pointer.
2799 2801
            let nilReg = try optionalNilReg(self, subject.val, subject.type);
2800 2802
            try emitBrCmp(self, il::CmpOp::Eq, il::Type::W64, il::Val::Reg(nilReg), il::Val::Imm(0), matchBlock, fallthrough);
2801 2803
        }
2802 2804
        case MatchSubjectKind::OptionalAggregate => {
2803 2805
            let base = emitValToReg(self, subject.val);
2804 2806
2805 -
            if isNil { // Optional aggregate: `nil` means tag is zero.
2807 +
            if isNil {
2806 2808
                let tagReg = tvalTagReg(self, base);
2807 2809
                try emitBr(self, tagReg, fallthrough, matchBlock);
2808 2810
            } else {
2809 -
                // `lowerExpr` applies the resolver's optional-lift coercion,
2810 -
                // materializing the tagged representation for value patterns.
2811 +
                // `lowerExpr` applies the optional-lift coercion and
2812 +
                // materializes the tagged representation for value patterns.
2811 2813
                let pattVal = try lowerExpr(self, pattern);
2812 2814
                let pattReg = emitValToReg(self, pattVal);
2813 2815
                let eq = try lowerOptionalEq(self, subject.bindType, base, pattReg, 0);
2814 2816
                let eqReg = emitValToReg(self, eq);
2815 2817
2817 2819
            }
2818 2820
        }
2819 2821
        case MatchSubjectKind::Union(unionInfo) => {
2820 2822
            assert not isNil;
2821 2823
2822 -
            let case resolver::NodeExtra::UnionVariant { tag: variantTag, .. } =
2823 -
                resolver::nodeData(self.low.resolver, pattern).extra
2824 -
            else {
2824 +
            let case resolver::NodeExtra::UnionVariant {
2825 +
                tag: variantTag,
2826 +
                ..
2827 +
            } = resolver::nodeData(self.low.resolver, pattern).extra else {
2825 2828
                throw LowerError::ExpectedVariant;
2826 2829
            };
2827 2830
            // Void unions are passed by value (the tag itself).
2828 2831
            // Non-void unions are passed by reference (need to load tag).
2829 2832
            // When matching by reference, always load from the pointer.
2830 2833
            if unionInfo.isAllVoid {
2831 2834
                let mut tagVal = subject.val;
2832 2835
                match subject.by {
2833 -
                    case resolver::MatchBy::Ref, resolver::MatchBy::MutRef => {
2836 +
                    case resolver::MatchBy::Ref,
2837 +
                         resolver::MatchBy::MutRef => {
2834 2838
                        let base = emitValToReg(self, subject.val);
2835 2839
                        set tagVal = loadTag(self, base, 0, il::Type::W8);
2836 2840
                    }
2837 2841
                    case resolver::MatchBy::Value => {}
2838 2842
                }
2842 2846
                let tagReg = tvalTagReg(self, base);
2843 2847
2844 2848
                try emitBrCmp(self, il::CmpOp::Eq, il::Type::W8, il::Val::Reg(tagReg), il::Val::Imm(variantTag as i64), matchBlock, fallthrough);
2845 2849
            }
2846 2850
        }
2847 -
        else => { // Value comparison.
2851 +
        else => {
2848 2852
            assert not isNil;
2849 2853
            let pattVal = try lowerExpr(self, pattern);
2850 2854
            if isAggregateType(subject.type) {
2851 2855
                // Aggregate types need structural comparison rather than
2852 2856
                // scalar compare.
2878 2882
    for i in 0..(patterns.len - 1) {
2879 2883
        let pattern = patterns[i];
2880 2884
        let nextArm = try createBlock(self, "arm");
2881 2885
        try emitPatternMatch(self, subject, pattern, matchBlock, nextArm);
2882 2886
2883 -
        // Seal the intermediate arm block: all predecessor edges are known
2887 +
        // Seal the intermediate arm block: all predecessor edges are known.
2884 2888
        // This ensures SSA construction can resolve variable uses through
2885 -
        // single-predecessor optimization instead of creating unresolved block
2886 -
        // parameters.
2889 +
        // single-predecessor optimization instead of creating unresolved
2890 +
        // block parameters.
2887 2891
        try switchToAndSeal(self, nextArm);
2888 2892
    }
2889 2893
    // Handle last pattern: go to fallthrough block on failure.
2890 2894
    let last = patterns[patterns.len - 1];
2891 2895
    try emitPatternMatch(self, subject, last, matchBlock, fallthrough);
2892 2896
}
2893 -
2894 2897
/// Emit a match binding pattern.
2895 2898
/// Binding patterns always match for regular values, but for optionals they
2896 2899
/// check for the presence of a value. Jumps to `valuePresent` on success,
2897 2900
/// `valueAbsent` on failure.
2898 2901
fn emitBindingTest(
3534 3537
    if unwrapped.by == resolver::MatchBy::Value and isAggregateType(unwrapped.effectiveTy) {
3535 3538
        set val = try emitStackVal(self, unwrapped.effectiveTy, val);
3536 3539
    }
3537 3540
3538 3541
    let mut bindType = unwrapped.effectiveTy;
3539 -
    if let case resolver::Type::Optional(inner) = unwrapped.effectiveTy {
3540 -
        set bindType = *inner;
3542 +
    if let case resolver::Type::Core(
3543 +
        resolver::CoreType::Optional { payload }
3544 +
    ) = unwrapped.effectiveTy {
3545 +
        set bindType = *payload;
3541 3546
    }
3542 3547
    let ilType = ilType(self.low, unwrapped.effectiveTy);
3543 3548
    let kind = matchSubjectKind(unwrapped.effectiveTy);
3544 3549
3545 3550
    return MatchSubject { val, type: unwrapped.effectiveTy, ilType, bindType, kind, by: unwrapped.by };
3573 3578
    let tagReg = nextReg(self);
3574 3579
    emitLoadW64At(self, tagReg, base, TVAL_TAG_OFFSET);
3575 3580
    return tagReg;
3576 3581
}
3577 3582
3578 -
/// Get the register to compare against `0` for optional `nil` checking.
3579 -
/// For null-ptr-optimized types, loads the data pointer, or returns it
3580 -
/// directly for scalar pointers. For aggregates, returns the tag register.
3583 +
/// Return the nil discriminator: thin pointers use their value, slices use their data pointer, and tagged optionals use their tag.
3581 3584
fn optionalNilReg(self: *mut FnLowerer, val: il::Val, typ: resolver::Type) -> il::Reg throws (LowerError) {
3582 3585
    let reg = emitValToReg(self, val);
3583 -
3584 -
    if let case resolver::Type::Optional(inner) = typ {
3585 -
        if let case resolver::Type::Slice(_) = *inner {
3586 -
            let ptrReg = nextReg(self);
3587 -
            emitLoadW64At(self, ptrReg, reg, SLICE_PTR_OFFSET);
3588 -
            return ptrReg;
3589 -
        }
3590 -
        if let case resolver::Type::Pointer(_) = *inner {
3591 -
            return reg;
3586 +
    if let inner = resolver::optionalTypeArgument(typ) {
3587 +
        if let case resolver::Type::Core(resolver::CoreType::Pointer { shape, .. }) = *inner {
3588 +
            match shape {
3589 +
                case resolver::PointerShape::Thin => return reg,
3590 +
                case resolver::PointerShape::Slice => {
3591 +
                    let ptrReg = nextReg(self);
3592 +
                    emitLoadW64At(self, ptrReg, reg, SLICE_PTR_OFFSET);
3593 +
                    return ptrReg;
3594 +
                }
3595 +
            }
3592 3596
        }
3593 3597
        return tvalTagReg(self, reg);
3594 3598
    }
3595 3599
    return reg;
3596 3600
}
3603 3607
        return il::Val::Imm(1) if isEq else il::Val::Imm(0);
3604 3608
    }
3605 3609
    let val = try lowerExpr(self, opt);
3606 3610
    let cmpReg = try optionalNilReg(self, val, optTy);
3607 3611
3608 -
    // Null-pointer-optimized types compare a 64-bit pointer against zero.
3609 -
    // Aggregate optionals compare an 8-bit tag byte against zero.
3610 -
    let cmpType = il::Type::W64 if resolver::isOptionalPointer(optTy) else il::Type::W8;
3612 +
    let inner = resolver::optionalTypeArgument(optTy)
3613 +
        else throw LowerError::ExpectedOptional;
3614 +
    let cmpType = il::Type::W64 if resolver::isNullableType(*inner) else il::Type::W8;
3611 3615
3612 3616
    let op = il::BinOp::Eq if isEq else il::BinOp::Ne;
3613 3617
    return emitTypedBinOp(self, op, cmpType, il::Val::Reg(cmpReg), il::Val::Imm(0));
3614 3618
}
3615 3619
3728 3732
/// tested against the subject element, branching to `failBlock` on mismatch.
3729 3733
fn bindArrayPatternElements(
3730 3734
    self: *mut FnLowerer,
3731 3735
    subject: *MatchSubject,
3732 3736
    items: *mut [*ast::Node],
3733 -
    failBlock: BlockId
3737 +
    failBlock: BlockId,
3734 3738
) throws (LowerError) {
3735 -
    let case resolver::Type::Array(arrInfo) = subject.type
3739 +
    let case resolver::Type::Core(resolver::CoreType::Array { item, .. }) = subject.type
3736 3740
        else throw LowerError::ExpectedSliceOrArray;
3737 3741
3738 -
    let elemTy = *arrInfo.item;
3742 +
    let elemTy = *item;
3739 3743
    let elemLayout = resolver::getTypeLayout(elemTy);
3740 3744
    let stride = elemLayout.size as i32;
3741 3745
    let base = emitValToReg(self, subject.val);
3742 3746
3743 3747
    for elem, i in items {
3812 3816
            }
3813 3817
            // Plain nested record destructuring pattern.
3814 3818
            // Auto-deref: if the field is a pointer, load it first.
3815 3819
            let mut derefType = fieldInfo.fieldType;
3816 3820
            let mut nestedBase = emitPtrOffset(self, base, fieldInfo.offset);
3817 -
            if let case resolver::Type::Pointer(pointer) = fieldInfo.fieldType {
3821 +
            if let case resolver::Type::Core(
3822 +
                resolver::CoreType::Pointer { shape: resolver::PointerShape::Thin, target, .. }
3823 +
            ) = fieldInfo.fieldType {
3818 3824
                let ptrReg = nextReg(self);
3819 3825
                emitLoadW64At(self, ptrReg, nestedBase, 0);
3820 3826
                set nestedBase = ptrReg;
3821 -
                set derefType = *pointer.target;
3827 +
                set derefType = *target;
3822 3828
            }
3823 3829
            let recInfo = resolver::getRecord(derefType)
3824 3830
                else throw LowerError::ExpectedRecord;
3825 3831
3826 3832
            try bindNestedRecordFields(self, nestedBase, lit, recInfo, matchBy, failBlock);
3848 3854
3849 3855
    // Auto-deref: when the field is a pointer and the pattern destructures
3850 3856
    // the pointed-to value, load the pointer and use the target type.
3851 3857
    // The loaded pointer becomes the base address for the nested subject.
3852 3858
    let mut derefBase: ?il::Reg = nil;
3853 -
    if let case resolver::Type::Pointer(pointer) = fieldType {
3859 +
    if let case resolver::Type::Core(resolver::CoreType::Pointer { shape: resolver::PointerShape::Thin, target, .. }) = fieldType {
3854 3860
        if resolver::isDestructuringPattern(pattern) {
3855 3861
            let ptrReg = nextReg(self);
3856 3862
            emitLoadW64At(self, ptrReg, fieldPtr, 0);
3857 3863
            set derefBase = ptrReg;
3858 -
            set fieldType = *pointer.target;
3864 +
            set fieldType = *target;
3859 3865
        }
3860 3866
    }
3861 3867
    // Build a MatchSubject for the nested field.
3862 3868
    let ilTy = ilType(self.low, fieldType);
3863 3869
    let kind = matchSubjectKind(fieldType);
4463 4469
    return ty;
4464 4470
}
4465 4471
4466 4472
/// Check if a resolver type lowers to an aggregate in memory.
4467 4473
fn isAggregateType(typ: resolver::Type) -> bool {
4468 -
    match typ {
4469 -
        case resolver::Type::Optional(resolver::Type::Pointer(_)) => {
4470 -
            // Optional pointers are scalar due to NPO.
4474 +
    if let payload = resolver::optionalTypeArgument(typ) {
4475 +
        if let case resolver::Type::Core(resolver::CoreType::Pointer { shape: resolver::PointerShape::Thin, .. }) = *payload {
4476 +
            // Optional thin pointers are scalar due to NPO.
4471 4477
            return false;
4472 4478
        }
4473 -
        case resolver::Type::Optional(_) => {
4474 -
            // All other optionals, including optional slices, are aggregates.
4475 -
            return true;
4476 -
        }
4477 -
        case resolver::Type::Nominal(_) => {
4479 +
        // All other optionals, including optional slices, are aggregates.
4480 +
        return true;
4481 +
    }
4482 +
    match typ {
4483 +
        case resolver::Type::Nominal(_) =>
4478 4484
            // Void unions are small enough to pass by value.
4479 -
            return not resolver::isVoidUnion(typ);
4480 -
        }
4481 -
        case resolver::Type::Slice(_),
4482 -
             resolver::Type::TraitObject(_),
4483 -
             resolver::Type::Array(_),
4484 -
             resolver::Type::Nil => return true,
4485 +
            return not resolver::isVoidUnion(typ),
4486 +
        case resolver::Type::Core(resolver::CoreType::Pointer { shape: resolver::PointerShape::Slice, .. }) => return true,
4487 +
        case resolver::Type::TraitObject(_) => return true,
4488 +
        case resolver::Type::Nil => return true,
4489 +
        case resolver::Type::Core(resolver::CoreType::Array { .. }) => return true,
4485 4490
        else => return false,
4486 4491
    }
4487 4492
}
4488 4493
4489 4494
/// Check if a resolver type is a small aggregate that can be
4607 4612
        }
4608 4613
    }
4609 4614
    return il::Val::Reg(dst);
4610 4615
}
4611 4616
4612 -
/// Wrap a value in an optional type.
4613 -
///
4614 -
/// For optional pointers (`?*T`), the value is returned as-is since pointers
4615 -
/// use zero to represent `nil`. For other optionals, builds a tagged aggregate.
4616 -
/// with the tag set to `1`, and the value as payload.
4617 +
/// Wrap an optional payload: nullable pointers keep their representation, while other payloads use a present tag and stored value.
4617 4618
fn wrapInOptional(self: *mut FnLowerer, val: il::Val, optType: resolver::Type) -> il::Val throws (LowerError) {
4618 -
    let case resolver::Type::Optional(inner) = optType else {
4619 -
        throw LowerError::ExpectedOptional;
4620 -
    };
4621 -
    // Null-pointer-optimized (NPO) types are used as-is -- valid values are never null.
4619 +
    let inner = resolver::optionalTypeArgument(optType)
4620 +
        else throw LowerError::ExpectedOptional;
4622 4621
    if resolver::isNullableType(*inner) {
4623 4622
        return val;
4624 4623
    }
4625 -
    let layout = resolver::getTypeLayout(optType);
4626 -
    let valOffset = resolver::getOptionalValOffset(*inner) as i32;
4627 -
4628 -
    return try buildTagged(self, layout, 1, val, *inner, 1, valOffset);
4624 +
    return try buildTagged(self, resolver::getTypeLayout(optType), 1, val, *inner, 1, resolver::getOptionalValOffset(*inner) as i32);
4629 4625
}
4630 4626
4631 -
/// Build a `nil` value for an optional type.
4632 -
///
4633 -
/// For optional pointers (`?*T`), returns an immediate `0` (null pointer).
4634 -
/// For other optionals, builds a tagged aggregate with tag set to `0` (absent).
4627 +
/// Build nil: thin pointers use zero, slices use a zero data pointer and length, and tagged optionals use an absent tag.
4635 4628
fn buildNilOptional(self: *mut FnLowerer, optType: resolver::Type) -> il::Val throws (LowerError) {
4636 -
    let case resolver::Type::Optional(inner) = optType
4629 +
    let inner = resolver::optionalTypeArgument(optType)
4637 4630
        else throw LowerError::ExpectedOptional;
4638 -
    if let case resolver::Type::Pointer(_) = *inner {
4639 -
        return il::Val::Imm(0);
4640 -
    }
4641 -
    if let case resolver::Type::Slice(slice) = *inner {
4642 -
        return try buildSliceValue(
4643 -
            self, slice.item, slice.mutable, il::Val::Imm(0), il::Val::Imm(0)
4644 -
        );
4631 +
    if let case resolver::Type::Core(resolver::CoreType::Pointer { shape, properties, target }) = *inner {
4632 +
        match shape {
4633 +
            case resolver::PointerShape::Thin => return il::Val::Imm(0),
4634 +
            case resolver::PointerShape::Slice =>
4635 +
                return try buildSliceValue(self, target, properties.mutable, il::Val::Imm(0), il::Val::Imm(0)),
4636 +
        }
4645 4637
    }
4646 -
    let valOffset = resolver::getOptionalValOffset(*inner) as i32;
4647 -
    return try buildTagged(self, resolver::getTypeLayout(optType), 0, nil, *inner, 1, valOffset);
4638 +
    return try buildTagged(self, resolver::getTypeLayout(optType), 0, nil, *inner, 1, resolver::getOptionalValOffset(*inner) as i32);
4648 4639
}
4649 4640
4650 4641
/// Build a result value for throwing functions.
4651 4642
fn buildResult(
4652 4643
    self: *mut FnLowerer,
4667 4658
    elemTy: *resolver::Type,
4668 4659
    mutable: bool,
4669 4660
    ptrVal: il::Val,
4670 4661
    lenVal: il::Val
4671 4662
) -> il::Val throws (LowerError) {
4672 -
    let sliceType = resolver::Type::Slice(resolver::SliceType {
4673 -
        class: types::PointerClass::Unsafe,
4674 -
        item: elemTy,
4663 +
    let sliceType = resolver::sliceType(
4664 +
        types::PointerClass::Unsafe,
4665 +
        elemTy,
4675 4666
        mutable,
4676 -
    });
4667 +
    );
4677 4668
    let dst = try emitReserve(self, sliceType);
4678 -
    let ptrTy = resolver::Type::Pointer(resolver::PointerType {
4679 -
        class: types::PointerClass::Unsafe,
4680 -
        target: elemTy,
4669 +
    let ptrTy = resolver::pointerType(
4670 +
        types::PointerClass::Unsafe,
4671 +
        elemTy,
4681 4672
        mutable,
4682 -
    });
4673 +
    );
4683 4674
4684 4675
    try emitStore(self, dst, SLICE_PTR_OFFSET, ptrTy, ptrVal);
4685 4676
    try emitStore(self, dst, SLICE_LEN_OFFSET, resolver::Type::U32, lenVal);
4686 4677
4687 4678
    return il::Val::Reg(dst);
4866 4857
    mutable: bool,
4867 4858
    a: il::Reg,
4868 4859
    b: il::Reg,
4869 4860
    offset: i32
4870 4861
) -> il::Val throws (LowerError) {
4871 -
    let ptrTy = resolver::Type::Pointer(resolver::PointerType {
4872 -
        class: types::PointerClass::Unsafe,
4873 -
        target: elemTy,
4862 +
    let ptrTy = resolver::pointerType(
4863 +
        types::PointerClass::Unsafe,
4864 +
        elemTy,
4874 4865
        mutable,
4875 -
    });
4866 +
    );
4876 4867
    let ptrEq = try emitEqAtOffset(self, a, b, offset + SLICE_PTR_OFFSET, ptrTy);
4877 4868
    let lenEq = try emitEqAtOffset(self, a, b, offset + SLICE_LEN_OFFSET, resolver::Type::U32);
4878 4869
4879 4870
    return emitTypedBinOp(self, il::BinOp::And, il::Type::W32, ptrEq, lenEq);
4880 4871
}
4905 4896
4906 4897
    // For simple inner types (no unions/nested optionals), use branchless comparison.
4907 4898
    // Unions and nested optionals may contain uninitialized payload bytes
4908 4899
    // when nil, so they need a guarded comparison.
4909 4900
    let isUnion = unionInfoFromType(inner) <> nil;
4910 -
    let mut isOptional = false;
4911 -
    if let case resolver::Type::Optional(_) = inner {
4912 -
        set isOptional = true;
4913 -
    }
4914 -
    if not isUnion and not isOptional {
4901 +
    if not isUnion and not resolver::isOptionalType(inner) {
4915 4902
        let tagEq = emitTypedBinOp(self, il::BinOp::Eq, il::Type::W8, tagA, tagB);
4916 4903
        let tagNil = emitTypedBinOp(self, il::BinOp::Eq, il::Type::W8, tagA, il::Val::Imm(0));
4917 4904
        let payloadEq = try emitEqAtOffset(self, a, b, offset + valOffset, inner);
4918 4905
4919 4906
        return emitTypedBinOp(self, il::BinOp::And, il::Type::W32, tagEq,
5089 5076
}
5090 5077
5091 5078
/// Compare two array values for equality, element by element.
5092 5079
fn lowerArrayEq(
5093 5080
    self: *mut FnLowerer,
5094 -
    arr: resolver::ArrayType,
5081 +
    item: *resolver::Type,
5082 +
    lengthArgument: *resolver::Type,
5095 5083
    a: il::Reg,
5096 5084
    b: il::Reg,
5097 5085
    offset: i32
5098 5086
) -> il::Val throws (LowerError) {
5099 -
    let elemLayout = resolver::getTypeLayout(*arr.item);
5087 +
    let elemLayout = resolver::getTypeLayout(*item);
5100 5088
    let stride = elemLayout.size as i32;
5101 5089
    let mut result: ?il::Val = nil;
5102 -
    let length = resolver::concreteArrayLength(arr.length)
5090 +
    let length = resolver::concreteArrayLength(lengthArgument)
5103 5091
        else throw LowerError::MissingMetadata;
5104 5092
5105 5093
    for i in 0..length {
5106 5094
        let elemOffset = offset + (i as i32) * stride;
5107 -
        let cmp = try emitEqAtOffset(self, a, b, elemOffset, *arr.item);
5095 +
        let cmp = try emitEqAtOffset(self, a, b, elemOffset, *item);
5108 5096
        set result = emitLogicalAnd(self, result, cmp);
5109 5097
    }
5110 5098
    if let r = result {
5111 5099
        return r;
5112 5100
    }
5120 5108
    typ: resolver::Type,
5121 5109
    a: il::Reg,
5122 5110
    b: il::Reg,
5123 5111
    offset: i32
5124 5112
) -> il::Val throws (LowerError) {
5113 +
    let mut effective = typ;
5114 +
    if let payload = resolver::optionalTypeArgument(typ) {
5115 +
        set effective = *payload;
5116 +
    }
5117 +
    if let case resolver::Type::Core(resolver::CoreType::Pointer { shape: resolver::PointerShape::Slice, properties, target }) = effective {
5118 +
        return try lowerSliceEq(
5119 +
            self, target, properties.mutable, a, b, offset
5120 +
        );
5121 +
    }
5125 5122
    match typ {
5126 -
        case resolver::Type::Slice(slice) =>
5127 -
            return try lowerSliceEq(self, slice.item, slice.mutable, a, b, offset),
5128 -
        case resolver::Type::Optional(inner) => {
5129 -
            if let case resolver::Type::Slice(slice) = *inner {
5130 -
                // Optional slices use null pointer optimization.
5131 -
                return try lowerSliceEq(self, slice.item, slice.mutable, a, b, offset);
5132 -
            }
5133 -
            return try lowerOptionalEq(self, *inner, a, b, offset);
5134 -
        }
5135 -
        case resolver::Type::Array(arr) =>
5136 -
            return try lowerArrayEq(self, arr, a, b, offset),
5123 +
        case resolver::Type::Core(
5124 +
            resolver::CoreType::Optional { payload }
5125 +
        ) => return try lowerOptionalEq(self, *payload, a, b, offset),
5126 +
        case resolver::Type::Core(resolver::CoreType::Array { item, length }) =>
5127 +
            return try lowerArrayEq(self, item, length, a, b, offset),
5137 5128
        case resolver::Type::Nominal(resolver::NominalType::Record(recInfo)) =>
5138 5129
            return try lowerRecordEq(self, recInfo, a, b, offset),
5139 5130
        case resolver::Type::Nominal(resolver::NominalType::Union(unionInfo)) =>
5140 5131
            return try lowerUnionEq(self, unionInfo, a, b, offset),
5141 5132
        else => {
5237 5228
/// Lower an array literal expression like `[1, 2, 3]`.
5238 5229
fn lowerArrayLit(self: *mut FnLowerer, node: *ast::Node, elements: *mut [*ast::Node]) -> il::Val
5239 5230
    throws (LowerError)
5240 5231
{
5241 5232
    let typ = try typeOf(self, node);
5242 -
    let case resolver::Type::Array(arrInfo) = typ else {
5243 -
        throw LowerError::ExpectedArray;
5244 -
    };
5245 -
    let elemTy = *arrInfo.item;
5233 +
    let case resolver::Type::Core(resolver::CoreType::Array { item, .. }) = typ
5234 +
        else throw LowerError::ExpectedArray;
5235 +
    let elemTy = *item;
5246 5236
    let elemLayout = resolver::getTypeLayout(elemTy);
5247 5237
    let dst = try emitReserve(self, typ);
5248 5238
5249 5239
    for elemNode, i in elements {
5250 5240
        let elemVal = try lowerExpr(self, elemNode);
5260 5250
// TODO: Beyond a certain length, lower this to a loop.
5261 5251
fn lowerArrayRepeatLit(self: *mut FnLowerer, node: *ast::Node, repeat: ast::ArrayRepeatLit) -> il::Val
5262 5252
    throws (LowerError)
5263 5253
{
5264 5254
    let typ = try typeOf(self, node);
5265 -
    let case resolver::Type::Array(arrInfo) = typ else {
5266 -
        throw LowerError::ExpectedArray;
5267 -
    };
5268 -
    let elemTy = *arrInfo.item;
5269 -
    let length = resolver::concreteArrayLength(arrInfo.length)
5255 +
    let case resolver::Type::Core(resolver::CoreType::Array { item, length: lengthArgument }) = typ
5256 +
        else throw LowerError::ExpectedArray;
5257 +
    let elemTy = *item;
5258 +
    let length = resolver::concreteArrayLength(lengthArgument)
5270 5259
        else throw LowerError::MissingMetadata;
5271 5260
    let elemLayout = resolver::getTypeLayout(elemTy);
5272 5261
    let dst = try emitReserve(self, typ);
5273 5262
5274 5263
    // Evaluate the repeated item once.
5492 5481
    self: *mut FnLowerer,
5493 5482
    sliceNode: *ast::Node,
5494 5483
    arrayNode: *ast::Node
5495 5484
) -> il::Val throws (LowerError) {
5496 5485
    let sliceTy = try typeOf(self, sliceNode);
5497 -
    let case resolver::Type::Slice(slice) = sliceTy else {
5498 -
        throw LowerError::UnexpectedType(&sliceTy);
5499 -
    };
5486 +
    let case resolver::Type::Core(resolver::CoreType::Pointer { shape: resolver::PointerShape::Slice, properties: slice, target: item }) = sliceTy
5487 +
        else throw LowerError::UnexpectedType(&sliceTy);
5500 5488
    let arrayTy = try typeOf(self, arrayNode);
5501 -
    let case resolver::Type::Array(arrayInfo) = arrayTy else {
5502 -
        throw LowerError::ExpectedArray;
5503 -
    };
5504 -
    let length = resolver::concreteArrayLength(arrayInfo.length)
5489 +
    let case resolver::Type::Core(resolver::CoreType::Array { length: lengthArgument, .. }) = arrayTy
5490 +
        else throw LowerError::ExpectedArray;
5491 +
    let length = resolver::concreteArrayLength(lengthArgument)
5505 5492
        else throw LowerError::MissingMetadata;
5506 5493
    if length == 0 {
5507 5494
        return try buildSliceValue(
5508 -
            self, slice.item, slice.mutable, il::Val::Imm(0), il::Val::Imm(0)
5495 +
            self,
5496 +
            item,
5497 +
            slice.mutable,
5498 +
            il::Val::Imm(0),
5499 +
            il::Val::Imm(0),
5509 5500
        );
5510 5501
    }
5511 5502
    if resolver::isConstExpr(self.low.resolver, arrayNode) {
5512 5503
        let mut b = dataBuilder(self.low.allocator);
5513 5504
        match arrayNode.value {
5516 5507
            case ast::NodeValue::ArrayRepeatLit(repeat) =>
5517 5508
                try lowerConstArrayRepeatInto(self.low, repeat, arrayTy, self.fnName, &mut b),
5518 5509
            else => throw LowerError::UnexpectedNodeValue(arrayNode),
5519 5510
        }
5520 5511
        let result = dataBuilderFinish(&b);
5521 -
        let alignment = resolver::getTypeLayout(*slice.item).alignment;
5512 +
        let alignment = resolver::getTypeLayout(*item).alignment;
5522 5513
        return try lowerConstDataAsSlice(
5523 -
            self, result.values, alignment, not slice.mutable,
5524 -
            slice.item, slice.mutable, length
5514 +
            self,
5515 +
            result.values,
5516 +
            alignment,
5517 +
            not slice.mutable,
5518 +
            item,
5519 +
            slice.mutable,
5520 +
            length,
5525 5521
        );
5526 5522
    }
5527 5523
    let data = try lowerExpr(self, arrayNode);
5528 5524
    let count = il::Val::Imm(length as i64);
5529 -
    return try buildSliceValue(self, slice.item, slice.mutable, data, count);
5525 +
    return try buildSliceValue(self, item, slice.mutable, data, count);
5530 5526
}
5531 5527
5532 5528
/// Lower the common element pointer computation for subscript operations.
5533 5529
/// Handles both arrays and slices by resolving the container type, extracting
5534 5530
/// the data pointer (for slices), and emitting an [`il::Instr::Elem`] to compute
5544 5540
5545 5541
    let mut dataReg = baseReg;
5546 5542
    let mut elemType: resolver::Type = undefined;
5547 5543
5548 5544
    match subjectTy {
5549 -
        case resolver::Type::Slice(slice) => {
5550 -
            set elemType = *slice.item;
5545 +
        case resolver::Type::Core(resolver::CoreType::Pointer { shape: resolver::PointerShape::Slice, target, .. }) => {
5546 +
            set elemType = *target;
5551 5547
            let sliceLen = loadSliceLen(self, baseReg);
5552 5548
            // Runtime safety check: index must be strictly less than slice length.
5553 -
            try emitTrapUnlessCmp(self, il::CmpOp::Ult, il::Type::W32, indexVal, sliceLen);
5549 +
            try emitTrapUnlessCmp(
5550 +
                self, il::CmpOp::Ult, il::Type::W32, indexVal, sliceLen
5551 +
            );
5554 5552
5555 5553
            set dataReg = loadSlicePtr(self, baseReg);
5556 5554
        }
5557 -
        case resolver::Type::Array(arrInfo) => {
5558 -
            set elemType = *arrInfo.item;
5555 +
        case resolver::Type::Core(resolver::CoreType::Array { item, length: lengthArgument }) => {
5556 +
            set elemType = *item;
5559 5557
            // Runtime safety check: index must be strictly less than array length.
5560 5558
            // Skip when the index is a compile-time constant, since we check
5561 5559
            // that in the resolver.
5562 5560
            if not resolver::isConstExpr(self.low.resolver, index) {
5563 -
                let length = resolver::concreteArrayLength(arrInfo.length)
5561 +
                let length = resolver::concreteArrayLength(lengthArgument)
5564 5562
                    else throw LowerError::MissingMetadata;
5565 5563
                let arrLen = il::Val::Imm(length as i64);
5566 -
                try emitTrapUnlessCmp(self, il::CmpOp::Ult, il::Type::W32, indexVal, arrLen);
5564 +
                try emitTrapUnlessCmp(
5565 +
                    self, il::CmpOp::Ult, il::Type::W32, indexVal, arrLen
5566 +
                );
5567 5567
            }
5568 5568
        }
5569 5569
        else => throw LowerError::ExpectedSliceOrArray,
5570 5570
    }
5571 5571
    let elemLayout = resolver::getTypeLayout(elemType);
5837 5837
    let r = try resolveSliceRangePtr(self, container, range, info);
5838 5838
    let itemType = try specializeType(self, *info.itemType);
5839 5839
    let elemSize = resolver::getTypeLayout(itemType).size;
5840 5840
    let rhsTy = try typeOf(self, rhs);
5841 5841
5842 -
    if let case resolver::Type::Slice(_) = rhsTy {
5842 +
    if let case resolver::Type::Core(resolver::CoreType::Pointer { shape: resolver::PointerShape::Slice, .. }) = rhsTy {
5843 5843
        // Copy from source slice.
5844 5844
        let srcReg = emitValToReg(self, try lowerExpr(self, rhs));
5845 5845
        let srcData = loadSlicePtr(self, srcReg);
5846 5846
        let srcLen = loadSliceLen(self, srcReg);
5847 5847
6595 6595
/// String literals are stored as global data and the result is a slice
6596 6596
/// pointing to the data with the appropriate length.
6597 6597
fn lowerStringLit(self: *mut FnLowerer, node: *ast::Node, s: *[u8]) -> il::Val throws (LowerError) {
6598 6598
    // Get the slice type from the node.
6599 6599
    let sliceTy = try typeOf(self, node);
6600 -
    let case resolver::Type::Slice(slice) = sliceTy
6600 +
    let case resolver::Type::Core(resolver::CoreType::Pointer { shape: resolver::PointerShape::Slice, properties: slice, target: item }) = sliceTy
6601 6601
        else throw LowerError::ExpectedSliceOrArray;
6602 6602
    // Build the string data value.
6603 6603
    let ptr = try! alloc::alloc(
6604 6604
        self.low.arena, @sizeOf(il::DataValue), @alignOf(il::DataValue)
6605 6605
    ) as *mut il::DataValue;
6606 6606
6607 6607
    set *ptr = il::DataValue { item: il::DataItem::Str(s), count: 1 };
6608 6608
6609 6609
    return try lowerConstDataAsSlice(
6610 -
        self, @sliceOf(ptr, 1), 1, true, slice.item, slice.mutable, s.len
6610 +
        self,
6611 +
        @sliceOf(ptr, 1),
6612 +
        1,
6613 +
        true,
6614 +
        item,
6615 +
        slice.mutable,
6616 +
        s.len,
6611 6617
    );
6612 6618
}
6613 6619
6614 6620
/// Lower a builtin call expression.
6615 6621
fn lowerBuiltinCall(self: *mut FnLowerer, node: *ast::Node, kind: ast::Builtin, args: *mut [*ast::Node]) -> il::Val throws (LowerError) {
6638 6644
fn lowerSliceOf(self: *mut FnLowerer, node: *ast::Node, args: *mut [*ast::Node]) -> il::Val throws (LowerError) {
6639 6645
    if args.len <> 2 {
6640 6646
        throw LowerError::InvalidArgCount;
6641 6647
    }
6642 6648
    let sliceTy = try typeOf(self, node);
6643 -
    let case resolver::Type::Slice(slice) = sliceTy
6649 +
    let case resolver::Type::Core(resolver::CoreType::Pointer { shape: resolver::PointerShape::Slice, properties: slice, target: item }) = sliceTy
6644 6650
        else throw LowerError::ExpectedSliceOrArray;
6645 6651
    let ptrVal = try lowerExpr(self, args[0]);
6646 6652
    let lenVal = try lowerExpr(self, args[1]);
6647 -
    return try buildSliceValue(self, slice.item, slice.mutable, ptrVal, lenVal);
6653 +
    return try buildSliceValue(
6654 +
        self, item, slice.mutable, ptrVal, lenVal
6655 +
    );
6648 6656
}
6649 6657
6650 -
/// Lower `@relocate(destination, source)` as an overlap-safe byte move.
6658 +
/// Lower `@relocate(destination, source)` as an overlap-safe typed move.
6651 6659
fn lowerRelocate(
6652 6660
    self: *mut FnLowerer,
6653 6661
    args: *mut [*ast::Node],
6654 6662
) -> il::Val throws (LowerError) {
6655 6663
    if args.len <> 2 {
6656 6664
        throw LowerError::InvalidArgCount;
6657 6665
    }
6666 +
    let destinationType = try typeOf(self, args[0]);
6667 +
    let case resolver::Type::Core(resolver::CoreType::Pointer { shape: resolver::PointerShape::Slice, target: item, .. }) = destinationType
6668 +
        else throw LowerError::ExpectedSliceOrArray;
6658 6669
6659 6670
    let destinationSlice = emitValToReg(self, try lowerExpr(self, args[0]));
6660 6671
    let destination = loadSlicePtr(self, destinationSlice);
6661 6672
    let destinationLen = loadSliceLen(self, destinationSlice);
6662 6673
    let sourceSlice = emitValToReg(self, try lowerExpr(self, args[1]));
6663 6674
    let source = loadSlicePtr(self, sourceSlice);
6664 6675
    let sourceLen = loadSliceLen(self, sourceSlice);
6665 6676
6666 -
    // The destination must hold every source byte.
6677 +
    // The destination must hold every source element.
6667 6678
    try emitTrapIfLt(self, il::Type::W32, destinationLen, sourceLen);
6679 +
    let itemSize = resolver::getTypeLayout(*item).size;
6680 +
    let mut byteLen = sourceLen;
6681 +
    if itemSize <> 1 {
6682 +
        set byteLen = emitTypedBinOp(self, il::BinOp::Mul, il::Type::W32, sourceLen, il::Val::Imm(itemSize as i64));
6683 +
    }
6668 6684
6669 6685
    let forwardBlock = try createBlock(self, "relocate.forward");
6670 6686
    let backwardBlock = try createBlock(self, "relocate.backward");
6671 6687
    let doneBlock = try createBlock(self, "relocate.done");
6672 6688
    try emitBrCmp(
6683 6699
    try switchToAndSeal(self, forwardBlock);
6684 6700
    try emitByteCopyLoop(
6685 6701
        self,
6686 6702
        destination,
6687 6703
        source,
6688 -
        sourceLen,
6704 +
        byteLen,
6689 6705
        "relocate.forward",
6690 6706
    );
6691 6707
    try emitJmp(self, doneBlock);
6692 6708
6693 6709
    // Moving toward a higher address must copy from the end for overlap safety.
6698 6714
        "relocate.backward",
6699 6715
        il::Param { value: remainingReg, type: il::Type::W32 },
6700 6716
    );
6701 6717
    let backwardBody = try createBlock(self, "relocate.backward");
6702 6718
    let backwardDone = try createBlock(self, "relocate.backward");
6703 -
    try emitJmpWithArg(self, backwardHeader, sourceLen);
6719 +
    try emitJmpWithArg(self, backwardHeader, byteLen);
6704 6720
6705 6721
    switchToBlock(self, backwardHeader);
6706 6722
    try emitBrCmp(
6707 6723
        self,
6708 6724
        il::CmpOp::Ult,
7209 7225
/// If the parent is already a pointer type, the value is used directly.
7210 7226
/// If the parent is a value type (eg. a local record), its address is taken.
7211 7227
fn lowerReceiver(self: *mut FnLowerer, parent: *ast::Node, parentTy: resolver::Type) -> il::Val
7212 7228
    throws (LowerError)
7213 7229
{
7214 -
    if let case resolver::Type::Pointer(_) = parentTy {
7230 +
    if let case resolver::Type::Core(resolver::CoreType::Pointer { shape: resolver::PointerShape::Thin, .. }) = parentTy {
7215 7231
        // Already a pointer: lower and use directly.
7216 7232
        return try lowerExpr(self, parent);
7217 7233
    }
7218 7234
    // Value type: take its address by lowering it and returning the slot pointer.
7219 7235
    // Aggregate types are already lowered as pointers to stack slots.
7675 7691
        case ast::NodeValue::Char(c) => {
7676 7692
            set val = il::Val::Imm(c as i64);
7677 7693
        }
7678 7694
        case ast::NodeValue::Nil => {
7679 7695
            let typ = try typeOf(self, node);
7680 -
            if let case resolver::Type::Optional(_) = typ {
7696 +
            if resolver::isOptionalType(typ) {
7681 7697
                set val = try buildNilOptional(self, typ);
7682 7698
            } else if let case resolver::Type::Nil = typ {
7683 7699
                // Standalone `nil` without a concrete optional type. We can't
7684 7700
                // generate a proper value representation.
7685 7701
                throw LowerError::MissingType(node);
7729 7745
                    else => set val = try lowerFieldAccess(self, access),
7730 7746
                }
7731 7747
            } else {
7732 7748
                let parentTy = try typeOf(self, access.parent);
7733 7749
                let mut handled = false;
7734 -
                if let case resolver::Type::Array(array) = parentTy {
7750 +
                if let case resolver::Type::Core(resolver::CoreType::Array { length: lengthArgument, .. }) = parentTy {
7735 7751
                    if let case ast::NodeValue::Ident(name) = access.child.value;
7736 7752
                       mem::eq(name, "len")
7737 7753
                    {
7738 -
                        let length = resolver::concreteArrayLength(array.length)
7754 +
                        let length = resolver::concreteArrayLength(lengthArgument)
7739 7755
                            else throw LowerError::MissingMetadata;
7740 7756
                        set val = il::Val::Imm(length as i64);
7741 7757
                        set handled = true;
7742 7758
                    }
7743 7759
                }
7833 7849
             resolver::Type::U16 => return il::Type::W16,
7834 7850
        case resolver::Type::I32,
7835 7851
             resolver::Type::U32 => return il::Type::W32,
7836 7852
        case resolver::Type::I64,
7837 7853
             resolver::Type::U64,
7838 -
             resolver::Type::Pointer(_),
7839 -
             resolver::Type::Slice(_),
7854 +
             resolver::Type::Core(_),
7840 7855
             resolver::Type::TraitObject(_),
7841 -
             resolver::Type::Array(_),
7842 -
             resolver::Type::Optional(_),
7843 7856
             resolver::Type::Fn(_) => return il::Type::W64,
7844 7857
        case resolver::Type::Nominal(_) => {
7845 7858
            if resolver::isVoidUnion(typ) {
7846 7859
                return il::Type::W8;
7847 7860
            }
lib/std/lang/resolver.rad +706 -744
180 180
    name: *[u8],
181 181
    valueType: Type,
182 182
    symbol: *mut Symbol,
183 183
}
184 184
185 -
/// Array type payload.
186 -
export record ArrayType {
187 -
    /// Element type.
188 -
    item: *Type,
189 -
    /// Typed constant descriptor for the array length.
190 -
    length: *Type,
191 -
}
192 -
193 185
/// Anonymous record whose field layout depends on rigid parameters.
194 186
export record GenericRecordType {
195 187
    /// Fields in declaration order.
196 188
    fields: *[RecordField],
197 189
    /// Whether the fields have labels.
470 462
    args: *[*Type],
471 463
    /// Source node for the application.
472 464
    site: *ast::Node,
473 465
}
474 466
475 -
/// Pointer-like address payload.
476 -
export record PointerType {
467 +
/// Shared ownership, safety, and mutability metadata for pointer-like values.
468 +
export record PointerProperties {
477 469
    /// Ownership and safety class.
478 470
    class: types::PointerClass,
479 -
    /// Pointer target type.
480 -
    target: *Type,
481 -
    /// Whether the pointer is mutable.
471 +
    /// Whether the referenced value may be mutated.
482 472
    mutable: bool,
483 473
}
484 474
485 -
/// Pointer-like slice payload.
486 -
export record SliceType {
487 -
    /// Ownership and safety class.
488 -
    class: types::PointerClass,
489 -
    /// Slice element type.
490 -
    item: *Type,
491 -
    /// Whether the slice is mutable.
492 -
    mutable: bool,
475 +
/// Storage shape of an intrinsic pointer-like value.
476 +
export union PointerShape {
477 +
    /// One-word pointer to a single value.
478 +
    Thin,
479 +
    /// Two-word pointer and length pair.
480 +
    Slice,
481 +
}
482 +
483 +
/// Semantic application of an intrinsic core type.
484 +
export union CoreType {
485 +
    /// Pointer or slice application.
486 +
    Pointer {
487 +
        /// Storage shape and operation set.
488 +
        shape: PointerShape,
489 +
        /// Ownership, safety, and mutability metadata.
490 +
        properties: PointerProperties,
491 +
        /// Referenced value or slice element type.
492 +
        target: *Type,
493 +
    },
494 +
    /// Repeated element aggregate.
495 +
    Array {
496 +
        /// Element type.
497 +
        item: *Type,
498 +
        /// Typed constant descriptor for the array length.
499 +
        length: *Type,
500 +
    },
501 +
    /// Union-like value-or-nil application.
502 +
    Optional {
503 +
        /// Payload type.
504 +
        payload: *Type,
505 +
    },
493 506
}
494 507
495 508
/// Erased pointer-like type payload.
496 509
export record TraitObjectType {
497 -
    /// Ownership and safety class.
498 -
    class: types::PointerClass,
510 +
    /// Ownership, safety, and mutability metadata.
511 +
    properties: PointerProperties,
499 512
    /// Trait definition.
500 513
    traitInfo: *TraitType,
501 -
    /// Whether the pointer is mutable.
502 -
    mutable: bool,
503 514
}
504 515
505 516
/// Describes a type computed during semantic analysis.
506 517
export union Type {
507 518
    /// A type that couldn't be decided.
515 526
    /// Range types, eg. `start..end`.
516 527
    Range {
517 528
        start: ?*Type,
518 529
        end: ?*Type,
519 530
    },
520 -
    /// Pointer-like address.
521 -
    Pointer(PointerType),
522 -
    /// Pointer-like slice.
523 -
    Slice(SliceType),
524 -
    /// Eg. `[i32; 32]`.
525 -
    Array(ArrayType),
531 +
    /// Intrinsic pointer, slice, array, or optional application.
532 +
    Core(CoreType),
526 533
    /// Rigid integer constant parameter within a generic declaration.
527 534
    ConstParameter(*GenericParamType),
528 535
    /// Canonical typed integer generic argument.
529 536
    ConstArgument {
530 537
        type: *Type,
533 540
    /// Symbolic integer expression awaiting constant-parameter substitution.
534 541
    GenericConstExpr {
535 542
        type: *Type,
536 543
        expr: *ast::Node,
537 544
    },
538 -
    /// Eg. `?T`.
539 -
    Optional(*Type),
540 545
    /// Eg. `fn id(i32) -> i32`.
541 546
    Fn(*FnType),
542 547
    /// Named, ie. user-defined types, includes union variants.
543 548
    Nominal(*NominalType),
544 549
    /// Rigid type parameter within a generic declaration.
549 554
    GenericDataApply(*GenericDataApplyType),
550 555
    /// An erased pointer-like type with a v-table.
551 556
    TraitObject(TraitObjectType),
552 557
}
553 558
559 +
/// Construct a fixed-size intrinsic pointer application.
560 +
export fn pointerType(class: types::PointerClass, target: *Type, mutable: bool) -> Type {
561 +
    return Type::Core(CoreType::Pointer { shape: PointerShape::Thin, properties: PointerProperties { class, mutable }, target });
562 +
}
563 +
564 +
/// Construct a direct intrinsic slice application.
565 +
export fn sliceType(class: types::PointerClass, item: *Type, mutable: bool) -> Type {
566 +
    return Type::Core(CoreType::Pointer { shape: PointerShape::Slice, properties: PointerProperties { class, mutable }, target: item });
567 +
}
568 +
569 +
/// Construct an intrinsic repeated aggregate application.
570 +
export fn arrayType(item: *Type, length: *Type) -> Type {
571 +
    return Type::Core(CoreType::Array { item, length });
572 +
}
573 +
574 +
/// Construct an intrinsic value-or-nil application.
575 +
export fn optionalType(payload: *Type) -> Type {
576 +
    return Type::Core(CoreType::Optional { payload });
577 +
}
578 +
579 +
/// Return the value-type argument of a core application.
580 +
fn coreValueArgument(core: CoreType) -> *Type {
581 +
    match core {
582 +
        case CoreType::Pointer { target, .. } => return target,
583 +
        case CoreType::Array { item, .. } => return item,
584 +
        case CoreType::Optional { payload } => return payload,
585 +
    }
586 +
}
587 +
588 +
/// Return the constant argument of a core application, when present.
589 +
fn coreConstantArgument(core: CoreType) -> ?*Type {
590 +
    if let case CoreType::Array { length, .. } = core {
591 +
        return length;
592 +
    }
593 +
    return nil;
594 +
}
595 +
596 +
/// Reconstruct a core application with transformed generic arguments.
597 +
fn coreWithArguments(core: CoreType, valueArgument: *Type, constantArgument: ?*Type) -> CoreType {
598 +
    match core {
599 +
        case CoreType::Pointer { shape, properties, .. } =>
600 +
            return CoreType::Pointer { shape, properties, target: valueArgument },
601 +
        case CoreType::Array { .. } => {
602 +
            let length = constantArgument
603 +
                else panic "coreWithArguments: missing array length";
604 +
            return CoreType::Array { item: valueArgument, length };
605 +
        }
606 +
        case CoreType::Optional { .. } =>
607 +
            return CoreType::Optional { payload: valueArgument },
608 +
    }
609 +
}
610 +
554 611
/// Return a concrete `u32` array length from its typed descriptor.
555 612
export fn concreteArrayLength(length: *Type) -> ?u32 {
556 613
    let case Type::ConstArgument { type, value } = *length else return nil;
557 614
    if *type <> Type::U32 or value.negative
558 615
        or value.magnitude > parser::U32_MAX as u64
1137 1194
    loopDepth: u32,
1138 1195
}
1139 1196
1140 1197
/// Unwrap a pointer type for pattern matching.
1141 1198
export fn unwrapMatchSubject(ty: Type) -> MatchSubject {
1142 -
    if let case Type::Pointer(pointer) = ty {
1143 -
        let by = MatchBy::MutRef if pointer.mutable else MatchBy::Ref;
1144 -
        return MatchSubject { effectiveTy: *pointer.target, by };
1199 +
    if let case Type::Core(CoreType::Pointer { shape: PointerShape::Thin, properties, target }) = ty {
1200 +
        let by = MatchBy::MutRef if properties.mutable else MatchBy::Ref;
1201 +
        return MatchSubject { effectiveTy: *target, by };
1145 1202
    }
1146 1203
    return MatchSubject { effectiveTy: ty, by: MatchBy::Value };
1147 1204
}
1148 1205
1149 1206
/// Global resolver state.
1289 1346
}
1290 1347
1291 1348
/// Return whether a type contains a rigid generic parameter.
1292 1349
export fn containsGenericParameter(ty: Type) -> bool {
1293 1350
    match ty {
1294 -
        case Type::Pointer(pointer) =>
1295 -
            return containsGenericParameter(*pointer.target),
1296 -
        case Type::Slice(slice) =>
1297 -
            return containsGenericParameter(*slice.item),
1351 +
        case Type::Core(core) => {
1352 +
            if containsGenericParameter(*coreValueArgument(core)) {
1353 +
                return true;
1354 +
            }
1355 +
            if let argument = coreConstantArgument(core) {
1356 +
                return containsGenericParameter(*argument);
1357 +
            }
1358 +
            return false;
1359 +
        }
1298 1360
        case Type::Parameter(_), Type::ConstParameter(_),
1299 1361
             Type::GenericConstExpr { .. } => return true,
1300 -
        case Type::Array(array) =>
1301 -
            return containsGenericParameter(*array.item)
1302 -
                or containsGenericParameter(*array.length),
1303 -
        case Type::Optional(inner) => return containsGenericParameter(*inner),
1304 1362
        // Symbolic anonymous records require materialization even when their
1305 1363
        // own fields happen not to mention a rigid parameter.
1306 1364
        case Type::GenericRecord(_) => return true,
1307 1365
        case Type::GenericDataApply(_) => return true,
1308 1366
        case Type::Fn(info) => {
1336 1394
        }
1337 1395
        else => return false,
1338 1396
    }
1339 1397
}
1340 1398
1341 -
/// Return whether a by-value type reaches an in-progress nominal placeholder.
1342 -
fn hasUnresolvedNominalLayout(ty: Type) -> bool {
1399 +
/// Condition that prevents ordinary by-value layout.
1400 +
union LayoutObstacle {
1401 +
    /// A path reaches an in-progress nominal declaration.
1402 +
    UnresolvedNominal,
1403 +
    /// A path reaches an opaque value.
1404 +
    Opaque,
1405 +
    /// Layout depends on unresolved generic arguments.
1406 +
    Symbolic,
1407 +
}
1408 +
1409 +
/// Return whether a type reaches one layout obstacle by value.
1410 +
fn hasLayoutObstacle(ty: Type, obstacle: LayoutObstacle) -> bool {
1343 1411
    match ty {
1344 -
        case Type::Pointer(_), Type::Slice(_) => return false,
1345 -
        case Type::Array(array) => return hasUnresolvedNominalLayout(*array.item),
1346 -
        case Type::Optional(inner) => return hasUnresolvedNominalLayout(*inner),
1412 +
        // Pointer and slice layouts do not depend on their targets.
1413 +
        case Type::Core(CoreType::Pointer { .. }),
1414 +
             Type::TraitObject(_),
1415 +
             Type::Fn(_) => return false,
1416 +
        case Type::Core(core) => {
1417 +
            if hasLayoutObstacle(*coreValueArgument(core), obstacle) {
1418 +
                return true;
1419 +
            }
1420 +
            if obstacle == LayoutObstacle::Symbolic {
1421 +
                if let argument = coreConstantArgument(core) {
1422 +
                    return containsGenericParameter(*argument);
1423 +
                }
1424 +
            }
1425 +
            return false;
1426 +
        }
1347 1427
        case Type::Nominal(info) => {
1428 +
            if obstacle <> LayoutObstacle::UnresolvedNominal {
1429 +
                return false;
1430 +
            }
1348 1431
            if let case NominalType::Placeholder(_) = *info {
1349 1432
                return true;
1350 1433
            }
1351 1434
            return false;
1352 1435
        }
1353 1436
        case Type::GenericRecord(rec) => {
1437 +
            if obstacle == LayoutObstacle::Symbolic {
1438 +
                return true;
1439 +
            }
1354 1440
            for field in rec.fields {
1355 -
                if hasUnresolvedNominalLayout(field.fieldType) {
1441 +
                if hasLayoutObstacle(field.fieldType, obstacle) {
1356 1442
                    return true;
1357 1443
                }
1358 1444
            }
1359 1445
            return false;
1360 1446
        }
1447 +
        case Type::Opaque => return obstacle == LayoutObstacle::Opaque,
1448 +
        case Type::Parameter(_), Type::ConstParameter(_),
1449 +
             Type::GenericConstExpr { .. }, Type::GenericDataApply(_) =>
1450 +
            return obstacle == LayoutObstacle::Symbolic,
1361 1451
        else => return false,
1362 1452
    }
1363 1453
}
1364 1454
1365 1455
/// Materialize concrete generic data applications within a type while
1368 1458
    self: *mut Resolver,
1369 1459
    ty: Type,
1370 1460
    site: *ast::Node,
1371 1461
) -> Type throws (ResolveError) {
1372 1462
    match ty {
1373 -
        case Type::Pointer(pointer) => {
1374 -
            let inner = try materializeConcreteGenericData(self, *pointer.target, site);
1375 -
            return Type::Pointer(PointerType {
1376 -
                class: pointer.class,
1377 -
                target: allocType(self, inner),
1378 -
                mutable: pointer.mutable,
1379 -
            });
1380 -
        }
1381 -
        case Type::Slice(slice) => {
1382 -
            let inner = try materializeConcreteGenericData(self, *slice.item, site);
1383 -
            return Type::Slice(SliceType {
1384 -
                class: slice.class,
1385 -
                item: allocType(self, inner),
1386 -
                mutable: slice.mutable,
1387 -
            });
1388 -
        }
1389 -
        case Type::Array(array) => {
1390 -
            let item = try materializeConcreteGenericData(self, *array.item, site);
1391 -
            let length = try materializeConcreteGenericData(
1392 -
                self, *array.length, site
1463 +
        case Type::Core(core) => {
1464 +
            let argument = try materializeConcreteGenericData(
1465 +
                self, *coreValueArgument(core), site
1393 1466
            );
1394 -
            return Type::Array(ArrayType {
1395 -
                item: allocType(self, item),
1396 -
                length: allocType(self, length),
1397 -
            });
1398 -
        }
1399 -
        case Type::Optional(inner) => {
1400 -
            let value = try materializeConcreteGenericData(self, *inner, site);
1401 -
            return Type::Optional(allocType(self, value));
1467 +
            let mut constantArgument: ?*Type = nil;
1468 +
            if let descriptor = coreConstantArgument(core) {
1469 +
                let concrete = try materializeConcreteGenericData(
1470 +
                    self, *descriptor, site
1471 +
                );
1472 +
                set constantArgument = allocType(self, concrete);
1473 +
            }
1474 +
            return Type::Core(coreWithArguments(
1475 +
                core,
1476 +
                allocType(self, argument),
1477 +
                constantArgument,
1478 +
            ));
1402 1479
        }
1403 1480
        case Type::GenericDataApply(app) => {
1404 1481
            let a = alloc::arenaAllocator(&mut self.arena);
1405 1482
            let mut args = vec::Vec⟨*Type⟩ { data: &mut [], len: 0 };
1406 1483
            let mut concrete = true;
1560 1637
export fn copyStructuralTypeToArena(
1561 1638
    ty: Type,
1562 1639
    arena: *mut alloc::Arena,
1563 1640
) -> Type {
1564 1641
    match ty {
1565 -
        case Type::Pointer(pointer) => {
1566 -
            let target = copyStructuralTypeToArena(*pointer.target, arena);
1567 -
            return Type::Pointer(PointerType {
1568 -
                class: pointer.class,
1569 -
                target: allocArenaType(arena, target),
1570 -
                mutable: pointer.mutable,
1571 -
            });
1572 -
        }
1573 -
        case Type::Slice(slice) => {
1574 -
            let item = copyStructuralTypeToArena(*slice.item, arena);
1575 -
            return Type::Slice(SliceType {
1576 -
                class: slice.class,
1577 -
                item: allocArenaType(arena, item),
1578 -
                mutable: slice.mutable,
1579 -
            });
1580 -
        }
1581 -
        case Type::Array(array) => {
1582 -
            let item = copyStructuralTypeToArena(*array.item, arena);
1583 -
            let length = copyStructuralTypeToArena(*array.length, arena);
1584 -
            return Type::Array(ArrayType {
1585 -
                item: allocArenaType(arena, item),
1586 -
                length: allocArenaType(arena, length),
1587 -
            });
1588 -
        }
1589 -
        case Type::Optional(inner) => {
1590 -
            let stored = copyStructuralTypeToArena(*inner, arena);
1591 -
            return Type::Optional(allocArenaType(arena, stored));
1642 +
        case Type::Core(core) => {
1643 +
            let argument = copyStructuralTypeToArena(
1644 +
                *coreValueArgument(core), arena
1645 +
            );
1646 +
            let mut constantArgument: ?*Type = nil;
1647 +
            if let descriptor = coreConstantArgument(core) {
1648 +
                let stored = copyStructuralTypeToArena(*descriptor, arena);
1649 +
                set constantArgument = allocArenaType(arena, stored);
1650 +
            }
1651 +
            return Type::Core(coreWithArguments(
1652 +
                core,
1653 +
                allocArenaType(arena, argument),
1654 +
                constantArgument,
1655 +
            ));
1592 1656
        }
1593 1657
        case Type::Fn(info) => {
1594 1658
            let a = alloc::arenaAllocator(arena);
1595 1659
            let mut params = vec::Vec⟨*Type⟩ { data: &mut [], len: 0 };
1596 1660
            let mut throwList = vec::Vec⟨*Type⟩ { data: &mut [], len: 0 };
1663 1727
                        else throw ResolveError::Failure;
1664 1728
                    return Type::ConstArgument { type, value };
1665 1729
                }
1666 1730
            }
1667 1731
        }
1668 -
        case Type::Pointer(pointer) => {
1669 -
            let inner = try substituteTypeWithContext(context, *pointer.target, sub);
1670 -
            return Type::Pointer(PointerType {
1671 -
                class: pointer.class,
1672 -
                target: allocSubstitutedType(context, inner),
1673 -
                mutable: pointer.mutable,
1674 -
            });
1675 -
        }
1676 -
        case Type::Slice(slice) => {
1677 -
            let inner = try substituteTypeWithContext(context, *slice.item, sub);
1678 -
            return Type::Slice(SliceType {
1679 -
                class: slice.class,
1680 -
                item: allocSubstitutedType(context, inner),
1681 -
                mutable: slice.mutable,
1682 -
            });
1683 -
        }
1684 -
        case Type::Array(array) => {
1685 -
            let item = try substituteTypeWithContext(context, *array.item, sub);
1686 -
            let length = try substituteTypeWithContext(
1687 -
                context, *array.length, sub
1732 +
        case Type::Core(core) => {
1733 +
            let argument = try substituteTypeWithContext(
1734 +
                context, *coreValueArgument(core), sub
1688 1735
            );
1689 -
            return Type::Array(ArrayType {
1690 -
                item: allocSubstitutedType(context, item),
1691 -
                length: allocSubstitutedType(context, length),
1692 -
            });
1693 -
        }
1694 -
        case Type::Optional(inner) => {
1695 -
            let value = try substituteTypeWithContext(context, *inner, sub);
1696 -
            return Type::Optional(allocSubstitutedType(context, value));
1736 +
            let mut constantArgument: ?*Type = nil;
1737 +
            if let descriptor = coreConstantArgument(core) {
1738 +
                let replacement = try substituteTypeWithContext(
1739 +
                    context, *descriptor, sub
1740 +
                );
1741 +
                set constantArgument =
1742 +
                    allocSubstitutedType(context, replacement);
1743 +
            }
1744 +
            return Type::Core(coreWithArguments(
1745 +
                core,
1746 +
                allocSubstitutedType(context, argument),
1747 +
                constantArgument,
1748 +
            ));
1697 1749
        }
1698 1750
        case Type::GenericDataApply(app) => {
1699 1751
            let a = alloc::arenaAllocator(substitutionArena(context));
1700 1752
            let mut args = vec::Vec⟨*Type⟩ { data: &mut [], len: 0 };
1701 1753
            let mut symbolic = false;
1753 1805
                    context, field.fieldType, sub
1754 1806
                );
1755 1807
                let mut storedFieldType = fieldType;
1756 1808
                match *context {
1757 1809
                    case TypeSubstitutionContext::Resolution { resolver, site } => {
1758 -
                        if hasUnresolvedNominalLayout(fieldType) {
1810 +
                        if hasLayoutObstacle(
1811 +
                            fieldType, LayoutObstacle::UnresolvedNominal
1812 +
                        ) {
1759 1813
                            throw emitError(
1760 1814
                                resolver, site, ErrorKind::GenericRecursiveLayout
1761 1815
                            );
1762 1816
                        }
1763 1817
                        try ensureStorableType(resolver, site, fieldType);
2484 2538
}
2485 2539
2486 2540
/// Get the layout of a type.
2487 2541
export fn getTypeLayout(ty: Type) -> Layout {
2488 2542
    match ty {
2489 -
        case Type::Pointer(_) => return Layout { size: PTR_SIZE, alignment: PTR_SIZE },
2490 -
        case Type::Slice(_), Type::TraitObject(_) =>
2543 +
        case Type::Core(CoreType::Pointer { shape, .. }) => {
2544 +
            match shape {
2545 +
                case PointerShape::Thin =>
2546 +
                    return Layout { size: PTR_SIZE, alignment: PTR_SIZE },
2547 +
                case PointerShape::Slice =>
2548 +
                    return Layout { size: PTR_SIZE * 2, alignment: PTR_SIZE },
2549 +
            }
2550 +
        }
2551 +
        case Type::Core(CoreType::Array { item, length }) =>
2552 +
            return getArrayLayout(item, length),
2553 +
        case Type::Core(CoreType::Optional { payload }) => {
2554 +
            if isNullableType(*payload) {
2555 +
                return getTypeLayout(*payload);
2556 +
            }
2557 +
            let payloadLayout = getTypeLayout(*payload);
2558 +
            let payloadOffset = getOptionalValOffset(*payload);
2559 +
            let alignment = max(payloadLayout.alignment, 1);
2560 +
            return Layout {
2561 +
                size: mem::alignUp(payloadOffset + payloadLayout.size, alignment),
2562 +
                alignment,
2563 +
            };
2564 +
        }
2565 +
        case Type::TraitObject(_) =>
2491 2566
            return Layout { size: PTR_SIZE * 2, alignment: PTR_SIZE },
2492 2567
        case Type::Void, Type::Never => return Layout { size: 0, alignment: 0 },
2493 2568
        case Type::Bool, Type::U8, Type::I8 => return Layout { size: 1, alignment: 1 },
2494 2569
        case Type::U16, Type::I16 => return Layout { size: 2, alignment: 2 },
2495 2570
        case Type::U32, Type::I32 => return Layout { size: 4, alignment: 4 },
2496 2571
        case Type::Int => return Layout { size: 8, alignment: 8 },
2497 2572
        case Type::U64, Type::I64 => return Layout { size: 8, alignment: 8 },
2498 2573
        case Type::Fn(_) => return Layout { size: PTR_SIZE, alignment: PTR_SIZE },
2499 -
        case Type::Array(arr) => return getArrayLayout(arr),
2500 -
        case Type::Optional(inner) => return getOptionalLayout(*inner),
2501 2574
        case Type::Nominal(info) => return getNominalLayout(*info),
2502 2575
        else => {
2503 2576
            panic "getTypeLayout: the given type cannot be layed out";
2504 2577
        }
2505 2578
    }
2517 2590
        }
2518 2591
    }
2519 2592
    return layout;
2520 2593
}
2521 2594
2522 -
/// Get the layout of a concrete array type.
2523 -
export fn getArrayLayout(arr: ArrayType) -> Layout {
2524 -
    let length = concreteArrayLength(arr.length)
2595 +
/// Get the layout of a concrete array application.
2596 +
export fn getArrayLayout(item: *Type, lengthArgument: *Type) -> Layout {
2597 +
    if hasLayoutObstacle(*lengthArgument, LayoutObstacle::Symbolic) {
2598 +
        panic "getArrayLayout: symbolic array length";
2599 +
    }
2600 +
    let length = concreteArrayLength(lengthArgument)
2525 2601
        else panic "getArrayLayout: symbolic array length";
2526 -
    let itemLayout = getTypeLayout(*arr.item);
2602 +
    let itemLayout = getTypeLayout(*item);
2527 2603
    return Layout {
2528 2604
        size: itemLayout.size * length,
2529 2605
        alignment: itemLayout.alignment,
2530 2606
    };
2531 2607
}
2532 2608
2533 -
/// Get the layout of an optional type.
2534 -
export fn getOptionalLayout(inner: Type) -> Layout {
2535 -
    // Nullable types use null pointer optimization -- no tag byte needed.
2536 -
    if isNullableType(inner) {
2537 -
        return getTypeLayout(inner);
2538 -
    }
2539 -
    let innerLayout = getTypeLayout(inner);
2540 -
    let tagSize: u32 = 1;
2541 -
    let valOffset = mem::alignUp(tagSize, innerLayout.alignment);
2542 -
    let alignment = max(innerLayout.alignment, 1);
2543 2609
2544 -
    return Layout {
2545 -
        size: mem::alignUp(valOffset + innerLayout.size, alignment),
2546 -
        alignment,
2547 -
    };
2610 +
/// Return the payload of an optional type.
2611 +
export fn optionalTypeArgument(ty: Type) -> ?*Type {
2612 +
    if let case Type::Core(CoreType::Optional { payload }) = ty {
2613 +
        return payload;
2614 +
    }
2615 +
    return nil;
2548 2616
}
2549 2617
2550 -
/// Get the payload offset within an optional aggregate.
2551 -
export fn getOptionalValOffset(inner: Type) -> u32 {
2552 -
    let innerLayout = getTypeLayout(inner);
2553 -
    return mem::alignUp(1, innerLayout.alignment);
2618 +
/// Get the byte offset of an optional payload.
2619 +
export fn getOptionalValOffset(payload: Type) -> u32 {
2620 +
    return mem::alignUp(1, getTypeLayout(payload).alignment);
2554 2621
}
2555 2622
2556 2623
/// Check if a type is optional.
2557 2624
export fn isOptionalType(ty: Type) -> bool {
2558 -
    match ty {
2559 -
        case Type::Optional(_) => return true,
2560 -
        else => return false,
2561 -
    }
2562 -
}
2563 -
2564 -
/// Check if a type uses null pointer optimization.
2565 -
/// This applies to optional pointers `?*T` and optional slices `?*[T]`,
2566 -
/// where `nil` is represented as a null data pointer with no tag byte.
2567 -
export fn isOptionalPointer(ty: Type) -> bool {
2568 -
    if let case Type::Optional(inner) = ty {
2569 -
        return isNullableType(*inner);
2570 -
    }
2571 -
    return false;
2572 -
}
2573 -
2574 -
/// Check if a type uses the optional aggregate representation.
2575 -
export fn isOptionalAggregate(ty: Type) -> bool {
2576 -
    if let case Type::Optional(inner) = ty {
2577 -
        return not isNullableType(*inner);
2578 -
    }
2579 -
    return false;
2625 +
    return optionalTypeArgument(ty) <> nil;
2580 2626
}
2581 2627
2582 2628
/// Check if a type can use null to represent `nil`.
2629 +
///
2583 2630
/// Pointers and slices have a data pointer that is never null when valid.
2584 2631
export fn isNullableType(ty: Type) -> bool {
2585 -
    match ty {
2586 -
        case Type::Pointer(_), Type::Slice(_) => return true,
2587 -
        else => return false,
2632 +
    if let case Type::Core(CoreType::Pointer { .. }) = ty {
2633 +
        return true;
2588 2634
    }
2635 +
    return false;
2589 2636
}
2590 2637
2591 2638
/// Get the layout of a nominal type.
2592 2639
export fn getNominalLayout(info: NominalType) -> Layout {
2593 2640
    match info {
2680 2727
}
2681 2728
2682 2729
/// Check if a type should be treated as an address-like value.
2683 2730
fn isAddressType(ty: Type) -> bool {
2684 2731
    match ty {
2685 -
        case Type::Pointer(_), Type::Slice(_), Type::Fn(_) => return true,
2732 +
        case Type::Core(CoreType::Pointer { .. }) => return true,
2733 +
        case Type::Fn(_) => return true,
2686 2734
        else => return false,
2687 2735
    }
2688 2736
}
2689 2737
2690 2738
/// Return the representable range for an integer type.
2753 2801
2754 2802
/// Ensure all nested nominal types in a type are resolved.
2755 2803
fn ensureTypeResolved(self: *mut Resolver, ty: Type, site: *ast::Node) throws (ResolveError) {
2756 2804
    match ty {
2757 2805
        case Type::Nominal(info) => try ensureNominalResolved(self, info, site),
2758 -
        case Type::Slice(slice) => try ensureTypeResolved(self, *slice.item, site),
2759 -
        case Type::Pointer(_) => {}, // Pointers have fixed layout, don't recurse.
2760 -
        case Type::Array(arr) => try ensureTypeResolved(self, *arr.item, site),
2761 -
        case Type::Optional(inner) => try ensureTypeResolved(self, *inner, site),
2806 +
        // Pointer and slice layouts never depend on their targets, which also
2807 +
        // cuts recursive nominal layout cycles.
2808 +
        case Type::Core(CoreType::Pointer { .. }) => {},
2809 +
        case Type::Core(core) =>
2810 +
            try ensureTypeResolved(self, *coreValueArgument(core), site),
2762 2811
        else => {},
2763 2812
    }
2764 2813
}
2765 2814
2766 2815
/// Ensure a nominal type has its body resolved.
2821 2870
        to == types::PointerClass::Owned
2822 2871
        and from == types::PointerClass::Ref
2823 2872
    );
2824 2873
}
2825 2874
2875 +
/// Return whether pointer properties permit assignment.
2876 +
fn pointerPropertiesAssignable(
2877 +
    to: PointerProperties,
2878 +
    from: PointerProperties,
2879 +
) -> bool {
2880 +
    return pointerClassesAssignable(to.class, from.class)
2881 +
        and (not to.mutable or from.mutable);
2882 +
}
2883 +
2826 2884
/// Check if the `from` type is assignable to the `to` type, and return a
2827 2885
/// coercion plan if so.
2828 2886
fn isAssignable(self: *mut Resolver, to: Type, from: Type, rval: *ast::Node) -> ?Coercion {
2829 2887
    if to == Type::Unknown or from == Type::Unknown {
2830 2888
        return nil;
2840 2898
        return Coercion::Identity;
2841 2899
    }
2842 2900
    if typesEqual(to, from) {
2843 2901
        return Coercion::Identity;
2844 2902
    }
2845 -
    if let case Type::Pointer(lhs) = to {
2846 -
        let case Type::Pointer(rhs) = from else return nil;
2847 -
        if not pointerClassesAssignable(lhs.class, rhs.class) {
2848 -
            return nil;
2849 -
        }
2850 -
        // Allow coercion from `*T` to `*opaque`, and mutable counterparts.
2851 -
        if *lhs.target == Type::Opaque {
2852 -
            if lhs.mutable and not rhs.mutable {
2903 +
    if let case Type::Core(lhs) = to {
2904 +
        match lhs {
2905 +
            case CoreType::Pointer { shape: lhsShape, properties: lhsProperties, target: lhsTarget } => {
2906 +
                let case Type::Core(CoreType::Pointer { shape: rhsShape, properties: rhsProperties, target: rhsTarget }) = from else return nil;
2907 +
                if lhsShape <> rhsShape
2908 +
                    or not pointerPropertiesAssignable(
2909 +
                        lhsProperties, rhsProperties
2910 +
                    )
2911 +
                {
2912 +
                    return nil;
2913 +
                }
2914 +
                // Allow pointer-like coercion from `T` to `opaque`, including
2915 +
                // mutable counterparts.
2916 +
                if *lhsTarget == Type::Opaque {
2917 +
                    return Coercion::Identity;
2918 +
                }
2919 +
                return isAssignable(self, *lhsTarget, *rhsTarget, rval);
2920 +
            }
2921 +
            case CoreType::Array { item: lhsItem, length: lhsLength } => {
2922 +
                let case Type::Core(CoreType::Array {
2923 +
                    item: rhsItem, length: rhsLength
2924 +
                }) = from else return nil;
2925 +
                if not typesEqual(*lhsLength, *rhsLength) {
2926 +
                    return nil;
2927 +
                }
2928 +
                // For array literals, check each element individually for
2929 +
                // assignability.
2930 +
                match rval.value {
2931 +
                    case ast::NodeValue::ArrayLit(items) => {
2932 +
                        if let length = concreteArrayLength(lhsLength); length == 0 {
2933 +
                            return Coercion::Identity;
2934 +
                        }
2935 +
                        // TODO: This won't work, because we should be setting
2936 +
                        // coercions for every list item, but we don't. It's best
2937 +
                        // to not have an `isAssignable` function and just have
2938 +
                        // one that records coercions.
2939 +
                        if isListAssignable(self, *lhsItem, items) {
2940 +
                            return Coercion::Identity;
2941 +
                        }
2942 +
                        return nil;
2943 +
                    }
2944 +
                    case ast::NodeValue::ArrayRepeatLit(repeat) =>
2945 +
                        return isAssignable(
2946 +
                            self, *lhsItem, *rhsItem, repeat.item
2947 +
                        ),
2948 +
                    else => {
2949 +
                        if typesEqual(*lhsItem, *rhsItem) {
2950 +
                            return Coercion::Identity;
2951 +
                        }
2952 +
                        return nil;
2953 +
                    }
2954 +
                }
2955 +
            }
2956 +
            case CoreType::Optional { payload } => {
2957 +
                if from == Type::Nil {
2958 +
                    return Coercion::OptionalLift(to);
2959 +
                }
2960 +
                if let _ = isAssignable(self, *payload, from, rval) {
2961 +
                    return Coercion::OptionalLift(to);
2962 +
                }
2963 +
                if let case Type::Core(CoreType::Optional {
2964 +
                    payload: fromPayload
2965 +
                }) = from {
2966 +
                    return isAssignable(self, *payload, *fromPayload, rval);
2967 +
                }
2853 2968
                return nil;
2854 2969
            }
2855 -
            return Coercion::Identity;
2856 -
        }
2857 -
        if lhs.mutable and not rhs.mutable {
2858 -
            return nil;
2859 2970
        }
2860 -
        return isAssignable(self, *lhs.target, *rhs.target, rval);
2861 2971
    }
2862 2972
    if let case Type::TraitObject(lhs) = to {
2863 -
        if let case Type::Pointer(rhs) = from {
2864 -
            if not pointerClassesAssignable(lhs.class, rhs.class)
2865 -
                or (lhs.mutable and not rhs.mutable)
2866 -
            {
2973 +
        if let case Type::Core(CoreType::Pointer { shape: PointerShape::Thin, properties, target }) = from {
2974 +
            if not pointerPropertiesAssignable(lhs.properties, properties) {
2867 2975
                return nil;
2868 2976
            }
2869 -
            if let inst = findInstance(self, lhs.traitInfo, *rhs.target) {
2870 -
                return Coercion::TraitObject { traitInfo: lhs.traitInfo, inst };
2977 +
            if let inst = findInstance(self, lhs.traitInfo, *target) {
2978 +
                return Coercion::TraitObject {
2979 +
                    traitInfo: lhs.traitInfo,
2980 +
                    inst,
2981 +
                };
2871 2982
            }
2872 2983
        }
2873 2984
        if let case Type::TraitObject(rhs) = from {
2874 -
            if not pointerClassesAssignable(lhs.class, rhs.class)
2875 -
                or lhs.traitInfo <> rhs.traitInfo
2876 -
            {
2877 -
                return nil;
2878 -
            }
2879 -
            if lhs.mutable and not rhs.mutable {
2985 +
            if not pointerPropertiesAssignable(
2986 +
                lhs.properties, rhs.properties
2987 +
            ) or lhs.traitInfo <> rhs.traitInfo {
2880 2988
                return nil;
2881 2989
            }
2882 2990
            return Coercion::Identity;
2883 2991
        }
2884 2992
        return nil;
2885 2993
    }
2886 -
    if let case Type::Slice(lhs) = to {
2887 -
        let case Type::Slice(rhs) = from else return nil;
2888 -
        if not pointerClassesAssignable(lhs.class, rhs.class)
2889 -
            or (lhs.mutable and not rhs.mutable)
2890 -
        {
2891 -
            return nil;
2892 -
        }
2893 -
        // Allow coercion from `*[T]` to `*[opaque]`, and mutable counterparts.
2894 -
        if *lhs.item == Type::Opaque {
2895 -
            return Coercion::Identity;
2896 -
        }
2897 -
        return isAssignable(self, *lhs.item, *rhs.item, rval);
2898 -
    }
2899 -
    match to {
2900 -
        case Type::Array(lhs) => {
2901 -
            let case Type::Array(rhs) = from
2902 -
                else return nil;
2903 -
2904 -
            if not typesEqual(*lhs.length, *rhs.length) {
2905 -
                return nil;
2906 -
            }
2907 -
            // For array literals, check each element individually for
2908 -
            // assignability.
2909 -
            match rval.value {
2910 -
                case ast::NodeValue::ArrayLit(items) => {
2911 -
                    if let length = concreteArrayLength(lhs.length); length == 0 {
2912 -
                        return Coercion::Identity;
2913 -
                    }
2914 -
                    // TODO: This won't work, because we should be setting coercions
2915 -
                    // for every list item, but we don't. It's best to not have an
2916 -
                    // `isAssignable` function and just have one that records coercions.
2917 -
                    if isListAssignable(self, *lhs.item, items) {
2918 -
                        return Coercion::Identity;
2919 -
                    }
2920 -
                    return nil;
2921 -
                }
2922 -
                case ast::NodeValue::ArrayRepeatLit(repeat) => {
2923 -
                    return isAssignable(self, *lhs.item, *rhs.item, repeat.item);
2924 -
                }
2925 -
                else => {
2926 -
                    if typesEqual(*lhs.item, *rhs.item) {
2927 -
                        return Coercion::Identity;
2928 -
                    }
2929 -
                    return nil;
2930 -
                }
2931 -
            }
2932 -
        }
2933 -
2934 -
        case Type::Optional(inner) => {
2935 -
            if from == Type::Nil {
2936 -
                return Coercion::OptionalLift(to);
2937 -
            }
2938 -
            if let _ = isAssignable(self, *inner, from, rval) {
2939 -
                return Coercion::OptionalLift(to);
2940 -
            }
2941 -
            if let case Type::Optional(fromInner) = from {
2942 -
                return isAssignable(self, *inner, *fromInner, rval);
2943 -
            }
2944 -
            return nil;
2945 -
        }
2946 2994
2995 +
    match to {
2947 2996
        case Type::Fn(toInfo) => {
2948 2997
            // Allow function type structural matching.
2949 2998
            if let case Type::Fn(fromInfo) = from {
2950 2999
                if fnTypeEqual(toInfo, fromInfo) {
2951 3000
                    return Coercion::Identity;
3011 3060
        }
3012 3061
    }
3013 3062
    return true;
3014 3063
}
3015 3064
3065 +
/// Return whether two core applications are structurally equal.
3066 +
fn coreTypesEqual(left: CoreType, right: CoreType) -> bool {
3067 +
    match left {
3068 +
        case CoreType::Pointer { shape, properties, target } => {
3069 +
            let case CoreType::Pointer {
3070 +
                shape: rightShape, properties: rightProperties, target: rightTarget
3071 +
            } = right else return false;
3072 +
            return shape == rightShape and properties == rightProperties
3073 +
                and typesEqual(*target, *rightTarget);
3074 +
        }
3075 +
        case CoreType::Array { item, length } => {
3076 +
            let case CoreType::Array {
3077 +
                item: rightItem, length: rightLength
3078 +
            } = right else return false;
3079 +
            return typesEqual(*item, *rightItem)
3080 +
                and typesEqual(*length, *rightLength);
3081 +
        }
3082 +
        case CoreType::Optional { payload } => {
3083 +
            let case CoreType::Optional { payload: rightPayload } = right
3084 +
                else return false;
3085 +
            return typesEqual(*payload, *rightPayload);
3086 +
        }
3087 +
    }
3088 +
}
3089 +
3016 3090
/// Check if two types are structurally equal.
3017 3091
export fn typesEqual(a: Type, b: Type) -> bool {
3092 +
    if let case Type::Core(lhs) = a {
3093 +
        let case Type::Core(rhs) = b else return false;
3094 +
        return coreTypesEqual(lhs, rhs);
3095 +
    }
3018 3096
    if a == b {
3019 3097
        return true;
3020 3098
    }
3021 -
    if let case Type::Pointer(av) = a {
3022 -
        let case Type::Pointer(bv) = b else return false;
3023 -
        return av.class == bv.class and av.mutable == bv.mutable
3024 -
            and typesEqual(*av.target, *bv.target);
3025 -
    }
3026 -
    if let case Type::Slice(av) = a {
3027 -
        let case Type::Slice(bv) = b else return false;
3028 -
        return av.class == bv.class and av.mutable == bv.mutable
3029 -
            and typesEqual(*av.item, *bv.item);
3030 -
    }
3031 3099
    if let case Type::TraitObject(av) = a {
3032 3100
        let case Type::TraitObject(bv) = b else return false;
3033 -
        return av.class == bv.class and av.mutable == bv.mutable
3101 +
        return av.properties == bv.properties
3034 3102
            and av.traitInfo == bv.traitInfo;
3035 3103
    }
3036 3104
    match a {
3037 3105
        case Type::Range { start: aStart, end: aEnd } => {
3038 3106
            let case Type::Range { start: bStart, end: bEnd } = b
3049 3117
                let bValue = bEnd else return false;
3050 3118
                return typesEqual(*aValue, *bValue);
3051 3119
            }
3052 3120
            return bEnd == nil;
3053 3121
        }
3054 -
        case Type::Array(aa) => {
3055 -
            let case Type::Array(ab) = b else return false;
3056 -
            return typesEqual(*aa.length, *ab.length)
3057 -
                and typesEqual(*aa.item, *ab.item);
3058 -
        }
3059 -
        case Type::Optional(oa) => {
3060 -
            let case Type::Optional(ob) = b else return false;
3061 -
            return typesEqual(*oa, *ob);
3062 -
        }
3063 3122
        case Type::Fn(fa) => {
3064 3123
            let case Type::Fn(fb) = b else return false;
3065 3124
            return fnTypeEqual(fa, fb);
3066 3125
        }
3067 3126
        case Type::GenericDataApply(aa) => {
3078 3137
        }
3079 3138
        else => return false,
3080 3139
    }
3081 3140
}
3082 3141
3083 -
/// Return whether `ty` is a direct reference.
3084 -
export fn isRefType(ty: Type) -> bool {
3142 +
/// Return the shared properties of a direct pointer-like value.
3143 +
fn pointerProperties(ty: Type) -> ?PointerProperties {
3085 3144
    match ty {
3086 -
        case Type::Pointer(PointerType { class: types::PointerClass::Ref, .. }),
3087 -
             Type::Slice(SliceType { class: types::PointerClass::Ref, .. }),
3088 -
             Type::TraitObject(TraitObjectType { class: types::PointerClass::Ref, .. }) => return true,
3089 -
        else => return false,
3145 +
        case Type::Core(CoreType::Pointer { properties, .. }) =>
3146 +
            return properties,
3147 +
        case Type::TraitObject(info) => return info.properties,
3148 +
        else => return nil,
3090 3149
    }
3091 3150
}
3092 3151
3152 +
/// Return whether `ty` is a direct reference.
3153 +
export fn isRefType(ty: Type) -> bool {
3154 +
    let properties = pointerProperties(ty) else return false;
3155 +
    return properties.class == types::PointerClass::Ref;
3156 +
}
3157 +
3093 3158
/// Return whether a type contains a reference.
3094 3159
fn containsRef(ty: Type) -> bool {
3095 3160
    if isRefType(ty) {
3096 3161
        return true;
3097 3162
    }
3098 -
    if let case Type::Pointer(pointer) = ty {
3099 -
        return containsRef(*pointer.target);
3100 -
    }
3101 -
    if let case Type::Slice(slice) = ty {
3102 -
        return containsRef(*slice.item);
3103 -
    }
3104 3163
    match ty {
3105 -
        case Type::Array(array) => return containsRef(*array.item),
3106 -
        case Type::Optional(inner) => return containsRef(*inner),
3164 +
        case Type::Core(core) =>
3165 +
            return containsRef(*coreValueArgument(core)),
3107 3166
        case Type::GenericRecord(rec) => {
3108 3167
            for field in rec.fields {
3109 3168
                if containsRef(field.fieldType) {
3110 3169
                    return true;
3111 3170
                }
3119 3178
    }
3120 3179
}
3121 3180
3122 3181
/// Return whether a type is exact-linear.
3123 3182
export fn isLinear(ty: Type) -> bool {
3183 +
    if let properties = pointerProperties(ty) {
3184 +
        return properties.class == types::PointerClass::Owned;
3185 +
    }
3124 3186
    match ty {
3125 -
        case Type::Pointer(PointerType { class: types::PointerClass::Owned, .. }),
3126 -
             Type::Slice(SliceType { class: types::PointerClass::Owned, .. }),
3127 -
             Type::TraitObject(TraitObjectType { class: types::PointerClass::Owned, .. }) => return true,
3128 -
        case Type::Pointer(PointerType { class: types::PointerClass::Ref, .. }),
3129 -
             Type::Pointer(PointerType { class: types::PointerClass::Unsafe, .. }),
3130 -
             Type::Slice(SliceType { class: types::PointerClass::Ref, .. }),
3131 -
             Type::Slice(SliceType { class: types::PointerClass::Unsafe, .. }),
3132 -
             Type::TraitObject(TraitObjectType { class: types::PointerClass::Ref, .. }),
3133 -
             Type::TraitObject(TraitObjectType { class: types::PointerClass::Unsafe, .. }) => return false,
3134 -
3135 -
        case Type::Array(array) => return isLinear(*array.item),
3136 -
        case Type::Optional(inner) => return isLinear(*inner),
3187 +
        case Type::Core(core) =>
3188 +
            return isLinear(*coreValueArgument(core)),
3137 3189
        case Type::Nominal(NominalType::Record(recInfo)) => {
3138 3190
            if recInfo.declaredLinear {
3139 3191
                return true;
3140 3192
            }
3141 3193
            for field in recInfo.fields {
3160 3212
    }
3161 3213
}
3162 3214
3163 3215
/// Return whether `ty` is a direct unsafe pointer-like value.
3164 3216
fn isUnsafePointerType(ty: Type) -> bool {
3165 -
    match ty {
3166 -
        case Type::Pointer(PointerType { class: types::PointerClass::Unsafe, .. }),
3167 -
             Type::Slice(SliceType { class: types::PointerClass::Unsafe, .. }),
3168 -
             Type::TraitObject(TraitObjectType { class: types::PointerClass::Unsafe, .. }) => return true,
3169 -
        else => return false,
3170 -
    }
3217 +
    let properties = pointerProperties(ty) else return false;
3218 +
    return properties.class == types::PointerClass::Unsafe;
3171 3219
}
3172 3220
3173 3221
/// Get the record info from a record type.
3174 3222
export fn getRecord(ty: Type) -> ?RecordType {
3175 3223
    let case Type::Nominal(NominalType::Record(recInfo)) = ty else return nil;
3176 3224
    return recInfo;
3177 3225
}
3178 3226
3179 -
/// Auto-dereference a type: if it's a pointer, return the target type.
3227 +
/// Auto-dereference a type: if it is a pointer, return the target type.
3180 3228
export fn autoDeref(ty: Type) -> Type {
3181 -
    if let case Type::Pointer(view) = ty {
3182 -
        return *view.target;
3229 +
    if let case Type::Core(CoreType::Pointer { shape: PointerShape::Thin, target, .. }) = ty {
3230 +
        return *target;
3183 3231
    }
3184 3232
    return ty;
3185 3233
}
3186 3234
3187 3235
/// Get field info for a record-like type (records, slices) by field index.
3188 3236
export fn getRecordField(ty: Type, index: u32) -> ?RecordField {
3189 -
    if let case Type::Slice(slice) = ty {
3237 +
    if let case Type::Core(CoreType::Pointer { shape: PointerShape::Slice, properties, target }) = ty {
3190 3238
        match index {
3191 3239
            case 0 => return RecordField {
3192 3240
                name: PTR_FIELD,
3193 -
                fieldType: Type::Pointer(PointerType {
3194 -
                    class: slice.class,
3195 -
                    target: slice.item,
3196 -
                    mutable: slice.mutable,
3197 -
                }),
3241 +
                fieldType: pointerType(
3242 +
                    properties.class, target, properties.mutable
3243 +
                ),
3198 3244
                offset: 0,
3199 3245
            },
3200 3246
            case 1 => return RecordField {
3201 3247
                name: LEN_FIELD,
3202 3248
                fieldType: Type::U32,
3220 3266
    }
3221 3267
    if left == right {
3222 3268
        return true;
3223 3269
    }
3224 3270
    // Comparisons with optionals.
3225 -
    if let case Type::Optional(l) = left {
3226 -
        if let case Type::Optional(r) = right {
3227 -
            return isComparable(*l, *r);
3271 +
    if let case Type::Core(CoreType::Optional { payload: lhs }) = left {
3272 +
        if let case Type::Core(CoreType::Optional { payload: rhs }) = right {
3273 +
            return isComparable(*lhs, *rhs);
3228 3274
        } else if right == Type::Nil {
3229 3275
            return true;
3230 3276
        }
3231 -
        return isComparable(*l, right);
3232 -
    } else if let case Type::Optional(_) = right {
3233 -
        return isComparable(right, left); // Flip order.
3277 +
        return isComparable(*lhs, right);
3278 +
    } else if isOptionalType(right) {
3279 +
        return isComparable(right, left);
3234 3280
    }
3235 3281
    // Pointer comparisons ignore mutability.
3236 -
    if let case Type::Pointer(l) = left {
3237 -
        if let case Type::Pointer(r) = right {
3238 -
            return typesEqual(*l.target, *r.target);
3282 +
    if let case Type::Core(CoreType::Pointer { shape: PointerShape::Thin, target: lhs, .. }) = left {
3283 +
        if let case Type::Core(CoreType::Pointer { shape: PointerShape::Thin, target: rhs, .. }) = right {
3284 +
            return typesEqual(*lhs, *rhs);
3239 3285
        }
3240 3286
    }
3241 3287
    // Numeric types.
3242 3288
    if isNumericType(left) and isNumericType(right) {
3243 3289
        return true;
3261 3307
3262 3308
/// Check that a type is optional, otherwise throw an error.
3263 3309
fn checkOptional(self: *mut Resolver, node: *ast::Node) -> *Type
3264 3310
    throws (ResolveError)
3265 3311
{
3266 -
    if let case Type::Optional(inner) = try infer(self, node) {
3267 -
        return inner;
3312 +
    if let case Type::Core(CoreType::Optional { payload }) =
3313 +
        try infer(self, node)
3314 +
    {
3315 +
        return payload;
3268 3316
    }
3269 3317
    throw emitError(self, node, ErrorKind::ExpectedOptional);
3270 3318
}
3271 3319
3272 3320
/// Check that a node's type is equal to the expected type.
3750 3798
    self: *mut Resolver,
3751 3799
    ty: Type,
3752 3800
    visited: ?*GenericRootVisit,
3753 3801
) -> bool {
3754 3802
    match ty {
3755 -
        case Type::Pointer(pointer) =>
3756 -
            return markGenericDataTypeRootedInner(self, *pointer.target, visited),
3757 -
        case Type::Slice(slice) =>
3758 -
            return markGenericDataTypeRootedInner(self, *slice.item, visited),
3759 -
        case Type::Array(array) =>
3760 -
            return markGenericDataTypeRootedInner(self, *array.item, visited),
3761 -
        case Type::Optional(inner) =>
3762 -
            return markGenericDataTypeRootedInner(self, *inner, visited),
3803 +
        case Type::Core(core) => {
3804 +
            let mut changed = markGenericDataTypeRootedInner(
3805 +
                self, *coreValueArgument(core), visited
3806 +
            );
3807 +
            if let argument = coreConstantArgument(core) {
3808 +
                set changed = markGenericDataTypeRootedInner(
3809 +
                    self, *argument, visited
3810 +
                ) or changed;
3811 +
            }
3812 +
            return changed;
3813 +
        }
3763 3814
        case Type::Fn(info) => {
3764 3815
            let mut changed = markGenericDataTypeRootedInner(
3765 3816
                self, *info.returnType, visited
3766 3817
            );
3767 3818
            for param in info.paramTypes {
4100 4151
                members, substitution
4101 4152
            } => {
4102 4153
                set fieldType = try substituteType(
4103 4154
                    self, *members[i], substitution, typeNode
4104 4155
                );
4105 -
                if hasUnresolvedNominalLayout(fieldType) {
4156 +
                if hasLayoutObstacle(
4157 +
                    fieldType, LayoutObstacle::UnresolvedNominal
4158 +
                ) {
4106 4159
                    throw emitError(
4107 4160
                        self, typeNode, ErrorKind::GenericRecursiveLayout
4108 4161
                    );
4109 4162
                }
4110 4163
                try ensureStorableType(self, typeNode, fieldType);
4175 4228
                members, substitution
4176 4229
            } => {
4177 4230
                set valueType = try substituteType(
4178 4231
                    self, *members[i], substitution, variantNode
4179 4232
                );
4180 -
                if hasUnresolvedNominalLayout(valueType) {
4233 +
                if hasLayoutObstacle(
4234 +
                    valueType, LayoutObstacle::UnresolvedNominal
4235 +
                ) {
4181 4236
                    throw emitError(
4182 4237
                        self, variantNode, ErrorKind::GenericRecursiveLayout
4183 4238
                    );
4184 4239
                }
4185 4240
                if let typeNode = variantDecl.type {
4854 4909
/// Reject nested references while allowing a direct parameter reference.
4855 4910
fn validateValueTypeReferences(self: *mut Resolver, node: *ast::Node, ty: Type)
4856 4911
    throws (ResolveError)
4857 4912
{
4858 4913
    if isRefType(ty) {
4859 -
        if let case Type::Pointer(pointer) = ty {
4860 -
            if containsRef(*pointer.target) {
4861 -
                throw emitError(self, node, ErrorKind::InvalidRefPosition);
4862 -
            }
4863 -
        } else if let case Type::Slice(slice) = ty {
4864 -
            if containsRef(*slice.item) {
4914 +
        if let case Type::Core(core) = ty {
4915 +
            if containsRef(*coreValueArgument(core)) {
4865 4916
                throw emitError(self, node, ErrorKind::InvalidRefPosition);
4866 4917
            }
4867 4918
        }
4868 4919
    } else if containsRef(ty) {
4869 4920
        throw emitError(self, node, ErrorKind::InvalidRefPosition);
5024 5075
        case ast::NodeValue::Try(expr) => return try resolveTry(self, node, expr, hint),
5025 5076
        case ast::NodeValue::Return { value } => return try resolveReturn(self, node, value),
5026 5077
        case ast::NodeValue::Throw { expr } => return try resolveThrow(self, node, expr),
5027 5078
        case ast::NodeValue::Panic { message } => {
5028 5079
            // TODO: Have easy access to string type.
5029 -
            try visitOptional(self, message, Type::Slice(SliceType {
5030 -
                class: types::PointerClass::Owned,
5031 -
                item: allocType(self, Type::U8),
5032 -
                mutable: false,
5033 -
            }));
5080 +
            try visitOptional(self, message, sliceType(types::PointerClass::Owned, allocType(self, Type::U8), false));
5034 5081
            return setNodeType(self, node, Type::Never);
5035 5082
        },
5036 5083
        case ast::NodeValue::Assert { condition, message } => {
5037 5084
            try visit(self, condition, Type::Bool);
5038 5085
            // TODO: Have easy access to string type.
5039 -
            try visitOptional(self, message, Type::Slice(SliceType {
5040 -
                class: types::PointerClass::Owned,
5041 -
                item: allocType(self, Type::U8),
5042 -
                mutable: false,
5043 -
            }));
5086 +
            try visitOptional(self, message, sliceType(types::PointerClass::Owned, allocType(self, Type::U8), false));
5044 5087
            return setNodeType(self, node, Type::Void);
5045 5088
        },
5046 5089
        case ast::NodeValue::UnOp(unop) => return try resolveUnOp(self, node, unop),
5047 5090
        case ast::NodeValue::ExprStmt(expr) => {
5048 5091
            // Pass `Void` as expected type to indicate value is discarded.
5055 5098
            // `super` by itself is invalid, must be used in scope access.
5056 5099
            throw emitError(self, node, ErrorKind::InvalidModulePath);
5057 5100
        },
5058 5101
        case ast::NodeValue::Nil => {
5059 5102
            // Use the hint type if it's an optional, otherwise fall back to `Nil`.
5060 -
            if let case Type::Optional(_) = hint {
5103 +
            if isOptionalType(hint) {
5061 5104
                return setNodeType(self, node, hint);
5062 5105
            }
5063 5106
            return setNodeType(self, node, Type::Nil);
5064 5107
        },
5065 5108
        case ast::NodeValue::Undef => {
5074 5117
            return setNodeType(self, node, Type::U8);
5075 5118
        }
5076 5119
        case ast::NodeValue::String(text) => {
5077 5120
            setNodeConstValue(self, node, ConstValue::String(text));
5078 5121
            let byteTy = allocType(self, Type::U8);
5079 -
            let sliceTy = allocType(self, Type::Slice(SliceType {
5080 -
                class: types::PointerClass::Owned,
5081 -
                item: byteTy,
5082 -
                mutable: false,
5083 -
            }));
5122 +
            let sliceTy = allocType(self, sliceType(types::PointerClass::Owned, byteTy, false));
5084 5123
            return setNodeType(self, node, *sliceTy);
5085 5124
        },
5086 5125
        case ast::NodeValue::Number(lit) => {
5087 5126
            setNodeConstValue(self, node, ConstValue::Int(ConstInt {
5088 5127
                magnitude: lit.magnitude,
5253 5292
        },
5254 5293
        case ast::NodeValue::AddressOf(addr) => {
5255 5294
            let ty = typeFor(self, node) else {
5256 5295
                return false;
5257 5296
            };
5258 -
            if let case Type::Slice(_) = ty {
5297 +
            if let case Type::Core(CoreType::Pointer { shape: PointerShape::Slice, .. }) = ty {
5259 5298
                return isConstExpr(self, addr.target);
5260 5299
            }
5261 5300
            return false;
5262 5301
        },
5263 5302
        case ast::NodeValue::RecordLit(lit) => {
6414 6453
            throw emitError(self, receiverType, ErrorKind::ReceiverMutabilityMismatch);
6415 6454
        }
6416 6455
6417 6456
        // Build the function type for the instance method.
6418 6457
        // The receiver becomes the first parameter.
6419 -
        let receiverPtrType = Type::Pointer(PointerType {
6420 -
            class: receiverClass,
6421 -
            target: allocType(self, concreteType),
6422 -
            mutable: receiverMut,
6423 -
        });
6458 +
        let receiverPtrType = pointerType(receiverClass, allocType(self, concreteType), receiverMut);
6424 6459
6425 6460
        // Validate that the instance method's signature matches the
6426 6461
        // trait method's signature exactly (params, return type, throws).
6427 6462
        if sig.params.len <> expectedFn.paramTypes.len {
6428 6463
            throw emitError(self, methodNode, ErrorKind::FnArgCountMismatch(CountMismatch {
6615 6650
    attrs: ?ast::Attributes,
6616 6651
) throws (ResolveError) {
6617 6652
    // Resolve the receiver type: must be `*Type` or `*mut Type` pointing to a
6618 6653
    // nominal type.
6619 6654
    let fullReceiverTy = try infer(self, receiverType);
6620 -
    let case Type::Pointer(receiver) = fullReceiverTy
6621 -
        else throw emitError(self, receiverType, ErrorKind::TraitReceiverMismatch);
6622 -
    let concreteType = *receiver.target;
6655 +
    let case Type::Core(CoreType::Pointer { shape: PointerShape::Thin, properties: receiver, target }) = fullReceiverTy else {
6656 +
        throw emitError(self, receiverType, ErrorKind::TraitReceiverMismatch);
6657 +
    };
6658 +
    let concreteType = *target;
6623 6659
    let case Type::Nominal(nominalTy) = concreteType
6624 6660
        else throw emitError(self, receiverType, ErrorKind::ExpectedRecord);
6625 6661
    try ensureNominalResolved(self, nominalTy, receiverType);
6626 6662
6627 6663
    let methodName = try nodeName(self, name);
6635 6671
    // Resolve parameter types.
6636 6672
    let a = alloc::arenaAllocator(&mut self.arena);
6637 6673
    let mut paramTypes = vec::Vec⟨*Type⟩ { data: &mut [], len: 0 };
6638 6674
6639 6675
    // Receiver is the first parameter.
6640 -
    let receiverPtrType = Type::Pointer(PointerType {
6641 -
        class: receiver.class,
6642 -
        target: allocType(self, concreteType),
6643 -
        mutable: receiver.mutable,
6644 -
    });
6676 +
    let receiverPtrType = pointerType(
6677 +
        receiver.class, allocType(self, concreteType), receiver.mutable
6678 +
    );
6645 6679
    vec::append⟨*Type⟩(
6646 6680
        &mut paramTypes, allocType(self, receiverPtrType), a
6647 6681
    );
6648 6682
6649 6683
    for paramNode in sig.params {
6957 6991
    pattern: *ast::Node,
6958 6992
    scrutineeTy: Type,
6959 6993
    mode: IdentMode,
6960 6994
    matchBy: MatchBy
6961 6995
) throws (ResolveError) {
6962 -
    if let case Type::Pointer(pointer) = scrutineeTy; isDestructuringPattern(pattern) {
6963 -
        try resolveCasePattern(self, pattern, *pointer.target, mode, matchBy);
6996 +
    if let case Type::Core(CoreType::Pointer { shape: PointerShape::Thin, target, .. }) = scrutineeTy; isDestructuringPattern(pattern) {
6997 +
        try resolveCasePattern(self, pattern, *target, mode, matchBy);
6964 6998
        return;
6965 6999
    }
6966 7000
    // TODO: Collapse these nested matches.
6967 7001
    match scrutineeTy {
6968 7002
        case Type::Nominal(info) => {
6981 7015
                        } else => {}
6982 7016
                    }
6983 7017
                } else => {}
6984 7018
            }
6985 7019
        }
6986 -
        case Type::Array(arrayInfo) => {
7020 +
        case Type::Core(CoreType::Array { item, length }) => {
6987 7021
            if let case ast::NodeValue::ArrayLit(items) = pattern.value {
6988 -
                let length = concreteArrayLength(arrayInfo.length)
6989 -
                    else throw emitError(self, pattern, ErrorKind::ConstExprRequired);
6990 -
                if items.len as u32 <> length {
6991 -
                    throw emitError(self, pattern, ErrorKind::RecordFieldCountMismatch(
6992 -
                        CountMismatch { expected: length, actual: items.len as u32 }
6993 -
                    ));
7022 +
                let lengthValue = concreteArrayLength(length)
7023 +
                    else throw emitError(
7024 +
                        self, pattern, ErrorKind::ConstExprRequired
7025 +
                    );
7026 +
                if items.len as u32 <> lengthValue {
7027 +
                    throw emitError(
7028 +
                        self,
7029 +
                        pattern,
7030 +
                        ErrorKind::RecordFieldCountMismatch(CountMismatch {
7031 +
                            expected: lengthValue,
7032 +
                            actual: items.len as u32,
7033 +
                        }),
7034 +
                    );
6994 7035
                }
6995 -
                let elemTy = *arrayInfo.item;
6996 -
                for item in items {
6997 -
                    try resolveCasePattern(self, item, elemTy, IdentMode::Bind, matchBy);
7036 +
                let elemTy = *item;
7037 +
                for child in items {
7038 +
                    try resolveCasePattern(
7039 +
                        self, child, elemTy, IdentMode::Bind, matchBy
7040 +
                    );
6998 7041
                }
6999 7042
                setNodeType(self, pattern, scrutineeTy);
7000 7043
                return;
7001 7044
            }
7002 7045
        } else => {}
7063 7106
        }
7064 7107
    }
7065 7108
    // Extract item type and store pre-computed loop metadata for the lowerer.
7066 7109
    let mut itemTy: Type = undefined;
7067 7110
    match iterableTy {
7068 -
        case Type::Slice(slice) => {
7069 -
            set itemTy = *slice.item;
7111 +
        case Type::Core(CoreType::Pointer { shape: PointerShape::Slice, target, .. }) => {
7112 +
            set itemTy = *target;
7070 7113
            setForLoopInfo(self, node, ForLoopInfo::Collection {
7071 -
                elemType: slice.item, length: nil, bindingName, indexName
7114 +
                elemType: target,
7115 +
                length: nil,
7116 +
                bindingName,
7117 +
                indexName,
7072 7118
            });
7073 7119
        }
7074 7120
        case Type::Range { start, .. } => {
7075 7121
            // Iterable ranges must have a start, and since we enforce type
7076 7122
            // equality for start and end, that is always the item type.
7084 7130
7085 7131
            setForLoopInfo(self, node, ForLoopInfo::Range {
7086 7132
                valType, range, bindingName, indexName
7087 7133
            });
7088 7134
        }
7089 -
        case Type::Array(arrayInfo) => {
7090 -
            set itemTy = *arrayInfo.item;
7135 +
        case Type::Core(CoreType::Array { item, length }) => {
7136 +
            set itemTy = *item;
7091 7137
            setForLoopInfo(self, node, ForLoopInfo::Collection {
7092 -
                elemType: arrayInfo.item,
7093 -
                length: arrayInfo.length,
7138 +
                elemType: item,
7139 +
                length,
7094 7140
                bindingName,
7095 7141
                indexName,
7096 7142
            });
7097 7143
        }
7098 7144
        else => throw emitError(self, forStmt.iterable, ErrorKind::ExpectedIterable),
7283 7329
    throws (ResolveError)
7284 7330
{
7285 7331
    let subjectTy = try infer(self, sw.subject);
7286 7332
    let subject = unwrapMatchSubject(subjectTy);
7287 7333
7288 -
    if let case Type::Optional(inner) = subject.effectiveTy {
7289 -
        try resolveMatchOptional(self, node, sw, inner, subject.by);
7290 -
    } else if let case Type::Nominal(NominalType::Union(u)) = subject.effectiveTy {
7334 +
    if let case Type::Core(CoreType::Optional { payload }) =
7335 +
        subject.effectiveTy
7336 +
    {
7337 +
        try resolveMatchOptional(self, node, sw, payload, subject.by);
7338 +
    } else if let case Type::Nominal(NominalType::Union(u)) =
7339 +
        subject.effectiveTy
7340 +
    {
7291 7341
        try resolveMatchUnion(self, node, sw, subject.effectiveTy, u, subject.by);
7292 7342
    } else {
7293 7343
        try resolveMatchGeneric(self, node, sw, subject.effectiveTy);
7294 7344
    }
7295 7345
7313 7363
    sw: ast::Match,
7314 7364
    innerTy: *Type,
7315 7365
    matchBy: MatchBy
7316 7366
) -> Type throws (ResolveError)
7317 7367
{
7318 -
    let subjectTy = Type::Optional(innerTy);
7368 +
    let subjectTy = optionalType(innerTy);
7319 7369
    let prongs = sw.prongs;
7320 7370
    let mut hasValue = false;
7321 7371
    let mut hasNil = false;
7322 7372
    let mut catchAll = false;
7323 7373
    let mut matchType = Type::Never;
7533 7583
) -> Type throws (ResolveError) {
7534 7584
    match prong.arm {
7535 7585
        case ast::ProngArm::Binding(pat) => {
7536 7586
            // For optionals, bind the unwrapped inner type.
7537 7587
            let mut bindTy = subjectTy;
7538 -
            if let case Type::Optional(inner) = subjectTy {
7539 -
                set bindTy = *inner;
7588 +
            if let case Type::Core(CoreType::Optional { payload }) = subjectTy {
7589 +
                set bindTy = *payload;
7540 7590
            }
7541 7591
            try bindPatternVar(self, pat, bindTy, matchBy);
7542 7592
        }
7543 7593
        case ast::ProngArm::Case(patterns) => {
7544 7594
            for pattern in patterns {
7629 7679
    throws (ResolveError)
7630 7680
{
7631 7681
    let mut bindTy = ty;
7632 7682
    match matchBy {
7633 7683
        case MatchBy::Value => {}
7634 -
        case MatchBy::Ref => set bindTy = Type::Pointer(PointerType {
7635 -
            class: types::PointerClass::Ref,
7636 -
            target: allocType(self, ty),
7637 -
            mutable: false,
7638 -
        }),
7639 -
        case MatchBy::MutRef => set bindTy = Type::Pointer(PointerType {
7640 -
            class: types::PointerClass::Ref,
7641 -
            target: allocType(self, ty),
7642 -
            mutable: true,
7643 -
        }),
7684 +
        case MatchBy::Ref => set bindTy = pointerType(types::PointerClass::Ref, allocType(self, ty), false),
7685 +
        case MatchBy::MutRef => set bindTy = pointerType(types::PointerClass::Ref, allocType(self, ty), true),
7644 7686
    }
7645 7687
    match binding.value {
7646 7688
        case ast::NodeValue::Placeholder => {
7647 7689
            // Nothing to do.
7648 7690
        }
7800 7842
    let exprTy = try infer(self, pat.scrutinee);
7801 7843
7802 7844
    match pat.kind {
7803 7845
        case ast::PatternKind::Binding => {
7804 7846
            // Simple binding requires an optional expression.
7805 -
            let case Type::Optional(inner) = exprTy else {
7806 -
                throw emitError(self, pat.scrutinee, ErrorKind::ExpectedOptional);
7847 +
            let case Type::Core(CoreType::Optional { payload }) = exprTy else {
7848 +
                throw emitError(
7849 +
                    self, pat.scrutinee, ErrorKind::ExpectedOptional
7850 +
                );
7807 7851
            };
7808 -
            let payloadTy = *inner;
7852 +
            let payloadTy = *payload;
7809 7853
            let _ = try bindValueIdent(self, pat.pattern, node, payloadTy, pat.mutable, 0, 0);
7810 7854
            // The `else` branch supplies the binding when the optional is nil.
7811 7855
            try checkAssignable(self, letElse.elseBranch, payloadTy);
7812 7856
7813 7857
            return setNodeType(self, node, Type::Void);
7853 7897
                expected: 2,
7854 7898
                actual: args.len as u32,
7855 7899
            }));
7856 7900
        }
7857 7901
        let ptrType = try visit(self, args[0], Type::Unknown);
7858 -
        let case Type::Pointer(ptr) = ptrType else {
7902 +
        let case Type::Core(CoreType::Pointer { shape: PointerShape::Thin, properties, target }) = ptrType else {
7859 7903
            throw emitError(self, node, ErrorKind::ExpectedPointer);
7860 7904
        };
7861 7905
        let _ = try checkAssignable(self, args[1], Type::U32);
7862 -
        return setNodeType(self, node, Type::Slice(SliceType {
7863 -
            class: ptr.class,
7864 -
            item: ptr.target,
7865 -
            mutable: ptr.mutable,
7866 -
        }));
7906 +
        return setNodeType(
7907 +
            self,
7908 +
            node,
7909 +
            sliceType(properties.class, target, properties.mutable),
7910 +
        );
7867 7911
    }
7868 7912
    if kind == ast::Builtin::Relocate {
7869 7913
        if args.len <> 2 {
7870 7914
            throw emitError(self, node, ErrorKind::BuiltinArgCountMismatch(CountMismatch {
7871 7915
                expected: 2,
7872 7916
                actual: args.len as u32,
7873 7917
            }));
7874 7918
        }
7875 7919
        let destinationType = try visit(self, args[0], Type::Unknown);
7876 -
        let case Type::Slice(destination) = destinationType else {
7920 +
        let case Type::Core(CoreType::Pointer { shape: PointerShape::Slice, properties: destination, target: item }) = destinationType else {
7877 7921
            throw emitError(self, args[0], ErrorKind::ExpectedIndexable);
7878 7922
        };
7879 -
        if not destination.mutable or not typesEqual(*destination.item, Type::U8) {
7923 +
        if hasLayoutObstacle(*item, LayoutObstacle::Opaque) {
7924 +
            throw emitError(self, args[0], ErrorKind::OpaqueTypeNotAllowed);
7925 +
        }
7926 +
        if not destination.mutable {
7880 7927
            throw emitTypeMismatch(self, args[0], TypeMismatch {
7881 -
                expected: Type::Slice(SliceType {
7882 -
                    class: destination.class,
7883 -
                    item: allocType(self, Type::U8),
7884 -
                    mutable: true,
7885 -
                }),
7928 +
                expected: sliceType(destination.class, item, true),
7886 7929
                actual: destinationType,
7887 7930
            });
7888 7931
        }
7889 7932
        let sourceType = try visit(self, args[1], Type::Unknown);
7890 -
        let case Type::Slice(source) = sourceType else {
7933 +
        let case Type::Core(CoreType::Pointer { shape: PointerShape::Slice, properties: source, target: sourceItem }) = sourceType else {
7891 7934
            throw emitError(self, args[1], ErrorKind::ExpectedIndexable);
7892 7935
        };
7893 -
        if not source.mutable or not typesEqual(*source.item, Type::U8) {
7936 +
        if not source.mutable or not typesEqual(*sourceItem, *item) {
7894 7937
            throw emitTypeMismatch(self, args[1], TypeMismatch {
7895 -
                expected: Type::Slice(SliceType {
7896 -
                    class: source.class,
7897 -
                    item: allocType(self, Type::U8),
7898 -
                    mutable: true,
7899 -
                }),
7938 +
                expected: sliceType(source.class, item, true),
7900 7939
                actual: sourceType,
7901 7940
            });
7902 7941
        }
7903 7942
        return setNodeType(self, node, Type::Void);
7904 7943
    }
8012 8051
    }
8013 8052
    if not containsGenericParameter(pattern) {
8014 8053
        return true;
8015 8054
    }
8016 8055
    match pattern {
8017 -
        case Type::Pointer(pointer) => {
8018 -
            let case Type::Pointer(actualPointer) = actual else return false;
8019 -
            return pointer.class == actualPointer.class
8020 -
                and pointer.mutable == actualPointer.mutable
8021 -
                and inferGenericArgument(
8022 -
                    self,
8023 -
                    *pointer.target,
8024 -
                    *actualPointer.target,
8025 -
                    params,
8026 -
                    inferred,
8027 -
                );
8028 -
        }
8029 -
        case Type::Slice(slice) => {
8030 -
            let case Type::Slice(actualSlice) = actual else return false;
8031 -
            return slice.class == actualSlice.class
8032 -
                and slice.mutable == actualSlice.mutable
8033 -
                and inferGenericArgument(
8056 +
        case Type::Core(core) => {
8057 +
            let case Type::Core(actualCore) = actual else return false;
8058 +
            let placeholder = coreValueArgument(core);
8059 +
            if not coreTypesEqual(
8060 +
                coreWithArguments(core, placeholder, placeholder),
8061 +
                coreWithArguments(actualCore, placeholder, placeholder),
8062 +
            ) { return false; }
8063 +
            if let patternConstant = coreConstantArgument(core) {
8064 +
                let actualConstant = coreConstantArgument(actualCore)
8065 +
                    else return false;
8066 +
                if not inferGenericArgument(
8034 8067
                    self,
8035 -
                    *slice.item,
8036 -
                    *actualSlice.item,
8068 +
                    *patternConstant,
8069 +
                    *actualConstant,
8037 8070
                    params,
8038 8071
                    inferred,
8039 -
                );
8040 -
        }
8041 -
        case Type::Optional(inner) => {
8042 -
            let case Type::Optional(actualInner) = actual else return false;
8043 -
            return inferGenericArgument(
8044 -
                self, *inner, *actualInner, params, inferred
8045 -
            );
8046 -
        }
8047 -
        case Type::Array(array) => {
8048 -
            let case Type::Array(actualArray) = actual else return false;
8072 +
                ) {
8073 +
                    return false;
8074 +
                }
8075 +
            }
8049 8076
            return inferGenericArgument(
8050 -
                self, *array.length, *actualArray.length, params, inferred
8051 -
            ) and inferGenericArgument(
8052 -
                self, *array.item, *actualArray.item, params, inferred
8077 +
                self,
8078 +
                *coreValueArgument(core),
8079 +
                *coreValueArgument(actualCore),
8080 +
                params,
8081 +
                inferred,
8053 8082
            );
8054 8083
        }
8055 8084
        case Type::GenericDataApply(application) => {
8056 8085
            let case Type::Nominal(nominal) = actual else return false;
8057 8086
            let concrete = genericDataSpecializationForNominal(self, nominal)
8171 8200
        return nil;
8172 8201
    }
8173 8202
    let traitSym = try resolveNamePath(self, access.parent);
8174 8203
    let case SymbolData::Trait(traitInfo) = traitSym.data else return nil;
8175 8204
    let receiverTy = try infer(self, call.args[0]);
8176 -
    let case Type::Pointer(receiver) = receiverTy else return nil;
8177 -
    let case Type::Parameter(param) = *receiver.target else return nil;
8205 +
    let case Type::Core(CoreType::Pointer { shape: PointerShape::Thin, properties: receiver, target }) = receiverTy else return nil;
8206 +
    let case Type::Parameter(param) = *target else return nil;
8178 8207
    let mut hasBound = false;
8179 8208
    for bound in param.bounds {
8180 8209
        if bound == traitInfo {
8181 8210
            set hasBound = true;
8182 8211
            break;
8292 8321
            );
8293 8322
            let case Type::Fn(info) = calleeTy
8294 8323
                else throw emitError(self, call.callee, ErrorKind::Internal);
8295 8324
            if selected.method.mutable {
8296 8325
                let mut isMutPtr = false;
8297 -
                if let case Type::Pointer(pointer) = parentTy {
8298 -
                    set isMutPtr = pointer.mutable;
8326 +
                if let properties = pointerProperties(parentTy) {
8327 +
                    set isMutPtr = properties.mutable;
8299 8328
                }
8300 -
                if not isMutPtr and not (try canBorrowMutFrom(self, access.parent)) {
8329 +
                if not isMutPtr
8330 +
                    and not (try canBorrowMutFrom(self, access.parent))
8331 +
                {
8301 8332
                    throw emitError(self, access.parent, ErrorKind::ImmutableBinding);
8302 8333
                }
8303 8334
            }
8304 8335
            try checkUnsafeCall(self, call.callee, info);
8305 8336
            try checkCallArgs(self, node, call, info, ctx);
8317 8348
        if let case Type::TraitObject(traitObject) = subjectTy {
8318 8349
            let methodName = try nodeName(self, access.child);
8319 8350
            let method = findTraitMethod(traitObject.traitInfo, methodName)
8320 8351
                else throw emitError(self, access.child, ErrorKind::RecordFieldUnknown(methodName));
8321 8352
            // Reject mutable-receiver methods called on immutable trait objects.
8322 -
            if method.mutable and not traitObject.mutable {
8323 -
                throw emitError(self, access.parent, ErrorKind::ImmutableBinding);
8353 +
            if method.mutable and not traitObject.properties.mutable {
8354 +
                throw emitError(
8355 +
                    self, access.parent, ErrorKind::ImmutableBinding
8356 +
                );
8324 8357
            }
8325 8358
            try checkCallArgs(self, node, call, method.fnType, ctx);
8326 8359
8327 8360
            setTraitMethodCall(self, node, traitObject.traitInfo, method.index);
8328 8361
            return setNodeType(self, node, *method.fnType.returnType);
8335 8368
                // Reject mutable-receiver methods on immutable bindings.
8336 8369
                // If the parent is already a mutable pointer, the receiver is fine.
8337 8370
                // Otherwise, check that the parent can yield a mutable borrow.
8338 8371
                if method.mutable {
8339 8372
                    let mut isMutPtr = false;
8340 -
                    if let case Type::Pointer(pointer) = parentTy {
8341 -
                        set isMutPtr = pointer.mutable;
8373 +
                    if let properties = pointerProperties(parentTy) {
8374 +
                        set isMutPtr = properties.mutable;
8342 8375
                    }
8343 -
                    if not isMutPtr and not (try canBorrowMutFrom(self, access.parent)) {
8376 +
                    if not isMutPtr
8377 +
                        and not (try canBorrowMutFrom(self, access.parent))
8378 +
                    {
8344 8379
                        throw emitError(self, access.parent, ErrorKind::ImmutableBinding);
8345 8380
                    }
8346 8381
                }
8347 8382
                // Check arguments (excluding receiver).
8348 8383
                try checkCallArgs(self, node, call, method.fnType, ctx);
8382 8417
            try checkSliceRangeIndices(self, range);
8383 8418
8384 8419
            let mut item: *Type = undefined;
8385 8420
            let mut capacity: ?*Type = nil;
8386 8421
8387 -
            if let case Type::Slice(slice) = subjectTy {
8388 -
                if not slice.mutable {
8389 -
                    throw emitError(self, container, ErrorKind::ImmutableBinding);
8422 +
            let case Type::Core(core) = subjectTy
8423 +
                else throw emitError(
8424 +
                    self, container, ErrorKind::ExpectedIndexable
8425 +
                );
8426 +
            match core {
8427 +
                case CoreType::Pointer { shape: PointerShape::Slice, properties, target } => {
8428 +
                    if not properties.mutable {
8429 +
                        throw emitError(
8430 +
                            self, container, ErrorKind::ImmutableBinding
8431 +
                        );
8432 +
                    }
8433 +
                    set item = target;
8390 8434
                }
8391 -
                set item = slice.item;
8392 -
            } else {
8393 -
                match subjectTy {
8394 -
                    case Type::Array(a) => {
8395 -
                        if let length = concreteArrayLength(a.length) {
8396 -
                            try validateArraySliceBounds(self, range, length, node);
8397 -
                        }
8398 -
                        set item = a.item;
8399 -
                        set capacity = a.length;
8435 +
                case CoreType::Array { item: arrayItem, length } => {
8436 +
                    if let value = concreteArrayLength(length) {
8437 +
                        try validateArraySliceBounds(
8438 +
                            self, range, value, node
8439 +
                        );
8400 8440
                    }
8401 -
                    else => throw emitError(self, container, ErrorKind::ExpectedIndexable),
8441 +
                    set item = arrayItem;
8442 +
                    set capacity = length;
8402 8443
                }
8444 +
                else => throw emitError(
8445 +
                    self, container, ErrorKind::ExpectedIndexable
8446 +
                ),
8403 8447
            }
8404 8448
            // RHS is either a fill value or a source slice.
8405 8449
            let rhsTy = try infer(self, assign.right);
8406 -
            if let case Type::Slice(source) = rhsTy {
8407 -
                if *source.item <> *item {
8450 +
            if let case Type::Core(CoreType::Pointer { shape: PointerShape::Slice, target, .. }) = rhsTy {
8451 +
                if not typesEqual(*target, *item) {
8408 8452
                    throw emitTypeMismatch(
8409 8453
                        self,
8410 8454
                        assign.right,
8411 -
                        TypeMismatch { expected: *item, actual: *source.item },
8455 +
                        TypeMismatch { expected: *item, actual: *target },
8412 8456
                    );
8413 8457
                }
8414 8458
            } else {
8415 8459
                try checkAssignable(self, assign.right, *item);
8416 8460
            }
8417 -
            setSliceRangeInfo(self, node, SliceRangeInfo { itemType: item, mutable: true, capacity });
8461 +
            setSliceRangeInfo(self, node, SliceRangeInfo {
8462 +
                itemType: item,
8463 +
                mutable: true,
8464 +
                capacity,
8465 +
            });
8418 8466
            setNodeType(self, assign.left, *item);
8419 8467
8420 8468
            return setNodeType(self, node, Type::Void);
8421 8469
        }
8422 8470
    }
8508 8556
    if isUnsafePointerType(containerTy) {
8509 8557
        try requireUnsafe(self, container);
8510 8558
    }
8511 8559
    try checkIndex(self, indexNode);
8512 8560
    let subjectTy = autoDeref(containerTy);
8513 -
    if let case Type::Slice(slice) = subjectTy {
8514 -
        return setNodeType(self, node, *slice.item);
8515 -
    }
8516 -
8517 -
    match subjectTy {
8518 -
        case Type::Array(arrayInfo) => {
8519 -
            return setNodeType(self, node, *arrayInfo.item);
8520 -
        }
8521 -
        else => {
8522 -
            throw emitError(self, container, ErrorKind::ExpectedIndexable);
8561 +
    if let case Type::Core(core) = subjectTy {
8562 +
        match core {
8563 +
            case CoreType::Pointer { shape: PointerShape::Slice, target, .. } =>
8564 +
                return setNodeType(self, node, *target),
8565 +
            case CoreType::Array { item, .. } =>
8566 +
                return setNodeType(self, node, *item),
8567 +
            else => {}
8523 8568
        }
8524 8569
    }
8570 +
    throw emitError(self, container, ErrorKind::ExpectedIndexable);
8525 8571
}
8526 8572
8527 8573
/// Find a record field by name.
8528 8574
fn findRecordField(s: *RecordType, fieldName: *[u8]) -> ?u32 {
8529 8575
    for field, i in s.fields {
8689 8735
fn resolveAnonRecordLit(self: *mut Resolver, node: *ast::Node, lit: ast::RecordLit, hint: Type) -> Type
8690 8736
    throws (ResolveError)
8691 8737
{
8692 8738
    // Unwrap optional hint to get the inner record type.
8693 8739
    let mut innerHint = hint;
8694 -
    if let case Type::Optional(inner) = hint {
8695 -
        set innerHint = *inner;
8740 +
    if let case Type::Core(CoreType::Optional { payload }) = hint {
8741 +
        set innerHint = *payload;
8696 8742
    }
8697 8743
    let mut hintInfo: ?RecordType = nil;
8698 8744
    if let case Type::Nominal(info) = innerHint {
8699 8745
        try ensureNominalResolved(self, info, node);
8700 8746
        if let case NominalType::Record(s) = *info {
8741 8787
        setNodeType(self, fieldNode, fieldType);
8742 8788
    }
8743 8789
    return setNodeType(self, node, innerHint);
8744 8790
}
8745 8791
8792 +
/// Return an array element type supplied directly or through an optional hint.
8793 +
fn arrayItemFromHint(hint: Type) -> ?Type {
8794 +
    let mut candidate = hint;
8795 +
    if let case Type::Core(CoreType::Optional { payload }) = hint {
8796 +
        set candidate = *payload;
8797 +
    }
8798 +
    if let case Type::Core(CoreType::Array { item, .. }) = candidate {
8799 +
        return *item;
8800 +
    }
8801 +
    return nil;
8802 +
}
8803 +
8746 8804
/// Analyze an array literal expression.
8747 8805
fn resolveArrayLit(self: *mut Resolver, node: *ast::Node, items: *mut [*ast::Node], hint: Type) -> Type
8748 8806
    throws (ResolveError)
8749 8807
{
8750 8808
    let length = items.len;
8751 8809
    let mut expectedTy: Type = Type::Unknown;
8752 8810
8753 -
    if let case Type::Array(ary) = hint {
8754 -
        set expectedTy = *ary.item;
8755 -
    } else if let case Type::Optional(inner) = hint {
8756 -
        if let case Type::Array(ary) = *inner {
8757 -
            set expectedTy = *ary.item;
8758 -
        }
8759 -
    };
8811 +
    if let item = arrayItemFromHint(hint) {
8812 +
        set expectedTy = item;
8813 +
    }
8760 8814
    for itemNode in items {
8761 8815
        let itemTy = try visit(self, itemNode, expectedTy);
8762 8816
        assert itemTy <> Type::Unknown;
8763 8817
8764 8818
        // Set the expected type to the first type we encounter.
8769 8823
        }
8770 8824
    }
8771 8825
    if expectedTy == Type::Unknown {
8772 8826
        throw emitError(self, node, ErrorKind::CannotInferType);
8773 8827
    };
8774 -
    let arrayTy = Type::Array(ArrayType {
8775 -
        item: allocType(self, expectedTy),
8776 -
        length: allocConcreteArrayLength(self, length),
8777 -
    });
8828 +
    let arrayTy = arrayType(allocType(self, expectedTy), allocConcreteArrayLength(self, length));
8778 8829
    return setNodeType(self, node, arrayTy);
8779 8830
}
8780 8831
8781 8832
/// Analyze an array repeat literal expression.
8782 8833
fn resolveArrayRepeat(self: *mut Resolver, node: *ast::Node, lit: ast::ArrayRepeatLit, hint: Type) -> Type
8783 8834
    throws (ResolveError)
8784 8835
{
8785 8836
    let mut itemHint = hint;
8786 -
    if let case Type::Array(ary) = hint {
8787 -
        set itemHint = *ary.item;
8788 -
    } else if let case Type::Optional(inner) = hint {
8789 -
        if let case Type::Array(ary) = *inner {
8790 -
            set itemHint = *ary.item;
8791 -
        }
8837 +
    if let item = arrayItemFromHint(hint) {
8838 +
        set itemHint = item;
8792 8839
    }
8793 8840
    let valueTy = try visit(self, lit.item, itemHint);
8794 8841
    let _ = try checkNumeric(self, lit.count);
8795 8842
    let mut length: *Type = undefined;
8796 8843
    if let value = constValueEntry(self, lit.count) {
8805 8852
    {
8806 8853
        set length = allocSymbolicArrayLength(self, lit.count);
8807 8854
    } else {
8808 8855
        throw emitError(self, lit.count, ErrorKind::ConstExprRequired);
8809 8856
    }
8810 -
    return setNodeType(self, node, Type::Array(ArrayType {
8811 -
        item: allocType(self, valueTy),
8857 +
    return setNodeType(self, node, arrayType(
8858 +
        allocType(self, valueTy),
8812 8859
        length,
8813 -
    }));
8860 +
    ));
8814 8861
}
8815 8862
8816 8863
/// Resolve union variant access.
8817 8864
fn resolveUnionVariantAccess(
8818 8865
    self: *mut Resolver,
8936 8983
    let parentTy = try infer(self, access.parent);
8937 8984
    if isUnsafePointerType(parentTy) {
8938 8985
        try requireUnsafe(self, access.parent);
8939 8986
    }
8940 8987
    let subjectTy = autoDeref(parentTy);
8941 -
    if let case Type::Slice(slice) = subjectTy {
8988 +
    if let case Type::Core(CoreType::Pointer { shape: PointerShape::Slice, properties, target }) = subjectTy {
8942 8989
        let fieldNode = access.child;
8943 8990
        let fieldName = try nodeName(self, fieldNode);
8944 8991
        if mem::eq(fieldName, PTR_FIELD) {
8945 8992
            setRecordFieldIndex(self, fieldNode, 0);
8946 8993
            return setNodeType(
8947 8994
                self,
8948 8995
                node,
8949 -
                Type::Pointer(PointerType {
8950 -
                    class: slice.class,
8951 -
                    target: slice.item,
8952 -
                    mutable: slice.mutable,
8953 -
                }),
8996 +
                pointerType(properties.class, target, properties.mutable),
8954 8997
            );
8955 8998
        }
8956 8999
        if mem::eq(fieldName, LEN_FIELD) {
8957 9000
            setRecordFieldIndex(self, fieldNode, 1);
8958 9001
            return setNodeType(self, node, Type::U32);
9022 9065
            if let method = findMethod(self, subjectTy, fieldName) {
9023 9066
                return setNodeType(self, node, Type::Fn(method.fnType));
9024 9067
            }
9025 9068
            throw emitError(self, node, ErrorKind::RecordFieldUnknown(fieldName));
9026 9069
        }
9027 -
        case Type::Array(arrayInfo) => {
9070 +
        case Type::Core(CoreType::Array { length, .. }) => {
9028 9071
            let fieldNode = access.child;
9029 9072
            let fieldName = try nodeName(self, fieldNode);
9030 9073
9031 9074
            if mem::eq(fieldName, LEN_FIELD) {
9032 -
                if let length = concreteArrayLength(arrayInfo.length) {
9075 +
                if let value = concreteArrayLength(length) {
9033 9076
                    setNodeConstValue(
9034 -
                        self, node, constInt(length as u64, 32, false, false)
9077 +
                        self, node, constInt(value as u64, 32, false, false)
9035 9078
                    );
9036 9079
                }
9037 9080
                return setNodeType(self, node, Type::U32);
9038 9081
            }
9039 -
            throw emitError(self, node, ErrorKind::ArrayFieldUnknown(fieldName));
9082 +
            throw emitError(
9083 +
                self, node, ErrorKind::ArrayFieldUnknown(fieldName)
9084 +
            );
9040 9085
        }
9041 9086
        else => {
9042 9087
            // Check for standalone methods on any nominal type (e.g. unions).
9043 9088
            if let case Type::Nominal(_) = subjectTy {
9044 9089
                let fieldName = try nodeName(self, access.child);
9065 9110
            if mutable {
9066 9111
                return true;
9067 9112
            }
9068 9113
            // Check if the type is a mutable pointer or slice.
9069 9114
            let ty = typeFor(self, node) else return false;
9070 -
            if let case Type::Pointer(pointer) = ty {
9071 -
                return pointer.mutable;
9072 -
            }
9073 -
            if let case Type::Slice(slice) = ty {
9074 -
                return slice.mutable;
9075 -
            }
9076 -
            return false;
9115 +
            let properties = pointerProperties(ty) else return false;
9116 +
            return properties.mutable;
9077 9117
        }
9078 9118
        case ast::NodeValue::FieldAccess(access) => {
9079 9119
            let _ = try infer(self, access.parent);
9080 9120
            return try canBorrowMutFrom(self, access.parent);
9081 9121
        }
9094 9134
        case ast::NodeValue::Subscript { container, .. } => {
9095 9135
            let containerTy = try infer(self, container);
9096 9136
            // Subscript auto-derefs pointers, so check the actual indexed type.
9097 9137
            let subjectTy = autoDeref(containerTy);
9098 9138
9099 -
            if let case Type::Slice(slice) = subjectTy {
9100 -
                return slice.mutable;
9101 -
            }
9102 -
            if let case Type::Array(_) = subjectTy {
9103 -
                return try canBorrowMutFrom(self, container);
9139 +
            if let case Type::Core(core) = subjectTy {
9140 +
                match core {
9141 +
                    case CoreType::Pointer { shape: PointerShape::Slice, properties, .. } =>
9142 +
                        return properties.mutable,
9143 +
                    case CoreType::Array { .. } =>
9144 +
                        return try canBorrowMutFrom(self, container),
9145 +
                    else => {}
9146 +
                }
9104 9147
            }
9105 9148
            return false;
9106 9149
        }
9107 9150
        case ast::NodeValue::ArrayLit(_),
9108 9151
             ast::NodeValue::ArrayRepeatLit(_) =>
9111 9154
        }
9112 9155
        case ast::NodeValue::Call(_) => {
9113 9156
            // A call returning `*mut T` (or `&mut [T]`) yields a
9114 9157
            // mutable place. Non-pointer returns cannot be mutably borrowed.
9115 9158
            let ty = try infer(self, node);
9116 -
            if let case Type::Pointer(pointer) = ty {
9117 -
                return pointer.mutable;
9118 -
            }
9119 -
            if let case Type::Slice(slice) = ty {
9120 -
                return slice.mutable;
9121 -
            }
9122 -
            return false;
9159 +
            let properties = pointerProperties(ty) else return false;
9160 +
            return properties.mutable;
9123 9161
        }
9124 9162
        case ast::NodeValue::Deref(inner) => {
9125 9163
            let innerTy = try infer(self, inner);
9126 9164
9127 -
            if let case Type::Pointer(pointer) = innerTy {
9128 -
                return pointer.mutable;
9129 -
            }
9130 -
            if let case Type::Slice(slice) = innerTy {
9131 -
                return slice.mutable;
9165 +
            if let properties = pointerProperties(innerTy) {
9166 +
                return properties.mutable;
9132 9167
            }
9133 9168
            // Record deref: mutability depends on the inner binding.
9134 9169
            if let case Type::Nominal(NominalType::Record(recInfo)) = innerTy {
9135 9170
                if not recInfo.labeled and recInfo.fields.len == 1 {
9136 9171
                    return try canBorrowMutFrom(self, inner);
9165 9200
            try checkSliceRangeIndices(self, range);
9166 9201
9167 9202
            let mut item: *Type = undefined;
9168 9203
            let mut capacity: ?*Type = nil;
9169 9204
9170 -
            if let case Type::Slice(slice) = subjectTy {
9171 -
                if addr.mutable and not slice.mutable {
9172 -
                    throw emitError(self, addr.target, ErrorKind::ImmutableBinding);
9173 -
                }
9174 -
                set item = slice.item;
9175 -
            } else {
9176 -
                match subjectTy {
9177 -
                    case Type::Array(arrayInfo) => {
9178 -
                        if let length = concreteArrayLength(arrayInfo.length) {
9179 -
                            try validateArraySliceBounds(self, range, length, node);
9180 -
                        }
9181 -
                        set item = arrayInfo.item;
9182 -
                        set capacity = arrayInfo.length;
9205 +
            let case Type::Core(core) = subjectTy
9206 +
                else throw emitError(
9207 +
                    self, container, ErrorKind::ExpectedIndexable
9208 +
                );
9209 +
            match core {
9210 +
                case CoreType::Pointer { shape: PointerShape::Slice, properties, target } => {
9211 +
                    if addr.mutable and not properties.mutable {
9212 +
                        throw emitError(
9213 +
                            self, addr.target, ErrorKind::ImmutableBinding
9214 +
                        );
9183 9215
                    }
9184 -
                    else => {
9185 -
                        throw emitError(self, container, ErrorKind::ExpectedIndexable);
9216 +
                    set item = target;
9217 +
                }
9218 +
                case CoreType::Array { item: arrayItem, length } => {
9219 +
                    if let value = concreteArrayLength(length) {
9220 +
                        try validateArraySliceBounds(
9221 +
                            self, range, value, node
9222 +
                        );
9186 9223
                    }
9224 +
                    set item = arrayItem;
9225 +
                    set capacity = length;
9187 9226
                }
9227 +
                else => throw emitError(
9228 +
                    self, container, ErrorKind::ExpectedIndexable
9229 +
                ),
9188 9230
            }
9189 -
            let sliceTy = Type::Slice(SliceType {
9190 -
                class,
9191 -
                item,
9192 -
                mutable: addr.mutable,
9193 -
            });
9231 +
            let sliceTy = sliceType(class, item, addr.mutable);
9194 9232
            let alloc = allocType(self, sliceTy);
9195 9233
            setSliceRangeInfo(self, node, SliceRangeInfo {
9196 9234
                itemType: item,
9197 9235
                mutable: addr.mutable,
9198 9236
                capacity,
9201 9239
            return setNodeType(self, node, *alloc);
9202 9240
        }
9203 9241
    }
9204 9242
    // Derive a hint for the target type from the slice hint.
9205 9243
    let mut targetHint: Type = Type::Unknown;
9206 -
    if let case Type::Slice(slice) = hint {
9207 -
        set targetHint = Type::Array(ArrayType {
9208 -
            item: slice.item,
9209 -
            length: allocConcreteArrayLength(self, 0),
9210 -
        });
9244 +
    if let case Type::Core(CoreType::Pointer { shape: PointerShape::Slice, target, .. }) = hint {
9245 +
        set targetHint = arrayType(
9246 +
            target, allocConcreteArrayLength(self, 0)
9247 +
        );
9211 9248
    }
9212 9249
    let targetTy = try visit(self, addr.target, targetHint);
9213 9250
9214 9251
    // Mark local variable symbols as address-taken so the lowerer
9215 9252
    // allocates a stack slot eagerly.
9222 9259
                else => {}
9223 9260
            }
9224 9261
        }
9225 9262
    }
9226 9263
9227 -
    if let case Type::Array(arrayInfo) = targetTy {
9264 +
    if let case Type::Core(CoreType::Array { item, .. }) = targetTy {
9228 9265
        match addr.target.value {
9229 9266
            case ast::NodeValue::ArrayLit(_),
9230 9267
                 ast::NodeValue::ArrayRepeatLit(_) =>
9231 9268
            {
9232 -
                let sliceTy = Type::Slice(SliceType {
9233 -
                    class,
9234 -
                    item: arrayInfo.item,
9235 -
                    mutable: addr.mutable,
9236 -
                });
9269 +
                let sliceTy = sliceType(class, item, addr.mutable);
9237 9270
                return setNodeType(self, node, *allocType(self, sliceTy));
9238 9271
            }
9239 9272
            else => {}
9240 9273
        }
9241 9274
    }
9242 -
    let pointerTy = Type::Pointer(PointerType {
9275 +
    let pointerTy = pointerType(
9243 9276
        class,
9244 -
        target: allocType(self, targetTy),
9245 -
        mutable: addr.mutable,
9246 -
    });
9277 +
        allocType(self, targetTy),
9278 +
        addr.mutable,
9279 +
    );
9247 9280
    return setNodeType(self, node, pointerTy);
9248 9281
}
9249 9282
9250 9283
/// Analyze a dereference expression.
9251 9284
fn resolveDeref(self: *mut Resolver, node: *ast::Node, targetNode: *ast::Node, hint: Type) -> Type
9252 9285
    throws (ResolveError)
9253 9286
{
9254 9287
    let operandTy = try visit(self, targetNode, hint);
9255 -
    if let case Type::Pointer(pointer) = operandTy {
9256 -
        if pointer.class == types::PointerClass::Unsafe {
9288 +
    if let case Type::Core(CoreType::Pointer { shape: PointerShape::Thin, properties, target }) = operandTy {
9289 +
        if properties.class == types::PointerClass::Unsafe {
9257 9290
            try requireUnsafe(self, targetNode);
9258 9291
        }
9259 9292
        // Disallow dereferencing opaque pointers.
9260 -
        if *pointer.target == Type::Opaque {
9261 -
            throw emitError(self, targetNode, ErrorKind::OpaqueTypeDeref);
9293 +
        if *target == Type::Opaque {
9294 +
            throw emitError(self, node, ErrorKind::OpaqueTypeDeref);
9262 9295
        }
9263 -
        return setNodeType(self, node, *pointer.target);
9296 +
        return setNodeType(self, node, *target);
9264 9297
    }
9265 9298
    // Auto-deref for single-field unlabeled records.
9266 9299
    if let case Type::Nominal(NominalType::Record(recInfo)) = operandTy {
9267 9300
        if not recInfo.labeled and recInfo.fields.len == 1 {
9268 9301
            let fieldTy = recInfo.fields[0].fieldType;
9271 9304
        }
9272 9305
    }
9273 9306
    throw emitError(self, targetNode, ErrorKind::ExpectedPointer);
9274 9307
}
9275 9308
9276 -
/// Check if a type is a pointer to opaque.
9277 -
fn isOpaquePointer(ty: Type) -> bool {
9278 -
    if let case Type::Pointer(pointer) = ty {
9279 -
        return *pointer.target == Type::Opaque;
9280 -
    }
9281 -
    return false;
9282 -
}
9283 -
9284 -
/// Check if a type is an opaque slice.
9285 -
fn isOpaqueSlice(ty: Type) -> bool {
9286 -
    if let case Type::Slice(slice) = ty {
9287 -
        return *slice.item == Type::Opaque;
9288 -
    }
9289 -
    return false;
9290 -
}
9291 -
9292 -
/// Check if an `as` cast between two types is valid.
9293 -
fn isValidCast(source: Type, target: Type) -> bool {
9309 +
/// Check if an `as` cast is valid.
9310 +
fn isValidCast(source: Type, target: Type, allowRefToUnsafe: bool) -> bool {
9294 9311
    // Allow identity casts.
9295 -
    if source == target {
9312 +
    if typesEqual(source, target) {
9296 9313
        return true;
9297 9314
    }
9298 9315
    // Allow numeric to numeric.
9299 9316
    if isNumericType(source) and isNumericType(target) {
9300 9317
        return true;
9302 9319
    // Allow `void` union to numeric.
9303 9320
    // TODO: Check that variant index fits in target type.
9304 9321
    if isVoidUnion(source) and isNumericType(target) {
9305 9322
        return true;
9306 9323
    }
9307 -
    // Allow address to numeric.
9308 -
    if let case Type::Slice(_) = source {
9309 -
        // Disallow slice to numeric; slices are fat pointers.
9310 -
    } else if isAddressType(source) and isNumericType(target) {
9324 +
    // Thin pointers and function addresses can be cast to numeric values.
9325 +
    if isAddressType(source) and isNumericType(target) {
9326 +
        if let case Type::Core(CoreType::Pointer { shape: PointerShape::Slice, .. }) = source {
9327 +
            return false;
9328 +
        }
9311 9329
        return true;
9312 9330
    }
9313 -
    // Allow pointer casts if one side is `*opaque` or target types are castable.
9314 -
    if let case Type::Pointer(sourcePointer) = source {
9315 -
        if let case Type::Pointer(targetPointer) = target {
9316 -
            if sourcePointer.class <> targetPointer.class {
9317 -
                return false;
9318 -
            }
9319 -
            if targetPointer.mutable and not sourcePointer.mutable {
9320 -
                return false;
9321 -
            }
9322 -
            if isOpaquePointer(source) or isOpaquePointer(target) {
9323 -
                return true;
9324 -
            }
9325 -
            return isValidCast(*sourcePointer.target, *targetPointer.target);
9326 -
        }
9331 +
    let case Type::Core(CoreType::Pointer { shape: sourceShape, properties: sourceProperties, target: sourceTarget }) = source else return false;
9332 +
    let case Type::Core(CoreType::Pointer { shape: targetShape, properties: targetProperties, target: targetTarget }) = target else return false;
9333 +
    let classCompatible = sourceProperties.class == targetProperties.class
9334 +
        or (allowRefToUnsafe
9335 +
            and sourceProperties.class == types::PointerClass::Ref
9336 +
            and targetProperties.class == types::PointerClass::Unsafe);
9337 +
    if sourceShape <> targetShape
9338 +
        or not classCompatible
9339 +
        or (targetProperties.mutable and not sourceProperties.mutable)
9340 +
    {
9341 +
        return false;
9327 9342
    }
9328 -
    // Allow slice casts if one side is `*[opaque]`, target is `*[u8]`,
9329 -
    // or element types are castable.
9330 -
    if let case Type::Slice(sourceSlice) = source {
9331 -
        if let case Type::Slice(targetSlice) = target {
9332 -
            if sourceSlice.class <> targetSlice.class {
9333 -
                return false;
9334 -
            }
9335 -
            if targetSlice.mutable and not sourceSlice.mutable {
9336 -
                return false;
9337 -
            }
9338 -
            if isOpaqueSlice(source) or isOpaqueSlice(target) {
9339 -
                return true;
9340 -
            }
9341 -
            if *targetSlice.item == Type::U8 {
9342 -
                return true;
9343 -
            }
9344 -
            return isValidCast(*sourceSlice.item, *targetSlice.item);
9345 -
        }
9343 +
    if *sourceTarget == Type::Opaque or *targetTarget == Type::Opaque {
9344 +
        return true;
9345 +
    }
9346 +
    match sourceShape {
9347 +
        // Slices additionally allow casts to a byte target.
9348 +
        case PointerShape::Slice if *targetTarget == Type::U8 => return true,
9349 +
        else => return isValidCast(*sourceTarget, *targetTarget, false),
9346 9350
    }
9347 -
    return false;
9348 9351
}
9349 9352
9350 9353
/// Analyze an `as` cast expression.
9351 9354
fn resolveAs(self: *mut Resolver, node: *ast::Node, expr: ast::As) -> Type
9352 9355
    throws (ResolveError)
9358 9361
    }
9359 9362
9360 9363
    assert sourceTy <> Type::Unknown;
9361 9364
    assert targetTy <> Type::Unknown;
9362 9365
9363 -
    let mut valid = isValidCast(sourceTy, targetTy);
9364 -
    if let case Type::Pointer(sourcePointer) = sourceTy {
9365 -
        if let case Type::Pointer(targetPointer) = targetTy {
9366 -
            if sourcePointer.class == types::PointerClass::Ref and
9367 -
               targetPointer.class == types::PointerClass::Unsafe and
9368 -
               (not targetPointer.mutable or sourcePointer.mutable) and
9369 -
               isValidCast(*sourcePointer.target, *targetPointer.target)
9370 -
            {
9371 -
                set valid = true;
9372 -
            }
9373 -
        }
9374 -
    }
9375 -
    if let case Type::Slice(sourceSlice) = sourceTy {
9376 -
        if let case Type::Slice(targetSlice) = targetTy {
9377 -
            if sourceSlice.class == types::PointerClass::Ref and
9378 -
               targetSlice.class == types::PointerClass::Unsafe and
9379 -
               (not targetSlice.mutable or sourceSlice.mutable) and
9380 -
               isValidCast(*sourceSlice.item, *targetSlice.item)
9381 -
            {
9382 -
                set valid = true;
9383 -
            }
9384 -
        }
9385 -
    }
9366 +
    let valid = isValidCast(sourceTy, targetTy, true);
9386 9367
    if valid {
9387 9368
        // Propagate the constant value after applying the cast's target-width
9388 9369
        // truncation and signed interpretation.
9389 9370
        if let value = constValueEntry(self, expr.value) {
9390 9371
            if let case ConstValue::Int(i) = value {
9460 9441
    // If we're not catching the error, nor panicking on error, nor returning
9461 9442
    // optional, then the current function must be able to propagate it.
9462 9443
    let mut tryResultTy = resultTy;
9463 9444
    if tryExpr.returnsOptional {
9464 9445
        // `try?` converts errors to `nil` and wraps the result in an optional.
9465 -
        if let case Type::Optional(_) = resultTy {
9466 -
            // Already optional, no wrapping needed.
9467 -
        } else {
9468 -
            set tryResultTy = Type::Optional(allocType(self, resultTy));
9446 +
        if not isOptionalType(resultTy) {
9447 +
            set tryResultTy = optionalType(allocType(self, resultTy));
9469 9448
        }
9470 9449
    } else if tryExpr.catches.len > 0 {
9471 9450
        // `try ... catch` -- one or more catch clauses.
9472 9451
        set tryResultTy = try resolveTryCatches(self, node, tryExpr.catches, calleeInfo, resultTy, hint);
9473 9452
    } else if not tryExpr.shouldPanic {
9884 9863
            // We use the already-optional type from the other side rather than
9885 9864
            // constructing a new optional, so that e.g. `?u8 == 42` coerces
9886 9865
            // `42` to `?u8` (not `?i32`). We also record OptionalLift directly
9887 9866
            // rather than using expectAssignable, because comparisons should
9888 9867
            // allow e.g. `?*mut T == *T` where mutability differs.
9889 -
            if let case Type::Optional(_) = leftTy {
9868 +
            if isOptionalType(leftTy) {
9890 9869
                if not isOptionalType(rightTy) {
9891 9870
                    setNodeCoercion(self, binop.right, Coercion::OptionalLift(leftTy));
9892 9871
                }
9893 -
            } else if let case Type::Optional(_) = rightTy {
9872 +
            } else if isOptionalType(rightTy) {
9894 9873
                setNodeCoercion(self, binop.left, Coercion::OptionalLift(rightTy));
9895 9874
            }
9896 9875
            set resultTy = Type::Bool;
9897 9876
        },
9898 9877
        else => {
9901 9880
                let leftTy = try infer(self, binop.left);
9902 9881
                let rightTy = try visit(self, binop.right, leftTy);
9903 9882
9904 9883
                // Allow arithmetic on owning pointers and unsafe pointers, but
9905 9884
                // never on references.
9906 -
                if let case Type::Pointer(leftPointer) = leftTy {
9907 -
                    if *leftPointer.target == Type::Opaque {
9908 -
                        throw emitError(self, node, ErrorKind::OpaquePointerArithmetic);
9885 +
                if let case Type::Core(CoreType::Pointer { shape: PointerShape::Thin, properties, target }) = leftTy {
9886 +
                    if *target == Type::Opaque {
9887 +
                        throw emitError(
9888 +
                            self, node, ErrorKind::OpaquePointerArithmetic
9889 +
                        );
9909 9890
                    }
9910 -
                    if leftPointer.class <> types::PointerClass::Ref
9891 +
                    if properties.class <> types::PointerClass::Ref
9911 9892
                        and isNumericType(rightTy)
9912 9893
                    {
9913 -
                        if leftPointer.class == types::PointerClass::Unsafe {
9894 +
                        if properties.class == types::PointerClass::Unsafe {
9914 9895
                            try requireUnsafe(self, node);
9915 9896
                        }
9916 9897
                        return setNodeType(self, node, leftTy);
9917 9898
                    }
9918 9899
                }
9919 -
                if let case Type::Pointer(rightPointer) = rightTy {
9920 -
                    if *rightPointer.target == Type::Opaque {
9921 -
                        throw emitError(self, node, ErrorKind::OpaquePointerArithmetic);
9900 +
                if let case Type::Core(CoreType::Pointer { shape: PointerShape::Thin, properties, target }) = rightTy {
9901 +
                    if *target == Type::Opaque {
9902 +
                        throw emitError(
9903 +
                            self, node, ErrorKind::OpaquePointerArithmetic
9904 +
                        );
9922 9905
                    }
9923 9906
                    if binop.op == ast::BinaryOp::Add
9924 -
                        and rightPointer.class <> types::PointerClass::Ref
9907 +
                        and properties.class <> types::PointerClass::Ref
9925 9908
                        and isNumericType(leftTy)
9926 9909
                    {
9927 -
                        if rightPointer.class == types::PointerClass::Unsafe {
9910 +
                        if properties.class == types::PointerClass::Unsafe {
9928 9911
                            try requireUnsafe(self, node);
9929 9912
                        }
9930 9913
                        return setNodeType(self, node, rightTy);
9931 9914
                    }
9932 9915
                }
10069 10052
                            self, length, ErrorKind::ConstExprRequired
10070 10053
                        );
10071 10054
                    }
10072 10055
                }
10073 10056
            }
10074 -
            set ty = Type::Array(ArrayType {
10075 -
                item: allocType(self, item),
10076 -
                length: lengthType,
10077 -
            });
10057 +
            set ty = arrayType(allocType(self, item), lengthType);
10078 10058
        }
10079 10059
        case ast::TypeSig::Slice { class, itemType, mutable } => {
10080 10060
            let item = try resolveTypeSyntax(self, itemType, context);
10081 -
            set ty = Type::Slice(SliceType {
10082 -
                class,
10083 -
                item: allocType(self, item),
10084 -
                mutable,
10085 -
            });
10061 +
            set ty = sliceType(class, allocType(self, item), mutable);
10086 10062
        }
10087 10063
        case ast::TypeSig::Pointer { class, valueType, mutable } => {
10088 10064
            let target = try resolveTypeSyntax(self, valueType, context);
10089 -
            set ty = Type::Pointer(PointerType {
10090 -
                class,
10091 -
                target: allocType(self, target),
10092 -
                mutable,
10093 -
            });
10065 +
            set ty = pointerType(class, allocType(self, target), mutable);
10094 10066
        }
10095 10067
        case ast::TypeSig::Optional { valueType } => {
10096 10068
            let payload = try resolveTypeSyntax(self, valueType, context);
10097 -
            set ty = Type::Optional(allocType(self, payload));
10069 +
            set ty = optionalType(allocType(self, payload));
10098 10070
        }
10099 10071
        case ast::TypeSig::Nominal(name) => {
10100 10072
            if let case TypeSyntaxContext::Symbolic = context {
10101 10073
                if let case ast::NodeValue::GenericApply(app) = name.value {
10102 10074
                    let templateSym = try resolveGenericDataTarget(self, app.target);
10301 10273
            if not traitInfo.objectSafe {
10302 10274
                throw emitError(self, traitName, ErrorKind::TraitNotObjectSafe);
10303 10275
            }
10304 10276
            setNodeSymbol(self, traitName, sym);
10305 10277
            set ty = Type::TraitObject(TraitObjectType {
10306 -
                class,
10278 +
                properties: PointerProperties { class, mutable },
10307 10279
                traitInfo,
10308 -
                mutable,
10309 10280
            });
10310 10281
        }
10311 10282
    }
10312 10283
    return setNodeType(self, node, ty);
10313 10284
}
10314 10285
10315 -
/// Check if a type can be used for inferrence.
10286 +
/// Check if a type can be used for inference.
10316 10287
fn isTypeInferrable(type: Type) -> bool {
10317 -
    if let case Type::Pointer(pointer) = type {
10318 -
        return isTypeInferrable(*pointer.target);
10319 -
    }
10320 10288
    match type {
10321 10289
        case Type::Unknown, Type::Nil, Type::Undefined, Type::Int => return false,
10322 -
        case Type::Array(ary) => return isTypeInferrable(*ary.item),
10323 -
        case Type::Optional(opt) => return isTypeInferrable(*opt),
10290 +
        case Type::Core(core) =>
10291 +
            return isTypeInferrable(*coreValueArgument(core)),
10324 10292
        else => return true,
10325 10293
    }
10326 10294
}
10327 10295
10328 10296
/// Analyze a standalone expression by wrapping it in a synthetic function.
10934 10902
10935 10903
    for arg, i in call.args {
10936 10904
        let expected = *info.paramTypes[i];
10937 10905
        let root = linearRootSymbol(checker.resolver, arg);
10938 10906
        let mut argExclusive = isLinear(expected);
10939 -
        if let case Type::Pointer(PointerType { class: types::PointerClass::Ref, mutable, .. }) = expected {
10940 -
            set argExclusive = mutable;
10941 -
        } else if let case Type::Slice(SliceType {
10942 -
            class: types::PointerClass::Ref, mutable, ..
10943 -
        }) = expected {
10944 -
            set argExclusive = mutable;
10945 -
        } else if let case Type::TraitObject(TraitObjectType {
10946 -
            class: types::PointerClass::Ref, mutable, ..
10947 -
        }) = expected {
10948 -
            set argExclusive = mutable;
10907 +
        if let properties = pointerProperties(expected);
10908 +
            properties.class == types::PointerClass::Ref
10909 +
        {
10910 +
            set argExclusive = properties.mutable;
10949 10911
        }
10950 10912
        if not isUnsafePointerType(expected) {
10951 10913
            if let rootSym = root {
10952 10914
                for j in 0..rootsLen {
10953 10915
                    if let previous = roots[j] {
lib/std/lang/resolver/printer.rad +32 -24
102 102
            io::print("i32");
103 103
        }
104 104
        case super::Type::I64 => {
105 105
            io::print("i64");
106 106
        }
107 -
        case super::Type::Pointer(pointer) => {
108 -
            printPtrPrefix(pointer.class, pointer.mutable);
109 -
            printTypeBody(*pointer.target, brief);
110 -
        }
111 -
        case super::Type::Slice(slice) => {
112 -
            printPtrPrefix(slice.class, slice.mutable);
113 -
            io::print("[");
114 -
            printTypeBody(*slice.item, brief);
115 -
            io::print("]");
116 -
        }
117 -
        case super::Type::Array(array) => {
118 -
            io::print("[");
119 -
            printTypeBody(*array.item, brief);
120 -
            io::print("; ");
121 -
            if let length = super::concreteArrayLength(array.length) {
122 -
                io::printU32(length);
123 -
            } else {
124 -
                printTypeBody(*array.length, brief);
107 +
        case super::Type::Core(core) => {
108 +
            match core {
109 +
                case super::CoreType::Pointer { shape, properties, target } => {
110 +
                    printPtrPrefix(properties.class, properties.mutable);
111 +
                    match shape {
112 +
                        case super::PointerShape::Thin =>
113 +
                            printTypeBody(*target, brief),
114 +
                        case super::PointerShape::Slice => {
115 +
                            io::print("[");
116 +
                            printTypeBody(*target, brief);
117 +
                            io::print("]");
118 +
                        }
119 +
                    }
120 +
                }
121 +
                case super::CoreType::Array { item, length } => {
122 +
                    io::print("[");
123 +
                    printTypeBody(*item, brief);
124 +
                    io::print("; ");
125 +
                    if let value = super::concreteArrayLength(length) {
126 +
                        io::printU32(value);
127 +
                    } else {
128 +
                        printTypeBody(*length, brief);
129 +
                    }
130 +
                    io::print("]");
131 +
                }
132 +
                case super::CoreType::Optional { payload } => {
133 +
                    io::print("?");
134 +
                    printTypeBody(*payload, brief);
135 +
                }
125 136
            }
126 -
            io::print("]");
127 -
        }
128 -
        case super::Type::Optional(inner) => {
129 -
            io::print("?");
130 -
            printTypeBody(*inner, brief);
131 137
        }
132 138
        case super::Type::Fn(fnType) => {
133 139
            if fnType.isUnsafe {
134 140
                io::print("unsafe ");
135 141
            }
196 202
                printTypeBody(*arg, brief);
197 203
            }
198 204
            io::print("]");
199 205
        }
200 206
        case super::Type::TraitObject(object) => {
201 -
            printPtrPrefix(object.class, object.mutable);
207 +
            printPtrPrefix(
208 +
                object.properties.class, object.properties.mutable
209 +
            );
202 210
            io::print("opaque ");
203 211
            io::print(object.traitInfo.name);
204 212
        }
205 213
        case super::Type::Range { start, end } => {
206 214
            if let s = start {
lib/std/lang/resolver/tests.rad +85 -80
454 454
455 455
/// Require an array type and return its element type.
456 456
fn expectArrayType(ty: super::Type, length: u32) -> super::Type
457 457
    throws (testing::TestError)
458 458
{
459 -
    let case super::Type::Array(info) = ty
460 -
        else throw testing::TestError::Failed;
461 -
    let actualLength = super::concreteArrayLength(info.length)
459 +
    let case super::Type::Core(super::CoreType::Array {
460 +
        item, length: lengthArgument
461 +
    }) = ty else throw testing::TestError::Failed;
462 +
    let actualLength = super::concreteArrayLength(lengthArgument)
462 463
        else throw testing::TestError::Failed;
463 464
    try testing::expect(actualLength == length);
464 465
465 -
    return *info.item;
466 -
}
467 -
468 -
/// Require a slice type and return its element type.
469 -
fn expectSliceType(ty: super::Type, mutable: bool) -> super::Type
470 -
    throws (testing::TestError)
471 -
{
472 -
    let case super::Type::Slice(super::SliceType {
473 -
        class: types::PointerClass::Owned, item, mutable: sliceMut
474 -
    }) = ty
475 -
        else throw testing::TestError::Failed;
476 -
    try testing::expect(sliceMut == mutable);
477 -
478 466
    return *item;
479 467
}
480 468
481 -
/// Require a pointer type and return its target type.
482 -
fn expectPointerType(ty: super::Type, mutable: bool) -> super::Type
483 -
    throws (testing::TestError)
484 -
{
485 -
    let case super::Type::Pointer(super::PointerType {
486 -
        class: types::PointerClass::Owned, target, mutable: ptrMut
487 -
    }) = ty
469 +
/// Require an owned pointer-like type and return its value argument.
470 +
fn expectPointerType(
471 +
    ty: super::Type,
472 +
    shape: super::PointerShape,
473 +
    mutable: bool,
474 +
) -> super::Type throws (testing::TestError) {
475 +
    let case super::Type::Core(super::CoreType::Pointer { shape: actualShape, properties, target }) = ty
488 476
        else throw testing::TestError::Failed;
489 -
    try testing::expect(ptrMut == mutable);
490 -
477 +
    try testing::expect(actualShape == shape);
478 +
    try testing::expect(properties.class == types::PointerClass::Owned);
479 +
    try testing::expect(properties.mutable == mutable);
491 480
    return *target;
492 481
}
493 482
494 483
/// Verify that a node has a constant integer value with the expected magnitude.
495 484
fn expectConstInt(a: *super::Resolver, node: *ast::Node, expected: u32)
541 530
    let mut a = testResolver();
542 531
    let result = try resolveExprStr(&mut a, "\"hello\"");
543 532
544 533
    try expectNoErrors(&result);
545 534
    let ty = try typeOf(&a, result.root);
546 -
    let elemTy = try expectSliceType(ty, false);
535 +
    let elemTy = try expectPointerType(ty, super::PointerShape::Slice, false);
547 536
    try testing::expect(elemTy == super::Type::U8);
548 537
}
549 538
550 539
@test fn testResolveAsNumeric() throws (testing::TestError) {
551 540
    {
794 783
    let stmt = try getBlockStmt(result.root, 0);
795 784
    let case ast::NodeValue::Let(decl) = stmt.value
796 785
        else throw testing::TestError::Failed;
797 786
    let arrayTy = try typeOf(&a, decl.value);
798 787
    let elemTy = try expectArrayType(arrayTy, 2);
799 -
    let case super::Type::Optional(inner) = elemTy
788 +
    let case super::Type::Core(super::CoreType::Optional { payload }) = elemTy
800 789
        else throw testing::TestError::Failed;
801 -
    try testing::expect(*inner == super::Type::I32);
790 +
    try testing::expect(*payload == super::Type::I32);
802 791
}
803 792
804 793
@test fn testResolveArrayLiteralOptionalMismatch() throws (testing::TestError) {
805 794
    let mut a = testResolver();
806 795
    let result = try resolveProgramStr(&mut a, "let xs: [?bool; 2] = [1, 2];");
875 864
876 865
    let sliceStmt = try getBlockStmt(result.root, 1);
877 866
    let case ast::NodeValue::Let(sliceDecl) = sliceStmt.value
878 867
        else throw testing::TestError::Failed;
879 868
    let sliceTy = try typeOf(&a, sliceDecl.value);
880 -
    let elemTy = try expectSliceType(sliceTy, false);
869 +
    let elemTy = try expectPointerType(sliceTy, super::PointerShape::Slice, false);
881 870
    try testing::expect(elemTy == super::Type::I32);
882 871
883 872
    let indexStmt = try getBlockStmt(result.root, 2);
884 873
    try expectExprStmtType(&a, indexStmt, super::Type::I32);
885 874
}
898 887
899 888
    let ptrStmt = try getBlockStmt(result.root, 3);
900 889
    let case ast::NodeValue::ExprStmt(ptrExpr) = ptrStmt.value
901 890
        else throw testing::TestError::Failed;
902 891
    let ptrTy = try typeOf(&a, ptrExpr);
903 -
    let targetTy = try expectPointerType(ptrTy, false);
892 +
    let targetTy = try expectPointerType(ptrTy, super::PointerShape::Thin, false);
904 893
    try testing::expect(targetTy == super::Type::I32);
905 894
}
906 895
907 896
@test fn testResolveSliceLiteralImmutable() throws (testing::TestError) {
908 897
    let mut a = testResolver();
3695 3684
    let mut a = testResolver();
3696 3685
    let result = try resolveProgramStr(&mut a, "fn f(a: i32) { let o: *opaque = &a; let ptr: *i32 = o; }");
3697 3686
    let err = try expectError(&result);
3698 3687
    let case super::ErrorKind::TypeMismatch(mismatch) = err.kind
3699 3688
        else throw testing::TestError::Failed;
3700 -
    let case super::Type::Pointer(super::PointerType {
3701 -
        class: types::PointerClass::Owned, target: expectedTarget, ..
3702 -
    }) = mismatch.expected
3703 -
        else throw testing::TestError::Failed;
3704 -
    let case super::Type::Pointer(super::PointerType {
3705 -
        class: types::PointerClass::Owned, target: actualTarget, ..
3706 -
    }) = mismatch.actual
3707 -
        else throw testing::TestError::Failed;
3689 +
    let case super::Type::Core(super::CoreType::Pointer {
3690 +
        shape: super::PointerShape::Thin, properties: expectedProperties, target: expectedTarget
3691 +
    }) = mismatch.expected else throw testing::TestError::Failed;
3692 +
    let case super::Type::Core(super::CoreType::Pointer {
3693 +
        shape: super::PointerShape::Thin, properties: actualProperties, target: actualTarget
3694 +
    }) = mismatch.actual else throw testing::TestError::Failed;
3695 +
    assert expectedProperties.class == types::PointerClass::Owned;
3696 +
    assert actualProperties.class == types::PointerClass::Owned;
3708 3697
3709 3698
    try testing::expect(*expectedTarget == super::Type::I32);
3710 3699
    try testing::expect(*actualTarget == super::Type::Opaque);
3711 3700
}
3712 3701
3822 3811
3823 3812
    // Verify the array constant has the correct type with length 3.
3824 3813
    let arrStmt = try getBlockStmt(result.root, 1);
3825 3814
    let sym = super::symbolFor(&a, arrStmt)
3826 3815
        else throw testing::TestError::Failed;
3827 -
    let case super::SymbolData::Constant { type: super::Type::Array(arrType), .. } = sym.data
3828 -
        else throw testing::TestError::Failed;
3829 -
    let actualLength = super::concreteArrayLength(arrType.length)
3816 +
    let case super::SymbolData::Constant { type: arrayType, .. } = sym.data
3830 3817
        else throw testing::TestError::Failed;
3831 -
    try testing::expect(actualLength == 3);
3818 +
    let _ = try expectArrayType(arrayType, 3);
3832 3819
}
3833 3820
3834 3821
/// Test that a record field can use a constant as its array length.
3835 3822
@test fn testRecordFieldWithConstArrayLength() throws (testing::TestError) {
3836 3823
    let mut a = testResolver();
4274 4261
        let case super::ErrorKind::TypeMismatch(_) = err.kind
4275 4262
            else throw testing::TestError::Failed;
4276 4263
    }
4277 4264
}
4278 4265
4266 +
/// Test `@relocate` accepts mutable slices with a matching non-byte item type.
4267 +
@test fn testResolveRelocateTypedSlices() throws (testing::TestError) {
4268 +
    let mut a = testResolver();
4269 +
    let program = "fn f(destination: *mut [i32], source: *mut [i32]) { @relocate(destination, source); }";
4270 +
    let result = try resolveProgramStr(&mut a, program);
4271 +
    try expectNoErrors(&result);
4272 +
}
4273 +
4274 +
/// Test `@relocate` rejects mutable slices with different item types.
4275 +
@test fn testResolveRelocateItemMismatch() throws (testing::TestError) {
4276 +
    let mut a = testResolver();
4277 +
    let program = "fn f(destination: *mut [i32], source: *mut [u32]) { @relocate(destination, source); }";
4278 +
    let result = try resolveProgramStr(&mut a, program);
4279 +
    let err = try expectError(&result);
4280 +
    let case super::ErrorKind::TypeMismatch(_) = err.kind
4281 +
        else throw testing::TestError::Failed;
4282 +
}
4283 +
4284 +
/// Test `@relocate` rejects slices whose item size is unknown.
4285 +
@test fn testResolveRelocateOpaqueItems() throws (testing::TestError) {
4286 +
    {
4287 +
        let mut a = testResolver();
4288 +
        let program = "fn f(destination: *mut [opaque], source: *mut [opaque]) { @relocate(destination, source); }";
4289 +
        let result = try resolveProgramStr(&mut a, program);
4290 +
        let _ = try expectErrorKind(&result, super::ErrorKind::OpaqueTypeNotAllowed);
4291 +
    } {
4292 +
        let mut a = testResolver();
4293 +
        let program = "fn f(destination: *mut [[opaque; 2]], source: *mut [[opaque; 2]]) { @relocate(destination, source); }";
4294 +
        let result = try resolveProgramStr(&mut a, program);
4295 +
        let _ = try expectErrorKind(&result, super::ErrorKind::OpaqueTypeNotAllowed);
4296 +
    }
4297 +
}
4298 +
4279 4299
/// Test `match &opt` produces immutable pointer bindings.
4280 4300
@test fn testResolveMatchRefUnionBinding() throws (testing::TestError) {
4281 4301
    let mut a = testResolver();
4282 4302
    let program = "union Opt { Some(i32), None } fn f() { let opt = Opt::Some(42); match &opt { case Opt::Some(x) => { *x; } else => {} } }";
4283 4303
    let result = try resolveProgramStr(&mut a, program);
4293 4313
        else throw testing::TestError::Failed;
4294 4314
    let payloadSym = super::findSymbolInScope(scope, "x")
4295 4315
        else throw testing::TestError::Failed;
4296 4316
    let case super::SymbolData::Value { type: payloadValType, .. } = payloadSym.data
4297 4317
        else throw testing::TestError::Failed;
4298 -
    let case super::Type::Pointer(super::PointerType {
4299 -
        class: types::PointerClass::Ref, target, mutable
4300 -
    }) = payloadValType
4318 +
    let case super::Type::Core(super::CoreType::Pointer { shape: super::PointerShape::Thin, properties, target }) = payloadValType
4301 4319
        else throw testing::TestError::Failed;
4302 -
    assert not mutable;
4320 +
    assert properties.class == types::PointerClass::Ref;
4321 +
    assert not properties.mutable;
4303 4322
    assert *target == super::Type::I32;
4304 4323
}
4305 4324
4306 4325
/// Test `match &mut opt` produces mutable pointer bindings.
4307 4326
@test fn testResolveMatchMutRefUnionBinding() throws (testing::TestError) {
4320 4339
        else throw testing::TestError::Failed;
4321 4340
    let payloadSym = super::findSymbolInScope(scope, "x")
4322 4341
        else throw testing::TestError::Failed;
4323 4342
    let case super::SymbolData::Value { type: payloadValType, .. } = payloadSym.data
4324 4343
        else throw testing::TestError::Failed;
4325 -
    let case super::Type::Pointer(super::PointerType {
4326 -
        class: types::PointerClass::Ref, target, mutable
4327 -
    }) = payloadValType
4344 +
    let case super::Type::Core(super::CoreType::Pointer { shape: super::PointerShape::Thin, properties, target }) = payloadValType
4328 4345
        else throw testing::TestError::Failed;
4329 -
    assert mutable;
4346 +
    assert properties.class == types::PointerClass::Ref;
4347 +
    assert properties.mutable;
4330 4348
    assert *target == super::Type::I32;
4331 4349
}
4332 4350
4333 4351
/// Non-constant integer widening must use an explicit cast.
4334 4352
@test fn testResolveIntegerWideningRequiresCast() throws (testing::TestError) {
4981 4999
    try expectNoErrors(&result);
4982 5000
4983 5001
    let arrStmt = try getBlockStmt(result.root, 3);
4984 5002
    let sym = super::symbolFor(&a, arrStmt)
4985 5003
        else throw testing::TestError::Failed;
4986 -
    let case super::SymbolData::Constant { type: super::Type::Array(arrType), .. } = sym.data
5004 +
    let case super::SymbolData::Constant { type: arrayType, .. } = sym.data
4987 5005
        else throw testing::TestError::Failed;
4988 -
    let actualLength = super::concreteArrayLength(arrType.length)
4989 -
        else throw testing::TestError::Failed;
4990 -
    try testing::expect(actualLength == 5);
5006 +
    let _ = try expectArrayType(arrayType, 5);
4991 5007
}
4992 5008
4993 5009
/// Test cross-module constant expression: a constant in one module references
4994 5010
/// a constant from another module via scope access.
4995 5011
@test fn testCrossModuleConstExpr() throws (testing::TestError) {
5066 5082
    try expectNoErrors(&result);
5067 5083
5068 5084
    let arrStmt = try getBlockStmt(result.root, 2);
5069 5085
    let sym = super::symbolFor(&a, arrStmt)
5070 5086
        else throw testing::TestError::Failed;
5071 -
    let case super::SymbolData::Constant { type: super::Type::Array(arrType), .. } = sym.data
5072 -
        else throw testing::TestError::Failed;
5073 -
    let actualLength = super::concreteArrayLength(arrType.length)
5087 +
    let case super::SymbolData::Constant { type: arrayType, .. } = sym.data
5074 5088
        else throw testing::TestError::Failed;
5075 -
    try testing::expect(actualLength == 4);
5089 +
    let _ = try expectArrayType(arrayType, 4);
5076 5090
}
5077 5091
5078 5092
/// Test unsuffixed integer literals in constant expressions.
5079 5093
@test fn testConstExprUnsuffixedLiterals() throws (testing::TestError) {
5080 5094
    try expectConstFold("constant A: u32 = 4 * 4;", 0, 16);
5495 5509
    let sym = super::symbolFor(&a, block.statements[0])
5496 5510
        else throw testing::TestError::Failed;
5497 5511
    let template = super::genericTemplateFor(&a, sym)
5498 5512
        else throw testing::TestError::Failed;
5499 5513
    let rigid = super::Type::Parameter(template.params[0]);
5500 -
    let pointer = super::Type::Pointer(super::PointerType {
5501 -
        class: types::PointerClass::Owned,
5502 -
        target: super::allocType(&mut a, rigid),
5503 -
        mutable: true,
5504 -
    });
5505 -
    let symbolic = super::Type::Optional(super::allocType(&mut a, pointer));
5514 +
    let pointer = super::pointerType( types::PointerClass::Owned, super::allocType(&mut a, rigid), true, );
5515 +
    let symbolic = super::optionalType( super::allocType(&mut a, pointer), );
5506 5516
    let concrete = super::allocType(&mut a, super::Type::U32);
5507 5517
    let args: [*super::Type; 1] = [concrete];
5508 5518
    let sub = super::Substitution { params: template.params, args: &args[..] };
5509 5519
    let replaced = try super::substituteType(
5510 5520
        &mut a, symbolic, &sub, block.statements[0]
5511 5521
    ) catch {
5512 5522
        throw testing::TestError::Failed;
5513 5523
    };
5514 -
    let case super::Type::Optional(inner) = replaced
5524 +
    let case super::Type::Core(super::CoreType::Optional { payload }) = replaced
5515 5525
        else throw testing::TestError::Failed;
5516 -
    let case super::Type::Pointer(super::PointerType {
5517 -
        class: types::PointerClass::Owned, target, mutable
5518 -
    }) = *inner
5526 +
    let case super::Type::Core(super::CoreType::Pointer { shape: super::PointerShape::Thin, properties, target }) = *payload
5519 5527
        else throw testing::TestError::Failed;
5520 -
    assert mutable;
5528 +
    assert properties.class == types::PointerClass::Owned;
5529 +
    assert properties.mutable;
5521 5530
    assert *target == super::Type::U32;
5522 5531
}
5523 5532
5524 5533
/// Duplicate generic parameter names are rejected in their declaration scope.
5525 5534
@test fn testDuplicateGenericParameterRejected() throws (testing::TestError) {
5539 5548
        "record Value {} fn invalid⟨T: Value⟩(value: T) {}",
5540 5549
    );
5541 5550
    try expectErrorKind(&result, super::ErrorKind::GenericBoundNotTrait);
5542 5551
}
5543 5552
5544 -
/// Integer constant parameters specialize array layouts.
5553 +
/// Non-u32 integer constant parameters specialize array layouts.
5545 5554
@test fn testGenericConstParameterArrayLayout() throws (testing::TestError) {
5546 5555
    let mut a = testResolver();
5547 5556
    let result = try resolveProgramStr(
5548 5557
        &mut a,
5549 -
        "record Buffer⟨constant N: u32⟩ { data: [u8; N] } instantiate Buffer⟨4⟩;",
5558 +
        "record Buffer⟨constant N: u64⟩ { data: [u8; N] } instantiate Buffer⟨4⟩;",
5550 5559
    );
5551 5560
    try expectNoErrors(&result);
5552 5561
    let case ast::NodeValue::Block(block) = result.root.value
5553 5562
        else throw testing::TestError::Failed;
5554 5563
    let case ast::NodeValue::Instantiate(applications) = block.statements[1].value
5557 5566
        else throw testing::TestError::Failed;
5558 5567
    let case super::Type::Nominal(nominal) = resolved
5559 5568
        else throw testing::TestError::Failed;
5560 5569
    let case super::NominalType::Record(recordType) = *nominal
5561 5570
        else throw testing::TestError::Failed;
5562 -
    let case super::Type::Array(arrayType) = recordType.fields[0].fieldType
5563 -
        else throw testing::TestError::Failed;
5564 -
    let actualLength = super::concreteArrayLength(arrayType.length)
5565 -
        else throw testing::TestError::Failed;
5566 -
    assert actualLength == 4;
5571 +
    let item = try expectArrayType(recordType.fields[0].fieldType, 4);
5572 +
    assert item == super::Type::U8;
5567 5573
    assert recordType.layout.size == 4;
5568 5574
}
5569 5575
5570 5576
/// Equivalent integer expressions share a canonical specialization.
5571 5577
@test fn testGenericConstParameterCanonical() throws (testing::TestError) {
5744 5750
        else throw testing::TestError::Failed;
5745 5751
    let case super::Type::Nominal(listType) = resolved
5746 5752
        else throw testing::TestError::Failed;
5747 5753
    let case super::NominalType::Record(recordType) = *listType
5748 5754
        else throw testing::TestError::Failed;
5749 -
    let case super::Type::Optional(optionalTarget) = recordType.fields[1].fieldType
5755 +
    let case super::Type::Core(super::CoreType::Optional { payload }) = recordType.fields[1].fieldType
5750 5756
        else throw testing::TestError::Failed;
5751 -
    let case super::Type::Pointer(super::PointerType {
5752 -
        class: types::PointerClass::Owned, target, ..
5753 -
    }) = *optionalTarget
5757 +
    let case super::Type::Core(super::CoreType::Pointer { shape: super::PointerShape::Thin, properties, target }) = *payload
5754 5758
        else throw testing::TestError::Failed;
5759 +
    assert properties.class == types::PointerClass::Owned;
5755 5760
    let case super::Type::Nominal(nextType) = *target
5756 5761
        else throw testing::TestError::Failed;
5757 5762
    assert nextType == listType;
5758 5763
}
5759 5764
test/tests/relocate.rad +12 -1
1 1
//! returns: 0
2 -
//! Test overlap-safe relocation between mutable byte slices.
2 +
//! Test overlap-safe relocation between mutable typed slices.
3 3
4 4
@default fn main() -> i32 {
5 5
    let mut bytes: [u8; 6] = [1, 2, 3, 4, 5, 6];
6 6
7 7
    let rightDestination: *mut [u8] = &mut bytes[1..5];
31 31
    @relocate(emptyDestination, emptySource);
32 32
33 33
    assert bytes[0] == 1;
34 34
    assert bytes[5] == 6;
35 35
36 +
    let mut words: [i32; 5] = [10, 20, 30, 40, 50];
37 +
    let wordDestination: *mut [i32] = &mut words[1..5];
38 +
    let wordSource: *mut [i32] = &mut words[0..4];
39 +
    @relocate(wordDestination, wordSource);
40 +
41 +
    assert words[0] == 10;
42 +
    assert words[1] == 10;
43 +
    assert words[2] == 20;
44 +
    assert words[3] == 30;
45 +
    assert words[4] == 40;
46 +
36 47
    return 0;
37 48
}