- There is no way to specify that a delegate is strongly pure without resorting to type deduction, because
- Member functions/local functions are handled inconsistently.
- Delegate types legally obtained from certain member functions are illegal to declare.
- 'pure' means 'weakly pure' for member functions and 'strongly pure' for local functions. Therefore it means 'weakly pure' for delegates, as those can be obtained from both.
- Delegates may break the transitivity of immutable, and by extension, shared.
A good first step in fixing up immutable/shared would be to make everything that is annotated 'error' pass, and the line annotated 'ok' should fail:
import std.stdio;
struct S{
int x;
int foo()pure{
return x++;
}
int bar()immutable pure{
// return x++; // error
return 2;
}
}
int delegate()pure s(){
int x;
int foo()pure{
// return x++; // error
return 2;
}
/+int bar()immutable pure{ // error
return 2;
}+/
return &foo;
}
void main(){
S s;
int delegate()pure dg = &s.foo;
// int delegate()pure immutable dg2 = &s.bar; // error
writeln(dg(), dg(), dg(), dg()); // 0123
immutable int delegate()pure dg3 = dg; // ok
writeln(dg3(), dg3(), dg3(), dg3()); // 4567
// static assert(is(typeof(cast()dg3)==int delegate() immutable pure)); // error
auto bar = &s.bar;
pragma(msg, typeof(bar)); // "int delegate() immutable pure"
}
Comment #1 by bearophile_hugs — 2014-01-14T12:59:17Z
Related:
void foo(const int[] a) {
int bar() pure {
return a[0];
}
}
void main() {}
DMD 2.065alpha gives (also note the duplicated error message):
test1.d(3): Error: pure nested function 'bar' cannot access mutable data 'a'
test1.d(3): Error: pure nested function 'bar' cannot access mutable data 'a'
Comment #2 by timon.gehr — 2014-04-05T14:31:07Z
(In reply to comment #0)
>
> struct S{
> int x;
> int foo()pure{
> return x++;
> }
> int bar()immutable pure{
> // return x++; // error
> return 2;
> }
> }
>
(this behaviour is obviously expected.)
Comment #3 by xinok — 2014-04-26T03:25:43Z
'S.bar' is immutable, meaning it can only access immutable members of 'S', so the error is correct.
's.bar' (note the s is lowercase)
(In reply to timon.gehr from comment #0)
> - There is no way to specify that a delegate is strongly pure without
> resorting to type deduction, because
> - Member functions/local functions are handled inconsistently.
> - Delegate types legally obtained from certain member functions are
> illegal to declare.
> - 'pure' means 'weakly pure' for member functions and 'strongly pure'
> for local functions. Therefore it means 'weakly pure' for delegates, as
> those can be obtained from both.
I think the trouble of member functions vs local functions may be a technicality: The struct object is seen as a hidden argument passed to the member function, whereas it's not seen this way with local functions. Because pure functions can take mutable arguments, it will work for member functions. But because the context pointer is not seen as an argument for local functions, it fails.
> - Delegates may break the transitivity of immutable, and by extension,
> shared.
>
> A good first step in fixing up immutable/shared would be to make everything
> that is annotated 'error' pass, and the line annotated 'ok' should fail:
>
> import std.stdio;
>
> struct S{
> int x;
> int foo()pure{
> return x++;
> }
> int bar()immutable pure{
> // return x++; // error
> return 2;
> }
> }
Immutable functions can only read immutable members, so this error is actually correct.
>
> int delegate()pure s(){
> int x;
> int foo()pure{
> // return x++; // error
> return 2;
> }
> /+int bar()immutable pure{ // error
> return 2;
> }+/
> return &foo;
> }
Function 'bar' actually compiles fine for me, no errors.
>
> void main(){
> S s;
> int delegate()pure dg = &s.foo;
> // int delegate()pure immutable dg2 = &s.bar; // error
> writeln(dg(), dg(), dg(), dg()); // 0123
> immutable int delegate()pure dg3 = dg; // ok
> writeln(dg3(), dg3(), dg3(), dg3()); // 4567
> // static assert(is(typeof(cast()dg3)==int delegate() immutable pure));
> // error
> auto bar = &s.bar;
> pragma(msg, typeof(bar)); // "int delegate() immutable pure"
> }
immutable int delegate()pure == immutable(int delegate()pure) != int delegate() immutable pure
Other than some confusion with member functions vs local functions, I'm not sure there's actually any bugs here.
Comment #4 by timon.gehr — 2014-04-26T10:15:45Z
(In reply to Xinok from comment #3)
> 'S.bar' is immutable, meaning it can only access immutable members of 'S',
> so the error is correct.
> ...
>
> Immutable functions can only read immutable members, so this error is
> actually correct.
>
(Indeed. I already noted this.)
> I think the trouble of member functions vs local functions may be a
> technicality: The struct object is seen as a hidden argument passed to the
> member function, whereas it's not seen this way with local functions. Because > pure functions can take mutable arguments, it will work for member functions. > But because the context pointer is not seen as an argument for local
> functions, it fails.
But obviously it is a hidden argument. There are no distinctions to be made between local functions and member functions in this respect, and this is especially important since delegates can be formed from both and their types need to have a consistent interpretation.
> >
> > int delegate()pure s(){
> > int x;
> > int foo()pure{
> > // return x++; // error
> > return 2;
> > }
> > /+int bar()immutable pure{ // error
> > return 2;
> > }+/
> > return &foo;
> > }
>
> Function 'bar' actually compiles fine for me, no errors.
> ...
This is a change in behaviour since the bug was reported.
> >
> > void main(){
> > S s;
> > int delegate()pure dg = &s.foo;
> > // int delegate()pure immutable dg2 = &s.bar; // error
> > writeln(dg(), dg(), dg(), dg()); // 0123
> > immutable int delegate()pure dg3 = dg; // ok
> > writeln(dg3(), dg3(), dg3(), dg3()); // 4567
> > // static assert(is(typeof(cast()dg3)==int delegate() immutable pure));
> > // error
> > auto bar = &s.bar;
> > pragma(msg, typeof(bar)); // "int delegate() immutable pure"
> > }
>
> immutable int delegate()pure == immutable(int delegate()pure) != int
> delegate() immutable pure
> ...
Sure. Why is this relevant? The first 'error' appears to be fine now. The line annotated 'ok' absolutely must fail. In case this is not obvious consider the following code, which compiles and runs fine now:
class C{}
C foo(immutable C delegate()@safe pure dg)pure @safe{
return dg();
}
void main()@safe{
C c = new C();
struct S{
C c;
C foo()pure{ return c; }
}
S s=S(c);
immutable(C) d=foo(&s.foo);
assert(c is d);
}
I.e. this 'ok' is a loophole that allows unsafe type coercion.
The static assertion should pass. If top-level immutable is stripped using the cast(), this should not affect the qualification of the context pointer (because this way, it is @safe).
This is analogous to
immutable(int*) x;
static assert(is(typeof(cast()x)==immutable(int)*));
>
> Other than some confusion with member functions vs local functions,
(This confusion is actually the main part of the bug report and all the other problems seem to be quite related to it.)
> I'm not sure there's actually any bugs here.
Some parser limitations appear to have been fixed since this issue was reported but there are still plenty bugs as demonstrated above.
I'm adjusting the implicit pure annotation behavior for nested functions in:
https://github.com/D-Programming-Language/dmd/pull/4344
If the nested function is a template, attribute inference is preferred and pure will be added only when the function is accessing enclosing pure function context.
auto foo() pure {
int x;
auto bar()() { // template function bar can be impure by default
// bar will be forced to get weak purity, only when it's trying
// to access enclosing pure function context.
//x = 1;
// otherwise, bar can call impure functions and then
// it will be inferred to impure.
impureFuncCall().
}
}
Once nested template function is inferred to impure, it cannot access enclosing pure context anymore.
auto foo() pure {
int x;
auto bar()() {
impureFuncCall().
x = 1; // Error: impure function 'bar' cannot access variable 'x'
// declared in enclosing pure function 'foo'
}
}
Comment #12 by github-bugzilla — 2015-02-18T03:37:31Z
*** Issue 7457 has been marked as a duplicate of this issue. ***
Comment #14 by k.hara.pg — 2015-03-25T05:14:08Z
*** Issue 10614 has been marked as a duplicate of this issue. ***
Comment #15 by k.hara.pg — 2015-03-25T05:19:51Z
*** Issue 11412 has been marked as a duplicate of this issue. ***
Comment #16 by timon.gehr — 2015-07-03T17:59:42Z
(In reply to Kenji Hara from comment #11)
> I'm adjusting the implicit pure annotation behavior for nested functions in:
> https://github.com/D-Programming-Language/dmd/pull/4344
>
> If the nested function is a template, attribute inference is preferred and
> pure will be added only when the function is accessing enclosing pure
> function context.
>
> auto foo() pure {
> int x;
> auto bar()() { // template function bar can be impure by default
> // bar will be forced to get weak purity, only when it's trying
> // to access enclosing pure function context.
> //x = 1;
>
> // otherwise, bar can call impure functions and then
> // it will be inferred to impure.
> impureFuncCall().
> }
> }
>
> Once nested template function is inferred to impure, it cannot access
> enclosing pure context anymore.
>
> auto foo() pure {
> int x;
> auto bar()() {
> impureFuncCall().
> x = 1; // Error: impure function 'bar' cannot access variable 'x'
> // declared in enclosing pure function 'foo'
> }
> }
I think this is not right. Why should impure nested functions not be able to access the enclosing context corresponding to frames of pure functions?
Comment #17 by k.hara.pg — 2015-07-04T03:59:48Z
(In reply to timon.gehr from comment #16)
> I think this is not right. Why should impure nested functions not be able to
> access the enclosing context corresponding to frames of pure functions?
Because the nested impure function may modify the context of other pure functions.
int g;
int impureFuncCall() { return g; }
auto func(out int delegate() pure pureDg, out void delegate() impureDg) pure
{
int x = 1; // become a closure variable
int foo() pure // weak purity
{
return x;
}
auto bar()()
{
// modify the context of pure funciton 'foo'
// depending on the global state,
if (impureFuncCall())
x = 2; // !!
}
pureDg = &foo;
impureDg = &bar!();
}
void main()
{
int delegate() pure pureDg;
void delegate() impureDg;
func(pureDg, impureDg);
assert(pureDg() == 1);
g = 1; // modify the global state
impureDg(); // modify the context of pureDg.
assert(pureDg() == 1); // fails~
}
Comment #18 by timon.gehr — 2015-07-04T17:35:26Z
(In reply to Kenji Hara from comment #17)
> (In reply to timon.gehr from comment #16)
> > I think this is not right. Why should impure nested functions not be able to
> > access the enclosing context corresponding to frames of pure functions?
>
> Because the nested impure function may modify the context of other pure
> functions.
>
> int g;
> int impureFuncCall() { return g; }
>
> auto func(out int delegate() pure pureDg, out void delegate() impureDg) pure
> {
> int x = 1; // become a closure variable
>
> int foo() pure // weak purity
> {
> return x;
> }
>
> auto bar()()
> {
> // modify the context of pure funciton 'foo'
> // depending on the global state,
> if (impureFuncCall())
> x = 2; // !!
> }
>
> pureDg = &foo;
> impureDg = &bar!();
> }
>
> void main()
> {
> int delegate() pure pureDg;
> void delegate() impureDg;
>
> func(pureDg, impureDg);
>
> assert(pureDg() == 1);
>
> g = 1; // modify the global state
> impureDg(); // modify the context of pureDg.
>
> assert(pureDg() == 1); // fails~
> }
I understand. My question was why you consider this to be a _problem_.
pureDg is weakly pure. I can change the context of a weakly pure delegate depending on global state in other ways if I want to.
Comment #19 by k.hara.pg — 2015-07-05T06:22:22Z
(In reply to timon.gehr from comment #18)
> I understand. My question was why you consider this to be a _problem_.
> pureDg is weakly pure. I can change the context of a weakly pure delegate
> depending on global state in other ways if I want to.
I thought it would violate the purity concept in D.
Comment #20 by schveiguy — 2015-07-06T12:51:04Z
(In reply to Kenji Hara from comment #19)
> (In reply to timon.gehr from comment #18)
> > I understand. My question was why you consider this to be a _problem_.
> > pureDg is weakly pure. I can change the context of a weakly pure delegate
> > depending on global state in other ways if I want to.
>
> I thought it would violate the purity concept in D.
I agree with Timon. It's not that much different from a class with two member functions, one pure and one not pure.
Comment #21 by k.hara.pg — 2015-07-07T14:46:44Z
> It's not that much different from a class with two
> member functions, one pure and one not pure.
Oh, it sounds very reasonable. Could you please open a new issue to fix the current unnecessarily tight behavior?
Comment #22 by schveiguy — 2015-07-07T15:56:40Z
(In reply to Kenji Hara from comment #21)
> Oh, it sounds very reasonable. Could you please open a new issue to fix the
> current unnecessarily tight behavior?
https://issues.dlang.org/show_bug.cgi?id=14781