Jump to page: 1 2
Thread overview
Deprecate implicit conversion between signed and unsigned integers
May 12
monkyyy
May 14
Dom DiSc
May 14
Dom DiSc
May 13
Dukc
May 14
An Pham
May 12

D inherited these implicit conversions from C and C++, where they are widely regarded as a source of bugs.

  • In his 2018 paper, "Subscripts and sizes should be signed", Bjarne Stroustrup gives several examples of bugs caused by the use of unsigned sizes and indices in C++. About half of them are caused by wrapping subtraction; the other half are caused by implicit conversion from signed to unsigned.

  • The C++20 standard includes safe integer comparison functions specifically to avoid these implicit conversions when comparing integers of different signedness.

  • Both GCC and Clang provide a -Wsign-conversion flag to warn about these implicit conversions.

In D, they cause the additional problem of breaking value-range propagation (VRP):

enum byte a = -1;
enum uint b = a; // Ok, but...
enum byte c = b; // Error - original value range has been lost

D would be a simpler, easier-to-use language if these implicit conversions were removed. The first step to doing that is to deprecate them.

While this is a breaking change, migration of old code would be very simple: simply insert an explicit cast to silence the error and restore the original behavior. In many cases, migration could be performed automatically with a tool that uses the DMD frontend as a library.

I believe this change would be received positively by existing D programmers, since D's willingness to discard C and C++'s mistakes is one of the things that draws programmers to D in the first place.

May 12

On Sunday, 12 May 2024 at 13:32:36 UTC, Paul Backus wrote:

>

D
I believe this change would be received positively by existing D programmers, since D's willingness to discard C and C++'s mistakes is one of the things that draws programmers to D in the first place.

D often takes a worse of both worlds; bad defaults and verbose handling

size_t(-1) >0 is a problem with indexes being unsigned for a theatrical computer with >9334 petabytes of ram (2^63) being the wrong tradeoff

No; types would need better defaults before even considering adding verbosity id rather see breaking changes.

May 12

On Sunday, 12 May 2024 at 13:32:36 UTC, Paul Backus wrote:

>

In D, they cause the additional problem of breaking value-range propagation (VRP):

enum byte a = -1;
enum uint b = a; // Ok, but...
enum byte c = b; // Error - original value range has been lost

D would be a simpler, easier-to-use language if these implicit conversions were removed. The first step to doing that is to deprecate them.

Signed to unsigned should be deprecated (except where VRP can tell the source was not negative).

Unsigned to signed can preserve the value range when the signed type is bigger than the unsigned type, e.g.:

extern ubyte x;
short y = x; // OK, short.max >= ubyte.max
byte z = x;  // Deprecate, byte.max < ubyte.max

These deprecations should be for the next edition of D.

>

While this is a breaking change, migration of old code would be very simple: simply insert an explicit cast to silence the error and restore the original behavior.

cast can be bug-prone if the original type gets changed. It would be better to have druntime template functions signed and unsigned to do the casts with IFTI to avoid changing the size of the type.

>

In many cases, migration could be performed automatically with a tool that uses the DMD frontend as a library.

Can you give some examples?

>

I believe this change would be received positively by existing D programmers, since D's willingness to discard C and C++'s mistakes is one of the things that draws programmers to D in the first place.

Yes, implicit conversion to a type with incompatible value range is too bug-prone, D should prevent that. It is particularly galling that decent C compilers have had warnings for this for such a long time.

What about comparisons between incompatible signed and unsigned, deprecate too?

May 12

On Sunday, 12 May 2024 at 20:20:10 UTC, Nick Treleaven wrote:

>

cast can be bug-prone if the original type gets changed. It would be better to have druntime template functions signed and unsigned to do the casts with IFTI to avoid changing the size of the type.

I forgot, those already exist, at least in Phobos:
https://dlang.org/phobos/std_conv.html#.unsigned

