January 31, 2019
On 1/31/19 2:26 AM, Andrei Alexandrescu wrote:
> The trouble is major.
> 
> Replace "if" with "while":
> 
> while (ref_fun(10)) { ... }
> ==>
> {
>    int __tmp = 10;
>    while (ref_fun(__tmp)) { ... }
> }
> 
> That means ref_fun is called with the same lvalue multiple times. In all likelihood this is not what you want!

Yes, the trouble specifically is loops. Because loops execute their internal expressions over and over again.

Unfortunately, this means lowering isn't possible. That is, lowering to something expressible in the normal language isn't possible.

However, we all know that loops are essentially "lowered" in the AST to simple ifs and gotos. We just need to operate at that level.

So for instance the above looks something like this in AST:

loop_continue:
if(ref_fun(10))
{
  ...
}
else
   goto loop_end;
goto loop_continue;
loop_end:

(I know I'm omitting a lot of extra stuff like scope cleanup, that is implied here, as I don't know the exact details).

What needs to happen is the temporary (with extra scope)is inserted between the loop start and the if statement:

loop_continue:
{
   int __tmp = 10;
   if(ref_fun(__tmp))
   {
      ...
   }
   else
      goto loop_end;
}
goto loop_continue;
loop_end:

> A possible retort is: "Of course, while would not be lowered that way, but a slightly different way!" etc. The point is, ALL OF THAT must be in the DIP, not assumed obvious or clarified in informal discusson outside the DIP.
> 
> Again: please be thorough, state your assumptions, cover all cases.

Agree, this needs to handle all possible cases. Really I think loops are the only problems, foreach, for, and while/do..while

A possible way forward is inventing a new syntax to allow declarations in this space, and then lowering can happen. Something similar to if(auto x = ...)

-Steve
January 31, 2019
On 1/31/19 3:25 AM, Walter Bright wrote:
> But the DIP says const ref is not required. Therefore, copying an lvalue to a temporary cannot be allowed, therefore implicit conversion of lvalues cannot happen.

The biggest reason I see to not worry about const is that we already don't for member functions. And the world hasn't ended (yet).

> 
> Then we're faced with the question of if implicit conversion of lvalues is not allowed, should implicit conversion of rvalues be allowed? I'm not so sure it should be. For one thing, a lot of people are confused about lvalues vs rvalues, and would find the difference in behavior puzzling. For another, it can complicate overloading rules. I'd say allowing the conversions needs a strong rationale.

We could certainly start out with no conversions allowed (except you MUST allow conversions of compile-time data -- e.g. literals and CTFE produced values -- that is very key), and then relax the rules later if that's important.

-Steve
January 31, 2019
On Thursday, 31 January 2019 at 02:10:05 UTC, Manu wrote:
> I still can't see a truck-sized hole.

I don't know if it's truck-sized, but here's another corner case:

    int doubleMyValue(ref int x) {
        x *= 2;
        return x;
    }

    Point pt;
    pt.x = 5;
    pt.y = foobar();

    doubleMyValue(pt.x);
    assert(pt.x == 10);

Question: in the above code, will the assertion pass?

Answer: it depends on Point's implementation. If x is a member variable, then yes. If it's a getter, then doubleMyValue will take a rvalue and x won't be mutated and the assertion will fail.

I think this is a non-trivial conceptual problem.
January 31, 2019
On 1/31/19 11:04 AM, Olivier FAURE wrote:
> On Thursday, 31 January 2019 at 02:10:05 UTC, Manu wrote:
>> I still can't see a truck-sized hole.
> 
> I don't know if it's truck-sized, but here's another corner case:
> 
>      int doubleMyValue(ref int x) {
>          x *= 2;
>          return x;
>      }
> 
>      Point pt;
>      pt.x = 5;
>      pt.y = foobar();
> 
>      doubleMyValue(pt.x);
>      assert(pt.x == 10);
> 
> Question: in the above code, will the assertion pass?
> 
> Answer: it depends on Point's implementation. If x is a member variable, then yes. If it's a getter, then doubleMyValue will take a rvalue and x won't be mutated and the assertion will fail.
> 
> I think this is a non-trivial conceptual problem.

Yeah, that's already a thing that ref in D doesn't protect against:

struct Point
{
   private int _x, _y;
   ref int x() { return _x; }
   ref int y() { return _y; }
}

struct Rect
{
   private Point _origin, _lengths;
   Point origin() { return _origin; }
   Point lengths() { return _lengths; }
   void origin(Point p) { _origin = p; }
   void lengths(Point p) { _lengths = p; }
}

