Introduce traits to GDScript - #1314
Conversation
(cherry picked from commit a50fc5acd81e0aa7d69e23faa462ecb6bbdad165)
(cherry picked from commit 02a3ada50224e1cdc95077ee0d67d633ff904d7f)
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (11)
🚧 Files skipped from review as they are similar to previous changes (9)
WalkthroughAdds GDScript traits with ChangesGDScript trait language
Class-list container migration
Estimated code review effort: 5 (Critical) | ~120 minutes Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 14
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
modules/gdscript/gdscript_editor.cpp (2)
1274-1281: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winDo not expose constructor behavior on trait metatypes.
Treating traits exactly like classes makes completion suggest
new, infer a constructed trait instance, and show constructor hints, while the analyzer explicitly forbids trait construction. Preserve trait instance-member inference, but gate all metatype constructor handling onkind != TRAIT.
modules/gdscript/gdscript_editor.cpp#L1274-L1281: suppress the genericnewcompletion for trait metatypes.modules/gdscript/gdscript_editor.cpp#L2824-L2853: do not inferTrait.new()as a trait instance.modules/gdscript/gdscript_editor.cpp#L2980-L3011: do not produce constructor argument hints for traits.🤖 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_editor.cpp` around lines 1274 - 1281, Gate constructor-specific behavior on DataType::kind != TRAIT across modules/gdscript/gdscript_editor.cpp at 1274-1281, 2824-2853, and 2980-3011: suppress generic new completion, prevent Trait.new() from inferring a trait instance, and omit constructor argument hints for trait metatypes. Preserve trait instance-member inference and existing class behavior.
4017-4025: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winPreserve the nested trait lookup result.
The following post-processing condition at Line 4046 excludes only
CLASS. ForTRAIT, it overwrites the resolved trait’sclass_namewith the containing type and treats the trait as a regular member. Exclude bothCLASSandTRAITthere.🤖 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_editor.cpp` around lines 4017 - 4025, Update the post-processing condition near the lookup handling after the TRAIT/CLASS switch in the GDScript editor so it excludes both GDScriptParser::ClassNode::Member::CLASS and GDScriptParser::ClassNode::Member::TRAIT. Preserve the resolved nested trait result and prevent its class_name from being overwritten with the containing type.modules/gdscript/gdscript_analyzer.cpp (1)
6818-6829: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winUse trait composition when checking trait-target compatibility.
This branch treats a trait like an inherited class and only walks
base_type. A class that directly uses the target trait is therefore rejected when assigned, passed, or returned through that trait type. Check each source class’straits_fqtnfor the target trait before traversing its parent.🤖 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 6818 - 6829, Update the TRAIT/CLASS compatibility branch to inspect each source class’s traits_fqtn for p_target.class_type before traversing its parent. Preserve the existing direct class/FQCN match, continue checking each ancestor’s composed traits, and return true when the target trait is found.modules/gdscript/language_server/gdscript_extend_parser.cpp (1)
469-473: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winPreserve trait identity in language-server output.
Routing traits through the class path labels outline symbols as
classand serializes traits undersub_classeswithout a discriminator. Clients cannot distinguish trait declarations from classes.
modules/gdscript/language_server/gdscript_extend_parser.cpp#L469-L473: emit trait-specific symbol detail/kind based onp_class->type.modules/gdscript/language_server/gdscript_extend_parser.cpp#L950-L953: serialize a trait discriminator or separate trait collection.🤖 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/language_server/gdscript_extend_parser.cpp` around lines 469 - 473, Preserve trait identity in the language-server symbol output: update the TRAIT/CLASS handling around parse_class_symbol to derive trait-specific detail or kind from p_class->type instead of treating traits as classes; also update the serialization logic at modules/gdscript/language_server/gdscript_extend_parser.cpp#L950-L953 to emit a trait discriminator or dedicated trait collection so clients can distinguish traits from classes.
🧹 Nitpick comments (5)
editor/doc/doc_tools.cpp (1)
430-430: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueUse value semantics for transparent conversion.
classes[classes_idx]yields aStringName. Assigning it to aconst String &relies on C++ lifetime extension to keep the implicitly constructed temporaryStringalive. It is clearer to use value semantics, making the copy/conversion explicit to readers.♻️ Proposed refactor
- const String &name = classes[classes_idx]; + String name = classes[classes_idx];🤖 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 `@editor/doc/doc_tools.cpp` at line 430, Update the local declaration in the surrounding class-processing code to store classes[classes_idx] as a String value instead of a const String reference, making the StringName-to-String conversion explicit and avoiding reliance on temporary lifetime extension.core/object/class_db.cpp (1)
261-268: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse
original_sizefor robust sort-range calculations.In both functions, the starting index for sorting is calculated using
size() - container.size(), which relies on the strict assumption that exactlycontainer.size()elements were appended. If a filtering condition (e.g.,continue) is ever added to these loops (as seen inClassDB::get_extensions_class_list), this math will break and cause an out-of-bounds pointer or sort an incorrect range.Track the
original_sizebefore the loop for mathematically robust offsetting.
core/object/class_db.cpp#L261-L268: Trackuint32_t original_size = p_classes.size();before the loop, use it forreserve, and pass&p_classes[original_size]andp_classes.size() - original_sizetosorter.sort().core/object/script_language.cpp#L525-L530: Trackuint32_t original_size = r_global_classes.size();before the loop, use it forreserve, and pass&r_global_classes[original_size]andr_global_classes.size() - original_sizetosorter.sort().♻️ Proposed refactor for ClassDB::get_class_list
- p_classes.reserve(p_classes.size() + classes.size()); + uint32_t original_size = p_classes.size(); + p_classes.reserve(original_size + classes.size()); for (const KeyValue<StringName, ClassInfo> &cls : classes) { p_classes.push_back(cls.key); } SortArray<StringName, StringName::AlphCompare> sorter; - sorter.sort(&p_classes[p_classes.size() - classes.size()], classes.size()); + sorter.sort(&p_classes[original_size], p_classes.size() - original_size);♻️ Proposed refactor for ScriptServer::get_global_class_list
- r_global_classes.reserve(r_global_classes.size() + global_classes.size()); + uint32_t original_size = r_global_classes.size(); + r_global_classes.reserve(original_size + global_classes.size()); for (const KeyValue<StringName, GlobalScriptClass> &global_class : global_classes) { r_global_classes.push_back(global_class.key); } SortArray<StringName, StringName::AlphCompare> sorter; - sorter.sort(&r_global_classes[r_global_classes.size() - global_classes.size()], global_classes.size()); + sorter.sort(&r_global_classes[original_size], r_global_classes.size() - original_size);🤖 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 `@core/object/class_db.cpp` around lines 261 - 268, In ClassDB::get_class_list (core/object/class_db.cpp, lines 261-268), record original_size before iterating, use it for reserve sizing, and sort from p_classes[original_size] across the appended count p_classes.size() - original_size. Apply the same change in ScriptServer::get_global_class_list (core/object/script_language.cpp, lines 525-530) using r_global_classes and its original_size.tests/core/object/test_class_db.h (1)
521-539: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valuePrefer a range-based for loop.
Since
LocalVectorsupports modern iterators, you can simplify this by using a range-based for loop instead of managing indices manually.Additionally, if the previous test list was explicitly sorted (as the AI summary noted), please double-check whether a deterministic sort is still required to prevent arbitrary test failures. If so, you may need to add
class_list.sort_custom<StringName::AlphCompare>();before the loop.♻️ Proposed refactor
LocalVector<StringName> class_list; ClassDB::get_class_list(class_list); - for (uint32_t class_list_idx = 0; class_list_idx < class_list.size(); class_list_idx++) { - StringName class_name = class_list[class_list_idx]; + for (const StringName &class_name : class_list) { ClassDB::APIType api_type = ClassDB::get_api_type(class_name);🤖 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 `@tests/core/object/test_class_db.h` around lines 521 - 539, Update the class-list iteration in the test to use a range-based for loop over class_list instead of class_list_idx indexing. Verify whether deterministic ordering is required by the prior test behavior, and if so, sort class_list with the existing StringName alphabetical comparator before the loop.editor/project_upgrade/project_converter_3_to_4.cpp (1)
1196-1198: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueUse a const reference for iteration.
The
classes_listelements are only read within the loop, so usingconst StringName &is safer and more idiomatic.♻️ Proposed refactor
LocalVector<StringName> classes_list; ClassDB::get_class_list(classes_list); - for (StringName &name_of_class : classes_list) { + for (const StringName &name_of_class : classes_list) {🤖 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 `@editor/project_upgrade/project_converter_3_to_4.cpp` around lines 1196 - 1198, Update the range-based loop over classes_list in the project conversion code to use a const StringName reference, since name_of_class is only read. Keep the existing iteration logic unchanged.modules/gdscript/gdscript_vm.cpp (1)
1742-1749: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winEliminate redundant virtual calls to
get_script_instance().Both trait opcodes retrieve the object's script instance twice unnecessarily. Instead of calling
get_script_instance()again inside the condition blocks, utilize the pointer you've already retrieved.
modules/gdscript/gdscript_vm.cpp#L1742-L1749: Replacesrc->operator Object *()->get_script_instance()->get_script().ptr()withscr_inst->get_script().ptr().modules/gdscript/gdscript_vm.cpp#L977-L980: Cacheobject->get_script_instance()in a local variable before checking it, and use that local variable to retrieve the script.🤖 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_vm.cpp` around lines 1742 - 1749, Eliminate redundant get_script_instance() calls in both trait opcode paths: at modules/gdscript/gdscript_vm.cpp lines 1742-1749, use the existing scr_inst local when retrieving the script; at lines 977-980, cache object->get_script_instance() before the null check and use that cached pointer to retrieve the script.
🤖 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 7017-7022: Update the trait method validation around
p_source_function and p_target_function to reject a static class implementation
when the required trait method is non-static. Emit the appropriate analyzer
error and prevent the implementation from being accepted; preserve the existing
warning and promotion behavior for a non-static implementation overriding a
static trait method.
- Around line 6960-6968: Fix parameter signature formatting by inserting the
comma before each parameter type after the first in the signature-building logic
near modules/gdscript/gdscript_analyzer.cpp lines 6960-6968 and in the
missing-implementation diagnostic near lines 7079-7084. Update both affected
sites so generated signatures use separators such as “Node,Node2D” rather than
appending commas after types.
- Around line 1779-1793: In both uses-chain resolution sites, update the
CONSTANT path to assign the resolved trait from member_datatype.class_type
instead of reading member.m_class, while retaining member.m_class for
class/trait members. At modules/gdscript/gdscript_analyzer.cpp lines 1779-1793
and 1839-1857, validate that the selected class_type is non-null and represents
a trait before continuing; reject invalid constant results through the existing
error path.
- Around line 1978-1997: The inheritance conflict check around class_base_type
currently inspects only the immediate script parent; traverse each ancestor in
the full script inheritance chain and compare the trait members against every
ancestor’s members. Preserve the existing native-class checks, and update the
class_type branch so inherited grandparent members also set inheritance_match to
false.
- Around line 1888-1899: Update the trait-use resolution logic in the TRAIT
branch to reject a target trait when trait->resolving_uses is already true,
treating it as a cyclic use alongside the existing traits_fqtn membership check.
Route this case through the cyclic-use error path and preserve the resolved_uses
reset so the counterpart trait also reports the cycle, rather than allowing the
later !trait->resolved_uses branch to continue silently.
- Around line 4284-4297: Update the cast analysis around the trait compatibility
check at modules/gdscript/gdscript_analyzer.cpp:4284-4297 and the corresponding
test analysis at modules/gdscript/gdscript_analyzer.cpp:5716-5734 to reject only
when the class and trait inheritance constraints cannot overlap; otherwise emit
the unsafe runtime operation, including for hard base-typed classes that may
contain a compatible subclass. Apply the same overlap rule consistently at both
sites.
- Around line 2252-2254: Update the rest-parameter unused-warning condition near
the existing parameter check in the function analysis path to also exclude
bodyless methods via p_function->is_bodyless. Keep the current warning behavior
unchanged for non-bodyless methods.
In `@modules/gdscript/gdscript_byte_codegen.cpp`:
- Around line 1066-1072: Update the GDTRAIT branch of write_cast to pass
p_type.trait_type to OPCODE_CAST_TO_TRAIT instead of p_target.type.trait_type,
while preserving the existing opcode and source/target operand handling.
In `@modules/gdscript/gdscript_editor.cpp`:
- Around line 1120-1124: Update _list_available_types() at
modules/gdscript/gdscript_editor.cpp:1120-1124 to filter global entries
according to p_include_trait and inheritance-only semantics, excluding traits
when traits are not requested and excluding incompatible classes for inheritance
completion. Update the COMPLETION_USES_TYPE handling at
modules/gdscript/gdscript_editor.cpp:3554-3574 to add only local and global
traits, not ordinary classes.
In `@modules/gdscript/gdscript_function.h`:
- Around line 82-83: Update GDScriptFunction::is_type so the GDTRAIT case
performs runtime checking instead of breaking to return false: verify the
variant is an object, retrieve its script, and determine whether the associated
GDScript implements the requested trait. Move is_type implementation to the
source file if needed to access complete GDScript/GDScriptFunction definitions,
and expose or reuse a static trait-checking helper such as _is_class_using_trait
or GDScript::has_trait.
In `@modules/gdscript/gdscript_parser.cpp`:
- Around line 989-993: Update the path-only trait handling in the parser branch
after match(GDScriptTokenizer::Token::PERIOD) so it records and completes uses
without returning immediately; consume the comma and continue parsing subsequent
trait entries such as `B`. Preserve the existing return behavior only when no
further trait entries remain.
In `@modules/gdscript/gdscript_tokenizer.h`:
- Around line 136-137: Update TOKENIZER_VERSION in gdscript_tokenizer_buffer.h
to a new value reflecting the added TRAIT_NAME and USES token IDs, ensuring
serialized .gdc buffers use the correct token mapping.
In `@modules/gdscript/gdscript_vm.cpp`:
- Around line 1720-1728: Remove the unused GET_VARIANT_PTR(to_type, 2) call from
the OPCODE_CAST_TO_TRAIT handler, while retaining src, dst, and trait_type_idx
retrieval and validation.
In `@modules/gdscript/gdscript.cpp`:
- Around line 2772-2773: Add "trait_name" to the reserved-word list alongside
the existing "trait" and "uses" entries so the API matches the tokenizer's
recognized keywords.
---
Outside diff comments:
In `@modules/gdscript/gdscript_analyzer.cpp`:
- Around line 6818-6829: Update the TRAIT/CLASS compatibility branch to inspect
each source class’s traits_fqtn for p_target.class_type before traversing its
parent. Preserve the existing direct class/FQCN match, continue checking each
ancestor’s composed traits, and return true when the target trait is found.
In `@modules/gdscript/gdscript_editor.cpp`:
- Around line 1274-1281: Gate constructor-specific behavior on DataType::kind !=
TRAIT across modules/gdscript/gdscript_editor.cpp at 1274-1281, 2824-2853, and
2980-3011: suppress generic new completion, prevent Trait.new() from inferring a
trait instance, and omit constructor argument hints for trait metatypes.
Preserve trait instance-member inference and existing class behavior.
- Around line 4017-4025: Update the post-processing condition near the lookup
handling after the TRAIT/CLASS switch in the GDScript editor so it excludes both
GDScriptParser::ClassNode::Member::CLASS and
GDScriptParser::ClassNode::Member::TRAIT. Preserve the resolved nested trait
result and prevent its class_name from being overwritten with the containing
type.
In `@modules/gdscript/language_server/gdscript_extend_parser.cpp`:
- Around line 469-473: Preserve trait identity in the language-server symbol
output: update the TRAIT/CLASS handling around parse_class_symbol to derive
trait-specific detail or kind from p_class->type instead of treating traits as
classes; also update the serialization logic at
modules/gdscript/language_server/gdscript_extend_parser.cpp#L950-L953 to emit a
trait discriminator or dedicated trait collection so clients can distinguish
traits from classes.
---
Nitpick comments:
In `@core/object/class_db.cpp`:
- Around line 261-268: In ClassDB::get_class_list (core/object/class_db.cpp,
lines 261-268), record original_size before iterating, use it for reserve
sizing, and sort from p_classes[original_size] across the appended count
p_classes.size() - original_size. Apply the same change in
ScriptServer::get_global_class_list (core/object/script_language.cpp, lines
525-530) using r_global_classes and its original_size.
In `@editor/doc/doc_tools.cpp`:
- Line 430: Update the local declaration in the surrounding class-processing
code to store classes[classes_idx] as a String value instead of a const String
reference, making the StringName-to-String conversion explicit and avoiding
reliance on temporary lifetime extension.
In `@editor/project_upgrade/project_converter_3_to_4.cpp`:
- Around line 1196-1198: Update the range-based loop over classes_list in the
project conversion code to use a const StringName reference, since name_of_class
is only read. Keep the existing iteration logic unchanged.
In `@modules/gdscript/gdscript_vm.cpp`:
- Around line 1742-1749: Eliminate redundant get_script_instance() calls in both
trait opcode paths: at modules/gdscript/gdscript_vm.cpp lines 1742-1749, use the
existing scr_inst local when retrieving the script; at lines 977-980, cache
object->get_script_instance() before the null check and use that cached pointer
to retrieve the script.
In `@tests/core/object/test_class_db.h`:
- Around line 521-539: Update the class-list iteration in the test to use a
range-based for loop over class_list instead of class_list_idx indexing. Verify
whether deterministic ordering is required by the prior test behavior, and if
so, sort class_list with the existing StringName alphabetical comparator before
the loop.
🪄 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
Run ID: e8193825-879b-4abb-991a-1efd163fec87
⛔ Files ignored due to path filters (7)
modules/gdscript/tests/scripts/Traits/analyzer/errors/invalid_test_and_cast.outis excluded by!**/*.outmodules/gdscript/tests/scripts/Traits/analyzer/errors/invalid_trait_function.outis excluded by!**/*.outmodules/gdscript/tests/scripts/Traits/analyzer/errors/invalid_trait_inheritance.outis excluded by!**/*.outmodules/gdscript/tests/scripts/Traits/analyzer/errors/invalid_trait_uses.outis excluded by!**/*.outmodules/gdscript/tests/scripts/Traits/analyzer/features/trait_cohesion.outis excluded by!**/*.outmodules/gdscript/tests/scripts/Traits/analyzer/features/trait_test_and_cast.outis excluded by!**/*.outmodules/gdscript/tests/scripts/Traits/analyzer/warnings/trait_static_function.outis excluded by!**/*.out
📒 Files selected for processing (50)
core/core_bind.cppcore/debugger/remote_debugger.cppcore/extension/extension_api_dump.cppcore/io/resource_loader.cppcore/io/resource_saver.cppcore/object/class_db.cppcore/object/class_db.hcore/object/script_language.cppcore/object/script_language.hdoc/classes/ProjectSettings.xmleditor/doc/doc_tools.cppeditor/editor_data.cppeditor/file_system/editor_file_system.cppeditor/gui/create_dialog.cppeditor/project_upgrade/project_converter_3_to_4.cppeditor/script/script_editor_plugin.cppeditor/settings/editor_build_profile.cppeditor/shader/visual_shader_editor_plugin.cppmodules/gdscript/editor/gdscript_docgen.cppmodules/gdscript/editor/gdscript_highlighter.cppmodules/gdscript/editor/gdscript_translation_parser_plugin.cppmodules/gdscript/gdscript.cppmodules/gdscript/gdscript.hmodules/gdscript/gdscript_analyzer.cppmodules/gdscript/gdscript_analyzer.hmodules/gdscript/gdscript_byte_codegen.cppmodules/gdscript/gdscript_cache.cppmodules/gdscript/gdscript_cache.hmodules/gdscript/gdscript_compiler.cppmodules/gdscript/gdscript_editor.cppmodules/gdscript/gdscript_function.hmodules/gdscript/gdscript_parser.cppmodules/gdscript/gdscript_parser.hmodules/gdscript/gdscript_tokenizer.cppmodules/gdscript/gdscript_tokenizer.hmodules/gdscript/gdscript_vm.cppmodules/gdscript/gdscript_warning.cppmodules/gdscript/gdscript_warning.hmodules/gdscript/language_server/gdscript_extend_parser.cppmodules/gdscript/tests/scripts/Traits/analyzer/errors/invalid_test_and_cast.gdmodules/gdscript/tests/scripts/Traits/analyzer/errors/invalid_trait_function.gdmodules/gdscript/tests/scripts/Traits/analyzer/errors/invalid_trait_inheritance.gdmodules/gdscript/tests/scripts/Traits/analyzer/errors/invalid_trait_uses.gdmodules/gdscript/tests/scripts/Traits/analyzer/features/global_trait.notest.gdmodules/gdscript/tests/scripts/Traits/analyzer/features/trait_cohesion.gdmodules/gdscript/tests/scripts/Traits/analyzer/features/trait_test_and_cast.gdmodules/gdscript/tests/scripts/Traits/analyzer/warnings/trait_static_function.gdmodules/mono/class_db_api_json.cppmodules/mono/editor/bindings_generator.cpptests/core/object/test_class_db.h
| case GDScriptParser::ClassNode::Member::CONSTANT: | ||
| if (member_datatype.kind != GDScriptParser::DataType::SCRIPT && member_datatype.kind != GDScriptParser::DataType::TRAIT) { | ||
| push_error(vformat(R"(Constant "%s" is not a preloaded script or trait.)", name), id); | ||
| err = ERR_PARSE_ERROR; | ||
| } | ||
| break; | ||
| default: | ||
| push_error(vformat(R"(Cannot use %s "%s" in uses chain.)", member.get_type_name(), name), id); | ||
| err = ERR_PARSE_ERROR; | ||
| } | ||
| if (err || parser->errors.size() > error_count) { | ||
| break; | ||
| } | ||
| trait = member.m_class; | ||
| found = true; |
There was a problem hiding this comment.
🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win
Do not read member.m_class from a constant union member.
Both paths accept CONSTANT, then unconditionally assign trait = member.m_class. For constants this union field contains a ConstantNode *, so subsequent ClassNode access is undefined behavior. Use the resolved datatype’s class_type for constants and reject a null/non-trait result.
modules/gdscript/gdscript_analyzer.cpp#L1779-L1793: selectmember_datatype.class_typefor preloaded trait constants.modules/gdscript/gdscript_analyzer.cpp#L1839-L1857: apply the same typed selection for nested uses chains.
📍 Affects 1 file
modules/gdscript/gdscript_analyzer.cpp#L1779-L1793(this comment)modules/gdscript/gdscript_analyzer.cpp#L1839-L1857
🤖 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 1779 - 1793, In both
uses-chain resolution sites, update the CONSTANT path to assign the resolved
trait from member_datatype.class_type instead of reading member.m_class, while
retaining member.m_class for class/trait members. At
modules/gdscript/gdscript_analyzer.cpp lines 1779-1793 and 1839-1857, validate
that the selected class_type is non-null and represents a trait before
continuing; reject invalid constant results through the existing error path.
| // Do not allows cyclic use of traits: TraitA uses TraitB and TraitB uses TraitA. | ||
| if (p_class->type == GDScriptParser::Node::TRAIT && trait->traits_fqtn.has(p_class->fqcn)) { | ||
| push_error(vformat(R"(Cyclic use of trait. "%s" trait can not use trait that uses it.)", trait_name), trait_name_node); | ||
| trait->resolved_uses = false; // Trigger same error on the other trait. | ||
| continue; | ||
| } | ||
|
|
||
| if (!trait->resolved_uses) { | ||
| if (!trait->resolving_uses) { | ||
| push_error(vformat(R"(Could not resolve trait's uses from "%s".)", trait_name), trait_name_node); | ||
| } | ||
| continue; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Detect traits that are already resolving as cycles.
For TraitA uses TraitB and TraitB uses TraitA, TraitA.traits_fqtn is not populated until Line 2019. The reverse-membership check therefore misses the cycle, while the resolving_uses branch silently continues and marks TraitB resolved with incomplete composition.
Reject trait->resolving_uses as a cyclic use instead.
🤖 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 1888 - 1899, Update the
trait-use resolution logic in the TRAIT branch to reject a target trait when
trait->resolving_uses is already true, treating it as a cyclic use alongside the
existing traits_fqtn membership check. Route this case through the cyclic-use
error path and preserve the resolved_uses reset so the counterpart trait also
reports the cycle, rather than allowing the later !trait->resolved_uses branch
to continue silently.
There was a problem hiding this comment.
Can you give me a quick reproduction example that I can use for verification?
There was a problem hiding this comment.
@Arctis-Fireblight, add this as a trait analyzer error-case script, for example modules/gdscript/tests/scripts/Traits/analyzer/errors/cyclic_trait_uses.gd:
trait TraitA:
uses TraitB
trait TraitB:
uses TraitA
func test():
passExpected behavior after the fix: parsing/analyzing this script should fail with a cyclic-trait-use diagnostic (at least for TraitB uses TraitA; the existing resolved_uses = false behavior may also cause the counterpart trait to report it).
Current problematic behavior: while resolving TraitB, TraitA is still marked resolving_uses, but its traits_fqtn has not yet been populated. The existing cycle check misses this, so resolution can complete without reporting the cycle.
For a slightly less direct chain, this should fail as well:
trait TraitA:
uses TraitB
trait TraitB:
uses TraitC
trait TraitC:
uses TraitA
func test():
passThere was a problem hiding this comment.
Yeah, this is 100% a problem. I'll work on a fix.
There was a problem hiding this comment.
@Arctis-Fireblight, thanks for confirming. The direct and three-trait-cycle cases should provide good regression coverage when you have the fix ready. 🐇
| } else if (p_class->extends_used) { | ||
| GDScriptParser::DataType class_base_type = p_class->base_type; | ||
| if (class_base_type.kind == GDScriptParser::DataType::NATIVE && class_base_type.native_type != SNAME("RefCounted")) { | ||
| for (const GDScriptParser::ClassNode::Member &member : trait->members) { | ||
| if (ClassDB::has_enum(class_base_type.native_type, member.get_name()) || | ||
| ClassDB::has_signal(class_base_type.native_type, member.get_name()) || | ||
| ClassDB::has_method(class_base_type.native_type, member.get_name()) || | ||
| ClassDB::has_integer_constant(class_base_type.native_type, member.get_name())) { | ||
| inheritance_match = false; | ||
| break; | ||
| } | ||
| } | ||
| } else if (class_base_type.class_type != nullptr) { | ||
| for (const GDScriptParser::ClassNode::Member &member : trait->members) { | ||
| if (class_base_type.class_type->has_member(member.get_name())) { | ||
| inheritance_match = false; | ||
| break; | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Traverse the full script inheritance chain for conflicts.
When the trait has no explicit extends, this only checks the immediate parent’s own members. A trait member can therefore shadow a method inherited from a grandparent, bypassing the intended conflict enforcement.
🤖 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 1978 - 1997, The
inheritance conflict check around class_base_type currently inspects only the
immediate script parent; traverse each ancestor in the full script inheritance
chain and compare the trait members against every ancestor’s members. Preserve
the existing native-class checks, and update the class_type branch so inherited
grandparent members also set inheritance_match to false.
There was a problem hiding this comment.
Can you please give me a reproduction example for this one as well?
There was a problem hiding this comment.
@Arctis-Fireblight, here is a minimal reproduction:
trait AddsGreeting:
func greet() -> void:
print("trait greeting")
class Grandparent:
func greet() -> void:
print("grandparent greeting")
class Parent extends Grandparent:
pass
class Child extends Parent:
uses AddsGreeting
func test() -> void:
Child.new().greet()AddsGreeting has no explicit extends, while Child extends a GDScript class. At the current check, class_base_type.class_type is Parent, and Parent does not declare greet(), so the conflict is missed even though Grandparent does.
As a result, the trait’s greet() is copied into Child, shadowing/overriding the inherited Grandparent.greet() method. The expected behavior, based on the existing native-parent conflict handling, is to reject uses AddsGreeting because the method already exists somewhere in Child’s inherited script hierarchy.
🐇🔍
| if (!p_function->is_bodyless && p_function->parameters[i]->usages == 0 && !String(p_function->parameters[i]->identifier->name).begins_with("_") && !p_function->is_abstract) { | ||
| parser->push_warning(p_function->parameters[i]->identifier, GDScriptWarning::UNUSED_PARAMETER, function_visible_name, p_function->parameters[i]->identifier->name); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Suppress the warning for bodyless rest parameters too.
The normal parameter path now excludes bodyless trait methods, but the rest-parameter warning at Line 2291 does not. A required bodyless method using ...args will still report an impossible-to-fix unused-parameter warning.
Proposed fix
- if (p_function->rest_parameter->usages == 0 && !String(p_function->rest_parameter->identifier->name).begins_with("_") && !p_function->is_abstract) {
+ if (!p_function->is_bodyless && p_function->rest_parameter->usages == 0 && !String(p_function->rest_parameter->identifier->name).begins_with("_") && !p_function->is_abstract) {🤖 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 2252 - 2254, Update the
rest-parameter unused-warning condition near the existing parameter check in the
function analysis path to also exclude bodyless methods via
p_function->is_bodyless. Keep the current warning behavior unchanged for
non-bodyless methods.
| if ((p_cast->operand->get_datatype().kind == GDScriptParser::DataType::CLASS || p_cast->operand->get_datatype().kind == GDScriptParser::DataType::TRAIT) && cast_type.kind == GDScriptParser::DataType::TRAIT) { | ||
| if (p_cast->operand->is_constant) { | ||
| push_error(vformat(R"(Invalid cast. Cannot convert from "%s" to "%s".)", p_cast->operand->get_datatype().to_string(), cast_type.to_string()), p_cast->cast_type); | ||
| } else { | ||
| GDScriptParser::DataType operand_type = p_cast->operand->get_datatype(); | ||
| bool is_using_trait = false; | ||
| if (operand_type.class_type && cast_type.class_type) { | ||
| is_using_trait = operand_type.class_type->traits_fqtn.has(cast_type.class_type->fqcn); | ||
| } | ||
| if (operand_type.is_hard_type() && !is_using_trait && operand_type.kind != GDScriptParser::DataType::VARIANT) { | ||
| push_error(vformat(R"(Invalid cast. Cannot convert from "%s" to "%s".)", p_cast->operand->get_datatype().to_string(), cast_type.to_string()), p_cast->cast_type); | ||
| } else { | ||
| mark_node_unsafe(p_cast); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Allow runtime trait checks for compatible base-typed objects.
A hard class type that does not itself use the trait may still hold a subclass that does. These branches reject such casts/tests statically instead of emitting the unsafe runtime operation, breaking patterns such as checking a base-typed Node against a Node-compatible trait.
modules/gdscript/gdscript_analyzer.cpp#L4284-L4297: reject only when the class and trait inheritance constraints cannot overlap.modules/gdscript/gdscript_analyzer.cpp#L5716-L5734: use the same overlap rule before converting the test into a compile-time error.
📍 Affects 1 file
modules/gdscript/gdscript_analyzer.cpp#L4284-L4297(this comment)modules/gdscript/gdscript_analyzer.cpp#L5716-L5734
🤖 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 4284 - 4297, Update the
cast analysis around the trait compatibility check at
modules/gdscript/gdscript_analyzer.cpp:4284-4297 and the corresponding test
analysis at modules/gdscript/gdscript_analyzer.cpp:5716-5734 to reject only when
the class and trait inheritance constraints cannot overlap; otherwise emit the
unsafe runtime operation, including for hard base-typed classes that may contain
a compatible subclass. Apply the same overlap rule consistently at both sites.
There was a problem hiding this comment.
Can you please give me a reproduction example?
There was a problem hiding this comment.
@Arctis-Fireblight, here is a minimal reproduction. The same issue applies to both as and is:
trait Damageable extends Node:
func take_damage() -> void:
pass
class Enemy extends Node:
uses Damageable
func test() -> void:
# The static type is Node, but the runtime value is an Enemy,
# which implements Damageable.
var value: Node = Enemy.new()
var damageable := value as Damageable
print(value is Damageable)
damageable.take_damage()value is only known to be a Node statically, so it does not have Damageable in its traits_fqtn. However, an Enemy is a valid runtime value for value, and it does use Damageable.
Currently, the analyzer rejects the cast/type test as if Node could never implement the trait:
Invalid cast. Cannot convert from "Node" to "Damageable".
The expected behavior is to allow the operation as a runtime-checked (unsafe) cast/test:
value as Damageablesucceeds whenvalueis anEnemy, otherwise evaluates tonull.value is Damageableevaluates totruefor theEnemyinstance.
The same applies with an intermediate script base class, e.g. var value: BaseEnemy = Enemy.new(), where only Enemy uses the trait.
There was a problem hiding this comment.
@Arctis-Fireblight, the Comment image shows the expected reproducer: value is statically typed as Node, holds Enemy1, and Line 14 (value is Damageable1) is rejected with:
Expression is of type "Node" so it can't be of type "Damageable1".
Thanks for confirming. This validates the issue: the analyzer is treating the static base type as conclusive, despite a compatible subclass being able to implement the trait at runtime.
| case GDTRAIT: | ||
| break; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🔴 Critical | 🏗️ Heavy lift
Implement runtime type checking for traits.
is_type currently breaks and falls through to return false; for traits. This causes all runtime type checks (such as validating arguments passed to trait-typed function parameters or typed property assignments) to fail unconditionally.
You need to implement this case by checking if the variant is an object, retrieving its script, and checking if it implements the trait.
Note: Since checking traits_fqtn requires GDScript and GDScriptFunction implementations that are likely incomplete at this point in the header, you will likely need to move the implementation of is_type to a .cpp file and expose _is_class_using_trait as a static helper (or add a has_trait method to GDScript).
🤖 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_function.h` around lines 82 - 83, Update
GDScriptFunction::is_type so the GDTRAIT case performs runtime checking instead
of breaking to return false: verify the variant is an object, retrieve its
script, and determine whether the associated GDScript implements the requested
trait. Move is_type implementation to the source file if needed to access
complete GDScript/GDScriptFunction definitions, and expose or reuse a static
trait-checking helper such as _is_class_using_trait or GDScript::has_trait.
There was a problem hiding this comment.
Can you please give me a quick reproduction script for this one as well?
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n -C 3 --glob '*.gd' \
':\s*[A-Za-z_][A-Za-z0-9_]*|as\s+[A-Za-z_][A-Za-z0-9_]*' \
modules/gdscript/tests/scripts/Traits
printf '\n--- trait declarations and uses ---\n'
rg -n -C 2 --glob '*.gd' \
'^\s*(trait|trait_name|uses)\b' \
modules/gdscript/tests/scripts/TraitsLength of output: 20396
@Arctis-Fireblight, here is a minimal reproduction. Attach it to a Node and run the scene:
extends Node
trait Tagged:
func marker() -> void:
pass
class TaggedValue:
uses Tagged
func accept_tagged(_value: Tagged) -> void:
print("accepted")
func _ready() -> void:
# This instance implements `Tagged`, so this should be accepted.
accept_tagged(TaggedValue.new())Expected: it prints accepted.
Current behavior: the trait-typed argument validation rejects TaggedValue.new() even though TaggedValue declares uses Tagged. The parameter check reaches GDScriptDataType::is_type(), where the GDTRAIT case currently falls through to the final return false.
A typed property assignment should trigger the same path:
var value: Tagged
func _ready() -> void:
value = TaggedValue.new() # Incorrectly rejected currently.There was a problem hiding this comment.
@Arctis-Fireblight, the Comment image shows the editor flagging Line 15, accept_tagged(TaggedValue.new()), with:
Invalid argument for "accept_tagged()" function: argument 1 should be "Tagged" but is "TaggedValue".
You’re right — this is worse than the runtime-only issue I described. The analyzer rejects the call statically, so the script cannot be run in the first place.
The GDTRAIT fallthrough in GDScriptDataType::is_type() remains a real runtime validation bug (e.g., dynamic calls, typed assignments checked at runtime), but this reproduction also exposes a separate/static compatibility gap: the analyzer must recognize that a class declaring uses Tagged satisfies a Tagged parameter type.
So the fix needs both:
- Analyzer/type compatibility: treat a class using a trait as compatible with that trait, preventing this compile-time error.
- Runtime validation: implement the
GDTRAITbranch inGDScriptDataType::is_type()for values whose type is only known at runtime.
Thanks for catching that distinction — I’ll revise the finding’s scope accordingly.
🐇💭
| if (!match(GDScriptTokenizer::Token::PERIOD)) { | ||
| current_class->traits.push_back(uses); | ||
| complete_extents(uses); | ||
| return; | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Continue parsing after a path-only trait entry.
This return leaves a following comma unconsumed, so valid composition such as uses "a.gd", B fails.
Proposed fix
if (!match(GDScriptTokenizer::Token::PERIOD)) {
current_class->traits.push_back(uses);
complete_extents(uses);
- return;
+ if (!match(GDScriptTokenizer::Token::COMMA)) {
+ return;
+ }
+ continue;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (!match(GDScriptTokenizer::Token::PERIOD)) { | |
| current_class->traits.push_back(uses); | |
| complete_extents(uses); | |
| return; | |
| } | |
| if (!match(GDScriptTokenizer::Token::PERIOD)) { | |
| current_class->traits.push_back(uses); | |
| complete_extents(uses); | |
| if (!match(GDScriptTokenizer::Token::COMMA)) { | |
| return; | |
| } | |
| continue; | |
| } |
🤖 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_parser.cpp` around lines 989 - 993, Update the
path-only trait handling in the parser branch after
match(GDScriptTokenizer::Token::PERIOD) so it records and completes uses without
returning immediately; consume the comma and continue parsing subsequent trait
entries such as `B`. Preserve the existing return behavior only when no further
trait entries remain.
| OPCODE(OPCODE_CAST_TO_TRAIT) { | ||
| CHECK_SPACE(4); | ||
| GET_VARIANT_PTR(src, 0); | ||
| GET_VARIANT_PTR(dst, 1); | ||
| GET_VARIANT_PTR(to_type, 2); | ||
|
|
||
| int trait_type_idx = _code_ptr[ip + 3]; | ||
| GD_ERR_BREAK(trait_type_idx < 0 || trait_type_idx >= _global_names_count); | ||
| const StringName trait_type = _global_names_ptr[trait_type_idx]; |
There was a problem hiding this comment.
🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win
Remove invalid variant pointer retrieval.
GET_VARIANT_PTR(to_type, 2) attempts to read _code_ptr[ip + 3] as a variant stack address. However, for OPCODE_CAST_TO_TRAIT, the instruction length is only 4 words, and _code_ptr[ip + 3] stores trait_type_idx (an index into global names).
Treating this name index as a stack address triggers the GD_ERR_BREAK stack-bounds check within the macro, which will unconditionally abort the opcode execution if the index exceeds the current stack size. Since to_type is entirely unused here, simply remove this line.
🐛 Proposed fix
OPCODE(OPCODE_CAST_TO_TRAIT) {
CHECK_SPACE(4);
GET_VARIANT_PTR(src, 0);
GET_VARIANT_PTR(dst, 1);
- GET_VARIANT_PTR(to_type, 2);
int trait_type_idx = _code_ptr[ip + 3];
GD_ERR_BREAK(trait_type_idx < 0 || trait_type_idx >= _global_names_count);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| OPCODE(OPCODE_CAST_TO_TRAIT) { | |
| CHECK_SPACE(4); | |
| GET_VARIANT_PTR(src, 0); | |
| GET_VARIANT_PTR(dst, 1); | |
| GET_VARIANT_PTR(to_type, 2); | |
| int trait_type_idx = _code_ptr[ip + 3]; | |
| GD_ERR_BREAK(trait_type_idx < 0 || trait_type_idx >= _global_names_count); | |
| const StringName trait_type = _global_names_ptr[trait_type_idx]; | |
| OPCODE(OPCODE_CAST_TO_TRAIT) { | |
| CHECK_SPACE(4); | |
| GET_VARIANT_PTR(src, 0); | |
| GET_VARIANT_PTR(dst, 1); | |
| int trait_type_idx = _code_ptr[ip + 3]; | |
| GD_ERR_BREAK(trait_type_idx < 0 || trait_type_idx >= _global_names_count); | |
| const StringName trait_type = _global_names_ptr[trait_type_idx]; |
🤖 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_vm.cpp` around lines 1720 - 1728, Remove the unused
GET_VARIANT_PTR(to_type, 2) call from the OPCODE_CAST_TO_TRAIT handler, while
retaining src, dst, and trait_type_idx retrieval and validation.
f9e3476 to
337c30e
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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 `@doc/classes/ProjectSettings.xml`:
- Around line 559-560: Update the descriptions for the experimental-trait
settings in ProjectSettings.xml: correct experimental_trait to explain that its
integer warning level selects warning versus error behavior, and revise
show_experimental_trait_warning to describe its separate boolean role in
suppressing emission entirely. Distinguish their relationship clearly and fix
the “When set” wording typo.
🪄 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
Run ID: 932c03ba-52c4-4706-a9ae-8426c1674cc7
📒 Files selected for processing (9)
core/config/project_settings.cppdoc/classes/ProjectSettings.xmlmodules/gdscript/gdscript.cppmodules/gdscript/gdscript_analyzer.cppmodules/gdscript/gdscript_analyzer.hmodules/gdscript/gdscript_disassembler.cppmodules/gdscript/gdscript_tokenizer_buffer.hmodules/gdscript/gdscript_warning.cppmodules/gdscript/gdscript_warning.h
🚧 Files skipped from review as they are similar to previous changes (8)
- modules/gdscript/gdscript_tokenizer_buffer.h
- modules/gdscript/gdscript_warning.cpp
- modules/gdscript/gdscript_analyzer.h
- modules/gdscript/gdscript_warning.h
- core/config/project_settings.cpp
- modules/gdscript/gdscript_disassembler.cpp
- modules/gdscript/gdscript.cpp
- modules/gdscript/gdscript_analyzer.cpp
- Added support for Traits OP Codes to the GDScript disassembler - Fixed some implicit casts in gdscript_analyzer.cpp - Fixed missing keyword highlighting for `trait_name` - Added suppressable warning to project settings to notify users that traits are still experimental - incremented `TOKENIZER_VERSION` to 102 - Fixed failing unit tests due to addition of the `experimental_trait_warning` by suppressing this warning during tests
337c30e to
ad51e4b
Compare



This PR builds on the work of SeremTitus and integrates his work on traits into Redot.
Prerequisites from Godot 4.6
ScriptServer::get_global_class_listgodotengine/godot#108577 by YYF233333This PR merges the following:
is) and Casting (as) godotengine/godot#112933Examples of trait usage:
In this example, we define two traits: Damageable and Enemy.
Traits can be defined globally using the
trait_namekeyword, or locally with thetraitkeyword.Traits can then be used by classes or other traits using the
useskeyword.Traits can also declare methods that must be implemented by an implementing class, like the
on_death()method in this example.Lastly, traits can be tested for and casted by using the
isoraskeywords.Summary by CodeRabbit
New Features
trait_name,usescomposition (including nested traits), trait inheritance, overrides (including static), signals/enums, and bodyless trait functions.is) and casts (as).Bug Fixes
Documentation
statickeyword.Tests