Bug 21511 – Uniqueness detection failed in non-template function with delegate parameter
Status
RESOLVED
Resolution
INVALID
Severity
critical
Priority
P1
Component
dmd
Product
D
Version
D2
Platform
All
OS
All
Creation time
2020-12-29T00:16:46Z
Last change time
2021-01-02T02:59:19Z
Keywords
rejects-valid, safe
Assigned to
No Owner
Creator
Bolpat
Comments
Comment #0 by qs.il.paperinik — 2020-12-29T00:16:46Z
immutable(int)* f(const scope int* function() pure @safe fp) @safe
{
return fp(); // okay
}
immutable(int)* g(const scope int* delegate() pure @safe dg) @safe
{
return dg(); // error
}
immutable(int)* h(DG)(const scope DG dg) @safe
// just to be sure nothing surprising happens:
if (is(DG == int* delegate() pure @safe))
{
return dg(); // okay
}
The reason why the result of calling the function pointer or delegate are valid, is due to uniqueness.[1] Especially weird is the fact that h lowers to g after the template parameter is determined (only one option). Whether the template parameter is given explicitly or is inferred from the argument is irrelevant.
struct Test
{
int* pureFunc() @safe pure;
int* impureFunc() @safe;
}
@safe unittest
{
Test test;
auto p = h(&test.pureFunc); // okay
auto q = h!(int* delegate() pure @safe)(&test.pureFunc); // also okay
}
[1] https://dlang.org/spec/const3.html#creating_immutable_data
Comment #1 by ag0aep6g — 2020-12-29T15:11:04Z
(In reply to Bolpat from comment #0)
> immutable(int)* g(const scope int* delegate() pure @safe dg) @safe
> {
> return dg(); // error
> }
>
> immutable(int)* h(DG)(const scope DG dg) @safe
> // just to be sure nothing surprising happens:
> if (is(DG == int* delegate() pure @safe))
> {
> return dg(); // okay
> }
>
> The reason why the result of calling the function pointer or delegate are
> valid, is due to uniqueness.[1] Especially weird is the fact that h lowers
> to g after the template parameter is determined (only one option).
[...]
> [1] https://dlang.org/spec/const3.html#creating_immutable_data
There is a significant difference between g and h: g isn't `pure`. h is.
The issue can be summarized like this:
immutable(int)* foo(const scope int* delegate() pure @safe dg) pure @safe
{
immutable int* i = dg(); /* error (line A) */
return dg(); /* no error (line B) */
}
The heart of the problem is that delegates offer a head const mechanism. `dg` can return a pre-existing mutable int* from its context. It's not guaranteed to be unique. Example:
class C
{
int x;
int* method() pure @safe { return &x; }
}
void main()
{
auto c = new C;
const int* delegate() dg = &c.method;
int* p = dg(); /* no error; not a unique reference (line C) */
}
Such a `dg()` cannot be allowed to implicitly convert to immutable. The compiler recognizes that when it rejects line A, but it misses line B.
We can either:
1) disallow line B, or
2) make `const` on delegates transitive, consequently allowing line A and disallowing line C.