Skip to content

Latest commit

 

History

History
354 lines (250 loc) · 19.9 KB

DIP1015.md

File metadata and controls

354 lines (250 loc) · 19.9 KB

Deprecation and removal of implicit conversion from integer and character literals to bool

Field Value
DIP: 1015
Review Count: 2
Author: Michael V. Franklin (slavo5150@yahoo.com)
Implementation: DMD PR 7310
Status: Formal Assessment

Abstract

This DIP proposes the deprecation and removal of implicit conversion from integer and character literals to bool, thereby closing a hole in the type system and resolving overload resolution bugs. For the purpose of this document, Integer literals are literals of type int, uint, long or ulong and Character literals are literals of type char, wchar, or dchar.

Reference

Relevant D programming language specifications

Bugzilla issues

  • Issue 9999 - Integer literal 0 and 1 should prefer integer type in overload resolution
  • Issue 10560 - Enum typed as int with value equal to 0 or 1 prefer bool over int overload]

Preemptive changes in Phobos and DRuntime anticipating this change

Other

Rationale

D's existing implementation implicitly converts integer and character literals that evaluate to 0 and 1 to false and true respectively.

Example A

The following example currently compiles in the existing implementation.

void main()
{
    bool boolValue;

    // Character literals
    boolValue = '\0';
    boolValue = '\1';

    boolValue = '\u0000';
    boolValue = '\u0001';

    boolValue = '\U00000000';
    boolValue = '\U00000001';

    // Integer literals
    boolValue = 0;
    boolValue = 1;

    boolValue = 0u;
    boolValue = 1u;

    boolValue = 0L;
    boolValue = 1L;

    boolValue = 0UL;
    boolValue = 1UL;
}

The implicit conversion of character literals to bool is questionable, as there is no intrinsic relationship between the characters '\0' and '\1', and the boolean states false and true. This behavior may be an unintended consequence of integer promotion in D and implicit integer literal conversion to bool. The reader of such code would be right to question whether such expressions are actually programming errors or the author's intent, as they circumvent the type system without an explicit cast.

D has a boolean type bool, boolean literals false and true, and also supports explicit casting of integers to bool, so implicit conversion from integer literals to bool is unnecessary, and even problematic as demonstrated below.

Issues 9999 and 10560 document how implicit conversion of integer literals to bool causes the compiler to make an assumption possibly contrary to what the programmer intended.

Example B

The following example produces no assertion failures under the existing implementation.

int f(bool b) { return 1; }
int f(int i) { return 2; }

enum E : int
{
    a = 0,
    b = 1,
    c = 2,
}

void main()
{
    assert(f(E.a) == 1);   // Potential Bug: calls f(bool).  Did the author intend to call f(int)?
    assert(f(E.b) == 1);   // Potential Bug: calls f(bool).  Did the author intend to call f(int)?
    assert(f(E.c) == 2);   // called f(int)
}

Since there is no f(E) overload, and E inherits from int, it would be logical to expect the compiler to select the f(int) overload in any call to f with an argument of type E. However, due to value range propagation and the implicit conversion of integer literals to bool, under the existing implementation, the f(bool) overload is selected.

Example C

The following example is similar to Example B but with a slight nuance. Under the existing implementation, it produces no assertion failures.

int f(bool b) { return 1; }
int f(long l) { return 2; }

enum int a = 2;
enum int b = 1;

int c = 2;
int d = 1;

void main()
{
    // a - b evaluated at compile time
    assert(f(a - b) == 1); // Potential Bug: calls f(bool).  Did the author intend to call f(long)?

    // c - d evaluated at run time
    assert(f(c - d) == 2); // calls f(long)
}

Which overload the compiler selects is dependent on whether the subtraction expression is evaluated at compile time or run time. If evaluated at compile time, due to value range propagation and implicit conversion of integer literals to bool, the f(bool) overload is called. If evaluated at run time, the f(long) overload is called.

Summary

D already has boolean literals true and false, so the implicit conversion of integer literals to bool is not necessary; they only introduce ambiguity in the source code, circumvent the type system, and through bad interplay with value range propagation, cause unintuitive behavior in overload resolution.

If required, users can always resort to explicit casts (e.g. cast(bool)0) to take control and convey their intent.

Conditionals such as if(1), while(1), assert(0) will not be affected by this proposal and their behavior will remain as it does in the existing implementation.

Description

As illustrated in Example D below, the scope of the proposed change is targeted and precise.

This DIP proposes a two-stage implementation. In stage 1, the compiler will emit a deprecation warning for any expression that directly or indirectly makes an implicit cast from an integer literal to a bool. In stage 2, the compiler will emit an error for said expressions.

Only literals that evaluate to 0 or 1 are affected; all other literals are to remain unchanged.

Only implicit conversions are affected; explicit casts are to remain unchanged.

Example D

