Thread overview
Recursion and attribute inference
Nov 02, 2022
Dukc
Nov 02, 2022
Paul Backus
Nov 03, 2022
zjh
Nov 03, 2022
Paul Backus
Nov 03, 2022
Dukc
Nov 03, 2022
Dennis
Nov 03, 2022
Timon Gehr
November 02, 2022

While writing about attribute inference to a D blog post draft, I encountered an ambiguous case.

float recurse()(float val, float till)
{
    return val < till? recurse(val * 2, till): val;
}

@safe void main()
{
    import std;

    writeln(recurse(1.5, 40.0));
    writeln(typeid(&recurse!()));
}

This compiles and prints

48
float function(float, float) pure nothrow @nogc @safe*

. However, the spec says that a function that recurses will not be inferred as @safe, pure or nothrow.

Which is right here, the implementation or the spec?

November 02, 2022

On Wednesday, 2 November 2022 at 19:17:10 UTC, Dukc wrote:

>

Which is right here, the implementation or the spec?

I'm inclined to say the implementation is right, since the code does not actually violate memory safety.

According to the latest D Foundation meeting summary, Dennis Korpel is looking into making @safe inference work properly for recursive functions, so it's possible that this limitation will be lifted entirely in the future.

November 03, 2022

On Wednesday, 2 November 2022 at 22:01:24 UTC, Paul Backus wrote:

>

Dennis Korpel is looking into making @safe inference work properly for recursive functions,

Is there any plan to deduce functions properties ?
I think that if we can deduce functions attributes .It will greatly reduce the attribute soup, make D code more concise, and greatly increase the adoption of D.

November 03, 2022

On Thursday, 3 November 2022 at 01:20:11 UTC, zjh wrote:

>

On Wednesday, 2 November 2022 at 22:01:24 UTC, Paul Backus wrote:

>

Dennis Korpel is looking into making @safe inference work properly for recursive functions,

Is there any plan to deduce functions properties ?
I think that if we can deduce functions attributes .It will greatly reduce the attribute soup, make D code more concise, and greatly increase the adoption of D.

I believe it's being considered as an option, but there are no concrete plans yet. Personally, I think it's a good idea.

November 03, 2022

On Wednesday, 2 November 2022 at 22:01:24 UTC, Paul Backus wrote:

>

On Wednesday, 2 November 2022 at 19:17:10 UTC, Dukc wrote:

>

Which is right here, the implementation or the spec?

I'm inclined to say the implementation is right, since the code does not actually violate memory safety.

I'm going to assume that. Thanks.

November 03, 2022

On Wednesday, 2 November 2022 at 22:01:24 UTC, Paul Backus wrote:

>

I'm inclined to say the implementation is right, since the code does not actually violate memory safety.

According to the latest D Foundation meeting summary, Dennis Korpel is looking into making @safe inference work properly for recursive functions, so it's possible that this limitation will be lifted entirely in the future.

Right.

When a function calls itself directly, attribute checks for that call are ignored.
However, when there's one or more functions in between, the reasoning goes "I'm calling a function that can't finish attribute inference right now, so conservatively assume all attributes failed to infer to be on the safe side." It seems the specification was modified to document this shortcoming of the implementation.

I wish we could simply assume all attributes were inferred, which would make this work:

void fun1()() { fun2(); }
void fun2()() { fun1(); }

void main() @safe pure nothrow @nogc
{
    fun1(); // currently fails
}

But here's a more tricky case:

@system void systemFunc();

void fun1()()
{
    alias T = typeof(fun2());
    systemFunc();
}

void fun2()()
{
    fun1();
}

void main0() @system
{
    fun1();
}

void main() @safe
{
    fun2(); // not system!
}

The compiler analyzes fun1, then fun2, and then needs to decide if the call to fun1 is safe.
If it assumes 'yes', then later when it sees the call to systemFunc(), it would need to go back and make fun2() @system, and re-analyze the function body of fun1 where T is now a @system function.

DMD is not suited to do this kind of backtracking, and that would potentially be very slow as well, so this probably won't be fixed in the general case.

However, in the absence of typeof() and other function type dependencies, a list of callees for each function can be maintained to solve the most common case of mutual function dependencies through simple function calls.

November 03, 2022
On 11/3/22 17:42, Dennis wrote:
> 
> The compiler analyzes `fun1`, then `fun2`, and then needs to decide if the call to `fun1` is safe.
> If it assumes 'yes', then later when it sees the call to `systemFunc()`, it would need to go back and make `fun2()` @system, and re-analyze the function body of `fun1` where `T` is now a @system function.
> 
> DMD is not suited to do this kind of backtracking, and that would potentially be very slow as well, so this probably won't be fixed in the general case.
> ...

I agree. I don't think there should be backtracking as it won't terminate in the general case even if there are consistent solutions to all constraints.

I guess in this case it's just annoying that there is no obvious default to choose if you are pressed to decide the attributes while still inferring them. I guess you could defer resolving them in derived types as well and couple inference appropriately, but I don't know if it is worth it, seems a bit tricky to get right/efficient with template instantiations because argument types might change after the fact.

> However, in the absence of typeof() and other function type dependencies, a list of callees for each function can be maintained to solve the most common case of mutual function dependencies through simple function calls.

+1.