www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Looks like wrong error message

reply welkam <wwwelkam gmail.com> writes:
Error says that it cant deduce function from argument types but 
in reality it fails to meet function template constraints. In 
this case !is(CommonType!(staticMap!(ElementType, 
staticMap!(Unqual, Ranges))) == void)

In human terms it means that arguments are not the same type. Is 
this require bug report or it works as expected?

Code:
void main() {
     import std.range : chain;
     string s = "string";
     auto arrayOfStrings = ["string"];
     arrayOfStrings.chain(s);
}

error:
Main.d(9): Error: template std.range.chain cannot deduce function 
from argument types !()(string[], string), candidates are:
C:\D\dmd2\windows\bin\..\..\src\phobos\std\range\package.d(894):  
       std.range.chain(Ranges...)(Ranges rs) if (Ranges.length > 0 
&& allSatisfy!(isInputRange, staticMap!(Unqual, Ranges)) && 
!is(CommonType!(staticMap!(ElementType, staticMap!(Unqual, 
Ranges))) == void))
Jan 28 2018
parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Sunday, January 28, 2018 20:02:48 welkam via Digitalmars-d-learn wrote:
 Error says that it cant deduce function from argument types but
 in reality it fails to meet function template constraints. In
 this case !is(CommonType!(staticMap!(ElementType,
 staticMap!(Unqual, Ranges))) == void)

 In human terms it means that arguments are not the same type. Is
 this require bug report or it works as expected?

 Code:
 void main() {
      import std.range : chain;
      string s = "string";
      auto arrayOfStrings = ["string"];
      arrayOfStrings.chain(s);
 }

 error:
 Main.d(9): Error: template std.range.chain cannot deduce function
 from argument types !()(string[], string), candidates are:
 C:\D\dmd2\windows\bin\..\..\src\phobos\std\range\package.d(894):
        std.range.chain(Ranges...)(Ranges rs) if (Ranges.length > 0
 && allSatisfy!(isInputRange, staticMap!(Unqual, Ranges)) &&
 !is(CommonType!(staticMap!(ElementType, staticMap!(Unqual,
 Ranges))) == void))
There is nothing incorrect about the error message. The compiler looked at all of the functions in the overload set, and it found none that matched. The reason that it found none that matched was because it couldn't find any function template where those arguments passed the template constraint, and it tells you so. So, there is no bug here in the sense that the compiler is telling you the wrong thing. You can certainly open a bug report arguing that the error messages isn't human-friendly enough and suggest alternatives, and something along those lines may be implemented, but the message isn't actually wrong. So, feel free to open a bug report. However, you're not going to get an error message that says anything like "the arguments aren't the same type." The compiler doesn't understand what the template constraint means in "human terms." It just knows whether it's true or false, and in this case, if you provide arguments that don't have a common type that they implicitly convert to, then the template constraint will fail. But ultimately, you're going to have to read the template constraint and figure out why the arguments are failing. - Jonathan M Davis
Jan 28 2018
parent reply welkam <wwwelkam gmail.com> writes:
On Sunday, 28 January 2018 at 20:42:52 UTC, Jonathan M Davis 
wrote:
 There is nothing incorrect about the error message. The 
 compiler looked at all of the functions in the overload set, 
 and it found none that matched. The reason that it found none 
 that matched was because it couldn't find any function template 
 where those arguments passed the template constraint, and it 
 tells you so.

 So, there is no bug here in the sense that the compiler is 
 telling you the wrong thing. You can certainly open a bug 
 report arguing that the error messages isn't human-friendly 
 enough and suggest alternatives, and something along those 
 lines may be implemented, but the message isn't actually wrong. 
 So, feel free to open a bug report.

 However, you're not going to get an error message that says 
 anything like "the arguments aren't the same type." The 
 compiler doesn't understand what the template constraint means 
 in "human terms." It just knows whether it's true or false, and 
 in this case, if you provide arguments that don't have a common 
 type that they implicitly convert to, then the template 
 constraint will fail. But ultimately, you're going to have to 
 read the template constraint and figure out why the arguments 
 are failing.

 - Jonathan M Davis
