On Wednesday, 25 February 2015 at 18:08:55 UTC, deadalnix wrote:
On Wednesday, 25 February 2015 at 01:12:15 UTC, Zach the Mystic wrote:
int r; // declaration scopedepth(0)

void fun(int a /*scopedepth(0)*/) {
 int b; // depth(1)
 {
   int c; // depth(2)
   {
     int d; // (3)
   }
   {
     int e; // (3)
   }
 }
 int f; // (1)
}


You have element of differing lifetime at scope depth 0 so far.

Sorry for the delay.

I made a mistake. Parameter `a` will have a *declaration* scope of 1, just like int b above. It's *reference* scope will have depth 0, with the "mystery" bit for the first parameter set.

Principle 5: It's always un@safe to copy a declaration scope from a higher scopedepth to a reference variable stored at lower scopedepth. DIP69 tries to banish this type of thing only in `scope` variables, but I'm not afraid to banish it in all @safe code period:

void gun() @safe {
 T* t; // t's declaration depth: 1
 T u;
 {
   T* uu = &u; // fine, this is normal
   T tt;
   t = &tt; // t's reference depth: 2, error, un@safe
 }
 // now t is corrupted
}


Bingo. However, when you throw goto into the mix, weird thing happens. The general idea is good but need refining.

I addressed this further down, in Principle 10. My proposed solution has the compiler detecting the presence of code which could both 1) be visited again (through a jump label or a loop) and 2) is in a branching condition. In these cases it pushes any statement which copies a reference onto a special stack. When the branching condition finishes, it revisits the stack, "reheating" the scopes in reverse order. If there is a way to defeat this technique, it must be very convoluted, since the scopes do nothing but accumulate possibilities. It may even be mathematically impossible.

Principle 7: In this system, all scopes are *transitive*: any reference type with double indirections inherits the scope of the outermost reference. Think of it this way:


It is more complex than that, and this is where most proposals fail short (including this one and DIP69). If you want to disallow the assignment of a reference to something with a short lifetime, you can't consider scope transitive when used as a lvalue. You can, however, consider it transitive when used as an rvalue.

The more general rule is that you want to consider the largest possible lifetime of an lvalue, and the smallest possible one for an rvalue.

When going through an indirection, that will differ, unless we choose to tag all indirections, which is undesirable.

I'm unclear about what you're saying. Can you give an example in code?

Principle 8: Any time a reference is copied, the reference scope inherits the *maximum* of the two scope depths:


That makes control flow analysis easier, so I can buy this :)

Principle 8: We don't need to know! For all intents and purposes, a reference parameter has infinite lifetime for the duration of the function it is compiled in. Whenever we copy any reference, we do a bitwise OR on *all* of the mystery scopes. The new reference accumulates every scope it has ever had access to, directly or indirectly.


That would allow to copy a parameter reference to a global, which is dead unsafe.

Actually, it's not unsafe, so long as you have the parameter attribute `noscope` (or possibly `static`) working for you:

void fun(T* a) {
  static T* t;
  *t = a; // this might be safe
}

The truth is, this *might* be safe. It's only unsafe if the parameter `a` is located on the stack. From within the function, the compiler can't possibly know this. But if it forces you to mark `a` with `noscope` (or is allowed to infer the same), it tells the caller all it needs to know about `a`. Simply put, it's an error to pass a local to a `noscope` parameter. And it runs all the way down: any parameter which it itself passed to a `noscope` parameter must also be marked `noscope`. (Note: I'm actually preferring the name `static` at this point, but using `noscope` for consistency):

T* fun(noscope T* a) {
  static T* t;
  *t = a; // this might be safe
}

void tun(T* b) {
  T c;
  fun(&c); // error, local
  fun(b); // error, unless b also marked (or inferred) `noscope`
}

There is some goodness in there. Please address my comment and tell me if I'm wrong, but I think you didn't covered all bases.

The only base I'm really worried about is the lvalue vs rvalue base. Hopefully we can fix that!

Reply via email to