The following example illustrates the type of expressions affected by this proposal, how they will respond to the proposed change, and how users can modify existing code to make it compatible.

int f(bool b) { return 1; }
int f(int i) { return 2; }

enum E : int
{
    a = 0,
    b = 1,
    c = 2,
}

int g(bool b) { return 1; }
int g(long i) { return 2; }

enum int a = 2;
enum int b = 1;

void main()
{
    bool boolValue;
    bool[] boolValues;

    import std.bitmanip : BitArray;
    BitArray bitArray;

    boolValue = '\0';         // Existing implementation: OK
                              // After stage 1: Deprecation warning
                              // After stage 2: Error
                              // Remedy: `boolValue = cast(bool)'\0'`

    boolValue = '\1';         // Existing implementation: OK
                              // After stage 1: Deprecation warning
                              // After stage 2: Error
                              // Remedy: `boolValue = cast(bool)'\1'`

    boolValue = '\u0000';     // Existing implementation: OK
                              // After stage 1: Deprecation warning
                              // After stage 2: Error
                              // Remedy: `boolValue = cast(bool)'\u0000'`

    boolValue = '\u0001';     // Existing implementation: OK
                              // After stage 1: Deprecation warning
                              // After stage 2: Error
                              // Remedy: `boolValue = cast(bool)'\u0001'`

    boolValue = '\U00000000'; // Existing implementation: OK
                              // After stage 1: Deprecation warning
                              // After stage 2: Error
                              // Remedy: `boolValue = cast(bool)'\U00000000'`

    boolValue = '\U00000001'; // Existing implementation: OK
                              // After stage 1: Deprecation warning
                              // After stage 2: Error
                              // Remedy: `boolValue = cast(bool)'\U00000001'`

    boolValue = 0;            // Existing implementation: OK
                              // After stage 1: Deprecation warning
                              // After stage 2: Error
                              // Remedy: `boolValue = cast(bool)0`

    boolValue = 1;            // Existing implementation: OK
                              // After stage 1: Deprecation warning
                              // After stage 2: Error
                              // Remedy: `boolValue = cast(bool)1`

    boolValue = 0u;           // Existing implementation: OK
                              // After stage 1: Deprecation warning
                              // After stage 2: Error
                              // Remedy: `boolValue = cast(bool)0u`

    boolValue = 1u;           // Existing implementation: OK
                              // After stage 1: Deprecation warning
                              // After stage 2: Error
                              // Remedy: `boolValue = cast(bool)1u`

    boolValue = 0L;           // Existing implementation: OK
                              // After stage 1: Deprecation warning
                              // After stage 2: Error
                              // Remedy: `boolValue = cast(bool)0L`

    boolValue = 1L;           // Existing implementation: OK
                              // After stage 1: Deprecation warning
                              // After stage 2: Error
                              // Remedy: `boolValue = cast(bool)1L`

    boolValue = 0UL;          // Existing implementation: OK
                              // After stage 1: Deprecation warning
                              // After stage 2: Error
                              // Remedy: `boolValue = cast(bool)0UL`

    boolValue = 1UL;          // Existing implementation: OK
                              // After stage 1: Deprecation warning
                              // After stage 2: Error
                              // Remedy: `boolValue = cast(bool)1UL`

    boolValues = [0,1,0,1];   // Existing implementation: OK
                              // After stage 1: Deprecation warning
                              // After stage 2: Error
                              // Remedy: `boolValue = cast(bool[])[0,1,0,1]`

    bitArray = BitArray([0,1,0,1]); // Existing implementation: OK
                                    // After stage 1: Deprecation warning
                                    // After stage 2: Error
                                    // Remedy: `bitArray = BitArray(cast(bool[])[0,1,0,1])`.
                                    // `int[]` constructor and function overloads can also be added
                                    // to `BitArray`'s implementation to avoid requiring the user to
                                    // insert an explicit cast.

    f(E.a);                   // Existing implementation: `calls f(bool)`
                              // After stage 1: Deprecation warning, still `calls f(bool)`
                              // After stage 2: calls `f(int)`
                              // Remedy to keep existing behavior: `f(cast(bool)E.a)`

    f(E.b);                   // Existing implementation: `calls f(bool)`
                              // After stage 1: Deprecation warning, still `calls f(bool)`
                              // After stage 2: calls `f(int)`
                              // Remedy to keep existing behavior: `f(cast(bool)E.a)`

    g(a - b);                 // Existing implementation: `calls g(bool)`
                              // After stage 1: Deprecation warning, still `calls g(bool)`
                              // After stage 2: calls `g(long)`
                              // Remedy to keep existing behavior: `f(cast(bool)(a - b))`
}

// Conditionals are not affected by this proposal.  The following code's behavior
// will remain as it does in the existing implementation.
if (1) { }
assert(0);
static assert(0);
do { } while (1);
while (1) { }
for(;1;) { }