I would not complain if there were multiple functions just like error said. But the set contains only one making it not a set anymore and it says nothing about constraints. I downloaded dmd source. Might figure out something myself tomorrow
Jan 28 2018
next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Sun, Jan 28, 2018 at 10:53:39PM +0000, welkam via Digitalmars-d-learn wrote:
 On Sunday, 28 January 2018 at 20:42:52 UTC, Jonathan M Davis wrote:
[...]
 However, you're not going to get an error message that says anything
 like "the arguments aren't the same type." The compiler doesn't
 understand what the template constraint means in "human terms." It
 just knows whether it's true or false, and in this case, if you
 provide arguments that don't have a common type that they implicitly
 convert to, then the template constraint will fail. But ultimately,
 you're going to have to read the template constraint and figure out
 why the arguments are failing.
[...] Well, that's not *quite* true. The compiler *does* have enough information to be able evaluate the constraints, for one. So in theory it *should* be able to identify which constraint(s) failed. Some time ago there was a push to refactor Phobos sig constraints in CNF (conjunctive normal form), which would simplify this somewhat: if a function has sig constraints X, Y and Z, and Y evaluated to false, then the compiler could print condition Y as the cause of failure. Furthermore, the reason for the perceived unhelpful message is more subtle. It's because a sig constraint amounts to a limitation in scope of a particular overload, effectively saying "this overload only applies if conditions X, Y and Z are true". So a failure in one or more sig constraints isn't necessarily a *problem* per se; it just means that particular overload declines instantiation to the rest of the overload set. Only if the failure ultimately leads to a failure to match *all* overloads, can we say for sure that the failed constraint was the "cause" of the mismatch. And this is where things get tricky, because if you have an overload of, say, 10 members, then obviously a failure to find a match means that *every* overload had its sig constraints fail one way or another. But without being able to read the programmer's mind, the compiler has no idea which overload was the *intended* target of the function call; so it would not be able to tell which failed sig constraint was the most relevant to the user. All >=10 failed conditions would have to be displayed, and it would be up to the user to decide which one is the relevant one. Of course, even in spite of this, displaying the failed conditions is still better than nothing. If the compiler assumes CNF on sig constraints, then it can list, along with each candidate overload, the failing term(s) in the CNF for that particular overload as the reason that overload wasn't chosen. For example, the output could be something like: Error: unable to match func() with argument type X; candidates are: std/range/package.d(100): auto func(R)(R r) if (isInputRange!R && is(ElementType!R == char): condition is(ElementType!R == char) failed std/range/package.d(200: auto func(R)(R r) if (isForwardRange!R && is(ElementType!R : int): condition isForwardRange!R failed std/range/package.d(300): auto func(E)(E[] arr): X does not match parameter type E[] You'd still have to parse through each listed overload, but at least the compiler would tell you which sig constraint failed. IME, this would be helpful, because sometimes it's not at all obvious which sig constraint failed, especially if the sig constraints call upon other templates to test the incoming type (e.g., isInputRange, which contains a whole bunch of tests, any of which could fail and it's not always obvious which ones), and I wind up having to parse through *all* sig constraints of *all* overloads just to figure out which one was the real cause.
 I would not complain if there were multiple functions just like error
 said.  But the set contains only one making it not a set anymore and
 it says nothing about constraints.
