Thread overview
idea: static scope(failure)
Jan 26, 2022
rikki cattermole
Apr 20, 2022
max haughton
January 25, 2022

Just recently running into some problems where deeply nested inside some library code, a static assert triggers. Of course, the static assert doesn't give any context with which it was called from my code.

And the compiler helpfully says something like (names changed to protect the proprietary):

somelibrary.d(263,16):        instantiated from here: `foo!(Json[string])`
somelibrary.d(286,25):        instantiated from here: `bar!(Json[string])`
somelibrary.d(204,16):        instantiated from here: `baz!(Json)`
somelibrary.d(263,16):        ... (1 instantiations, -v to show) ...

OK, so it stopped telling me useful information. And on top of that, the information isn't always enough to determine the cause, because in one of these instantiations, there's probably a static foreach, where I don't know which element being looped over is actually the cause.

If this was runtime I'd do something like:

scope(failure) writeln("Context: ", relevant, info);

And then when the exception is thrown, I can see some context. But I can't do that at compile time.

What I've done instead is:

static if(!__traits(compiles, entireLineOfCode)) pragma(msg, "Context: ", relevant, info);
entireLineOfCode;

Which is OK, but the place where I might want to print the context might not be for just one line of code, you might have to repeat an entire mess of stuff. And I was thinking, wouldn't it be cool to just do something like:

static scope(failure) pragma(msg, stuff);

Then, no matter what the context, or where it's failing, I can ensure that on a failure to compile, I can tease out some debug info at the relevant spot.

another possible syntax is something like:

pragma(msgOnFailure, stuff);

Which might even be gagged if the compiler isn't actually failing (i.e. this is inside a __traits(compiles))

Does this (or something like it) make sense?

-Steve

January 26, 2022
Perhaps this can be simplified somewhat.

version(compiles) {
	...
} else {
	string str = __errorMessage;
	writeln(str);
}
January 25, 2022

On 1/25/22 9:07 PM, rikki cattermole wrote:

>

Perhaps this can be simplified somewhat.

version(compiles) {
    ...
} else {
    string str = __errorMessage;
    writeln(str);
}

While this might be feasible, it suffers from the same problem as manually writing out the try/catch/finally blocks that scope(...) lowers to -- You need to enblock all the covered code, add indentation, modify lines that are far away (e.g. to add a brace) etc.

That being said, I'd be happy with anything that helps here.

-Steve

April 19, 2022

On 1/25/22 8:58 PM, Steven Schveighoffer wrote:

>

Just recently running into some problems where deeply nested inside some library code, a static assert triggers. Of course, the static assert doesn't give any context with which it was called from my code.

And the compiler helpfully says something like (names changed to protect the proprietary):

somelibrary.d(263,16):        instantiated from here: `foo!(Json[string])`
somelibrary.d(286,25):        instantiated from here: `bar!(Json[string])`
somelibrary.d(204,16):        instantiated from here: `baz!(Json)`
somelibrary.d(263,16):        ... (1 instantiations, -v to show) ...

OK, so it stopped telling me useful information. And on top of that, the information isn't always enough to determine the cause, because in one of these instantiations, there's probably a static foreach, where I don't know which element being looped over is actually the cause.

Just ran into this again (same project, ironically).

I just had a new idea that maybe could be even better -- the lines above specify the line the instantiation happened, and what the template parameters are. But what is really missing is the other static information. Specifically, which static foreach loop iteration it's in. With that, I could piece together what is happening, and no special code is necessary to be added to get more diagnostic information.

Maybe only output when -v is specified? One nice thing about failed compilation, you can just do it again and it is repeatable.

-Steve

April 20, 2022

On Wednesday, 26 January 2022 at 01:58:23 UTC, Steven Schveighoffer wrote:

>

Just recently running into some problems where deeply nested inside some library code, a static assert triggers. Of course, the static assert doesn't give any context with which it was called from my code.

And the compiler helpfully says something like (names changed to protect the proprietary):

somelibrary.d(263,16):        instantiated from here: `foo!(Json[string])`
somelibrary.d(286,25):        instantiated from here: `bar!(Json[string])`
somelibrary.d(204,16):        instantiated from here: `baz!(Json)`
somelibrary.d(263,16):        ... (1 instantiations, -v to show) ...

OK, so it stopped telling me useful information. And on top of that, the information isn't always enough to determine the cause, because in one of these instantiations, there's probably a static foreach, where I don't know which element being looped over is actually the cause.

If this was runtime I'd do something like:

scope(failure) writeln("Context: ", relevant, info);

And then when the exception is thrown, I can see some context. But I can't do that at compile time.

What I've done instead is:

static if(!__traits(compiles, entireLineOfCode)) pragma(msg, "Context: ", relevant, info);
entireLineOfCode;

Which is OK, but the place where I might want to print the context might not be for just one line of code, you might have to repeat an entire mess of stuff. And I was thinking, wouldn't it be cool to just do something like:

static scope(failure) pragma(msg, stuff);

Then, no matter what the context, or where it's failing, I can ensure that on a failure to compile, I can tease out some debug info at the relevant spot.

another possible syntax is something like:

pragma(msgOnFailure, stuff);

Which might even be gagged if the compiler isn't actually failing (i.e. this is inside a __traits(compiles))

Does this (or something like it) make sense?

-Steve

As steve and I have been discussing on the D discord, I've implemented this locally and had a play with it.

void main()
{
    string x;
    int y;
    set(x, y);
}

void set(T...)(ref T x)
{
    foreach(idx, elem; x)
    {
        elem = "a string";
        static scope(failure)
            static assert(0, "I couldn't set tuple[" ~ idx.stringof ~ "] equal to \"string\"");
    }
}

works with basically 10 lines of code added to the compiler.