__traits(compiles, ...) with syntax errors

Steven Schveighoffer schveiguy at gmail.com
Tue Jan 15 01:59:58 UTC 2019


On 1/14/19 6:58 PM, H. S. Teoh wrote:
> On Mon, Jan 14, 2019 at 04:31:06PM -0700, Jonathan M Davis via Digitalmars-d wrote:
>> On Monday, January 14, 2019 2:57:28 PM MST Paul Backus via Digitalmars-d
>> wrote:
>>> On Monday, 14 January 2019 at 21:47:40 UTC, Steven Schveighoffer
>>>
>>> wrote:
>>>> How many times have you written something like:
>>>>
>>>> void foo(T)(T t) if (__traits(compiles, t.bar())) // or
>>>> is(typeof(t.bar()))
>>>> {
>>>>
>>>>     t.bar();
>>>>
>>>> }
>>>>
>>>> And somehow, somewhere, this isn't called? Then you remove the
>>>> constraint, and find that there's a syntax error in the bar
>>>> template function (like a missing semicolon).
> 
> Yeah, I've run into this problem far too many times.  It's extremely
> annoying because the error is silent, and makes the compiler just move
> on to a different overload, leaving you scratching your head as to why
> it's not picking the "correct" overload.
> 
> 
>>> Ideally, you would catch this in the unit tests for `bar`. You are
>>> writing unit tests, aren't you? ;)
> [...]
> 
> The problem is not just trivial syntax errors like Steven's example.
> The problem is that sufficiently complex sig constraints will sometimes
> have unexpected corner cases that works in general, but fails for
> specific cases.
> 
> One nasty example that comes to mind is when the sig constraint only
> works with mutable types (or some other such qualified type) because you
> used an overly-restrictive test.  Initial unittests work because you're
> not expecting that changing the input to const would cause any trouble.
> Then some time later, you happen to need to pass a const object, and
> suddenly the compiler seemingly refuses to acknowledge that this
> overload of the template function exists, and insists on trying a
> different overload, or failing with a totally unhelpful error saying no
> overloads match.

So to clarify a bit, I'm not expecting miracles here. If your code is 
valid, but just isn't callable in the way you expect it to be, I don't 
see how the compiler can figure out that you meant it another way.

But my overall point really is that we have a mechanism to say "Does 
this compile?", when we really mean "Can I use this thing this way?". I 
wondered if it wouldn't be worth having a way to assume that all code is 
syntactically valid, and just throw errors when it's not.

And now that I'm thinking about it some more, I'm completely wrong. It 
is little typos that are syntactically valid that fail to compile -- 
syntax errors are still flagged (unless you are using mixins).

So this doesn't help at all :(

But I still face the same problems you bring up.

> (Oh, and did I mention, almost all of the above requires you to edit the
> *callee* code in order to find out where the template instantiation
> went? Good luck if it goes into some common templates used everywhere
> else, that if you change one line, something else unrelated in your
> program will stop compiling so you won't even get to the point of
> failure in the first place. Sometimes the only solution here is to
> copy-n-paste the entire lousy template function into a different module
> (possibly along with most/all of its dependencies) and call *that*
> instead, so that the compiler won't prematurely die in unrelated code.)

Oh yeah, this is a really hard problem. This captures a lot of the 
frustration that I've had too.

I'm wondering instead of having a way to help with this, if we can't 
specify (for debugging purposes), "this overload should be used". In 
other words, get rid of the constraints and force the compiler to use a 
certain call. That would help IMMENSELY.

I'm actually thinking of a library mechanism to do this, which is 
horrible, and awful, but might actually work:

struct PickMe
{
    string file;
    size_t line;
}

enum isMyOverload(...) more cruft

enum ItPickedMe(T, string file == __FILE__, size_t line == __LINE__) = 
hasUDA!(PickMe) && anySatisfy!(isMyOverload, getUDAs!(T, PickMe))

void foo(T)(T t) if(ItPickedMe!T || __traits(compiles, t.bar()))
{
    t.bar();
}

I don't want to do this, but a compiler feature that allowed it would be 
cool. Having to make copies of templates to be able to debug things is 
so horrid.

-Steve


More information about the Digitalmars-d mailing list