Bug 16528 – @safe inference does not work for mutually recursive functions
Status
RESOLVED
Resolution
DUPLICATE
Severity
normal
Priority
P1
Component
dmd
Product
D
Version
D2
Platform
All
OS
All
Creation time
2016-09-23T15:29:16Z
Last change time
2022-10-14T13:58:40Z
Keywords
safe
Assigned to
No Owner
Creator
Andrei Alexandrescu
Comments
Comment #0 by andrei — 2016-09-23T15:29:16Z
Consider:
void fun1(T)(T value)
{
if (value) fun2(value - 1);
}
void fun2(T)(T value)
{
if (value) fun1(value - 1);
}
@safe void main()
{
fun1(4);
}
This fails to compile although obviously the functions are safe. I'm looking for a reasonable workaround for the topN work. This doesn't work either:
void fun1(T)(T value)
{
if (value) fun2(value - 1);
}
void fun2(T)(T value)
{
if (value) () @trusted { fun1(value - 1); }();
}
@safe void main()
{
fun1(4);
}
Comment #1 by john.loughran.colvin — 2016-09-23T17:21:01Z
Ok so this is absolutely hideous and there is definitely something weird going on with the compiler in this region, but I *think* the following works as expected. There is probably an adaption of this that can be made simpler, but I don't have time to find it right now.
template fun1Wrap(T)
{
enum foo = q{
void impl(T value) %s
{
if (value) fun2%s(value - 1);
}
};
import std.format : format;
static if(__traits(compiles, { mixin(format(foo, `@safe`, `Safe`)); }))
mixin(format(foo, `@safe`, `Safe`));
else
mixin(foo);
}
void fun1Safe(T)(T t) @safe
{
fun1Wrap!T.impl(t);
}
void fun1(T)(T t)
{
static if (__traits(compiles, fun1Safe(t) ))
fun1Safe(t);
else
fun1Wrap!T.impl(t);
}
template fun2Wrap(T)
{
enum foo = q{%s void impl(T value)
{
if (value) fun1%s(value - 1);
}
};
import std.format : format;
static if(__traits(compiles, { mixin(format(foo, `@safe`, `Safe`)); }))
mixin(format(foo, `@safe`, `Safe`));
else
mixin(foo);
}
void fun2Safe(T)(T t) @safe
{
fun2Wrap!T.impl(t);
}
void fun2(T)(T t)
{
static if (__traits(compiles, fun1Safe(t) ))
fun2Safe(t);
else
fun2Wrap!T.impl(t);
}
@safe void main()
{
fun1(4);
}
Comment #2 by lodovico — 2016-09-23T18:38:23Z
Disclaimer: I don't know anything about DMD internals.
It looks like currently, in case of mutual recursion, the compiler takes the pessimistic approach and does not infer any attribute. This is too conservative. It is possible to devise an inference algorithm that optimistically assumes functions to have all attributes, and progressively removes them as needed.
Cycles due to mutual recursion can be arbitrarily complex, with many functions, but the informations gathered during instantiation are enough to identify the entire set of those functions. Then the compiler infers the safety of each of those function separately, without taking into account call instructions to other functions of the same recursion set. If this process identifies a function that does not have a specific attribute, than no function in the set has that attribute. Otherwise, all functions in the set have it.
I didn't reason that much about the above algorithm, but I'm under the impression that it works in all cases and is also quite easy to understand and implement.
Comment #3 by bugzilla — 2018-03-11T00:26:23Z
A minimal test case:
void fun1()()
{
fun2();
}
void fun2()()
{
fun1();
}
@safe void test()
{
fun1();
}
Comment #4 by john.michael.hall — 2020-06-21T15:45:18Z
The title of this could be adjusted so that it makes clear that this applies to all attribute inference. If you change Walter's example from @safe to nothrow, @nogc, or pure, then you get the same errors.
Comment #5 by dkorpel — 2022-10-03T14:30:10Z
*** Issue 23128 has been marked as a duplicate of this issue. ***
Comment #6 by dkorpel — 2022-10-14T13:58:40Z
*** This issue has been marked as a duplicate of issue 7205 ***