Neat-Lang / neat

The Neat Language compiler. Early beta?
BSD 3-Clause "New" or "Revised" License
111 stars 9 forks source link

Struct constructor doesn't require initialization of class reference #21

Open ntrel opened 1 year ago

ntrel commented 1 year ago
module objembed;

class C
{
    int i;
}

struct S
{
    C c;
    this() {}
}

void main()
{
    //S s; // error
    S s = S();
}

Declaring S s; gives:

      ~ Cannot declare s without initializer: S is not zero-initializable

But declaring S.this without initialization of c is not caught. s.c is then null.

FeepingCreature commented 1 year ago

Yes, this is ... kind of hard to fix. The problem is that we'd need control flow analysis (or runtime errors) to catch cases where there exist flows through the constructor that don't set a field. At the limit, this is no-shit halting-problem tier undecidable.

I could hack something like "every field in the struct must have at least one assignment somewhere in the constructor", but I fear this would only give the false impression of safety.

ntrel commented 1 year ago

Yes, some kind of basic flow analysis would be needed. If implemented like cppfront, the following code would error:

this(){
    if (random!bool()) // line 12
        c = new C; 
}
objembed:12: Error: `if` statement initializes `c` in only one branch

Requiring if to initialize a member in both branches if it is initialized in either seems a pragmatic way to solve the halting problem.

FeepingCreature commented 1 year ago

Okay, I'm gonna say that yes, I could do that, but I'm not gonna in the short and medium term. It seems a lot of effort for a mid improvement, and a lot of compiler machinery that wouldn't be used anywhere else. (Neat is relatively less reliant on compiler analysis than some other languages.)

Patches, as usual, welcome!

ntrel commented 1 year ago

It seems a lot of effort for a mid improvement

For now perhaps an out contract for structs with non-nullable members could be added in constructors that asserts that those members are not null. Later the if branch checking could be implemented and the out contracts removed (though the former can have false positives unlike the latter).

a lot of compiler machinery that wouldn't be used anywhere else

This would also be useful to ensure a struct with both a defined constructor and destructor is actually constructed and not left as T.init. This would guarantee that such structs do not have to handle T.init in their destructor (which has a runtime cost as well as a design cost to detect the init value). Then if there is no initialization of the struct, the compiler can avoid calling the destructor or allowing it to be copied. (I also raised this here).

Patches, as usual, welcome!

Thanks, I haven't looked at the source yet. (I'm a bit put off by the compile time as I'm spoilt by dmd ;-)).

FeepingCreature commented 1 year ago

Initial compile time is high, but follow-up should be fine as the cache is warm.

./build.sh builds the compiler with optimization. This is usually the right move, but can be turned off with FAST= ./build.sh.

That's a good idea with the out-condition assert! I'll put it on the TODO list.

Note that I'm distracted ATM making a toy cpu in VHDL.