Typed Arrays with a named enum type cannot be set using `Object.set`
Tested versions
- reproducible in v4.4.dev2.mono.official [97ef3c837], v4.3.stable.mono.official [77dcf97d8] and v4.2.2.stable.mono.official [15073afe3]
System information
Godot v4.3.stable.mono - Windows 10.0.22631 - GLES3 (Compatibility) - NVIDIA GeForce RTX 4070 Ti SUPER (NVIDIA; 32.0.15.6109) - AMD Ryzen 7 7800X3D 8-Core Processor (16 Threads)
Issue description
If you declare an array with a named enum type, the Object.set method will not set that array's value, but should.
Please see code in steps to reproduce or MRP for more details.
Steps to reproduce
Use the MRP, or create a single scene project with this script attached to a single Node2D and run it:
extends Node2D
enum THING {FOO, BAR, BAZ}
var my_typed_list: Array[THING] = []
func _ready() -> void:
self.set("my_typed_list", [THING.FOO, THING.BAR])
print("(Typed array) Should have 2 elements but doesn't: ", my_typed_list, " <--------- Incorrect")
my_typed_list = [THING.FOO, THING.BAR]
print("(Typed array) Should have 2 elements and does: ", my_typed_list, " <--------- Correct ")
Expected behavior: The array should print with 2 elements. Actual behavior: The array prints with 0 elements.
Minimal reproduction project (MRP)
This is likely part of a broader limitation of typed arrays as you can't assign a non-typed array to a typed one, see:
- https://github.com/godotengine/godot/issues/83511
- https://github.com/godotengine/godot-proposals/discussions/7364
Thanks. You're probably right that this is an expression of a broader issue. To add some more clarity here, the issue I am raising here is specific to Object.set as, for example, I am able to directly assign the untyped literal array (with =): eg, if you add these two lines to my reproduction code above:
my_typed_list = [THING.FOO, THING.BAR]
print("(Typed array) Should have 2 elements and does: ", my_typed_list, " <--------- Correct ")
It is unintuitive that Object.set would not work when direct assignment does. If this behavior is intended (even if temporarily) it should likely be documented, or raise a warning at runtime.
Array[^1] literals (the [] syntax) create untyped arrays in untyped contexts such as Object.set(). You can use type casting to create a typed array (note that the as operator only works on array literals, not arbitrary expressions).
self.set("my_typed_list", [THING.FOO, THING.BAR] as Array[THING])
# ^^^^^^^^^^^^^^^
- See also #71336.
Another problem is that Object.set() doesn't tell you about assignment errors:
Assigns value to the given property. If the property does not exist or the given value's type doesn't match, nothing happens.
I think this is because _set() is a multi-level call (called through classes in the inheritance hierarchy until the property is found). In my opinion, this is bad, since it can lead to difficult-to-debug errors quite often.
[^1]: And dictionary since 4.4 dev 2.
It may also be helpful to add a note about such arrays (and in 4.4 dicts) to the Saving Games example which is where I first ran into this issue.
@dalexeev Appreciate the input, and your workaround has led me to a similar workaround. Given that, here's some more context for this issue.
When adapting the saving games guide I linked in my previous comment, I tried the workaround you suggested in your previous comment.
For the arrays in question, turns out this does not work:
new_object.set(i, node_data[i] as Array[Effect.EFFECT])
but, this does work:
var placeholder: Array[Effect.EFFECT]
for item in node_data[i]:
placeholder.append(item as Effect.EFFECT)
new_object.set(i, placeholder)
I'm not yet sure why this is, because I can use the original example above to show that when casting the full array, if the elements inside are integers, they are also cast to enum.
self.set("my_typed_list", [0 as int, 1 as int] as Array[THING]) # <--- ends up correct
For the arrays in question, turns out this does not work:
new_object.set(i, node_data[i] as Array[Effect.EFFECT])
@brevven This only works for literals, I wrote about this above:
note that the
asoperator only works on array literals, not arbitrary expressions
For arbitrary expressions, you can use the typed array constructor:
Array(other_array, TYPE_INT, &"", null) # Array[int]
Array(other_array, TYPE_OBJECT, &"Node", null) # Array[Node]
Array(other_array, TYPE_OBJECT, &"Node", MyNode) # Array[MyNode]
But the constructor is primarily intended for dynamically creating typed arrays (when you don't know the element type in advance). This is ugly and verbose, we'd like something like:
Array[int](other_array)
Array[Node](other_array)
Array[MyNode](other_array)
- See #71336.
Thanks, somehow skipped right over that clarification, my apologies.