-
Notifications
You must be signed in to change notification settings - Fork 24
nullable static types documentation requested. #175
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,210 @@ | ||
| .. _doc_gdscript_nullable_types: | ||
|
|
||
| Nullable static types | ||
| ===================== | ||
|
|
||
| This guide builds on :ref:`doc_gdscript_static_typing`. In statically-typed | ||
| GDScript, a value is **non-nullable** by default: the type system flags ``null`` | ||
| as an illegal value for it. Writing a ``?`` after a type hint makes the value | ||
| **nullable**, opting it back into holding ``null``:: | ||
|
|
||
| var health: int? # May be an int or null; starts as null. | ||
| var player_name: String? = null | ||
|
|
||
| This is purely a static typing feature. It does not change how values are stored | ||
| at runtime — ``null`` is still ``Nil``. It changes what the analyzer allows and | ||
| what the virtual machine enforces. | ||
|
|
||
| .. note:: | ||
|
|
||
| How strictly "non-nullable" is enforced depends on the type. Built-in value | ||
| types (``int``, ``String``, ``Array``, and so on) are checked both when used | ||
| directly and at runtime. Enums are checked on transfer and at runtime. Object | ||
| references (``Node``, ``RefCounted``, script classes) keep GDScript's existing | ||
| behavior and may hold ``null`` regardless of the ``?``. See | ||
|
Comment on lines
+20
to
+24
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
rg -n -C 3 'Built-in value types|Array\?|Dictionary\?' tutorials/scripting/gdscript/gdscript_nullable_types.rst
rg -n -C 3 'reference type|value type|Array|Dictionary' --glob '*.rst' --glob '*.cpp' --glob '*.h' .Repository: Redot-Engine/redot-docs Length of output: 50380 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- nullable-types excerpt ---'
sed -n '1,175p' tutorials/scripting/gdscript/gdscript_nullable_types.rst
printf '%s\n' '--- basics terminology ---'
rg -n -C 4 'passed by reference|passed by value|value type|reference type|Array and Dictionary|Array.*Dictionary' tutorials/scripting/gdscript/gdscript_basics.rst
printf '%s\n' '--- focused nullable references ---'
rg -n -C 5 'Built-in value types|non-object|Array|Dictionary' tutorials/scripting/gdscript/gdscript_nullable_types.rstRepository: Redot-Engine/redot-docs Length of output: 12956 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- remaining nullable-types excerpt ---'
sed -n '175,225p' tutorials/scripting/gdscript/gdscript_nullable_types.rst
printf '%s\n' '--- deterministic terminology check ---'
python3 - <<'PY'
from pathlib import Path
basics = Path("tutorials/scripting/gdscript/gdscript_basics.rst").read_text()
nullable = Path("tutorials/scripting/gdscript/gdscript_nullable_types.rst").read_text()
needle = "Built-in types are stack-allocated. They are passed as values."
start = basics.index(needle)
end = basics.index("Basic built-in types", start)
print(basics[start:end].strip())
for term in ("Array", "Dictionary"):
print(f"{term}: nullable note={term in nullable[nullable.index('How strictly'):nullable.index('Syntax')]}; "
f"direct-use section={term + '?' in nullable[nullable.index('The UNSAFE_NULLABLE_ACCESS'):nullable.index('Runtime enforcement')]}")
PYRepository: Redot-Engine/redot-docs Length of output: 2591 Replace “built-in value types” with “non-object built-in types” in both sections. 🤖 Prompt for AI Agents |
||
| `Notes on objects and enums`_ below for the details. | ||
|
|
||
| Syntax | ||
| ------ | ||
|
|
||
| The ``?`` suffix is allowed anywhere a type hint is allowed: member variables, | ||
| local variables, function parameters, and return types:: | ||
|
|
||
| var member: int? | ||
|
|
||
| func f(v: int?) -> int?: | ||
| var local: Vector2? = null | ||
| return null | ||
|
|
||
| An uninitialized nullable variable defaults to ``null``, whereas a non-nullable | ||
| ``int`` would default to ``0``:: | ||
|
|
||
| var x: int? | ||
| print(x) # <null> | ||
|
|
||
| Compatible types | ||
| ---------------- | ||
|
|
||
| The ``?`` suffix works on nearly every type you can write as a hint: | ||
|
|
||
| +-----------------------+--------------------------------------------------------------------------------------------+ | ||
| | Category | Examples | | ||
| +=======================+============================================================================================+ | ||
| | Built-in value types | ``int?``, ``float?``, ``bool?``, ``String?``, ``StringName?``, ``Vector2?``, ``Color?``, … | | ||
| +-----------------------+--------------------------------------------------------------------------------------------+ | ||
| | Enums | ``State?`` (script enums), ``Vector2.Axis?``, ``Variant.Type?`` (native and nested enums) | | ||
| +-----------------------+--------------------------------------------------------------------------------------------+ | ||
| | Objects | ``Node?``, ``RefCounted?``, and script classes such as ``MyClass?`` | | ||
| +-----------------------+--------------------------------------------------------------------------------------------+ | ||
| | Collections | ``Array?``, ``Dictionary?`` | | ||
| +-----------------------+--------------------------------------------------------------------------------------------+ | ||
| | Typed collections | ``Array[int]?``, ``Dictionary[String, int]?`` — the *collection* is nullable | | ||
| +-----------------------+--------------------------------------------------------------------------------------------+ | ||
|
|
||
| The following forms are rejected because they are redundant or ambiguous: | ||
|
|
||
| - ``Variant?`` — :ref:`Variant <class_Variant>` already includes ``null``, so the | ||
| suffix is redundant and raises an error. | ||
| - ``void?`` — a function cannot return "maybe nothing"; this is a parser error. | ||
| - **Nullable element types**, such as ``Array[int?]`` or | ||
| ``Dictionary[String, int?]`` — only the container itself can be made nullable, | ||
| not its elements. Use ``Array[int]?`` (a nullable array of ints) instead. | ||
|
|
||
| Null-narrowing | ||
| -------------- | ||
|
|
||
| After you check a nullable value against ``null``, the analyzer *narrows* it to | ||
| its non-nullable type for the rest of the safe region, so you can use it without | ||
| a warning:: | ||
|
|
||
| func guard(v: int?) -> int: | ||
| if v == null: | ||
| return -1 | ||
| return v # Here v is known to be non-null (int). | ||
|
|
||
| func branch(v: int?) -> int: | ||
| if v != null: | ||
| return v + 1 # Narrowed inside the block. | ||
| return 0 | ||
|
|
||
| Narrowing is recognized for: | ||
|
|
||
| - **Early-return / early-exit guards:** ``if v == null: return``, then use ``v`` | ||
| afterwards. | ||
| - **Positive blocks:** the body of ``if v != null:``. | ||
| - **else branches:** the ``else`` of an ``if v == null:`` guard. | ||
| - **while conditions:** the body of ``while v != null:``. | ||
| - **Boolean and:** ``v != null and v > 5`` — the right-hand side sees ``v`` as | ||
| non-null. | ||
| - **or guards:** ``if a == null or b == null: return`` narrows both ``a`` and | ||
| ``b`` after the guard. | ||
| - **Reassignment with a non-null value** keeps the value narrowed:: | ||
|
|
||
| if v != null: | ||
| v = v + 1 # Still non-null. | ||
| return v | ||
|
|
||
| Narrowing is intentionally conservative. In the following cases the value stays | ||
| nullable and still produces the ``UNSAFE_NULLABLE_ACCESS`` warning: | ||
|
|
||
| - **Reassignment from a nullable source** re-widens the value; check it again:: | ||
|
|
||
| if v != null: | ||
| v = get_nullable() # v is nullable again. | ||
| return v + 1 # Warns. | ||
|
|
||
| - **break / continue guards** are not treated as narrowing:: | ||
|
|
||
| while true: | ||
| if v == null: | ||
| break | ||
| return v + 1 # Warns — break guard is not narrowed. | ||
|
|
||
| - **Non-definite guards** (a guard that does not unconditionally exit):: | ||
|
|
||
| if x == null: | ||
| if cond: | ||
| return -1 | ||
| return x + 1 # Warns — the guard might fall through. | ||
|
|
||
| - Combinations that do not actually prove non-null, such as | ||
| ``a == null and b == null`` or ``a != null or b != null``, do not narrow ``a`` | ||
| in the branch. | ||
|
|
||
| The UNSAFE_NULLABLE_ACCESS warning | ||
| ---------------------------------- | ||
|
|
||
| The analyzer emits ``UNSAFE_NULLABLE_ACCESS`` in two situations. | ||
|
|
||
| First, when a nullable **built-in value** (``int?``, ``String?``, ``Vector2?``, | ||
| ``Array?``, and so on) is used directly — in an operator, a subscript or property | ||
| access, or a ``for`` loop — without first narrowing it:: | ||
|
|
||
| var v: Vector2? = Vector2(3, 4) | ||
| print(v.x) # Warns: the value of type "Vector2?" may be null. | ||
|
|
||
| Second, when a nullable value of **any** type is transferred to a non-nullable | ||
| target: assigned to it, returned as it, or passed as an argument:: | ||
|
|
||
| var a: int? = 5 | ||
| var b: int = a # Warns: assigning nullable to non-nullable. | ||
| takes_int(a) # Warns: nullable argument to non-nullable parameter. | ||
|
|
||
| The direct-use check only applies to built-in value types. Nullable enums and | ||
| nullable object references are covered by the transfer check but not the | ||
| direct-use one — see `Notes on objects and enums`_ below. | ||
|
|
||
| The severity is configured by the | ||
| :ref:`debug/gdscript/warnings/unsafe_nullable_access<class_ProjectSettings_property_debug/gdscript/warnings/unsafe_nullable_access>` | ||
| project setting (default: warn). Set it to ``error`` to make unsafe nullable | ||
| access a hard compile error, or ``ignore`` to silence it. As with any GDScript | ||
| warning, you can suppress a single site with | ||
| ``@warning_ignore("unsafe_nullable_access")``. See :ref:`doc_gdscript_warning_system` | ||
| for details on the warning system. | ||
|
|
||
| Runtime enforcement | ||
| ------------------- | ||
|
|
||
| The warning is a static hint; for built-in value types and enums the runtime | ||
| independently rejects ``null`` reaching a non-nullable slot. Even if you ignore | ||
| the warning, the following fail at runtime:: | ||
|
|
||
| func bad(v: int?) -> int: | ||
| return v # If v is null: runtime error. | ||
|
|
||
| func use() -> void: | ||
| var value: int? = get_nullable() | ||
| print(value + 1) # If null: "Invalid operands 'Nil' and 'int'". | ||
|
|
||
| Typical runtime messages are: | ||
|
|
||
| - ``Trying to return value of type "Nil" from a function whose return type is "int".`` | ||
| - ``Invalid operands 'Nil' and 'int' in operator '+'.`` | ||
|
|
||
| Typed containers keep their own element checks. Returning a wrongly-typed array | ||
| from ``-> Array[int]?`` still errors with ``Trying to return an array of type | ||
| "Array" where expected return type is "Array[int]"``. | ||
|
|
||
| Notes on objects and enums | ||
| -------------------------- | ||
|
|
||
| Object references (``Node``, ``RefCounted``, script classes) keep GDScript's | ||
| existing nullability. Unlike built-in value types, an object type accepts ``null`` | ||
| at runtime whether or not it is marked with ``?``, so a null object reaching a | ||
| non-nullable object type is **not** a runtime error. | ||
|
|
||
| What ``?`` adds for objects is static tracking. ``Node?`` participates in | ||
| null-narrowing and raises ``UNSAFE_NULLABLE_ACCESS`` when the value is transferred | ||
| to a non-nullable target (assigned, returned, or passed as an argument). Accessing | ||
| a member or calling a method directly on a nullable object does *not* currently | ||
| raise the warning; only built-in value types are checked for direct use. | ||
|
Comment on lines
+188
to
+200
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
rg -n -C 4 'null instance|Attempt to call|UNSAFE_NULLABLE_ACCESS|Node\?' --glob '*.rst' --glob '*.cpp' --glob '*.h' .Repository: Redot-Engine/redot-docs Length of output: 7209 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- nullable-types.rst ---'
sed -n '130,215p' tutorials/scripting/gdscript/gdscript_nullable_types.rst
printf '%s\n' '--- null-instance documentation ---'
sed -n '245,265p' getting_started/first_2d_game/03.coding_the_player.rst
sed -n '2125,2135p' classes/class_node.rst
printf '%s\n' '--- repository scope ---'
git ls-files | rg '(^|/)(gdscript_nullable_types|gdscript|script|variant|object|node)' | head -120Repository: Redot-Engine/redot-docs Length of output: 12776 🌐 Web query:
💡 Result: In GDScript, calling a method on a null object or a previously freed instance results in a runtime error [1][2]. Because GDScript is dynamically typed and objects are nullable, attempting to access properties or call functions on a null reference will cause the engine to halt execution with an error such as "Invalid call. Nonexistent function" or "Cannot call method on a null value" [1][2]. To safely handle potentially null objects, you should implement the following patterns: 1. Use the Safe Call Operator (?.) The null-safe call operator (introduced in Godot 4) allows you to call a method or access a property only if the instance is not null [2]. If the instance is null, the expression short-circuits and returns null instead of throwing an error [3][2]. Example: Citations:
🌐 Web query:
💡 Result: In GDScript, calling a method on a null instance will trigger a runtime error [1]. It is essential to ensure an object is valid before attempting to access its members or methods [1]. When working with Godot Objects (which include Nodes), a variable may contain a reference to an object that has been freed from memory, even if the variable itself is not null [1]. Therefore, checking against null (e.g., if my_node!= null) is insufficient for verifying if an object still exists [1]. To safely check if an object instance is still valid, use the built-in function is_instance_valid [1][2]: if is_instance_valid(my_node): my_node.my_method Additionally, if you are unsure whether an object has a specific method, you can use the has_method function to prevent runtime errors [3][1]: if is_instance_valid(my_node) and my_node.has_method("my_method"): my_node.my_method For objects that inherit from RefCounted, the memory is managed automatically and the reference will typically become null when the object is no longer in use, but is_instance_valid remains a robust way to verify any Object-derived instance [3][1]. Citations:
Document runtime failures for direct nullable-object access. Although if node != null:
node.some_method()🤖 Prompt for AI Agents |
||
|
|
||
| Nullable enums behave the same way for direct use: an operator or subscript on a | ||
| nullable enum is not flagged, but transferring it to a non-nullable enum warns, | ||
| and — because enums are backed by integers — reaching a non-nullable enum with | ||
| ``null`` fails at runtime. | ||
|
|
||
| Freed objects keep GDScript's existing semantics. After ``node.free()``, comparing | ||
| ``node == null`` returns ``true``, while ``is_instance_valid(node)`` returns | ||
| ``false`` — the reference still points at the freed instance but compares equal to | ||
| ``null``. | ||
|
Comment on lines
+207
to
+210
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
rg -n -C 4 'is_instance_valid|freed instance|previously freed|== null' --glob '*.rst' --glob '*.cpp' --glob '*.h' .Repository: Redot-Engine/redot-docs Length of output: 25423 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- nullable tutorial context ---'
sed -n '180,215p' tutorials/scripting/gdscript/gdscript_nullable_types.rst
printf '%s\n' '--- Object validity documentation ---'
sed -n '40,55p;1038,1047p' classes/class_object.rst
printf '%s\n' '--- is_instance_valid documentation ---'
sed -n '6128,6136p' classes/class_@globalscope.rst
printf '%s\n' '--- repository references to freed-object equality ---'
rg -n -C 3 'freed objects|freed instance|become invalid|equal to ``null``|compare.*null|is_instance_valid' \
--glob '*.rst' --glob '*.md' --glob '*.cpp' --glob '*.h' .Repository: Redot-Engine/redot-docs Length of output: 19084 Use The tutorial contradicts the 🤖 Prompt for AI Agents |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: Redot-Engine/redot-docs
Length of output: 5549
🏁 Script executed:
Repository: Redot-Engine/redot-docs
Length of output: 23497
Update the nullability description in
gdscript_basics.rst.The statements at lines 695–698 conflict with the nullable static types documented in
gdscript_nullable_types.rst. Explain thatVariantalready includesnull, while statically typed built-in types require the?suffix to accept it.🤖 Prompt for AI Agents