Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 24 additions & 7 deletions std/algorithm/iteration.d
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,7 @@ if (fun.length >= 1)
// this validation loop can be moved into a template.
foreach (f; _funs)
{
static assert(!is(typeof(f(RE.init)) == void),
static assert(!is(typeof(f(rvalueOf!RE)) == void),
"Mapping function(s) must not return void: " ~ _funs.stringof);
}
}
Expand All @@ -472,7 +472,7 @@ if (fun.length >= 1)

// Do the validation separately for single parameters due to
// https://issues.dlang.org/show_bug.cgi?id=15777.
static assert(!is(typeof(_fun(RE.init)) == void),
static assert(!is(typeof(_fun(rvalueOf!RE)) == void),
"Mapping function(s) must not return void: " ~ _funs.stringof);
}

Expand Down Expand Up @@ -584,7 +584,7 @@ private struct MapResult(alias fun, Range)

static if (isRandomAccessRange!R)
{
static if (is(typeof(Range.init[ulong.max])))
static if (is(typeof(lvalueOf!Range[ulong.max])))
private alias opIndex_t = ulong;
else
private alias opIndex_t = uint;
Expand Down Expand Up @@ -885,10 +885,10 @@ private:
alias BinaryArgs = AliasSeq!(fun, "i", "a");

enum isRangeUnaryIterable(R) =
is(typeof(unaryFun!fun(R.init.front)));
is(typeof(unaryFun!fun(lvalueOf!R.front)));

enum isRangeBinaryIterable(R) =
is(typeof(binaryFun!BinaryArgs(0, R.init.front)));
is(typeof(binaryFun!BinaryArgs(0, lvalueOf!R.front)));

enum isRangeIterable(R) =
isInputRange!R &&
Expand Down Expand Up @@ -1136,6 +1136,23 @@ public:
assert(b == [ 3, 4, 5 ]);
}

// A `ref` parameter binds to a forward range's `front` when `front` is an lvalue
@safe unittest
{
static struct Fwd
{
int front;
int left = 3;
bool empty() const { return left == 0; }
void popFront() { --left; }
Fwd save() { return this; }
}

int[] seen;
Fwd(1).each!((ref e) { seen ~= e; e *= 2; });
assert(seen == [1, 2, 4]);
}

// https://issues.dlang.org/show_bug.cgi?id=15358
// application of `each` with >2 args (opApply)
@system unittest
Expand Down Expand Up @@ -6883,10 +6900,10 @@ if (isInputRange!R && Substs.length >= 2 && !is(CommonType!(Substs) == void))

