www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - static assert not printing out along the error diagnostic

reply someone <someone somewhere.com> writes:
The following example is from Ali's book   
http://ddili.org/ders/d.en/cond_comp.html:

```d
import std.stdio;

struct MyType(T) {
     static if (is (T == float)) {
         alias ResultType = double;

     } else static if (is (T == double)) {
         alias ResultType = real;

     } else {
         static assert(false, T.stringof ~ " is not supported");
     }

     ResultType doWork() {
         writefln("The return type for %s is %s.",
                  T.stringof, ResultType.stringof);
         ResultType result;
         // ...
         return result;
     }
}

void main() {
     auto f = MyType!float();
     f.doWork();

     auto d = MyType!double();
     d.doWork();
}
```

If I add in main():

```d
     auto g = MyType!string();
     g.doWork();
```

I get:

...: Error: undefined identifier `ResultType`
...: Error: template instance `...MyType!string` error 
instantiating

But nothing like:

```d
T.stringof ~ " is not supported"
```

Is there anything to be enabled on DMD ? A switch I mean.

I am trying to restrict a UDT to three specific types using this 
approach.
Jul 13
parent reply Paul Backus <snarwin gmail.com> writes:
On Wednesday, 14 July 2021 at 02:28:07 UTC, someone wrote:
 If I add in main():

 ```d
     auto g = MyType!string();
     g.doWork();
 ```

 I get:

 ...: Error: undefined identifier `ResultType`
 ...: Error: template instance `...MyType!string` error 
 instantiating

 But nothing like:

 ```d
 T.stringof ~ " is not supported"
 ```
Weirdly, adding a dummy `ResultType` declaration to the `else` branch makes the assert message show up: } else { alias ResultType = void; static assert(false, T.stringof ~ " is not supported"); } https://run.dlang.io/is/68sM0Z Somehow the compiler is processing the declaration of `doWork` before the `static assert` statement, even though it comes later in the source code. According to run.dlang.io this behavior was introduced in DMD 2.066.0, so the example in the book must not have been updated for quite a while.
Jul 13
parent reply someone <someone somewhere.com> writes:
On Wednesday, 14 July 2021 at 02:42:21 UTC, Paul Backus wrote:

 Weirdly, adding a dummy `ResultType` declaration to the `else`  
 branch makes the assert message show up:
My code is like following: ```d public struct gudtUGC(typeStringUTF) { static if (! (is (typeStringUTF == string) || is (typeStringUTF == wstring) || is (typeStringUTF == dstring))) { static assert(false, r"ooops … gudtUGC structure requires [string|wstring|dstring] ≠ ["d ~ typeStringUTF.stringof ~ r"]"d); /// meaning: this will halt compilation if this UDT was instantiated with a type other than the ones intended } else { ... actual structure code } } ``` ... and it never fired the assert when I added something like: ```d gudtUGC(int) something = gudtUGC(1); /// invalid of course ``` in main() ... so then I went to the D docs and to Ali's book afterward and there I tested his example to same results.
Jul 13
next sibling parent reply SealabJaster <sealabjaster gmail.com> writes:
On Wednesday, 14 July 2021 at 03:06:06 UTC, someone wrote:
 ...
I've ran into this rarely. At a guess, it seems something triggers the compiler to either evaluate something in non-lexical order; doesn't realise it needs to error out at the static assert instead of doing it later, or maybe it somehow ends up gagging the more relevant static assert error. The fix usually involves changing the code in a way to make the compiler evaluate things differently. For your example: ```d import std.stdio; struct MyType(T) { template ResultTypeT() { static if (is (T == float)) { alias ResultTypeT = double; } else static if (is (T == double)) { alias ResultTypeT = real; } else { static assert(false, T.stringof ~ " is not supported"); } } alias ResultType = ResultTypeT!(); ResultType doWork() { writefln("The return type for %s is %s.", T.stringof, ResultType.stringof); ResultType result; // ... return result; } } void main() { auto f = MyType!float(); f.doWork(); auto d = MyType!double(); d.doWork(); auto g = MyType!string(); g.doWork(); } ```
Jul 13
parent SealabJaster <sealabjaster gmail.com> writes:
On Wednesday, 14 July 2021 at 05:59:19 UTC, SealabJaster wrote:
 I've ran into this rarely. At a guess, it seems something 
 triggers the compiler to either evaluate something in 
 non-lexical order; doesn't realise it needs to error out at the 
 static assert instead of doing it later, or maybe it somehow 
 ends up gagging the more relevant static assert error.