[...] This topic has come up again numerous times, and I think *some* improvement on this front is necessary. One improvement is to decrease our reliance on overloads, and to use more permissive sig constraints with static ifs inside the function body that can provide a more helpful error message. Not to mention, some of the current Phobos overloads are divided based on implementation details user code shouldn't need to know about, rather than *logical* overload boundaries, for example: // handle the case when S is a string auto func(R,S)(R haystack, S needle) if (isInputRange!R && isSomeString!S) ... // handle the case when S is an array but not a string auto func(R,S)(R haystack, S needle) if (isInputRange!R && !isSomeString!S && isArray!S) ... This is leaking implementation details into the sig constraints; it really should be written this way instead: auto func(R,S)(R haystack, S needle) if (isInputRange!R) { static if (isSomeString!S) ... // handle the case where S is a string else static if (isArray!S) ... // handle the case where S is an array but not a string else // Helpful message if implementation doesn't // support the incoming type static assert(0, "S must be either a string or an array"); } This way, there's (1) a smaller number of overloads the user needs to parse through when an error occurs; (2) when an error occurs, it's obvious that the failing condition is isInputRange!R: the user only needs to figure out 1 sig constraint as opposed to 4 to find the problem; and (3) the else-clause of an internal static if can be used to provide more user-friendly error messages, if the argument is an input range, but not of a kind the current implementation supports. I'd even venture to say that this applies not just to Phobos but to template code in general. Personally, I go by the rule that the sig constraints should be general, and should document what the user would logically understand the function parameters ought to be (i.e., the parameter must be an input range). Anything else, like handling various specializations (do X if the element type is char, do Y if it's a POD, etc.) should be done inside the function body, away from the public-facing sigs. If the current implementation doesn't handle all of the types that the function logically should, then the else-clause of the internal static if can be used to provide a more user-friendly error message explaining why the passed-in type doesn't work. T -- We are in class, we are supposed to be learning, we have a teacher... Is it too much that I expect him to teach me??? -- RL
Jan 28 2018
prev sibling parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Sunday, January 28, 2018 16:08:17 H. S. Teoh via Digitalmars-d-learn 
wrote:
 On Sun, Jan 28, 2018 at 10:53:39PM +0000, welkam via Digitalmars-d-learn 
wrote:
 On Sunday, 28 January 2018 at 20:42:52 UTC, Jonathan M Davis wrote:
[...]
 However, you're not going to get an error message that says anything
 like "the arguments aren't the same type." The compiler doesn't
 understand what the template constraint means in "human terms." It
 just knows whether it's true or false, and in this case, if you
 provide arguments that don't have a common type that they implicitly
 convert to, then the template constraint will fail. But ultimately,
 you're going to have to read the template constraint and figure out
 why the arguments are failing.
[...] Well, that's not *quite* true. The compiler *does* have enough information to be able evaluate the constraints, for one. So in theory it *should* be able to identify which constraint(s) failed. Some time ago there was a push to refactor Phobos sig constraints in CNF (conjunctive normal form), which would simplify this somewhat: if a function has sig constraints X, Y and Z, and Y evaluated to false, then the compiler could print condition Y as the cause of failure.
Sure, work could be done to indicate which pieces of a template constraint are true and which are false, but the point still stands that it can't actually tell the programmer what that means in "human terms." The intent isn't there. Now, that's why there have been suggestions for doing stuff like providing error strings to go with template constraints that can be displayed on failure, but ultimately, the compiler can't read the programmer's mind.
 Of course, even in spite of this, displaying the failed conditions is
 still better than nothing.  If the compiler assumes CNF on sig
 constraints, then it can list, along with each candidate overload, the
 failing term(s) in the CNF for that particular overload as the reason
 that overload wasn't chosen.
IMHO, that's doomed to failure unless the compiler somehow requires that the template constraints be in CNF, which would be really annoying in its own right. The most practical thing at this point is to intelligently use traits with useful names in template constraints and to simplify the constraints as much as possible, with the overloading primarily done internally with static ifs - like you like to point out, and we've done some of that to Phobos, but there's a lot of work left to be done there. Still, I think that it's always going to be the case on some level that the programmer is going to have to look at template constraints and figure out why they're failing, even if we do manage to improve how much information is given about which parts are failing. - Jonathan M Davis
Jan 28 2018