Rect r;
r.origin = Point(1, 2);
r.lengths = Point(5, 5);
doubleMyValue(r.lengths.x);
assert(r.lengths.x == 10); // fail

-Steve
January 31, 2019
On 1/31/19 11:04 AM, Olivier FAURE wrote:
> On Thursday, 31 January 2019 at 02:10:05 UTC, Manu wrote:
>> I still can't see a truck-sized hole.
> 
> I don't know if it's truck-sized, but here's another corner case:
> 
>      int doubleMyValue(ref int x) {
>          x *= 2;
>          return x;
>      }
> 
>      Point pt;
>      pt.x = 5;
>      pt.y = foobar();
> 
>      doubleMyValue(pt.x);
>      assert(pt.x == 10);
> 
> Question: in the above code, will the assertion pass?
> 
> Answer: it depends on Point's implementation. If x is a member variable, then yes. If it's a getter, then doubleMyValue will take a rvalue and x won't be mutated and the assertion will fail.
> 
> I think this is a non-trivial conceptual problem.

BTW, the DIP discusses how to annotate these rare situations:

int doubleMyValue(ref int x) { ... }
@disable int doubleMyValue(int x);

-Steve
January 31, 2019
On 1/31/19 11:38 AM, Steven Schveighoffer wrote:
> On 1/31/19 11:04 AM, Olivier FAURE wrote:
>> On Thursday, 31 January 2019 at 02:10:05 UTC, Manu wrote:
>>> I still can't see a truck-sized hole.
>>
>> I don't know if it's truck-sized, but here's another corner case:
>>
>>      int doubleMyValue(ref int x) {
>>          x *= 2;
>>          return x;
>>      }
>>
>>      Point pt;
>>      pt.x = 5;
>>      pt.y = foobar();
>>
>>      doubleMyValue(pt.x);
>>      assert(pt.x == 10);
>>
>> Question: in the above code, will the assertion pass?
>>
>> Answer: it depends on Point's implementation. If x is a member variable, then yes. If it's a getter, then doubleMyValue will take a rvalue and x won't be mutated and the assertion will fail.
>>
>> I think this is a non-trivial conceptual problem.
> 
> Yeah, that's already a thing that ref in D doesn't protect against:
> 
> struct Point
> {
>     private int _x, _y;
>     ref int x() { return _x; }
>     ref int y() { return _y; }
> }
> 
> struct Rect
> {
>     private Point _origin, _lengths;
>     Point origin() { return _origin; }
>     Point lengths() { return _lengths; }
>     void origin(Point p) { _origin = p; }
>     void lengths(Point p) { _lengths = p; }
> }
> 
> Rect r;
> r.origin = Point(1, 2);
> r.lengths = Point(5, 5);
> doubleMyValue(r.lengths.x);
> assert(r.lengths.x == 10); // fail
> 
> -Steve

Affirmative. This discussion should be part of the revised DIP along with an assessment of its gravity.

Goes the same with scope-level variables replaced with homonym functions that return rvalues.


Andrei
January 31, 2019
On Thursday, 31 January 2019 at 16:38:42 UTC, Steven Schveighoffer wrote:
> Yeah, that's already a thing that ref in D doesn't protect against:

It took me a while to understand what the compiler was doing.

This really feels like something that shouldn't compile.
January 31, 2019
On Thursday, 31 January 2019 at 21:42:04 UTC, Olivier FAURE wrote:
> [snip]
>
> It took me a while to understand what the compiler was doing.
>
> This really feels like something that shouldn't compile.

It doesn't compile with dip1000 without first giving the getter functions a return attribute for this.
January 31, 2019
On Thursday, 31 January 2019 at 18:31:22 UTC, Steven Schveighoffer wrote:
> BTW, the DIP discusses how to annotate these rare situations:
>
> int doubleMyValue(ref int x) { ... }
> @disable int doubleMyValue(int x);
>
> -Steve

I don't think that's a solution. The problem is in the getter method, not in doubleMyValue. If nothing else, since the DIP is designed to work on existing functions, it could happen on doubleMyValue functions which would be both designed by and used by people completely unaware of DIP-1016.
January 31, 2019
On 1/31/19 4:42 PM, Olivier FAURE wrote:
> On Thursday, 31 January 2019 at 16:38:42 UTC, Steven Schveighoffer wrote:
>> Yeah, that's already a thing that ref in D doesn't protect against:
> 
> It took me a while to understand what the compiler was doing.
> 
> This really feels like something that shouldn't compile.

The proposal could actually disallow rvalues that have lvalue syntax, such as "symbol", "symbol[expr]", "symbol.symbol", "symbol.symbol[expr]", etc. Ugh. Gets hairy quickly.