Stage 1's implementation will require updating the Deprecated Features page, an update to the language specification, and an entry in the changelog.

Stage 2's implementation will require updating the Deprecated Features page, and adding an entry to the changelog.

This proposed change may cause existing code to no longer compile, or in some cases, change behavior, but such code is arguably already ambiguous, and it would only benefit users to require them to remove such ambiguity from their code by stating their intent through explicit casts. Furthermore, this deprecation closes a hole in the type system, making the D programming language just that much more robust for writing safe and reliable code.

Alternatives considered

Change to overload resolution rules

A pull request was created to resolve Issue 9999 by adding a special case to partial ordering resolution:

In partial ordering resolution, bool parameter is treated as less specialized than other integer types (byte, ubyte, short, ushort, int, uint, long, ulong, char, wchar, dchar).

It was rejected by Walter Bright:

I do not agree with this enhancement. First off, making special cases for partial ordering takes a simple, straightforward idea and turns it into a potential morass of conflicting cases that we'll be stuck with forever. Secondly, the only issue here is whether 1 should be implicitly convertible to bool.

Walter identified the fundamental source of the problem to be the implicit conversion of integer literals to bool.

A change to the overload resolution rules would still break code relying on the existing overload resolution behavior illustrated in Example B and Example C, resulting in a deprecation process similar to, if not the same as, that of the proposed solution, but would not close the hole in the type system.

Disallow implicit conversion of integer literals to bool, but only for overloaded functions

It may be possible to fix the bugs documented in issue 9999 and 10560 by preventing implicit conversion of integer literals to bool, but only for overloaded functions.

This was rejected for similar reasons as the former; it complicates the compiler's implementation with special cases that need to be implemented, documented, explained, and understood.

Also, like the former, it would still break code relying on the existing overload resolution behavior illustrated in Example B and Example C, resulting in a deprecation process similar to, if not the same as, that of the proposed solution, but would not close the hole in the type system.

This solution was also not analyzed in enough detail to determine whether it would scale.

Breaking Changes and Deprecations

The proposed implementation will cause any implicit conversion from an integer or character literal to a bool to result in a deprecation warning at first, and eventually a compiler error. Existing code employing said conversions will need to be modified with an explicit cast to remain compilable in the future. See Example D for specific examples.

The proposed implementation is to be rolled out through a sequence of steps to minimize any disruption.

  1. An announcement will be made on the D programming language forum to raise awareness of the change in the community. Users will have a link to this DIP and the implementation so they can understand how to mitigate breakage in their code, and monitor the timing of the subsequent releases.

  2. After approval of this DIP, code in DMD's test suite, Phobos, and DRuntime will be updated with the appropriate remedies documented in Description to avoid deprecation warnings or errors in the stage 1 and stage 2 releases. Preemptive pull requests anticipating the change proposed in this DIP have already been merged for Phobos and DRuntime, but another pass will need to be made.

  3. After the changes in step 2 have been merged, stage 1 can be merged. From that point on the compiler will emit deprecation warnings when an implicit conversion from an integer literal to bool is encountered. This will precisely identify instances in users' code that will no longer compile when stage 2 is released, but will still perform the conversion, buying users time to update their code. The deprecation will remain in place for a time period to be decided by the language maintainers at the time of this DIP's formal assessment.

  4. After the time period specified in step 3 has elapsed, stage 2 can be merged. From that point on the compiler will emit errors when an implicit conversion from an integer literal to bool is encountered.

Copyright & License

Copyright (c) 2018 by the D Language Foundation

Licensed under Creative Commons Zero 1.0

Reviews

Community Review Round 1

Reviewed Version

Discussion

The overall feedback was positive. Aside from a few typos and one mistaken implication about C (all of which were corrected by the DIP author), criticisms focused on lack of clarity for two specific cases.

It was pointed out that the DIP made no mention of situations when integers are used in conditionals, such as if(1). The DIP author updated the DIP to clarify that this case is not affected by the DIP and added a test case to the implementation to ensure it remains unaffected.

Another mentioned oversight was the case of assert(0). The DIP author updated the DIP to reflect that this case is not affected by the DIP.

Final Review

Reviewed Version

Discussion

The DIP proposed deprecating declarations of enum : bool that did not explicitly initialize values to true or false. It was pointed out that this is not necessary. The author agreed and removed that part of the proposal.

A suggestion was made that the DIP should require a new compiler flag. The DIP author did not find this necessary as he does not consider the changes from this proposal serious enough to justify a new flag. He was open to adding the suggestion if others thought it necessary, or as an option for the language maintainers to consider. The precedent of -transition=intpromote was subsequently brought up as an example, along with a description of how it would fit into the steps of project migration. Another commenter suggested the -dip1000 example. The DIP author did not respond to those suggestions and did not update the DIP to reflect them.