On Monday, 25 April 2016 at 17:52:58 UTC, Andrei Alexandrescu wrote:
/d935/f781.d(16): Error: template f781.find cannot deduce function from argument types !()(NotARange, int), candidates are: /d935/f781.d(3): f781.find(R, E)(R range, E elem) constraint failed: isInputRange!NotARange

This is more-or-less what I've been wanting to do (though I was thinking of using color or something in the signature to show pass/fail/not tested on each clause, but your approach works too.)

It is very important that it shows what failed and what the arguments are. The rest is nice, but less impactful.

So this would make a big difference and should be a high priority to implement.

BTW I'd also like traditional overloaded functions to show the match/not match report of arguments. It lists them now but is hard to read a long list. If the computer just said "argument #2 didn't match, int != string" or something it'd give at-a-glance info there too.

But indeed, constraints are the much higher return.

Idea #2: Allow custom error messages

Let me show you what I've been toying with the last few weeks:



struct Range {
        bool empty() { return true; }
        void popFront() {}
        int front;
}

// you test it at declaration point to get static errors
// i recommend people do this now, even with our less-helpful
// isInputRagnge
mixin validateInputRange!Range;

/* *************** */


import std.traits;

// the validate mixin prints the errors
mixin template validateInputRange(T) {
        static assert(isInputRange!T, checkInputRange!T);
}

// the is template returns bool if it passed
template isInputRange(T) {
        enum isInputRange = checkInputRange!T.length == 0;
}


// and the check function generates an array of error
// strings using introspection
pragma(inline, true)
template checkInputRange(T) {
        string[] checkInputRangeHelper() {
                string[] errors;

                static if(!hasMember!(T, "empty"))
                        errors ~= "has no member `empty`";
                else static if(!memberCanBeUsedInIf!(T, "empty"))
                        errors ~= "empty cannot be used in if";

                static if(!hasMember!(T, "popFront"))
                        errors ~= "has no member `popFront`";
                else static if(!isCallableWithZeroArguments!(T, "popFront"))
errors ~= "`popFront()` is not callable. Found type: " ~ typeof(__traits(getMember, T, "popFront")).stringof ~ ". Expected: void()";

                static if(!hasMember!(T, "front"))
                        errors ~= "has no member `front`";

                return errors;
        }

        enum checkInputRange = checkInputRangeHelper();
}

/* *************** */

// these can be added to std.traits

template memberCanBeUsedInIf(T, string member) {
        static if(__traits(compiles, (inout int = 0){
                T t = T.init;
                if(__traits(getMember, t, member)) {}
        }))
                enum memberCanBeUsedInIf = true;
        else
                enum memberCanBeUsedInIf = false;
}

template isCallableWithZeroArguments(T, string member) {
        static if(__traits(compiles, (inout int = 0){
                T t = T.init;
                (__traits(getMember, t, member))();
        }))
                enum isCallableWithZeroArguments = true;
        else
                enum isCallableWithZeroArguments = false;

}



===============

With appropriate library support, those check functions could be pretty easily written and the rest generated automatically.

Now, the compiler doesn't know anything about the error strings, but generating them with simple CTFE gives us the full language to define everything. The compiler could just learn the pattern (or we add some pragma) that when isInputRange fails, it prints out the report the library generated.


But this is doable today and shouldn't break any code.

Reply via email to