July 14, 2017
On Monday, 10 July 2017 at 04:02:59 UTC, Nicholas Wilson wrote:
> 1)@noreturn
> 2)@disable(return)
> 3)none
>
> w.r.t optimisation assuming both 1 & 3  impact DMD equally [...]

I don't think that's true.  A Bottom type does not cover all use cases of @noreturn/@pragma(noreturn).

Example 1: Polymorphism

class Bird { void fly() { ... } };
class Penguin : Bird { override void fly() @pragma(noreturn) { assert(0); }  };
class EvolvedPenguin : Penguin { override void fly() { ... } };

There's no way to encode that information in a return type.

Example 2: Compile-time polymorphism

Same as above, except during compile time.  While it looks ok in theory (just return Bottom, and everything is alright), it seems very tricky to get right.  Example from checkedint.d:

auto r = hook.hookOpUnary!op(payload);
return Checked!(typeof(r), Hook)(r);

Let's say the hook refuses to perform hookOpUnary, so r is Bottom.  Unfortunately, Checked!(Bottom, Hook)(r) doesn't compile (because "if (isIntegral!T || is(T == Checked!(U, H), U, H))" fails).  While Bottom may be substituted into all expressions (which doesn't seem easy anyway), it for sure can't be inserted as any template argument.  As seen before, Checked is not Bottom-proof.  I would think that most templates are not Bottom-proof and making them Bottom-proof seems quite a bit of work.

With @pragma(noreturn) that situation would be no problem.

Example 3: Unreachable statements/Implicit noreturn inference

As pointed out by Steven Schveighoffer, the current unreachability errors should probably be removed in generic code.

If we do that, then generic functions can be @pragma(noreturn) if certain conditions are met.  A compiler can easily figure that out, but writing it inside static ifs could be almost impossible.

Assume we have a hook to Checked that disallows casts.  Current signature:

U opCast(U, this _)() if (isIntegral!U || isFloatingPoint!U || is(U == bool))

The compiler can figure out that all code paths end with an @pragma(noreturn), so it can add that pragma implicitly to the signature.  However, the compiler can't change the return type from U to Bottom (otherwise static equality checks with U will fail).

July 14, 2017
On Friday, 14 July 2017 at 15:39:01 UTC, Guillaume Boucher wrote:
> Example 1: Polymorphism
>
> class Bird { void fly() { ... } };
> class Penguin : Bird { override void fly() @pragma(noreturn) { assert(0); }  };
> class EvolvedPenguin : Penguin { override void fly() { ... } };
>

No matter how you look at it, this code should simply not be allowed:

Bird bird = ...;
bird.fly(); // is this return or noreturn?

Penguin penguin = ...;
penguin.fly(); // is this return or noreturn?

In both cases, compiler cannot draw any conclusions about return/noreturn and thus I believe such code should not be allowed.

And if this is disallowed, a Bottom type would fit again.
July 14, 2017
On 07/14/2017 03:06 PM, Lurker wrote:
> On Friday, 14 July 2017 at 15:39:01 UTC, Guillaume Boucher wrote:
>> Example 1: Polymorphism
>>
>> class Bird { void fly() { ... } };
>> class Penguin : Bird { override void fly() @pragma(noreturn) { assert(0); }  };
>> class EvolvedPenguin : Penguin { override void fly() { ... } };
>>
> 
> No matter how you look at it, this code should simply not be allowed:
> 
> Bird bird = ...;
> bird.fly(); // is this return or noreturn?
> 
> Penguin penguin = ...;
> penguin.fly(); // is this return or noreturn?
> 
> In both cases, compiler cannot draw any conclusions about return/noreturn and thus I believe such code should not be allowed.

Conventional thinking has it that derived methods should "require less and deliver more" such that substitution is possible. That's where contravariant parameters and covariant returns come from. Therefore, methods that do not return should be able to override those that return. (The opposite is unworkable btw.) Note that the absence of a "noreturn" annotation does not imply a guarantee that the method does return.

Andrei
July 15, 2017
On 7/13/2017 5:18 PM, Andrei Alexandrescu wrote:
> On 7/13/17 2:37 PM, Timon Gehr wrote:
>> On Thursday, 13 July 2017 at 17:25:18 UTC, Timon Gehr wrote:
>>> Anyway, my assertion that Bottom cannot be a subtype of all other types was actually incorrect: the compiler does not need to generate code for implicit conversion from Bottom to some other type, so it can be treated as a subtype.
>>> ...
>>
>> (Actually, there are some complications like the .sizeof property. Anyway, it is clear what the semantics of Bottom are, no matter whether it is subtyping or implicit conversion.)
> 
> I wonder if sizeof could be made size_t.max. -- Andrei

I thought bottom.sizeof would be 0.
July 16, 2017
On 16.07.2017 05:30, Walter Bright wrote:
> On 7/13/2017 5:18 PM, Andrei Alexandrescu wrote:
>> On 7/13/17 2:37 PM, Timon Gehr wrote:
>>> On Thursday, 13 July 2017 at 17:25:18 UTC, Timon Gehr wrote:
>>>> Anyway, my assertion that Bottom cannot be a subtype of all other types was actually incorrect: the compiler does not need to generate code for implicit conversion from Bottom to some other type, so it can be treated as a subtype.
>>>> ...
>>>
>>> (Actually, there are some complications like the .sizeof property. Anyway, it is clear what the semantics of Bottom are, no matter whether it is subtyping or implicit conversion.)
>>
>> I wonder if sizeof could be made size_t.max. -- Andrei
> 
> I thought bottom.sizeof would be 0.

0 is the obvious size for the unit type (the type with a single value, in D this is for example void[0]), as in:

struct S{
    T x;
    void[0] nothing;
}
static assert(S.sizeof == T.sizeof);

on the other hand

struct S{
   T x;
   Bottom everything;
}

turns the entire struct into an empty type. It is therefore most natural to say that Bottom.sizeof == ∞. (It's the only choice for which S.sizeof == Bottom.sizeof.)

Another way to think about it: If something of type A* converts to something of type B* without problems, then one would expect B.sizeof <= A.sizeof. This would imply that Bottom.sizeof >= size_t.max. (Because Bottom* converts to all other pointer types.)

One small issue is that one needs to avoid overflow for the size of a struct that has multiple fields where one of them is of type Bottom.

July 16, 2017
On 07/15/2017 11:30 PM, Walter Bright wrote:
> On 7/13/2017 5:18 PM, Andrei Alexandrescu wrote:
>> On 7/13/17 2:37 PM, Timon Gehr wrote:
>>> On Thursday, 13 July 2017 at 17:25:18 UTC, Timon Gehr wrote:
>>>> Anyway, my assertion that Bottom cannot be a subtype of all other types was actually incorrect: the compiler does not need to generate code for implicit conversion from Bottom to some other type, so it can be treated as a subtype.
>>>> ...
>>>
>>> (Actually, there are some complications like the .sizeof property. Anyway, it is clear what the semantics of Bottom are, no matter whether it is subtyping or implicit conversion.)
>>
>> I wonder if sizeof could be made size_t.max. -- Andrei
> 
> I thought bottom.sizeof would be 0.

My thinking comes from bottom being the subtype of all types in the universe. Therefore, it must include the state of all types. But let's wait for Timon. -- Andrei
July 16, 2017
On 14.07.2017 17:39, Guillaume Boucher wrote:
> On Monday, 10 July 2017 at 04:02:59 UTC, Nicholas Wilson wrote:
>> 1)@noreturn
>> 2)@disable(return)
>> 3)none
>>
>> w.r.t optimisation assuming both 1 & 3  impact DMD equally [...]
> 
> I don't think that's true.  A Bottom type does not cover all use cases of @noreturn/@pragma(noreturn).
> ...

