Skip to content

Add nullable static types to GDScript. - #1335

Open
mcdubhghlas wants to merge 3 commits into
Redot-Engine:masterfrom
mcdubhghlas:nullsies
Open

Add nullable static types to GDScript.#1335
mcdubhghlas wants to merge 3 commits into
Redot-Engine:masterfrom
mcdubhghlas:nullsies

Conversation

@mcdubhghlas

@mcdubhghlas mcdubhghlas commented Jul 29, 2026

Copy link
Copy Markdown
Member

Title.

Summary by CodeRabbit

  • New Features
    • Added nullable type syntax (?) for values, containers, enums, callables, and return types.
    • Improved null-safety with non-null narrowing in conditional and boolean expressions.
    • Nullable variables now remain null when not explicitly initialized.
  • Bug Fixes
    • Added safety checks for nullable access, operations, assignments, arguments, and returns.
    • Improved nullable handling for typed containers and enum values.
  • Documentation
    • Added a project setting for unsafe nullable access warnings.
  • Tests
    • Added comprehensive parser, analyzer, and runtime coverage for nullable behavior.

@mcdubhghlas
mcdubhghlas requested review from a team July 29, 2026 22:24
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 33cdf6b0-0a22-4d06-b57c-b6529cf7e8fe

📥 Commits

Reviewing files that changed from the base of the PR and between 4e10d54 and 78bcf7a.

