Thread overview
Add __traits(canCall) and __traits(resolve)
3 days ago
Dom DiSc
1 day ago
Paul Backus
2 hours ago
Atila Neves
5 days ago

Just having run into the problem of __traits(compiles) swallowing unexpected errors for the umpteenth time, I'm wondering if we can formally get this feature into the language.

This was a similar post by me a while back: https://forum.dlang.org/post/rj5hok$c6q$1@digitalmars.com

What I'd like to see are 2 traits calls.

__traits(canCall, expression)

This trait should return 0 if the expression is not a function call, or if the function call expression does not match any in-scope symbols.

It should return the number of matching symbols at the highest matching level. If this number is 1, then the call should be expected to resolve to a single correct function call.

If the number is greater than 1, then the code can expect that calling the symbol in this manner will result in an ambiguity error (as long as all functions are valid).

This does NOT compile the function, it just uses all existing compiler mechanisms to find the matches and select the correct option. Semantic errors in the function should not cause this to return 0 (the point is to avoid the issue with __traits(compiles)).

Rationale

__traits(compiles) is often used to find whether a match to a call exists, but often times ends up with the confusing result of just ignoring the function altogether.

Everyone who has overloaded toString with an output range has had this experience:

import std.range;
struct S
{
    int x;
    void toString(Out)(ref Out outputRange) if (isOutputRange!(Out, char))
    {
        // forgot to import std.format;
        outputRange.formattedWrite("x is %s", x);
    }
}

void main()
{
    import std.stdio;

    writeln(S(3)); // S(3), but expected x is 3
}

Debugging such things is difficult, because the "best effort" function writeln decides that S.toString doesn't exist, so it just doesn't bother calling it, and makes up its own formatting.

If instead, writeln used this trait, it could see that the toString call matches, and try to use it, producing an error the user can see and fix.

This does not fix errors in signature, or template constraints.

The experience of trying to implement hooks in D is significantly degraded due to this limitation, and I'm hoping this would improve the situation.

__traits(resolve, expression)

IFTI and other calls are tricky to predict. This traits should take a valid compilable call expression (no errors this time), and give you a symbol that the compiler would use to resolve the expression.

I'm expecting a result here for expressions that are call expressions only. For other expressions, I would maybe expect an error? I don't know. Are there other tricky situations that would benefit from this?

The result would be an alias to the resolving symbol determined by the IFTI or overload resolution algorithm.

I'm not 100% sure how to specify this. But something like:

void foo(size_t opt = 42, K, V, T : K[V])(T val) {}

alias x = __traits(resolve, foo(int[int].init));
// equivalent to:
// alias x = foo!(42, int, int, int[int]);

Another example:

void foo(long x) { }
void foo(string s) { }

auto getHandler(T)() {
   void function(T) x = &foo;
   return x;
}

void main()
{
    auto x = getHandler!int;
    x(1);
}

This produces an error, because there is no foo overload that exactly matches int as the parameter. What you really want inside getHandler is, give me the address of the foo overload that would be called if I passed in the argument of type T.

// I'd like to write:
auto getHandler(T) {
    return &__traits(resolve, foo(T.init));
}

Rationale

This feature unlocks introspection capabilities that are nearly impossible today. You can write the alias as above, but you can't tap into the machinery of IFTI or overload resolution (with conversions) without actually trying to call these items.

What this is saying is "give me the thing you would call if I wrote this expression".

These kinds of questions can be answered by the compiler, but we have no way of asking them in a satisfactory way.

-Steve

3 days ago

On Saturday, 26 July 2025 at 02:27:08 UTC, Steven Schveighoffer wrote:

>

Just having run into the problem of __traits(compiles) swallowing unexpected errors for the umpteenth time, I'm wondering if we can formally get this feature into the language.
[...]

>

What I'd like to see are 2 traits calls.

__traits(canCall, expression)
[...]
__traits(resolve, expression)
[...]

>

What this is saying is "give me the thing you would call if I wrote this expression".

These kinds of questions can be answered by the compiler, but we have no way of asking them in a satisfactory way.

Yes, nice idea. I often wished something like this would be possible.

2 days ago

On Saturday, 26 July 2025 at 02:27:08 UTC, Steven Schveighoffer wrote:

>

__traits(resolve, expression)

I was informed that opend has a feature similar to this proposal:

__traits(resolveFunctionCall, overloadSet, argTypes...)

While similar, it's not exactly what I want. Because it's not as natural as "what would you do if I wrote this expression".

For example, this would not work well with UFCS functions. I'm unsure if it works with template functions.

-Steve

2 days ago
Something I've wanted for along time now, to speed up reflection is the offering to filter symbols.

```d
__traits(filterFunctionByTypes, source, Return, Parameters...)
```

Where ``source`` could be a type, alias (overload set), named import ext.

Where ``Return`` could be ``auto`` to mean "don't care".

But also:

```d
__traits(filterFunctionByExpression, source, args...)
```

The result of both is an alias sequence of symbols.
1 day ago

On Tuesday, 29 July 2025 at 04:28:44 UTC, Richard (Rikki) Andrew Cattermole wrote:

>

Something I've wanted for along time now, to speed up reflection is the offering to filter symbols.

__traits(filterFunctionByTypes, source, Return, Parameters...)

Where source could be a type, alias (overload set), named import ext.

Where Return could be auto to mean "don't care".

But also:

__traits(filterFunctionByExpression, source, args...)

The result of both is an alias sequence of symbols.

This is a tough one to implement, because of the auto thing. Is there a reason you need to avoid passing actual types?

This also doesn't seem to be designed to handle templates?

-Steve

1 day ago

On Tuesday, 29 July 2025 at 02:11:22 UTC, Steven Schveighoffer wrote:

>

On Saturday, 26 July 2025 at 02:27:08 UTC, Steven Schveighoffer wrote:

>

__traits(resolve, expression)

I was informed that opend has a feature similar to this proposal:

__traits(resolveFunctionCall, overloadSet, argTypes...)

While similar, it's not exactly what I want. Because it's not as natural as "what would you do if I wrote this expression".

Also, taking a list of types instead of an actual argument list means that you cannot distinguish between by-ref and by-value overloads of the same function.

1 day ago

On Saturday, 26 July 2025 at 02:27:08 UTC, Steven Schveighoffer wrote:

>

__traits(resolve, expression)

Although we came up with it independently, I have to give credit to Paul Backus to coming up with this first!

Now I know I got the right idea ;) And even the name matches!

https://forum.dlang.org/post/atjkxxmugmfbsntvwgpi@forum.dlang.org

-Steve

1 day ago
On 30/07/2025 2:48 AM, Steven Schveighoffer wrote:
> On Tuesday, 29 July 2025 at 04:28:44 UTC, Richard (Rikki) Andrew Cattermole wrote:
>> Something I've wanted for along time now, to speed up reflection is the offering to filter symbols.
>>
>> ```d
>> __traits(filterFunctionByTypes, source, Return, Parameters...)
>> ```
>>
>> Where ``source`` could be a type, alias (overload set), named import ext.
>>
>> Where ``Return`` could be ``auto`` to mean "don't care".
>>
>> But also:
>>
>> ```d
>> __traits(filterFunctionByExpression, source, args...)
>> ```
>>
>> The result of both is an alias sequence of symbols.
> 
> This is a tough one to implement, because of the auto thing. Is there a reason you need to avoid passing actual types?

It would be types. Its just that auto would be consumed to mean ignore return since its a required argument.

> This also doesn't seem to be designed to handle templates?
> 
> -Steve

Some should work, but others may need more control to do the filtering as you want it to work.

The main thing is that its a transfer function, take in alias sequence, give an alias sequence.

It'll apply in a lot more cases if you use this form.
2 hours ago

On Saturday, 26 July 2025 at 02:27:08 UTC, Steven Schveighoffer wrote:

>

Just having run into the problem of __traits(compiles) swallowing unexpected errors for the umpteenth time, I'm wondering if we can formally get this feature into the language.

[...]

Both make sense to me. I've also lost count of how many times I've had to debug why __traits(compiles) was failing.