diff options
author | Iain Buclaw <ibuclaw@gdcproject.org> | 2021-12-05 17:11:12 +0100 |
---|---|---|
committer | Iain Buclaw <ibuclaw@gdcproject.org> | 2021-12-09 00:58:58 +0100 |
commit | 0fb57034770aa20adced4d176f34ca611c2945bf (patch) | |
tree | 1f5735c8b4f25aa4a290e5ae8124713c24f98359 /libphobos/src/std/algorithm | |
parent | c15aa46cca0649b68613d3292cf71c7cc57ef78f (diff) | |
download | gcc-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.d | 401 | ||||
-rw-r--r-- | libphobos/src/std/algorithm/iteration.d | 237 | ||||
-rw-r--r-- | libphobos/src/std/algorithm/mutation.d | 2 | ||||
-rw-r--r-- | libphobos/src/std/algorithm/searching.d | 8 | ||||
-rw-r--r-- | libphobos/src/std/algorithm/sorting.d | 12 |
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; |