⛔ Files ignored due to path filters (42)
  • modules/gdscript/tests/scripts/analyzer/errors/nullable_array_element.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/analyzer/errors/nullable_dictionary_key_element.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/analyzer/errors/nullable_dictionary_value_element.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/analyzer/errors/nullable_variant.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/analyzer/warnings/nullable_and_false_no_narrow.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/analyzer/warnings/nullable_break_guard_no_narrow.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/analyzer/warnings/nullable_compound_assignment.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/analyzer/warnings/nullable_continue_guard_no_narrow.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/analyzer/warnings/nullable_narrowing_limits.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/analyzer/warnings/nullable_or_true_no_narrow.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/analyzer/warnings/nullable_unsafe_operations.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/analyzer/warnings/nullable_unsafe_transfer.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/analyzer/warnings/unsafe_nullable_access.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/parser/errors/nullable_void_return.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/errors/nullable_enum_null_transfer.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/errors/nullable_null_reaches_argument.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/errors/nullable_null_reaches_typed_local.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/errors/nullable_null_use.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/errors/nullable_return_null_from_nonnull.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/errors/nullable_typed_array_assign_mismatch.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/errors/nullable_typed_array_reassign_mismatch.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/errors/nullable_typed_dictionary_assign_mismatch.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/errors/nullable_typed_dictionary_reassign_mismatch.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/errors/nullable_typed_dictionary_return_mismatch.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/errors/nullable_typed_return_mismatch.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/features/nullable_empty_callable.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/features/nullable_enum_raw_integer.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/features/nullable_enum_transfer.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/features/nullable_freed_object.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/features/nullable_multivar_and.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/features/nullable_multivar_or_guard.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/features/nullable_narrowing.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/features/nullable_narrowing_else.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/features/nullable_narrowing_mutation.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/features/nullable_nested_enum.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/features/nullable_not_compound_or.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/features/nullable_renarrow_after_reassign.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/features/nullable_self_reference_assignment.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/features/nullable_typed_container_returns.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/features/nullable_types.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/features/nullable_variant_null_return.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/features/nullable_while_body_mutation.out is excluded by !**/*.out
📒 Files selected for processing (54)
  • doc/classes/ProjectSettings.xml
  • modules/gdscript/gdscript_analyzer.cpp
  • modules/gdscript/gdscript_analyzer.h
  • modules/gdscript/gdscript_byte_codegen.cpp
  • modules/gdscript/gdscript_compiler.cpp
  • modules/gdscript/gdscript_disassembler.cpp
  • modules/gdscript/gdscript_function.h
  • modules/gdscript/gdscript_parser.cpp
  • modules/gdscript/gdscript_parser.h
  • modules/gdscript/gdscript_vm.cpp
  • modules/gdscript/gdscript_warning.cpp
  • modules/gdscript/gdscript_warning.h
  • modules/gdscript/tests/scripts/analyzer/errors/nullable_array_element.gd
  • modules/gdscript/tests/scripts/analyzer/errors/nullable_dictionary_key_element.gd
  • modules/gdscript/tests/scripts/analyzer/errors/nullable_dictionary_value_element.gd
  • modules/gdscript/tests/scripts/analyzer/errors/nullable_variant.gd
  • modules/gdscript/tests/scripts/analyzer/warnings/nullable_and_false_no_narrow.gd
  • modules/gdscript/tests/scripts/analyzer/warnings/nullable_break_guard_no_narrow.gd
  • modules/gdscript/tests/scripts/analyzer/warnings/nullable_compound_assignment.gd
  • modules/gdscript/tests/scripts/analyzer/warnings/nullable_continue_guard_no_narrow.gd
  • modules/gdscript/tests/scripts/analyzer/warnings/nullable_narrowing_limits.gd
  • modules/gdscript/tests/scripts/analyzer/warnings/nullable_or_true_no_narrow.gd
  • modules/gdscript/tests/scripts/analyzer/warnings/nullable_unsafe_operations.gd
  • modules/gdscript/tests/scripts/analyzer/warnings/nullable_unsafe_transfer.gd
  • modules/gdscript/tests/scripts/analyzer/warnings/unsafe_nullable_access.gd
  • modules/gdscript/tests/scripts/parser/errors/nullable_void_return.gd
  • modules/gdscript/tests/scripts/runtime/errors/nullable_enum_null_transfer.gd
  • modules/gdscript/tests/scripts/runtime/errors/nullable_null_reaches_argument.gd
  • modules/gdscript/tests/scripts/runtime/errors/nullable_null_reaches_typed_local.gd
  • modules/gdscript/tests/scripts/runtime/errors/nullable_null_use.gd
  • modules/gdscript/tests/scripts/runtime/errors/nullable_return_null_from_nonnull.gd
  • modules/gdscript/tests/scripts/runtime/errors/nullable_typed_array_assign_mismatch.gd
  • modules/gdscript/tests/scripts/runtime/errors/nullable_typed_array_reassign_mismatch.gd
  • modules/gdscript/tests/scripts/runtime/errors/nullable_typed_dictionary_assign_mismatch.gd
  • modules/gdscript/tests/scripts/runtime/errors/nullable_typed_dictionary_reassign_mismatch.gd
  • modules/gdscript/tests/scripts/runtime/errors/nullable_typed_dictionary_return_mismatch.gd
  • modules/gdscript/tests/scripts/runtime/errors/nullable_typed_return_mismatch.gd
  • modules/gdscript/tests/scripts/runtime/features/nullable_empty_callable.gd
  • modules/gdscript/tests/scripts/runtime/features/nullable_enum_raw_integer.gd
  • modules/gdscript/tests/scripts/runtime/features/nullable_enum_transfer.gd
  • modules/gdscript/tests/scripts/runtime/features/nullable_freed_object.gd
  • modules/gdscript/tests/scripts/runtime/features/nullable_multivar_and.gd
  • modules/gdscript/tests/scripts/runtime/features/nullable_multivar_or_guard.gd
  • modules/gdscript/tests/scripts/runtime/features/nullable_narrowing.gd
  • modules/gdscript/tests/scripts/runtime/features/nullable_narrowing_else.gd
  • modules/gdscript/tests/scripts/runtime/features/nullable_narrowing_mutation.gd
  • modules/gdscript/tests/scripts/runtime/features/nullable_nested_enum.gd
  • modules/gdscript/tests/scripts/runtime/features/nullable_not_compound_or.gd
  • modules/gdscript/tests/scripts/runtime/features/nullable_renarrow_after_reassign.gd
  • modules/gdscript/tests/scripts/runtime/features/nullable_self_reference_assignment.gd
  • modules/gdscript/tests/scripts/runtime/features/nullable_typed_container_returns.gd
  • modules/gdscript/tests/scripts/runtime/features/nullable_types.gd
  • modules/gdscript/tests/scripts/runtime/features/nullable_variant_null_return.gd
  • modules/gdscript/tests/scripts/runtime/features/nullable_while_body_mutation.gd
🚧 Files skipped from review as they are similar to previous changes (51)
  • modules/gdscript/tests/scripts/analyzer/errors/nullable_dictionary_key_element.gd
  • modules/gdscript/tests/scripts/analyzer/warnings/unsafe_nullable_access.gd
  • modules/gdscript/gdscript_warning.h
  • modules/gdscript/tests/scripts/runtime/errors/nullable_enum_null_transfer.gd
  • modules/gdscript/tests/scripts/runtime/errors/nullable_typed_array_reassign_mismatch.gd
  • modules/gdscript/tests/scripts/analyzer/warnings/nullable_unsafe_operations.gd
  • doc/classes/ProjectSettings.xml
  • modules/gdscript/tests/scripts/analyzer/errors/nullable_dictionary_value_element.gd
  • modules/gdscript/tests/scripts/analyzer/warnings/nullable_and_false_no_narrow.gd
  • modules/gdscript/tests/scripts/analyzer/warnings/nullable_break_guard_no_narrow.gd
  • modules/gdscript/tests/scripts/analyzer/warnings/nullable_or_true_no_narrow.gd
  • modules/gdscript/tests/scripts/runtime/features/nullable_while_body_mutation.gd
  • modules/gdscript/tests/scripts/runtime/features/nullable_narrowing_else.gd
  • modules/gdscript/tests/scripts/runtime/features/nullable_multivar_and.gd
  • modules/gdscript/tests/scripts/analyzer/errors/nullable_variant.gd
  • modules/gdscript/tests/scripts/runtime/errors/nullable_typed_return_mismatch.gd
  • modules/gdscript/tests/scripts/runtime/errors/nullable_null_use.gd
  • modules/gdscript/tests/scripts/runtime/errors/nullable_typed_array_assign_mismatch.gd
  • modules/gdscript/gdscript_warning.cpp
  • modules/gdscript/gdscript_analyzer.h
  • modules/gdscript/tests/scripts/runtime/features/nullable_nested_enum.gd
  • modules/gdscript/tests/scripts/analyzer/errors/nullable_array_element.gd
  • modules/gdscript/tests/scripts/runtime/features/nullable_enum_transfer.gd
  • modules/gdscript/tests/scripts/analyzer/warnings/nullable_compound_assignment.gd
  • modules/gdscript/tests/scripts/analyzer/warnings/nullable_continue_guard_no_narrow.gd
  • modules/gdscript/gdscript_parser.h
  • modules/gdscript/tests/scripts/analyzer/warnings/nullable_narrowing_limits.gd
  • modules/gdscript/gdscript_disassembler.cpp
  • modules/gdscript/tests/scripts/runtime/features/nullable_enum_raw_integer.gd
  • modules/gdscript/tests/scripts/runtime/features/nullable_typed_container_returns.gd
  • modules/gdscript/tests/scripts/parser/errors/nullable_void_return.gd
  • modules/gdscript/tests/scripts/runtime/features/nullable_not_compound_or.gd
  • modules/gdscript/tests/scripts/runtime/features/nullable_self_reference_assignment.gd
  • modules/gdscript/gdscript_function.h
  • modules/gdscript/tests/scripts/runtime/features/nullable_types.gd
  • modules/gdscript/tests/scripts/runtime/features/nullable_narrowing_mutation.gd
  • modules/gdscript/tests/scripts/runtime/features/nullable_variant_null_return.gd
  • modules/gdscript/gdscript_compiler.cpp
  • modules/gdscript/tests/scripts/runtime/errors/nullable_typed_dictionary_return_mismatch.gd
  • modules/gdscript/tests/scripts/runtime/features/nullable_narrowing.gd
  • modules/gdscript/tests/scripts/runtime/features/nullable_renarrow_after_reassign.gd
  • modules/gdscript/gdscript_vm.cpp
  • modules/gdscript/gdscript_byte_codegen.cpp
  • modules/gdscript/tests/scripts/runtime/features/nullable_multivar_or_guard.gd
  • modules/gdscript/tests/scripts/runtime/errors/nullable_typed_dictionary_reassign_mismatch.gd
  • modules/gdscript/tests/scripts/runtime/errors/nullable_null_reaches_argument.gd
  • modules/gdscript/tests/scripts/runtime/features/nullable_freed_object.gd
  • modules/gdscript/tests/scripts/runtime/errors/nullable_null_reaches_typed_local.gd
  • modules/gdscript/tests/scripts/runtime/errors/nullable_typed_dictionary_assign_mismatch.gd
  • modules/gdscript/gdscript_analyzer.cpp
  • modules/gdscript/tests/scripts/runtime/errors/nullable_return_null_from_nonnull.gd

Walkthrough

Nullable type syntax, flow-sensitive non-null narrowing, nullable-aware bytecode execution, unsafe-access warnings, and analyzer and runtime tests are added across GDScript.

Changes

Nullable GDScript support

Layer / File(s) Summary
Nullable type contracts and parsing
modules/gdscript/gdscript_parser.*, modules/gdscript/gdscript_function.h, modules/gdscript/gdscript_analyzer.cpp
Nullable markers are parsed and propagated through parser and runtime data types. Nullable container elements and Variant? are rejected. Nullable built-ins and enums accept nil.
Analyzer narrowing and nullable diagnostics
modules/gdscript/gdscript_analyzer.*, modules/gdscript/gdscript_warning.*
Branches and short-circuit expressions narrow nullable identifiers. Assignments invalidate narrowing. Unsafe nullable operations and transfers emit UNSAFE_NULLABLE_ACCESS.
Nullable bytecode generation and compilation
modules/gdscript/gdscript_compiler.cpp, modules/gdscript/gdscript_byte_codegen.cpp, modules/gdscript/gdscript_disassembler.cpp
Nullable metadata is propagated into bytecode. Typed assignment and return opcodes encode nullability. Nullable values use the regular call path and nullable members skip synthesized defaults.
VM nullable execution
modules/gdscript/gdscript_vm.cpp
Typed assignment and return opcodes accept NIL for nullable targets and retain strict validation for other values.
Analyzer and parser test coverage
modules/gdscript/tests/scripts/analyzer/**, modules/gdscript/tests/scripts/parser/**
Fixtures cover nullable syntax restrictions, narrowing, unsafe operations, and nullable transfers.
Runtime nullable behavior test coverage
modules/gdscript/tests/scripts/runtime/**
Fixtures cover nullable values, enums, objects, callables, narrowing mutations, typed containers, returns, and invalid transfers.
Documentation
doc/classes/ProjectSettings.xml
The debug/gdscript/warnings/unsafe_nullable_access setting is documented.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant GDScriptParser
  participant GDScriptAnalyzer
  participant GDScriptCompiler
  participant GDScriptVM
  GDScriptParser->>GDScriptAnalyzer: provide nullable type information
  GDScriptAnalyzer->>GDScriptAnalyzer: narrow identifiers and validate nullable access
  GDScriptAnalyzer->>GDScriptCompiler: provide checked nullable types
  GDScriptCompiler->>GDScriptVM: emit nullable assignment and return metadata
  GDScriptVM->>GDScriptVM: accept NIL for nullable targets or enforce validation
Loading

Suggested labels: enhancement, topic:core, topic:gdscript

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the pull request's primary change: adding nullable static types to GDScript.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@modules/gdscript/gdscript_analyzer.cpp`:
- Around line 970-975: Update the nested-enum resolution paths for
Variant.SomeEnum and built-in nested enums so they pass through the existing
nullable finalization block guarded by p_type->is_nullable, allowing annotations
such as Vector2.Axis? to set result.is_nullable. Preserve the current Variant
rejection behavior, and add a regression test covering nullable nested enums.
- Around line 2090-2098: Update the IF handling in the non-null narrowing logic
to use the absence of an exiting false branch rather than requiring false_block
to be null. Replace the false_block-null condition in the true_exits branch with
the corresponding !false_exits check, preserving the existing symmetric branch
and retaining the non-null fact after an else whose path does not exit.

In `@modules/gdscript/gdscript_function.h`:
- Around line 85-87: Complete nullable handling across all listed sites: in
modules/gdscript/gdscript_function.h:85-87, accept nullable Variant::NIL before
switching on GDScriptDataType::kind; in
modules/gdscript/gdscript_analyzer.cpp:4971-4975 and :5098-5102, emit
nullable-access warnings for non-meta object/class bases in attribute and index
access; in :6386-6398, reject nullable sources assigned to non-nullable targets
before object polymorphism checks while preserving nil acceptance for nullable
targets. Add regressions covering Node? and script-class assignment,
parameter/return transfers, null values, and member access.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: eb70ba44-2981-44d8-bd62-91969edb0ad2

📥 Commits

Reviewing files that changed from the base of the PR and between 5264617 and b11c8ea.

⛔ Files ignored due to path filters (40)
  • modules/gdscript/tests/scripts/analyzer/errors/nullable_array_element.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/analyzer/errors/nullable_dictionary_key_element.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/analyzer/errors/nullable_dictionary_value_element.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/analyzer/errors/nullable_variant.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/analyzer/warnings/nullable_and_false_no_narrow.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/analyzer/warnings/nullable_break_guard_no_narrow.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/analyzer/warnings/nullable_compound_assignment.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/analyzer/warnings/nullable_continue_guard_no_narrow.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/analyzer/warnings/nullable_narrowing_limits.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/analyzer/warnings/nullable_or_true_no_narrow.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/analyzer/warnings/nullable_unsafe_operations.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/analyzer/warnings/nullable_unsafe_transfer.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/analyzer/warnings/unsafe_nullable_access.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/parser/errors/nullable_void_return.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/errors/nullable_enum_null_transfer.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/errors/nullable_null_reaches_argument.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/errors/nullable_null_reaches_typed_local.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/errors/nullable_null_use.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/errors/nullable_return_null_from_nonnull.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/errors/nullable_typed_array_assign_mismatch.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/errors/nullable_typed_array_reassign_mismatch.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/errors/nullable_typed_dictionary_assign_mismatch.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/errors/nullable_typed_dictionary_reassign_mismatch.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/errors/nullable_typed_dictionary_return_mismatch.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/errors/nullable_typed_return_mismatch.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/features/nullable_empty_callable.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/features/nullable_enum_raw_integer.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/features/nullable_enum_transfer.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/features/nullable_freed_object.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/features/nullable_multivar_and.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/features/nullable_multivar_or_guard.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/features/nullable_narrowing.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/features/nullable_narrowing_mutation.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/features/nullable_not_compound_or.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/features/nullable_renarrow_after_reassign.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/features/nullable_self_reference_assignment.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/features/nullable_typed_container_returns.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/features/nullable_types.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/features/nullable_variant_null_return.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/features/nullable_while_body_mutation.out is excluded by !**/*.out
