Bug 7603 – Default initializer for ref/out must be an lvalue
Status
RESOLVED
Resolution
FIXED
Severity
normal
Priority
P2
Component
dmd
Product
D
Version
D2
Platform
All
OS
All
Creation time
2012-02-27T18:44:00Z
Last change time
2012-12-11T01:38:35Z
Keywords
accepts-invalid
Assigned to
nobody
Creator
andrej.mitrovich
Comments
Comment #0 by andrej.mitrovich — 2012-02-27T18:44:34Z
void test1(ref bool val = true) {
}
void test2(out bool val = true) {
}
void main()
{
bool x;
test1(x); // compiles ok
test2(x); // compiles ok
test1(); // Error: constant true is not an lvalue
test2(); // Error: constant true is not an lvalue
}
The compiler should probably disallow that kind of function signature regardless of any calls. The initializer doesn't really do anything.
Comment #1 by bearophile_hugs — 2012-02-27T19:56:57Z
Ow.
Even this fails:
void test1(ref int val = 10) {}
void test2(out int val = 20) {}
void main() {
int x;
test1(x);
assert(x == 10);
test2(x);
assert(x == 20);
}
Comment #2 by andrej.mitrovich — 2012-10-04T10:34:29Z
(In reply to comment #0)
> void test1(ref bool val = true) {
> }
>
> void test2(out bool val = true) {
> }
Actually I'm only half-right. Phobos uses this in std.random:
void randomShuffle(Range, RandomGen = Random)(Range r, ref RandomGen gen = rndGen);
So 'val' is either a reference to a passed-in argument, or a reference to the default argument which is an lvalue.
Default argument for 'ref' parameter should only be allowed if the argument is an lvalue ('true' is an rvalue'). The compiler already checks this but *only* if the function is called without arguments:
void test(ref bool val = true) { }
void main()
{
bool b;
test(b); // ok, no errors
test(); // errors only on this invocation
}
The compiler should error as soon as it sees the invalid declaration. I'll make a pull shortly.
Btw, the same applies to 'out'. An rvalue isn't valid, but an lvalue as default argument is perfectly valid:
bool w = true;
void test(out bool val = w) { }
void main()
{
test();
assert(w == false);
}
Comment #3 by bearophile_hugs — 2012-10-04T12:27:15Z
See also Issue 5850
Comment #4 by maxim — 2012-10-04T21:32:15Z
(In reply to comment #1)
> Ow.
>
> Even this fails:
>
> void test1(ref int val = 10) {}
> void test2(out int val = 20) {}
> void main() {
> int x;
> test1(x);
> assert(x == 10);
> test2(x);
> assert(x == 20);
> }
Why wouldn't this fail? Default arguments are used if no argument is given. Sine you provide arguments and functions don't modify them, arguments are not changed. The only modification happens due to parameter storage class out.
void test1(ref int val = 10) {}
void test2(out int val = 20) {}
void main() {
int x = 5;
test1(x);
assert(x == 5);
test2(x);
assert(x == 0);
}
Passes both assertions as it should.
Comment #5 by andrej.mitrovich — 2012-10-05T09:30:41Z
(In reply to comment #4)
> Why wouldn't this fail? Default arguments are used if no argument is given.
> Sine you provide arguments and functions don't modify them, arguments are not
> changed. The only modification happens due to parameter storage class out.
>
> void test1(ref int val = 10) {}
> void test2(out int val = 20) {}
> void main() {
> int x = 5;
> test1(x);
> assert(x == 5);
> test2(x);
> assert(x == 0);
> }
>
> Passes both assertions as it should.
Ignoring what bear's confusion here, the function declaration is invalid. Call test1() alone:
void test1(ref int val = 10) { }
void main()
{
test1(); // Error: constant 10 is not an lvalue
}
It makes no sense adding default arguments if they'll never compile, so this needs to be a CT error when the function is parsed and not when the function is called.
Comment #6 by maxim — 2012-10-05T09:41:09Z
(In reply to comment #5)
> (In reply to comment #4)
> > Why wouldn't this fail? Default arguments are used if no argument is given.
> > Sine you provide arguments and functions don't modify them, arguments are not
> > changed. The only modification happens due to parameter storage class out.
> >
> > void test1(ref int val = 10) {}
> > void test2(out int val = 20) {}
> > void main() {
> > int x = 5;
> > test1(x);
> > assert(x == 5);
> > test2(x);
> > assert(x == 0);
> > }
> >
> > Passes both assertions as it should.
>
> Ignoring what bear's confusion here, the function declaration is invalid. Call
> test1() alone:
>
> void test1(ref int val = 10) { }
> void main()
> {
> test1(); // Error: constant 10 is not an lvalue
> }
>
> It makes no sense adding default arguments if they'll never compile, so this
> needs to be a CT error when the function is parsed and not when the function is
> called.
Agree here, but I was talking about that it is logically that those asserts should fail, rather than about accepting non-lvalue default arguments.
Comment #7 by github-bugzilla — 2012-10-06T11:40:42Z
Comment #8 by andrej.mitrovich — 2012-10-07T14:04:47Z
I've just realized I shot myself in the foot with this change (well, partially).
Direct snippet of some code I've used (the main thing to look for is "ref Set!SymID oldIDs = Set!SymID.init")
SymID[] getClassTreeImpl(Table)(Class par, Table table, ref Set!SymID oldIDs = Set!SymID.init, bool skipRoot = false)
{
typeof(return) result;
if (!skipRoot && par.ID !in oldIDs)
{
oldIDs.add(par.ID);
result ~= par.ID;
}
foreach (ID, cls; table)
{
if (cls.baseIDs.canFind(par.ID))
result ~= getClassTreeImpl(cls, table, oldIDs);
}
return result;
}
Basically, getClassTreeImpl can be passed an existing set of keys or it will create a new one if not provided and will update it during recursive calls.
I can fix my code, but I worry if my changeset is going to brake anyone elses code. I wonder if this trick is used that often.. I guess we'll have to wait and see.
Comment #9 by andrej.mitrovich — 2012-12-11T01:38:35Z
*** Issue 5850 has been marked as a duplicate of this issue. ***