Bug 17351 – Static const array can't be evaluated at compile time when passed as ref argument

Status
RESOLVED
Resolution
FIXED
Severity
blocker
Priority
P1
Component
dmd
Product
D
Version
D2
Platform
All
OS
All
Creation time
2017-04-26T09:43:51Z
Last change time
2020-08-13T02:17:27Z
Keywords
pull
Assigned to
No Owner
Creator
Lucia Cojocaru

Comments

Comment #0 by lucia.mcojocaru — 2017-04-26T09:43:51Z
Static const arrays can't be evaluated at compile time when they are passed as __ref__ arguments to a function. bool fun(S)(ref S[3] a) { return true; } // (1) void main() { static const int[3] sa2 = 1; static assert(fun(sa2)); } staticcomp.d(8): Error: static variable sa2 cannot be read at compile time staticcomp.d(8): called from here: fun(sa2) staticcomp.d(8): while evaluating: static assert(fun(sa2)) Non-ref param implementation compiles without errors. bool fun(S)(S[3] a) { return true; } // (2) Defining both (1) and (2) overloads in the same program fails to pick the non-ref overload and gives the same compilation error.
Comment #1 by andrei — 2017-04-26T14:14:28Z
This issue is not particular to arrays. The following code also fails to compile: bool fun(S)(ref S a) { return true; } // (1) void main() { static const int sa2 = 1; static assert(fun(sa2)); }
Comment #2 by uplink.coder — 2017-04-28T19:17:01Z
This code demands an Lvalue to be used at ctfe. remove the ref from fun and it will work. We cannot allow the ref-fun to compile.
Comment #3 by andrei — 2017-04-29T00:09:46Z
Please let's keep this opened until we get to a resolution, thanks. There are several issues here. First, consider this variation: bool fun(const int a) { return true; } bool gun(const int[3] a) { return true; } void main() { static const int x1 = 1; static assert(fun(x1)); static const int[3] x; static assert(gun(x)); } The int goes through, the int[3] does not although the pass by value protocol is the same for both. Apparently float is also handled the same as int. At a minimum we should include in the specification what types enjoy such special treatment. The second experiment: bool fun(const int a) { return true; } bool fun(ref const int a) { return true; } void main() { static const int x1 = 1; static assert(fun(x1)); } Here, the code attempts to avoid the problem by overloading on ref. However, the call is resolved to the ref version even though it does not go through. This should definitely be fixed, otherwise we're in the position of making a workaround impossible. The third experiment: bool fun(T)(auto ref const T a) { return true; } void main() { static const int x1 = 1; static assert(fun(x1)); } Here, the code gives complete leeway to the compiler as to what version should be called. Again, the compiler chooses the ref version to then refuse to let it go through.
Comment #4 by uplink.coder — 2017-04-29T05:13:41Z
As far as I can see there is no special treatment. CTFE does only work on literals results of ctfe evaluations are always literals.
Comment #5 by ag0aep6g — 2017-04-29T05:45:54Z
(In reply to Andrei Alexandrescu from comment #3) > bool fun(const int a) { return true; } > bool gun(const int[3] a) { return true; } > > void main() > { > static const int x1 = 1; > static assert(fun(x1)); > static const int[3] x; > static assert(gun(x)); > } > > The int goes through, the int[3] does not although the pass by value > protocol is the same for both. You have to initialize the int[3], as you did with the int. I suppose the compiler assumes that you're going to do run-time initialization via `static this` when you're not initializing in the declaration. In that case, the value can't be used at compile time, obviously. [...] > bool fun(const int a) { return true; } > bool fun(ref const int a) { return true; } > > void main() > { > static const int x1 = 1; > static assert(fun(x1)); > } > > Here, the code attempts to avoid the problem by overloading on ref. However, > the call is resolved to the ref version even though it does not go through. > This should definitely be fixed, otherwise we're in the position of making a > workaround impossible. You can work around by using an enum instead of a static const. That makes x1 an rvalue, and the ref overload isn't involved anymore. It might be worthwhile to require enums for compile-time constants; i.e., ban non-enum consts/immutables. The const/immutable qualifiers simply don't imply compile-time constancy. enum does. That would be a breaking change, of course. And it would break benign code such as `static const a = 1; static const b = a + 1;`. But a clean cut may be better than confusing special cases.
Comment #6 by andrei — 2017-04-29T13:46:26Z
(In reply to ag0aep6g from comment #5) > You have to initialize the int[3], as you did with the int. I suppose the > compiler assumes that you're going to do run-time initialization via `static > this` when you're not initializing in the declaration. In that case, the > value can't be used at compile time, obviously. Cool. Indeed this works even with structs: bool fun(const int a) { return true; } bool gun(const int[3] a) { return true; } bool hun(const S a) { return true; } struct S { int a; } void main() { static const int x1 = 1; static assert(fun(x1)); static const int[3] x = [1, 2, 3]; static assert(gun(x)); static const S x2 = S(1); static assert(hun(x2)); } So we're in good shape. > [...] > > bool fun(const int a) { return true; } > > bool fun(ref const int a) { return true; } > > > > void main() > > { > > static const int x1 = 1; > > static assert(fun(x1)); > > } > > > > Here, the code attempts to avoid the problem by overloading on ref. However, > > the call is resolved to the ref version even though it does not go through. > > This should definitely be fixed, otherwise we're in the position of making a > > workaround impossible. > > You can work around by using an enum instead of a static const. Not an option. The context is the following. We're looking at lowering all array comparisons like this: e1 == e2 -----> __equals(e1, e2) e1 != e2 -----> !__equals(e1, e2) Then implement __equals as a template in object.d. There are a number of challenges related to that, which we solved together with Walter. The current approach defines these overloads: bool __equals(L, R)(L[] lhs, R[] rhs); bool __equals(L, R, n1)(auto ref L[n1] lhs, R[] rhs); bool __equals(L, R, n2)(auto ref L[] lhs, auto ref R[n2] rhs); bool __equals(L, R, n1, n2)(auto ref L[n1] lhs, auto ref R[n2] rhs); This approach has a number of benefits compared to the status quo, among which much better speed (and as a perk fewer dynamic allocations for array literals when passed in comparisons such as arr == [1, 2, 3]). The auto ref is necessary so we don't copy arrays into the comparison functions, yet we still work with rvalue arrays. Now, Lucia (@somzzz) has gotten to the point where it all passes druntime and phobos unittests, but breaks in exactly one point in the compiler. It can be reduced to this: static const int[3] x = [1, 2, 3]; static assert(x == x); Here, the compiler chooses the "ref" version even though it is not compilable. The version with rvalues should be taken and used.
Comment #7 by andrei — 2017-04-29T13:48:04Z
I meant: bool __equals(L, R)(L[] lhs, R[] rhs); bool __equals(L, R, size_t n1)(auto ref L[n1] lhs, R[] rhs); bool __equals(L, R, size_t n2)(auto ref L[] lhs, auto ref R[n2] rhs); bool __equals(L, R, size_t n1, size_t n2)(auto ref L[n1] lhs, auto ref R[n2] rhs); (There are also constraints on L and R that I omitted.)
Comment #8 by ag0aep6g — 2017-04-29T15:01:38Z
(In reply to Andrei Alexandrescu from comment #6) > Now, Lucia (@somzzz) has gotten to the point where it all passes druntime > and phobos unittests, but breaks in exactly one point in the compiler. It > can be reduced to this: > > static const int[3] x = [1, 2, 3]; > static assert(x == x); Got it. You need the special overload behavior to keep that assert working with the new implementation. I'm not particularly opposed to tweaking overload resolution to that end. However, as mentioned, another option could be to disallow that static assert on the grounds that x being static const doesn't mean it's a compile-time constant. I realize that this would be a relatively disruptive change, but it would simplify the language instead of complicating it further. It would also make this little gem impossible: ---- void main() { const bool x = __ctfe; static const bool y = x; static assert(x == y); /* passes */ assert(x == y); /* fails */ } ----
Comment #9 by uplink.coder — 2017-05-19T09:34:43Z
(In reply to Andrei Alexandrescu from comment #6) > (In reply to ag0aep6g from comment #5) > > You have to initialize the int[3], as you did with the int. I suppose the > > compiler assumes that you're going to do run-time initialization via `static > > this` when you're not initializing in the declaration. In that case, the > > value can't be used at compile time, obviously. > > Cool. Indeed this works even with structs: > > bool fun(const int a) { return true; } > bool gun(const int[3] a) { return true; } > bool hun(const S a) { return true; } > > struct S { int a; } > > void main() > { > static const int x1 = 1; > static assert(fun(x1)); > static const int[3] x = [1, 2, 3]; > static assert(gun(x)); > static const S x2 = S(1); > static assert(hun(x2)); > } > > So we're in good shape. > > > [...] > > > bool fun(const int a) { return true; } > > > bool fun(ref const int a) { return true; } > > > > > > void main() > > > { > > > static const int x1 = 1; > > > static assert(fun(x1)); > > > } > > > > > > Here, the code attempts to avoid the problem by overloading on ref. However, > > > the call is resolved to the ref version even though it does not go through. > > > This should definitely be fixed, otherwise we're in the position of making a > > > workaround impossible. > > > > You can work around by using an enum instead of a static const. > > Not an option. The context is the following. We're looking at lowering all > array comparisons like this: > > e1 == e2 -----> __equals(e1, e2) > e1 != e2 -----> !__equals(e1, e2) > > Then implement __equals as a template in object.d. There are a number of > challenges related to that, which we solved together with Walter. The > current approach defines these overloads: > > bool __equals(L, R)(L[] lhs, R[] rhs); > bool __equals(L, R, n1)(auto ref L[n1] lhs, R[] rhs); > bool __equals(L, R, n2)(auto ref L[] lhs, auto ref R[n2] rhs); > bool __equals(L, R, n1, n2)(auto ref L[n1] lhs, auto ref R[n2] rhs); > > This approach has a number of benefits compared to the status quo, among > which much better speed (and as a perk fewer dynamic allocations for array > literals when passed in comparisons such as arr == [1, 2, 3]). > > The auto ref is necessary so we don't copy arrays into the comparison > functions, yet we still work with rvalue arrays. > > Now, Lucia (@somzzz) has gotten to the point where it all passes druntime > and phobos unittests, but breaks in exactly one point in the compiler. It > can be reduced to this: > > static const int[3] x = [1, 2, 3]; > static assert(x == x); > > Here, the compiler chooses the "ref" version even though it is not > compilable. The version with rvalues should be taken and used. I can give you a access to ref at ctfe assuming you never modify the literal given
Comment #10 by kinke — 2018-09-04T19:15:53Z
I seem to have come across a variant of this as well. This works since v2.067: int f1(ref const int p) { return p; } int f2(ref const int[2] p) { return p[0] + p[1]; } void main() { static immutable int[2] P = [ 0, 1 ]; static assert(f1(P[0]) == 0); static assert(f2(P) == 1); } But only if there's the first static assert (!). Comment that out, and it'll complain about 'static variable `P` cannot be read at compile time'. See https://run.dlang.io/is/uyWPmm.
Comment #11 by dlang-bot — 2020-08-10T10:34:09Z
@Geod24 created dlang/dmd pull request #11545 "Fix 17351 - Manifest constants can't sometimes be passed by `ref` in CTFE" fixing this issue: - Fix 17351 - Manifest constants can't sometimes be passed by `ref` in CTFE As mentioned in the comment, the fix is crude but works well. https://github.com/dlang/dmd/pull/11545
Comment #12 by dlang-bot — 2020-08-13T02:17:27Z
dlang/dmd pull request #11545 "Fix 17351 - Manifest constants can't sometimes be passed by `ref` in CTFE" was merged into stable: - ecf2ce2f54fc86e2f075815235ddd0a3425eb8ef by Geod24: Fix 17351 - Manifest constants can't sometimes be passed by `ref` in CTFE As mentioned in the comment, the fix is crude but works well. https://github.com/dlang/dmd/pull/11545