digitalmars.D - The evils of __traits(compiles)
- H. S. Teoh (28/28) Jan 21 2021 Ran into this today: was submitting a PR for the ddbus package, which
- Paul Backus (6/14) Jan 21 2021 This seems like a bug that would have been easily caught by a
- jmh530 (19/26) Jan 21 2021 It's just so easy and when it works it works without requiring
- Paul Backus (6/15) Jan 21 2021 This is also a great illustration of the pitfalls of using
- jmh530 (5/10) Jan 21 2021 The code as it is used in the library requires isMutable!T before
- Paul Backus (10/23) Jan 21 2021 You probably wouldn't use inout that way directly, but you might
- Steven Schveighoffer (3/31) Jan 21 2021 https://forum.dlang.org/post/rj5hok$c6q$1@digitalmars.com
- SealabJaster (6/8) Jan 21 2021 If there's any singular thing I'd ever want to see from D this
Ran into this today: was submitting a PR for the ddbus package, which has a function .registerMethods that allows automatic registration of DBus methods by introspecting a class object, and generating the appropriate setHandler calls to the router object. Since not all method signatures can be supported by DBus, .registerMethods uses __traits(compiles) as a quick way of determining whether setHandler is usable on a particular method (unsupported methods will not compile when passed to setHandler, so they will be skipped). Unfortunately, my PR touches one of the functions used by setHandler. A typo caused a compile error inside setHandler itself, but since __traits(compiles) does not distinguish between errors caused by the candidate method and errors inside setHandler itself, the erroneous code simply caused *all* methods to be skipped -- silently. The problem would not be noticed until runtime when some methods mysteriously go missing. :-( This problem isn't specific to ddbus; I've run into this in a lot of D code that's heavy on compile-time introspection. __traits(compiles) is a quick and easy way of getting code to work, but it inevitably leads to pain because of the way it gags *all* compile errors, including the ones you didn't expect and probably should be reported. tl;dr: __traits(compiles) is evil, and should be avoided. Unless you *really* mean, literally, "does this piece of code compile", and you're not using that as a stand-in for some other intended semantics (like "does X conform to Y's signature constraints" or some such). SFINAE is evil. >:-( T -- Unix is my IDE. -- Justin Whear
Jan 21 2021
On Thursday, 21 January 2021 at 20:27:36 UTC, H. S. Teoh wrote:Unfortunately, my PR touches one of the functions used by setHandler. A typo caused a compile error inside setHandler itself, but since __traits(compiles) does not distinguish between errors caused by the candidate method and errors inside setHandler itself, the erroneous code simply caused *all* methods to be skipped -- silently. The problem would not be noticed until runtime when some methods mysteriously go missing. :-(This seems like a bug that would have been easily caught by a unit test for setHandler. Remember: the compiler does not check your templates until you actually try to instantiate them. If you want to be sure that they instantiate correctly, you have to test them.
Jan 21 2021
On Thursday, 21 January 2021 at 20:27:36 UTC, H. S. Teoh wrote:[snip] tl;dr: __traits(compiles) is evil, and should be avoided. Unless you *really* mean, literally, "does this piece of code compile", and you're not using that as a stand-in for some other intended semantics (like "does X conform to Y's signature constraints" or some such). SFINAE is evil. >:-( TIt's just so easy and when it works it works without requiring much thought. Check out below. Not so easy to replace isAddable. struct Foo { int payload; Foo opBinary(string op)(Foo x) if (op == "*") { return Foo(payload * x.payload); } } enum bool isAddable(T) = __traits(compiles, { auto temp = T.init + T.init; }); void main() { static assert(isAddable!int); static assert(!isAddable!Foo); }
Jan 21 2021
On Thursday, 21 January 2021 at 21:20:50 UTC, jmh530 wrote:It's just so easy and when it works it works without requiring much thought. Check out below. Not so easy to replace isAddable.[...]enum bool isAddable(T) = __traits(compiles, { auto temp = T.init + T.init; }); void main() { static assert(isAddable!int); static assert(!isAddable!Foo); }This is also a great illustration of the pitfalls of using __traits(compiles) without thorough unit testing, because your code has a bug in it that isn't covered by your tests: static assert(isAddable!(inout(int))); // fails
Jan 21 2021
On Thursday, 21 January 2021 at 21:42:19 UTC, Paul Backus wrote:[snip] This is also a great illustration of the pitfalls of using __traits(compiles) without thorough unit testing, because your code has a bug in it that isn't covered by your tests: static assert(isAddable!(inout(int))); // failsThe code as it is used in the library requires isMutable!T before T gets passed into that logic. However, I still think it's weird. Changing inout to const or immutable works. I never would think to use inout(int) that way...
Jan 21 2021
On Thursday, 21 January 2021 at 22:09:46 UTC, jmh530 wrote:On Thursday, 21 January 2021 at 21:42:19 UTC, Paul Backus wrote:You probably wouldn't use inout that way directly, but you might write something like auto fun(T)(T a, T b) if (isAddable!T) { // do something with a + b } ...and then later on down the line you might call `fun` in some context where T is inout-qualified, like a copy constructor.[snip] This is also a great illustration of the pitfalls of using __traits(compiles) without thorough unit testing, because your code has a bug in it that isn't covered by your tests: static assert(isAddable!(inout(int))); // failsThe code as it is used in the library requires isMutable!T before T gets passed into that logic. However, I still think it's weird. Changing inout to const or immutable works. I never would think to use inout(int) that way...
Jan 21 2021
On 1/21/21 3:27 PM, H. S. Teoh wrote:Ran into this today: was submitting a PR for the ddbus package, which has a function .registerMethods that allows automatic registration of DBus methods by introspecting a class object, and generating the appropriate setHandler calls to the router object. Since not all method signatures can be supported by DBus, .registerMethods uses __traits(compiles) as a quick way of determining whether setHandler is usable on a particular method (unsupported methods will not compile when passed to setHandler, so they will be skipped). Unfortunately, my PR touches one of the functions used by setHandler. A typo caused a compile error inside setHandler itself, but since __traits(compiles) does not distinguish between errors caused by the candidate method and errors inside setHandler itself, the erroneous code simply caused *all* methods to be skipped -- silently. The problem would not be noticed until runtime when some methods mysteriously go missing. :-( This problem isn't specific to ddbus; I've run into this in a lot of D code that's heavy on compile-time introspection. __traits(compiles) is a quick and easy way of getting code to work, but it inevitably leads to pain because of the way it gags *all* compile errors, including the ones you didn't expect and probably should be reported. tl;dr: __traits(compiles) is evil, and should be avoided. Unless you *really* mean, literally, "does this piece of code compile", and you're not using that as a stand-in for some other intended semantics (like "does X conform to Y's signature constraints" or some such). SFINAE is evil. >:-(https://forum.dlang.org/post/rj5hok$c6q$1 digitalmars.com -Steve
Jan 21 2021
On Thursday, 21 January 2021 at 23:05:16 UTC, Steven Schveighoffer wrote:https://forum.dlang.org/post/rj5hok$c6q$1 digitalmars.com -SteveIf there's any singular thing I'd ever want to see from D this year, this is it. Something like that would make debugging so much nicer, especially when using libraries.
Jan 21 2021