May 13
Paul Backus kirjoitti 12.5.2024 klo 16.32:
> D would be a simpler, easier-to-use language if these implicit conversions were removed. The first step to doing that is to deprecate them.
Ditching all backwards-compatibility issues, it would be a good idea. But, this would cause *tremendous* amounts of breakage.

Before, I would have said it simply isn't worth it. But since we're going to have editions, maybe. I'm still somewhat sceptical though. Nothing will break without a warning and people can stay at older editions if they want, but it's going to add a lot of work for someone migrating 100_000 lines to a new edition. That amount of code will likely have hundreds or even thousands of deprecations to fix.

I tend to think that if we will write an official automatic tool to add the needed casts, it's probably worth it. Otherwise not.
May 13
On Monday, 13 May 2024 at 12:48:04 UTC, Dukc wrote:
> Paul Backus kirjoitti 12.5.2024 klo 16.32:
> Ditching all backwards-compatibility issues, it would be a good idea. But, this would cause *tremendous* amounts of breakage.
>
> Before, I would have said it simply isn't worth it. But since we're going to have editions, maybe. I'm still somewhat sceptical though. Nothing will break without a warning and people can stay at older editions if they want, but it's going to add a lot of work for someone migrating 100_000 lines to a new edition. That amount of code will likely have hundreds or even thousands of deprecations to fix.

I think even with editions we need to avoid making it hard to port code to a newer edition. So instead of a deprecation, we could make it a `-w` warning instead.

> I tend to think that if we will write an official automatic tool to add the needed casts, it's probably worth it. Otherwise not.


May 13

I think yes, we should ban signed/unsigned conversions, but I also think implicit conversions when VRP has validated all the values are representable is fine (e.g. ubyte should implicitly convert to short or int). This should cut down on the false positives.

-Steve

May 14

On Sunday, 12 May 2024 at 20:20:10 UTC, Nick Treleaven wrote:

>

What about comparisons between incompatible signed and unsigned, deprecate too?

We have a working solution that always returns the correct result (see https://issues.dlang.org/show_bug.cgi?id=259). I never understood why anyone would rely on a wrong comparison result, so this should not be considered a breaking change.

May 14

On Tuesday, 14 May 2024 at 06:59:16 UTC, Dom DiSc wrote:

>

We have a working solution that always returns the correct result (see https://issues.dlang.org/show_bug.cgi?id=259).

And by the way: This solution doesn't involve integer propagation at all and also works for comparison of long with ulong.
For beginners this is by far the worst bug in D, and its there since 18 (in words: eightteen) years - feels like lingering there longer than D itself.
This is so distracting.

It's ten lines of code and costs nothing (except if you indeed compare differnt signed types, and then it's still very cheep):

int opCmp(T, U)(const(T) a, const(U) b) pure @safe @nogc nothrow
   if(isIntegral!T && isIntegral!U && is(Unqual!T != Unqual!U))
{
   static if(isSigned!T && isUnsigned!U && T.sizeof <= U.sizeof)
      return (a < 0) ? -1 : opCmp(cast(U)a, b);
   else static if(isUnsigned!T && isSigned!U && T.sizeof >= U.sizeof)
      return (b < 0) ? 1 : opCmp(a, cast(T)b);
   else // use common type as ever:
      return opCmp(cast(CommonType!(T, U))a, cast(CommonType!(T, U))b);
}
May 14

On Sunday, 12 May 2024 at 13:32:36 UTC, Paul Backus wrote:

>

D inherited these implicit conversions from C and C++, where they are widely regarded as a source of bugs.

Just focus on sign vs unsign is not good enough. Sometime you need to specify a range of values. There is module std.CheckedInt which

  1. Should extend it to be a runtime system module which does not need to 'import'
  2. Add range template parameters Checked!(int, X, Y, ...) like Checked!(int, -5, 200, ...)
    which only hold values from -5 to 200 inclusively
  3. Extend language to allow implicit passing parameter void foo(Checked!(int, X, Y, ...) z)
    and can be called by foo(10) is ok but foo(1000) should failed
« First   ‹ Prev
1 2