Skip to content

std.bitmanip.write does not work with ranges with slicing #11028

@jmdavis

Description

@jmdavis

This code

void main()
{
    import std.algorithm.comparison;
    import std.bitmanip;
    import std.range.primitives;

    static struct Range
    {
        @property bool empty() @safe const { return _arr.empty; }
        @property ubyte front() @safe const { return _arr[0]; }
        void popFront() @safe { _arr = _arr[1 .. $]; }
        auto save() @safe { return this; }
        @property size_t length() @safe const { return _arr.length; }
        auto opSlice(size_t i, size_t j) @safe { return Range(_arr[i .. j]); }
        this(ubyte[] arr) @safe { _arr = arr; }
        ubyte[] _arr;
    }
    static assert(isForwardRange!Range);
    static assert(!isRandomAccessRange!Range);
    static assert(hasSlicing!Range);

    {
        auto range = Range([0, 0, 0, 0, 0, 0, 0, 0]);
        range.write!uint(29110231u, 0);
        assert(equal(range, cast(ubyte[])[1, 188, 47, 215, 0, 0, 0, 0]));
    }
    {
        auto range = Range([0, 0, 0, 0, 0, 0, 0, 0]);
        size_t index = 0;
        range.write!ushort(261, &index);
        assert(equal(range, cast(ubyte[])[1, 5, 0, 0, 0, 0, 0, 0]));
        assert(index == 2);
    }
}

fails to compile, giving

/usr/local/include/dmd/std/bitmanip.d(3955): Error: cannot modify expression `range[begin..end]` because it is not an lvalue
    range[begin .. end] = bytes[0 .. T.sizeof];
         ^
/usr/local/include/dmd/std/bitmanip.d(3935): Error: template instance `std.bitmanip.write!(uint, Endian.bigEndian, Range)` error instantiating
    write!(T, endianness)(range, value, &index);
                         ^
test2.d(24):        instantiated from here: `write!(uint, Endian.bigEndian, Range)`
        range.write!uint(29110231u, 0);
                        ^
/usr/local/include/dmd/std/bitmanip.d(3955): Error: cannot modify expression `range[begin..end]` because it is not an lvalue
    range[begin .. end] = bytes[0 .. T.sizeof];
         ^
test2.d(30): Error: template instance `std.bitmanip.write!(ushort, Endian.bigEndian, Range)` error instantiating
        range.write!ushort(261, &index);

Basically, write assumes that a ubyte[] can be assigned to a slice of the range. This technically could be implemented (unlike assigning a slice of a range to a ubyte[]), but it's an overload that's a royal pain to write, and the fact that no one caught the fact that peek doesn't work with ranges with slices (#11027) does make it pretty likely that no one has tried write with such a range, but since the issue with caught with read years ago but not the other two (https://issues.dlang.org/show_bug.cgi?id=17247), I'm not sure how reliable that is. Either way, the template constraint does not require such an overload, so ranges with slicing but without the appropriate assignment operator won't compile.

To further complicate things, the template constraint does not require that the elements be assignable even though the code clearly does - but since the code assigns a ubyte[] to a slice, which is not a standard range operation, hasAssignableElements wouldn't check for that anyway. So, it's theoretically possible that changing the code to use hasAssignableElements would break code if someone actually did implement the appropriate overload of opIndexAssign, though I would be very surprised if anyone has done that and used write. Now, the template constraint could be change to require assignable elements while still doing the sliced assignment in the cases where it compiles, and then the only code that should break would be code that had such an assignment operator while not allowing individual elements to be assigned, which seems like it would be kind of crazy for anyone to have implemented even if it's technically possible. So, maybe that's the proper fix, but either way, it needs to be fixed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Phobos 2Issues and PR's specific to Phobos 2.

    Type

    No fields configured for Bug.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions