August 10, 2019
On Saturday, 10 August 2019 at 08:09:08 UTC, Dukc wrote:
> The problem I'm targeting is that currently the compiler will not complain, if the user, when expecting to mutate range elements, iterates by `ref` over foreach.

Meant: iterates by `ref` `foreach` over a range of rvalues
August 10, 2019
On Sat, Aug 10, 2019 at 1:11 AM Dukc via Digitalmars-d <digitalmars-d@puremagic.com> wrote:
>
> On Friday, 9 August 2019 at 21:55:45 UTC, Manu wrote:
> >
> > Sorry if that sounded blunt.
>
> No, not at all. I was just surprised that you questioned my core resoning, yet wanted the dip to move forward.
>
> >
> > Let me try and be more clear; by comparison, we can consider [Snip]
> >
> > I suspect from reading that you are trying to solve the second
> > case
> > with this DIP, but it's already solved by Andrei's work (which
> > he
> > presented at dconf).
> > What this DIP should actually solve though, in the 3rd case. We
> > need that.
>
> Hmm, you want to retain the current behaviour of `ref`? This confuses me, because `auto ref` I'm proposing has exactly the same semantics as `ref` does now, yet you also want it to be included.
>
> My guess is that you're thinking the benefit of `auto ref` to be allowing to handle rvalue ranges without becoming a pointer internally, thus being more efficient in at least some cases. The rest of my answer assumes that was your point.
>
> I disagree. I am not trying to target efficiency issues a all. If my problem was that, I'd simply propose foreach `ref` to use rvalue copies directly, not via internal pointers. Unlike in C++, refs and the values they point to are indistinguishable from D user perspective, so that is just an implementation detail that would probably get through even without a DIP. Unless it's already done.
>
> The problem I'm targeting is that currently the compiler will not complain, if the user, when expecting to mutate range elements, iterates by `ref` over foreach. But since I know there's need for the notion I'm deprecating, I also suggest `auto ref` for the behaviour.
>
> I know this is a kind of a jerk change, as it forces to change code. But I thought that the migration path is so simple (just changing `ref` to `auto ref` where you get deprecation messages) that avoiding it (see alternative 1 of the DIP) is not worth having a language inconsistency.

I'm thought about it some more, and I think you're right about the deprecation.
I think I can agree that the probability of confusion + mistake where
a ref to temporary exists is higher weighted than the uniformity with
function calling.
I tend to hate special-case rules (in this case, loop counters
behaving differently than function arguments), but I think I'm
persuaded here.

I support your text unamended ;) .. we've needed `auto ref` in foreach for a long time.

I'm curious about this line: "It should be allowed in static foreach, but with no effect, as elements of compile-time aggregates can never be lvalues."

Is that true? I don't think there's any reason they can't be lvalues. But what I'm trying to get my head around is if the loop counter for `static foreach` is a variable at all, or if it's an alias?

I think there's inherent complexity here, because there's a lot of cases... and some should work with auto ref, but not all.

Okay, here's some experiment cases:

  int x = 1;
  static foreach (i; AliasSeq!(10, x, 20))
      pragma(msg, __traits(isRef, i));

> false false false

In this case, `i` is an alias. Ref-ness doesn't mean anything here.

This can't work:

  int x = 1;
  static foreach (int i; AliasSeq!(10, x, 20))
      pragma(msg, __traits(isRef, i));

> Error: can't initialise `i` because `x` is a runtime value

Because it tries to initialise an int from elements of tuple, and `x` is a runtime variable.

This could work if ref was treated the same way as parameter arguments:

  int x = 1;
  static foreach (ref int i; AliasSeq!(10, x, 20))
      pragma(msg, __traits(isRef, i));

> true true true

In that case, rvalues (which can be determined at compile time) would
populate temporaries, the same way as function parameters.
But according to the rules in your dip, we don't allow that, and I
conceded above.

This should work though:

  int x = 1;
  static foreach (ref int i; AliasSeq!(x, x, x))
      pragma(msg, __traits(isRef, i));

> true true true

