godotengine / godotengine/godot
[GDScript] local lambda function cannot call itself without boxing
- Dominant language
- C++
- Stars
- 117k
- Forks
- 26.8k
- PR merge metrics
- PR metrics pending
Description
### Tested versions
v4.5.1.stable.mono.official [f62fdbde1]
### System information
Godot v4.5.1.stable.mono - Windows 10 (build 19044) - Multi-window, 4 monitors - Vulkan (Forward+) - dedicated NVIDIA GeForce RTX 4060 Ti (NVIDIA; 32.0.15.8097) - AMD Ryzen 9 7950X 16-Core Processor (32 threads) - 63.05 GiB memory
### Issue description
A invoking a local gdscript lambda function within itself will cause the following issue.
```gdscript
func _raw_callable() -> void:
var visit_chidren : Callable;
visit_chidren = func (node: Node) -> void:
for child in node.get_children():
print(child.name);
visit_chidren.call(child); # Attempt to call function 'null::null (Callable)' on a null instance.
visit_chidren.call(self);
```
To make this behavior work you need to manually allocate this Callable with RefCounted:
```gdscript
func _boxed_callable() -> void:
var visit_chidren := BoxedCallable.new();
visit_chidren.callable = func (node: Node) -> void:
for child in node.get_children():
print(child.name);
visit_chidren.callable.call(child);
visit_chidren.callable.call(self);
class BoxedCallable extends RefCounted:
var callable : Callable;
```
### Steps to reproduce
https://github.com/user-attachments/assets/33e354a6-07db-45f4-832b-932c38015701
1. Create a new project and create a scene with arbitrary structure.
2. Attach the script down below to the root node.
3. Uncomment the `_raw_callable` inside `_ready`.
4. Run the scene, and inspect the `Attempt to call function 'null::null (Callable)' on a null instance.` error.
5. Comment the `_raw_callable` and uncomment the `_boxed_callable`.
6. Run the scene, and inspect the function runs correctly.
```gdscript
extends Node
func _ready() -> void:
# _raw_callable();
# _boxed_callable();
return;
func _raw_callable() -> void:
var visit_chidren : Callable;
visit_chidren = func (node: Node) -> void:
for child in node.get_children():
print(child.name);
visit_chidren.call(child); # Attempt to call function 'null::null (Callable)' on a null instance.
visit_chidren.call(self);
func _boxed_callable() -> void:
var visit_chidren := BoxedCallable.new();
visit_chidren.callable = func (node: Node) -> void:
for child in node.get_children():
print(child.name);
visit_chidren.callable.call(child);
visit_chidren.callable.call(self);
class BoxedCallable extends RefCounted:
var callable : Callable;
```
### Minimal reproduction project (MRP)
Already covered in Steps to reproduce
Contributor guide
Research direction
Start with the minimal reproduction and compare the `_raw_callable` and `_boxed_callable` entry points described in the issue. Run the scene with each version and trace why the recursive local Callable becomes null without boxing. Done means the raw recursive lambda runs correctly without the `null::null (Callable)` error.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100