2 days ago
On 13/09/2025 10:19 PM, kdevel wrote:
> On Saturday, 13 September 2025 at 02:39:40 UTC, Richard (Rikki) Andrew Cattermole wrote:
>> [...]
>> Afterwards, I came up with an example, where DIP1000 will error:
>>
>> ```d
>> int* ptr;
>>
>> void func(bool b, scope int* p) @safe {
>>   assert(!b);
>>
>>   if (b) {
>>     ptr = p; // Error: scope variable `p` assigned to global variable `ptr`
>>   }
>> }
>> ```
>>
>> This is clearly a false positive; that branch could never run!
> 
> What about "release" mode? And what about that code:

What assert lowers to is irrelevant as far as a frontend DFA is concerned. It runs before that is taken into account.

> ```d
> void main ()
> {
>     assert (0);
> 
>     string s = "x";
>     s [0] = 'y';
> }
> ```
> 
> Is "Error: cannot modify `immutable` expression `s[0]`" a false positive, too?

Yes actually.

Not all languages, and some in the ML line should have the ability to not process anything after the assert statement. They apply DFA into the type system to varying degrees. We don't.

For halt, we may have the ability to kill it off eventually. However we can't cull non-halt asserts, as we are not tieing our semantic analysis to a DFA engine. It is too late to change that.

Note: a halt assert is: assert(0)
2 days ago
On 14/09/2025 1:06 AM, kdevel wrote:
> On Saturday, 13 September 2025 at 11:08:29 UTC, IchorDev wrote:
>> [...]
>>> What about "release" mode? And what about that code:
>>>
>>> ```d
>>> void main ()
>>> {
>>>    assert (0);
>>>
>>>    string s = "x";
>>>    s [0] = 'y';
>>> }
>>> ```
>>
>> The program has entered an invalid state and the behaviour of the continuing execution of the program is undefined.
> 
> Please note that the program is not run but just compiled.
> 
>> [...]
>>> Is "Error: cannot modify `immutable` expression `s[0]`" a false positive, too?
>>
>> It's not a 'false positive' because it's not related to DIP1000's stack escape rules at all.
> 
> I thought that the intent of the original code
> 
> ```d
> int* ptr;
> 
> void func(bool b, scope int* p) @safe {
>    assert(!b);
> 
>    if (b) {
>      ptr = p; // Error: scope variable `p` assigned to global variable `ptr`
>    }
> }
> ```
> 
> was to show that there is some code-block
> 
> ```d
>      ptr = p; // Error: scope variable `p` assigned to global variable `ptr`
> ```
> 
> which is never executed regardless of the value of `b`.

Half right.

