On Thursday, 25 May 2023 at 15:51:15 UTC, Ali Çehreli wrote:
>On 5/24/23 16:04, Cecil Ward wrote:
>Is it possible for our compilers to detect direct infinite
recursion?
I agree the compiler can detect some cases. However, a diagnostic may not be what the programmer wants in all cases.
I am writing on this topic because I've recently been pleasantly surprised how dmd lets me inject assert(false) expressions during development:
void foo() {
someCode();
assert(false, "some information");
moreCode();
}
I am a printf
-style programmer, where I use such asserts as well.
I think the compiler used to complain about that in earlier versions (or maybe I remember compilers of earlier languages like C++?); the compiler would say "unreachable code".
I am thankful that dmd allows me do it during development and debugging.
So, your case may fall into this category where although it doesn't make sense, a programmer may have caused it intentionally.
I just tried what DMD does with
for (ubyte i = 0; i < 256u; ++i) { ... }
It doesn’t care that i < 256
is trivially true (by type). GCC and Clang give me:
warning: comparison is always true due to limited range of data type (GCC)
warning: result of comparison of constant 256 with expression of type 'unsigned char' is always true (Clang)
(For the record, in C++, this is platform dependent; there’s no guarantee by the language that unsigned char
is only 8-bit.)
An assert(false)
leading to dead code is indeed similar, but also different from an infinite loop/recursion. This is opinionated: assert(false)
is placed with intention; it’s a good question if (or under which circumstances) dead code should be an error; dead code is dubious and in a release build or otherwise optimized build, in non-template code, I’d probably like the compiler telling me about it. An infinite loop/recursion without observable effects is almost certainly not intentional and something is wrong: The condition is wrong or the body doesn’t affect the condition as intended. Even in a debug build, it would be valuable to have the info. There could be 3 degrees of diagnostic:
- In a debug build, an infinite loop/recursion, if detected, produces a warning. One big reason for this is that in debug mode,
pure
functions can actually have observable effects. - In a regular (non-debug, non-release, non-optimized) build, an infinite loop/recursion by syntax (something like
while(1){}
orint f(int x) => f(x)
and a little less basic examples) are errors (because they’re obviously wrong and should be detected by any compiler) and infinite loop/recursion by (deep) semantic analysis gets a warning. - In optimized/release, any detected infinite loop/recursion is an error.
Of course, by infinite loop/recursion, I mean those without observable effects, but “infinite loop/recursion” is wordy enough. A read–eval–print loop is perfectly fine. I might add that pure
is not enough. Throwing an exception can break a loop, so nothrow
is required as well for all the operations in question.