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
16 changes: 16 additions & 0 deletions changelog/traits_no_init.dd
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Range traits and `std.traits` no longer build the `.init` of the type they inspect

For a struct or a static array, `T.init` makes the compiler build the type's whole
default-initializer literal, with one entry per static-array element. Traits such as
$(REF isInputRange, std, range, primitives), $(REF ElementType, std, range, primitives),
$(REF CommonType, std, traits), $(REF isIterable, std, traits) and
$(REF hasElaborateAssign, std, traits), and the constraints of
$(REF map, std, algorithm, iteration) and $(REF minElement, std, algorithm, searching),
used `T.init` only to form an expression of type `T`. They now use
$(REF lvalueOf, std, traits) and $(REF rvalueOf, std, traits), so their compile-time
memory and time no longer grow with the size of `T`.

Their answers are unchanged, except for a type that declares a member named `init`. There,
`T.init` named that member, so the traits answered for the member; they now answer for the
type. For example, a range type with a member function `init` is now an input range when its
primitives make it one.
8 changes: 4 additions & 4 deletions std/algorithm/iteration.d
Original file line number Diff line number Diff line change
Expand Up @@ -658,7 +658,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. " ~
" Consider using `each` instead of `map!(" ~ _funs.stringof ~ ")`");
}
Expand Down Expand Up @@ -771,7 +771,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 @@ -7123,10 +7123,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 @@ -1445,7 +1445,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 @@ -1465,7 +1465,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 @@ -1504,7 +1504,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 @@ -1539,7 +1539,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 @@ -1550,7 +1550,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 @@ -3756,13 +3756,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 @@ -9146,6 +9146,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
65 changes: 57 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 @@ -1023,7 +1046,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 @@ -1091,7 +1114,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(lvalueOf!R.back.init) == ElementType!R);

/// ditto
enum bool isBidirectionalRange(R, E) =
Expand Down Expand Up @@ -1374,7 +1397,11 @@ object `r` of type `R`. For example, `ElementType!(T[])` is
*/
template ElementType(R)
{
static if (is(typeof(R.init.front.init) T))
// An enum's `.init` is its first member, not a new literal, and only that expression gives
// an enum whose base type is an array a `front`. `R.sizeof`: see `isInputRange`.
static if (is(R == enum) && is(typeof(R.init.front.init) T))
alias ElementType = T;
else static if (!is(R == enum) && is(typeof(R.sizeof)) && is(typeof(lvalueOf!R.front.init) T))
alias ElementType = T;
else
alias ElementType = void;
Expand Down Expand Up @@ -1453,6 +1480,28 @@ 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));
}

/**
The encoding element type of `R`. For narrow strings (`char[]`,
`wchar[]` and their qualified variants including `string` and
Expand Down Expand Up @@ -1764,7 +1813,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 @@ -1777,7 +1826,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
Loading