All elements are lvalues, so we accept `ref`.

Finally, this should work as expected:

  int x = 1;
  static foreach (auto ref int i; AliasSeq!(10, x, 20))
      pragma(msg, __traits(isRef, i));

> false true false

Functionally identical to the first case, except the interesting
distinction that `i` contains information which can be used in
introspection logic, which is almost always present in static foreach
bodies.
I believe this should be allowed AND behave as expected; that is; `i`
is a ref when encountering an lvalue.
If it does not behave in this way, it should not be allowed.


Now, there's a different set of cases, where you call static foreach over a not-a-tuple:

  static foreach (i; [10, 20, 30])
      pragma(msg, __traits(isRef, i));

> false false false

In this case, `i` is not an alias like the previous experiment, it is
inferred to be `int`.
Should be the same as `static foreach (int i; ...)`, right?

  static foreach (ref i; [10, 20, 30])
      pragma(msg, __traits(isRef, i));

> true true true

As above, we could allow this by creating temporaries the same as function arguments... but we've decided not to allow ref iterators from rvalues.

  static foreach (auto ref i; [10, 20, 30])
      pragma(msg, __traits(isRef, i));

> false false false ??

I guess the whole array is an rvalue, so then the loop counter would take copies of the elements?

It gets interesting when you do this:

  int[3] x = [10, 20, 30];
  static foreach (ref i; x)
      pragma(msg, __traits(isRef, i));

> true true true

And:

  int[3] x = [10, 20, 30];
  static foreach (auto ref i; x)
      pragma(msg, __traits(isRef, i));

> true true true

These should work as expected.


TL;DR, your sentence "It should be allowed in static foreach, but with no effect" should be removed, and if you want to detail the expected semantics with `static foreach`, that might be a good idea.
August 11, 2019
On Saturday, 10 August 2019 at 21:42:00 UTC, Manu wrote:
>   static foreach (ref i; [10, 20, 30])
>       pragma(msg, __traits(isRef, i));
>
>> true true true
>
> As above, we could allow this by creating temporaries the same as function arguments... but we've decided not to allow ref iterators from rvalues.

It doesn't need temporaries, [2][0] is an lvalue with existing dmd:

void f(ref int i);
void main(){
    f([2][0]); //compiles
}

> TL;DR, your sentence "It should be allowed in static foreach, but with no effect" should be removed, and if you want to detail the expected semantics with `static foreach`, that might be a good idea.

+1
August 11, 2019
On Sun, Aug 11, 2019 at 7:55 AM Nick Treleaven via Digitalmars-d <digitalmars-d@puremagic.com> wrote:
>
> On Saturday, 10 August 2019 at 21:42:00 UTC, Manu wrote:
> >   static foreach (ref i; [10, 20, 30])
> >       pragma(msg, __traits(isRef, i));
> >
> >> true true true
> >
> > As above, we could allow this by creating temporaries the same as function arguments... but we've decided not to allow ref iterators from rvalues.
>
> It doesn't need temporaries, [2][0] is an lvalue with existing dmd:
>
> void f(ref int i);
> void main(){
>      f([2][0]); //compiles
> }

Oh yeah... I forgot about that. Caused me so much trouble over the years. So bad!

> > TL;DR, your sentence "It should be allowed in static foreach, but with no effect" should be removed, and if you want to detail the expected semantics with `static foreach`, that might be a good idea.
>
> +1
August 13, 2019
On Saturday, 10 August 2019 at 21:42:00 UTC, Manu wrote:

> I tend to hate special-case rules (in this case, loop counters
> behaving differently than function arguments), but I think I'm
> persuaded here.

Not sure what you mean here... example?

> Okay, here's some experiment cases:
>
>   int x = 1;
>   static foreach (i; AliasSeq!(10, x, 20))
>       pragma(msg, __traits(isRef, i));
>
>> false false false
>
> In this case, `i` is an alias. Ref-ness doesn't mean anything here.

I thought that would compile only without the `static` keyword. Gotta investigate. If it compiles, I'm going to say it should behave exactly as written without the `static` keyword.

Same answer for the rest of examples with tuples.

> Now, there's a different set of cases, where you call static foreach over a not-a-tuple:
>
>   static foreach (i; [10, 20, 30])
>       pragma(msg, __traits(isRef, i));
>
>> false false false
>
> In this case, `i` is not an alias like the previous experiment, it is
> inferred to be `int`.
> Should be the same as `static foreach (int i; ...)`, right?
>
>   static foreach (ref i; [10, 20, 30])
>       pragma(msg, __traits(isRef, i));
>
>> true true true

Yes, these are the cases I meant with `static foreach`. I think `[10, 20, 30]` is a rvalue, and if the latter of these two examples compiles, it should not.

>
> As above, we could allow this by creating temporaries the same as function arguments... but we've decided not to allow ref iterators from rvalues.
>
>   static foreach (auto ref i; [10, 20, 30])
>       pragma(msg, __traits(isRef, i));
>
>> false false false ??

Yes, exactly what's supposed to happen.

>
> I guess the whole array is an rvalue, so then the loop counter would take copies of the elements?
>
> It gets interesting when you do this:
>
>   int[3] x = [10, 20, 30];
>   static foreach (ref i; x)
>       pragma(msg, __traits(isRef, i));
>
>> true true true

I don't think that compiles. x is not an enum value. But I might be wrong, I'll have to recheck this also.

>
> And:
>
>   int[3] x = [10, 20, 30];
>   static foreach (auto ref i; x)
>       pragma(msg, __traits(isRef, i));
>
>> true true true

Same as above. If x was enum, then it should not compile with `ref` and should return false false false with `auto ref`.

> TL;DR, your sentence "It should be allowed in static foreach, but with no effect" should be removed, and if you want to detail the expected semantics with `static foreach`, that might be a good idea.

I will check those examples that I thought won't compile do. If they do, I'll be more explicit.
August 13, 2019
On Sunday, 11 August 2019 at 14:52:08 UTC, Nick Treleaven wrote:
> void f(ref int i);
> void main(){
>     f([2][0]); //compiles
> }

Huh? What happens here?


August 13, 2019
On Tue, Aug 13, 2019 at 6:50 AM Dukc via Digitalmars-d <digitalmars-d@puremagic.com> wrote:
>
> On Saturday, 10 August 2019 at 21:42:00 UTC, Manu wrote:
>
> > I tend to hate special-case rules (in this case, loop counters behaving differently than function arguments), but I think I'm persuaded here.
>
> Not sure what you mean here... example?

struct S
{
  int front();
  ...
}

void takesRef(ref int);

{
  S s;

  takesRef(s.front()); // <- fine, temporary is created and passed by ref

  foreach(ref i; s) // <- error: no temporary is created; we have
decided to emit an error where an rval is given to a loop counter
  { ... }
}

This is the special case I'm talking about.

> > Okay, here's some experiment cases:
> >
> >   int x = 1;
> >   static foreach (i; AliasSeq!(10, x, 20))
> >       pragma(msg, __traits(isRef, i));
> >
> >> false false false
> >
> > In this case, `i` is an alias. Ref-ness doesn't mean anything here.
>
> I thought that would compile only without the `static` keyword. Gotta investigate. If it compiles, I'm going to say it should behave exactly as written without the `static` keyword.

Iterating a tuple is a compile-time expansion, which is distinct from
a runtime expansion.
The result is `false false false` either way, but static and
non-static are very different operations.

`foreach` will iterate an array of int's, `static foreach` will expand the tuple and `i` will be an alias of each element.

> Same answer for the rest of examples with tuples.

No, static and non-static foreach are completely different semantically.
My examples may show the same outputs either way, but it's easy to
make cases which show the distinction between static and non-static.

> > Now, there's a different set of cases, where you call static foreach over a not-a-tuple:
> >
> >   static foreach (i; [10, 20, 30])
> >       pragma(msg, __traits(isRef, i));
> >
> >> false false false
> >
> > In this case, `i` is not an alias like the previous experiment,
> > it is
> > inferred to be `int`.
> > Should be the same as `static foreach (int i; ...)`, right?
> >
> >   static foreach (ref i; [10, 20, 30])
> >       pragma(msg, __traits(isRef, i));
> >
> >> true true true
>
> Yes, these are the cases I meant with `static foreach`. I think `[10, 20, 30]` is a rvalue, and if the latter of these two examples compiles, it should not.

Actually, because of D's 'weird shit' law, the array is not an rvalue.
(I think...?)

> > As above, we could allow this by creating temporaries the same as function arguments... but we've decided not to allow ref iterators from rvalues.
> >
> >   static foreach (auto ref i; [10, 20, 30])
> >       pragma(msg, __traits(isRef, i));
> >
> >> false false false ??
>
> Yes, exactly what's supposed to happen.

Right... what's your point?
Incidentally, I'm not sure this will hold; because array literals are
not rvalues, so I think it might be `true true true` here...

> > I guess the whole array is an rvalue, so then the loop counter would take copies of the elements?
> >
> > It gets interesting when you do this:
> >
> >   int[3] x = [10, 20, 30];
> >   static foreach (ref i; x)
> >       pragma(msg, __traits(isRef, i));
> >
> >> true true true
>
> I don't think that compiles. x is not an enum value. But I might be wrong, I'll have to recheck this also.

Why not? It's an alias... it should compile just fine.

> >
> > And:
> >
> >   int[3] x = [10, 20, 30];
> >   static foreach (auto ref i; x)
> >       pragma(msg, __traits(isRef, i));
> >
> >> true true true
>
> Same as above. If x was enum, then it should not compile with `ref` and should return false false false with `auto ref`.

`x` is not an enum, it's a local.
The cases I propose to consider are exactly what I wrote, not some other thing.

For instance:

int fun(ref int x)
{
  static foreach (i; AliasSeq!(10, x, 30))  // <- outputs `false true false`
    pragma(msg, __traits(isRef, i));

  foreach (i; AliasSeq!(10, x, 30)) // <- outputs `false false false` (!!!)
    pragma(msg, __traits(isRef, i));
}

And there's another interesting case of 'weird shit' here too. That
second one should output `false`, not `false false false`; the
pragma(msg) should only evaluate once, because the non-static foreach
shouldn't be expanding... but strangely, runtime foreach when given a
tuple behaves almost like a static foreach, but the loop counter is
not an alias for the tuple element, it is rather an `auto i =
element[n]` copy.
I actually think this form of runtime foreach should be deprecated, if
you want to do this with runtime foreach, wrap the tuple in `[ ]`.

Here there be dargons!

> > TL;DR, your sentence "It should be allowed in static foreach, but with no effect" should be removed, and if you want to detail the expected semantics with `static foreach`, that might be a good idea.
>
> I will check those examples that I thought won't compile do. If they do, I'll be more explicit.

I'm not strictly talking about what does actually compile, I'm talking about what makes semantic sense, and would be uniform semantically with no bizarre cases. Some cases might not work, but should.
August 14, 2019
On Tuesday, 13 August 2019 at 23:34:32 UTC, Manu wrote:
>
> struct S
> {
>   int front();
>   ...
> }
>
> void takesRef(ref int);
>
> {
>   S s;
>
>   takesRef(s.front()); // <- fine, temporary is created and passed by ref
>
>   foreach(ref i; s) // <- error: no temporary is created; we have
> decided to emit an error where an rval is given to a loop counter
>   { ... }
> }
>
> This is the special case I'm talking about.

Nope, does not pass: `Error: function onlineapp.takesRef(ref int integer) is not callable using argument types (int)`


>> > Okay, here's some experiment cases:
>> >
>> >   int x = 1;
>> >   static foreach (i; AliasSeq!(10, x, 20))
>> >       pragma(msg, __traits(isRef, i));
>> >
>> >> false false false
>
> Iterating a tuple is a compile-time expansion, which is distinct from
> a runtime expansion.
> The result is `false false false` either way, but static and
> non-static are very different operations.
>
> `foreach` will iterate an array of int's, `static foreach` will expand the tuple and `i` will be an alias of each element.
>

No. `foreach` over an alias sequence will be unrolled at compile time, and behave like a `static foreach` does with CTFEd arrays. That was the way we did what `static foreach` does now, remember?

But I have to confess I'm not sure what `static foreach` does over an alias sequence. I ran a few tests, and some compiled despite my expectations, but didn't get a clear picture what it does. I think I need to read `static foreach` DIP again.

>> Same answer for the rest of examples with tuples.
>
> No, static and non-static foreach are completely different semantically.
> My examples may show the same outputs either way, but it's easy to
> make cases which show the distinction between static and non-static.

See above.

>> Yes, these are the cases I meant with `static foreach`. I think `[10, 20, 30]` is a rvalue, and if the latter of these two examples compiles, it should not.
>
> Actually, because of D's 'weird shit' law, the array is not an rvalue.
> (I think...?)

It is. I tested that: `Error: constant value 10 cannot be ref`

>
>> > As above, we could allow this by creating temporaries the same as function arguments... but we've decided not to allow ref iterators from rvalues.
>> >
>> >   static foreach (auto ref i; [10, 20, 30])
>> >       pragma(msg, __traits(isRef, i));
>> >
>> >> false false false ??
>>
>> Yes, exactly what's supposed to happen.
>
> Right... what's your point?
> Incidentally, I'm not sure this will hold; because array literals are
> not rvalues, so I think it might be `true true true` here...

Because the loop won't work with `ref`, it falls back to iteration-by-copy. But you're right that if the literals were not rvalues, that would result in `true true true`.

>> > I guess the whole array is an rvalue, so then the loop counter would take copies of the elements?
>> >
>> > It gets interesting when you do this:
>> >
>> >   int[3] x = [10, 20, 30];
>> >   static foreach (ref i; x)
>> >       pragma(msg, __traits(isRef, i));
>> >
>> >> true true true
>
> Why not? It's an alias... it should compile just fine.

x is a runtime static array. Tested that also: `Error: variable x cannot be read at compile time`

>
>> >
>> > And:
>> >
>> >   int[3] x = [10, 20, 30];
>> >   static foreach (auto ref i; x)
>> >       pragma(msg, __traits(isRef, i));
>
> `x` is not an enum, it's a local.
> The cases I propose to consider are exactly what I wrote, not some other thing.

You are right it is not an enum, like I also just said. But that's exactly the reason why it cannot compile, `ref` (or `auto ref`) or no. `static foreach` requires the values of the loop aggregate to be available at compile time, not just it's length.

I might well support an argument that this shouldn't be the case, but as it is, my DIP should not need to discuss that case.

>
> For instance:
>
> int fun(ref int x)
> {
>   static foreach (i; AliasSeq!(10, x, 30))  // <- outputs `false true false`
>     pragma(msg, __traits(isRef, i));
>
>   foreach (i; AliasSeq!(10, x, 30)) // <- outputs `false false false` (!!!)
>     pragma(msg, __traits(isRef, i));
> }
>
> [snip]
> I actually think this form of runtime foreach should be deprecated, if
> you want to do this with runtime foreach, wrap the tuple in `[ ]`.

An interesting idea for another potential DIP.

>
>
> I'm not strictly talking about what does actually compile, I'm talking about what makes semantic sense, and would be uniform semantically with no bizarre cases. Some cases might not work, but should.

Perhaps, in some cases. But again, it's outside the scope of this DIP.

August 14, 2019
On Tuesday, 13 August 2019 at 23:34:32 UTC, Manu wrote:
> On Tue, Aug 13, 2019 at 6:50 AM Dukc via Digitalmars-d <digitalmars-d@puremagic.com> wrote:
>> >   static foreach (ref i; [10, 20, 30])
>> >       pragma(msg, __traits(isRef, i));
>> >
>> >> true true true
>>
>> Yes, these are the cases I meant with `static foreach`. I think `[10, 20, 30]` is a rvalue, and if the latter of these two examples compiles, it should not.
>
> Actually, because of D's 'weird shit' law, the array is not an rvalue.
> (I think...?)

An array literal is an rvalue:

void f(ref int[] a);
void main(){
    f([10, 20, 30]); // error
}

error: cannot pass rvalue argument `[10, 20, 30]` of type `int[]` to parameter `ref int[] a`

>> > As above, we could allow this by creating temporaries the same as function arguments... but we've decided not to allow ref iterators from rvalues.
>> >
>> >   static foreach (auto ref i; [10, 20, 30])
>> >       pragma(msg, __traits(isRef, i));
>> >
>> >> false false false ??
>>
>> Yes, exactly what's supposed to happen.
>
> Right... what's your point?
> Incidentally, I'm not sure this will hold; because array literals are
> not rvalues, so I think it might be `true true true` here...

An array literal is an rvalue, but indexing an array literal is an lvalue. So the above should print 'true' for each pragma.
August 14, 2019
On Wed, Aug 14, 2019 at 8:40 AM Dukc via Digitalmars-d <digitalmars-d@puremagic.com> wrote:
>
> On Tuesday, 13 August 2019 at 23:34:32 UTC, Manu wrote:
> >
> > struct S
> > {
> >   int front();
> >   ...
> > }
> >
> > void takesRef(ref int);
> >
> > {
> >   S s;
> >
> >   takesRef(s.front()); // <- fine, temporary is created and
> > passed by ref
> >
> >   foreach(ref i; s) // <- error: no temporary is created; we
> > have
> > decided to emit an error where an rval is given to a loop
> > counter
> >   { ... }
> > }
> >
> > This is the special case I'm talking about.
>
> Nope, does not pass: `Error: function onlineapp.takesRef(ref int
> integer) is not callable using argument types (int)`

You need -preview=rvalueRefParam
I pointed you to Andrei's lecture that talks about this at the start
of the thread.

> >> > Okay, here's some experiment cases:
> >> >
> >> >   int x = 1;
> >> >   static foreach (i; AliasSeq!(10, x, 20))
> >> >       pragma(msg, __traits(isRef, i));
> >> >
> >> >> false false false
> >
> > Iterating a tuple is a compile-time expansion, which is
> > distinct from
> > a runtime expansion.
> > The result is `false false false` either way, but static and
> > non-static are very different operations.
> >
> > `foreach` will iterate an array of int's, `static foreach` will expand the tuple and `i` will be an alias of each element.
> >
>
> No. `foreach` over an alias sequence will be unrolled at compile time, and behave like a `static foreach` does with CTFEd arrays. That was the way we did what `static foreach` does now, remember?

Yes, I recall. But static foreach exists now, so I feel like it should be deprecated in favour of static foreach.

> But I have to confess I'm not sure what `static foreach` does over an alias sequence. I ran a few tests, and some compiled despite my expectations, but didn't get a clear picture what it does. I think I need to read `static foreach` DIP again.

It does this:

alias i = tuple[0];
... body
alias i = tuple[1];
... body
alias i = tuple[2];
... body

This is why the ref function arg shows ref correctly. It unrolls the loop for each element of the tuple as an alias.

> >> Same answer for the rest of examples with tuples.
> >
> > No, static and non-static foreach are completely different
> > semantically.
> > My examples may show the same outputs either way, but it's easy
> > to
> > make cases which show the distinction between static and
> > non-static.
>
> See above.

?

> >> Yes, these are the cases I meant with `static foreach`. I think `[10, 20, 30]` is a rvalue, and if the latter of these two examples compiles, it should not.
> >
> > Actually, because of D's 'weird shit' law, the array is not an
> > rvalue.
> > (I think...?)
>
> It is. I tested that: `Error: constant value 10 cannot be ref`

Okay, good. There was a time where array literals use to allocate using the GC...

