digitalmars.D - Difference between "can call" and "can compile"
- Steven Schveighoffer (63/63) Sep 07 2020 Consider a template function like this:
- FeepingCreature (5/12) Sep 07 2020 Yes yes yes yes yes *yes yes*!
- Stefan Koch (4/18) Sep 07 2020 I can see the appeal of the feature.
- FeepingCreature (35/56) Sep 07 2020 That would be so, so awesome.
- Steven Schveighoffer (23/36) Sep 07 2020 This is somewhat different than what I was proposing. However, it's very...
- FeepingCreature (15/40) Sep 07 2020 Yesss.
- Steven Schveighoffer (25/70) Sep 08 2020 No, the result of __traits(compiles) or is(typeof(...)) cannot be
- Paul Backus (19/34) Sep 08 2020 On the other hand, improving the compiler to give more
- Steven Schveighoffer (20/41) Sep 08 2020 The error could cause subtle differences. The code could actually
- Paul Backus (20/43) Sep 08 2020 I guess I've failed to communicate clearly. When I talk about
- Steven Schveighoffer (40/65) Sep 08 2020 Like for instance:
- Paul Backus (103/120) Sep 08 2020 writeln could just as easily use __traits(hasMember) to check for
- H. S. Teoh (12/23) Sep 08 2020 [...]
- Steven Schveighoffer (21/86) Sep 08 2020 hasMember is something I've been using a lot more lately. But it's not
- FeepingCreature (5/15) Sep 14 2020 Hear, hear.
- Paul Backus (16/25) Sep 15 2020 It sounds to me like your problem is that template constraints
- Steven Schveighoffer (33/57) Sep 15 2020 That's not what they are designed to do. It does not cause an error if a...
- Nick Treleaven (12/23) Sep 16 2020 ...
- Steven Schveighoffer (9/30) Sep 16 2020 Ehh... I don't care what it returns. I care that it can be called. The
- H. S. Teoh (13/18) Sep 07 2020 [...]
- Paul Backus (19/40) Sep 07 2020 The tedious-but-reliable way to handle this is to recompile with
- Steven Schveighoffer (20/64) Sep 07 2020 Thanks! This is the kind of response I'm looking for.
- Paul Backus (30/41) Sep 07 2020 It was actually the reverse. SumType's copy constructors were
- H. S. Teoh (16/20) Sep 08 2020 While it's great that we at least have -verrors=spec for those times
- Andrej Mitrovic (36/38) Sep 08 2020 I like your idea.
- WebFreak001 (7/13) Sep 15 2020 This is already possible from a library solution, see
- Steven Schveighoffer (8/24) Sep 15 2020 But that's the issue. If I know what small set of types should work,
- Jackel (11/16) Sep 15 2020 You mentioned UFCS, but if it is a template in a library then
- Steven Schveighoffer (11/26) Sep 15 2020 Yes, but input ranges and Phobos are not the only things that depend on
- FeepingCreature (27/43) Sep 15 2020 Hm!
- WebFreak001 (5/15) Sep 15 2020 huh where did you get that from? My proposed solution there just
- Steven Schveighoffer (19/37) Sep 16 2020 Oh, I misread what you were doing. I assumed you were using traits to
- Steven Schveighoffer (3/4) Sep 16 2020 ugh.
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
Sep 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? -SteveYes yes yes yes yes *yes yes*! Please!!!!! This would make template errors so unbelievably much better.
Sep 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 can see the appeal of the feature. Let me think about how difficult that would be to implement.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? -SteveYes yes yes yes yes *yes yes*! Please!!!!! This would make template errors so unbelievably much better.
Sep 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: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.)On Monday, 7 September 2020 at 14:57:24 UTC, Steven Schveighoffer wrote:I can see the appeal of the feature. Let me think about how difficult that would be to implement.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? -SteveYes yes yes yes yes *yes yes*! Please!!!!! This would make template errors so unbelievably much better.
Sep 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
Sep 07 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. -SteveYesss. 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?
Sep 07 2020
On 9/8/20 1:03 AM, FeepingCreature wrote:On Monday, 7 September 2020 at 16:10:13 UTC, Steven Schveighoffer wrote:No, the result of __traits(compiles) or is(typeof(...)) cannot be changed. I would never want that. What we would need is a new feature that then we can migrate existing code to use.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.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?Yes, this is exactly what I'm looking for. I was going to call it __traits(canCall) or something, but instantiates makes perfect sense, since this only really is important for template instantiation. In order to facilitate more complex conditions, I would say "the code in the expression passes semantic, with all template instantiations in the checked expression are matched" as the description. For actual errors in the code being checked, it should return false just like __traits(compiles). e.g.: void foo()() { import non.existent.mod; } __traits(compiles, foo()); // false __traits(instantiates, foo()); // true, foo's body isn't checked for errors, just that it instantiates. __traits(compiles, () {import non.existent.mod;} ()); // false __traits(instantiates, () {import non.existent.mod;} ()); // false, the code inside the expression doesn't pass semantic -Steve
Sep 08 2020
On Tuesday, 8 September 2020 at 13:57:34 UTC, Steven Schveighoffer wrote:What we would need is a new feature that then we can migrate existing code to use.On the other hand, improving the compiler to give more informative output about existing errors in existing code requires no migration and solves this entire category of problem, rather than just the subset involving IFTI or template argument deduction.If we're going to take this approach, why stop here? Surely there are other categories of error that a programmer might want to query: __traits(parses), __traits(resolvesIdentifiers), __traits(typeChecks), __traits(safetyChecks), __traits(passesCtorFlowAnalysis), etc. Might as well go ahead and add them all. The problem described in the OP of this thread is that the compiler fails to give the programmer useful information in certain situations. The solution is to improve the compiler so that it gives better information, not to add unprincipled hacks to the language so that the programmer can work around the compiler's deficiencies by hand.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?Yes, this is exactly what I'm looking for. I was going to call it __traits(canCall) or something, but instantiates makes perfect sense, since this only really is important for template instantiation.
Sep 08 2020
On 9/8/20 1:06 PM, Paul Backus wrote:On Tuesday, 8 September 2020 at 13:57:34 UTC, Steven Schveighoffer wrote:The error could cause subtle differences. The code could actually COMPILE, but not take the path intended. In which case the error is useless, even if it's more verbose. But I would be able to live with better error messages for the current situation. And by better error messages, I mean show me where my code failed to compile, not give me 5 pages of speculative compilation errors up a giant tree of calls.What we would need is a new feature that then we can migrate existing code to use.On the other hand, improving the compiler to give more informative output about existing errors in existing code requires no migration and solves this entire category of problem, rather than just the subset involving IFTI or template argument deduction.If we're going to take this approach, why stop here? Surely there are other categories of error that a programmer might want to query: __traits(parses), __traits(resolvesIdentifiers), __traits(typeChecks), __traits(safetyChecks), __traits(passesCtorFlowAnalysis), etc. Might as well go ahead and add them all.I'm looking to duplicate the error system for functions. That is, here is a function signature, don't worry about the innards, if you can call it with that signature, then you are done with the check. Then try to compile what you think should be valid, and if there's an error, just report that error. I don't want any of those other features. I want the equivalent of "I only looked at the documentation, and called the function as specified."The problem described in the OP of this thread is that the compiler fails to give the programmer useful information in certain situations. The solution is to improve the compiler so that it gives better informationIf you can solve that problem, then I'd be all for that.not to add unprincipled hacks to the language so that the programmer can work around the compiler's deficiencies by hand.This would be the opposite of working around things by hand. I already do that today. -Steve
Sep 08 2020
On Tuesday, 8 September 2020 at 17:49:08 UTC, Steven Schveighoffer wrote:On 9/8/20 1:06 PM, Paul Backus wrote:I guess I've failed to communicate clearly. When I talk about "errors" here, unless I specifically say otherwise, I am referring exclusively to "top-level" non-speculative errors--the kind that DMD prints with red letters and that cause compilation to fail. By definition, any code that contains such an error cannot compile, so the hypothetical you raise here is impossible.On Tuesday, 8 September 2020 at 13:57:34 UTC, Steven Schveighoffer wrote:The error could cause subtle differences. The code could actually COMPILE, but not take the path intended. In which case the error is useless, even if it's more verbose.What we would need is a new feature that then we can migrate existing code to use.On the other hand, improving the compiler to give more informative output about existing errors in existing code requires no migration and solves this entire category of problem, rather than just the subset involving IFTI or template argument deduction.And by better error messages, I mean show me where my code failed to compile, not give me 5 pages of speculative compilation errors up a giant tree of calls.Well, the compiler is already showing you where your code failed to compile: it failed in the template constraint, because `__traits(compiles, whatever)` evaluated to false. :) What you and I really want is for the compiler to show us the *root cause* of that failure (which `-verrors=spec` currently succeeds at), and to do so without overwhelming us with irrelevant information (which `-verrors=spec` currently fails at).If it's possible to selectively un-gag errors based on whether they happened during template argument deduction, it should also be possible to selectively un-gag errors based on whether they happened during template instantiation in general.The problem described in the OP of this thread is that the compiler fails to give the programmer useful information in certain situations. The solution is to improve the compiler so that it gives better informationIf you can solve that problem, then I'd be all for that.
Sep 08 2020
On 9/8/20 2:24 PM, Paul Backus wrote:On Tuesday, 8 September 2020 at 17:49:08 UTC, Steven Schveighoffer wrote:Like for instance: struct S { import std.range : isOutputRange; void toString(Output)(Output output) if (isOutputRange!(Output, dchar)) { put(output, "hello, this is an S!"); // no import of std.range.put } } Now, writeln(S.init) => S() Wait, where's my specified message? I've literally spent hours on stuff like this, where I'm racking my brain trying to figure out why that function can't be called, only to realize that the function doesn't compile in the first place, when called with the specified constraints satisfied. Whereas if writeln checked using the proposed __traits(instantiates) instead of __traits(compiles) on the call to S.toString(lockedTextOutput) (or whatever it does), then instead of the wrong path, I get a compilation error, telling me that my toString doesn't compile. It's also possible that a compilation error happens in a path that isn't chosen because __traits(compiles) returns false in the path we expect, but the path taken instead doesn't have a __traits(compiles) guarding it. In which case, a weirder unexpected error message occurs far away from the true problem.The error could cause subtle differences. The code could actually COMPILE, but not take the path intended. In which case the error is useless, even if it's more verbose.I guess I've failed to communicate clearly. When I talk about "errors" here, unless I specifically say otherwise, I am referring exclusively to "top-level" non-speculative errors--the kind that DMD prints with red letters and that cause compilation to fail. By definition, any code that contains such an error cannot compile, so the hypothetical you raise here is impossible.Well, the compiler is already showing you where your code failed to compile: it failed in the template constraint, because `__traits(compiles, whatever)` evaluated to false. :)In simple cases, this is enough. In complex cases, the error can be very far away from where the problem is.What you and I really want is for the compiler to show us the *root cause* of that failure (which `-verrors=spec` currently succeeds at), and to do so without overwhelming us with irrelevant information (which `-verrors=spec` currently fails at).I essentially want to know why it made the decisions it made. So yes, in some cases, this might involve seeing ALL the speculative compilation output, and sifting through it. In the majority of cases, I want to know why it couldn't call my function foo()(int) with an int parameter, which is what it's telling me. This would not solve all template compilation problems. Just most of them.The point is not to ungag errors during argument deduction or gag them afterwards, but to stop trying to compile after deduction is done for that call. Essentially, don't actually instantiate the template, just see whether there is a matching template you would instantate. -SteveIf you can solve that problem, then I'd be all for that.If it's possible to selectively un-gag errors based on whether they happened during template argument deduction, it should also be possible to selectively un-gag errors based on whether they happened during template instantiation in general.
Sep 08 2020
On Tuesday, 8 September 2020 at 18:59:00 UTC, Steven Schveighoffer wrote:Now, writeln(S.init) => S() Wait, where's my specified message? I've literally spent hours on stuff like this, where I'm racking my brain trying to figure out why that function can't be called, only to realize that the function doesn't compile in the first place, when called with the specified constraints satisfied. Whereas if writeln checked using the proposed __traits(instantiates) instead of __traits(compiles) on the call to S.toString(lockedTextOutput) (or whatever it does), then instead of the wrong path, I get a compilation error, telling me that my toString doesn't compile.writeln could just as easily use __traits(hasMember) to check for the presence of toString, and print a warning message if it exists but doesn't compile. In other words, the problem here is that writeln assumes "toString doesn't compile" implies "toString isn't supposed to compile," which in most cases is not actually true. Adding a new language feature is neither necessary nor sufficient to correct that assumption. I agree that it would probably be a good idea to change writeln (and other similar code) so that it does not make these kinds of assumptions.The point is not to ungag errors during argument deduction or gag them afterwards, but to stop trying to compile after deduction is done for that call. Essentially, don't actually instantiate the template, just see whether there is a matching template you would instantate.From the programmer's perspective, is there any difference? Let me make this concrete. Here's a contrived example program that illustrates the problem from your first post in this thread: --- example.d void use(alias fun)() if (__traits(compiles, fun())) {} void bad()() { oops(); } void irrelevant()() { oops(); } void test() { enum _ = __traits(compiles, irrelevant()); use!bad(); } --- Here's what the compiler currently prints when you compile it: --- example.d(18): Error: template instance `example.use!(bad)` does not match template declaration `use(alias fun)()` with `fun = bad()()` must satisfy the following constraint: ` __traits(compiles, fun())` --- Here's what the compiler currently prints when you compile it with `-verrors=spec`: --- (spec:1) example.d(12): Error: undefined identifier `oops` (spec:1) example.d(17): Error: template instance `example.irrelevant!()` error instantiating (spec:1) example.d(17): Error: template instance `example.irrelevant!()` cannot resolve forward reference (spec:1) example.d(17): Error: template `example.irrelevant` cannot deduce function from argument types `!()()`, candidates are: (spec:1) example.d(10): `irrelevant()()` (spec:1) example.d(7): Error: undefined identifier `oops` (spec:1) example.d(2): Error: template instance `example.bad!()` error instantiating (spec:1) example.d(2): Error: template instance `example.bad!()` cannot resolve forward reference (spec:1) example.d(2): Error: template `example.bad` cannot deduce function from argument types `!()()`, candidates are: (spec:1) example.d(5): `bad()()` (spec:1) example.d(7): Error: undefined identifier `oops` (spec:1) example.d(2): Error: template instance `example.bad!()` error instantiating (spec:1) example.d(2): Error: template instance `example.bad!()` cannot resolve forward reference (spec:1) example.d(2): Error: template `example.bad` cannot deduce function from argument types `!()()`, candidates are: (spec:1) example.d(5): `bad()()` example.d(18): Error: template instance `example.use!(bad)` does not match template declaration `use(alias fun)()` with `fun = bad()()` must satisfy the following constraint: ` __traits(compiles, fun())` --- And here's what I would like the compiler to print with my proposed `-verrors=spec-but-only-stuff-i-care-about` flag: --- (spec:1) example.d(7): Error: undefined identifier `oops` (spec:1) example.d(2): Error: template instance `example.bad!()` error instantiating (spec:1) example.d(2): Error: template instance `example.bad!()` cannot resolve forward reference (spec:1) example.d(2): Error: template `example.bad` cannot deduce function from argument types `!()()`, candidates are: (spec:1) example.d(5): `bad()()` (spec:1) example.d(7): Error: undefined identifier `oops` (spec:1) example.d(2): Error: template instance `example.bad!()` error instantiating (spec:1) example.d(2): Error: template instance `example.bad!()` cannot resolve forward reference (spec:1) example.d(2): Error: template `example.bad` cannot deduce function from argument types `!()()`, candidates are: (spec:1) example.d(5): `bad()()` example.d(18): Error: template instance `example.use!(bad)` does not match template declaration `use(alias fun)()` with `fun = bad()()` must satisfy the following constraint: ` __traits(compiles, fun())` --- Notice how the above includes messages about speculative errors in `bad`, but does NOT include messages about speculative errors in `irrelevant`.
Sep 08 2020
On Tue, Sep 08, 2020 at 07:48:36PM +0000, Paul Backus via Digitalmars-d wrote: [...]writeln could just as easily use __traits(hasMember) to check for the presence of toString, and print a warning message if it exists but doesn't compile. In other words, the problem here is that writeln assumes "toString doesn't compile" implies "toString isn't supposed to compile," which in most cases is not actually true. Adding a new language feature is neither necessary nor sufficient to correct that assumption. I agree that it would probably be a good idea to change writeln (and other similar code) so that it does not make these kinds of assumptions.[...] +1. I think in the past, before the we had __traits(hasMember), __traits(compiles) was used as a kind of poor man's substitute, based on the shaky assumption that "it compiles" == "it has member X". Now that we have better ways of expressing this, we should get rid of __traits(compiles) or other similar hacks based on similar shaky assumptions. T -- Life begins when you can spend your spare time programming instead of watching television. -- Cal Keegan
Sep 08 2020
On 9/8/20 3:48 PM, Paul Backus wrote:writeln could just as easily use __traits(hasMember) to check for the presence of toString, and print a warning message if it exists but doesn't compile. In other words, the problem here is that writeln assumes "toString doesn't compile" implies "toString isn't supposed to compile," which in most cases is not actually true. Adding a new language feature is neither necessary nor sufficient to correct that assumption.hasMember is something I've been using a lot more lately. But it's not the same. For instance, if the member was toString(int x), then it has the member toString, but would not match a call, and therefore should not be used. Contrived, but possible in other contexts. Or the "member" could be a UFCS function, where __traits(hasMember) doesn't work.I agree that it would probably be a good idea to change writeln (and other similar code) so that it does not make these kinds of assumptions.possibly with writeln, you can make the assumption that toString really should be picked even if it's not formed correctly. But that's not the case for other things.Let me make this concrete. Here's a contrived example program that illustrates the problem from your first post in this thread: --- example.d void use(alias fun)() if (__traits(compiles, fun())) {} void bad()() { oops(); } void irrelevant()() { oops(); } void test() { enum _ = __traits(compiles, irrelevant()); use!bad(); } ---...And here's what I would like the compiler to print with my proposed `-verrors=spec-but-only-stuff-i-care-about` flag: --- (spec:1) example.d(7): Error: undefined identifier `oops` (spec:1) example.d(2): Error: template instance `example.bad!()` error instantiating (spec:1) example.d(2): Error: template instance `example.bad!()` cannot resolve forward reference (spec:1) example.d(2): Error: template `example.bad` cannot deduce function from argument types `!()()`, candidates are: (spec:1) example.d(5): `bad()()` (spec:1) example.d(7): Error: undefined identifier `oops` (spec:1) example.d(2): Error: template instance `example.bad!()` error instantiating (spec:1) example.d(2): Error: template instance `example.bad!()` cannot resolve forward reference (spec:1) example.d(2): Error: template `example.bad` cannot deduce function from argument types `!()()`, candidates are: (spec:1) example.d(5): `bad()()` example.d(18): Error: template instance `example.use!(bad)` does not match template declaration `use(alias fun)()` with `fun = bad()()` must satisfy the following constraint: ` __traits(compiles, fun())` --- Notice how the above includes messages about speculative errors in `bad`, but does NOT include messages about speculative errors in `irrelevant`.Here's what it says when there is no constraint (and when I actually call fun inside use): onlineapp.d(9): Error: undefined identifier oops onlineapp.d(4): Error: template instance onlineapp.bad!() error instantiating onlineapp.d(20): instantiated from here: use!(bad) This is what I want it to do. Why can't it just do that? Why do I need to see 10 more or even 5 more lines of irrelevant error messages? -Steve
Sep 08 2020
On Tuesday, 8 September 2020 at 20:04:32 UTC, Steven Schveighoffer wrote:Here's what it says when there is no constraint (and when I actually call fun inside use): onlineapp.d(9): Error: undefined identifier oops onlineapp.d(4): Error: template instance onlineapp.bad!() error instantiating onlineapp.d(20): instantiated from here: use!(bad) This is what I want it to do. Why can't it just do that? Why do I need to see 10 more or even 5 more lines of irrelevant error messages? -SteveHear, hear. And yes, `hasMember` also doesn't solve my problem in any way at all.
Sep 14 2020
On Tuesday, 8 September 2020 at 20:04:32 UTC, Steven Schveighoffer wrote:Here's what it says when there is no constraint (and when I actually call fun inside use): onlineapp.d(9): Error: undefined identifier oops onlineapp.d(4): Error: template instance onlineapp.bad!() error instantiating onlineapp.d(20): instantiated from here: use!(bad) This is what I want it to do. Why can't it just do that? Why do I need to see 10 more or even 5 more lines of irrelevant error messages?It sounds to me like your problem is that template constraints are doing the exact thing they're designed to do: causing errors that would otherwise be reported inside a template to be reported outside of it. Re: irrelevant error messages, I think they're unavoidable in the general case. What if there are multiple template overloads? Only one of them is going to have the error you care about, but the compiler has no way to know which one. And likewise with static if and static assert statements inside the template body. The optimal solution to this problem, from a programming-language-theory perspective, is a system like Rust's traits that allows the compiler to type-check generic code prior to instantiation. Since we don't have that, we're going to have to settle for something "good enough."
Sep 15 2020
On 9/15/20 7:26 AM, Paul Backus wrote:On Tuesday, 8 September 2020 at 20:04:32 UTC, Steven Schveighoffer wrote:That's not what they are designed to do. It does not cause an error if a constraint doesn't pass, it's just not a match. Template constraints are designed to allow the template author to fine-tune how his template can be used. This allows template overloading that otherwise would be impossible or at least really difficult.Here's what it says when there is no constraint (and when I actually call fun inside use): onlineapp.d(9): Error: undefined identifier oops onlineapp.d(4): Error: template instance onlineapp.bad!() error instantiating onlineapp.d(20): instantiated from here: use!(bad) This is what I want it to do. Why can't it just do that? Why do I need to see 10 more or even 5 more lines of irrelevant error messages?It sounds to me like your problem is that template constraints are doing the exact thing they're designed to do: causing errors that would otherwise be reported inside a template to be reported outside of it.Re: irrelevant error messages, I think they're unavoidable in the general case. What if there are multiple template overloads? Only one of them is going to have the error you care about, but the compiler has no way to know which one. And likewise with static if and static assert statements inside the template body.It would know which one -- the one that matches. I want to say with my template constraint "I found a match that I can call, so use me". and then when the match fails to compile (NOT fails to match), then I get a targeted error message.The optimal solution to this problem, from a programming-language-theory perspective, is a system like Rust's traits that allows the compiler to type-check generic code prior to instantiation. Since we don't have that, we're going to have to settle for something "good enough."The compiler can type-check the interface, without checking the code. We can already do this with types, I want to do it with calls. An example void foo(T)(T t) if (isIntegral!T) { int x = t; } foo has declared "I accept all types that are integral", even though foo doesn't actually work for integers larger than int (a bug in either the constraints or the offending line). But if you do foo(long.max), it does not come back and say "can't find foo", it says "error, can't assign int x to long t", or whatever. That's the error I want to see. Now I can either change the constraint to reflect it should only take int size or less, or change the line to work with longs. In contrast, there's no way in the template constraints to say "I accept all parameters for which I can call it this way". You have to say "I accept all parameters for which this code compiles". There is a difference in specification, and I would prefer the latter. Without this facility, you are hiding all implementation errors, regardless of origin, behind constraints, which makes it really difficult to find them. -Steve
Sep 15 2020
On Tuesday, 8 September 2020 at 18:59:00 UTC, Steven Schveighoffer wrote:void toString(Output)(Output output) if (isOutputRange!(Output, dchar)) { put(output, "hello, this is an S!"); // no import of std.range.put }...if writeln checked using the proposed __traits(instantiates) instead of __traits(compiles) on the call to S.toString(lockedTextOutput) (or whatever it does), then instead of the wrong path, I get a compilation error, telling me that my toString doesn't compile.In theory, `is(typeof(S.init.toString(suitableOutputRange)))` could be true even if there is a semantic error in the body of your S.toString method. The typeof expression only needs to resolve the method then produce the type of that method. You specified the return type as `void` (not `auto`), so it is overkill to check the semantics of the method body at this point in compilation (it might not even get called). If typeof was changed to do less work, perhaps `is(typeof(...))` could do what you want.
Sep 16 2020
On 9/16/20 12:59 PM, Nick Treleaven wrote:On Tuesday, 8 September 2020 at 18:59:00 UTC, Steven Schveighoffer wrote:Ehh... I don't care what it returns. I care that it can be called. The fact that I wrote void in my toy example is not important. But you bring up a good point. Something like "see if this lambda compiles" might included figuring out the return type of a function. Which in turn means to analyze the body (if it's an auto return). So the formulation of a constraint may have to avoid using "see if this lambda works" techniques to avoid having to compile everything. -Stevevoid toString(Output)(Output output) if (isOutputRange!(Output, dchar)) { put(output, "hello, this is an S!"); // no import of std.range.put }....if writeln checked using the proposed __traits(instantiates) instead of __traits(compiles) on the call to S.toString(lockedTextOutput) (or whatever it does), then instead of the wrong path, I get a compilation error, telling me that my toString doesn't compile.In theory, `is(typeof(S.init.toString(suitableOutputRange)))` could be true even if there is a semantic error in the body of your S.toString method. The typeof expression only needs to resolve the method then produce the type of that method. You specified the return type as `void` (not `auto`), so it is overkill to check the semantics of the method body at this point in compilation (it might not even get called). If typeof was changed to do less work, perhaps `is(typeof(...))` could do what you want.
Sep 16 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
Sep 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
Sep 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: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. -SteveI 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
Sep 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=20842I 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.
Sep 07 2020
On Mon, Sep 07, 2020 at 12:27:46PM -0400, Steven Schveighoffer via Digitalmars-d wrote: [...]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.While it's great that we at least have -verrors=spec for those times when we're faced with a truly obtuse and impenetrable template error, IME it doesn't really improve the situation very much, because the real error is drowned in endless pages of other completely irrelevant errors before *and* after the relevant line(s). There have been times when I'd rather resort to other methods of narrowing down the problem (like copy-n-pasting and stepwise truncating a long UFCS chain until the problem is isolated, or just plain ole rewrite the darned code in a way that *doesn't* involve so many templates) than to engage in yet another search for the needle of an error in a veritable haystack of irrelevant speculative template error fluff. T -- Philosophy: how to make a career out of daydreaming.
Sep 08 2020
On Monday, 7 September 2020 at 14:57:24 UTC, Steven Schveighoffer wrote:Thoughts? -SteveI like your idea. It's interesting that this would actually be implementable in the library if we had an ability to retrieve a template's compile-time parameters. Then you could have something like: ----- void foo(T)(T v) { auto x = v.someProperty; } template isCallable (alias sym, Args...) { // create a syntactical "copy" of 'sym' void Proto (CompileTimeParams!sym)(RunTimeParams!sym) { } enum bool isCallable = is(typeof(Proto(Args))); } void bar(alias x)() if (isCallable!(x, 1)) { auto val = x(1); } void main () { bar!foo(); } ----- However: 1. We don't have a way to extract the compile-time parameters from a template / templated function 2. I'm not sure if there is an easy way to retrieve the run-time parameters either. There is `ParameterStorageClassTuple` but it's awkward to use. And regardless, a library solution will likely be very slow anyway. This belongs in the compiler.
Sep 08 2020
On Monday, 7 September 2020 at 14:57:24 UTC, Steven Schveighoffer wrote:Consider a template function like this: void foo(T)(T v) { auto x = v.someProperty; } [...]This is already possible from a library solution, see https://forum.dlang.org/post/iribazqozceygujkvvjg forum.dlang.org However I imagine it is slow because of mixin operations + not well defined because it uses stringof, but if you just want it in some small project of yours you can just use that.
Sep 15 2020
On 9/15/20 10:18 AM, WebFreak001 wrote:On Monday, 7 September 2020 at 14:57:24 UTC, Steven Schveighoffer wrote:But that's the issue. If I know what small set of types should work, then I can just write overloads for those types. It's when I don't know what type is going to be passed in. I could probably switch to using "hasMember" in this use case, which is likely to be correct for all purposes that I have. It's just not applicable to a public library, which might use things other than members. -SteveConsider a template function like this: void foo(T)(T v) { auto x = v.someProperty; } [...]This is already possible from a library solution, see https://forum.dlang.org/post/iribazqozceygujkvvjg forum.dlang.org However I imagine it is slow because of mixin operations + not well defined because it uses stringof, but if you just want it in some small project of yours you can just use that.
Sep 15 2020
On Tuesday, 15 September 2020 at 19:12:04 UTC, Steven Schveighoffer wrote:I could probably switch to using "hasMember" in this use case, which is likely to be correct for all purposes that I have. It's just not applicable to a public library, which might use things other than members. -SteveYou mentioned UFCS, but if it is a template in a library then user defined UFCS shouldn't work. The only valid case I can think of would be for a built in array, using front() and friends. But the library would have to import those functions for it to work. I do agree with what you are saying, in fact it almost seems like __traits(compiles) is a code smell. There's usually a better way to express it, and as is with the case you are talking about, a different feature would be more appropriate and provide better error messages.
Sep 15 2020
On 9/15/20 8:20 PM, Jackel wrote:On Tuesday, 15 September 2020 at 19:12:04 UTC, Steven Schveighoffer wrote:Yes, but input ranges and Phobos are not the only things that depend on UFCS. There's also a question of whether a free function can be called with a provided type, which has no analogue to __traits(hasMember).I could probably switch to using "hasMember" in this use case, which is likely to be correct for all purposes that I have. It's just not applicable to a public library, which might use things other than members.You mentioned UFCS, but if it is a template in a library then user defined UFCS shouldn't work. The only valid case I can think of would be for a built in array, using front() and friends. But the library would have to import those functions for it to work.I do agree with what you are saying, in fact it almost seems like __traits(compiles) is a code smell. There's usually a better way to express it, and as is with the case you are talking about, a different feature would be more appropriate and provide better error messages.Yes, exactly. I've spent way more than my fair share unraveling via library surgery what the compiler should be able to do itself, as long as the proper tools existed. Don't get me wrong, I'd be fine with a solution that doesn't need a new feature (that would be ideal actually). -Steve
Sep 15 2020
On Wednesday, 16 September 2020 at 00:28:59 UTC, Steven Schveighoffer wrote:Yes, but input ranges and Phobos are not the only things that depend on UFCS. There's also a question of whether a free function can be called with a provided type, which has no analogue to __traits(hasMember).Hm! I may have something: ``` public template hasInstantiationFor(alias F, T) { import std.traits : ReturnType; struct NoMatch { } alias proxy = F; alias proxy(T) = () => NoMatch(); enum hasInstantiationFor = !is(ReturnType!(proxy!T) == NoMatch); } ``` I could swear this used to straight up not work, but it seems to work now, at least for small local tests. I can't put it into production because it runs into a flood of unrelated bugs if I try to use it in an actual project. I swear it never ends with this language... But I'll try to dustmite this and maybe in DMD 2.095 we'll have a solution that actually works. Though it only works for templates that are characterized by specialization, not template inconditions. As far as I can tell there's no way to see if a template matches inconditions, because there's no "fallback incondition" like there is with specializations. So canInstantiate is still needed.I do agree with what you are saying, in fact it almost seems like __traits(compiles) is a code smell. There's usually a better way to express it, and as is with the case you are talking about, a different feature would be more appropriate and provide better error messages.Yes, exactly. I've spent way more than my fair share unraveling via library surgery what the compiler should be able to do itself, as long as the proper tools existed. Don't get me wrong, I'd be fine with a solution that doesn't need a new feature (that would be ideal actually). -Steve
Sep 15 2020
On Tuesday, 15 September 2020 at 19:12:04 UTC, Steven Schveighoffer wrote:On 9/15/20 10:18 AM, WebFreak001 wrote:huh where did you get that from? My proposed solution there just checks if you can call a function with the given arguments only if it's passing all the template constraints.[...]But that's the issue. If I know what small set of types should work, then I can just write overloads for those types. It's when I don't know what type is going to be passed in. I could probably switch to using "hasMember" in this use case, which is likely to be correct for all purposes that I have. It's just not applicable to a public library, which might use things other than members. -Steve
Sep 15 2020
On 9/16/20 1:58 AM, WebFreak001 wrote:On Tuesday, 15 September 2020 at 19:12:04 UTC, Steven Schveighoffer wrote:Oh, I misread what you were doing. I assumed you were using traits to get parameter information, and I didn't bother reading very well. Sorry! Indeed, this is a close solution. You would need to be worried about possible import issues (or overloads). Plus you might have unused functions that are not called. I'd also want to directly call the function instead. I may want to actually verify the linkage is correct. Something like: template doppleganger(alias fun) { mixin("private void doppleganger" ~ def[def.indexOf('(' .. $] ~ " {}"); } static assert(__traits(compiles, fun!foo(args))); Need a way to pass templates. But it does seem doable. As you point out, the stringof thing is pretty sketchy. But yes, this is pretty much what I would want to have. Just more of a solid feature than a hack ;) -SteveOn 9/15/20 10:18 AM, WebFreak001 wrote:huh where did you get that from? My proposed solution there just checks if you can call a function with the given arguments only if it's passing all the template constraints.[...]But that's the issue. If I know what small set of types should work, then I can just write overloads for those types. It's when I don't know what type is going to be passed in. I could probably switch to using "hasMember" in this use case, which is likely to be correct for all purposes that I have. It's just not applicable to a public library, which might use things other than members.
Sep 16 2020
On 9/16/20 9:04 AM, Steven Schveighoffer wrote:static assert(__traits(compiles, fun!foo(args)));ugh. static assert(__traits(compiles, doppleganger!foo(args)));
Sep 16 2020