Difference between revisions of "DIP83"
Line 129: | Line 129: | ||
== Examples of Possible Diagnostics Outputs == | == Examples of Possible Diagnostics Outputs == | ||
+ | |||
+ | === Scalar Assert Arguments === | ||
For example a failing | For example a failing | ||
Line 141: | Line 143: | ||
([1,2,3][2] is 3) != ([1,2,4][2] is 4) | ([1,2,3][2] is 3) != ([1,2,4][2] is 4) | ||
</syntaxhighlight> | </syntaxhighlight> | ||
+ | |||
+ | === Aggregate Assert Arguments === | ||
or, for aggregates, a failing | or, for aggregates, a failing | ||
Line 156: | Line 160: | ||
(a.y is 2) != (b.y is 3) | (a.y is 2) != (b.y is 3) | ||
</syntaxhighlight> | </syntaxhighlight> | ||
+ | |||
+ | === Array Assert Arguments === | ||
or, for arrays, a failing | or, for arrays, a failing |
Revision as of 18:12, 26 October 2015
Title: | Configurable Assert Diagnostics |
---|---|
DIP: | 83 |
Version: | 1 |
Status: | Draft |
Created: | 2015-10-01 |
Last Modified: | 2015-10-26 |
Author: | Per Nordlöw |
Links: |
Contents
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.
Binary Operator Lowering
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)(E1 e1, E2 e2, const string file, uint line, string function, string module);
Unary Operator Lowering
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)(E e, const string file, uint line, string function, string module);
Non-Operator Lowering
For the case when no unary or binary operator is present in the top assert expression, unary overload of onAssertFailed is called with op being empty string. In other words the assert expression
assert(e)
is rewritten (lowered) into
(auto ref e) {
if (e) return;
onAssertFailed!""(e, __FILE__, __LINE__, __FUNCTION__, __MODULE__);
} (e)
Configuring Diagnostics
Specific printing behaviour of assert() diagnostics can then be extendable by adding (typically templated) overloads of onAssertFailed for specific sets of types (concepts).
For instance, diagnostics specifically when comparing arithmetic types, could be realized through the overload
import std.traits : isArithmetic;
void onAssertFailed(string op, E1, E2)(E1 e1, E2 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 onAssertFailed for failing array/range or aggregate (struct or class) comparisons. This diagnostics could also have different pretty printing backends such as HTML.
Examples of Possible Diagnostics Outputs
Scalar Assert Arguments
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)
Aggregate Assert Arguments
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)
Array Assert Arguments
or, for arrays, a failing
const n = 1_000_000;
const a = iota(0, n).array;
const b = a.dup;
a[1_000 .. 1_002] = 0;
assert(a == b);
could pretty-print
(a[1_000 .. 1_0002] is [0, 0]) != (b[1_000 .. 1_0002] is [1_000, 1=001])
An optional `multiLine` flag could, when set, format output as
(a[1_000 .. 1_0002] is [0, 0]) !=
(b[1_000 .. 1_0002] is [1_000, 1=001])
to make comparison more visual.
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.