Wolfgang Thallergcc 15.1.0ada185d1 year agoHistory
587 lines · 17.1 KB · D
RawDownload
/* * Support for rich error messages generation with `assert` * * This module provides the `_d_assert_fail` hooks which are instantiated * by the compiler whenever `-checkaction=context` is used. * There are two hooks, one for unary expressions, and one for binary. * When used, the compiler will rewrite `assert(a >= b)` as * `assert(a >= b, _d_assert_fail!(typeof(a))(">=", a, b))`. * Temporaries will be created to avoid side effects if deemed necessary * by the compiler. * * For more information, refer to the implementation in DMD frontend * for `AssertExpression`'s semantic analysis. * * Copyright: D Language Foundation 2018 - 2020 * License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source:    $(DRUNTIMESRC core/internal/_dassert.d) * Documentation: https://dlang.org/phobos/core_internal_dassert.html */module core.internal.dassert; /** * Generates rich assert error messages for unary expressions * * The unary expression `assert(!una)` will be turned into * `assert(!una, _d_assert_fail("!", una))`. * This routine simply acts as if the user wrote `assert(una == false)`. * * Params: *   op = Operator that was used in the expression, currently only "!" *        is supported. *   a  = Result of the expression that was used in `assert` before *        its implicit conversion to `bool`. * * Returns: *   A string such as "$a != true" or "$a == true". */string _d_assert_fail(A)(const scope string op, auto ref const scope A a){    // Prevent InvalidMemoryOperationError when triggered from a finalizer    if (inFinalizer())        return "Assertion failed (rich formatting is disabled in finalizers)";     string[2] vals = [ miniFormatFakeAttributes(a), "true" ];    immutable token = op == "!" ? "==" : "!=";    return combine(vals[0 .. 1], token, vals[1 .. $]);} /** * Generates rich assert error messages for binary expressions * * The binary expression `assert(x == y)` will be turned into * `assert(x == y, _d_assert_fail!(typeof(x))("==", x, y))`. * * Params: *   comp = Comparison operator that was used in the expression. *   a  = Left hand side operand (can be a tuple). *   b  = Right hand side operand (can be a tuple). * * Returns: *   A string such as "$a $comp $b". */template _d_assert_fail(A...){    string _d_assert_fail(B...)(        const scope string comp, auto ref const scope A a, auto ref const scope B b)    if (B.length != 0 || A.length != 1) // Resolve ambiguity with unary overload    {        // Prevent InvalidMemoryOperationError when triggered from a finalizer        if (inFinalizer())            return "Assertion failed (rich formatting is disabled in finalizers)";         string[A.length + B.length] vals;        static foreach (idx; 0 .. A.length)            vals[idx] = miniFormatFakeAttributes(a[idx]);        static foreach (idx; 0 .. B.length)            vals[A.length + idx] = miniFormatFakeAttributes(b[idx]);        immutable token = invertCompToken(comp);        return combine(vals[0 .. A.length], token, vals[A.length .. $]);    }} /// Combines the supplied arguments into one string `"valA token valB"`private string combine(const scope string[] valA, const scope string token,    const scope string[] valB) pure nothrow @nogc @safe{    // Each separator is 2 chars (", "), plus the two spaces around the token.    size_t totalLen = (valA.length - 1) * 2 +        (valB.length - 1) * 2 + 2 + token.length;     // Empty arrays are printed as ()    if (valA.length == 0) totalLen += 2;    if (valB.length == 0) totalLen += 2;     foreach (v; valA) totalLen += v.length;    foreach (v; valB) totalLen += v.length;     // Include braces when printing tuples    const printBraces = (valA.length + valB.length) != 2;    if (printBraces) totalLen += 4; // '(', ')' for both tuples     char[] buffer = cast(char[]) pureAlloc(totalLen)[0 .. totalLen];    // @nogc-concat of "<valA> <comp> <valB>"    static void formatTuple (scope char[] buffer, ref size_t n, in string[] vals, in bool printBraces)    {        if (printBraces) buffer[n++] = '(';        foreach (idx, v; vals)        {            if (idx)            {                buffer[n++] = ',';                buffer[n++] = ' ';            }            buffer[n .. n + v.length] = v;            n += v.length;        }        if (printBraces) buffer[n++] = ')';    }     size_t n;    formatTuple(buffer, n, valA, printBraces);    buffer[n++] = ' ';    buffer[n .. n + token.length] = token;    n += token.length;    buffer[n++] = ' ';    formatTuple(buffer, n, valB, printBraces);    return (() @trusted => cast(string) buffer)();} /// Yields the appropriate `printf` format token for a type `T`private template getPrintfFormat(T){    static if (is(T == long))    {        enum getPrintfFormat = "%lld";    }    else static if (is(T == ulong))    {        enum getPrintfFormat = "%llu";    }    else static if (__traits(isIntegral, T))    {        static if (__traits(isUnsigned, T))        {            enum getPrintfFormat = "%u";        }        else        {            enum getPrintfFormat = "%d";        }    }    else    {        static assert(0, "Unknown format");    }} /** * Generates a textual representation of `v` without relying on Phobos. * The value is formatted as follows: * *  - primitive types and arrays yield their respective literals *  - pointers are printed as hexadecimal numbers *  - enum members are represented by their name *  - user-defined types are formatted by either calling `toString` *    if defined or printing all members, e.g. `S(1, 2)` * * Note that unions are rejected because this method cannot determine which * member is valid when calling this method. * * Params: *   v = the value to print * * Returns: a string respresenting `v` or `V.stringof` if `V` is not supported */private string miniFormat(V)(const scope ref V v){    import core.internal.traits: isAggregateType;     /// `shared` values are formatted as their base type    static if (is(V == shared T, T))    {        import core.atomic : atomicLoad;         // Use atomics to avoid race conditions whenever possible        static if (__traits(compiles, atomicLoad(v)))        {            if (!__ctfe)            {                T tmp = cast(T) atomicLoad(v);                return miniFormat(tmp);            }        }         // Fall back to a simple cast - we're violating the type system anyways        return miniFormat(*cast(const T*) &v);    }    // Format enum members using their name    else static if (is(V BaseType == enum))    {        // Always generate repeated if's instead of switch to skip the detection        // of non-integral enums. This method doesn't need to be fast.        static foreach (mem; __traits(allMembers, V))        {            if (v == __traits(getMember, V, mem))                return mem;        }         // Format invalid enum values as their base type        enum cast_ = "cast(" ~ V.stringof ~ ")";        const val = miniFormat(__ctfe ? cast(const BaseType) v : *cast(const BaseType*) &v);        return combine([ cast_ ], "", [ val ]);    }    else static if (is(V == bool))    {        return v ? "true" : "false";    }    // Detect vectors which match isIntegral / isFloating    else static if (is(V == __vector(ET[N]), ET, size_t N))    {        string msg = "[";        foreach (i; 0 .. N)        {            if (i > 0)                msg ~= ", ";             msg ~= miniFormat(v[i]);        }        msg ~= "]";        return msg;    }    else static if (__traits(isIntegral, V))    {        static if (is(V == char))        {            // Avoid invalid code points            if (v < 0x7F)                return ['\'', v, '\''];             uint tmp = v;            return "cast(char) " ~ miniFormat(tmp);        }        else static if (is(V == wchar) || is(V == dchar))        {            import core.internal.utf: isValidDchar, toUTF8;             // Avoid invalid code points            if (isValidDchar(v))                return toUTF8(['\'', v, '\'']);             uint tmp = v;            return "cast(" ~ V.stringof ~ ") " ~ miniFormat(tmp);        }        else        {            import core.internal.string;            static if (__traits(isUnsigned, V))                const val = unsignedToTempString(v);            else                const val = signedToTempString(v);            return val.get().idup();        }    }    else static if (__traits(isFloating, V))    {        import core.stdc.stdio : sprintf;        import core.stdc.config : LD = c_long_double;         // No suitable replacement for sprintf in druntime ATM        if (__ctfe)            return '<' ~ V.stringof ~ " not supported>";         // Workaround for https://issues.dlang.org/show_bug.cgi?id=20759        static if (is(LD == real))            enum realFmt = "%Lg";        else            enum realFmt = "%g";         char[60] val;        int len;        static if (is(V == float) || is(V == double))            len = sprintf(&val[0], "%g", v);        else static if (is(V == real))            len = sprintf(&val[0], realFmt, cast(LD) v);        else static if (is(V == cfloat) || is(V == cdouble))            len = sprintf(&val[0], "%g + %gi", v.re, v.im);        else static if (is(V == creal))            len = sprintf(&val[0], realFmt ~ " + " ~ realFmt ~ 'i', cast(LD) v.re, cast(LD) v.im);        else static if (is(V == ifloat) || is(V == idouble))            len = sprintf(&val[0], "%gi", v);        else // ireal        {            static assert(is(V == ireal));            static if (is(LD == real))                alias R = ireal;            else                alias R = idouble;            len = sprintf(&val[0], realFmt ~ 'i', cast(R) v);        }        return val.idup[0 .. len];    }    // special-handling for void-arrays    else static if (is(V == typeof(null)))    {        return "`null`";    }    else static if (is(V == U*, U))    {        // Format as ulong and prepend a 0x for pointers        import core.internal.string;        return cast(immutable) ("0x" ~ unsignedToTempString!16(cast(ulong) v));    }    // toString() isn't always const, e.g. classes inheriting from Object    else static if (__traits(compiles, { string s = V.init.toString(); }))    {        // Object references / struct pointers may be null        static if (is(V == class) || is(V == interface))        {            if (v is null)                return "`null`";        }         try        {            // Prefer const overload of toString            static if (__traits(compiles, { string s = v.toString(); }))                return v.toString();            else                return (cast() v).toString();        }        catch (Exception e)        {            return `<toString() failed: "` ~ e.msg ~ `", called on ` ~ formatMembers(v) ~`>`;        }    }    // Static arrays or slices (but not aggregates with `alias this`)    else static if (is(V : U[], U) && !isAggregateType!V)    {        import core.internal.traits: Unqual;        alias E = Unqual!U;         // special-handling for void-arrays        static if (is(E == void))        {            if (__ctfe)                return "<void[] not supported>";             const bytes = cast(byte[]) v;            return miniFormat(bytes);        }        // anything string-like        else static if (is(E == char) || is(E == dchar) || is(E == wchar))        {            const s = `"` ~ v ~ `"`;             // v could be a char[], dchar[] or wchar[]            static if (is(typeof(s) : const char[]))                return cast(immutable) s;            else            {                import core.internal.utf: toUTF8;                return toUTF8(s);            }        }        else        {            string msg = "[";            foreach (i, ref el; v)            {                if (i > 0)                    msg ~= ", ";                 // don't fully print big arrays                if (i >= 30)                {                    msg ~= "...";                    break;                }                msg ~= miniFormat(el);            }            msg ~= "]";            return msg;        }    }    else static if (is(V : Val[K], K, Val))    {        size_t i;        string msg = "[";        foreach (ref k, ref val; v)        {            if (i > 0)                msg ~= ", ";            // don't fully print big AAs            if (i++ >= 30)            {                msg ~= "...";                break;            }            msg ~= miniFormat(k) ~ ": " ~ miniFormat(val);        }        msg ~= "]";        return msg;    }    else static if (is(V == struct))    {        return formatMembers(v);    }    // Extern C++ classes don't have a toString by default    else static if (is(V == class) || is(V == interface))    {        if (v is null)            return "null";         // Extern classes might be opaque        static if (is(typeof(v.tupleof)))            return formatMembers(v);        else            return '<' ~ V.stringof ~ '>';    }    else    {        return V.stringof;    }} /// Formats `v`'s members as `V(<member 1>, <member 2>, ...)`private string formatMembers(V)(const scope ref V v){    enum ctxPtr = __traits(isNested, V);    enum isOverlapped = calcFieldOverlap([ v.tupleof.offsetof ]);     string msg = V.stringof ~ "(";    foreach (i, ref field; v.tupleof)    {        if (i > 0)            msg ~= ", ";         static if (isOverlapped[i])        {            msg ~= "<overlapped field>";        }        else        {            // Mark context pointer            static if (ctxPtr && i == v.tupleof.length - 1)                msg ~= "<context>: ";             msg ~= miniFormat(field);        }    }    msg ~= ")";    return msg;} /** * Calculates whether fields are overlapped based on the passed offsets. * * Params: *   offsets = offsets of all fields matching the order of `.tupleof` * * Returns: an array such that arr[n] = true indicates that the n'th field *          overlaps with an adjacent field **/private bool[] calcFieldOverlap(const scope size_t[] offsets){    bool[] overlaps = new bool[](offsets.length);     foreach (const idx; 1 .. overlaps.length)    {        if (offsets[idx - 1] == offsets[idx])            overlaps[idx - 1] = overlaps[idx] = true;    }     return overlaps;} /// Negates a comparison token, e.g. `==` is mapped to `!=`private string invertCompToken(scope string comp) pure nothrow @nogc @safe{    switch (comp)    {        case "==":            return "!=";        case "!=":            return "==";        case "<":            return ">=";        case "<=":            return ">";        case ">":            return "<=";        case ">=":            return "<";        case "is":            return "!is";        case "!is":            return "is";        case "in":            return "!in";        case "!in":

Lines 1–500 of 587.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t