Further reduced case:
import std.typecons;
alias N = Nullable!int;
void foo(N a) {
N b;
b = a; // `N b = a;` works fine
}
void main() {
N n;
foo(n);
}
Comment #2 by monarchdodra — 2013-01-26T13:21:56Z
Here is a preliminary analysis:
There is no opAssign(Nullable!T t).
Because of this doing a "nullable1 = nullable2" will actually trigger
"nullable1 = nullable2.get", at which point an exception is thrown. In the second example:
b = a; //This fails regardless of DMD
This can be fixed with:
//----
void opAssign()(Nullable!T other)
{
_value = other._value;
_isNull = other._isNull;
}
//----
That said I wonder if this is correct behavior: if "a" is null, then should "b = a" work? I think so, but I can see reasons why it shouldn't.
----------------
The real question is: Why did the first example ever work at all? I've tracked it down to this usecase, with *at least*, a 3-level nest:
//----
import std.typecons;
import std.stdio;
struct S
{
void opAssign(int){writeln("opAssign");}
int get(){writeln("get"); return 1;}
alias get this;
}
struct SS
{
S s;
}
struct SSS
{
SS ss;
this(SS i)
{
ss = i;
}
}
void main() {
SS ss;
SSS(ss);
}
//----
DMD 2.060:
//----
//----
DMD 2.061:
//----
get
opAssign
//----
AFAIK, 2.061 is the correct behavior. Something must have been fixed in 2.061, so that explains the *change* in behavior. So there should be nothing to fix there... But I'll let own of the compiler guys confirm.
In any case, I'll fix open a pull to improve Nullable to accept assignment from another nullable.
Comment #3 by lomereiter — 2013-01-26T13:33:21Z
Thanks for your analysis!
I wonder, though, how is this correct behaviour? My understanding is, since SS doesn't have opAssign, bitwise copy should happen in SSS constructor, and that's it. Why does S.opAssign get called in this case?
> AFAIK, 2.061 is the correct behavior. Something must have been fixed in 2.061,
> so that explains the *change* in behavior. So there should be nothing to fix
> there... But I'll let own of the compiler guys confirm.
>
> In any case, I'll fix open a pull to improve Nullable to accept assignment from
> another nullable.
Comment #4 by monarchdodra — 2013-01-26T13:42:10Z
(In reply to comment #3)
> Thanks for your analysis!
>
> I wonder, though, how is this correct behaviour? My understanding is, since SS
> doesn't have opAssign, bitwise copy should happen in SSS constructor, and
> that's it. Why does S.opAssign get called in this case?
>
> > AFAIK, 2.061 is the correct behavior. Something must have been fixed in 2.061,
> > so that explains the *change* in behavior. So there should be nothing to fix
> > there... But I'll let own of the compiler guys confirm.
> >
> > In any case, I'll fix open a pull to improve Nullable to accept assignment from
> > another nullable.
Technically, when S doesn't have an opAssign, then a *postblit* is called (which will basically be a bitwise copy when there is no postblit).
However, this only happens when there is NO opAssign at all. If there is an opAssign, then the compiler must call one of the existing opAssigns. Nullable does have an opAssign though (the one taking a T), so that is what the compiler is calling.
Comment #5 by lomereiter — 2013-01-26T14:32:15Z
OK, apparently default opAssign behaviour has changed to call opAssign of member fields since merging this pull request: https://github.com/D-Programming-Language/dmd/pull/166
Documentation at http://dlang.org/struct.html#AssignOverload is outdated, then.
>
> Technically, when S doesn't have an opAssign, then a *postblit* is called
> (which will basically be a bitwise copy when there is no postblit).
>
> However, this only happens when there is NO opAssign at all. If there is an
> opAssign, then the compiler must call one of the existing opAssigns. Nullable
> does have an opAssign though (the one taking a T), so that is what the compiler
> is calling.
Comment #6 by k.hara.pg — 2013-01-30T20:29:48Z
https://github.com/D-Programming-Language/dmd/pull/1585https://github.com/D-Programming-Language/phobos/pull/1105
---
(In reply to comment #2)
> ----------------
> The real question is: Why did the first example ever work at all? I've tracked
> it down to this usecase, with *at least*, a 3-level nest:
>
> //----
> import std.typecons;
> import std.stdio;
>
> struct S
> {
> void opAssign(int){writeln("opAssign");}
> int get(){writeln("get"); return 1;}
> alias get this;
> }
>
> struct SS
> {
> S s;
> }
>
> struct SSS
> {
> SS ss;
> this(SS i)
> {
> ss = i;
> }
> }
>
> void main() {
> SS ss;
> SSS(ss);
> }
> //----
>
> DMD 2.060:
> //----
> //----
>
> DMD 2.061:
> //----
> get
> opAssign
> //----
>
> AFAIK, 2.061 is the correct behavior. Something must have been fixed in 2.061,
> so that explains the *change* in behavior. So there should be nothing to fix
> there... But I'll let own of the compiler guys confirm.
>
> In any case, I'll fix open a pull to improve Nullable to accept assignment from
> another nullable.
Yes, it is intended behavior in 2.061. The pull request had merged:
https://github.com/D-Programming-Language/dmd/pull/166
Was for implementing it.
But, sorry, it was not complete. In 2.061, user-defined opAssign is now _overly_ considered and used for the identity assignment. It is a compiler regression.
In this case, Nullable!T should generate bitwise copy for identity assignment.
> alias N = Nullable!int;
> void foo(N a) {
> N b;
> b = a; // must be bitwise, must not invoke Nullable!int.opAssign(int)
> }
If the assignment is not identity, user-defined opAssing should be called.
void foo(N a) {
N b;
b = 1; // b.opAssign(1) is called
b = ""; // b.opAssign("") is called, and compiler will cause an error
}
Comment #7 by monarchdodra — 2013-01-30T23:19:47Z
(In reply to comment #6)
> https://github.com/D-Programming-Language/dmd/pull/1585
> https://github.com/D-Programming-Language/phobos/pull/1105
>
> ---
>
> (In reply to comment #2)
> > ----------------
> > The real question is: Why did the first example ever work at all? I've tracked
> > it down to this usecase, with *at least*, a 3-level nest:
> >
> > //----
> > import std.typecons;
> > import std.stdio;
> >
> > struct S
> > {
> > void opAssign(int){writeln("opAssign");}
> > int get(){writeln("get"); return 1;}
> > alias get this;
> > }
> >
> > struct SS
> > {
> > S s;
> > }
> >
> > struct SSS
> > {
> > SS ss;
> > this(SS i)
> > {
> > ss = i;
> > }
> > }
> >
> > void main() {
> > SS ss;
> > SSS(ss);
> > }
> > //----
> >
> > DMD 2.060:
> > //----
> > //----
> >
> > DMD 2.061:
> > //----
> > get
> > opAssign
> > //----
> >
> > AFAIK, 2.061 is the correct behavior. Something must have been fixed in 2.061,
> > so that explains the *change* in behavior. So there should be nothing to fix
> > there... But I'll let own of the compiler guys confirm.
> >
> > In any case, I'll fix open a pull to improve Nullable to accept assignment from
> > another nullable.
>
> Yes, it is intended behavior in 2.061. The pull request had merged:
> https://github.com/D-Programming-Language/dmd/pull/166
> Was for implementing it.
>
> But, sorry, it was not complete. In 2.061, user-defined opAssign is now
> _overly_ considered and used for the identity assignment. It is a compiler
> regression.
>
> In this case, Nullable!T should generate bitwise copy for identity assignment.
>
> > alias N = Nullable!int;
> > void foo(N a) {
> > N b;
> > b = a; // must be bitwise, must not invoke Nullable!int.opAssign(int)
> > }
>
> If the assignment is not identity, user-defined opAssing should be called.
>
> void foo(N a) {
> N b;
> b = 1; // b.opAssign(1) is called
> b = ""; // b.opAssign("") is called, and compiler will cause an error
> }
My apologies for not linking back:
https://github.com/D-Programming-Language/phobos/pull/1103
This is now a useless pull... correct?