haxe
haxe copied to clipboard
Definite assignment analysis for if-cases and while-loops fail to conclude that value is initialized before usage
The compiler is missing or is not precise enough in its definite assignment analysis of variables that are definitely assigned before a point of usage of the variable. This leads to the compiler giving an error for programs that are otherwise correct.
In both below failing cases, there is no way of the program to reach the trace()-call without the variable value being initialized beforehand:
class Test {
static function main() {
var value: Int;
if (true) {
value = 3;
}
trace(value); // ERROR: Local variable value used without being initialized
}
}
class Test {
static function main() {
var value: Int;
while (true) {
var random = Math.round(Math.random() * 10);
if (random != 3 {
value = random;
break;
}
}
trace(value); // ERROR: Local variable value used without being initialized
}
}
This is something that is handled in Clang (link), but not in Rust (link).
The first case is a matter of detecting if (true) in particular, which should be easy.
The second one requires merging all loop exits, i.e. all breaks. This will also require some stack handling for nested loops, but shouldn't be too tricky either.
Interestingly, the analyzer doesn't const-propagate the == case either:
class Main {
@:analyzer(full_debug)
static function main() {
var value:Int = 0;
while (true) {
var random = Math.round(Math.random() * 10);
if (random == 3) {
value = random;
break;
}
}
trace(value);
}
}
It should be able to generate this as trace(3). But that might be somewhat advanced and basically require treating == like =.
Related/duplicate: https://github.com/HaxeFoundation/haxe/issues/8054
That one seems much harder because it requires some sort of post-condition handling, and real code-flow analysis. The analyzer could probably figure it out but the var init checker won't be able to.
I think I would be satisfied with some way to disable the uninit var check for the variable.
IIRC my issue was using it with inlined constructor call, and initializing it to null prevented inlining. That can be solved by making the inlined constructor some empty dummy, and adding some init inlined function to actually initialize it, so it's not a big issue.
The first case is a matter of detecting
if (true)in particular, which should be easy.
if (...) {value = ...;} else {value = ...;} is another case as well, similar to the while (true) one. :slightly_smiling_face:
The first case is a matter of detecting
if (true)in particular, which should be easy.
if (...) {value = ...;} else {value = ...;}is another case as well, similar to thewhile (true)one. 🙂
That one is already handled though.