> >> > As above, we could allow this by creating temporaries the same as function arguments... but we've decided not to allow ref iterators from rvalues.
> >> >
> >> >   static foreach (auto ref i; [10, 20, 30])
> >> >       pragma(msg, __traits(isRef, i));
> >> >
> >> >> false false false ??
> >>
> >> Yes, exactly what's supposed to happen.
> >
> > Right... what's your point?
> > Incidentally, I'm not sure this will hold; because array
> > literals are
> > not rvalues, so I think it might be `true true true` here...
>
> Because the loop won't work with `ref`, it falls back to iteration-by-copy. But you're right that if the literals were not rvalues, that would result in `true true true`.

Right, sorry, I wasn't sure the current state of array literals. There was a time when they allocated memory.

> >> > I guess the whole array is an rvalue, so then the loop counter would take copies of the elements?
> >> >
> >> > It gets interesting when you do this:
> >> >
> >> >   int[3] x = [10, 20, 30];
> >> >   static foreach (ref i; x)
> >> >       pragma(msg, __traits(isRef, i));
> >> >
> >> >> true true true
> >
> > Why not? It's an alias... it should compile just fine.
>
> x is a runtime static array. Tested that also: `Error: variable x cannot be read at compile time`

It's not being read, just referenced. This can/should work, it just doesn't.

It should effectively be the same as:

int[3] x;
static foreach (i; AliasSeq!(x[0], x[1], x[2])) {}

Which doesn't work, but this does:

int x, y, z;
static foreach (i; AliasSeq!(x, y, z)) {}

This also works:

int[3] x;
static foreach (i; 0 .. 3) { ... x[i] ... }

Unrolling an array is super useful, we just don't support it... but we could!

> >> > And:
> >> >
> >> >   int[3] x = [10, 20, 30];
> >> >   static foreach (auto ref i; x)
> >> >       pragma(msg, __traits(isRef, i));
> >
> > `x` is not an enum, it's a local.
> > The cases I propose to consider are exactly what I wrote, not
> > some other thing.
>
> You are right it is not an enum, like I also just said. But that's exactly the reason why it cannot compile, `ref` (or `auto ref`) or no. `static foreach` requires the values of the loop aggregate to be available at compile time, not just it's length.

x is right there, or the code couldn't compile. Our meta is perfectly
fine with symbol aliases.
For the non-ref (ie, alias) case, sub the array expansion with `(x, y,
z)` instead of `(x[0], x[1], x[2])`. It's exactly the same thing, it's
just not supported is all.

>From there, whether we support static foreach fabricating a ref that
points to an alias is a different question, but it's totally possible.

It's not any different for the compiler than this:

void fun(ref int);
int x;
static foreach(ref i; AliasSeq!(x, x, x)) {} // <- i is a reference to a local
fun(x); fun(x); fun(x); // <- function receives a reference to local

It makes ref to x in both cases.

> I might well support an argument that this shouldn't be the case, but as it is, my DIP should not need to discuss that case.

It's just that you specifically mention static foreach and that it's
meaningless; which I'm not sure is true.
You should either not mention static foreach (your DIP applies to
runtime foreach), or should say what static foreach should support,
and that may be "static foreach does not interact with ref", but it's
possible that it could, and could be useful in the future.

I think there's just a few gaps still where things that should work
haven't been requested. Loop unrolling primarily.
We can't alias expressions at all.

> > I'm not strictly talking about what does actually compile, I'm talking about what makes semantic sense, and would be uniform semantically with no bizarre cases. Some cases might not work, but should.
>
> Perhaps, in some cases. But again, it's outside the scope of this DIP.

I was just pointing out that I think this text should change: "It should be allowed in static foreach, but with no effect, as elements of compile-time aggregates can never be lvalues."

Elements CAN be lvalues in static foreach, that's what I was trying to show:

int x, y, z;
static foreach (i; AliasSeq!(x, y, z)) {} // <- compiles in today's
language, all elements are lvalues.

I'm saying, if you allow `auto ref` in static foreach, it should do
the thing (capture lvalues by ref).
I think you should change it to say it is not allowed in static
foreach, at least until we work out if/how ref in static foreach
should work.
I think it should work naturally; ref's to symbol aliases is possible,
and I think the uniformity would be sensible, and I agree it's outside
this DIP, which is why you should change that text.