Jump to page: 1 24  
Page
Thread overview
Difference between "can call" and "can compile"
Sep 07, 2020
FeepingCreature
Sep 07, 2020
Stefan Koch
Sep 07, 2020
FeepingCreature
Sep 08, 2020
FeepingCreature
Sep 08, 2020
Paul Backus
Sep 08, 2020
Paul Backus
Sep 08, 2020
Paul Backus
Sep 08, 2020
H. S. Teoh
Sep 15, 2020
FeepingCreature
Sep 15, 2020
Paul Backus
Sep 16, 2020
Nick Treleaven
Sep 07, 2020
H. S. Teoh
Sep 07, 2020
Paul Backus
Sep 07, 2020
Paul Backus
Sep 08, 2020
H. S. Teoh
Sep 08, 2020
Andrej Mitrovic
Sep 15, 2020
WebFreak001
Sep 16, 2020
Jackel
Sep 16, 2020
FeepingCreature
Sep 16, 2020
WebFreak001
September 07, 2020
Consider a template function like this:

void foo(T)(T v)
{
   auto x = v.someProperty;
}

Now, I might create a constraint based on whether I can compile that function:

void bar(alias x)() if (__traits(compiles, x(1)))
{
   x(1);
}

now, if I try to call bar like:

bar!foo();

I get a compilation error. But it's not in foo, it's in bar. It says:

Error: template instance onlineapp.bar!(foo) does not match template declaration bar(alias x)()
  with x = foo(T)(T v)
  must satisfy the following constraint:
       __traits(compiles, x(1))

In this instance, the error message is straightforward. With a simple case like this, I can figure it out and move on.

However, in a more complex case, perhaps the constraint is encapsulated in a template. Perhaps that template basically checks if it can call it in various ways, with various types. Perhaps it depends on the context in which it was called.

And my alias I'm passing in is INTENDED to work. In fact, it might be specifically DESIGNED to work with this exact function. However, I don't get to see why it doesn't. I just get a "must satisfy the following constraint: isSomeConstraint!foo"

The problem is, if the intention is for it to pass, but the implementation is bugged, the error message is useless. If the thing I'm passing is a type with 200 LOC, and there's an error buried in there somewhere, the compiler is telling me "somewhere in this, it doesn't work". And sometimes those function signatures and their constraints are hairy themselves.

My usual steps taken at this point are to extract the constraint template and actually try to call the functions in the constraints with that type, and see what fails. This is somewhat annoying, and sometimes difficult to do based on where the code that implements the constraint actually is, and where the code actually calling it is (I may have to duplicate a lot of stuff).

But the thing is -- we already HAVE a constraint system that says whether the code should be callable or not -- the template constraint! In fact, we can declare right on foo that it should only compile if I can call someProperty on the value:

void foo(T)(T v) if (__traits(compiles, v.someProperty))
{
   auto x = v.someProperty;
}

However, for this extra bit of forethought, I get no different error messages.

I was wondering, would it be a useful addition to have a way to say "can call" instead of "can compile"? In other words, this has a valid template IFTI match, regardless of whether it compiles or not. In which case, you can distinguish mismatch errors from implementation errors.

In the example above, foo without the template constraint instantiated with int has a matched call, but the match doesn't compile. Whereas, the one with the template constraint doesn't have a match, and so the call itself will not compile. This difference could push the error down to where it really belongs, and save me the time of having to perform exploratory surgery on a library and its dependencies.

Thoughts?

-Steve
September 07, 2020
On Monday, 7 September 2020 at 14:57:24 UTC, Steven Schveighoffer wrote:
> I was wondering, would it be a useful addition to have a way to say "can call" instead of "can compile"? In other words, this has a valid template IFTI match, regardless of whether it compiles or not. In which case, you can distinguish mismatch errors from implementation errors.
>
> Thoughts?
>
> -Steve

Yes yes yes yes yes *yes yes*!

Please!!!!!

This would make template errors so unbelievably much better.
September 07, 2020
On Monday, 7 September 2020 at 15:08:42 UTC, FeepingCreature wrote:
> On Monday, 7 September 2020 at 14:57:24 UTC, Steven Schveighoffer wrote:
>> I was wondering, would it be a useful addition to have a way to say "can call" instead of "can compile"? In other words, this has a valid template IFTI match, regardless of whether it compiles or not. In which case, you can distinguish mismatch errors from implementation errors.
>>
>> Thoughts?
>>
>> -Steve
>
> Yes yes yes yes yes *yes yes*!
>
> Please!!!!!
>
> This would make template errors so unbelievably much better.