📒 Files selected for processing (51)
  • modules/gdscript/gdscript_analyzer.cpp
  • modules/gdscript/gdscript_analyzer.h
  • modules/gdscript/gdscript_byte_codegen.cpp
  • modules/gdscript/gdscript_compiler.cpp
  • modules/gdscript/gdscript_disassembler.cpp
  • modules/gdscript/gdscript_function.h
  • modules/gdscript/gdscript_parser.cpp
  • modules/gdscript/gdscript_parser.h
  • modules/gdscript/gdscript_vm.cpp
  • modules/gdscript/gdscript_warning.cpp
  • modules/gdscript/gdscript_warning.h
  • modules/gdscript/tests/scripts/analyzer/errors/nullable_array_element.gd
  • modules/gdscript/tests/scripts/analyzer/errors/nullable_dictionary_key_element.gd
  • modules/gdscript/tests/scripts/analyzer/errors/nullable_dictionary_value_element.gd
  • modules/gdscript/tests/scripts/analyzer/errors/nullable_variant.gd
  • modules/gdscript/tests/scripts/analyzer/warnings/nullable_and_false_no_narrow.gd
  • modules/gdscript/tests/scripts/analyzer/warnings/nullable_break_guard_no_narrow.gd
  • modules/gdscript/tests/scripts/analyzer/warnings/nullable_compound_assignment.gd
  • modules/gdscript/tests/scripts/analyzer/warnings/nullable_continue_guard_no_narrow.gd
  • modules/gdscript/tests/scripts/analyzer/warnings/nullable_narrowing_limits.gd
  • modules/gdscript/tests/scripts/analyzer/warnings/nullable_or_true_no_narrow.gd
  • modules/gdscript/tests/scripts/analyzer/warnings/nullable_unsafe_operations.gd
  • modules/gdscript/tests/scripts/analyzer/warnings/nullable_unsafe_transfer.gd
  • modules/gdscript/tests/scripts/analyzer/warnings/unsafe_nullable_access.gd
  • modules/gdscript/tests/scripts/parser/errors/nullable_void_return.gd
  • modules/gdscript/tests/scripts/runtime/errors/nullable_enum_null_transfer.gd
  • modules/gdscript/tests/scripts/runtime/errors/nullable_null_reaches_argument.gd
  • modules/gdscript/tests/scripts/runtime/errors/nullable_null_reaches_typed_local.gd
  • modules/gdscript/tests/scripts/runtime/errors/nullable_null_use.gd
  • modules/gdscript/tests/scripts/runtime/errors/nullable_return_null_from_nonnull.gd
  • modules/gdscript/tests/scripts/runtime/errors/nullable_typed_array_assign_mismatch.gd
  • modules/gdscript/tests/scripts/runtime/errors/nullable_typed_array_reassign_mismatch.gd
  • modules/gdscript/tests/scripts/runtime/errors/nullable_typed_dictionary_assign_mismatch.gd
  • modules/gdscript/tests/scripts/runtime/errors/nullable_typed_dictionary_reassign_mismatch.gd
  • modules/gdscript/tests/scripts/runtime/errors/nullable_typed_dictionary_return_mismatch.gd
  • modules/gdscript/tests/scripts/runtime/errors/nullable_typed_return_mismatch.gd
  • modules/gdscript/tests/scripts/runtime/features/nullable_empty_callable.gd
  • modules/gdscript/tests/scripts/runtime/features/nullable_enum_raw_integer.gd
  • modules/gdscript/tests/scripts/runtime/features/nullable_enum_transfer.gd
  • modules/gdscript/tests/scripts/runtime/features/nullable_freed_object.gd
  • modules/gdscript/tests/scripts/runtime/features/nullable_multivar_and.gd
  • modules/gdscript/tests/scripts/runtime/features/nullable_multivar_or_guard.gd
  • modules/gdscript/tests/scripts/runtime/features/nullable_narrowing.gd
  • modules/gdscript/tests/scripts/runtime/features/nullable_narrowing_mutation.gd
  • modules/gdscript/tests/scripts/runtime/features/nullable_not_compound_or.gd
  • modules/gdscript/tests/scripts/runtime/features/nullable_renarrow_after_reassign.gd
  • modules/gdscript/tests/scripts/runtime/features/nullable_self_reference_assignment.gd
  • modules/gdscript/tests/scripts/runtime/features/nullable_typed_container_returns.gd
  • modules/gdscript/tests/scripts/runtime/features/nullable_types.gd
  • modules/gdscript/tests/scripts/runtime/features/nullable_variant_null_return.gd
  • modules/gdscript/tests/scripts/runtime/features/nullable_while_body_mutation.gd

