-
-
Notifications
You must be signed in to change notification settings - Fork 608
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Issue 6178 - Struct inside the AA are not init correctly #2539
Conversation
|
This compiler change requires small phobos fix: dlang/phobos#1554 |
I didn't even know this was already calling opAssign. Personally I would get rid of this implicit construction feature altogether, it can cause user bugs too easily: struct S
{
// expensive ctor call
this(int) { }
}
int[int] a1;
S[int] a2;
a1[1] = 1; // ok
a2[1] = 1; // oops, meant to use 'a1', but the compiler didn't catch the errorThe above should really catch the bug and force you to write Also, allowing an implicit ctor call becomes a special case for structs, whereas it won't work for classes: class C
{
this(int) { }
}
C[int] aa;
aa[1] = 1; // denied |
The
Good point out. I didn't think about class with opAssing case. With current dmd, following code will cause range violation in runtime, because the non-identity assignment will always evaluated as "read index access + call opAssign on the read object". class C { void opAssign(int) {} }
C[int] aa;
aa[1] = 1; // range violationIf the struct of the AA value could not perform implicit constructor call, to follow the behavior of the class case would be better. I'll update this PR to introduce the behavior. I designed the implicit constructor call on AA value setting for the value semantics structs, e.g. BigInt[string] aa;
aa["val"] = 1; // construct BigInt(1) and store it in AA
aa["val"] = 2; // call opAssign on existing BigInt value in AAIn above case, explicit BigInt construct |
|
@9rnsr : I don't understand what the "alias this" has to do with anything. The way I see it, when you write: ENTRY[KEY] aa;
aa[key] = value;One of two things can happen:
Given the above, the two requirements become:
In both cases, I don't see how the Your code would mean that: struct T
{
int i;
alias i this;
}
void main()
{
T a = T(5); //Construction OK.
a = 5; //Asignement OK.
T[int] aa;
aa[0] = 5; //Nope, sorry (huh? But why?)
}This restriction doesn't make sense to me. |
|
In regards to the whole "implicit construction" vs "assignment" issue, I think a point could be that : EG: struct S
{
int i;
void opAssign(int){}
}
void main()
{
S[int] aa;
aa[0] = S(5); //Fine, insert a new S at location 0.
aa[0] = 4; //Fine, assign 4 to the entry at 0.
aa[1] = 1; //Nope, throw an error.
}This might be breaking change, but it is the only behavior that I think makes sense long term. D does not support implicit construction, so I don't see why AA's should make any exception in that regard. That might be breaking change though, but it's the only behavior I can see that supports implicit insertion on non-existing, while not tricking users with implicit conversion. |
Right.
Correct. And "assignment through alias this" cannot satisfy the condition #1. struct T { string s; int i; alias i this; }
T t = 1; // t cannot be initialized through alias this, because s would be left in uninitialized status |
|
@monarchdodra D already supports implicit construction when the declared variable type is explicitly give. struct S { this(int) {} }
S s = 1; // this(int) is invoked implicitlyI had thought about this problem in long term. My conclusion is, the AA value setting ( |
Right, but what I'm saying is that this has nothing to do with struct T { string s; int i; void opAssign(int); }
T t = 1; |
|
Updated PR for the case that the AA value struct is not constructible from foreign value. struct S { int n; void opAssign(int) {} }
S[int] aa;
aa[1] = 2; // In above, |
|
@monarchdodra See the latest update. |
struct S { this(int) {} }
S s = 1; // this(int) is invoked implicitlyI forgot about that, and I dislike that feature. It does however allow you to do cute things, for example: struct S { this(int, int) { } }
struct F { int x, y; }
void main()
{
F f = { 1, 2 };
S s = f.tupleof;
}I don't know if anyone ever used it like that, I mean it's not that hard to use |
|
@9rnsr : I pulled your phobos fix, because I thought it was cleaner that way anyways. I still don't understand what "alias this" has to do with AA. An entry needs to be constructible from "rhs", and/or be assignable. I really don't see how/why an alias this would interfere, and I think it would be wrong for the compiler to even mention it. If AA was a library type (which, arguably, it should be, and if not, it not, it needs to at least be able to behave that way), then it might look something like this: struct AA(Key, T)
{
auto opIndexAssign(Key key, U value)
{
if (keyIsInThis())
getRefValueAtKey(key) = value; //[HERE]
else
{
//(Up to debate on exact behavior we want)
static if (is(Unqual!T == Unqual!U)
emplaceAtKey(key, value);
else
throwKeyNotFoundError();
}
}
}The only place I see where alias this might interfere is at |
|
@monarchdodra OK, I changed my mind, and organized essential rewriting rules.
I updated commits based on the modified rule. |
I like it, but there's still one thing I'm unsure of: Your code seems to to be creating new keys as soon as an Entry can be constructed from a rhs (in which case an implicit construction happens). I think that is very lenient, and the condition should just be EG: struct S {int i; alias i this;}
void main()
{
S[int] aa;
aa[1] = 1; //Should this throw?
}Your use cases seem to imply that this would work (via construction)? I don't think it should create a new Key. An alternative behavior would be in case of assignment, to insert |
No.
It's wrong. If the AA value type |
Right. I'm not a fan of this solution anyways, so that works for me.
That's why my struct has a Ether way, I still find that: struct S
{
this(int){}
int i;
alias i this;
}
void main()
{
S[int] aa;
aa[1] = 1; //This doesn't throw?
}I still find it weird here that the AA takes the liberty of constructing an item and inserting it into a new key. |
You use struct literal syntax
I believe the behavior is useful, eg for BigInt or other value semantics structs. |
|
Alright. I can't say I'm a fan of "implicit ctor", but I get your point of view. In any case, this fixes and formalizes behavior, so I'm definitely OK with this pull. Your unittests make sense to me, and behave in ways that make sense. So, LGTM. |
|
Added test case for issue 10970 with small glue layer bug fix. |
|
@WalterBright and @andralex , could you review this? This PR determines AA value setting behavior, therefore your reviewing would be necessary. |
If the AA value setting invokes opAsign method, it would need additional runtime const to distinguish construction and assignment.
…opAssign call should always invoke read access.
If the key does not yet exist in AA, the assignment always throws RangeError.
Fixes `CondExp::toElem` to avoid "Internal error: backend\cgcs.c 351"
|
Add test case for issue 10595. |
Issue 6178 - Struct inside the AA are not init correctly
|
@9rnsr, may I ask you to also add docs? Missing docs issue: Issue 11104. |
|
@denis-sh Thanks. I'll fix the issue. |
|
@9rnsr, I also have a [silly?] question for a long time. Is it supposed there is a single implicit constructable rule which is used in all these cases: T t = x;
f(x); // void f(T);
S s = S(x); // struct S { T t; }except the first case is rewritten to |
|
One reason why opIndexAssign has this dual behavior is to avoid double lookup. |
http://d.puremagic.com/issues/show_bug.cgi?id=6178
If the AA value setting invokes opAsign method, it would need additional runtime const to distinguish construction and assignment.
Following is the implemented behavior.
If the setting is identity assignment (the AA value type is equal to toe rhs type):
If the setting is not identity assignment (the AA value type is not equal to toe rhs type):
If the key doesn"t exist in aa yet, it should work as initializing, with implicit constructor call.
If the key already exists in aa, so it should work as assignment (== opAssign call).
The case #2 is necessary to allow using
@disable this()structs as AA values.