I can see the appeal of the feature.

Let me think about how difficult that would be to implement.


September 07, 2020
On Monday, 7 September 2020 at 15:24:23 UTC, Stefan Koch wrote:
> On Monday, 7 September 2020 at 15:08:42 UTC, FeepingCreature wrote:
>> On Monday, 7 September 2020 at 14:57:24 UTC, Steven Schveighoffer wrote:
>>> I was wondering, would it be a useful addition to have a way to say "can call" instead of "can compile"? In other words, this has a valid template IFTI match, regardless of whether it compiles or not. In which case, you can distinguish mismatch errors from implementation errors.
>>>
>>> Thoughts?
>>>
>>> -Steve
>>
>> Yes yes yes yes yes *yes yes*!
>>
>> Please!!!!!
>>
>> This would make template errors so unbelievably much better.
>
> I can see the appeal of the feature.
>
> Let me think about how difficult that would be to implement.

That would be so, so awesome.

Let me clarify how important this is to me: I actually had a section at the end of my proposed DConf Online talk where I was gonna beg the devs to add this exact feature. In cases where you want a template function to match, but *maybe* will be fine if it doesn't match, so you have to keep going even if it doesn't __traits(compiles), this can be the difference between half an hour of debugging and instantly being told the (usually trivial) problem.

To explain:

You can pass `serialized`'s decode functions a helper function that can be used to decode problematic leaf types like `SysTime` or UUID in a protocol-specific fashion. However, if that helper doesn't match, we still want to keep going and try our default decode logic.

So how do we check if a helper matches? For instance, the helper may be

SysTime decode(T : SysTime)(const string attribute) { return SysTime.fromISOExtString(attribute); }

And then internally I do static if (__traits(compiles, decode!SysTime)).

But say that I forgot to import std.datetime. Then __traits(compiles) will return false and I will have no idea what happened. What I actually *want* is __traits(canInstantiate, decode, SysTime) - check if the template constraints can be satisfied enough to find an unambiguous template match. If I then try to instantiate the template and the compiler raises an error, that's okay! That's an error I want. I want that error. Give me that error. Please. :)

