Difference between revisions of "DIP83"

From D Wiki
Jump to: navigation, search
Line 63: Line 63:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
and similarly for unary expressions replace
+
and similarly, for unary expressions, rewrite (lower)
  
 
<syntaxhighlight lang="d">
 
<syntaxhighlight lang="d">
Line 78: Line 78:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
where on AssertFailed, in this unary operator case, is declared as
+
where on onAssertFailed, in this unary operator case, is declared as
  
 
<syntaxhighlight lang="d">
 
<syntaxhighlight lang="d">
Line 84: Line 84:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
The default implementation of '''assertBinOp''' (preferrably in druntime) would be a function that mimics the current behaviour of '''assert(lhs BINOP rhs)''' typically as
+
The default implementation of '''onAssertFailed''' (preferrably in druntime) could be a function that does nothing by default.
  
<syntaxhighlight lang="d">
+
Specific printing behaviour can then be extendable by adding (typically templated) overloads of '''onAssertFailed''' for specific sets of types (concepts).
void assertBinOp(string file, uint line, string module, string binOp)(L, R)(lazy L lhs, lazy R rhs, string message)
 
{
 
    version(assert)
 
    {
 
        const lhsValue = lhs();
 
        const rhsValue = rhs();
 
        if (!(mixin("lhsValue " ~ binOp ~ " rhsValue")))
 
        {
 
            import core.exception : AssertError;
 
            throw AssertError("Failed assert");
 
        }
 
    }
 
}
 
</syntaxhighlight>
 
 
 
 
 
The Phobos-developer can then do what he likes with the information he needs in the extra arguments in specific templated overloads of '''assertBinOp'''.
 
 
 
This specific behaviour could be extendable by adding (typically templated) overloads of '''assertBinOp''' for specific sets of types (concepts).
 
  
 
For instance, better diagnostics for comparing arithmetic types can be realized through the overload
 
For instance, better diagnostics for comparing arithmetic types can be realized through the overload
Line 111: Line 92:
 
<syntaxhighlight lang="d">
 
<syntaxhighlight lang="d">
 
import std.traits : isArithmetic;
 
import std.traits : isArithmetic;
void assertBinOp(string file, uint line, string module, string binOp)(L, R)(lazy L lhs, lazy R rhs, string message)
+
void onAssertFailed(string op)(e1, e2, const string file, uint line, string function, string module)
 
     if (isArithmetic!L && isArithmetic!R)
 
     if (isArithmetic!L && isArithmetic!R)
 
{
 
{
 
     version(assert)
 
     version(assert)
 
     {
 
     {
         const lhsValue = lhs();
+
         import core.exception : AssertError;
        const rhsValue = rhs();
+
        throw AssertError("Failed arithmetic assert: " ~ e1 ~ " " ~ binOp ~ " " ~ e2);
        if (!(mixin("lhsValue " ~ binOp ~ " rhsValue")))
 
        {
 
            import core.exception : AssertError;
 
            throw AssertError("Failed arithmetic assert: " ~ lhsValue ~ " " ~ binOp ~ " " ~ rhsValue);
 
        }
 
 
     }
 
     }
 
}
 
}

Revision as of 10:40, 5 October 2015

Title: Configurable Assert Diagnostics
DIP: 83
Version: 1
Status: Draft
Created: 2015-10-01
Last Modified: 2015-10-5
Author: Per Nordlöw
Links:

Abstract

Allow for assert to do pretty printing of its failing expression when flagged for in call to compiler. Printing is configurable via specific sets of (template) function overloads.

Rationale

A failing assert (in a unittest), currently, give no hint about why it failed. To aid the developer in debugging, the failing assert of a

  • binary expression, such as assert(x == y), should print the values of x and y and
  • unary expression, such as assert(!x), should print the value of x.

This extra, so called, pretty printing can be enabled by changing the dmd flag -unittest to, say, -unittest=verbose for a specific failing module. This will be more convenient than explicitly adding the prints of the left-hand-side expression lhs and right-hand-side expression rhs directly before the call to the failing assert.

Description

This DIP proposes to add library-level-configurable diagnostics to failing calls to assert(expr) typically called from within unittest-blocks.

This diagnostics is activated only when DMD is called with a specific command line flag, say -unittest=verbose or perhaps -diagnose=assert.

If DMD is called with this flag it will rewrite (lower) all assert expressions (AssertExpr in dmd source) such as

assert(a BINOP b)

into

(auto ref a, auto ref b) {
    if (a BINOP b) return;
    onAssertFailed!"BINOP"(a, b, __FILE__, __LINE__, __FUNCTION__, __MODULE__);
} (e1, e2)

where onAssertFailed, in this binary operator case, is declared as

void onAssertFailed(string op)(e1, e2, const string file, uint line, string function, string module);

and similarly, for unary expressions, rewrite (lower)

assert(UNOP e)

with

(auto ref e) {
    if (UNOP e) return;
    onAssertFailed!"UNOP"(e, __FILE__, __LINE__, __FUNCTION__, __MODULE__);
} (e)

where on onAssertFailed, in this unary operator case, is declared as

void onAssertFailed(string op)(e, const string file, uint line, string function, string module);

The default implementation of onAssertFailed (preferrably in druntime) could be a function that does nothing by default.

Specific printing behaviour can then be extendable by adding (typically templated) overloads of onAssertFailed for specific sets of types (concepts).

For instance, better diagnostics for comparing arithmetic types can be realized through the overload

import std.traits : isArithmetic;
void onAssertFailed(string op)(e1, e2, const string file, uint line, string function, string module)
    if (isArithmetic!L && isArithmetic!R)
{
    version(assert)
    {
        import core.exception : AssertError;
        throw AssertError("Failed arithmetic assert: " ~ e1 ~ " " ~ binOp ~ " " ~ e2);
    }
}

In this way D would get the extendability we want in testing-frameworks such as std.experimental.testing (https://github.com/D-Programming-Language/phobos/pull/3207) without adding a new assert-overload-set and without sacrifycing default memory usage in DMD/Phobos unittests.

Further, this solution enables the possibility to provide fancy diagnostics behaviour in assertBinOp for failing array/range or aggregate (struct or class) comparisons. This diagnostics could also have different pretty printing backends such as HTML.

For example a failing

assert([1,2,3] == [1,2,4]);

could pretty-print

([1,2,3][2] is 3) != ([1,2,4][2] is 4)

or, for aggregates, a failing

struct A { int x, y; }
auto a = A(1,2);
auto b = A(1,3);
assert(a == b);

could pretty-print

(a.y is 2) != (b.y is 3)

Parts of the solution list at

https://issues.dlang.org/show_bug.cgi?id=5547#c3

including rewriting/expansion of AssertExpr could probably reused.

Discussions on topic

Copyright

This document has been placed in the Public Domain.