aboutsummaryrefslogtreecommitdiff
path: root/libphobos/src/std/algorithm
diff options
context:
space:
mode:
authorIain Buclaw <ibuclaw@gdcproject.org>2021-12-05 17:11:12 +0100
committerIain Buclaw <ibuclaw@gdcproject.org>2021-12-09 00:58:58 +0100
commit0fb57034770aa20adced4d176f34ca611c2945bf (patch)
tree1f5735c8b4f25aa4a290e5ae8124713c24f98359 /libphobos/src/std/algorithm
parentc15aa46cca0649b68613d3292cf71c7cc57ef78f (diff)
downloadgcc-0fb57034770aa20adced4d176f34ca611c2945bf.zip
gcc-0fb57034770aa20adced4d176f34ca611c2945bf.tar.gz
gcc-0fb57034770aa20adced4d176f34ca611c2945bf.tar.bz2
d: Merge upstream dmd 568496d5b, druntime 178c44ff, phobos 574bf883b.
D front-end changes: - Import dmd v2.098.0 - New ImportC module for compiling preprocessed C11 code into D. - New -ftransition=in switch. - Improved handling of new 'noreturn' type. Druntime changes: - Import druntime v2.098.0 - Fix broken import in core.sys.linux.perf_event module (PR103558). Phobos changes: - Import phobos v2.098.0 - All sources are now compiled with -fpreview=fieldwise. gcc/d/ChangeLog: * dmd/MERGE: Merge upstream dmd 568496d5b. * Make-lang.in (D_FRONTEND_OBJS): Add d/common-file.o, d/common-outbuffer.o, d/common-string.o, d/file_manager.o, d/importc.o. Remove d/root-outbuffer.o. (d/common-%.o): New recipe. * d-builtins.cc (build_frontend_type): Update for new front-end interface. (d_build_d_type_nodes): Set noreturn_type_node. * d-codegen.cc (d_build_call): Don't call function if one of the arguments is type 'noreturn'. (build_vthis_function): Propagate TYPE_QUAL_VOLATILE from original function type. * d-frontend.cc (eval_builtin): Update signature. (getTypeInfoType): Likewise. (toObjFile): New function. * d-gimplify.cc (d_gimplify_call_expr): Always evaluate arguments from left to right. * d-lang.cc (d_handle_option): Handle OPT_ftransition_in. (d_parse_file): Don't generate D main if it is declared in user code. * d-tree.h (CALL_EXPR_ARGS_ORDERED): Remove. (enum d_tree_index): Add DTI_BOTTOM_TYPE. (noreturn_type_node): New. * decl.cc (apply_pragma_crt): Remove. (DeclVisitor::visit): Update for new front-end interface. (DeclVisitor::visit (PragmaDeclaration *)): Don't handle crt_constructor and crt_destructor pragmas. (DeclVisitor::visit (VarDeclaration *)): Don't generate declarations of type 'noreturn'. (DeclVisitor::visit (FuncDeclaration *)): Stop adding parameters when 'noreturn' type has been encountered. (get_symbol_decl): Set DECL_STATIC_CONSTRUCTOR and DECL_STATIC_DESTRUCTOR on decl node if requested. (aggregate_initializer_decl): Update for new front-end interface. * expr.cc (ExprVisitor::visit (CallExp *)): Always use the 'this' object as the result of calling any constructor function. (ExprVisitor::visit): Update for new front-end interface. * gdc.texi (Runtime Options): Document -fmain and -ftransition=in. * lang.opt (ftransition=in): New option. * modules.cc (get_internal_fn): Update for new front-end interface. * types.cc (TypeVisitor::visit): Likewise. (TypeVisitor::visit (TypeNoreturn *)): Return noreturn_type_node. (TypeVisitor::visit (TypeFunction *)): Stop adding parameters when 'notreturn' type has been encountered. Qualify function types that return 'noreturn' as TYPE_QUAL_VOLATILE. libphobos/ChangeLog: PR d/103558 * libdruntime/MERGE: Merge upstream druntime 178c44ff. * libdruntime/Makefile.am (DRUNTIME_DSOURCES_LINUX): Add core/sys/linux/syscalls.d. (DRUNTIME_DSOURCES_OPENBSD): Add core/sys/openbsd/pthread_np.d. * libdruntime/Makefile.in: Regenerate. * src/MERGE: Merge upstream phobos 574bf883b. * src/Makefile.am (D_EXTRA_DFLAGS): Add -fpreview=fieldwise. * src/Makefile.in: Regenerate. * testsuite/libphobos.exceptions/assert_fail.d: Update test. * testsuite/libphobos.betterc/test22336.d: New test.
Diffstat (limited to 'libphobos/src/std/algorithm')
-rw-r--r--libphobos/src/std/algorithm/comparison.d401
-rw-r--r--libphobos/src/std/algorithm/iteration.d237
-rw-r--r--libphobos/src/std/algorithm/mutation.d2
-rw-r--r--libphobos/src/std/algorithm/searching.d8
-rw-r--r--libphobos/src/std/algorithm/sorting.d12
5 files changed, 446 insertions, 214 deletions
diff --git a/libphobos/src/std/algorithm/comparison.d b/libphobos/src/std/algorithm/comparison.d
index c952544..2fcc2ba 100644
--- a/libphobos/src/std/algorithm/comparison.d
+++ b/libphobos/src/std/algorithm/comparison.d
@@ -58,10 +58,10 @@ T2=$(TR $(TDNW $(LREF $1)) $(TD $+))
*/
module std.algorithm.comparison;
-import std.functional : unaryFun, binaryFun;
+import std.functional : unaryFun, binaryFun, lessThan, greaterThan;
import std.range.primitives;
import std.traits;
-import std.meta : allSatisfy;
+import std.meta : allSatisfy, anySatisfy;
import std.typecons : tuple, Tuple, Flag, Yes;
import std.internal.attributes : betterC;
@@ -247,7 +247,7 @@ auto castSwitch(choices...)(Object switchObject)
bool result = true;
foreach (index, choice; choices)
{
- result &= is(ReturnType!choice == void);
+ result &= is(ReturnType!choice : void); // void or noreturn
}
return result;
}();
@@ -514,9 +514,54 @@ auto castSwitch(choices...)(Object switchObject)
) == "derived from I");
}
-/** Clamps a value into the given bounds.
+// https://issues.dlang.org/show_bug.cgi?id=22384
+@system unittest
+{
+ // Use explicit methods to enforce return types
+ static void objectSkip(Object) {}
+ static void defaultSkip() {}
+
+ static noreturn objectError(Object) { assert(false); }
+ static noreturn defaultError() { assert(false); }
+
+ {
+ alias test = castSwitch!(objectSkip, defaultError);
+ static assert(is(ReturnType!test == void));
+ }{
+ alias test = castSwitch!(objectError, defaultSkip);
+ static assert(is(ReturnType!test == void));
+ }{
+ alias test = castSwitch!(objectError, defaultError);
+ static assert(is(ReturnType!test == noreturn));
+ }
+
+ // Also works with non-void handlers
+ static int objectValue(Object) { return 1;}
+ static int defaultValue() { return 2; }
+
+ {
+ alias test = castSwitch!(objectValue, defaultError);
+ static assert(is(ReturnType!test == int));
+ }{
+ alias test = castSwitch!(objectError, defaultValue);
+ static assert(is(ReturnType!test == int));
+ }
+
+ // No confusion w.r.t. void callbacks
+ alias FP = void function();
+ static FP objectFunc(Object) { return &defaultSkip; }
+ static FP defaultFunc() { return &defaultSkip; }
+
+ {
+ alias test = castSwitch!(objectFunc, defaultError);
+ static assert(is(ReturnType!test == FP));
+ }{
+ alias test = castSwitch!(objectError, defaultFunc);
+ static assert(is(ReturnType!test == FP));
+ }
+}
-This function is equivalent to `max(lower, min(upper, val))`.
+/** Clamps `val` into the given bounds. Result has the same type as `val`.
Params:
val = The value to _clamp.
@@ -524,20 +569,22 @@ Params:
upper = The _upper bound of the _clamp.
Returns:
- Returns `val`, if it is between `lower` and `upper`.
- Otherwise returns the nearest of the two.
-
+ `lower` if `val` is less than `lower`, `upper` if `val` is greater than
+ `upper`, and `val` in all other cases. Comparisons are made
+ correctly (using $(REF lessThan, std,functional) and the return value
+ is converted to the return type using the standard integer coversion rules
+ $(REF greaterThan, std,functional)) even if the signedness of `T1`, `T2`,
+ and `T3` are different.
*/
-auto clamp(T1, T2, T3)(T1 val, T2 lower, T3 upper)
-if (is(typeof(max(min(val, upper), lower))))
+T1 clamp(T1, T2, T3)(T1 val, T2 lower, T3 upper)
+if (is(typeof(val.lessThan(lower) ? lower : val.greaterThan(upper) ? upper : val) : T1))
in
{
- import std.functional : greaterThan;
assert(!lower.greaterThan(upper), "Lower can't be greater than upper.");
}
do
{
- return max(min(val, upper), lower);
+ return val.lessThan(lower) ? lower : val.greaterThan(upper) ? upper : val;
}
///
@@ -550,6 +597,10 @@ do
assert(clamp(1, 1, 1) == 1);
assert(clamp(5, -1, 2u) == 2);
+
+ auto x = clamp(42, uint.max, uint.max);
+ static assert(is(typeof(x) == int));
+ assert(x == -1);
}
@safe unittest
@@ -564,7 +615,7 @@ do
// mixed sign
a = -5;
uint f = 5;
- static assert(is(typeof(clamp(f, a, b)) == int));
+ static assert(is(typeof(clamp(f, a, b)) == uint));
assert(clamp(f, a, b) == f);
// similar type deduction for (u)long
static assert(is(typeof(clamp(-1L, -2L, 2UL)) == long));
@@ -874,101 +925,121 @@ nothrow pure @safe unittest
// equal
/**
-Compares two ranges for equality, as defined by predicate `pred`
+Compares two or more ranges for equality, as defined by predicate `pred`
(which is `==` by default).
*/
template equal(alias pred = "a == b")
{
/++
- Compares two ranges for equality. The ranges may have
- different element types, as long as `pred(r1.front, r2.front)`
- evaluates to `bool`.
- Performs $(BIGOH min(r1.length, r2.length)) evaluations of `pred`.
+ Compares two or more ranges for equality. The ranges may have
+ different element types, as long as all are comparable by means of
+ the `pred`.
+ Performs $(BIGOH min(rs[0].length, rs[1].length, ...)) evaluations of `pred`. However, if
+ `equal` is invoked with the default predicate, the implementation may take the liberty
+ to use faster implementations that have the theoretical worst-case
+ $(BIGOH max(rs[0].length, rs[1].length, ...)).
At least one of the ranges must be finite. If one range involved is infinite, the result is
(statically known to be) `false`.
- If the two ranges are different kinds of UTF code unit (`char`, `wchar`, or
- `dchar`), then the arrays are compared using UTF decoding to avoid
+ If the ranges have different kinds of UTF code unit (`char`, `wchar`, or
+ `dchar`), then they are compared using UTF decoding to avoid
accidentally integer-promoting units.
Params:
- r1 = The first range to be compared.
- r2 = The second range to be compared.
+ rs = The ranges to be compared.
Returns:
- `true` if and only if the two ranges compare _equal element
+ `true` if and only if all ranges compare _equal element
for element, according to binary predicate `pred`.
+/
- bool equal(Range1, Range2)(Range1 r1, Range2 r2)
- if (isInputRange!Range1 && isInputRange!Range2 &&
- !(isInfinite!Range1 && isInfinite!Range2) &&
- is(typeof(binaryFun!pred(r1.front, r2.front))))
+ bool equal(Ranges...)(Ranges rs)
+ if (rs.length > 1
+ && allSatisfy!(isInputRange, Ranges)
+ && !allSatisfy!(isInfinite, Ranges)
+ && is(typeof(binaryFun!pred(rs[0].front, rs[1].front)))
+ && (rs.length == 2 || is(typeof(equal!pred(rs[1 .. $])) == bool))
+ )
{
- // Use code points when comparing two ranges of UTF code units that aren't
- // the same type. This is for backwards compatibility with autodecode
- // strings.
- enum useCodePoint =
- isSomeChar!(ElementEncodingType!Range1) && isSomeChar!(ElementEncodingType!Range2) &&
- ElementEncodingType!Range1.sizeof != ElementEncodingType!Range2.sizeof;
-
- static if (useCodePoint)
+ alias ElementEncodingTypes = staticMap!(ElementEncodingType, Ranges);
+ enum differentSize(T) = T.sizeof != ElementEncodingTypes[0].sizeof;
+ enum useCodePoint = allSatisfy!(isSomeChar, ElementEncodingTypes) &&
+ anySatisfy!(differentSize, ElementEncodingTypes);
+ enum bool comparableWithEq(alias r) = is(typeof(rs[0] == r));
+
+ static if (anySatisfy!(isInfinite, Ranges))
{
- import std.utf : byDchar;
- return equal(r1.byDchar, r2.byDchar);
+ return false;
}
- else
+ else static if (useCodePoint)
{
- static if (isInfinite!Range1 || isInfinite!Range2)
- {
- // No finite range can be ever equal to an infinite range.
- return false;
- }
- // Detect default pred and compatible dynamic arrays.
- else static if (is(typeof(pred) == string) && pred == "a == b" &&
- isArray!Range1 && isArray!Range2 && is(typeof(r1 == r2)))
- {
- return r1 == r2;
- }
- // If one of the arguments is a string and the other isn't, then auto-decoding
- // can be avoided if they have the same ElementEncodingType.
- else static if (is(typeof(pred) == string) && pred == "a == b" &&
- isAutodecodableString!Range1 != isAutodecodableString!Range2 &&
- is(immutable ElementEncodingType!Range1 == immutable ElementEncodingType!Range2))
+ import std.utf : byDchar;
+ static bool allByDchar(size_t done, Ranges...)(auto ref Ranges rs)
{
- import std.utf : byCodeUnit;
-
- static if (isAutodecodableString!Range1)
- return equal(r1.byCodeUnit, r2);
+ static if (done == rs.length)
+ return equalLoop(rs);
else
- return equal(r2.byCodeUnit, r1);
- }
- // Try a fast implementation when the ranges have comparable lengths.
- else static if (hasLength!Range1 && hasLength!Range2 && is(typeof(r1.length == r2.length)))
- {
- immutable len1 = r1.length;
- immutable len2 = r2.length;
- if (len1 != len2) return false; //Short circuit return
-
- // Lengths are the same, so we need to do an actual comparison.
- // Good news is we can squeeze out a bit of performance by not checking if r2 is empty.
- for (; !r1.empty; r1.popFront(), r2.popFront())
- {
- if (!binaryFun!(pred)(r1.front, r2.front)) return false;
- }
- return true;
+ return allByDchar!(done + 1)(rs[0 .. done], rs[done].byDchar, rs[done + 1 .. $]);
}
+ return allByDchar!0(rs);
+ }
+ else static if (is(typeof(pred) == string) && pred == "a == b" &&
+ allSatisfy!(isArray, Ranges) && allSatisfy!(comparableWithEq, rs))
+ {
+ static foreach (r; rs[1 .. $])
+ if (rs[0] != r)
+ return false;
+ return true;
+ }
+ // if one of the arguments is a string and the other isn't, then auto-decoding
+ // can be avoided if they have the same ElementEncodingType
+ // TODO: generalize this
+ else static if (rs.length == 2 && is(typeof(pred) == string) && pred == "a == b" &&
+ isAutodecodableString!(Ranges[0]) != isAutodecodableString!(Ranges[1]) &&
+ is(immutable ElementEncodingType!(Ranges[0]) == immutable ElementEncodingType!(Ranges[1])))
+ {
+ import std.utf : byCodeUnit;
+ static if (isAutodecodableString!(Ranges[0]))
+ return equal(rs[0].byCodeUnit, rs[1]);
else
+ return equal(rs[1].byCodeUnit, rs[0]);
+ }
+ else
+ {
+ static foreach (i, R; Ranges)
{
- //Generic case, we have to walk both ranges making sure neither is empty
- for (; !r1.empty; r1.popFront(), r2.popFront())
+ static if (hasLength!R)
{
- if (r2.empty || !binaryFun!(pred)(r1.front, r2.front)) return false;
+ static if (!is(typeof(firstLength)))
+ {
+ // Found the first range that has length
+ auto firstLength = rs[i].length;
+ }
+ else
+ {
+ // Compare the length of the current range against the first with length
+ if (firstLength != rs[i].length)
+ return false;
+ }
}
- return r2.empty;
}
+ return equalLoop(rs);
}
}
+
+ private bool equalLoop(Rs...)(Rs rs)
+ {
+ for (; !rs[0].empty; rs[0].popFront)
+ static foreach (r; rs[1 .. $])
+ if (r.empty || !binaryFun!pred(rs[0].front, r.front))
+ return false;
+ else
+ r.popFront;
+ static foreach (r; rs[1 .. $])
+ if (!r.empty)
+ return false;
+ return true;
+ }
}
///
@@ -992,6 +1063,31 @@ template equal(alias pred = "a == b")
assert(equal!isClose(b[], c[]));
}
+@safe @nogc unittest
+{
+ import std.algorithm.comparison : equal;
+ import std.math.operations : isClose;
+
+ auto s1 = "abc", s2 = "abc"w;
+ assert(equal(s1, s2, s2));
+ assert(equal(s1, s2, s2, s1));
+ assert(!equal(s1, s2, s2[1 .. $]));
+
+ int[4] a = [ 1, 2, 4, 3 ];
+ assert(!equal(a[], a[1..$], a[]));
+ assert(equal(a[], a[], a[]));
+ assert(equal!((a, b) => a == b)(a[], a[], a[]));
+
+ // different types
+ double[4] b = [ 1.0, 2, 4, 3];
+ assert(!equal(a[], b[1..$], b[]));
+ assert(equal(a[], b[], a[], b[]));
+
+ // predicated: ensure that two vectors are approximately equal
+ double[4] c = [ 1.0000000005, 2, 4, 3];
+ assert(equal!isClose(b[], c[], b[]));
+}
+
/++
Tip: `equal` can itself be used as a predicate to other functions.
This can be very useful when the element type of a range is itself a
@@ -1748,21 +1844,26 @@ store the lowest values.
// mismatch
/**
-Sequentially compares elements in `r1` and `r2` in lockstep, and
+Sequentially compares elements in `rs` in lockstep, and
stops at the first mismatch (according to `pred`, by default
equality). Returns a tuple with the reduced ranges that start with the
-two mismatched values. Performs $(BIGOH min(r1.length, r2.length))
+two mismatched values. Performs $(BIGOH min(r[0].length, r[1].length, ...))
evaluations of `pred`.
*/
-Tuple!(Range1, Range2)
-mismatch(alias pred = "a == b", Range1, Range2)(Range1 r1, Range2 r2)
-if (isInputRange!(Range1) && isInputRange!(Range2))
+Tuple!(Ranges)
+mismatch(alias pred = (a, b) => a == b, Ranges...)(Ranges rs)
+if (rs.length >= 2 && allSatisfy!(isInputRange, Ranges))
{
- for (; !r1.empty && !r2.empty; r1.popFront(), r2.popFront())
+ loop: for (; !rs[0].empty; rs[0].popFront)
{
- if (!binaryFun!(pred)(r1.front, r2.front)) break;
+ static foreach (r; rs[1 .. $])
+ {
+ if (r.empty || !binaryFun!pred(rs[0].front, r.front))
+ break loop;
+ r.popFront;
+ }
}
- return tuple(r1, r2);
+ return tuple(rs);
}
///
@@ -1773,6 +1874,12 @@ if (isInputRange!(Range1) && isInputRange!(Range2))
auto m = mismatch(x[], y[]);
assert(m[0] == x[3 .. $]);
assert(m[1] == y[3 .. $]);
+
+ auto m2 = mismatch(x[], y[], x[], y[]);
+ assert(m2[0] == x[3 .. $]);
+ assert(m2[1] == y[3 .. $]);
+ assert(m2[2] == x[3 .. $]);
+ assert(m2[3] == y[3 .. $]);
}
@safe @nogc unittest
@@ -1925,50 +2032,91 @@ auto predSwitch(alias pred = "a == b", T, R ...)(T switchExpression, lazy R choi
}
/**
-Checks if the two ranges have the same number of elements. This function is
+Checks if two or more ranges have the same number of elements. This function is
optimized to always take advantage of the `length` member of either range
if it exists.
-If both ranges have a length member, this function is $(BIGOH 1). Otherwise,
-this function is $(BIGOH min(r1.length, r2.length)).
+If all ranges have a `length` member or at least one is infinite,
+`_isSameLength`'s complexity is $(BIGOH 1). Otherwise, complexity is
+$(BIGOH n), where `n` is the smallest of the lengths of ranges with unknown
+length.
-Infinite ranges are considered of the same length. An infinite range has never the same length as a
-finite range.
+Infinite ranges are considered of the same length. An infinite range has never
+the same length as a finite range.
Params:
- r1 = a finite $(REF_ALTTEXT input range, isInputRange, std,range,primitives)
- r2 = a finite $(REF_ALTTEXT input range, isInputRange, std,range,primitives)
+ rs = two or more $(REF_ALTTEXT input ranges, isInputRange, std,range,primitives)
Returns:
`true` if both ranges have the same length, `false` otherwise.
*/
-bool isSameLength(Range1, Range2)(Range1 r1, Range2 r2)
-if (isInputRange!Range1 && isInputRange!Range2)
+bool isSameLength(Ranges...)(Ranges rs)
+if (allSatisfy!(isInputRange, Ranges))
{
- static if (isInfinite!Range1 || isInfinite!Range2)
- {
- return isInfinite!Range1 && isInfinite!Range2;
- }
- else static if (hasLength!(Range1) && hasLength!(Range2))
+ static if (anySatisfy!(isInfinite, Ranges))
{
- return r1.length == r2.length;
+ return allSatisfy!(isInfinite, Ranges);
}
- else static if (hasLength!(Range1) && !hasLength!(Range2))
+ else static if (anySatisfy!(hasLength, Ranges))
{
- return r2.walkLength(r1.length + 1) == r1.length;
- }
- else static if (!hasLength!(Range1) && hasLength!(Range2))
- {
- return r1.walkLength(r2.length + 1) == r2.length;
+ // Compute the O(1) length
+ auto baselineLength = size_t.max;
+ static foreach (i, R; Ranges)
+ {
+ static if (hasLength!R)
+ {
+ if (baselineLength == size_t.max)
+ baselineLength = rs[i].length;
+ else if (rs[i].length != baselineLength)
+ return false;
+ }
+ }
+ // Iterate all ranges without known length
+ foreach (_; 0 .. baselineLength)
+ static foreach (i, R; Ranges)
+ {
+ static if (!hasLength!R)
+ {
+ // All must be non-empty
+ if (rs[i].empty)
+ return false;
+ rs[i].popFront;
+ }
+ }
+ static foreach (i, R; Ranges)
+ {
+ static if (!hasLength!R)
+ {
+ // All must be now empty
+ if (!rs[i].empty)
+ return false;
+ }
+ }
+ return true;
}
else
{
- for (; !r1.empty; r1.popFront, r2.popFront)
- {
- if (r2.empty)
- return false;
- }
- return r2.empty;
+ // All have unknown length, iterate in lockstep
+ for (;;)
+ static foreach (i, r; rs)
+ {
+ if (r.empty)
+ {
+ // One is empty, so all must be empty
+ static if (i != 0)
+ {
+ return false;
+ }
+ else
+ {
+ static foreach (j, r1; rs[1 .. $])
+ if (!r1.empty)
+ return false;
+ return true;
+ }
+ }
+ r.popFront;
+ }
}
}
@@ -1976,34 +2124,42 @@ if (isInputRange!Range1 && isInputRange!Range2)
@safe nothrow pure unittest
{
assert(isSameLength([1, 2, 3], [4, 5, 6]));
+ assert(isSameLength([1, 2, 3], [4, 5, 6], [7, 8, 9]));
assert(isSameLength([0.3, 90.4, 23.7, 119.2], [42.6, 23.6, 95.5, 6.3]));
assert(isSameLength("abc", "xyz"));
+ assert(isSameLength("abc", "xyz", [1, 2, 3]));
int[] a;
int[] b;
assert(isSameLength(a, b));
+ assert(isSameLength(a, b, a, a, b, b, b));
assert(!isSameLength([1, 2, 3], [4, 5]));
+ assert(!isSameLength([1, 2, 3], [4, 5, 6], [7, 8]));
assert(!isSameLength([0.3, 90.4, 23.7], [42.6, 23.6, 95.5, 6.3]));
assert(!isSameLength("abcd", "xyz"));
+ assert(!isSameLength("abcd", "xyz", "123"));
+ assert(!isSameLength("abcd", "xyz", "1234"));
}
// Test CTFE
@safe @nogc pure @betterC unittest
{
- enum result1 = isSameLength([1, 2, 3], [4, 5, 6]);
- static assert(result1);
-
- enum result2 = isSameLength([0.3, 90.4, 23.7], [42.6, 23.6, 95.5, 6.3]);
- static assert(!result2);
+ static assert(isSameLength([1, 2, 3], [4, 5, 6]));
+ static assert(isSameLength([1, 2, 3], [4, 5, 6], [7, 8, 9]));
+ static assert(!isSameLength([0.3, 90.4, 23.7], [42.6, 23.6, 95.5, 6.3]));
+ static assert(!isSameLength([1], [0.3, 90.4], [42]));
}
@safe @nogc pure unittest
{
import std.range : only;
assert(isSameLength(only(1, 2, 3), only(4, 5, 6)));
+ assert(isSameLength(only(1, 2, 3), only(4, 5, 6), only(7, 8, 9)));
assert(isSameLength(only(0.3, 90.4, 23.7, 119.2), only(42.6, 23.6, 95.5, 6.3)));
assert(!isSameLength(only(1, 3, 3), only(4, 5)));
+ assert(!isSameLength(only(1, 3, 3), only(1, 3, 3), only(4, 5)));
+ assert(!isSameLength(only(1, 3, 3), only(4, 5), only(1, 3, 3)));
}
@safe nothrow pure unittest
@@ -2033,6 +2189,15 @@ if (isInputRange!Range1 && isInputRange!Range2)
DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Input) r11;
auto r12 = new ReferenceInputRange!int([1, 2, 3, 4, 5, 6, 7, 8]);
assert(!isSameLength(r11, r12));
+
+ import std.algorithm.iteration : filter;
+
+ assert(isSameLength(filter!"a >= 1"([1, 2, 3]), [4, 5, 6]));
+ assert(!isSameLength(filter!"a > 1"([1, 2, 3]), [4, 5, 6]));
+
+ assert(isSameLength(filter!"a > 1"([1, 2, 3]), filter!"a > 4"([4, 5, 6])));
+ assert(isSameLength(filter!"a > 1"([1, 2, 3]),
+ filter!"a > 4"([4, 5, 6]), filter!"a >= 5"([4, 5, 6])));
}
// Still functional but not documented anymore.
diff --git a/libphobos/src/std/algorithm/iteration.d b/libphobos/src/std/algorithm/iteration.d
index c3e8487..9e728e4 100644
--- a/libphobos/src/std/algorithm/iteration.d
+++ b/libphobos/src/std/algorithm/iteration.d
@@ -77,51 +77,6 @@ import std.range.primitives;
import std.traits;
import std.typecons : Flag, Yes, No;
-private template aggregate(fun...)
-if (fun.length >= 1)
-{
- /* --Intentionally not ddoc--
- * Aggregates elements in each subrange of the given range of ranges using
- * the given aggregating function(s).
- * Params:
- * fun = One or more aggregating functions (binary functions that return a
- * single _aggregate value of their arguments).
- * ror = A range of ranges to be aggregated.
- *
- * Returns:
- * A range representing the aggregated value(s) of each subrange
- * of the original range. If only one aggregating function is specified,
- * each element will be the aggregated value itself; if multiple functions
- * are specified, each element will be a tuple of the aggregated values of
- * each respective function.
- */
- auto aggregate(RoR)(RoR ror)
- if (isInputRange!RoR && isIterable!(ElementType!RoR))
- {
- return ror.map!(reduce!fun);
- }
-
- @safe unittest
- {
- import std.algorithm.comparison : equal, max, min;
-
- auto data = [[4, 2, 1, 3], [4, 9, -1, 3, 2], [3]];
-
- // Single aggregating function
- auto agg1 = data.aggregate!max;
- assert(agg1.equal([4, 9, 3]));
-
- // Multiple aggregating functions
- import std.typecons : tuple;
- auto agg2 = data.aggregate!(max, min);
- assert(agg2.equal([
- tuple(4, 1),
- tuple(9, -1),
- tuple(3, 3)
- ]));
- }
-}
-
/++
`cache` eagerly evaluates $(REF_ALTTEXT front, front, std,range,primitives) of `range`
on each construction or call to $(REF_ALTTEXT popFront, popFront, std,range,primitives),
@@ -2029,23 +1984,39 @@ private struct ChunkByGroup(alias eq, Range, bool eqEquivalenceAssured)
}
private Range current;
- private RefCounted!(OuterRange) mothership;
+ // using union prevents RefCounted destructor from propagating @system to
+ // user code
+ union { private RefCounted!(OuterRange) mothership; }
+ private @trusted ref cargo() { return mothership.refCountedPayload; }
- this(RefCounted!(OuterRange) origin)
+ private this(ref RefCounted!(OuterRange) origin)
{
- groupNum = origin.groupNum;
- current = origin.current.save;
+ () @trusted { mothership = origin; }();
+ groupNum = cargo.groupNum;
+ current = cargo.current.save;
assert(!current.empty, "Passed range 'r' must not be empty");
+
static if (eqEquivalenceAssured)
{
- start = origin.current.save;
+ start = cargo.current.save;
// Check for reflexivity.
assert(eq(start.front, current.front),
"predicate is not reflexive");
}
+ }
+
+ // Cannot be a copy constructor due to issue 22239
+ this(this) @trusted
+ {
+ import core.lifetime : emplace;
+ // since mothership has to be in a union, we have to manually trigger
+ // an increment to the reference count.
+ auto temp = mothership;
+ mothership = temp;
- mothership = origin;
+ // prevents the reference count from falling back with brute force
+ emplace(&temp);
}
@property bool empty() { return groupNum == size_t.max; }
@@ -2073,14 +2044,14 @@ private struct ChunkByGroup(alias eq, Range, bool eqEquivalenceAssured)
if (nowEmpty)
{
- if (groupNum == mothership.groupNum)
+ if (groupNum == cargo.groupNum)
{
// If parent range hasn't moved on yet, help it along by
// saving location of start of next Group.
- mothership.next = current.save;
+ cargo.next = current.save;
static if (!eqEquivalenceAssured)
{
- mothership.nextUpdated = true;
+ cargo.nextUpdated = true;
}
}
@@ -2094,6 +2065,11 @@ private struct ChunkByGroup(alias eq, Range, bool eqEquivalenceAssured)
copy.current = current.save;
return copy;
}
+
+ @trusted ~this()
+ {
+ mothership.destroy;
+ }
}
private enum GroupingOpType{binaryEquivalent, binaryAny, unary}
@@ -2110,23 +2086,48 @@ if (isForwardRange!Range)
static assert(isForwardRange!InnerRange);
- private RefCounted!OuterRange impl;
+ // using union prevents RefCounted destructor from propagating @system to
+ // user code
+ union { private RefCounted!OuterRange _impl; }
+ private @trusted ref impl() { return _impl; }
+ private @trusted ref implPL() { return _impl.refCountedPayload; }
this(Range r)
{
- static if (eqEquivalenceAssured)
+ import core.lifetime : move;
+
+ auto savedR = r.save;
+
+ static if (eqEquivalenceAssured) () @trusted
{
- impl = RefCounted!OuterRange(0, r, r.save);
- }
- else impl = RefCounted!OuterRange(0, r, r.save, false);
+ _impl = RefCounted!OuterRange(0, r, savedR.move);
+ }();
+ else () @trusted
+ {
+ _impl = RefCounted!OuterRange(0, r, savedR.move, false);
+ }();
+ }
+
+ // Cannot be a copy constructor due to issue 22239
+ this(this) @trusted
+ {
+ import core.lifetime : emplace;
+ // since _impl has to be in a union, we have to manually trigger
+ // an increment to the reference count.
+ auto temp = _impl;
+ _impl = temp;
+
+ // prevents the reference count from falling back with brute force
+ emplace(&temp);
}
- @property bool empty() { return impl.current.empty; }
+ @property bool empty() { return implPL.current.empty; }
static if (opType == GroupingOpType.unary) @property auto front()
{
import std.typecons : tuple;
- return tuple(unaryFun!pred(impl.current.front), InnerRange(impl));
+
+ return tuple(unaryFun!pred(implPL.current.front), InnerRange(impl));
}
else @property auto front()
{
@@ -2138,48 +2139,53 @@ if (isForwardRange!Range)
// Scan for next group. If we're lucky, one of our Groups would have
// already set .next to the start of the next group, in which case the
// loop is skipped.
- while (!impl.next.empty && eq(impl.current.front, impl.next.front))
+ while (!implPL.next.empty && eq(implPL.current.front, implPL.next.front))
{
- impl.next.popFront();
+ implPL.next.popFront();
}
- impl.current = impl.next.save;
+ implPL.current = implPL.next.save;
// Indicate to any remaining Groups that we have moved on.
- impl.groupNum++;
+ implPL.groupNum++;
}
else void popFront()
{
- if (impl.nextUpdated)
+ if (implPL.nextUpdated)
{
- impl.current = impl.next.save;
+ implPL.current = implPL.next.save;
}
else while (true)
{
- auto prevElement = impl.current.front;
- impl.current.popFront();
- if (impl.current.empty) break;
- if (!eq(prevElement, impl.current.front)) break;
+ auto prevElement = implPL.current.front;
+ implPL.current.popFront();
+ if (implPL.current.empty) break;
+ if (!eq(prevElement, implPL.current.front)) break;
}
- impl.nextUpdated = false;
+ implPL.nextUpdated = false;
// Indicate to any remaining Groups that we have moved on.
- impl.groupNum++;
+ implPL.groupNum++;
}
@property auto save()
{
// Note: the new copy of the range will be detached from any existing
// satellite Groups, and will not benefit from the .next acceleration.
- return typeof(this)(impl.current.save);
+ return typeof(this)(implPL.current.save);
}
static assert(isForwardRange!(typeof(this)), typeof(this).stringof
~ " must be a forward range");
+
+ @trusted ~this()
+ {
+ _impl.destroy;
+ }
}
//Test for https://issues.dlang.org/show_bug.cgi?id=14909
-@system unittest
+@safe unittest
{
import std.algorithm.comparison : equal;
import std.typecons : tuple;
@@ -2192,8 +2198,36 @@ if (isForwardRange!Range)
assert(u.equal!equal([[1],[2],[3]]));
}
+//Testing inferring @system correctly
+@safe unittest
+{
+ struct DeadlySave
+ {
+ int front;
+ @safe void popFront(){front++;}
+ @safe bool empty(){return front >= 5;}
+ @system auto save(){return this;}
+ }
+
+ auto test1()
+ {
+ DeadlySave src;
+ return src.walkLength;
+
+ }
+
+ auto test2()
+ {
+ DeadlySave src;
+ return src.chunkBy!((a,b) => a % 2 == b % 2).walkLength;
+ }
+
+ static assert(isSafe!test1);
+ static assert(!isSafe!test2);
+}
+
//Test for https://issues.dlang.org/show_bug.cgi?id=18751
-@system unittest
+@safe unittest
{
import std.algorithm.comparison : equal;
@@ -2205,7 +2239,7 @@ if (isForwardRange!Range)
}
//Additional test for fix for issues 14909 and 18751
-@system unittest
+@safe unittest
{
import std.algorithm.comparison : equal;
auto v = [2,4,8,3,6,9,1,5,7];
@@ -2329,7 +2363,7 @@ if (isInputRange!Range)
}
/// Showing usage with binary predicate:
-/*FIXME: @safe*/ @system unittest
+@safe unittest
{
import std.algorithm.comparison : equal;
@@ -2356,7 +2390,7 @@ if (isInputRange!Range)
}
/// Showing usage with unary predicate:
-/* FIXME: pure @safe nothrow*/ @system unittest
+/* FIXME: pure nothrow*/ @safe unittest
{
import std.algorithm.comparison : equal;
import std.range.primitives;
@@ -2755,7 +2789,7 @@ if (isInputRange!Range)
// https://issues.dlang.org/show_bug.cgi?id=13805
-@system unittest
+@safe unittest
{
[""].map!((s) => s).chunkBy!((x, y) => true);
}
@@ -2790,9 +2824,8 @@ if (isForwardRange!Range)
return ChunkByImpl!(not!pred, not!pred, GroupingOpType.binaryAny, Range)(r);
}
-//FIXME: these should be @safe
///
-nothrow pure @system unittest
+nothrow pure @safe unittest
{
import std.algorithm.comparison : equal;
import std.range : dropExactly;
@@ -2816,7 +2849,7 @@ nothrow pure @system unittest
}
//ensure we don't iterate the underlying range twice
-nothrow @system unittest
+nothrow @safe unittest
{
import std.algorithm.comparison : equal;
import std.math.algebraic : abs;
@@ -2827,7 +2860,7 @@ nothrow @system unittest
static int popfrontsSoFar;
auto front(){return elements[0];}
- nothrow void popFront()
+ nothrow @safe void popFront()
{ popfrontsSoFar++;
elements = elements[1 .. $];
}
@@ -2851,7 +2884,7 @@ nothrow @system unittest
}
// Issue 13595
-@system unittest
+@safe unittest
{
import std.algorithm.comparison : equal;
auto r = [1, 2, 3, 4, 5, 6, 7, 8, 9].splitWhen!((x, y) => ((x*y) % 3) > 0);
@@ -2863,7 +2896,7 @@ nothrow @system unittest
]));
}
-nothrow pure @system unittest
+nothrow pure @safe unittest
{
// Grouping by maximum adjacent difference:
import std.math.algebraic : abs;
@@ -3654,10 +3687,18 @@ if (isInputRange!RoR && isInputRange!(ElementType!RoR))
{
@property auto save()
{
- auto r = Result(_items.save, _current.save);
+ // the null check is important if it is a class range, since null.save will segfault; issue #22359
+ // could not just compare x is y here without static if due to a compiler assertion failure
+ static if (is(typeof(null) : typeof(_current)))
+ auto r = Result(_items.save, _current is null ? null : _current.save);
+ else
+ auto r = Result(_items.save, _current.save);
static if (isBidirectional)
{
- r._currentBack = _currentBack.save;
+ static if (is(typeof(null) : typeof(_currentBack)))
+ r._currentBack = _currentBack is null ? null : _currentBack.save;
+ else
+ r._currentBack = _currentBack.save;
r.reachedFinalElement = reachedFinalElement;
}
return r;
@@ -3838,6 +3879,26 @@ if (isInputRange!RoR && isInputRange!(ElementType!RoR))
static assert(isForwardRange!(typeof(joiner([""]))));
}
+@system unittest
+{
+ // this test is system because the virtual interface call to save
+ // is flexible and thus cannot be inferred safe automatically
+
+ // https://issues.dlang.org/show_bug.cgi?id=22359
+ import std.range;
+ ForwardRange!int bug(int[][] r)
+ {
+ import std.range : inputRangeObject;
+ import std.algorithm.iteration : map, joiner;
+
+ auto range = inputRangeObject(r);
+
+ return range.map!(a =>inputRangeObject(a)).joiner.inputRangeObject;
+ }
+ auto f = bug([[]]);
+ f.save(); // should not segfault
+}
+
@safe unittest
{
// Initial version of PR #6115 caused a compilation failure for
diff --git a/libphobos/src/std/algorithm/mutation.d b/libphobos/src/std/algorithm/mutation.d
index 88191bb..07cbb9b 100644
--- a/libphobos/src/std/algorithm/mutation.d
+++ b/libphobos/src/std/algorithm/mutation.d
@@ -1427,7 +1427,7 @@ private void moveEmplaceImpl(T)(ref scope T target, ref return scope T source)
{
import std.exception : doesPointTo;
assert(!(doesPointTo(source, source) && !hasElaborateMove!T),
- "Cannot move object with internal pointer unless `opPostMove` is defined.");
+ "Cannot move object of type " ~ T.stringof ~ " with internal pointer unless `opPostMove` is defined.");
}
static if (is(T == struct))
diff --git a/libphobos/src/std/algorithm/searching.d b/libphobos/src/std/algorithm/searching.d
index d6d02ba..9635206 100644
--- a/libphobos/src/std/algorithm/searching.d
+++ b/libphobos/src/std/algorithm/searching.d
@@ -122,7 +122,9 @@ template all(alias pred = "a")
if (isInputRange!Range)
{
static assert(is(typeof(unaryFun!pred(range.front))),
- "`" ~ pred.stringof[1..$-1] ~ "` isn't a unary predicate function for range.front");
+ "`" ~ (isSomeString!(typeof(pred))
+ ? pred.stringof[1..$-1] : pred.stringof)
+ ~ "` isn't a unary predicate function for range.front");
import std.functional : not;
return find!(not!(unaryFun!pred))(range).empty;
@@ -3572,6 +3574,8 @@ Params:
r = range from which the minimal element will be selected
seed = custom seed to use as initial element
+Precondition: If a seed is not given, `r` must not be empty.
+
Returns: The minimal element of the passed-in range.
Note:
@@ -3723,6 +3727,8 @@ Params:
r = range from which the maximum element will be selected
seed = custom seed to use as initial element
+Precondition: If a seed is not given, `r` must not be empty.
+
Returns: The maximal element of the passed-in range.
Note:
diff --git a/libphobos/src/std/algorithm/sorting.d b/libphobos/src/std/algorithm/sorting.d
index dbfe37f..f2877cc 100644
--- a/libphobos/src/std/algorithm/sorting.d
+++ b/libphobos/src/std/algorithm/sorting.d
@@ -2209,7 +2209,7 @@ package(std) template HeapOps(alias less, Range)
~ " RandomAccessRange");
static assert(hasLength!Range, Range.stringof ~ " must have length");
static assert(hasSwappableElements!Range || hasAssignableElements!Range,
- Range.stringof ~ " must have swappable of assignable Elements");
+ Range.stringof ~ " must have swappable or assignable Elements");
alias lessFun = binaryFun!less;
@@ -2329,8 +2329,8 @@ private template TimSortImpl(alias pred, R)
~ " RandomAccessRange");
static assert(hasLength!R, R.stringof ~ " must have a length");
static assert(hasSlicing!R, R.stringof ~ " must support slicing");
- static assert(hasAssignableElements!R, R.stringof ~ " must support"
- ~ " assigning elements");
+ static assert(hasAssignableElements!R, R.stringof ~ " must have"
+ ~ " assignable elements");
alias T = ElementType!R;
@@ -3571,7 +3571,7 @@ void topNImpl(alias less, R)(R r, size_t n, ref bool useSampling)
private size_t topNPartition(alias lp, R)(R r, size_t n, bool useSampling)
{
import std.format : format;
- assert(r.length >= 9 && n < r.length, "length must be longer than 9"
+ assert(r.length >= 9 && n < r.length, "length must be longer than 8"
~ " and n must be less than r.length");
immutable ninth = r.length / 9;
auto pivot = ninth / 2;
@@ -4220,8 +4220,8 @@ if (isRandomAccessRange!Range && hasLength!Range &&
Indexes.length >= 2 && Indexes.length <= 5 &&
allSatisfy!(isUnsigned, Indexes))
{
- assert(r.length >= Indexes.length, "r.length must be greater equal to"
- ~ " Indexes.length");
+ assert(r.length >= Indexes.length, "r.length must be greater than or"
+ ~ " equal to Indexes.length");
import std.functional : binaryFun;
alias lt = binaryFun!less;
enum k = Indexes.length;