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