godot icon indicating copy to clipboard operation
godot copied to clipboard

Variables in loops are not reset between iterations

Open dalexeev opened this issue 4 years ago • 6 comments

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

dalexeev avatar Dec 24 '21 17:12 dalexeev

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.

AThousandShips avatar Dec 25 '21 09:12 AThousandShips

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.

AThousandShips avatar Dec 25 '21 12:12 AThousandShips

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

jarneson avatar Dec 26 '21 16:12 jarneson

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

AThousandShips avatar Dec 27 '21 10:12 AThousandShips

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

NNesh avatar Jan 06 '22 04:01 NNesh

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

cdemirer avatar Jul 06 '22 05:07 cdemirer

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

dalexeev avatar Dec 01 '22 09:12 dalexeev

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

Chaosus avatar Dec 01 '22 09:12 Chaosus

I think it can be easily fixed

Let's reopen it so we don't forget to fix it? :)

dalexeev avatar Dec 02 '22 14:12 dalexeev