diff --git a/BitsKit.Generator/Models/BitFieldModel.cs b/BitsKit.Generator/Models/BitFieldModel.cs index f65a5f3..693ef8f 100644 --- a/BitsKit.Generator/Models/BitFieldModel.cs +++ b/BitsKit.Generator/Models/BitFieldModel.cs @@ -417,6 +417,8 @@ private bool TryGetByteStorageSource(bool writable, out string source) source = BackingFieldType switch { BackingFieldType.Memory => "{4}.Span", + BackingFieldType.Span when BackingField.TypeString == "byte[]" => + writable ? "((Span){4})" : "((ReadOnlySpan){4})", BackingFieldType.Span => "{4}", BackingFieldType.InlineArray when BackingField.TypeString == "byte" => writable ? "((Span)this)" : "((ReadOnlySpan)this)", diff --git a/BitsKit.Tests/GeneratorTests.Models.cs b/BitsKit.Tests/GeneratorTests.Models.cs index fa4189c..14274cf 100644 --- a/BitsKit.Tests/GeneratorTests.Models.cs +++ b/BitsKit.Tests/GeneratorTests.Models.cs @@ -328,6 +328,22 @@ public partial struct SpecializedMemoryAccessorStruct public Memory BigEndianBacking48; } +[BitObject(BitOrder.LeastSignificant)] +public partial struct OffsetArrayAccessorStruct +{ + [BitField(8)] + [BitField("AlignedValue", 32, BitFieldType.UInt32)] + public byte[] AlignedBacking; + + [BitField(13)] + [BitField("Value11", 11, BitFieldType.UInt32)] + public byte[] Backing11; + + [BitField(15)] + [BitField("BigEndianValue48", 48, BitFieldType.UInt64, ReverseBitOrder = true)] + public byte[] BigEndianBacking48; +} + [BitObject(BitOrder.LeastSignificant, AccessMode = BitObjectAccessMode.Unsafe)] public partial struct UnsafeMemoryAccessorStruct { diff --git a/BitsKit.Tests/GeneratorTests.cs b/BitsKit.Tests/GeneratorTests.cs index 8b793e2..fd72756 100644 --- a/BitsKit.Tests/GeneratorTests.cs +++ b/BitsKit.Tests/GeneratorTests.cs @@ -575,6 +575,51 @@ public void SpecializedStorageAccessorsMatchReferenceBits() } } + [TestMethod] + public void OffsetArrayAccessorsMatchReferenceBits() + { + Random random = new(0xA22A7); + + for (int i = 0; i < 1000; i++) + { + byte[] alignedBytes = new byte[5]; + byte[] bytes11 = new byte[(i & 1) == 0 ? 3 : 5]; + byte[] bigEndianBytes48 = new byte[(i & 1) == 0 ? 8 : 9]; + random.NextBytes(alignedBytes); + random.NextBytes(bytes11); + random.NextBytes(bigEndianBytes48); + + var actual = new OffsetArrayAccessorStruct + { + AlignedBacking = alignedBytes, + Backing11 = bytes11, + BigEndianBacking48 = bigEndianBytes48 + }; + + Assert.AreEqual(BitPrimitives.ReadUInt32LSB(alignedBytes, 8, 32), actual.AlignedValue); + Assert.AreEqual((uint)Helpers.ReadBitsLSB(bytes11, 13, 11), actual.Value11); + Assert.AreEqual(Helpers.ReadBitsMSB(bigEndianBytes48, 15, 48), actual.BigEndianValue48); + + uint nextAligned = unchecked((uint)random.NextInt64()); + uint next11 = (uint)random.Next(1 << 11); + ulong next48 = unchecked((ulong)random.NextInt64()) & 0xFFFFFFFFFFFFUL; + byte[] expectedAligned = (byte[])alignedBytes.Clone(); + byte[] expected11 = (byte[])bytes11.Clone(); + byte[] expected48 = (byte[])bigEndianBytes48.Clone(); + BitPrimitives.WriteUInt32LSB(expectedAligned, 8, nextAligned, 32); + Helpers.WriteBitsLSB(expected11, 13, next11, 11); + Helpers.WriteBitsMSB(expected48, 15, next48, 48); + + actual.AlignedValue = nextAligned; + actual.Value11 = next11; + actual.BigEndianValue48 = next48; + + CollectionAssert.AreEqual(expectedAligned, alignedBytes); + CollectionAssert.AreEqual(expected11, bytes11); + CollectionAssert.AreEqual(expected48, bigEndianBytes48); + } + } + [TestMethod] public void ReadOnlyMemberTest() { diff --git a/CHANGELOG.md b/CHANGELOG.md index b8b7699..272b0b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ Notable changes to the community-maintained fork are documented here. This proje - `BitBatchPrimitives` provides allocation-free contiguous and record-strided reads and writes for every supported integral type, Boolean values, and both bit orders. - `BitObjectAttribute.GenerateBatchAccessors` generates strongly typed packed and strided helpers for integral, Boolean, and enum fields. +### Fixed + +- Array-backed generated accessors compile when an optimized field begins at a nonzero byte offset. + ## 1.5.0 - 2026-07-21 ### Added