(Note that this doesn't *exactly* reduce to a function call. An important part here is that I don't know what the function will be called with; I have to instantiate the function template so that I can check what parameters it requires, which I'll then recursively try to decode.)
September 07, 2020
On Mon, Sep 07, 2020 at 10:57:24AM -0400, Steven Schveighoffer via Digitalmars-d wrote: [...]
> I was wondering, would it be a useful addition to have a way to say "can call" instead of "can compile"? In other words, this has a valid template IFTI match, regardless of whether it compiles or not. In which case, you can distinguish mismatch errors from implementation errors.
[...]

Yes, yes, and yes!  This could save hours of hair-tearing frustration when trying to debug a long UFCS chain where there's a typo in one of the lambdas.  Having it fail the sig constraint because of a typo means you get an undecipherable error in the innards of Phobos, far away from the actual problem, rather than an error in the lambda's body in user code (which has been gagged because it just so happens to be inside __traits(compiles) or is(typeof(...))).


T

-- 
Always remember that you are unique. Just like everybody else. -- despair.com
September 07, 2020
On 9/7/20 11:48 AM, FeepingCreature wrote:
> But say that I forgot to import std.datetime. Then __traits(compiles) will return false and I will have no idea what happened. What I actually *want* is __traits(canInstantiate, decode, SysTime) - check if the template constraints can be satisfied enough to find an unambiguous template match. If I then try to instantiate the template and the compiler raises an error, that's okay! That's an error I want. I want that error. Give me that error. Please. :)
> 
> (Note that this doesn't *exactly* reduce to a function call. An important part here is that I don't know what the function will be called with; I have to instantiate the function template so that I can check what parameters it requires, which I'll then recursively try to decode.)

This is somewhat different than what I was proposing. However, it's very closely related.

I absolutely need it to be matched with a function call, I don't want to have to do type acrobatics or check for UFCS, or whatever.

However, I think both our needs are satisfied if the feature is something like checking to see that all symbols are resolved and that all instantiations are unambiguous. What happens after that should be considered a normal ungagged error.

If I can pull out a non-template analogy, imagine you had code like:

int foo()
{
   blahblah(); // unresolved symbol
}

int bar()
{
   return foo();
}

And instead of the error on the "blahblah" line, you got an error that foo doesn't exist, and so bar cannot be compiled. This is kind of how the current template state of affairs is. I want the same benefits for template code that non-template code has.

-Steve
September 07, 2020
On Monday, 7 September 2020 at 14:57:24 UTC, Steven Schveighoffer wrote:
> I get a compilation error. But it's not in foo, it's in bar. It says:
>
> Error: template instance onlineapp.bar!(foo) does not match template declaration bar(alias x)()
>   with x = foo(T)(T v)
>   must satisfy the following constraint:
>        __traits(compiles, x(1))
>
> [...]
>
> The problem is, if the intention is for it to pass, but the implementation is bugged, the error message is useless. If the thing I'm passing is a type with 200 LOC, and there's an error buried in there somewhere, the compiler is telling me "somewhere in this, it doesn't work". And sometimes those function signatures and their constraints are hairy themselves.
>
> My usual steps taken at this point are to extract the constraint template and actually try to call the functions in the constraints with that type, and see what fails. This is somewhat annoying, and sometimes difficult to do based on where the code that implements the constraint actually is, and where the code actually calling it is (I may have to duplicate a lot of stuff).

The tedious-but-reliable way to handle this is to recompile with `-verrors=spec`, search the output for the original error message, and then scroll up until you find what you're looking for in the speculative errors. The only issue is that you have to wade through a lot of useless chaff to find the information you actually care about.

IMO the best way to make this easier would be to have the compiler provide a "stack trace" of speculative errors for each failed constraint whenever a template fails to instantiate--maybe behind a switch (`-verrors=constraints`) to avoid clogging up the output in the common case.

This has the advantage of working for *all* indirect errors like this, not just ones involving IFTI. To give a concrete example, `-verrors=constraints` would have helped me a lot with the problems I had to debug in SumType's copy constructors recently [1], whereas __traits(canCall) would have been completely useless.

[1] https://github.com/pbackus/sumtype/issues/36
September 07, 2020
On 9/7/20 12:12 PM, Paul Backus wrote:
> On Monday, 7 September 2020 at 14:57:24 UTC, Steven Schveighoffer wrote:
>> I get a compilation error. But it's not in foo, it's in bar. It says:
>>
>> Error: template instance onlineapp.bar!(foo) does not match template declaration bar(alias x)()
>>   with x = foo(T)(T v)
>>   must satisfy the following constraint:
>>        __traits(compiles, x(1))
>>
>> [...]
>>
>> The problem is, if the intention is for it to pass, but the implementation is bugged, the error message is useless. If the thing I'm passing is a type with 200 LOC, and there's an error buried in there somewhere, the compiler is telling me "somewhere in this, it doesn't work". And sometimes those function signatures and their constraints are hairy themselves.
>>
>> My usual steps taken at this point are to extract the constraint template and actually try to call the functions in the constraints with that type, and see what fails. This is somewhat annoying, and sometimes difficult to do based on where the code that implements the constraint actually is, and where the code actually calling it is (I may have to duplicate a lot of stuff).
> 
> The tedious-but-reliable way to handle this is to recompile with `-verrors=spec`, search the output for the original error message, and then scroll up until you find what you're looking for in the speculative errors. The only issue is that you have to wade through a lot of useless chaff to find the information you actually care about.
> 
> IMO the best way to make this easier would be to have the compiler provide a "stack trace" of speculative errors for each failed constraint whenever a template fails to instantiate--maybe behind a switch (`-verrors=constraints`) to avoid clogging up the output in the common case.
> 
> This has the advantage of working for *all* indirect errors like this, not just ones involving IFTI. To give a concrete example, `-verrors=constraints` would have helped me a lot with the problems I had to debug in SumType's copy constructors recently [1], whereas __traits(canCall) would have been completely useless.
> 
> [1] https://github.com/pbackus/sumtype/issues/36

Thanks! This is the kind of response I'm looking for.

I'm trying to understand what the issue and the solution was in that bug. I'm assuming once the compiler bugs were fixed, then you had to figure out why it wasn't compiling?

I wasn't aware of -verrors=spec, but it sounds like it spitting out ALL speculative errors, not just when a specific call fails to build? While giving a searchable set of data, the situation is only slightly improved.

It is true that if your constraint is the problem, then __traits(canCall) or whatever wouldn't help. But I don't see any constraint changes in that code.

Perhaps you can elaborate on the specific issue and how you solved it? It's unclear to me from the diff and the bug discussion that the __traits(canCall) would not help.

I would be OK with your proposal as well, and it actually could be done separately from the __traits(canCall) idea. Focusing the aim of the speculative errors might be useful in general -- like, give a specific instantiation line and parameters to debug, and only have it trace that one error, rather than having to search through pages of text.

-Steve
September 07, 2020
On Monday, 7 September 2020 at 16:27:46 UTC, Steven Schveighoffer wrote:
>
> Thanks! This is the kind of response I'm looking for.
>
> I'm trying to understand what the issue and the solution was in that bug. I'm assuming once the compiler bugs were fixed, then you had to figure out why it wasn't compiling?

It was actually the reverse. SumType's copy constructors were failing due to a compiler bug, but the error I was getting was:

src/sumtype.d(1412,4): Error: static assert:  "`handlers[0]` of type `template` never matches"

...because the copy constructor uses `match` internally, and that's the error `match` gives you when your handler fails to speculatively compile. Turning on -verrors=spec allowed me to see the error *inside* the handler that was causing speculative compilation to fail:

(spec:1) src/sumtype.d-mixin-407(407): Error: union Storage has constructors, cannot use { initializers }, use Storage( initializers ) instead

...which is what led me to diagnosing and, ultimately, fixing issue 20842 [1].

[1] https://issues.dlang.org/show_bug.cgi?id=20842

> I wasn't aware of -verrors=spec, but it sounds like it spitting out ALL speculative errors, not just when a specific call fails to build? While giving a searchable set of data, the situation is only slightly improved.
>
> It is true that if your constraint is the problem, then __traits(canCall) or whatever wouldn't help. But I don't see any constraint changes in that code.

You're right; I was in a hurry when I wrote my post and didn't think it through enough. Really, what I want is to see speculative errors if and only if they occur during a failed attempt at non-speculative template instantiation. In other words, when a template fails to instantiate and causes compilation to fail, I would like to know why, but if compilation succeeds, I don't care about the details. So, `-verrors=spec-fatal`, maybe.

It's possible you could even apply this recursively, and only show (spec:2) errors if they cause a failed template instantiation in (spec:1), etc. I haven't really thought it through, though; it's possible that would go too far and end up hiding information programmers actually want to see.
September 08, 2020
On Monday, 7 September 2020 at 16:10:13 UTC, Steven Schveighoffer wrote:
>
> This is somewhat different than what I was proposing. However, it's very closely related.
>
> I absolutely need it to be matched with a function call, I don't want to have to do type acrobatics or check for UFCS, or whatever.
>
> However, I think both our needs are satisfied if the feature is something like checking to see that all symbols are resolved and that all instantiations are unambiguous. What happens after that should be considered a normal ungagged error.
>
> If I can pull out a non-template analogy, imagine you had code like:
>
> int foo()
> {
>    blahblah(); // unresolved symbol
> }
>
> int bar()
> {
>    return foo();
> }
>
> And instead of the error on the "blahblah" line, you got an error that foo doesn't exist, and so bar cannot be compiled. This is kind of how the current template state of affairs is. I want the same benefits for template code that non-template code has.
>
> -Steve


Yesss.

Okay, so as far as I can see there's two avenues that should give us both what we want. The first, and IMO best one (but W&A won't agree) is a huge breaking language change that errors in templates that have matched the constraints are always reported as errors, even in a gagged context. The second, more limited one that I think also gives you what you want (?) is a pragma *like* __traits(compiles), maybe __traits(instantiates), with the change that instead of gagging everything, it captures only one category of error - "the call couldn't find a matching function or template" - and only from the expression directly passed to it. All other errors are reported as usual.

Would that do it?
« First   ‹ Prev
1 2 3 4