Solving a constraint hiding an error in a function literal

Nick Treleaven nick at geany.org
Fri Feb 3 12:43:54 UTC 2023


When a constraint for an alias parameter checks that calling it 
compiles, it instantiates any function literal template. That 
triggers any latent bugs in the user's function literal that 
couldn't be detected before IFTI instantiates it. Those bugs then 
cause the constraint to fail, because the literal does not 
compile. The constraint then removes the otherwise matching 
template from the list of candidates to use.
```d
void f(alias a)() if (is(typeof(a()))) {}

void main()
{
     f!(x => blarg);
}
```
https://issues.dlang.org/show_bug.cgi?id=11907
https://issues.dlang.org/show_bug.cgi?id=14217

This is a common pattern in Phobos.
Initially I thought a constraint expression like 
`__traits(compiles, a())` should be moved to a `static if` test 
with a helpful error message. std.algorithm.all was changed to 
use static assert instead:

https://issues.dlang.org/show_bug.cgi?id=13683
https://github.com/dlang/phobos/pull/6607/files

But that still swallows the actual compile error and just says 
the callable doesn't work without really saying why (why isn't it 
a unary predicate if it accepts `range.front` as an argument?). 
Alternatively, the constraint check could be removed and you will 
get an internal error actually stating the precise error in the 
callable's body. But it doesn't help with overloading (see below).

What if there was a new trait to solve this? Suppose 
`__traits(callable, expr, args)`, that just does IFTI (if `expr` 
is a function template) and then checks that the parameter list 
of `expr` accepts `args`. It does not check that `expr(args)` 
actually compiles, it ignores any errors in the body of `expr`.

It doesn't check the return type, because that's not possible in 
general when the body does not compile and return type inference 
is needed. But overloading on the return type of a callable 
doesn't seem as important compared to overloading on the 
parameters of a callable.

The new trait can be used as a constraint or as a `static if` 
condition. As a constraint it can help to determine which 
overload matches. Imagine two overloads:
```d
// body would call a(int)
void f(alias a)()
if (__traits(callable, a, int()))

// body would call a(int, int)
void f(alias a)()
if (__traits(callable, a, int(), int()))
```
The trait would allow one of the overloads to be selected based 
on the number of parameters that `a` has, *even when* the body of 
`a` contains an error. Then one of the overloads of `f` is 
instantiated and an internal template error will be raised that 
`a` has an error. Despite this being an internal error, it is far 
better than the Phobos status quo because you can see what's 
actually failing with a precise error message.




More information about the Digitalmars-d mailing list