private
{
typeof(R.init.drop(0)) rest;
typeof(rvalueOf!R.drop(0)) rest;
Ins needles;

typeof(R.init.take(0)) skip; // skip before next hit
typeof(rvalueOf!R.take(0)) skip; // skip before next hit
alias Hit = size_t; // 0 iff no hit, otherwise hit in needles[index-1]
alias E = Tuple!(typeof(skip), Hit);
Hit hitNr; // hit number: 0 means no hit, otherwise index+1 to needles that matched
Expand Down
10 changes: 5 additions & 5 deletions std/algorithm/searching.d
Original file line number Diff line number Diff line change
Expand Up @@ -1308,7 +1308,7 @@ Returns:
*/
private auto extremum(alias map, alias selector = "a < b", Range)(Range r)
if (isInputRange!Range && !isInfinite!Range &&
is(typeof(unaryFun!map(ElementType!(Range).init))))
is(typeof(unaryFun!map(rvalueOf!(ElementType!Range)))))
in
{
assert(!r.empty, "r is an empty range");
Expand All @@ -1328,7 +1328,7 @@ private auto extremum(alias map, alias selector = "a < b", Range,
(Range r, RangeElementType seedElement)
if (isInputRange!Range && !isInfinite!Range &&
!is(CommonType!(ElementType!Range, RangeElementType) == void) &&
is(typeof(unaryFun!map(ElementType!(Range).init))))
is(typeof(unaryFun!map(rvalueOf!(ElementType!Range)))))
{
import std.typecons : Rebindable2;

Expand Down Expand Up @@ -1367,7 +1367,7 @@ if (isInputRange!Range && !isInfinite!Range &&
}
else
{
alias MapType = Unqual!(typeof(mapFun(CommonElement.init)));
alias MapType = Unqual!(typeof(mapFun(rvalueOf!CommonElement)));
MapType extremeElementMapped = mapFun(extremeElement.get);

// direct access via a random access range is faster
Expand Down Expand Up @@ -1402,7 +1402,7 @@ if (isInputRange!Range && !isInfinite!Range &&

private auto extremum(alias selector = "a < b", Range)(Range r)
if (isInputRange!Range && !isInfinite!Range &&
!is(typeof(unaryFun!selector(ElementType!(Range).init))))
!is(typeof(unaryFun!selector(rvalueOf!(ElementType!Range)))))
{
return extremum!(a => a, selector)(r);
}
Expand All @@ -1413,7 +1413,7 @@ private auto extremum(alias selector = "a < b", Range,
(Range r, RangeElementType seedElement)
if (isInputRange!Range && !isInfinite!Range &&
!is(CommonType!(ElementType!Range, RangeElementType) == void) &&
!is(typeof(unaryFun!selector(ElementType!(Range).init))))
!is(typeof(unaryFun!selector(rvalueOf!(ElementType!Range)))))
{
return extremum!(a => a, selector)(r, seedElement);
}
Expand Down
4 changes: 2 additions & 2 deletions std/array.d
Original file line number Diff line number Diff line change
Expand Up @@ -3653,13 +3653,13 @@ if (isDynamicArray!A)
enum bool canPutConstRange =
isInputRange!(Unqual!Range) &&
!isInputRange!Range &&
is(typeof(Appender.init.put(Range.init.front)));
is(typeof(Appender.init.put(rvalueOf!Range.front)));
}
private template canPutRange(Range)
{
enum bool canPutRange =
isInputRange!Range &&
is(typeof(Appender.init.put(Range.init.front)));
is(typeof(Appender.init.put(rvalueOf!Range.front)));
}

/**
Expand Down
12 changes: 12 additions & 0 deletions std/range/package.d
Original file line number Diff line number Diff line change
Expand Up @@ -9035,6 +9035,18 @@ if (isInputRange!Source)
assert(equal!`equal(a, b)`(oddsByPairs[3 .. $].take(2), [[13, 15], [17, 19]]));
}

// Over an infinite sliceable source, `Chunks.opSlice` returns `typeof(takeExactly(this, 0))`,
// so `isInputRange!Chunks` is evaluated while `Chunks` is still being analysed. That must not
// leave `front` and `popFront` inferred `@system` or impure.
@safe pure unittest
{
import std.algorithm.comparison : equal;

auto r = sequence!"a[0] + n * a[1]"(1, 2).chunks(2);
r.popFront();
assert(r.front.equal([5, 7]));
}



/**
Expand Down
94 changes: 86 additions & 8 deletions std/range/primitives.d
Original file line number Diff line number Diff line change
Expand Up @@ -172,11 +172,14 @@ Params:
Returns:
`true` if R is an input range (possibly with element type `E`), `false` if not
*/
// `R.sizeof` lays out a struct before the lambdas below infer its member functions' attributes,
// as `R.init` did: a range trait evaluated inside the struct's own declaration (in a member's
// return type, like `Chunks.opSlice`) would otherwise infer those members impure and `@system`.
enum bool isInputRange(R) =
is(typeof(R.init) == R)
&& is(typeof((R r) { return r.empty; } (R.init)) == bool)
is(typeof(lvalueOf!R) == R) && is(typeof(R.sizeof))
&& is(typeof((R r) { return r.empty; } (rvalueOf!R)) == bool)
&& (is(typeof((return ref R r) => r.front)) || is(typeof(ref (return ref R r) => r.front)))
&& !is(typeof((R r) { return r.front; } (R.init)) == void)
&& !is(typeof((R r) { return r.front; } (rvalueOf!R)) == void)
&& is(typeof((R r) => r.popFront));

/// ditto
Expand Down Expand Up @@ -283,6 +286,26 @@ enum bool isInputRange(R, E) =
assert(r.equal([ 0, 1, 2 ]));
}

// A range type with a member named `init`, here from a mixin template, hides the built-in `.init`
@safe unittest
{
mixin template SetUp()
{
void init(int v) { i = v; }
}
static struct R
{
int i;
bool empty() const { return i >= 3; }
int front() const { return i; }
void popFront() { ++i; }
R save() { return this; }
mixin SetUp;
}
static assert(isInputRange!R);
static assert(isForwardRange!R);
}

/+
puts the whole raw element `e` into `r`. doPut will not attempt to
iterate, slice or transcode `e` in any way shape or form. It will $(B only)
Expand Down Expand Up @@ -1022,7 +1045,7 @@ Returns:
`true` if R is a forward range (possibly with element type `E`), `false` if not
*/
enum bool isForwardRange(R) = isInputRange!R
&& is(typeof((R r) { return r.save; } (R.init)) == R);
&& is(typeof((R r) { return r.save; } (rvalueOf!R)) == R);

/// ditto
enum bool isForwardRange(R, E) =
Expand Down Expand Up @@ -1090,7 +1113,7 @@ Returns:
enum bool isBidirectionalRange(R) = isForwardRange!R
&& is(typeof((R r) => r.popBack))
&& (is(typeof((return ref R r) => r.back)) || is(typeof(ref (return ref R r) => r.back)))
&& is(typeof(R.init.back.init) == ElementType!R);
&& is(typeof(backOf(lvalueOf!R)) == ElementType!R);

/// ditto
enum bool isBidirectionalRange(R, E) =
Expand Down Expand Up @@ -1363,6 +1386,13 @@ enum bool hasMobileElements(R) =
static assert( hasMobileElements!(dchar[]));
}

