From 17ae8fc9be5a54445de9b344f8a809c6a3c36d6b Mon Sep 17 00:00:00 2001 From: Steven Schveighoffer Date: Thu, 6 Aug 2020 20:07:57 -0400 Subject: [PATCH] Make it so std.utf can be tested with no autodecoding. --- posix.mak | 2 +- std/algorithm/comparison.d | 48 ++++++++++--- std/array.d | 72 +++++++++++-------- std/range/primitives.d | 15 +++- std/utf.d | 144 +++++++++++++++++++++++-------------- 5 files changed, 184 insertions(+), 97 deletions(-) diff --git a/posix.mak b/posix.mak index 9f1eca497..e17da4fc0 100644 --- a/posix.mak +++ b/posix.mak @@ -241,7 +241,7 @@ STD_MODULES=$(call P2MODULES,$(STD_PACKAGES)) # NoAutodecode test modules. # List all modules whose unittests are known to work without autodecode enabled. -NO_AUTODECODE_MODULES= +NO_AUTODECODE_MODULES= std/utf # Other D modules that aren't under std/ EXTRA_MODULES_COMMON := $(addprefix etc/c/,curl odbc/sql odbc/sqlext \ diff --git a/std/algorithm/comparison.d b/std/algorithm/comparison.d index ed29d06a5..d3b812672 100644 --- a/std/algorithm/comparison.d +++ b/std/algorithm/comparison.d @@ -887,12 +887,23 @@ template equal(alias pred = "a == b") enum hasFixedLength(T) = hasLength!T || isNarrowString!T; + // 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(R1, R2) = + isSomeChar!(ElementEncodingType!R1) && isSomeChar!(ElementEncodingType!R2) && + (ElementEncodingType!R1).sizeof != (ElementEncodingType!R2).sizeof; + /++ 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`. + 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 + accidentally integer-promoting units. + Params: r1 = The first range to be compared. r2 = The second range to be compared. @@ -902,7 +913,8 @@ template equal(alias pred = "a == b") for element, according to binary predicate `pred`. +/ bool equal(Range1, Range2)(Range1 r1, Range2 r2) - if (isInputRange!Range1 && isInputRange!Range2 && + if (!useCodePoint!(Range1, Range2) && + isInputRange!Range1 && isInputRange!Range2 && is(typeof(binaryFun!pred(r1.front, r2.front)))) { static assert(!(isInfinite!Range1 && isInfinite!Range2), @@ -928,7 +940,7 @@ template equal(alias pred = "a == b") // can be avoided if they have the same ElementEncodingType else static if (is(typeof(pred) == string) && pred == "a == b" && isAutodecodableString!Range1 != isAutodecodableString!Range2 && - is(ElementEncodingType!Range1 == ElementEncodingType!Range2)) + is(immutable ElementEncodingType!Range1 == immutable ElementEncodingType!Range2)) { import std.utf : byCodeUnit; @@ -968,6 +980,14 @@ template equal(alias pred = "a == b") return r2.empty; } } + + /// ditto + bool equal(Range1, Range2)(Range1 r1, Range2 r2) + if (useCodePoint!(Range1, Range2)) + { + import std.utf : byDchar; + return equal(r1.byDchar, r2.byDchar); + } } /// @@ -1073,20 +1093,28 @@ range of range (of range...) comparisons. @safe @nogc pure unittest { - import std.utf : byChar, byDchar; + import std.utf : byChar, byDchar, byWchar; assert(equal("æøå".byChar, "æøå")); + assert(equal("æøå".byChar, "æøå"w)); + assert(equal("æøå".byChar, "æøå"d)); assert(equal("æøå", "æøå".byChar)); - assert(equal("æøå".byDchar, "æøå"d)); - assert(equal("æøå"d, "æøå".byDchar)); -} - -@safe pure unittest -{ - import std.utf : byWchar; + assert(equal("æøå"w, "æøå".byChar)); + assert(equal("æøå"d, "æøå".byChar)); + assert(equal("æøå".byWchar, "æøå")); assert(equal("æøå".byWchar, "æøå"w)); + assert(equal("æøå".byWchar, "æøå"d)); + assert(equal("æøå", "æøå".byWchar)); assert(equal("æøå"w, "æøå".byWchar)); + assert(equal("æøå"d, "æøå".byWchar)); + + assert(equal("æøå".byDchar, "æøå")); + assert(equal("æøå".byDchar, "æøå"w)); + assert(equal("æøå".byDchar, "æøå"d)); + assert(equal("æøå", "æøå".byDchar)); + assert(equal("æøå"w, "æøå".byDchar)); + assert(equal("æøå"d, "æøå".byDchar)); } @safe @nogc pure unittest diff --git a/std/array.d b/std/array.d index f7d137ff9..a65b94cdf 100644 --- a/std/array.d +++ b/std/array.d @@ -92,7 +92,9 @@ public import std.range.primitives : save, empty, popFront, popBack, front, back * Allocates an array and initializes it with copies of the elements * of range `r`. * - * Narrow strings are handled as a special case in an overload. + * Narrow strings are handled as follows: + * - If autodecoding is turned on (default), then they are handled as a separate overload. + * - If autodecoding is turned off, then this is equivalent to duplicating the array. * * Params: * r = range (or aggregate with `opApply` function) whose elements are copied into the allocated array @@ -100,7 +102,7 @@ public import std.range.primitives : save, empty, popFront, popBack, front, back * allocated and initialized array */ ForeachType!Range[] array(Range)(Range r) -if (isIterable!Range && !isNarrowString!Range && !isInfinite!Range) +if (isIterable!Range && !isAutodecodableString!Range && !isInfinite!Range) { if (__ctfe) { @@ -145,7 +147,7 @@ if (isIterable!Range && !isNarrowString!Range && !isInfinite!Range) /// ditto ForeachType!(PointerTarget!Range)[] array(Range)(Range r) -if (isPointer!Range && isIterable!(PointerTarget!Range) && !isNarrowString!Range && !isInfinite!Range) +if (isPointer!Range && isIterable!(PointerTarget!Range) && !isAutodecodableString!Range && !isInfinite!Range) { return array(*r); } @@ -252,8 +254,11 @@ version (StdUnittest) } /** -Convert a narrow string to an array type that fully supports random access. -This is handled as a special case and always returns an array of `dchar` +Convert a narrow autodecoding string to an array type that fully supports +random access. This is handled as a special case and always returns an array +of `dchar` + +NOTE: This function is never used when autodecoding is turned off. Params: str = `isNarrowString` to be converted to an array of `dchar` @@ -262,7 +267,7 @@ Returns: the input. */ CopyTypeQualifiers!(ElementType!String,dchar)[] array(String)(scope String str) -if (isNarrowString!String) +if (isAutodecodableString!String) { import std.utf : toUTF32; auto temp = str.toUTF32; @@ -276,10 +281,19 @@ if (isNarrowString!String) @safe unittest { import std.range.primitives : isRandomAccessRange; + import std.traits : isAutodecodableString; - assert("Hello D".array == "Hello D"d); + // note that if autodecoding is turned off, `array` will not transcode these. + static if (isAutodecodableString!string) + assert("Hello D".array == "Hello D"d); + else + assert("Hello D".array == "Hello D"); + + static if (isAutodecodableString!wstring) + assert("Hello D"w.array == "Hello D"d); + else + assert("Hello D"w.array == "Hello D"w); - assert("Hello D"w.array == "Hello D"d); static assert(isRandomAccessRange!dstring == true); } @@ -339,8 +353,11 @@ if (isNarrowString!String) assert(e == f); assert(array(OpApply.init) == [0,1,2,3,4,5,6,7,8,9]); - assert(array("ABC") == "ABC"d); - assert(array("ABC".dup) == "ABC"d.dup); + static if (isAutodecodableString!string) + { + assert(array("ABC") == "ABC"d); + assert(array("ABC".dup) == "ABC"d.dup); + } } // https://issues.dlang.org/show_bug.cgi?id=8233 @@ -597,11 +614,11 @@ if (isInputRange!Values && isInputRange!Keys) auto r = "abcde".enumerate.filter!(a => a.index == 2); auto a = assocArray(r.map!(a => a.value), r.map!(a => a.index)); - assert(is(typeof(a) == size_t[dchar])); static if (autodecodeStrings) alias achar = dchar; else alias achar = immutable(char); + static assert(is(typeof(a) == size_t[achar])); assert(a == [achar('c'): size_t(2)]); } @@ -1265,7 +1282,7 @@ if (isSomeString!(T[]) && allSatisfy!(isCharOrStringOrDcharRange, U)) static if (is(Unqual!T == T) && allSatisfy!(isInputRangeWithLengthOrConvertible!dchar, U)) { - import std.utf : codeLength; + import std.utf : codeLength, byDchar; // mutable, can do in place //helper function: re-encode dchar to Ts and store at *ptr static T* putDChar(T* ptr, dchar ch) @@ -1330,7 +1347,7 @@ if (isSomeString!(T[]) && allSatisfy!(isCharOrStringOrDcharRange, U)) } else { - foreach (dchar ch; stuff[i]) + foreach (ch; stuff[i].byDchar) ptr = putDChar(ptr, ch); } } @@ -1942,7 +1959,9 @@ ElementEncodingType!(ElementType!RoR)[] join(RoR, R)(RoR ror, scope R sep) if (isInputRange!RoR && isInputRange!(Unqual!(ElementType!RoR)) && isInputRange!R && - is(immutable ElementType!(ElementType!RoR) == immutable ElementType!R)) + (is(immutable ElementType!(ElementType!RoR) == immutable ElementType!R) || + (isSomeChar!(ElementType!(ElementType!RoR)) && isSomeChar!(ElementType!R)) + )) { alias RetType = typeof(return); alias RetTypeElement = Unqual!(ElementEncodingType!RetType); @@ -2019,7 +2038,9 @@ if (isInputRange!RoR && ElementEncodingType!(ElementType!RoR)[] join(RoR, E)(RoR ror, scope E sep) if (isInputRange!RoR && isInputRange!(Unqual!(ElementType!RoR)) && - is(E : ElementType!(ElementType!RoR))) + ((is(E : ElementType!(ElementType!RoR))) || + (!autodecodeStrings && isSomeChar!(ElementType!(ElementType!RoR)) && + isSomeChar!E))) { alias RetType = typeof(return); alias RetTypeElement = Unqual!(ElementEncodingType!RetType); @@ -2175,21 +2196,12 @@ if (isInputRange!RoR && auto arr2 = "Здравствуй Мир Unicode".to!(T); auto arr = ["Здравствуй", "Мир", "Unicode"].to!(T[]); assert(join(arr) == "ЗдравствуйМирUnicode"); - static if (autodecodeStrings) - { - static foreach (S; AliasSeq!(char,wchar,dchar)) - {{ - auto jarr = arr.join(to!S(' ')); - static assert(is(typeof(jarr) == T)); - assert(jarr == arr2); - }} - } - else - { - // Turning off autodecode means the join() won't - // just convert arr[] to dchar, so mixing char - // types fails to compile. - } + static foreach (S; AliasSeq!(char,wchar,dchar)) + {{ + auto jarr = arr.join(to!S(' ')); + static assert(is(typeof(jarr) == T)); + assert(jarr == arr2); + }} static foreach (S; AliasSeq!(string,wstring,dstring)) {{ auto jarr = arr.join(to!S(" ")); diff --git a/std/range/primitives.d b/std/range/primitives.d index e7bffd990..d0071ecbd 100644 --- a/std/range/primitives.d +++ b/std/range/primitives.d @@ -2406,10 +2406,19 @@ if (isAutodecodableString!(T[]) && !isAggregateType!(T[])) } /** -Autodecoding is enabled if this is set to true. +EXPERIMENTAL: to try out removing autodecoding, set the version +`NoAutodecodeStrings`. Most things are expected to fail with this version +currently. */ - -enum autodecodeStrings = true; +version (NoAutodecodeStrings) +{ + enum autodecodeStrings = false; +} +else +{ + /// + enum autodecodeStrings = true; +} /** Implements the range interface primitive `front` for built-in diff --git a/std/utf.d b/std/utf.d index 50a4966ed..ea141760a 100644 --- a/std/utf.d +++ b/std/utf.d @@ -66,7 +66,7 @@ import core.exception : UnicodeException; import std.meta : AliasSeq; import std.range.primitives; import std.traits : isAutodecodableString, isPointer, isSomeChar, - isSomeString, isStaticArray, Unqual; + isSomeString, isStaticArray, Unqual, isConvertibleToString; import std.typecons : Flag, Yes, No; @@ -463,7 +463,8 @@ if (is(S : const wchar[])) /// Ditto uint stride(S)(auto ref S str) -if (isInputRange!S && is(immutable ElementType!S == immutable wchar)) +if (isInputRange!S && is(immutable ElementType!S == immutable wchar) && + !is(S : const wchar[])) { assert(!str.empty, "UTF-16 sequence is empty"); immutable uint u = str.front; @@ -1873,11 +1874,12 @@ version (StdUnittest) private void testDecode(R)(R range, import core.exception : AssertError; import std.exception : enforce; import std.string : format; + import std.traits : isNarrowString; static if (hasLength!R) immutable lenBefore = range.length; - static if (isRandomAccessRange!R) + static if (isRandomAccessRange!R && !isNarrowString!R) { { immutable result = decode(range, index); @@ -2105,11 +2107,10 @@ version (StdUnittest) private void testBadDecodeBack(R)(R range, size_t line = _ @system unittest { - import std.conv : to; import std.exception; assertCTFEable!( { - foreach (S; AliasSeq!(to!wstring, InputCU!wchar, RandomCU!wchar, + foreach (S; AliasSeq!((wstring s) => s, InputCU!wchar, RandomCU!wchar, (wstring s) => new RefBidirCU!wchar(s), (wstring s) => new RefRandomCU!wchar(s))) { @@ -2144,7 +2145,7 @@ version (StdUnittest) private void testBadDecodeBack(R)(R range, size_t line = _ } } - foreach (S; AliasSeq!(to!wstring, RandomCU!wchar, (wstring s) => new RefRandomCU!wchar(s))) + foreach (S; AliasSeq!((wchar[] s) => s.idup, RandomCU!wchar, (wstring s) => new RefRandomCU!wchar(s))) { auto str = S([cast(wchar) 0xD800, cast(wchar) 0xDC00, cast(wchar) 0x1400, @@ -2161,11 +2162,10 @@ version (StdUnittest) private void testBadDecodeBack(R)(R range, size_t line = _ @system unittest { - import std.conv : to; import std.exception; assertCTFEable!( { - foreach (S; AliasSeq!(to!dstring, RandomCU!dchar, InputCU!dchar, + foreach (S; AliasSeq!((dstring s) => s, RandomCU!dchar, InputCU!dchar, (dstring s) => new RefBidirCU!dchar(s), (dstring s) => new RefRandomCU!dchar(s))) { @@ -2202,7 +2202,7 @@ version (StdUnittest) private void testBadDecodeBack(R)(R range, size_t line = _ } } - foreach (S; AliasSeq!(to!dstring, RandomCU!dchar, (dstring s) => new RefRandomCU!dchar(s))) + foreach (S; AliasSeq!((dchar[] s) => s.idup, RandomCU!dchar, (dstring s) => new RefRandomCU!dchar(s))) { auto str = S([cast(dchar) 0x10000, cast(dchar) 0x1400, cast(dchar) 0xB9DDE]); testDecode(str, 0, 0x10000, 1); @@ -2398,7 +2398,8 @@ size_t encode(UseReplacementDchar useReplacementDchar = No.useReplacementDchar)( assertThrown!UTFException(encode(buf, cast(dchar) 0x110000)); assert(encode!(Yes.useReplacementDchar)(buf, cast(dchar) 0x110000) == buf.stride); - assert(buf.front == replacementDchar); + enum replacementDcharString = "\uFFFD"; + assert(buf[0 .. replacementDcharString.length] == replacementDcharString); }); } @@ -2616,9 +2617,11 @@ void encode(UseReplacementDchar useReplacementDchar = No.useReplacementDchar)( assertThrown!UTFException(encode(buf, cast(dchar) 0xDFFF)); assertThrown!UTFException(encode(buf, cast(dchar) 0x110000)); - assert(buf.back != replacementDchar); + enum replacementDcharString = "\uFFFD"; + enum rdcslen = replacementDcharString.length; + assert(buf[$ - rdcslen .. $] != replacementDcharString); encode!(Yes.useReplacementDchar)(buf, cast(dchar) 0x110000); - assert(buf.back == replacementDchar); + assert(buf[$ - rdcslen .. $] == replacementDcharString); }); } @@ -2774,7 +2777,7 @@ if (isSomeChar!C) The number of code units in `input` when encoded to `C` +/ size_t codeLength(C, InputRange)(InputRange input) -if (isInputRange!InputRange && !isInfinite!InputRange && is(ElementType!InputRange : dchar)) +if (isInputRange!InputRange && !isInfinite!InputRange && isSomeChar!(ElementType!InputRange)) { alias EncType = Unqual!(ElementEncodingType!InputRange); static if (isSomeString!InputRange && is(EncType == C) && is(typeof(input.length))) @@ -2783,7 +2786,7 @@ if (isInputRange!InputRange && !isInfinite!InputRange && is(ElementType!InputRan { size_t total = 0; - foreach (dchar c; input) + foreach (c; input.byDchar) total += codeLength!C(c); return total; @@ -2793,20 +2796,19 @@ if (isInputRange!InputRange && !isInfinite!InputRange && is(ElementType!InputRan /// @safe unittest { - import std.conv : to; assert(codeLength!char("hello world") == - to!string("hello world").length); + "hello world".length); assert(codeLength!wchar("hello world") == - to!wstring("hello world").length); + "hello world"w.length); assert(codeLength!dchar("hello world") == - to!dstring("hello world").length); + "hello world"d.length); assert(codeLength!char(`プログラミング`) == - to!string(`プログラミング`).length); + `プログラミング`.length); assert(codeLength!wchar(`プログラミング`) == - to!wstring(`プログラミング`).length); + `プログラミング`w.length); assert(codeLength!dchar(`プログラミング`) == - to!dstring(`プログラミング`).length); + `プログラミング`d.length); string haystack = `Être sans la verité, ça, ce ne serait pas bien.`; wstring needle = `Être sans la verité`; @@ -2949,8 +2951,9 @@ if (isInputRange!S && !isInfinite!S && isSomeChar!(ElementEncodingType!S)) import std.algorithm.comparison : equal; import std.internal.test.dummyrange : ReferenceInputRange; - auto r1 = new ReferenceInputRange!dchar("Hellø"); - auto r2 = new ReferenceInputRange!dchar("𐐷"); + alias RT = ReferenceInputRange!(ElementType!(string)); + auto r1 = new RT("Hellø"); + auto r2 = new RT("𐐷"); assert(r1.toUTF8.equal(['H', 'e', 'l', 'l', 0xC3, 0xB8])); assert(r2.toUTF8.equal([0xF0, 0x90, 0x90, 0xB7])); @@ -2991,8 +2994,9 @@ if (isInputRange!S && !isInfinite!S && isSomeChar!(ElementEncodingType!S)) import std.algorithm.comparison : equal; import std.internal.test.dummyrange : ReferenceInputRange; - auto r1 = new ReferenceInputRange!dchar("𤭢"); - auto r2 = new ReferenceInputRange!dchar("𐐷"); + alias RT = ReferenceInputRange!(ElementType!(string)); + auto r1 = new RT("𤭢"); + auto r2 = new RT("𐐷"); assert(r1.toUTF16.equal([0xD852, 0xDF62])); assert(r2.toUTF16.equal([0xD801, 0xDC37])); @@ -3369,10 +3373,10 @@ if (isSomeChar!C) Throws: `UTFException` if `str` is not well-formed. +/ -size_t count(C)(const(C)[] str) @trusted pure nothrow @nogc +size_t count(C)(const(C)[] str) @safe pure nothrow @nogc if (isSomeChar!C) { - return walkLength(str); + return walkLength(str.byDchar); } /// @@ -3553,13 +3557,11 @@ enum dchar replacementDchar = '\uFFFD'; * $(REF byGrapheme, std,uni). */ auto byCodeUnit(R)(R r) -if (isAutodecodableString!R || - isInputRange!R && isSomeChar!(ElementEncodingType!R) || - (is(R : const dchar[]) && !isStaticArray!R)) +if ((isConvertibleToString!R && !isStaticArray!R) || + (isInputRange!R && isSomeChar!(ElementEncodingType!R))) { - import std.traits : isNarrowString, StringTypeOf; - static if (isNarrowString!R || - // This would be cleaner if we had a way to check whether a type + import std.traits : StringTypeOf; + static if (// This would be cleaner if we had a way to check whether a type // was a range without any implicit conversions. (isAutodecodableString!R && !__traits(hasMember, R, "empty") && !__traits(hasMember, R, "front") && !__traits(hasMember, R, "popFront"))) @@ -3590,8 +3592,9 @@ if (isAutodecodableString!R || return ByCodeUnitImpl(r); } - else static if (is(R : const dchar[]) && !__traits(hasMember, R, "empty") && - !__traits(hasMember, R, "front") && !__traits(hasMember, R, "popFront")) + else static if (!isInputRange!R || + (is(R : const dchar[]) && !__traits(hasMember, R, "empty") && + !__traits(hasMember, R, "front") && !__traits(hasMember, R, "popFront"))) { return cast(StringTypeOf!R) r; } @@ -3606,6 +3609,7 @@ if (isAutodecodableString!R || @safe unittest { import std.range.primitives; + import std.traits : isAutodecodableString; auto r = "Hello, World!".byCodeUnit(); static assert(hasLength!(typeof(r))); @@ -3613,14 +3617,27 @@ if (isAutodecodableString!R || static assert(isRandomAccessRange!(typeof(r))); static assert(is(ElementType!(typeof(r)) == immutable char)); - // contrast with the range capabilities of standard strings + // contrast with the range capabilities of standard strings (with or + // without autodecoding enabled). auto s = "Hello, World!"; static assert(isBidirectionalRange!(typeof(r))); - static assert(is(ElementType!(typeof(s)) == dchar)); - - static assert(!isRandomAccessRange!(typeof(s))); - static assert(!hasSlicing!(typeof(s))); - static assert(!hasLength!(typeof(s))); + static if (isAutodecodableString!(typeof(s))) + { + // with autodecoding enabled, strings are non-random-access ranges of + // dchar. + static assert(is(ElementType!(typeof(s)) == dchar)); + static assert(!isRandomAccessRange!(typeof(s))); + static assert(!hasSlicing!(typeof(s))); + static assert(!hasLength!(typeof(s))); + } + else + { + // without autodecoding, strings are normal arrays. + static assert(is(ElementType!(typeof(s)) == immutable char)); + static assert(isRandomAccessRange!(typeof(s))); + static assert(hasSlicing!(typeof(s))); + static assert(hasLength!(typeof(s))); + } } /// `byCodeUnit` does no Unicode decoding @@ -3641,12 +3658,16 @@ if (isAutodecodableString!R || { import std.algorithm.comparison : equal; import std.range : popFrontN; + import std.traits : isAutodecodableString; { auto range = byCodeUnit("hello world"); range.popFrontN(3); assert(equal(range.save, "lo world")); - string str = range.source; - assert(str == "lo world"); + static if (isAutodecodableString!string) // only enabled with autodecoding + { + string str = range.source; + assert(str == "lo world"); + } } // source only exists if the range was wrapped { @@ -3705,7 +3726,7 @@ if (isAutodecodableString!R || { auto bcu = "hello".byCodeUnit().byCodeUnit(); static assert(isForwardRange!(typeof(bcu))); - static assert(is(typeof(bcu) == struct)); + static assert(is(typeof(bcu) == struct) == isAutodecodableString!string); auto s = bcu.save; bcu.popFront(); assert(s.front == 'h'); @@ -3714,7 +3735,7 @@ if (isAutodecodableString!R || auto bcu = "hello".byCodeUnit(); static assert(hasSlicing!(typeof(bcu))); static assert(isBidirectionalRange!(typeof(bcu))); - static assert(is(typeof(bcu) == struct)); + static assert(is(typeof(bcu) == struct) == isAutodecodableString!string); static assert(is(typeof(bcu) == typeof(bcu.byCodeUnit()))); auto ret = bcu.retro; assert(ret.front == 'o'); @@ -3725,7 +3746,7 @@ if (isAutodecodableString!R || auto bcu = "κόσμε"w.byCodeUnit(); static assert(hasSlicing!(typeof(bcu))); static assert(isBidirectionalRange!(typeof(bcu))); - static assert(is(typeof(bcu) == struct)); + static assert(is(typeof(bcu) == struct) == isAutodecodableString!wstring); static assert(is(typeof(bcu) == typeof(bcu.byCodeUnit()))); auto ret = bcu.retro; assert(ret.front == 'ε'); @@ -3742,7 +3763,7 @@ if (isAutodecodableString!R || auto orig = Stringish("\U0010fff8 𐁊 foo 𐂓"); auto bcu = orig.byCodeUnit(); static assert(is(typeof(bcu) == struct)); - static assert(!is(typeof(bcu) == Stringish)); + static assert(!is(typeof(bcu) == Stringish) == isAutodecodableString!Stringish); static assert(is(typeof(bcu) == typeof(bcu.byCodeUnit()))); static assert(is(ElementType!(typeof(bcu)) == immutable char)); assert(bcu.front == cast(char) 244); @@ -3757,7 +3778,7 @@ if (isAutodecodableString!R || auto orig = WStringish("\U0010fff8 𐁊 foo 𐂓"w); auto bcu = orig.byCodeUnit(); static assert(is(typeof(bcu) == struct)); - static assert(!is(typeof(bcu) == WStringish)); + static assert(!is(typeof(bcu) == WStringish) == isAutodecodableString!WStringish); static assert(is(typeof(bcu) == typeof(bcu.byCodeUnit()))); static assert(is(ElementType!(typeof(bcu)) == immutable wchar)); assert(bcu.front == cast(wchar) 56319); @@ -3786,7 +3807,10 @@ if (isAutodecodableString!R || auto orig = FuncStringish("\U0010fff8 𐁊 foo 𐂓"); auto bcu = orig.byCodeUnit(); - static assert(is(typeof(bcu) == struct)); + static if (isAutodecodableString!FuncStringish) + static assert(is(typeof(bcu) == struct)); + else + static assert(is(typeof(bcu) == string)); static assert(!is(typeof(bcu) == FuncStringish)); static assert(is(typeof(bcu) == typeof(bcu.byCodeUnit()))); static assert(is(ElementType!(typeof(bcu)) == immutable char)); @@ -3903,7 +3927,10 @@ if (isAutodecodableString!R || auto orig = Enum.a; auto bcu = orig.byCodeUnit(); static assert(!is(typeof(bcu) == Enum)); - static assert(is(typeof(bcu) == struct)); + static if (isAutodecodableString!Enum) + static assert(is(typeof(bcu) == struct)); + else + static assert(is(typeof(bcu) == string)); static assert(is(ElementType!(typeof(bcu)) == immutable char)); assert(bcu.front == 't'); } @@ -3913,7 +3940,10 @@ if (isAutodecodableString!R || auto orig = WEnum.a; auto bcu = orig.byCodeUnit(); static assert(!is(typeof(bcu) == WEnum)); - static assert(is(typeof(bcu) == struct)); + static if (isAutodecodableString!WEnum) + static assert(is(typeof(bcu) == struct)); + else + static assert(is(typeof(bcu) == wstring)); static assert(is(ElementType!(typeof(bcu)) == immutable wchar)); assert(bcu.front == 't'); } @@ -3927,8 +3957,16 @@ if (isAutodecodableString!R || assert(bcu.front == 't'); } - static assert(!is(typeof(byCodeUnit("hello")) == string)); - static assert(!is(typeof(byCodeUnit("hello"w)) == wstring)); + static if (autodecodeStrings) + { + static assert(!is(typeof(byCodeUnit("hello")) == string)); + static assert(!is(typeof(byCodeUnit("hello"w)) == wstring)); + } + else + { + static assert(is(typeof(byCodeUnit("hello")) == string)); + static assert(is(typeof(byCodeUnit("hello"w)) == wstring)); + } static assert(is(typeof(byCodeUnit("hello"d)) == dstring)); static assert(!__traits(compiles, byCodeUnit((char[5]).init)));