Bug 6169 – [CTFE] pure functions cannot compute constants using functions not marked as pure
Status
RESOLVED
Resolution
FIXED
Severity
normal
Priority
P2
Component
dmd
Product
D
Version
D2
Platform
All
OS
All
Creation time
2011-06-16T17:09:00Z
Last change time
2013-07-13T23:00:55Z
Keywords
CTFE, pull, rejects-valid
Assigned to
yebblies
Creator
timon.gehr
Comments
Comment #0 by timon.gehr — 2011-06-16T17:09:25Z
With DMD 2.053:
string impure(){return ";";}
void main() pure{
enum s = impure(); // fail (cannot call impure function 'impure')
mixin(impure()); // ditto
}
Removing the pure attribute from 'main' or adding it to 'impure' makes the code pass.
This restriction is nonsensical and should be removed.
Comment #1 by bearophile_hugs — 2011-06-16T18:42:49Z
I think you have just made D a bit more complex :-)
In D compile time and run time are fully separated (and computing an enum inside a CTFE function spans a fully separated and fully enclosed sub-computation), so there is no way compile-time constants can break the purity of a pure function. So this looks OK regarding run-time purity.
Currently in D in a function the path of compile-time execution has to be pure, even if the whole function is not pure, so you are right saying this program has to compile:
int x = 10;
int foo(bool b) {
if (b)
x++;
return 0;
}
pure void main() {
enum y = foo(false);
}
If in future D compilers CTFE will be allowed to modify global variables too, then I think your idea is in troubles. Otherwise at first sight it seems OK, but more thinking is required because future D compilers are allowed to perform pure-related optimizations on pure functions even in CTFE. Is nonpurity able to cause troubles to pure functions at compile-time?
In this program spam is pure, and it calls bar, that's not pure, to compute z. But this program doesn't cause troubles even if a smart D compiler applies pure optimization of spam()+spam() replacing it with spam()*2 because z is computed only once, because bar() is called in a sub-computation that's fully sealed:
pure nothrow int foo() {
int x = 1;
nothrow int bar(int y) { // nonpure
x++;
return x + y;
}
pure nothrow int spam() {
enum z = bar(1); // calls a nonpure
return z;
}
return spam() + spam();
}
enum r = foo();
void main() {}
So if I am right, then this proposal is safe :-)
One problem left is that this proposal introduces another special case in D, because the rules of purity have to say a pure function is allowed to call an impure one at compile-time. Is it worth it? I think it's acceptable.
Comment #2 by clugdbug — 2011-06-17T01:02:22Z
Applies to @safe as well.
CTFE enforces safety and purity, using more relaxed rules than @safe and pure do.
Comment #3 by timon.gehr — 2011-06-17T06:57:25Z
Interestingly, it does not apply to nothrow unless interpreting the function fails :). I think this is a diagnostics bug:
int foo(){return 0;}
int bar(){assert(0);}
void main() nothrow{
enum f = foo(); // fine
enum b = bar(); // assert(0) failed / bar is not nothrow
// main is nothrow yet may throw
}
(In reply to comment #1)
> ...
> If in future D compilers CTFE will be allowed to modify global variables too,
> then I think your idea is in troubles.
> [snip.]
I think letting CTFE mutate static storage is a bad idea anyways, but actually that does not matter for this. You are just computing some manifest constant during compile time that is later used to influence the function's behavior. This cannot possibly make the function return different results when passed the same arguments (even during compile time), ergo it is still pure.
> ...
> One problem left is that this proposal introduces another special case in D,
> because the rules of purity have to say a pure function is allowed to call an
> impure one at compile-time. Is it worth it? I think it's acceptable.
> [snip.]
Actually I think this is the same 'special case' as the one that says manifest constants are evaluated during compile time. The behavior we have now is a special case of this 'special case'. I think we remove some special casing (and therefore complexity) by fixing this.
Oh, and this should work too:
string impure(){return ";";}
void main() pure{static s = impure();}
*** Issue 7994 has been marked as a duplicate of this issue. ***
Comment #6 by monarchdodra — 2013-01-31T02:27:40Z
Just wanted to add that I just hit this bug. Any news on the progression of this bug fix?
I'm hitting this on a rather trivial use case, where I'm just trying to generate a compile-time-known error message:
//----
import std.string : format;
struct S
{
int* p;
ref inout(int) get() inout nothrow pure @safe
{
enum message = format("Called %s on null %s.", "get", S.stringof);
assert(p, message);
return *p;
}
}
//----
Comment #7 by yebblies — 2013-01-31T04:03:40Z
This bug is exactly where it has been for the last year - awaiting approval from Walter.
Comment #8 by yebblies — 2013-02-16T17:47:19Z
*** Issue 9517 has been marked as a duplicate of this issue. ***
Comment #9 by github-bugzilla — 2013-05-04T09:48:17Z
Comment #10 by bearophile_hugs — 2013-05-04T11:11:40Z
(In reply to comment #1)
> pure nothrow int foo() {
> int x = 1;
> nothrow int bar(int y) { // nonpure
> x++;
> return x + y;
> }
>
> pure nothrow int spam() {
> enum z = bar(1); // calls a nonpure
> return z;
> }
> return spam() + spam();
> }
>
> enum r = foo();
> void main() {}
A reduction of that code:
int foo() {
int x = 0;
int bar() {
return x;
}
enum y = bar();
return 0;
}
enum r = foo();
void main() {}
It gives:
temp.d(4): Error: variable x cannot be read at compile time
temp.d(6): called from here: bar()
temp.d(9): called from here: foo()
Comment #11 by yebblies — 2013-05-11T03:51:44Z
(In reply to comment #10)
>
> A reduction of that code:
>
>
> int foo() {
> int x = 0;
> int bar() {
> return x;
> }
> enum y = bar();
> return 0;
> }
> enum r = foo();
> void main() {}
>
>
> It gives:
>
> temp.d(4): Error: variable x cannot be read at compile time
> temp.d(6): called from here: bar()
> temp.d(9): called from here: foo()
That appears to be correct. While both 'foo' and 'bar' are running at compile time, they are in different compile time evaluation contexts. The evaluation of 'y' _must_ be independent of the running of 'foo'.
Comment #12 by yebblies — 2013-06-30T22:33:39Z
This was fixed ages ago.
Comment #13 by yebblies — 2013-06-30T22:34:45Z
*** Issue 10506 has been marked as a duplicate of this issue. ***
Comment #14 by monarchdodra — 2013-07-01T00:39:34Z
Not fully fixed for @safe. The conditions to reproduce are a bit complicated actually. It requires attribute inference, mixin and default args (!) I'm not sure which it is that it producing the problem:
--------
string bar(string op = "+") @property
{
return "a" ~ op ~ "b";
}
void foo()()
{
int a, b;
int c = mixin(bar);
}
@safe void main()
{
foo!()();
}
--------
main.d(14): Error: safe function 'D main' cannot call system function 'main.foo!().foo'
--------
Observations:
1) The problem is only with @safe, not pure.
2) Calling "min(bar("+"))" also makes the problem go away.
Built with HEAD from 30-06-2013
Comment #15 by k.hara.pg — 2013-07-01T08:01:17Z
(In reply to comment #14)
> Not fully fixed for @safe. The conditions to reproduce are a bit complicated
> actually. It requires attribute inference, mixin and default args (!) I'm not
> sure which it is that it producing the problem:
https://github.com/D-Programming-Language/dmd/pull/2290
Comment #16 by github-bugzilla — 2013-07-07T21:09:32Z