x uppercased.
construct from non-immutable source is allowed in non-@nogc-scope
alias S = SSOString; scope const char[] x0; const s0 = SSOString(x0); // no .idup scope const char[] x16 = new char[16]; const s16 = SSOString(x16); // will call .idup
construct from non-immutable source is not allowed in @nogc-scope
scope const char[] s; /+ TODO: why does this fail? static assert(!__traits(compiles, { const _ = SSOString(s); })); +/
verify isNull when @nogc constructing from small static array of chars
static foreach (const n; 0 .. SSOString.smallCapacity + 1) { { immutable(char)[n] x; assert(!SSOString(x).isNull); } }
verify isNull when constructing from large static array of chars
static foreach (const n; SSOString.smallCapacity + 1 .. 32) { { immutable(char)[n] x; assert(!SSOString(x).isNull); } }
verify isNull when constructing from dynamic array of chars
foreach (const n; 0 .. 32) { scope x = new immutable(char)[n]; assert(!SSOString(x).isNull); }
test behaviour of == and is operator
const SSOString x = "42"; assert(!x.isNull); assert(x == "42"); const SSOString y = "42"; assert(!y.isNull); assert(y == "42"); assert(x == y); assert(x == y[]); assert(x[] == y); assert(x[] == y[]); assert(x[] is x[]); assert(y[] is y[]); assert(x[] !is y[]); assert(x.ptr !is y.ptr); const SSOString z = "43"; assert(!z.isNull); assert(z == "43"); assert(x != z); assert(x[] != z[]); assert(x !is z); assert(x[] !is z[]);
1 static assert(SSOString.smallCapacity == 15); 2 3 import nxt.gc_traits : mustAddGCRange; 4 static assert(mustAddGCRange!SSOString); // `Large large.ptr` must be scanned 5 6 static assert(__traits(isZeroInit, SSOString)); 7 /+ TODO: assert(SSOString.init == SSOString.nullValue); +/ 8 9 auto s0 = SSOString.init; 10 assert(s0.isNull); 11 assert(s0.length == 0); 12 assert(s0.isLarge); 13 assert(s0[] == []); 14 15 char[SSOString.smallCapacity] charsSmallCapacity = "123456789_12345"; // fits in small string 16 const sSmallCapacity = SSOString(charsSmallCapacity); 17 assert(!sSmallCapacity.isLarge); 18 assert(sSmallCapacity.length == SSOString.smallCapacity); 19 () @trusted { assert(sSmallCapacity == charsSmallCapacity); }(); // TODO: -dip1000 without @trusted 20 21 const s0_ = SSOString(""); 22 assert(!s0_.isNull); // cannot distinguish 23 () @trusted { assert(s0 == s0_); }(); // TODO: -dip1000 without @trusted 24 25 const s7 = SSOString("0123456"); 26 assert(!s7.isNull); 27 28 const s7_ = SSOString("0123456_"[0 .. $ - 1]); 29 assert(s7.ptr !is s7_.ptr); // string data shall not overlap 30 () @trusted { assert(s7 == s7_); }(); // TODO: -dip1000 without @trusted 31 32 const _s7 = SSOString("_0123456"[1 .. $]); // source from other string literal 33 assert(s7.ptr !is _s7.ptr); // string data shall not overlap 34 () @trusted { assert(s7 == _s7); }(); // TODO: -dip1000 without @trusted 35 36 assert(!s7.isLarge); 37 assert(s7.length == 7); 38 assert(s7[] == "0123456"); 39 assert(s7[] == "_0123456"[1 .. $]); 40 assert(s7[] == "0123456_"[0 .. $ - 1]); 41 assert(s7[0 .. 4] == "0123"); 42 43 const s15 = SSOString("0123456789abcde"); 44 assert(!s15.isNull); 45 static assert(is(typeof(s15[]) == const(char)[])); 46 assert(!s15.isLarge); 47 assert(s15.length == 15); 48 assert(s15[] == "0123456789abcde"); 49 assert(s15[0 .. 4] == "0123"); 50 assert(s15[10 .. 15] == "abcde"); 51 assert(s15[10 .. $] == "abcde"); 52 53 const s16 = SSOString("0123456789abcdef"); 54 assert(!s16.isNull); 55 static assert(is(typeof(s16[]) == const(char)[])); 56 assert(s16.isLarge); 57 58 const s16_ = SSOString("0123456789abcdef_"[0 .. s16.length]); 59 assert(s16.length == s16_.length); 60 assert(s16[] == s16_[]); 61 assert(s16.ptr !is s16_.ptr); // string data shall not overlap 62 () @trusted { assert(s16 == s16_); }(); // but contents is equal // TODO: -dip1000 without @trusted 63 64 const _s16 = SSOString("_0123456789abcdef"[1 .. $]); 65 assert(s16.length == _s16.length); 66 assert(s16[] == _s16[]); // contents is equal 67 () @trusted { assert(s16 == _s16); }(); // contents is equal // TODO: -dip1000 without @trusted 68 69 assert(s16.length == 16); 70 assert(s16[] == "0123456789abcdef"); 71 assert(s16[0] == '0'); 72 assert(s16[10] == 'a'); 73 assert(s16[15] == 'f'); 74 assert(s16[0 .. 4] == "0123"); 75 assert(s16[10 .. 16] == "abcdef"); 76 assert(s16[10 .. $] == "abcdef");
metadata for null string
auto s = SSOString.init; assert(s.isNull); foreach (const i; 0 .. 8) { s.metadata = i; assert(s.metadata == i); assert(s.length == 0); }
metadata for small string
auto s = SSOString("0123456"); assert(!s.isNull); assert(!s.isLarge); foreach (const i; 0 .. 8) { s.metadata = i; assert(s.metadata == i); assert(s.length == 7); assert(!s.isLarge); assert(!s.isNull); }
metadata for small string with maximum length
auto s = SSOString("0123456789abcde"); assert(s.length == SSOString.smallCapacity); assert(!s.isNull); assert(!s.isLarge); foreach (const i; 0 .. 8) { s.metadata = i; assert(s.metadata == i); assert(s.length == 15); assert(!s.isLarge); assert(!s.isNull); }
metadata for large string with minimum length
auto s = SSOString("0123456789abcdef"); assert(s.length == SSOString.smallCapacity + 1); assert(!s.isNull); assert(s.isLarge); assert(!s.empty); foreach (const i; 0 .. 8) { s.metadata = i; assert(s.metadata == i); assert(s.length == 16); assert(s.isLarge); assert(!s.isNull); }
equality and equivalence
() @trusted { assert(SSOString() == SSOString("")); }(); // TODO: -dip1000 without @trusted () @trusted { assert(SSOString() !is SSOString("")); }(); // TODO: -dip1000 without @trusted
hashing of null, empty and non-empty
assert(SSOString().toHash == 0); assert(SSOString("").toHash == 0); assert(SSOString("a").toHash != 0); assert(SSOString("0123456789abcdef").toHash != 0);
construct from static array larger than smallCapacity
char[SSOString.smallCapacity + 1] charsMinLargeCapacity; const _ = SSOString(charsMinLargeCapacity);
hole handling
assert(!SSOString.init.isHole); assert(!SSOString("").isHole); assert(!SSOString("a").isHole); assert(SSOString.asHole.isHole);
DIP-1000 return ref escape analysis
static if (hasPreviewDIP1000) { static assert(!__traits(compiles, { immutable(char)* f1() @safe pure nothrow { SSOString x; return x.ptr; } })); static assert(!__traits(compiles, { string f1() @safe pure nothrow { SSOString x; return x[]; } })); static assert(!__traits(compiles, { string f2() @safe pure nothrow { SSOString x; return x.toString; } })); static assert(!__traits(compiles, { ref immutable(char) g() pure nothrow @safe @nogc { SSOString x; return x[0]; } })); }
ASCII purity and case-conversion
// these are all small ASCII assert( SSOString("a").isSmallASCII); assert( SSOString("b").isSmallASCII); assert( SSOString("z").isSmallASCII); assert( SSOString("_").isSmallASCII); assert( SSOString("abcd").isSmallASCII); assert( SSOString("123456789_12345").isSmallASCII); // these are not assert(!SSOString("123456789_123456").isSmallASCII); // too large assert(!SSOString("123456789_123ö").isSmallASCII); assert(!SSOString("ö").isSmallASCII); assert(!SSOString("Ö").isSmallASCII); assert(!SSOString("åäö").isSmallASCII); assert(!SSOString("ö-värld").isSmallASCII);
ASCII purity and case-conversion
assert(SSOString("A").toLower[] == "a"); assert(SSOString("a").toUpper[] == "A"); assert(SSOString("ABCDEFGHIJKLMNO").toLower[] == "abcdefghijklmno"); // small assert(SSOString("abcdefghijklmno").toUpper[] == "ABCDEFGHIJKLMNO"); // small assert(SSOString("ÅÄÖ").toLower[] == "åäö"); assert(SSOString("åäö").toUpper[] == "ÅÄÖ"); assert(SSOString("ABCDEFGHIJKLMNOP").toLower[] == "abcdefghijklmnop"); // large assert(SSOString("abcdefghijklmnop").toUpper[] == "ABCDEFGHIJKLMNOP"); // large char[6] x = "ÅÄÖ"; import std.uni : toLowerInPlace; auto xref = x[]; () @trusted { toLowerInPlace(xref); }(); // TODO: -dip1000 without @trusted assert(x == "åäö"); assert(xref == "åäö");
lexicographic comparison
const SSOString a = SSOString("a"); () @trusted { assert(a == SSOString("a")); }(); // TODO: -dip1000 without @trusted immutable SSOString b = SSOString("b"); () @trusted { assert(a < b); }(); // TODO: -dip1000 without @trusted () @trusted { assert(b > a); }(); // TODO: -dip1000 without @trusted assert(a[] < b[]); assert("a" < "b"); assert("a" < "å"); assert("Å" < "å"); () @trusted { assert(SSOString("a") < SSOString("å")); }(); // TODO: -dip1000 without @trusted () @trusted { assert(SSOString("ÅÄÖ") < SSOString("åäö")); }(); // TODO: -dip1000 without @trusted
cast to bool
// mimics behaviour of casting of `string` to `bool` () @trusted { assert(!SSOString()); }(); // TODO: -dip1000 without @trusted () @trusted { assert(SSOString("")); }(); // TODO: -dip1000 without @trusted () @trusted { assert(SSOString("abc")); }(); // TODO: -dip1000 without @trusted
to string conversion
// mutable small will GC-allocate { SSOString s = SSOString("123456789_12345"); assert(s.ptr is &s.opSlice()[0]); assert(s.ptr !is &s.toString()[0]); } // const small will GC-allocate { const SSOString s = SSOString("123456789_12345"); assert(s.ptr is &s.opSlice()[0]); assert(s.ptr !is &s.toString()[0]); } // immutable small will not allocate { immutable SSOString s = SSOString("123456789_12345"); assert(s.ptr is &s.opSlice()[0]); assert(s.ptr is &s.toString()[0]); /+ TODO: check return via -dip1000 +/ } /* Forbid return of possibly locally scoped `Smll` small stack object * regardless of head-mutability. */ static if (hasPreviewDIP1000) { static assert(!__traits(compiles, { immutable(char)* f1() @safe pure nothrow { SSOString x; return x.ptr; } })); static assert(!__traits(compiles, { immutable(char)* f1() @safe pure nothrow { const SSOString x; return x.ptr; } })); static assert(!__traits(compiles, { immutable(char)* f1() @safe pure nothrow { immutable SSOString x; return x.ptr; } })); /** TODO: Enable the following line when DIP-1000 works for opSlice() * * See_Also: https://issues.dlang.org/show_bug.cgi?id=18792 */ // static assert(!__traits(compiles, { string f1() @safe pure nothrow { immutable SSOString x; return x[]; } })); } // large will never allocate regardless of head-mutability { SSOString s = SSOString("123456789_123456"); assert(s.ptr is &s.opSlice()[0]); assert(s.ptr is &s.toString()[0]); // shouldn't this change? }