compiler: Construct slice range metadata in checked code
37360ae782d97c0bb80f93a5a31f46c5129b4f10335697294e6c1cea77424dc2
1 parent
b7d3a956
lib/std/lang/resolver.rad
+32 -44
| 1924 | 1924 | return index; |
|
| 1925 | 1925 | } |
|
| 1926 | 1926 | return nil; |
|
| 1927 | 1927 | } |
|
| 1928 | 1928 | ||
| 1929 | - | /// Get the slice range metadata for a subscript expression with a range index. |
|
| 1929 | + | /// Get the range metadata for a slice borrow or range assignment. |
|
| 1930 | 1930 | export fn sliceRangeInfoFor 'arena (self: &Resolver 'arena, node: *ast::Node) -> ?SliceRangeInfo { |
|
| 1931 | 1931 | if let case NodeExtra::SliceRange(info) = self.nodeData.entries[node.id].extra { |
|
| 1932 | 1932 | return info; |
|
| 1933 | 1933 | } |
|
| 1934 | 1934 | return nil; |
| 7745 | 7745 | throw emitError(self, container, ErrorKind::ImmutableBinding); |
|
| 7746 | 7746 | } |
|
| 7747 | 7747 | let subjectTy = autoDeref(containerTy); |
|
| 7748 | 7748 | try checkSliceRangeIndices(self, range); |
|
| 7749 | 7749 | ||
| 7750 | - | let mut item: *Type = undefined; |
|
| 7751 | - | let mut capacity: ?u32 = nil; |
|
| 7752 | - | ||
| 7753 | - | if let case Type::Slice { item: sliceItem, mutable: sliceMutable, .. } = subjectTy { |
|
| 7754 | - | if not sliceMutable { |
|
| 7755 | - | throw emitError(self, container, ErrorKind::ImmutableBinding); |
|
| 7756 | - | } |
|
| 7757 | - | set item = sliceItem; |
|
| 7758 | - | } else { |
|
| 7759 | - | match subjectTy { |
|
| 7760 | - | case Type::Array(a) => { |
|
| 7761 | - | try validateArraySliceBounds(self, range, a.length, node); |
|
| 7762 | - | set item = a.item; |
|
| 7763 | - | set capacity = a.length; |
|
| 7764 | - | } |
|
| 7765 | - | else => throw emitError(self, container, ErrorKind::ExpectedIndexable), |
|
| 7766 | - | } |
|
| 7750 | + | let info = sliceRangeInfo(subjectTy) |
|
| 7751 | + | else throw emitError(self, container, ErrorKind::ExpectedIndexable); |
|
| 7752 | + | if not info.mutable { |
|
| 7753 | + | throw emitError(self, container, ErrorKind::ImmutableBinding); |
|
| 7767 | 7754 | } |
|
| 7755 | + | if let capacity = info.capacity { |
|
| 7756 | + | try validateArraySliceBounds(self, range, capacity, node); |
|
| 7757 | + | } |
|
| 7758 | + | let item = info.itemType; |
|
| 7768 | 7759 | // RHS is either a fill value or a source slice. |
|
| 7769 | 7760 | let rhsTy = try infer(self, assign.right); |
|
| 7770 | 7761 | if let case Type::Slice { item: sourceItem, .. } = rhsTy { |
|
| 7771 | 7762 | if *sourceItem <> *item { |
|
| 7772 | 7763 | throw emitTypeMismatch( |
| 7777 | 7768 | } |
|
| 7778 | 7769 | } else { |
|
| 7779 | 7770 | try checkAssignable(self, assign.right, *item); |
|
| 7780 | 7771 | } |
|
| 7781 | 7772 | try validateRegionalStore(self, assign.left, assign.right, *item); |
|
| 7782 | - | setSliceRangeInfo(self, node, SliceRangeInfo { itemType: item, mutable: true, capacity }); |
|
| 7773 | + | setSliceRangeInfo(self, node, info); |
|
| 7783 | 7774 | setNodeType(self, assign.left, *item); |
|
| 7784 | 7775 | ||
| 7785 | 7776 | return setNodeType(self, node, Type::Void); |
|
| 7786 | 7777 | } |
|
| 7787 | 7778 | } |
| 7807 | 7798 | try validateRegionalStore(self, assign.left, assign.right, leftTy); |
|
| 7808 | 7799 | ||
| 7809 | 7800 | return setNodeType(self, node, leftTy); |
|
| 7810 | 7801 | } |
|
| 7811 | 7802 | ||
| 7803 | + | /// Construct complete range metadata for an array or slice type. |
|
| 7804 | + | /// Callers check array place access and select the resulting borrow access. |
|
| 7805 | + | fn sliceRangeInfo(ty: Type) -> ?SliceRangeInfo { |
|
| 7806 | + | match ty { |
|
| 7807 | + | case Type::Slice { item, mutable, .. } => |
|
| 7808 | + | return SliceRangeInfo { itemType: item, mutable, capacity: nil }, |
|
| 7809 | + | case Type::Array(array) => |
|
| 7810 | + | return SliceRangeInfo { itemType: array.item, mutable: true, capacity: array.length }, |
|
| 7811 | + | else => return nil, |
|
| 7812 | + | } |
|
| 7813 | + | } |
|
| 7814 | + | ||
| 7812 | 7815 | /// Ensure slice range bounds are valid `u32` values. |
|
| 7813 | 7816 | unsafe fn checkSliceRangeIndices 'arena (self: &mut Resolver 'arena, range: ast::Range) throws (ResolveError) { |
|
| 7814 | 7817 | if let start = range.start { |
|
| 7815 | 7818 | try checkIndex(self, start); |
|
| 7816 | 7819 | } |
| 8784 | 8787 | let containerTy = try infer(self, container); |
|
| 8785 | 8788 | let subjectTy = autoDeref(containerTy); |
|
| 8786 | 8789 | ||
| 8787 | 8790 | try checkSliceRangeIndices(self, range); |
|
| 8788 | 8791 | ||
| 8789 | - | let mut item: *Type = undefined; |
|
| 8790 | - | let mut capacity: ?u32 = nil; |
|
| 8791 | - | ||
| 8792 | - | if let case Type::Slice { item: sliceItem, mutable: sliceMutable, .. } = subjectTy { |
|
| 8793 | - | if ast::isExclusiveAddress(addr) and not sliceMutable { |
|
| 8794 | - | throw emitError(self, addr.target, ErrorKind::ImmutableBinding); |
|
| 8795 | - | } |
|
| 8796 | - | set item = sliceItem; |
|
| 8797 | - | } else { |
|
| 8798 | - | match subjectTy { |
|
| 8799 | - | case Type::Array(arrayInfo) => { |
|
| 8800 | - | try validateArraySliceBounds(self, range, arrayInfo.length, node); |
|
| 8801 | - | set item = arrayInfo.item; |
|
| 8802 | - | set capacity = arrayInfo.length; |
|
| 8803 | - | } |
|
| 8804 | - | else => { |
|
| 8805 | - | throw emitError(self, container, ErrorKind::ExpectedIndexable); |
|
| 8806 | - | } |
|
| 8807 | - | } |
|
| 8792 | + | let mut info = sliceRangeInfo(subjectTy) |
|
| 8793 | + | else throw emitError(self, container, ErrorKind::ExpectedIndexable); |
|
| 8794 | + | if ast::isExclusiveAddress(addr) and not info.mutable { |
|
| 8795 | + | throw emitError(self, addr.target, ErrorKind::ImmutableBinding); |
|
| 8808 | 8796 | } |
|
| 8797 | + | if let capacity = info.capacity { |
|
| 8798 | + | try validateArraySliceBounds(self, range, capacity, node); |
|
| 8799 | + | } |
|
| 8800 | + | set info.mutable = addr.kind == ast::AddressKind::Mutable; |
|
| 8809 | 8801 | let class = try addressClass(self, addr.target, hint); |
|
| 8810 | - | let sliceTy = Type::Slice { class, item, mutable: addr.kind == ast::AddressKind::Mutable }; |
|
| 8802 | + | let sliceTy = Type::Slice { class, item: info.itemType, mutable: info.mutable }; |
|
| 8811 | 8803 | let alloc = allocType(self, sliceTy); |
|
| 8812 | - | setSliceRangeInfo(self, node, SliceRangeInfo { |
|
| 8813 | - | itemType: item, |
|
| 8814 | - | mutable: addr.kind == ast::AddressKind::Mutable, |
|
| 8815 | - | capacity, |
|
| 8816 | - | }); |
|
| 8804 | + | setSliceRangeInfo(self, node, info); |
|
| 8817 | 8805 | setNodeType(self, addr.target, *alloc); |
|
| 8818 | 8806 | return setNodeType(self, node, *alloc); |
|
| 8819 | 8807 | } |
|
| 8820 | 8808 | } |
|
| 8821 | 8809 | // Derive a hint for the target type from the slice hint. |
lib/std/lang/resolver/tests.rad
+73 -0
| 53 | 53 | } |
|
| 54 | 54 | } |
|
| 55 | 55 | } |
|
| 56 | 56 | } |
|
| 57 | 57 | ||
| 58 | + | /// Slice operations retain initialized element, capacity, and access metadata. |
|
| 59 | + | @test unsafe fn testSliceRangeMetadata() throws (testing::TestError) { |
|
| 60 | + | let mut arena = testArena(); |
|
| 61 | + | let storage: 'test = &mut arena in { |
|
| 62 | + | let mut res = testResolver(storage); |
|
| 63 | + | let result = try resolveProgramStr(&mut res, |
|
| 64 | + | "fn f() { let mut xs: [u32; 4] = [1, 2, 3, 4]; set xs[1..3] = 7; let view = &mut xs[1..3]; set view[..] = 3; let shared = &view[..]; }" |
|
| 65 | + | ); |
|
| 66 | + | try expectNoErrors(&result); |
|
| 67 | + | let body = try getFnBody(&res, result.root, "f"); |
|
| 68 | + | for index in [1 as u32, 3] { |
|
| 69 | + | let stmt = body.statements[index]; |
|
| 70 | + | let case ast::NodeValue::Assign(_) = stmt.value else throw testing::TestError::Failed; |
|
| 71 | + | let info = super::sliceRangeInfoFor(&res, stmt) else throw testing::TestError::Failed; |
|
| 72 | + | assert *info.itemType == super::Type::U32; |
|
| 73 | + | assert info.mutable; |
|
| 74 | + | let capacity: ?u32 = 4 if index == 1 else nil; |
|
| 75 | + | assert info.capacity == capacity; |
|
| 76 | + | } |
|
| 77 | + | for index in [2 as u32, 4] { |
|
| 78 | + | let stmt = body.statements[index]; |
|
| 79 | + | let case ast::NodeValue::Let(binding) = stmt.value else throw testing::TestError::Failed; |
|
| 80 | + | let case ast::NodeValue::AddressOf(_) = binding.value.value else throw testing::TestError::Failed; |
|
| 81 | + | let info = super::sliceRangeInfoFor(&res, binding.value) else throw testing::TestError::Failed; |
|
| 82 | + | assert *info.itemType == super::Type::U32; |
|
| 83 | + | assert info.mutable == (index == 2); |
|
| 84 | + | let capacity: ?u32 = 4 if index == 2 else nil; |
|
| 85 | + | assert info.capacity == capacity; |
|
| 86 | + | } |
|
| 87 | + | } |
|
| 88 | + | } |
|
| 89 | + | ||
| 90 | + | /// Slice assignment and borrowing enforce static bounds and source access. |
|
| 91 | + | @test unsafe fn testSliceRangeErrors() throws (testing::TestError) { |
|
| 92 | + | for program in [ |
|
| 93 | + | "fn f() { let mut xs: [u32; 2] = [1, 2]; set xs[..3] = 0; }", |
|
| 94 | + | "fn f() { let mut xs: [u32; 2] = [1, 2]; set xs[2..1] = 0; }", |
|
| 95 | + | "fn f() { let mut xs: [u32; 2] = [1, 2]; let view = &mut xs[..3]; }", |
|
| 96 | + | ] { |
|
| 97 | + | let mut arena = testArena(); |
|
| 98 | + | let storage: 'test = &mut arena in { |
|
| 99 | + | let mut res = testResolver(storage); |
|
| 100 | + | let result = try resolveProgramStr(&mut res, program); |
|
| 101 | + | let err = try expectError(&result); |
|
| 102 | + | assert err.kind == super::ErrorKind::SliceRangeOutOfBounds, program; |
|
| 103 | + | } |
|
| 104 | + | } |
|
| 105 | + | for program in [ |
|
| 106 | + | "fn f() { let mut value: u32 = 1; set value[..] = 0; }", |
|
| 107 | + | "fn f() { let mut value: u32 = 1; let view = &value[..]; }", |
|
| 108 | + | ] { |
|
| 109 | + | let mut arena = testArena(); |
|
| 110 | + | let storage: 'test = &mut arena in { |
|
| 111 | + | let mut res = testResolver(storage); |
|
| 112 | + | let result = try resolveProgramStr(&mut res, program); |
|
| 113 | + | let err = try expectError(&result); |
|
| 114 | + | assert err.kind == super::ErrorKind::ExpectedIndexable, program; |
|
| 115 | + | } |
|
| 116 | + | } |
|
| 117 | + | for program in [ |
|
| 118 | + | "fn f() { let xs: [u32; 2] = [1, 2]; let view = &xs[..]; set view[..] = 0; }", |
|
| 119 | + | "fn f() { let xs: [u32; 2] = [1, 2]; let view = &xs[..]; let other = &mut view[..]; }", |
|
| 120 | + | ] { |
|
| 121 | + | let mut arena = testArena(); |
|
| 122 | + | let storage: 'test = &mut arena in { |
|
| 123 | + | let mut res = testResolver(storage); |
|
| 124 | + | let result = try resolveProgramStr(&mut res, program); |
|
| 125 | + | let err = try expectError(&result); |
|
| 126 | + | assert err.kind == super::ErrorKind::ImmutableBinding, program; |
|
| 127 | + | } |
|
| 128 | + | } |
|
| 129 | + | } |
|
| 130 | + | ||
| 58 | 131 | /// Array layouts retain element alignment for empty and populated arrays. |
|
| 59 | 132 | @test fn testArrayLayoutFromElementLayout() throws (testing::TestError) { |
|
| 60 | 133 | for alignment in [1 as u32, 2, 4, 8, 16] { |
|
| 61 | 134 | let item = super::Layout { size: alignment, alignment }; |
|
| 62 | 135 | for count in [0 as u32, 1, 3] { |
test/tests/regions.slice.metadata.rad
added
+27 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | ||
| 3 | + | /// Slice access retains element types and respects nested borrow regions. |
|
| 4 | + | @default fn main() -> u32 { |
|
| 5 | + | let mut values: [u32; 4] = [1, 2, 3, 4]; |
|
| 6 | + | set values[1..3] = 7; |
|
| 7 | + | let view: 'outer = &mut values[..] in { |
|
| 8 | + | set view[1..3] = 9; |
|
| 9 | + | let inner: 'inner = &mut view[1..3] in { |
|
| 10 | + | set inner[..] = 5; |
|
| 11 | + | set inner[1..1] = 99; |
|
| 12 | + | assert inner.len == 2; |
|
| 13 | + | } |
|
| 14 | + | let shared: 'read = &view[..] in { |
|
| 15 | + | assert shared[0] == 1; |
|
| 16 | + | assert shared[1] == 5; |
|
| 17 | + | assert shared[2] == 5; |
|
| 18 | + | assert shared[3] == 4; |
|
| 19 | + | } |
|
| 20 | + | } |
|
| 21 | + | assert values == [1, 5, 5, 4]; |
|
| 22 | + | let empty: [u32; 0] = []; |
|
| 23 | + | let view: 'empty = &empty[..] in { |
|
| 24 | + | assert view.len == 0; |
|
| 25 | + | } |
|
| 26 | + | return 0; |
|
| 27 | + | } |