There is a code block that will never be executed, it is because of variable b's state is known to prevent that code block from execution.
2 days ago
On 13/09/2025 11:06 PM, Dennis wrote:
> On Saturday, 13 September 2025 at 02:39:40 UTC, Richard (Rikki) Andrew Cattermole wrote:
>> Paraphrased, why should we use a DFA engine instead of DIP1000?
>>
>> (...)
>>
>> Afterwards, I came up with an example, where DIP1000 will error:
> 
> Thanks for providing the example, it's a great discussion starter.
> 
> Like I said yesterday, it's not that I don't "know what data flow analysis is all about" - when you have a solution, you can come up with problems it solves, sure.
> 
> I was mostly curious what real world code made you believe that more advanced DFA is necessary for useful scope pointer analysis. There's many problems with DIP1000, but no issue was ever opened requesting scope checks to be disabled in dead code. As far as I'm aware, [users expect dead code to be type checked by the frontend as usual](https:// forum.dlang.org/post/hullwyrnmgcaoghaqbux@forum.dlang.org).

That isn't my interpretation.

The code you linked would have been removed by a backend optimizer. It is dead code. It exists to get effects from the frontend (poorly), due to lacking language capabilities to archive the results needed.

DFA engines, for the purposes of linting like this thread is about, should focus on what code could actually run at runtime.

The fact that D is not architectured to be able to stop these false positives is an intended bug in our design. Not all PL's work like this the ML family certainly don't. They tie very advanced analysis engines including DFA into their type system.

Just because people don't understand that this is possible, doesn't mean it hasn't been an issue. I see it as a case that people don't know what they don't know. So they don't complain. This is a hole that the C family relies upon having due to historical reasons and so hasn't reached common knowledge.
2 days ago
On Saturday, 13 September 2025 at 15:43:27 UTC, Richard (Rikki) Andrew Cattermole wrote:
> [...]
>> I thought that the intent of the original code
>> 
>> ```d
>> int* ptr;
>> 
>> void func(bool b, scope int* p) @safe {
>>    assert(!b);
>> 
>>    if (b) {
>>      ptr = p; // Error: scope variable `p` assigned to global variable `ptr`
>>    }
>> }
>> ```
>> 
>> was to show that there is some code-block
>> 
>> ```d
>>      ptr = p; // Error: scope variable `p` assigned to global variable `ptr`
>> ```
>> 
>> which is never executed regardless of the value of `b`.
>
> Half right.
>
> There is a code block that will never be executed, it is because of variable b's state is known to prevent that code block from execution.

Then it is dead code that should be manually removed? Isn't that the problem here? The compiler correctly complains about invalid code, this is not a false positive, even if that code is never executed.

If I want code not to be executed I comment it out or delete it.


2 days ago
DIP1000 is necessary when the DFA is faced with only a function prototype rather than having the body of the function available.
2 days ago
On 9/13/2025 4:06 AM, Dennis wrote:
> As far as I'm aware, [users expect dead code to be type checked by the frontend as usual](https://forum.dlang.org/post/hullwyrnmgcaoghaqbux@forum.dlang.org).

That is correct. For the false path of a `static if`, the code must be syntactically correct but does not have to be semantically correct. `static if` would be rather useless if it required semantic correctness.

2 days ago
Requiring the language to remove all dead code before checking for errors means the entire program must be subjected to DFA. The result is compilation will slow down dramatically. It means separate compilation is no longer possible.

As a pragmatic choice, D is designed to not require DFA. It is a deliberate architectural choice, not a bug.

That said, an optional DFA can be quite useful in detecting otherwise hidden programming errors. But I'm not convinced it's a good idea to require it in order to successfully compile code, as your opening example suggests.
2 days ago
On 14/09/2025 3:12 PM, Walter Bright wrote:
> On 9/13/2025 4:06 AM, Dennis wrote:
>> As far as I'm aware, [users expect dead code to be type checked by the frontend as usual](https://forum.dlang.org/post/ hullwyrnmgcaoghaqbux@forum.dlang.org).
> 
> That is correct. For the false path of a `static if`, the code must be syntactically correct but does not have to be semantically correct. `static if` would be rather useless if it required semantic correctness.

The linked code is not static if, its a regular if.
2 days ago
On 14/09/2025 3:20 PM, Walter Bright wrote:
> Requiring the language to remove all dead code before checking for errors means the entire program must be subjected to DFA. The result is compilation will slow down dramatically. It means separate compilation is no longer possible.
> 
> As a pragmatic choice, D is designed to not require DFA. It is a deliberate architectural choice, not a bug.

It seems we have a miscommunication on this.

The requirements of D say its a feature, the PL literature says its a bug.

It can be both!

And yes, I agree the choice D went with is a good one for the C family. Very pragmatic.

Where DFA and CFG analysis is implemented (such as limited VRP and blockexit.d) are very impressive, I enjoyed seeing it.

However, these algorithms when in use, are not applied consistently.

https://github.com/dlang/dmd/issues/21859

It passes nothrow, but not -betterC:

```d
void func() nothrow {
    if (0) {
     	 throw new Exception("");  // Error: cannot use `throw` statements with -betterC
    }
}
```

> That said, an optional DFA can be quite useful in detecting otherwise hidden programming errors. But I'm not convinced it's a good idea to require it in order to successfully compile code, as your opening example suggests.

DFA is a rather large body is literature, we have to split it up before going eww no on any of it.

There is:

- Single forward pass: fast DFA, DIP1000, limited VRP/CFG analysis in frontend.
- Work list algorithm: @live, slow DFA if I ever build it.

Also:

- In the type system: i.e. storage classes, dictates what templates instantiate.
- Lint level: does not effect mangling, only limits what code is considered valid, no mutation of type system.

Considerations on the type system we can basically go no on, simply because dmd isn't architectured to handle it. Which is a good choice as I said elsewhere for the C family, but not all languages are like that and they do get benefits from doing so.

Work list algorithm is great but it has to be opt-in, not everyone is going to benefit from the slow down.

That kinda leaves us with single forward pass and lint level for anything on by default. Which unfortunately DIP1000 is not (due to it messing with storage classes, not that I'm arguing that was a wrong choice when it was implemented).

You can do an absolute load of stuff at lint level with a single forward pass engine. Which I'm finding out as I learn that my fast DFA engine is probably close to bleeding edge oops. Worthy of some serious pride as I
am reevaluating other language options lol.

This isn't a situation where we only have the worst set of options available to us. Each has their strengths and weaknesses. Its possible to have multiple opinions based upon how you are categorizing things for this particular subject.

2 days ago
On 14/09/2025 3:09 PM, Walter Bright wrote:
> DIP1000 is necessary when the DFA is faced with only a function prototype rather than having the body of the function available.

I'm curious as to why you said this.

There are only three differences I can come up with between a classical DFA and DIP1000:

- Attributes and lack thereof
- Single forward pass vs work list algorithm
- In the type system (storage classes ugh), vs lint level or optimization level

Any variation of the three can be present in a _modern_ frontend DFA.

The delineation between the two is basically nonexistent from my perspective. Unless you are not referring to modern frontend DFA, and actually mean classical DFA's which are indeed quite different.