// `r.front` / `r.back` in a return statement, so a member function is called whether or not
// it is a `@property` (in a bare `typeof(r.front)` it is not), a field or a `ref` result is
// returned by reference (no copy, so a non-copyable element type is answered too), and no
// `.init` of the element type is built.
private auto ref frontOf(R)(return ref R r) { return r.front; }
private auto ref backOf(R)(return ref R r) { return r.back; }

/**
The element type of `R`. `R` does not have to be a range. The
element type is determined as the type yielded by `r.front` for an
Expand All @@ -1373,7 +1403,8 @@ object `r` of type `R`. For example, `ElementType!(T[])` is
*/
template ElementType(R)
{
static if (is(typeof(R.init.front.init) T))
// `R.sizeof`: see `isInputRange`.
static if (is(typeof(R.sizeof)) && is(typeof(frontOf(lvalueOf!R)) T))
alias ElementType = T;
else
alias ElementType = void;
Expand Down Expand Up @@ -1452,6 +1483,53 @@ template ElementType(R)
static assert(is(ElementType!R == E));
}

// The range traits also work when no primitive is a `@property`
@safe unittest
{
static struct R
{
size_t i;
bool empty() const { return i >= 3; }
int front() const { return cast(int) i; }
void popFront() { ++i; }
R save() { return this; }
int back() const { return cast(int) i; }
void popBack() { ++i; }
size_t length() const { return 3 - i; }
R opSlice(size_t, size_t) { return this; }
}
static assert(isInputRange!R);
static assert(isForwardRange!R);
static assert(isBidirectionalRange!R);
static assert(hasSlicing!R);
static assert(is(ElementType!R == int));
}

// An element type with a member named `init`, here from a mixin template, hides the built-in `.init`
@safe unittest
{
mixin template SetUp()
{
void init(int v) { x = v; }
}
static struct S
{
int x;
mixin SetUp;
}
static assert(is(ElementType!(S[]) == S));
static assert(isRandomAccessRange!(S[]));
}

// A variable of an enum type whose base type is a non-string array has no `front`
@safe unittest
{
enum E : int[] { a = [1, 2] }
E e = E.a;
static assert(!__traits(compiles, e.front));
static assert(is(ElementType!E == void));
}

/**
The encoding element type of `R`. For narrow strings (`char[]`,
`wchar[]` and their qualified variants including `string` and
Expand Down Expand Up @@ -1763,7 +1841,7 @@ The following expression must be true for `hasSlicing` to be `true`:
----
isForwardRange!R
&& !(isAutodecodableString!R && !isAggregateType!R)
&& is(typeof((R r) { return r[1 .. 1].length; } (R.init)) == size_t)
&& is(typeof((R r) { return r[1 .. 1].length; } (rvalueOf!R)) == size_t)
&& (is(typeof(lvalueOf!R[1 .. 1]) == R) || isInfinite!R)
&& (!is(typeof(lvalueOf!R[0 .. $])) || is(typeof(lvalueOf!R[0 .. $]) == R))
&& (!is(typeof(lvalueOf!R[0 .. $])) || isInfinite!R
Expand All @@ -1776,7 +1854,7 @@ The following expression must be true for `hasSlicing` to be `true`:
*/
enum bool hasSlicing(R) = isForwardRange!R
&& !(isAutodecodableString!R && !isAggregateType!R)
&& is(typeof((R r) { return r[1 .. 1].length; } (R.init)) == size_t)
&& is(typeof((R r) { return r[1 .. 1].length; } (rvalueOf!R)) == size_t)
&& (is(typeof(lvalueOf!R[1 .. 1]) == R) || isInfinite!R)
&& (!is(typeof(lvalueOf!R[0 .. $])) || is(typeof(lvalueOf!R[0 .. $]) == R))
&& (!is(typeof(lvalueOf!R[0 .. $])) || isInfinite!R
Expand Down
Loading