godotengine / godotengine/godot

[GDScript] local lambda function cannot call itself without boxing

Open
#112,669 11 comments 0 reactions 0 assignees View on GitHub
discussion topic:gdscript
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.