To put this more coherently, I think it's to do with the fact the compiler sometimes doesn't immediately stop compilation on error, usually so it can try to produce even more errors to help aid you. So it might be storing the `static assert` error, chokes on `doWork` since `ResultType` isn't defined in this case, and for some odd reason just completely drops the `static assert`: ```d // Obviously this isn't valid D, but mostly to show what I think happens. struct MyType! { // `else` branch does not define a `ResultType` // // Compiler sees this static assert, goes into 'error collection' mode I guess we could call it. static assert(false, "bad type"); // Error collection mode sees that `ResultType` isn't defined. // But for some reason, it chokes up, and doesn't produce the static assert error in the output. ResultType doWork(){...} } ```
Jul 13
prev sibling parent reply jfondren <julian.fondren gmail.com> writes:
On Wednesday, 14 July 2021 at 03:06:06 UTC, someone wrote:
 in main() ... so then I went to the D docs and to Ali's book 
 afterward and there I tested his example to same results.
The current behavior seems like it could be taken for a bug, or at least room for improvement in letting static assertions fail faster than other template instantiation, but the workaround seems to be to require all branches of a static if to be valid from the perspective of the rest of the code. Which is very easy to do if you just don't have these branches at all. case 1: ```d public struct gudtUGC(typeStringUTF) { static if (! (is (typeStringUTF == string) || is (typeStringUTF == wstring) || is (typeStringUTF == dstring))) { static assert(false, r"ooops … gudtUGC structure requires [string|wstring|dstring] ≠ ["d ~ typeStringUTF.stringof ~ r"]"d); } else { // actual structure code } } ``` alternate 1: - pull tests out into a named enum template, like std.traits - always static assert enum, rather than conditionally asserting false - always have the rest of the code ```d enum isString(T) = is(T == string) || is(T == wstring) || is(T == dstring); // very similar to std.traits.isSomeString struct gudtUGC(T) { static assert(isString!T, "error message"); // unconditional structure code } ``` case 2: ```d struct MyType(T) { static if (is (T == float)) { alias ResultType = double; } else static if (is (T == double)) { alias ResultType = real; } else { static assert(false, T.stringof ~ " is not supported"); } // code relying on some ResultType } ``` alternate 2a: - create a type function with std.meta ```d enum SmallFloat(T) = is(T == float) || is(T == double); template largerFloat(T) { import std.meta : AliasSeq, staticIndexOf; static assert(SmallFloat!T, T.stringof ~ " is not supported"); alias largerFloat = AliasSeq!(void, double, real)[1+staticIndexOf!(T, AliasSeq!(float, double))]; } struct MyType(T) { alias ResultType = largerFloat!T; // code relying on some ResultType } ``` alternate 2b: - at least make all the branches valid ```d enum SmallFloat(T) = is(T == float) || is(T == double); struct MyType(T) { static assert(SmallFloat!T, T.stringof ~ " is not supported"); static if (is(T == float)) { alias ResultType = double; } else static if (is(T == double)) { alias ResultType = real; } else { alias ResultType = void; // dummy } // code relying on ResultType } ``` A benefit of having tests like SmallFloat is that you can use them with template constraints... ```d void moreWork(T)() if (SmallFloat!T) { } unittest { moreWork!float; // this is fine moreWork!real; // ! } ``` ... and get readable error messages out of the box, without having to write static assert messages: ``` example.d(23): Error: template instance `example.moreWork!real` does not match template declaration `moreWork(T)()` with `T = real` must satisfy the following constraint: ` SmallFloat!T` ```
Jul 13
parent reply someone <someone somewhere.com> writes:
On Wednesday, 14 July 2021 at 06:28:37 UTC, jfondren wrote:

 alternate 1:
 - pull tests out into a named enum template, like std.traits
 - always static assert enum, rather than conditionally 
 asserting false
 - always have the rest of the code

 ```d
 enum isString(T) = is(T == string) || is(T == wstring) || is(T 
 == dstring);
 // very similar to std.traits.isSomeString

 struct gudtUGC(T) {
     static assert(isString!T, "error message");
     // unconditional structure code
 }
 ```
... is it me or this isn't triggering the assert either ?
Jul 14
parent reply jfondren <julian.fondren gmail.com> writes:
On Wednesday, 14 July 2021 at 18:04:44 UTC, someone wrote:
 On Wednesday, 14 July 2021 at 06:28:37 UTC, jfondren wrote:

 alternate 1:
 - pull tests out into a named enum template, like std.traits
 - always static assert enum, rather than conditionally 
 asserting false
 - always have the rest of the code

 ```d
 enum isString(T) = is(T == string) || is(T == wstring) || is(T 
 == dstring);
 // very similar to std.traits.isSomeString

 struct gudtUGC(T) {
     static assert(isString!T, "error message");
     // unconditional structure code
 }
 ```
... is it me or this isn't triggering the assert either ?
This isn't a complete example. The same problem elsewhere in your program can cause dmd to exit before getting to the static assert here, for the same reason that the static assert in your original code wasn't got to. Here's a complete example: ```d enum isString(T) = is(T == string) || is(T == wstring) || is(T == dstring); struct gudtUGC(T) { static assert(isString!T, "error message"); // unconditional structure code } unittest { gudtUGC!int; } ``` which fails with ``` example.d(4): Error: static assert: "error message" example.d(9): instantiated from here: `gudtUGC!int` ``` If you have the static assert there but then still follow up with static ifs that only conditionally produce an alias, then you have the same problem still.
Jul 14
parent reply someone <someone somewhere.com> writes:
On Wednesday, 14 July 2021 at 19:00:08 UTC, jfondren wrote:
 On Wednesday, 14 July 2021 at 18:04:44 UTC, someone wrote:
 On Wednesday, 14 July 2021 at 06:28:37 UTC, jfondren wrote:

 alternate 1:
 - pull tests out into a named enum template, like std.traits
 - always static assert enum, rather than conditionally 
 asserting false
 - always have the rest of the code

 ```d
 enum isString(T) = is(T == string) || is(T == wstring) || 
 is(T == dstring);
 // very similar to std.traits.isSomeString

 struct gudtUGC(T) {
     static assert(isString!T, "error message");
     // unconditional structure code
 }
 ```
... is it me or this isn't triggering the assert either ?
This isn't a complete example. The same problem elsewhere in your program can cause dmd to exit before getting to the static assert here, for the same reason that the static assert in your original code wasn't got to. Here's a complete example: ```d enum isString(T) = is(T == string) || is(T == wstring) || is(T == dstring); struct gudtUGC(T) { static assert(isString!T, "error message"); // unconditional structure code } unittest { gudtUGC!int; } ``` which fails with ``` example.d(4): Error: static assert: "error message" example.d(9): instantiated from here: `gudtUGC!int` ``` If you have the static assert there but then still follow up with static ifs that only conditionally produce an alias, then you have the same problem still.
Please, go to the bottom of the unittest block and uncomment one of those lines (DMD version here is DMD64 D Compiler v2.096.1): ```d /// implementation-bugs [-] using foreach (with this structure) 20483 unittest's testUTFcommon() last line /// implementation-bugs [-] static assert not firing /// implementation‐tasks [✓] for the time being input parameters are declared const instead of in; eventually they'll be back to in when the related DIP was setted once and for all; but, definetely—not scope const /// implementation‐tasks [✓] reconsider excessive cast usage as suggested by Ali: bypassing compiler checks could be potentially harmful … cast and integer promotion http://ddili.org/ders/d.en/cast.html /// implementation‐tasks [-] reconsider making this whole UDT zero‐based as suggested by ag0aep6g—has a good point /// implementation‐tasks‐possible [-] pad[L|R] /// implementation‐tasks‐possible [-] replicate/repeat /// implementation‐tasks‐possible [-] replace(string, string) /// implementation‐tasks‐possible [-] translate(string, string) … same‐size strings matching one‐to‐one /// usage: array slicing can be used for usual things like: left() right() substr() etc … mainly when grapheme‐clusters are not expected at all /// usage: array slicing needs a zero‐based first range argument and a second one one‐based (or one‐past‐beyond; which it is somehow … counter‐intuitive module fw.types.UniCode; import std.algorithm : map, joiner; import std.array : array; import std.conv : to; import std.range : walkLength, take, tail, drop, dropBack; /// repeat, padLeft, padRight import std.stdio; import std.uni : Grapheme, byGrapheme; /// within this file: gudtUGC shared static this() { } /// the following will be executed only‐once per‐app: static this() { } /// the following will be executed only‐once per‐thread: static ~this() { } /// the following will be executed only‐once per‐thread: shared static ~this() { } /// the following will be executed only‐once per‐app: alias stringUGC = Grapheme; alias stringUGC08 = gudtUGC!(stringUTF08); alias stringUGC16 = gudtUGC!(stringUTF16); alias stringUGC32 = gudtUGC!(stringUTF32); alias stringUTF08 = string; /// same as immutable(char )[]; alias stringUTF16 = wstring; /// same as immutable(wchar)[]; alias stringUTF32 = dstring; /// same as immutable(dchar)[]; enum isTypeSupported(type) = is(type == stringUTF08) || is(type == stringUTF16) || is(type == stringUTF32) ; /// mixin templateUGC!(stringUTF08, r"gudtUGC08"d); /// mixin templateUGC!(stringUTF16, r"gudtUGC16"d); /// mixin templateUGC!(stringUTF32, r"gudtUGC32"d); /// template templateUGC (typeStringUTF, alias lstrStructureID) { aliases in main() public struct gudtUGC(typeStringUTF) { /// UniCode grapheme‐cluster‐aware string manipulation (implemented for one‐based operations) static assert( isTypeSupported!(typeStringUTF), r"ooops … gudtUGC structure requires [string|wstring|dstring] ≠ ["d ~ typeStringUTF.stringof ~ r"]"d ); /// meaning: this will halt compilation if this UDT was instantiated with a type other than the ones intended /// provides: public property size_t count /// provides: public size_t decode(typeStringUTF strSequence) /// provides: public typeStringUTF encode() /// provides: public gudtUGC!(typeStringUTF) take(size_t intStart, size_t intCount = 1) /// provides: public gudtUGC!(typeStringUTF) takeL(size_t intCount) /// provides: public gudtUGC!(typeStringUTF) takeR(size_t intCount) /// provides: public gudtUGC!(typeStringUTF) chopL(size_t intCount) /// provides: public gudtUGC!(typeStringUTF) chopR(size_t intCount) /// provides: public gudtUGC!(typeStringUTF) padL(size_t intCount, typeStringUTF strPadding = r" ") /// provides: public gudtUGC!(typeStringUTF) padR(size_t intCount, typeStringUTF strPadding = r" ") /// provides: public typeStringUTF takeasUTF(size_t intStart, size_t intCount = 1) /// provides: public typeStringUTF takeLasUTF(size_t intCount) /// provides: public typeStringUTF takeRasUTF(size_t intCount) /// provides: public typeStringUTF chopLasUTF(size_t intCount) /// provides: public typeStringUTF chopRasUTF(size_t intCount) /// provides: public typeStringUTF padL(size_t intCount, typeStringUTF strPadding = r" ") /// provides: public typeStringUTF padR(size_t intCount, typeStringUTF strPadding = r" ") /// usage; eg: stringUGC32("äëåčñœß … russian = русский 🇷🇺 ≠ 🇯🇵 日本語 = japanese"d).take(35, 3).take(1,2).take(1,1).encode(); /// 日 /// usage; eg: stringUGC32("äëåčñœß … russian = русский 🇷🇺 ≠ 🇯🇵 日本語 = japanese"d).take(35).encode(); /// 日 /// usage; eg: stringUGC32("äëåčñœß … russian = русский 🇷🇺 ≠ 🇯🇵 日本語 = japanese"d).takeasUTF(35); /// 日 void popFront() { ++pintSequenceCurrent; } bool empty() { return pintSequenceCurrent == pintSequenceCount; } typeStringUTF front() { return takeasUTF(pintSequenceCurrent); } private stringUGC[] pugcSequence; private size_t pintSequenceCount = 0; property public size_t count() { return pintSequenceCount; } private size_t pintSequenceCurrent = 0; this( const typeStringUTF lstrSequence ) { /// (1) given UTF‐encoded sequence this.decode(lstrSequence); } safe public size_t decode( /// UniCode (UTF‐encoded → grapheme‐cluster) sequence const typeStringUTF lstrSequence ) { /// (1) given UTF‐encoded sequence size_t lintSequenceCount = 0; if (lstrSequence is null) { pugcSequence = null; pintSequenceCount = 0; pintSequenceCurrent = 0; } else { pugcSequence = lstrSequence.byGrapheme.array; pintSequenceCount = pugcSequence.walkLength; pintSequenceCurrent = 1; lintSequenceCount = pintSequenceCount; } return lintSequenceCount; } safe public typeStringUTF encode() { /// UniCode (grapheme‐cluster → UTF‐encoded) sequence typeStringUTF lstrSequence = null; if (pintSequenceCount >= 1) { lstrSequence = pugcSequence .map!((ref g) => g[]) .joiner .to!(typeStringUTF) ; } return lstrSequence; } safe public gudtUGC!(typeStringUTF) take( /// UniCode (grapheme‐cluster → grapheme‐cluster) sequence const size_t lintStart, const size_t lintCount = 1 ) { /// (1) given start position >= 1 /// (2) given count >= 1 gudtUGC!(typeStringUTF) lugcSequence; if (lintStart >= 1 && lintCount >= 1) { size_t lintRange1 = lintStart - 1; size_t lintRange2 = lintRange1 + lintCount; if (lintRange2 <= pintSequenceCount) { lugcSequence = gudtUGC!(typeStringUTF)(pugcSequence[lintRange1..lintRange2] .map!((ref g) => g[]) .joiner .to!(typeStringUTF) ); } } return lugcSequence; } safe public gudtUGC!(typeStringUTF) takeL( /// UniCode (grapheme‐cluster → grapheme‐cluster) sequence const size_t lintCount ) { /// (1) given count >= 1 gudtUGC!(typeStringUTF) lugcSequence; if (lintCount >= 1 && lintCount <= pintSequenceCount) { lugcSequence = gudtUGC!(typeStringUTF)(pugcSequence .take(lintCount) .map!((ref g) => g[]) .joiner .to!(typeStringUTF) ); } return lugcSequence; } safe public gudtUGC!(typeStringUTF) takeR( /// UniCode (grapheme‐cluster → grapheme‐cluster) sequence const size_t lintCount ) { /// (1) given count >= 1 gudtUGC!(typeStringUTF) lugcSequence; if (lintCount >= 1 && lintCount <= pintSequenceCount) { lugcSequence = gudtUGC!(typeStringUTF)(pugcSequence .tail(lintCount) .map!((ref g) => g[]) .joiner .to!(typeStringUTF) ); } return lugcSequence; } safe public gudtUGC!(typeStringUTF) chopL( /// UniCode (grapheme‐cluster → grapheme‐cluster) sequence const size_t lintCount ) { /// (1) given count >= 1 gudtUGC!(typeStringUTF) lugcSequence; if (lintCount >= 1 && lintCount <= pintSequenceCount) { lugcSequence = gudtUGC!(typeStringUTF)(pugcSequence .drop(lintCount) .map!((ref g) => g[]) .joiner .to!(typeStringUTF) ); } return lugcSequence; } safe public gudtUGC!(typeStringUTF) chopR( /// UniCode (grapheme‐cluster → grapheme‐cluster) sequence const size_t lintCount ) { /// (1) given count >= 1 gudtUGC!(typeStringUTF) lugcSequence; if (lintCount >= 1 && lintCount <= pintSequenceCount) { lugcSequence = gudtUGC!(typeStringUTF)(pugcSequence .dropBack(lintCount) .map!((ref g) => g[]) .joiner .to!(typeStringUTF) ); } return lugcSequence; } safe public typeStringUTF takeasUTF( /// UniCode (grapheme‐cluster → UTF‐encoded) sequence const size_t lintStart, const size_t lintCount = 1 ) { /// (1) given start position >= 1 /// (2) given count >= 1 typeStringUTF lstrSequence = null; if (lintStart >= 1 && lintCount >= 1) { /// eg: see take() size_t lintRange1 = lintStart - 1; size_t lintRange2 = lintRange1 + lintCount; if (lintRange2 <= pintSequenceCount) { lstrSequence = pugcSequence[lintRange1..lintRange2] .map!((ref g) => g[]) .joiner .to!(typeStringUTF) ; } } return lstrSequence; } safe public typeStringUTF takeLasUTF( /// UniCode (grapheme‐cluster → UTF‐encoded) sequence const size_t lintCount ) { /// (1) given count >= 1 typeStringUTF lstrSequence = null; if (lintCount >= 1 && lintCount <= pintSequenceCount) { lstrSequence = pugcSequence .take(lintCount) .map!((ref g) => g[]) .joiner .to!(typeStringUTF) ; } return lstrSequence; } safe public typeStringUTF takeRasUTF( /// UniCode (grapheme‐cluster → UTF‐encoded) sequence const size_t lintCount ) { /// (1) given count >= 1 typeStringUTF lstrSequence = null; if (lintCount >= 1 && lintCount <= pintSequenceCount) { lstrSequence = pugcSequence .tail(lintCount) .map!((ref g) => g[]) .joiner .to!(typeStringUTF) ; } return lstrSequence; } safe public typeStringUTF chopLasUTF( /// UniCode (grapheme‐cluster → UTF‐encoded) sequence const size_t lintCount ) { /// (1) given count >= 1 typeStringUTF lstrSequence = null; if (lintCount >= 1 && lintCount <= pintSequenceCount) { lstrSequence = pugcSequence .drop(lintCount) .map!((ref g) => g[]) .joiner .to!(typeStringUTF) ; } return lstrSequence; } safe public typeStringUTF chopRasUTF( /// UniCode (grapheme‐cluster → UTF‐encoded) sequence const size_t lintCount ) { /// (1) given count >= 1 typeStringUTF lstrSequence = null; if (lintCount >= 1 && lintCount <= pintSequenceCount) { lstrSequence = pugcSequence .dropBack(lintCount) .map!((ref g) => g[]) .joiner .to!(typeStringUTF) ; } return lstrSequence; } safe public typeStringUTF padLasUTF( /// UniCode (grapheme‐cluster → UTF‐encoded) sequence const size_t lintCount, const typeStringUTF lstrPadding = r" " ) { /// (1) given count >= 1 /// [2] given padding (default is a single blank space) typeStringUTF lstrSequence = null; if (lintCount >= 1 && lintCount > pintSequenceCount) { lstrSequence = null; /// pending } return lstrSequence; } safe public typeStringUTF padRasUTF( /// UniCode (grapheme‐cluster → UTF‐encoded) sequence const size_t lintCount, const typeStringUTF lstrPadding = r" " ) { /// (1) given count >= 1 /// [2] given padding (default is a single blank space) typeStringUTF lstrSequence = null; if (lintCount >= 1 && lintCount > pintSequenceCount) { lstrSequence = null; /// pending } return lstrSequence; } } unittest { void testUTFcommon( /// the following should be the same (regardless of the encoding being used) and is the whole point of this UDT being made: typeStringUTF, typeStringUGC )( const typeStringUTF lstrSequence3 ) { typeStringUGC lugcSequence3 = typeStringUGC(lstrSequence3); with (lugcSequence3) { assert(encode() == lstrSequence3); assert(take(35, 3).take(1,2).take(1,1).encode() == r"日"); assert(take(21).encode() == r"р"); assert(take(27).encode() == r"й"); assert(take(35).encode() == r"日"); assert(take(37).encode() == r"語"); assert(take(21, 7).encode() == r"русский"); assert(take(35, 3).encode() == r"日本語"); assert(takeasUTF(21) == r"р"); assert(takeasUTF(27) == r"й"); assert(takeasUTF(35) == r"日"); assert(takeasUTF(37) == r"語"); assert(takeasUTF(21, 7) == r"русский"); assert(takeasUTF(35, 3) == r"日本語"); assert(takeL(1).encode() == r"ä"); assert(takeR(1).encode() == r"😎"); assert(takeL(7).encode() == r"äëåčñœß"); assert(takeR(16).encode() == r"日本語 = japanese 😎"); assert(takeLasUTF(1) == r"ä"); assert(takeRasUTF(1) == r"😎"); assert(takeLasUTF(7) == r"äëåčñœß"); assert(takeRasUTF(16) == r"日本語 = japanese 😎"); assert(chopL(10).encode() == r"russian = русский 🇷🇺 ≠ 🇯🇵 日本語 = japanese 😎"); assert(chopR(21).encode() == r"äëåčñœß … russian = русский 🇷🇺"); assert(chopLasUTF(10) == r"russian = русский 🇷🇺 ≠ 🇯🇵 日本語 = japanese 😎"); assert(chopRasUTF(21) == r"äëåčñœß … russian = русский 🇷🇺"); } typeStringUTF lstrSequence3reencoded; for ( size_t lintSequenceUGC = 1; lintSequenceUGC <= lstrSequence3.byGrapheme.walkLength; ++lintSequenceUGC ) { lstrSequence3reencoded ~= lugcSequence3.takeasUTF(lintSequenceUGC); } assert(lstrSequence3reencoded == lstrSequence3); lstrSequence3reencoded = null; foreach (typeStringUTF lstrSequence3UGC; lugcSequence3) { lstrSequence3reencoded ~= lstrSequence3UGC; } //assert(lstrSequence3reencoded == lstrSequence3); /// } void testUTF08( const stringUTF08 lstrSequence1, const stringUTF08 lstrSequence2, const stringUTF08 lstrSequence3 ) { assert(lstrSequence1.byGrapheme.walkLength == 50); /// checking UGC sizes; ie grapheme‐cluster count assert(lstrSequence2.byGrapheme.walkLength == 50); assert(lstrSequence3.byGrapheme.walkLength == 50); assert(lstrSequence1.walkLength == 50); /// checking UGA sizes; ie code‐point count assert(lstrSequence2.walkLength == 50); assert(lstrSequence3.walkLength == 52); assert(lstrSequence1.length == 50); /// checking UTF sizes; ie code‐unit count assert(lstrSequence2.length == 60); assert(lstrSequence3.length == 91); testUTFcommon!(stringUTF08, stringUGC08)(lstrSequence3); /// checking for correct string manipulation } void testUTF16( const stringUTF16 lstrSequence1, const stringUTF16 lstrSequence2, const stringUTF16 lstrSequence3 ) { assert(lstrSequence1.byGrapheme.walkLength == 50); /// checking UGC sizes; ie grapheme‐cluster count assert(lstrSequence2.byGrapheme.walkLength == 50); assert(lstrSequence3.byGrapheme.walkLength == 50); assert(lstrSequence1.walkLength == 50); /// checking UGA sizes; ie code‐point count assert(lstrSequence2.walkLength == 50); assert(lstrSequence3.walkLength == 52); assert(lstrSequence1.length == 50); /// checking UTF sizes; ie code‐unit count assert(lstrSequence2.length == 50); assert(lstrSequence3.length == 57); testUTFcommon!(stringUTF16, stringUGC16)(lstrSequence3); /// checking for correct string manipulation } void testUTF32( const stringUTF32 lstrSequence1, const stringUTF32 lstrSequence2, const stringUTF32 lstrSequence3 ) { assert(lstrSequence1.byGrapheme.walkLength == 50); /// checking UGC sizes; ie grapheme‐cluster count assert(lstrSequence2.byGrapheme.walkLength == 50); assert(lstrSequence3.byGrapheme.walkLength == 50); assert(lstrSequence1.walkLength == 50); /// checking UGA sizes; ie code‐point count assert(lstrSequence2.walkLength == 50); assert(lstrSequence3.walkLength == 52); assert(lstrSequence1.length == 50); /// checking UTF sizes; ie code‐unit count assert(lstrSequence2.length == 50); assert(lstrSequence3.length == 52); testUTFcommon!(stringUTF32, stringUGC32)(lstrSequence3); /// checking for correct string manipulation } testUTF08( r"12345678901234567890123456789012345678901234567890"c, r"1234567890АВГДЕЗИЙКЛABCDEFGHIJabcdefghijQRSTUVWXYZ"c, r"äëåčñœß … russian = русский 🇷🇺 ≠ 🇯🇵 日本語 = japanese 😎"c ); testUTF16( r"12345678901234567890123456789012345678901234567890"w, r"1234567890АВГДЕЗИЙКЛABCDEFGHIJabcdefghijQRSTUVWXYZ"w, r"äëåčñœß … russian = русский 🇷🇺 ≠ 🇯🇵 日本語 = japanese 😎"w ); testUTF32( r"12345678901234567890123456789012345678901234567890"d, r"1234567890АВГДЕЗИЙКЛABCDEFGHIJabcdefghijQRSTUVWXYZ"d, r"äëåčñœß … russian = русский 🇷🇺 ≠ 🇯🇵 日本語 = japanese 😎"d ); //stringUGC32 lugcSequence3 = stringUGC32(cast(char) 'x'); //stringUGC32 lugcSequence3 = stringUGC32(1); } ```
Jul 14
parent reply jfondren <julian.fondren gmail.com> writes:
On Wednesday, 14 July 2021 at 22:59:38 UTC, someone wrote:
 Please, go to the bottom of the unittest block and uncomment 
 one of those lines (DMD version here is DMD64 D Compiler 
 v2.096.1):
so, these lines: ```d stringUGC32 lugcSequence3 = stringUGC32(cast(char) 'x'); stringUGC32 lugcSequence4 = stringUGC32(1); ``` which call stringUGC32, an alias: ```d alias stringUGC32 = gudtUGC!(stringUTF32); alias stringUTF32 = dstring; /// same as immutable(dchar)[]; ``` which explicitly instantiates gudtUGC with the type dstring, ```d enum isTypeSupported(type) = is(type == stringUTF08) || is(type == stringUTF16) || is(type == stringUTF32); ... public struct gudtUGC(typeStringUTF) { static assert(isTypeSupported!(typeStringUTF), r"ooops … gudtUGC structure requires [string|wstring|dstring] ≠ ["d ~ typeStringUTF.stringof ~ r"]"d); ``` which passes the assert. You're explicitly instantiating the struct with dstring, *not getting an assertion error because dstring passes the test*, and are then passing the constructor a different type, which gets you a type error at that time. However, even if you avoid that alias and try either implicit or explicit instantiation of the template with bad types, the template instantiation fails first in random other places, like a default param of " " not making sense as a char: ```d staticif1.d(369): Error: cannot implicitly convert expression `" "` of type `string` to `const(char)` staticif1.d(387): Error: cannot implicitly convert expression `" "` of type `string` to `const(char)` staticif1.d(545): Error: template instance `fw.types.UniCode.gudtUGC!char` error instantiating ``` So, yeah, even with no static ifs in the file, you're still not getting these static asserts to fail before other parts of the template. Conclusion: you shouldn't rely on static asserts providing a good UI in the case of an error. They still *work*, the struct still wouldn't instantiate with a `char` or the like if all the other char-incompatibilities were removed, but for UI purposes they're not great because you can't rely on them firing according to normal control flow. You're using aliases though, and not instantiating the struct directly. If you make those function templates you can use template constraints with them, which does result in a nice UI: ```d gudtUGC!stringUTF08 thing8(T)(T arg) if (isTypeSupported!T) { return gudtUGC!T(arg); } gudtUGC!stringUTF16 thing16(T)(T arg) if (isTypeSupported!T) { return gudtUGC!T(arg); } gudtUGC!stringUTF32 thing32(T)(T arg) if (isTypeSupported!T) { return gudtUGC!T(arg); } ... stringUGC32 lugcSequence3 = thing32(cast(char) 'x'); stringUGC32 lugcSequence4 = thing32(1); ``` (introducing new 'thing' functions as stringUGC32 used is elsewhere as a type and not just a function. So you still need the aliases.) Which fails in this manner: ```d staticif1.d(548): Error: template `fw.types.UniCode.thing32` cannot deduce function from argument types `!()(char)`, candidates are: staticif1.d(46): `thing32(T)(T arg)` with `T = char` must satisfy the following constraint: ` isTypeSupported!T` ``` and would fail more verbosely if the three functions had the same name, instead of numbered names to follow the stringUGC32 pattern. `isTypeSupported` isn't great here, but you can pick a more precise name. Or in the case of already numbered functions like this, you could use more precise tests... or just drop the tests entirely and have non-templated functions: ```d gudtUGC!stringUTF08 thing8(stringUTF08 arg) { return typeof(return)(arg); } gudtUGC!stringUTF16 thing16(stringUTF16 arg) { return typeof(return)(arg); } gudtUGC!stringUTF32 thing32(stringUTF32 arg) { return typeof(return)(arg); } ``` which results in normal type errors: ``` staticif2.d(548): Error: function `fw.types.UniCode.thing32(dstring arg)` is not callable using argument types `(char)` staticif2.d(548): cannot pass argument `'x'` of type `char` to parameter `dstring arg` ```
Jul 14
parent someone <someone somewhere.com> writes:
On Wednesday, 14 July 2021 at 23:44:18 UTC, jfondren wrote:
 On Wednesday, 14 July 2021 at 22:59:38 UTC, someone wrote:
 [...]
so, these lines: ```d stringUGC32 lugcSequence3 = stringUGC32(cast(char) 'x'); stringUGC32 lugcSequence4 = stringUGC32(1); ``` which call stringUGC32, an alias: ```d alias stringUGC32 = gudtUGC!(stringUTF32); alias stringUTF32 = dstring; /// same as immutable(dchar)[]; ``` which explicitly instantiates gudtUGC with the type dstring, ```d enum isTypeSupported(type) = is(type == stringUTF08) || is(type == stringUTF16) || is(type == stringUTF32); ... public struct gudtUGC(typeStringUTF) { static assert(isTypeSupported!(typeStringUTF), r"ooops … gudtUGC structure requires [string|wstring|dstring] ≠ ["d ~ typeStringUTF.stringof ~ r"]"d); ``` which passes the assert. You're explicitly instantiating the struct with dstring, *not getting an assertion error because dstring passes the test*, and are then passing the constructor a different type, which gets you a type error at that time.
I can't believe I completely forgot about my alias; I am a stupid to the say the least, I raised this issue without thinking much :(
 However, even if you avoid that alias and try either implicit 
 or explicit instantiation of the template with bad types, the 
 template instantiation fails first in random other places, like 
 a default param of " " not making sense as a char:

 ```d
 staticif1.d(369): Error: cannot implicitly convert expression 
 `" "` of type `string` to `const(char)`
 staticif1.d(387): Error: cannot implicitly convert expression 
 `" "` of type `string` to `const(char)`
 staticif1.d(545): Error: template instance 
 `fw.types.UniCode.gudtUGC!char` error instantiating
 ```

 So, yeah, even with no static ifs in the file, you're still not 
 getting these static asserts to fail before other parts of the 
 template. Conclusion: you shouldn't rely on static asserts 
 providing a good UI in the case of an error. They still *work*, 
 the struct still wouldn't instantiate with a `char` or the like 
 if all the other char-incompatibilities were removed, but for 
 UI purposes they're not great because you can't rely on them 
 firing according to normal control flow.
ACK for the static asserts.
 You're using aliases though, and not instantiating the struct 
 directly. If you make those function templates you can use 
 template constraints with them, which does result in a nice UI:
A few days ago when I started coding this UDT (and started thinking of *actually using it*) the first thing that came to mind was that to get a simple, say, strWhatever.left(1) I would end up writing gudtUGC!(dstring)(strWhatever).left(1).encode() or gudtUGC!(dstring)(strWhatever).leftAsUTF(1) or something akin to these ones, so I played a bit with the aliases and ended up with gudtUGC[08|16|32] which seemed a bit more friendly and crystal-clear on their types. The thing with aliases is that they are really useful *until* you forgot you're using aliases and end up biting yourself on a situation like this one. Again, sorry for being too silly on my part.
 ```d
 gudtUGC!stringUTF08 thing8(T)(T arg) if (isTypeSupported!T) { 
 return gudtUGC!T(arg); }
 gudtUGC!stringUTF16 thing16(T)(T arg) if (isTypeSupported!T) { 
 return gudtUGC!T(arg); }
 gudtUGC!stringUTF32 thing32(T)(T arg) if (isTypeSupported!T) { 
 return gudtUGC!T(arg); }

 ...

     stringUGC32 lugcSequence3 = thing32(cast(char) 'x');
     stringUGC32 lugcSequence4 = thing32(1);
 ```

 (introducing new 'thing' functions as stringUGC32 used is 
 elsewhere as a type and not just a function. So you still need 
 the aliases.)

 Which fails in this manner:

 ```d
 staticif1.d(548): Error: template `fw.types.UniCode.thing32` 
 cannot deduce function from argument types `!()(char)`, 
 candidates are:
 staticif1.d(46):        `thing32(T)(T arg)`
   with `T = char`
   must satisfy the following constraint:
 `       isTypeSupported!T`
 ```

 and would fail more verbosely if the three functions had the 
 same name, instead of numbered names to follow the stringUGC32 
 pattern.

 `isTypeSupported` isn't great here, but you can pick a more 
 precise name. Or in the case of already numbered functions like 
 this, you could use more precise tests... or just drop the 
 tests entirely and have non-templated functions:

 ```d
 gudtUGC!stringUTF08 thing8(stringUTF08 arg) { return 
 typeof(return)(arg); }
 gudtUGC!stringUTF16 thing16(stringUTF16 arg) { return 
 typeof(return)(arg); }
 gudtUGC!stringUTF32 thing32(stringUTF32 arg) { return 
 typeof(return)(arg); }
 ```

 which results in normal type errors:

 ```
 staticif2.d(548): Error: function 
 `fw.types.UniCode.thing32(dstring arg)` is not callable using 
 argument types `(char)`
 staticif2.d(548):        cannot pass argument `'x'` of type 
 `char` to parameter `dstring arg`
 ```
I will explore this approach. Thank you very much for your time and your detailed step-by-step reply. I guess you were LoL when you first saw this one :) !
Jul 14