Variables in loops are not reset between iterations
Godot version
v4.0.dev.custom_build [b5c825af0]
System information
Kubuntu 21.10
Issue description
Variables declared in the body of the loop are not reset between iterations unless you explicitly initialize them (var a = 0). This is a regression to 3.x. Also, in the while loop, the uninitialized variable is for some reason equal to 4.
Steps to reproduce
v3.4.1.stable.official [aa1b95889]:
tool
extends EditorScript
func _run() -> void:
for _i in 5:
var a
print(a)
a = 1
print('---')
var i = 0
while i < 5:
var a
print(a)
a = 1
i += 1
Null
Null
Null
Null
Null
---
Null
Null
Null
Null
Null
v4.0.dev.custom_build [b5c825af0]:
@tool
extends EditorScript
func _run() -> void:
for _i in 5:
var a
print(a)
a = 1
print('---')
var i = 0
while i < 5:
var a
print(a)
a = 1
i += 1
null
1
1
1
1
---
4 # ?!
1
1
1
1
Minimal reproduction project
—
The uninitialized variable appears to somehow be connected to the loop condition, or possibly the loop variable _i as if you change the loop to for _i in 6 it starts out as 5
Adding further uninitialized variables show additional strangeness, for example:
@tool
extends EditorScript
func _run() -> void:
for _i in range(1, 6, 3):
var a
print(a)
a = 1
print('---')
var i = 0
while i < 2:
var a
var b
var c
print(a)
print(b)
print(c)
a = 1
b = 1
c = 1
i += 1
Gives
null
1
---
4
7
(1, 6, 3)
1
1
1
Where it seems that 4 is _i as it passed the loop check, 7 would be the value for _i as it failed the check, and (1, 6, 3) would be the range.
I'm not familiar with the internals of GDScript, and new here, but might be helpful for debugging.
Some further prodding and it seems that this occurs also for if statements and appears to be caused by variables not being cleared when entering scope, when the variable has a type specified with var a: int and similar it is default constructed, but if the type is builtin, for example var a: Object, or no type is specified the variable appears to simply be left as is on the stack from earlier scope.
Perhaps a related case, shadowing a variable in a for in uses the value from the outer scope:
extends Node3D
func loop(shadowed):
for shadowed in 5:
print(shadowed)
func _ready():
loop(5)
reults in :
5
5
5
5
5
The problem seems to arise because in 3.4 variable statements are assigned their default value on creation whereas in 4.0 variable statements in the compiler are assumed to be nil and does nothing for variables with unknown or non-builtin (Object) type
3.4 https://github.com/godotengine/godot/blob/b4d7d8766670e2e6380d7d76798f81f840ffe168/modules/gdscript/gdscript_parser.cpp#L2956-L2970
4.0 https://github.com/godotengine/godot/blob/28174d531b7128f0281fc2b88da2f4962fd3513e/modules/gdscript/gdscript_compiler.cpp#L1809-L1844
If add these lines to gdscript_compiler:
...
} else {
// The `else` branch is for objects, in such case we leave it as `null`.
codegen.generator->write_construct(local, Variant::NIL, Vector<GDScriptCodeGenerator::Address>());
}
} else {
codegen.generator->write_construct(local, Variant::NIL, Vector<GDScriptCodeGenerator::Address>());
}
You will get the follow results (as in 3.4): null null null null null --- null null null null null
But it doesn't fix the issue with for. I think, there is something wrong with codegen for the for node
Perhaps a related case, shadowing a variable in a for in uses the value from the outer scope:
But it doesn't fix the issue with for. I think, there is something wrong with codegen for the for node
There was a missing error check. (#62701 fixes that)
codegen.generator->write_construct(local, Variant::NIL, Vector<GDScriptCodeGenerator::Address>());
The fix seems good, I wonder if it's desired. The reference seems to suggest so: https://docs.godotengine.org/en/latest/tutorials/scripting/gdscript/gdscript_basics.html#variables
var a # Data type is 'null' by default.
@vnen
I tested #69303. For int, Vector2, RefCounted the bug is fixed, but for String it remains.
@tool
extends EditorScript
func _run() -> void:
for _i in 5:
var a: String
print(a)
a = "abc"
print('---')
var i = 0
while i < 5:
var a: String
print(a)
a = "abc"
i += 1
abc
abc
abc
abc
---
abc
abc
abc
abc
I tested https://github.com/godotengine/godot/pull/69303. For int, Vector2, RefCounted the bug is fixed, but for String it remains.
Thanks for the test, I think it can be easily fixed
I think it can be easily fixed
Let's reopen it so we don't forget to fix it? :)