May 06, 2017
https://issues.dlang.org/show_bug.cgi?id=17374

          Issue ID: 17374
           Summary: Improve inferred attribute error message
           Product: D
           Version: D2
          Hardware: All
                OS: Windows
            Status: NEW
          Severity: enhancement
          Priority: P1
         Component: dmd
          Assignee: nobody@puremagic.com
          Reporter: destructionator@gmail.com

Consider the following:

void call(alias T)() {
    T();
}

void foo() {}

void main() @nogc {
    call!foo();
}


If you compile that, you get the error:

Error: @nogc function 'D main' cannot call non-@nogc function
'test.call!(foo).call'


The problem is that since `call` is inferred, you don't really know why - it likely has to do with some parameter, but you aren't sure. This has been a FAQ recently on the forums and chatroom.

Here's my suggested solution: report that AND go down and report what in the call graph failed the check without inferring.

In this case, the error message would be something like:


Error: @nogc function 'D main' cannot call non-@nogc function 'test.call!(foo).call' because it eventually calls non-@nogc function 'foo'



The "eventually" is because of a case like this:


void call(alias T)() {
        T();
}

void secondLayer(alias T)() {
        T();

}
void foo() {}

void main() @nogc {
        secondLayer!(call!foo)();
}



The error message I want here is:

 Error: @nogc function 'D main' cannot call non-@nogc function
'test.secondLayer!(call).secondLayer' because it eventually calls 'foo'.


secondLayer calls `call!foo`, but since that is inferred too, we don't want to put the blame there. So it doesn't issue a message about that per se (it could, but it would be useless error message spam). Instead, it keeps going until it hits a non-inferred function or inferred function that fails because it uses a built in (like a ~ b or the new keyword, etc.) and reports the first one of those it sees.


A real world case of this would be:


import std.range;
import std.algorithm;

void main() @nogc {
        string foo;
        auto a = foo.retro();
        foreach(item; a) {}
}


Well, imagine that with a bunch of maps and filters too, your regular range pipeline to make it more complicated, but even this short test case gives you:


lll.d(7): Error: @nogc function 'D main' cannot call non-@nogc function
'std.range.retro!string.retro.Result!().Result.popFront'
lll.d(7): Error: @nogc function 'D main' cannot call non-@nogc function
'std.range.retro!string.retro.Result!().Result.front'


You can figure it has to do with `retro` and work around here, but if it has more maps and filters and so on, it would be really hard to figure out what this is (I had to trace this on IRC for a newb).

And even if you did figure it was retry... why? Why wouldn't that work? Imagine the case of a unittest that is trying to compile this to test the library. The error message could solve that very quickly by saying "because it eventually calls `std.range.primitives.popBack` which is not @nogc because of `throw new Exception`.


This would make inferred attributes much better and take away one of my three major complaints against them in my recent reddit rant, and it is all knowledge the compiler already has....

--