www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Help needed: immutable struct is a type modifier, and that's wrong and

reply FeepingCreature <feepingcreature gmail.com> writes:
See this bug: https://issues.dlang.org/show_bug.cgi?id=20670

immutable struct S { }

static if(is(S == immutable T, T)) {
     static assert(is(S == T));
}

So what happens here is that the spec states that immutable 
struct S is "the same as if every member had been marked 
immutable." But that's not at all what the compiler actually 
does: apparently, it just stores S with an immutable modifier.

As the code shows, immutable modifiers can be stripped away. This 
is of course bad, because it means that there's a type "S", but 
there's also a type "mutable S" that can only be created by 
template specialization or Unqual, and it can't be assigned to S 
despite nominally being the exact same type.

Help?

(For now, I'll probably homebrew an Unqual that uses mixin to 
figure out when a type has a "fake immutable modifier". But I 
won't like it.)
Mar 13 2020
next sibling parent FeepingCreature <feepingcreature gmail.com> writes:
https://gist.github.com/FeepingCreature/61d1ead8b744cfa2152849e5b6680bb2

Here's a workaround for the bug. It's a version of `Unqual` that 
uses a completely different mechanism to get at the 
qualifier-less type: it looks up a symbol by that name in the 
surrounding aggregate, or tries to jump to the "parent of the 
first member".

Even though it "works" for our usecases, this clearly does not 
spark joy.
Mar 13 2020
prev sibling next sibling parent reply Jacob Carlborg <doob me.com> writes:
On Friday, 13 March 2020 at 09:33:18 UTC, FeepingCreature wrote:
 See this bug: https://issues.dlang.org/show_bug.cgi?id=20670

 immutable struct S { }

 static if(is(S == immutable T, T)) {
     static assert(is(S == T));
 }

 So what happens here is that the spec states that immutable 
 struct S is "the same as if every member had been marked 
 immutable." But that's not at all what the compiler actually 
 does: apparently, it just stores S with an immutable modifier.

 As the code shows, immutable modifiers can be stripped away. 
 This is of course bad, because it means that there's a type 
 "S", but there's also a type "mutable S" that can only be 
 created by template specialization or Unqual, and it can't be 
 assigned to S despite nominally being the exact same type.

 Help?

 (For now, I'll probably homebrew an Unqual that uses mixin to 
 figure out when a type has a "fake immutable modifier". But I 
 won't like it.)
import std; immutable struct S { } void main() { Unqual!S s; Unqual!S s2; pragma(msg, typeof(s)); // will print S s = s2; } The above code will compile, but if you add a member to S, it fails: Error: cannot modify struct instance `s` of type `S` because it contains `const` or `immutable` members Even though it looks like it's possible to strip of the immutable qualifier it will actually not work in practice. If you don't have any members, it doesn't really matter. If you do have members, it will not compile. -- /Jacob Carlborg
Mar 13 2020
parent reply FeepingCreature <feepingcreature gmail.com> writes:
On Friday, 13 March 2020 at 13:25:52 UTC, Jacob Carlborg wrote:
 import std;

 immutable struct S { }

 void main()
 {
     Unqual!S s;
     Unqual!S s2;

     pragma(msg, typeof(s)); // will print S
     s = s2;
 }

 The above code will compile, but if you add a member to S, it 
 fails:

 Error: cannot modify struct instance `s` of type `S` because it 
 contains `const` or `immutable` members

 Even though it looks like it's possible to strip of the 
 immutable qualifier it will actually not work in practice. If 
 you don't have any members, it doesn't really matter. If you do 
 have members, it will not compile.

 --
 /Jacob Carlborg
Okay, so you're saying there's an immutable modifier on S, but it doesn't do anything because all the fields are immutable too? So the actual problem would be https://issues.dlang.org/show_bug.cgi?id=20671 , where the immutable-stripped type is just different enough to prevent array conversion. In that case, isn't the solution just to ditch the extraneous implicit immutable() entirely? I mean, either it should always be on S or it should never be on S.
Mar 13 2020
parent reply Jacob Carlborg <doob me.com> writes:
On Friday, 13 March 2020 at 14:17:45 UTC, FeepingCreature wrote:

 Okay, so you're saying there's an immutable modifier on S, but 
 it doesn't do anything because all the fields are immutable too?
Well, it looks like the spec is kind of correct: "A struct declaration can have a storage class of const, immutable or shared. It has an equivalent effect as declaring each member of the struct as const, immutable or shared" What happens if the struct doesn't have any members? If there are no members that can be immutable then the struct can't be immutable? I don't know. -- /Jacob Carlborg
Mar 13 2020
parent FeepingCreature <feepingcreature gmail.com> writes:
On Friday, 13 March 2020 at 14:49:49 UTC, Jacob Carlborg wrote:
 On Friday, 13 March 2020 at 14:17:45 UTC, FeepingCreature wrote:

 Okay, so you're saying there's an immutable modifier on S, but 
 it doesn't do anything because all the fields are immutable 
 too?
Well, it looks like the spec is kind of correct: "A struct declaration can have a storage class of const, immutable or shared. It has an equivalent effect as declaring each member of the struct as const, immutable or shared" What happens if the struct doesn't have any members? If there are no members that can be immutable then the struct can't be immutable? I don't know. -- /Jacob Carlborg
Late answer: the problem is that the storage class *doesn't* just declare each member as immutable. If you declare ``` immutable struct S { int i; } ``` then this is *not* the same as ``` struct S { immutable int i; } ``` but rather something that behaves similar to ``` private struct S_ { immutable int i; } alias S = immutable(S_); ``` because the compiler can't differentiate between `immutable struct S` and `immutable(S)`. Which results in the problem that `Unqual!S` yields `S_`, which is a type that isn't supposed to exist, or at least not in a end-user visible way.
Apr 15 2020
prev sibling parent reply RazvanN <razvan.nitu1305 gmail.com> writes:
On Friday, 13 March 2020 at 09:33:18 UTC, FeepingCreature wrote:
 See this bug: https://issues.dlang.org/show_bug.cgi?id=20670

 immutable struct S { }

 static if(is(S == immutable T, T)) {
     static assert(is(S == T));
 }

 So what happens here is that the spec states that immutable 
 struct S is "the same as if every member had been marked 
 immutable." But that's not at all what the compiler actually 
 does: apparently, it just stores S with an immutable modifier.

 As the code shows, immutable modifiers can be stripped away. 
 This is of course bad, because it means that there's a type 
 "S", but there's also a type "mutable S" that can only be 
 created by template specialization or Unqual, and it can't be 
 assigned to S despite nominally being the exact same type.

 Help?

 (For now, I'll probably homebrew an Unqual that uses mixin to 
 figure out when a type has a "fake immutable modifier". But I 
 won't like it.)
There is a difference between defining a struct as immutable (e.g. immutable struct A {}) and declaring an immutable instance of a struct (e.g. immutable S a;). In the first case the immutable acts as a storage class specifier whereas in the second one it is a type constructor. Unqual is used to ditch only type constructors not storage class specifiers. In the case of storage class specifiers there is no way to get rid of them because, as you mentioned, there is no associated type. So from this perspective, I think that [1] should be fixed. Additionally, I think that static if(is(S == immutable T, T)) should also not pass. The correct form should be static if(is(S == immutable)) This way you express the fact that S is an immutably defined type (with a storage specifier), whereas in the first case you test if S is an immutably declared type (with a type constructor). [1]https://issues.dlang.org/show_bug.cgi?id=20670
Apr 15 2020
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 16.04.20 05:55, RazvanN wrote:
 
 
 Additionally, I think that
 
 static if(is(S == immutable T, T))
 
 should also not pass.
 
 The correct form should be
 
 static if(is(S == immutable))
 
 This way you express the fact that S is an immutably defined type (with 
 a storage specifier), whereas in the first case you test if S is an 
 immutably declared type (with a type constructor).
So the proposal is to turn `is` into an even more convoluted mess? :) Why should it even be possible to test for `immutable` on the declaration? That seems like an implementation detail as the only thing it achieves is that all members are marked that way.
Apr 15 2020
parent reply RazvanN <razvan.nitu1305 gmail.com> writes:
On Thursday, 16 April 2020 at 04:28:35 UTC, Timon Gehr wrote:
 On 16.04.20 05:55, RazvanN wrote:
 
 
 Additionally, I think that
 
 static if(is(S == immutable T, T))
 
 should also not pass.
 
 The correct form should be
 
 static if(is(S == immutable))
 
 This way you express the fact that S is an immutably defined 
 type (with a storage specifier), whereas in the first case you 
 test if S is an immutably declared type (with a type 
 constructor).
So the proposal is to turn `is` into an even more convoluted mess? :) Why should it even be possible to test for `immutable` on the declaration? That seems like an implementation detail as the only thing it achieves is that all members are marked that way.
I agree that this is kind of awkward, but things are confusing otherwise. Do you agree that in this situation `static if (is(S == immutable T, T))` should not pass? Or should it pass and the `static assert(is(S == T))` should also pass? In that case, T should bind to an empty type, which D, as far as I know does not support at the moment.
Apr 15 2020
parent reply RazvanN <razvan.nitu1305 gmail.com> writes:
On Thursday, 16 April 2020 at 06:05:12 UTC, RazvanN wrote:
 On Thursday, 16 April 2020 at 04:28:35 UTC, Timon Gehr wrote:
 On 16.04.20 05:55, RazvanN wrote:
 
 
 Additionally, I think that
 
 static if(is(S == immutable T, T))
 
 should also not pass.
 
 The correct form should be
 
 static if(is(S == immutable))
 
 This way you express the fact that S is an immutably defined 
 type (with a storage specifier), whereas in the first case 
 you test if S is an immutably declared type (with a type 
 constructor).
So the proposal is to turn `is` into an even more convoluted mess? :) Why should it even be possible to test for `immutable` on the declaration? That seems like an implementation detail as the only thing it achieves is that all members are marked that way.
Oh I see your point now. In the above example S and T are the same type because `immutable immutable S` == `immutable S`. Right, but the fact that an immutably defined struct cannot be stripped of it's qualifier brings some complications here. Anyway, the fact that T is not equal to S is definitely a bug in this situation.
Apr 15 2020
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 16.04.20 08:09, RazvanN wrote:
 On Thursday, 16 April 2020 at 06:05:12 UTC, RazvanN wrote:
 On Thursday, 16 April 2020 at 04:28:35 UTC, Timon Gehr wrote:
 On 16.04.20 05:55, RazvanN wrote:
 Additionally, I think that

 static if(is(S == immutable T, T))

 should also not pass.

 The correct form should be

 static if(is(S == immutable))

 This way you express the fact that S is an immutably defined type 
 (with a storage specifier), whereas in the first case you test if S 
 is an immutably declared type (with a type constructor).
So the proposal is to turn `is` into an even more convoluted mess? :) Why should it even be possible to test for `immutable` on the declaration? That seems like an implementation detail as the only thing it achieves is that all members are marked that way.
Oh I see your point now. In the above example S and T are the same type because `immutable immutable S` == `immutable S`. Right, but the fact that an immutably defined struct cannot be stripped of it's qualifier brings some complications here. Anyway, the fact that T is not equal to S is definitely a bug in this situation.
My point was rather that I don't see any reason why the following two declarations should be treated differently: immutable struct S{ int x; } struct S{ immutable int x; } I think those should be equivalent declarations. Also, I think `is(S==immutable(T),T)` and `is(S==immutable)` should behave the same except that the first one also declares `T`.
Apr 16 2020
parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 4/16/20 11:21 AM, Timon Gehr wrote:
 On 16.04.20 08:09, RazvanN wrote:
 On Thursday, 16 April 2020 at 06:05:12 UTC, RazvanN wrote:
 On Thursday, 16 April 2020 at 04:28:35 UTC, Timon Gehr wrote:
 On 16.04.20 05:55, RazvanN wrote:
 Additionally, I think that

 static if(is(S == immutable T, T))

 should also not pass.

 The correct form should be

 static if(is(S == immutable))

 This way you express the fact that S is an immutably defined type 
 (with a storage specifier), whereas in the first case you test if S 
 is an immutably declared type (with a type constructor).
So the proposal is to turn `is` into an even more convoluted mess? :) Why should it even be possible to test for `immutable` on the declaration? That seems like an implementation detail as the only thing it achieves is that all members are marked that way.
Oh I see your point now. In the above example S and T are the same type because `immutable immutable S` == `immutable S`. Right, but the fact that an immutably defined struct cannot be stripped of it's qualifier brings some complications here. Anyway, the fact that T is not equal to S is definitely a bug in this situation.
My point was rather that I don't see any reason why the following two declarations should be treated differently: immutable struct S{ int x; } struct S{ immutable int x; }
So you are suggesting a change to current behavior? immutable struct S {int x; } struct T {immutable int x; } pragma(msg, is(S == immutable)); // true pragma(msg, is(T == immutable)); // false
 
 I think those should be equivalent declarations.
It makes sense. But in practice, I'm sure people make an assumption based on whether something is immutable and don't check whether an struct has all immutable members, even though semantically the two are equivalent. We would need something in std.traits that does what is needed. -Steve
Apr 16 2020