Re: static assert not printing out along the error diagnostic
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.
Re: static assert not printing out along the error diagnostic
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.
Re: static assert not printing out along the error diagnostic
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(); } ```
Re: static assert not printing out along the error diagnostic
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(){...} } ```
Re: static assert not printing out along the error diagnostic
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` ```
Re: static assert not printing out along the error diagnostic
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 ?
Re: static assert not printing out along the error diagnostic
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.
Re: static assert not printing out along the error diagnostic
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) always misses the last grapheme‐cluster … possible phobos bug # 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) { /// if these were possible there will be no need for stringUGC## 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!(type
Re: static assert not printing out along the error diagnostic
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` ```
Re: static assert not printing out along the error diagnostic
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 firs