I think it does, but it is a significantly more invasive language change.

> Example 1: Polymorphism
> 
> class Bird { void fly() { ... } };
> class Penguin : Bird { override void fly() @pragma(noreturn) { assert(0); }  };
> class EvolvedPenguin : Penguin { override void fly() { ... } };
> 
> There's no way to encode that information in a return type.
> ...

I'd say a function with return type Bottom can override any function in the base class.

> Example 2: Compile-time polymorphism
> 
> Same as above, except during compile time.  While it looks ok in theory (just return Bottom, and everything is alright), it seems very tricky to get right.  Example from checkedint.d:
> 
> auto r = hook.hookOpUnary!op(payload);
> return Checked!(typeof(r), Hook)(r);
> 
> Let's say the hook refuses to perform hookOpUnary, so r is Bottom.  Unfortunately, Checked!(Bottom, Hook)(r) doesn't compile (because "if (isIntegral!T || is(T == Checked!(U, H), U, H))" fails).  While Bottom may be substituted into all expressions (which doesn't seem easy anyway), it for sure can't be inserted as any template argument.  As seen before, Checked is not Bottom-proof.  I would think that most templates are not Bottom-proof and making them Bottom-proof seems quite a bit of work.
> ...

The problem for this example is that the current implementation of isIntegral would return false for Bottom.

> With @pragma(noreturn) that situation would be no problem.
> ...

pragma(noreturn) is indeed the simpler solution, as it does not interact with anything else. The fact that template constraints in some cases need to be aware of the existence of Bottom in order to work for Bottom is clearly a negative property of this solution in the context of D.

> Example 3: Unreachable statements/Implicit noreturn inference
> 
> As pointed out by Steven Schveighoffer, the current unreachability errors should probably be removed in generic code.
> 
> If we do that, then generic functions can be @pragma(noreturn) if certain conditions are met.  A compiler can easily figure that out, but writing it inside static ifs could be almost impossible.
> 
> Assume we have a hook to Checked that disallows casts.  Current signature:
> 
> U opCast(U, this _)() if (isIntegral!U || isFloatingPoint!U || is(U == bool))
> 
> The compiler can figure out that all code paths end with an @pragma(noreturn), so it can add that pragma implicitly to the signature.  However, the compiler can't change the return type from U to Bottom (otherwise static equality checks with U will fail).
> 

You can return 'auto' instead of U. Then the return type will be inferred either as U or Bottom.

July 16, 2017
On Sunday, 16 July 2017 at 13:03:40 UTC, Timon Gehr wrote:
>> I don't think that's true.  A Bottom type does not cover all use cases of @noreturn/@pragma(noreturn).
>> ...
>
> I think it does, but it is a significantly more invasive language change.

The best you can hope for is that any code with pragma(noreturn) can be rewritten into functionally equivalent code that uses Bottom and gives the same hints to the optimizer.

pragma(noreturn) can be inferred implicitly which makes it more impactful in practice.

> I'd say a function with return type Bottom can override any function in the base class.

That solves the "Penguin : Bird" step, but not "EvolvedPenguin : Penguin" (which can fly).

Andrei argues that my example don't comply with a puristic understanding of inheritance.  Maybe that's enough of a reason to not optimize such use cases, but it still shows that pragma(noreturn) is somehow stronger than Bottom.


> pragma(noreturn) is indeed the simpler solution, as it does not interact with anything else. The fact that template constraints in some cases need to be aware of the existence of Bottom in order to work for Bottom is clearly a negative property of this solution in the context of D.

Yes, basically this.


> You can return 'auto' instead of U. Then the return type will be inferred either as U or Bottom.

Sure there are workarounds.  Also here:

auto deref(T)(ref T* x) { return deref(*x); }
ref T deref(T)(ref T x) if (!isPointer!T) { return x; }

But when every small function needs a rewrite, something seems off.
July 16, 2017
On Sunday, 16 July 2017 at 12:41:06 UTC, Timon Gehr wrote:
> It is therefore most natural to say that Bottom.sizeof == ∞.

True, but size_t.max doesn't have the properties of ∞.
The only sane choice to me seems to be a value of type Bottom, i.e. is(typeof(Bottom.sizeof) == Bottom).

July 16, 2017
On 16.07.2017 21:49, Guillaume Boucher wrote:
> 
>> I'd say a function with return type Bottom can override any function in the base class.
> 
> That solves the "Penguin : Bird" step, but not "EvolvedPenguin : Penguin" (which can fly).
> 
> Andrei argues that my example don't comply with a puristic understanding of inheritance.  Maybe that's enough of a reason to not optimize such use cases, but it still shows that pragma(noreturn) is somehow stronger than Bottom.

The issue isn't purism, it is type safety. If you create an EvolvedPenguin, upcast it to a Penguin and call the fly method you get UB. So noreturn would indeed need to enforce that all overrides are also noreturn.