Comment thread modules/gdscript/gdscript_analyzer.cpp
Comment thread modules/gdscript/gdscript_analyzer.cpp
Comment thread modules/gdscript/gdscript_function.h

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
modules/gdscript/gdscript_analyzer.cpp (1)

2079-2103: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Restore the complete narrowing state, not only its size.

These scopes snapshot narrowed_non_null by length and later call resize(), but invalidate_narrowing() can erase facts that existed before the snapshot. Resizing cannot restore those removed entries, so a nested assignment or boolean expression can permanently lose an outer non-null fact and produce false UNSAFE_NULLABLE_ACCESS warnings afterward. Save and restore the vector contents (or use an equivalent scoped snapshot), and add a regression covering an outer null check with a nested branch that reassigns the narrowed variable.

Also applies to: 2284-2392, 3015-3029, 3254-3260

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/gdscript/gdscript_analyzer.cpp` around lines 2079 - 2103, Restore the
full narrowed_non_null contents after each scoped narrowing operation, rather
than only resizing to the saved length. Update the snapshot/cleanup logic around
the shown suite loop and the corresponding scopes near the other referenced
locations, preserving facts removed by invalidate_narrowing() during nested
branches or expressions. Add a regression covering an outer null check whose
nested branch reassigns the narrowed variable, ensuring the outer non-null fact
remains available afterward.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@modules/gdscript/gdscript_analyzer.cpp`:
- Around line 2079-2103: Restore the full narrowed_non_null contents after each
scoped narrowing operation, rather than only resizing to the saved length.
Update the snapshot/cleanup logic around the shown suite loop and the
corresponding scopes near the other referenced locations, preserving facts
removed by invalidate_narrowing() during nested branches or expressions. Add a
regression covering an outer null check whose nested branch reassigns the
narrowed variable, ensuring the outer non-null fact remains available afterward.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: a888371c-2741-4562-86b0-9680d6e02ded

📥 Commits

Reviewing files that changed from the base of the PR and between b11c8ea and 4e10d54.

⛔ Files ignored due to path filters (2)
  • modules/gdscript/tests/scripts/runtime/features/nullable_narrowing_else.out is excluded by !**/*.out
  • modules/gdscript/tests/scripts/runtime/features/nullable_nested_enum.out is excluded by !**/*.out
📒 Files selected for processing (4)
  • doc/classes/ProjectSettings.xml
  • modules/gdscript/gdscript_analyzer.cpp
  • modules/gdscript/tests/scripts/runtime/features/nullable_narrowing_else.gd
  • modules/gdscript/tests/scripts/runtime/features/nullable_nested_enum.gd

@decryptedchaos

Copy link
Copy Markdown
Member

LGTM

@Shakai-Dev Shakai-Dev left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants