Jump to page: 1 2
Thread overview
static assert not printing out along the error diagnostic
Jul 14, 2021
someone
Jul 14, 2021
Paul Backus
Jul 14, 2021
someone
Jul 14, 2021
SealabJaster
Jul 14, 2021
SealabJaster
Jul 14, 2021
jfondren
Jul 14, 2021
someone
Jul 14, 2021
jfondren
Jul 14, 2021
someone
Jul 14, 2021
jfondren
Jul 15, 2021
someone
July 14, 2021

The following example is from Ali's book @ http://ddili.org/ders/d.en/cond_comp.html:

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():

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

I get:

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

But nothing like:

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.

July 14, 2021

On Wednesday, 14 July 2021 at 02:28:07 UTC, someone wrote:

>

If I add in main():

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

I get:

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

But nothing like:

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.

July 14, 2021

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:

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:

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.

July 14, 2021

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:

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();
}
July 14, 2021

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:

// 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(){...}
}
July 14, 2021

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:

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
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:

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
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
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...

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`
July 14, 2021

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
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 ?

July 14, 2021

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
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:

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.

July 14, 2021

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
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:

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):

/// 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!(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) {

             /// eg#1: takeasUTF(1,3) → range#1=start-1=1-1=0 and range#2=range#1+count=0+3=3 → 0..3
             /// eg#1: takeasUTF(6,3) → range#2=start-1=6-1=5 and range#2=range#1+count=5+3=8 → 5..8

             /// eg#2: takeasUTF(01,1) → range#1=start-1=01-1=00 and range#2=range#1+count=00+1=01 → 00..01
             /// eg#2: takeasUTF(50,1) → range#2=start-1=50-1=49 and range#2=range#1+count=49+1=50 → 49..50

             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); /// ooops … always missing last grapheme‐cluster: possible bug # 20483

       }

       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);

}
July 14, 2021

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:

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

which call stringUGC32, an alias:

alias stringUGC32 = gudtUGC!(stringUTF32);
alias stringUTF32 = dstring; /// same as immutable(dchar)[];

which explicitly instantiates gudtUGC with the type dstring,

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:

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:

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:

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:

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`
« First   ‹ Prev
1 2