Skip to content

Add @override, @nodepath, and @not_null annotations for early error detection #12310

@ajafov98

Description

@ajafov98

Describe the project you are working on

I'm working on my first serious game project with Godot - a 2D platformer with some basic inventory and quest systems. As the project has grown larger, I've encountered several types of errors that are difficult to trace and debug because they only manifest at runtime under specific conditions.

Describe the problem or limitation you are having in your project

I've encounter three major categories of problems:

  1. Silent function override failures: Typos in overridden function names (like _proccess instead of _process) lead to hours of debugging. The function never gets called but the engine doesn't warn about this.

  2. Invalid node paths: References to nodes in the scene tree frequently break during refactoring or scene restructuring. These issues only become apparent when that specific feature is used, often leading to crashes in edge cases that are difficult to test systematically.

  3. Null reference errors: "Attempt to call function on null instance" is our most common runtime error. These frequently occur because of assumptions about initialization order or node availability that turn out to be incorrect.

All three issues share a common trait: they're discovered very late in the development process, typically during playtesting or after deployment, making them costly to fix.

Describe the feature / enhancement and how it helps to overcome the problem or limitation

I propose adding three targeted annotations to GDScript that would help catch these common errors much earlier in the development process:

  1. @override Annotation: Verifies at parse time that a function actually exists in a parent class to override.
  2. @nodepath Annotation: Validates at scene load time that a node path exists (and optionally is of a specific type).
  3. @not_null Annotation: Enforces that variables are assigned non-null values during initialization.

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

1. @override Annotation

The @override annotation would verify at parse time that the function actually exists in a parent class.

extends Node2D

# This would cause a compiler error: "function '_proccess' does not override any function in parent classes"
@override 
func _proccess(delta: float) -> void:  # Typo in '_process'
    position.x += 10 * delta

# This would pass validation
@override
func _process(delta: float) -> void:
    position.x += 10 * delta

2. @nodepath Annotation

The @nodepath annotation would validate at scene load time that the path exists.

# This would validate that this path exists
@nodepath
@export var player_sprite_path: NodePath

# With path directly specified
@nodepath
@export var camera_path := "Camera"

func _ready() -> void:
    var sprite = get_node(player_sprite_path)
    sprite.modulate = Color.RED

As an optional enhancement, @nodepath could also validate that the node is of an expected type:

# This would validate that this path exists AND is a Sprite2D
@nodepath(Sprite2D)
@export var player_sprite_path: NodePath

3. @not_null Annotation

The @not_null annotation would enforce that a variable is assigned a non-null value during initialization.

# Must be initialized in _ready or _init
@not_null var weapon: Weapon

# Must be initialized in constructor call
@not_null var player_name: String

func _init(p_name: String) -> void:
    player_name = p_name  # Required, would error if missing
    
func _ready() -> void:
    weapon = $WeaponSlot.get_child(0)
    # If weapon is still null here, the engine would throw an error

If this enhancement will not be used often, can it be worked around with a few lines of script?

These issues cannot be effectively worked around in script:

  1. For @override, I don't think there's any way to check if a function really overrides a function in parent classes. Only a visual tip in the lines which shows it's overriden.

  2. For @nodepath, one can add runtime checks, but this is both boilerplate code and still only catches errors during runtime:

func _ready():
    if not has_node(player_sprite_path):
        push_error("Invalid path: " + str(player_sprite_path))
  1. For @not_null, one could add assertions, but these still only fire at runtime:
func _ready():
    assert(weapon != null, "Weapon must be initialized!")

The main benefit of these annotations is catching errors earlier in the development process (parse/load time rather than runtime), which no script workaround can provide.

Is there a reason why this should be core and not an add-on in the asset library?

From what I understand so far about Godot, these features need to work with the parser and compiler, which I don't think add-ons can modify because they all require deeper integration with the engine. Generally useful features that would benefit a large percentage of Godot projects by catching common bugs early, making them appropriate for core inclusion.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions