From 17acb48c313b9dd98997ac4533165286340b6b6d Mon Sep 17 00:00:00 2001 From: Serem Titus Date: Tue, 1 Jul 2025 03:14:39 -0500 Subject: [PATCH 1/6] GDTrait --- doc/classes/ProjectSettings.xml | 3 + modules/gdscript/editor/gdscript_docgen.cpp | 5 + .../gdscript_translation_parser_plugin.cpp | 1 + modules/gdscript/gdscript.cpp | 3 +- modules/gdscript/gdscript_analyzer.cpp | 872 +++++++++++++++++- modules/gdscript/gdscript_analyzer.h | 10 + modules/gdscript/gdscript_cache.cpp | 10 +- modules/gdscript/gdscript_cache.h | 2 + modules/gdscript/gdscript_compiler.cpp | 10 + modules/gdscript/gdscript_editor.cpp | 60 +- modules/gdscript/gdscript_function.h | 3 + modules/gdscript/gdscript_parser.cpp | 129 ++- modules/gdscript/gdscript_parser.h | 73 +- modules/gdscript/gdscript_tokenizer.cpp | 4 + modules/gdscript/gdscript_tokenizer.h | 1 + modules/gdscript/gdscript_warning.cpp | 3 + modules/gdscript/gdscript_warning.h | 2 + .../gdscript_extend_parser.cpp | 2 + .../analyzer/errors/invalid_trait_function.gd | 46 + .../errors/invalid_trait_function.out | 11 + .../errors/invalid_trait_inheritance.gd | 5 + .../errors/invalid_trait_inheritance.out | 2 + .../analyzer/errors/invalid_trait_uses.gd | 28 + .../analyzer/errors/invalid_trait_uses.out | 4 + .../analyzer/features/trait_cohesion.gd | 62 ++ .../analyzer/features/trait_cohesion.out | 13 + .../warnings/trait_static_function.gd | 12 + .../warnings/trait_static_function.out | 2 + 28 files changed, 1301 insertions(+), 77 deletions(-) create mode 100644 modules/gdscript/tests/scripts/Traits/analyzer/errors/invalid_trait_function.gd create mode 100644 modules/gdscript/tests/scripts/Traits/analyzer/errors/invalid_trait_function.out create mode 100644 modules/gdscript/tests/scripts/Traits/analyzer/errors/invalid_trait_inheritance.gd create mode 100644 modules/gdscript/tests/scripts/Traits/analyzer/errors/invalid_trait_inheritance.out create mode 100644 modules/gdscript/tests/scripts/Traits/analyzer/errors/invalid_trait_uses.gd create mode 100644 modules/gdscript/tests/scripts/Traits/analyzer/errors/invalid_trait_uses.out create mode 100644 modules/gdscript/tests/scripts/Traits/analyzer/features/trait_cohesion.gd create mode 100644 modules/gdscript/tests/scripts/Traits/analyzer/features/trait_cohesion.out create mode 100644 modules/gdscript/tests/scripts/Traits/analyzer/warnings/trait_static_function.gd create mode 100644 modules/gdscript/tests/scripts/Traits/analyzer/warnings/trait_static_function.out diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index b6862b7808b..72eb7028496 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -663,6 +663,9 @@ When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a signal is declared but never explicitly used in the class. + + When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when an overridden static member from a trait is not declared with the [code]static[/code] keyword. + When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a local variable is unused. diff --git a/modules/gdscript/editor/gdscript_docgen.cpp b/modules/gdscript/editor/gdscript_docgen.cpp index a17371d143f..430afefe9b5 100644 --- a/modules/gdscript/editor/gdscript_docgen.cpp +++ b/modules/gdscript/editor/gdscript_docgen.cpp @@ -132,6 +132,7 @@ void GDScriptDocGen::_doctype_from_gdtype(const GDType &p_gdtype, String &r_type } r_type = "Object"; return; + case GDType::TRAIT: case GDType::CLASS: if (p_gdtype.is_meta_type) { r_type = GDScript::get_class_static(); @@ -377,6 +378,10 @@ void GDScriptDocGen::_generate_docs(GDScript *p_script, const GDP::ClassNode *p_ for (const GDP::ClassNode::Member &member : p_class->members) { switch (member.type) { case GDP::ClassNode::Member::CLASS: { + if (p_class->type == GDP::Node::TRAIT) { + // Trait do not have scripts for their inner classes. + continue; + } const GDP::ClassNode *inner_class = member.m_class; const StringName &class_name = inner_class->identifier->name; diff --git a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp index c2e0dede7ed..914a7a5496e 100644 --- a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp +++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp @@ -147,6 +147,7 @@ void GDScriptEditorTranslationParserPlugin::_traverse_class(const GDScriptParser const GDScriptParser::ClassNode::Member &m = p_class->members[i]; // Other member types can't contain translatable strings. switch (m.type) { + case GDScriptParser::ClassNode::Member::TRAIT: case GDScriptParser::ClassNode::Member::CLASS: _traverse_class(m.m_class); break; diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 307cdf638d0..c2389e35625 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -2769,7 +2769,8 @@ Vector GDScriptLanguage::get_reserved_words() const { "namespace", // Reserved for potential future use. "signal", "static", - "trait", // Reserved for potential future use. + "trait", + "uses", "var", // Other keywords. "await", diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 176afa7651f..a536bebd4b0 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -246,6 +246,7 @@ bool GDScriptAnalyzer::has_member_name_conflict_in_script_class(const StringName member->type == GDScriptParser::ClassNode::Member::CONSTANT || member->type == GDScriptParser::ClassNode::Member::ENUM || member->type == GDScriptParser::ClassNode::Member::ENUM_VALUE || + member->type == GDScriptParser::ClassNode::Member::TRAIT || member->type == GDScriptParser::ClassNode::Member::CLASS || member->type == GDScriptParser::ClassNode::Member::SIGNAL) { return true; @@ -297,7 +298,7 @@ Error GDScriptAnalyzer::check_native_member_name_conflict(const StringName &p_me Error GDScriptAnalyzer::check_class_member_name_conflict(const GDScriptParser::ClassNode *p_class_node, const StringName &p_member_name, const GDScriptParser::Node *p_member_node) { /// @todo Check outer classes for static members only const GDScriptParser::DataType *current_data_type = &p_class_node->base_type; - while (current_data_type && current_data_type->kind == GDScriptParser::DataType::Kind::CLASS) { + while (current_data_type && (current_data_type->kind == GDScriptParser::DataType::Kind::CLASS || current_data_type->kind == GDScriptParser::DataType::TRAIT)) { GDScriptParser::ClassNode *current_class_node = current_data_type->class_type; if (has_member_name_conflict_in_script_class(p_member_name, current_class_node, p_member_node)) { String parent_class_name = current_class_node->fqcn; @@ -422,11 +423,15 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c class_type.is_meta_type = true; class_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; class_type.kind = GDScriptParser::DataType::CLASS; + if (p_class->type == GDScriptParser::Node::TRAIT) { + class_type.kind = GDScriptParser::DataType::TRAIT; + } class_type.class_type = p_class; class_type.script_path = parser->script_path; class_type.builtin_type = Variant::OBJECT; p_class->set_datatype(class_type); + Ref ext_parser_ref; GDScriptParser::DataType result; if (!p_class->extends_used) { result.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; @@ -462,6 +467,7 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c } #endif // DEBUG_ENABLED + ext_parser_ref = ext_parser; base = ext_parser->get_parser()->head->get_datatype(); } else { if (p_class->extends.is_empty()) { @@ -496,6 +502,7 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c } #endif // DEBUG_ENABLED + ext_parser_ref = base_parser; base = base_parser->get_parser()->head->get_datatype(); } } else if (ProjectSettings::get_singleton()->has_autoload(name) && ProjectSettings::get_singleton()->get_autoload(name).is_singleton) { @@ -523,6 +530,7 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c } #endif // DEBUG_ENABLED + ext_parser_ref = info_parser; base = info_parser->get_parser()->head->get_datatype(); } else if (class_exists(name)) { if (Engine::get_singleton()->has_singleton(name)) { @@ -557,6 +565,9 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c switch (member.type) { case GDScriptParser::ClassNode::Member::CLASS: break; // OK. + case GDScriptParser::ClassNode::Member::TRAIT: + push_error(vformat(R"(Could not inherit from trait "%s".)", name), id); + return ERR_PARSE_ERROR; case GDScriptParser::ClassNode::Member::CONSTANT: if (member_datatype.kind != GDScriptParser::DataType::SCRIPT && member_datatype.kind != GDScriptParser::DataType::CLASS) { push_error(vformat(R"(Constant "%s" is not a preloaded script or class.)", name), id); @@ -612,6 +623,12 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c return ERR_PARSE_ERROR; } + // Check if inherits trait Script. + if (result.kind == GDScriptParser::DataType::TRAIT) { + push_error("Could not inherit from trait.", p_class); + return ERR_PARSE_ERROR; + } + // Check for cyclic inheritance. const GDScriptParser::ClassNode *base_class = result.class_type; while (base_class) { @@ -634,6 +651,33 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c parser->current_class = previous_class; + // Copy over used traits_fqtn since it is also inherited. + if (result.class_type) { + if (!result.class_type->resolved_uses) { + if (ext_parser_ref.is_null()) { + // Extending class in script. + resolve_class_uses(result.class_type); + } else { + GDScriptAnalyzer *ext_analyzer = ext_parser_ref->get_analyzer(); + GDScriptParser *ext_parser = ext_parser_ref->get_parser(); + int error_count = ext_parser->errors.size(); + ext_analyzer->resolve_class_uses(result.class_type); + if (ext_parser->errors.size() > error_count) { + push_error("Could not resolve extending class.", p_class); + return ERR_PARSE_ERROR; + } + } + } + if (!result.class_type->resolved_uses && !result.class_type->resolving_uses) { + push_error("Could not resolve uses from extending class.", p_class); + } + for (String trait_fqtn : result.class_type->traits_fqtn) { + if (!p_class->traits_fqtn.has(trait_fqtn)) { + p_class->traits_fqtn.append(trait_fqtn); + } + } + } + return OK; } @@ -645,11 +689,16 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c if (p_recursive) { for (int i = 0; i < p_class->members.size(); i++) { - if (p_class->members[i].type == GDScriptParser::ClassNode::Member::CLASS) { - err = resolve_class_inheritance(p_class->members[i].m_class, true); - if (err) { - return err; - } + switch (p_class->members[i].type) { + case GDScriptParser::ClassNode::Member::TRAIT: + case GDScriptParser::ClassNode::Member::CLASS: + err = resolve_class_inheritance(p_class->members[i].m_class, true); + if (err) { + return err; + } + break; + default: + break; } } } @@ -666,7 +715,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type return bad_type; } - if (p_type->get_datatype().is_resolving()) { + if (p_type->get_datatype().is_resolving() && parser->head->type != GDScriptParser::Node::TRAIT) { push_error(R"(Could not resolve datatype: Cyclic reference.)", p_type); return bad_type; } @@ -709,7 +758,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type Ref gdscript = local.constant->initializer->reduced_value; if (gdscript.is_valid()) { Ref ref = parser->get_depended_parser_for(gdscript->get_script_path()); - if (ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED) != OK) { + if (ref->raise_status(GDScriptParserRef::USES_SOLVED) != OK) { push_error(vformat(R"(Could not parse script from "%s".)", gdscript->get_script_path()), first_id); return bad_type; } @@ -800,7 +849,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type String ext = path.get_extension(); if (ext == GDScriptLanguage::get_singleton()->get_extension()) { Ref ref = parser->get_depended_parser_for(path); - if (ref.is_null() || ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED) != OK) { + if (ref.is_null() || ref->raise_status(GDScriptParserRef::USES_SOLVED) != OK) { push_error(vformat(R"(Could not parse global class "%s" from "%s".)", first, ScriptServer::get_global_class_path(first)), p_type); return bad_type; } @@ -824,7 +873,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type } } } - } else if (ResourceLoader::get_resource_type(autoload.path) == "GDScript") { + } else if (ResourceLoader::get_resource_type(autoload.path) == "GDScript" || ResourceLoader::get_resource_type(autoload.path) == "GDScriptTrait") { script_path = autoload.path; } if (script_path.is_empty()) { @@ -835,7 +884,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type push_error(vformat(R"(The referenced autoload "%s" (from "%s") could not be loaded.)", first, script_path), p_type); return bad_type; } - if (ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED) != OK) { + if (ref->raise_status(GDScriptParserRef::USES_SOLVED) != OK) { push_error(vformat(R"(Could not parse singleton "%s" from "%s".)", first, script_path), p_type); return bad_type; } @@ -868,6 +917,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type GDScriptParser::ClassNode::Member member = script_class->get_member(first); switch (member.type) { + case GDScriptParser::ClassNode::Member::TRAIT: case GDScriptParser::ClassNode::Member::CLASS: result = member.get_datatype(); found = true; @@ -885,7 +935,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type Ref gdscript = member.constant->initializer->reduced_value; if (gdscript.is_valid()) { Ref ref = parser->get_depended_parser_for(gdscript->get_script_path()); - if (ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED) != OK) { + if (ref->raise_status(GDScriptParserRef::USES_SOLVED) != OK) { push_error(vformat(R"(Could not parse script from "%s".)", gdscript->get_script_path()), p_type); return bad_type; } @@ -912,7 +962,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type } if (p_type->type_chain.size() > 1) { - if (result.kind == GDScriptParser::DataType::CLASS) { + if (result.kind == GDScriptParser::DataType::CLASS || result.kind == GDScriptParser::DataType::TRAIT) { for (int i = 1; i < p_type->type_chain.size(); i++) { GDScriptParser::DataType base = result; reduce_identifier_from_base(p_type->type_chain[i], &base); @@ -987,7 +1037,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, } }); - if (member.get_datatype().is_resolving()) { + if (member.get_datatype().is_resolving() && p_class->type != GDScriptParser::Node::TRAIT) { push_error(vformat(R"(Could not resolve member "%s": Cyclic reference.)", member.get_name()), p_source); return; } @@ -1252,11 +1302,13 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, member.enum_value.identifier->set_datatype(make_class_enum_type(UNNAMED_ENUM, p_class, parser->script_path, false)); } break; + case GDScriptParser::ClassNode::Member::TRAIT: case GDScriptParser::ClassNode::Member::CLASS: check_class_member_name_conflict(p_class, member.m_class->identifier->name, member.m_class); // If it's already resolving, that's ok. if (!member.m_class->base_type.is_resolving()) { resolve_class_inheritance(member.m_class, p_source); + resolve_class_uses(member.m_class, p_source); } break; case GDScriptParser::ClassNode::Member::GROUP: @@ -1313,9 +1365,10 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas if (resolve_class_inheritance(p_class) != OK) { return; } + resolve_class_uses(p_class); GDScriptParser::DataType base_type = p_class->base_type; - if (base_type.kind == GDScriptParser::DataType::CLASS) { + if (base_type.kind == GDScriptParser::DataType::CLASS || base_type.kind == GDScriptParser::DataType::TRAIT) { GDScriptParser::ClassNode *base_class = base_type.class_type; resolve_class_interface(base_class, p_class); } @@ -1326,7 +1379,7 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas #ifdef DEBUG_ENABLED if (!has_static_data) { GDScriptParser::ClassNode::Member member = p_class->members[i]; - if (member.type == GDScriptParser::ClassNode::Member::CLASS) { + if (member.type == GDScriptParser::ClassNode::Member::CLASS || member.type == GDScriptParser::ClassNode::Member::TRAIT) { has_static_data = member.m_class->has_static_data; } } @@ -1353,9 +1406,13 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas if (p_recursive) { for (int i = 0; i < p_class->members.size(); i++) { - GDScriptParser::ClassNode::Member member = p_class->members[i]; - if (member.type == GDScriptParser::ClassNode::Member::CLASS) { - resolve_class_interface(member.m_class, true); + switch (p_class->members[i].type) { + case GDScriptParser::ClassNode::Member::TRAIT: + case GDScriptParser::ClassNode::Member::CLASS: + resolve_class_interface(p_class->members[i].m_class, true); + break; + default: + break; } } } @@ -1371,6 +1428,9 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co if (p_class->resolved_body) { return; } + if (!p_class->resolved_uses) { + resolve_class_uses(p_class); + } if (!parser->has_class(p_class)) { if (parser_ref.is_null()) { @@ -1523,7 +1583,7 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co } } else if (member.type == GDScriptParser::ClassNode::Member::SIGNAL) { #ifdef DEBUG_ENABLED - if (member.signal->usages == 0) { + if (member.signal->usages == 0 && p_class->type != GDScriptParser::Node::TRAIT) { parser->push_warning(member.signal->identifier, GDScriptWarning::UNUSED_SIGNAL, member.signal->identifier->name); } #endif // DEBUG_ENABLED @@ -1581,9 +1641,408 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, bo if (p_recursive) { for (int i = 0; i < p_class->members.size(); i++) { - GDScriptParser::ClassNode::Member member = p_class->members[i]; - if (member.type == GDScriptParser::ClassNode::Member::CLASS) { - resolve_class_body(member.m_class, true); + switch (p_class->members[i].type) { + case GDScriptParser::ClassNode::Member::TRAIT: + case GDScriptParser::ClassNode::Member::CLASS: + resolve_class_body(p_class->members[i].m_class, true); + break; + default: + break; + } + } + } +} + +void GDScriptAnalyzer::resolve_class_uses(GDScriptParser::ClassNode *p_class, const GDScriptParser::Node *p_source) { + if (p_class->resolved_uses || p_class->resolving_uses) { + return; + } + + if (p_source == nullptr && parser->has_class(p_class)) { + p_source = p_class; + } + + if (!parser->has_class(p_class)) { + String script_path = p_class->get_datatype().script_path; + Ref parser_ref = parser->get_depended_parser_for(script_path); + if (parser_ref.is_null()) { + push_error(vformat(R"(Could not find script "%s".)", script_path), p_source); + return; + } + + Error err = parser_ref->raise_status(GDScriptParserRef::PARSED); + if (err) { + push_error(vformat(R"(Could not resolve script "%s": %s.)", script_path, error_names[err]), p_source); + return; + } + + ERR_FAIL_COND_MSG(!parser_ref->get_parser()->has_class(p_class), R"(Parser bug: Mismatched external parser.)"); + + GDScriptAnalyzer *other_analyzer = parser_ref->get_analyzer(); + GDScriptParser *other_parser = parser_ref->get_parser(); + + int error_count = other_parser->errors.size(); + other_analyzer->resolve_class_body(p_class); + if (other_parser->errors.size() > error_count) { + push_error(vformat(R"(Could not resolve class "%s".)", p_class->fqcn), p_source); + } + return; + } + + p_class->resolving_uses = true; + + Vector used_traits; + for (GDScriptParser::UsesNode *uses : p_class->traits) { + GDScriptParser::ClassNode *trait = nullptr; + + // Find trait used. + String trait_name = uses->path; + GDScriptParser::Node *trait_name_node = uses; + Ref ext_parser_ref; + int use_index = 0; + if (uses->name.is_empty()) { + if (uses->path.is_empty()) { + push_error("Could not resolve an empty trait path.", uses); + continue; + } + if (uses->path.is_relative_path()) { + uses->path = parser->script_path.get_base_dir().path_join(uses->path).simplify_path(); + } + ext_parser_ref = parser->get_depended_parser_for(uses->path); + if (ext_parser_ref.is_null()) { + push_error(vformat(R"(Could not resolve trait's path "%s".)", uses->path), uses); + continue; + } + Error err = ext_parser_ref->raise_status(GDScriptParserRef::BODY_SOLVED); + if (err) { + push_error(vformat(R"(Could not resolve trait's body from "%s".)", uses->path), uses); + continue; + } + trait = ext_parser_ref->get_parser()->head; + } else { + GDScriptParser::IdentifierNode *id = uses->name[use_index++]; + const StringName &name = id->name; + trait_name = name; + trait_name_node = id; + if (ScriptServer::is_global_class(name)) { + String base_path = ScriptServer::get_global_class_path(name); + uses->path = base_path; + ext_parser_ref = parser->get_depended_parser_for(base_path); + if (ext_parser_ref.is_null()) { + push_error(vformat(R"(Could not resolve trait "%s".)", name), id); + continue; + } + Error err = ext_parser_ref->raise_status(GDScriptParserRef::BODY_SOLVED); + if (err) { + push_error(vformat(R"(Could not resolve trait's body from "%s".)", name), id); + continue; + } + trait = ext_parser_ref->get_parser()->head; + } else if (ProjectSettings::get_singleton()->has_autoload(name) && ProjectSettings::get_singleton()->get_autoload(name).is_singleton) { + push_error(vformat(R"("%s" is an autoload, so it cannot be used as a trait.)", name), id); + continue; + } else if (class_exists(name)) { + push_error(vformat(R"(Native class "%s" cannot be used as a trait.)", name), id); + continue; + } else { + // Look for other classes in script. + bool found = false; + Error err = OK; + List script_classes; + get_class_node_current_scope_classes(p_class, &script_classes, id); + for (GDScriptParser::ClassNode *look_class : script_classes) { + int error_count = parser->errors.size(); + if (look_class->identifier && look_class->identifier->name == name) { + resolve_class_inheritance(look_class, id); + resolve_class_uses(look_class, id); + resolve_class_interface(look_class, id); + resolve_class_body(look_class, id); + if (parser->errors.size() > error_count) { + break; + } + trait = look_class; + found = true; + break; + } + if (look_class->has_member(name)) { + resolve_class_member(look_class, name, id); + GDScriptParser::ClassNode::Member member = look_class->get_member(name); + GDScriptParser::DataType member_datatype = member.get_datatype(); + switch (member.type) { + case GDScriptParser::ClassNode::Member::TRAIT: + case GDScriptParser::ClassNode::Member::CLASS: + resolve_class_inheritance(static_cast(member.get_source_node()), id); + resolve_class_uses(static_cast(member.get_source_node()), id); + resolve_class_interface(static_cast(member.get_source_node()), id); + resolve_class_body(static_cast(member.get_source_node()), id); + break; + 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; + break; + } + } + if (!found) { + if (err == OK) { + push_error(vformat(R"(Could not find trait "%s".)", name), id); + } + continue; + } + uses->path = parser->script_path; + } + } + Error err = OK; + for (; use_index < uses->name.size(); use_index++) { + GDScriptParser::IdentifierNode *id = uses->name[use_index]; + const StringName &name = id->name; + trait_name += "." + name; + trait_name_node = id; + if (trait->has_member(name)) { + GDScriptParser::ClassNode::Member member = trait->get_member(name); + GDScriptParser::DataType member_datatype = member.get_datatype(); + int error_count = parser->errors.size(); + switch (member.type) { + case GDScriptParser::ClassNode::Member::CLASS: + case GDScriptParser::ClassNode::Member::TRAIT: { + if (ext_parser_ref.is_null()) { + // class/trait is in script. + resolve_class_inheritance(static_cast(member.get_source_node()), id); + resolve_class_uses(static_cast(member.get_source_node()), id); + resolve_class_interface(static_cast(member.get_source_node()), id); + resolve_class_body(static_cast(member.get_source_node()), id); + } else { + GDScriptAnalyzer *ext_analyzer = ext_parser_ref->get_analyzer(); + GDScriptParser *ext_parser = ext_parser_ref->get_parser(); + int ext_error_count = ext_parser->errors.size(); + ext_analyzer->resolve_class_inheritance(static_cast(member.get_source_node())); + ext_analyzer->resolve_class_uses(static_cast(member.get_source_node())); + ext_analyzer->resolve_class_interface(static_cast(member.get_source_node())); + ext_analyzer->resolve_class_body(static_cast(member.get_source_node())); + if (ext_parser->errors.size() > ext_error_count) { + push_error(vformat(R"(Could not resolve external %s member "%s".)", member.get_type_name(), member.get_name()), id); + err = ERR_PARSE_ERROR; + } + } + } break; + 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 (parser->errors.size() > error_count) { + push_error(vformat(R"(Could not resolve "%s" chain.)", trait_name), trait_name_node); + err = ERR_PARSE_ERROR; + } + if (err) { + break; + } + trait = member.m_class; + } else { + push_error(vformat(R"(Could not resolve "%s" chain.)", trait_name), trait_name_node); + err = ERR_PARSE_ERROR; + } + } + if (err) { + continue; + } + uses->fqtn = trait->fqcn; + + if (trait->type != GDScriptParser::Node::TRAIT) { + push_error(vformat(R"("%s" is not a trait.)", trait_name), trait_name_node); + continue; + } + + if (p_class->fqcn == trait->fqcn) { + push_error(vformat(R"("%s" trait can not use itself.)", trait_name), trait_name_node); + continue; + } + + // Skip if trait is already implemented traits. + if (p_class->traits_fqtn.has(trait->fqcn)) { + if (used_traits.has(trait)) { + push_error(vformat(R"("%s" trait use is already declared.)", trait_name), trait_name_node); + } else { + used_traits.push_back(trait); + } + continue; + } + + // 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; + } + + // Check for cohesion with implemented traits. + bool trait_cohesion = true; + for (const GDScriptParser::ClassNode *used_trait : used_traits) { + for (auto &key_value : used_trait->members_indices) { + const StringName &member_name = key_value.key; + if (trait->has_member(member_name)) { + // Check if copied over from a common trait. + GDScriptParser::ClassNode::Member trait_member = trait->get_member(member_name); + bool copied_over = trait_member.get_source_node()->trait_origin.has(p_class->fqcn); + for (const String &trait_fqcn : p_class->traits_fqtn) { + if (!copied_over && trait_member.get_source_node()->trait_origin.has(trait_fqcn)) { + copied_over = true; + break; + } + } + if (!copied_over) { + trait_cohesion = false; + } + } + } + if (!trait_cohesion) { + break; + } + } + if (!trait_cohesion) { + push_error(vformat(R"(Can not use "%s" trait since it shadows members from previous traits.)", trait_name), trait_name_node); + continue; + } + + // Check for inheritance is compatible. + bool inheritance_match = true; + if (trait->extends_used) { + const GDScriptParser::DataType trait_base_type = trait->base_type; + if (!p_class->extends_used) { + if (trait_base_type.kind != GDScriptParser::DataType::NATIVE) { + inheritance_match = false; + } else if (trait_base_type.native_type != SNAME("RefCounted") && !ClassDB::is_parent_class(SNAME("RefCounted"), trait_base_type.native_type)) { + inheritance_match = false; + } + } else { + const GDScriptParser::DataType class_base_type = p_class->base_type; + if (trait_base_type.kind == GDScriptParser::DataType::NATIVE) { + if (class_base_type.class_type != nullptr) { + GDScriptParser::ClassNode *parent = class_base_type.class_type; + while (parent != nullptr) { + if (parent->base_type.kind == GDScriptParser::DataType::NATIVE) { + if (trait_base_type.native_type == parent->base_type.native_type) { + inheritance_match = true; + } else { + inheritance_match = ClassDB::is_parent_class(parent->base_type.native_type, trait_base_type.native_type); + } + break; + } + parent = parent->base_type.class_type; + } + } else { + if (trait_base_type.native_type == class_base_type.native_type) { + inheritance_match = true; + } else { + inheritance_match = ClassDB::is_parent_class(class_base_type.native_type, trait_base_type.native_type); + } + } + } else { + inheritance_match = trait_base_type == class_base_type; + if (!inheritance_match && class_base_type.class_type != nullptr) { + GDScriptParser::ClassNode *parent = class_base_type.class_type; + while (parent != nullptr) { + if (trait_base_type == parent->base_type) { + inheritance_match = true; + break; + } + parent = parent->base_type.class_type; + } + } + } + } + } 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; + } + } + } + } + if (!inheritance_match) { + String trait_extends_name = "RefCounted"; + if (trait->extends_used) { + trait_extends_name = trait->base_type.to_string(); + } + push_error(vformat(R"("%s" trait extending "%s" does not match class inheritance.)", trait_name, trait_extends_name), trait_name_node); + continue; + } + + // Trait Copy over, Extending and overriding class members. + extend_class(p_class, trait, trait_name_node, trait_name); + + // Ensures cached external parser class, resolving dependencies issues. + if (!ext_parser_ref.is_null()) { + for (const KeyValue> &E : ext_parser_ref->get_analyzer()->external_class_parser_cache) { + external_class_parser_cache.insert(E.key, E.value); + } + } + + // Store references to used traits for type checks. + p_class->traits_fqtn.append(trait->fqcn); + for (String other_trait_fqcn : trait->traits_fqtn) { + if (!p_class->traits_fqtn.has(other_trait_fqcn)) { + p_class->traits_fqtn.append(other_trait_fqcn); + } + } + uses->traits_fqtn = trait->traits_fqtn; + + used_traits.append(trait); + } + + p_class->resolving_uses = false; + p_class->resolved_uses = true; +} + +void GDScriptAnalyzer::resolve_class_uses(GDScriptParser::ClassNode *p_class, bool p_recursive) { + resolve_class_uses(p_class); + + if (p_recursive) { + for (int i = 0; i < p_class->members.size(); i++) { + switch (p_class->members[i].type) { + case GDScriptParser::ClassNode::Member::TRAIT: + case GDScriptParser::ClassNode::Member::CLASS: + resolve_class_uses(p_class->members[i].m_class, true); + break; + default: + break; } } } @@ -1595,9 +2054,11 @@ void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node, bool p_is_root switch (p_node->type) { case GDScriptParser::Node::NONE: break; // Unreachable. + case GDScriptParser::Node::TRAIT: case GDScriptParser::Node::CLASS: // NOTE: Currently this route is never executed, `resolve_class_*()` is called directly. if (OK == resolve_class_inheritance(static_cast(p_node), true)) { + resolve_class_uses(static_cast(p_node), true); resolve_class_interface(static_cast(p_node), true); resolve_class_body(static_cast(p_node), true); } @@ -1671,6 +2132,7 @@ void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node, bool p_is_root case GDScriptParser::Node::FUNCTION: case GDScriptParser::Node::PASS: case GDScriptParser::Node::SIGNAL: + case GDScriptParser::Node::USES: // Nothing to do. break; } @@ -1739,7 +2201,7 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * StringName function_name = p_function->identifier != nullptr ? p_function->identifier->name : StringName(); - if (p_function->get_datatype().is_resolving()) { + if (p_function->get_datatype().is_resolving() && parser->head->type != GDScriptParser::Node::TRAIT) { push_error(vformat(R"(Could not resolve function "%s": Cyclic reference.)", function_name), p_source); return; } @@ -1787,7 +2249,7 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * resolve_parameter(p_function->parameters[i]); method_info.arguments.push_back(p_function->parameters[i]->get_datatype().to_property_info(p_function->parameters[i]->identifier->name)); #ifdef DEBUG_ENABLED - if (p_function->parameters[i]->usages == 0 && !String(p_function->parameters[i]->identifier->name).begins_with("_") && !p_function->is_abstract) { + 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); } is_shadowing(p_function->parameters[i]->identifier, "function parameter", true); @@ -1996,19 +2458,19 @@ void GDScriptAnalyzer::resolve_function_body(GDScriptParser::FunctionNode *p_fun p_function->resolved_body = true; if (p_function->body->statements.is_empty()) { - // Non-abstract functions must have a body. - if (p_function->source_lambda != nullptr) { - push_error(R"(A lambda function must have a ":" followed by a body.)", p_function); - } else if (!p_function->is_abstract) { - push_error(R"(A function must either have a ":" followed by a body, or be marked as "@abstract".)", p_function); - } - return; - } else { - // Abstract functions must not have a body. - if (p_function->is_abstract) { - push_error(R"(An abstract function cannot have a body.)", p_function->body); + if (parser->current_class->type != GDScriptParser::Node::TRAIT) { + // Non-abstract functions must have a body. + if (p_function->source_lambda != nullptr) { + push_error(R"(A lambda function must have a ":" followed by a body.)", p_function); + } else if (!p_function->is_abstract) { + push_error(R"(A function must either have a ":" followed by a body, or be marked as "@abstract".)", p_function); + } return; } + } else if (p_function->is_abstract) { + // Abstract functions must not have a body. + push_error(R"(An abstract function cannot have a body.)", p_function->body); + return; } GDScriptParser::FunctionNode *previous_function = parser->current_function; @@ -2023,7 +2485,7 @@ void GDScriptAnalyzer::resolve_function_body(GDScriptParser::FunctionNode *p_fun // Use the suite inferred type if return isn't explicitly set. p_function->set_datatype(p_function->body->get_datatype()); } else if (p_function->get_datatype().is_hard_type() && (p_function->get_datatype().kind != GDScriptParser::DataType::BUILTIN || p_function->get_datatype().builtin_type != Variant::NIL)) { - if (!p_function->body->has_return && (p_is_lambda || p_function->identifier->name != GDScriptLanguage::get_singleton()->strings._init)) { + if (!p_function->is_bodyless && !p_function->body->has_return && (p_is_lambda || p_function->identifier->name != GDScriptLanguage::get_singleton()->strings._init)) { push_error(R"(Not all code paths return a value.)", p_function); } } @@ -2667,6 +3129,7 @@ void GDScriptAnalyzer::reduce_expression(GDScriptParser::ExpressionNode *p_expre case GDScriptParser::Node::ASSERT: case GDScriptParser::Node::BREAK: case GDScriptParser::Node::BREAKPOINT: + case GDScriptParser::Node::TRAIT: case GDScriptParser::Node::CLASS: case GDScriptParser::Node::CONSTANT: case GDScriptParser::Node::CONTINUE: @@ -2685,6 +3148,7 @@ void GDScriptAnalyzer::reduce_expression(GDScriptParser::ExpressionNode *p_expre case GDScriptParser::Node::TYPE: case GDScriptParser::Node::VARIABLE: case GDScriptParser::Node::WHILE: + case GDScriptParser::Node::USES: ERR_FAIL_MSG("Reaching unreachable case"); } @@ -2915,9 +3379,14 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig return; } else if (p_assignment->assignee->type == GDScriptParser::Node::SUBSCRIPT && static_cast(p_assignment->assignee)->base->is_constant) { const GDScriptParser::DataType &base_type = static_cast(p_assignment->assignee)->base->datatype; - if (base_type.kind != GDScriptParser::DataType::SCRIPT && base_type.kind != GDScriptParser::DataType::CLASS) { // Static variables. - push_error("Cannot assign a new value to a constant.", p_assignment->assignee); - return; + switch (base_type.kind) { + case GDScriptParser::DataType::SCRIPT: + case GDScriptParser::DataType::TRAIT: + case GDScriptParser::DataType::CLASS: // Static variables. + break; + default: + push_error("Cannot assign a new value to a constant.", p_assignment->assignee); + return; } } else if (assignee_type.is_read_only) { push_error("Cannot assign a new value to a read-only property.", p_assignment->assignee); @@ -3603,6 +4072,11 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a return; } + if (!is_self && base_type.is_constant && base_type.kind == GDScriptParser::DataType::TRAIT) { + push_error(vformat(R"*(Cannot call a trait's functions directly; call through a class that uses it instead.)*"), p_call); + return; + } + int default_arg_count = 0; BitField method_flags = {}; GDScriptParser::DataType return_type; @@ -3797,6 +4271,10 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) { cast_type.get_container_element_type_or_variant(0), cast_type.get_container_element_type_or_variant(1)); } + if (p_cast->operand->get_datatype().kind == GDScriptParser::DataType::TRAIT || cast_type.kind == GDScriptParser::DataType::TRAIT) { + 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); + } + if (!cast_type.is_variant()) { GDScriptParser::DataType op_type = p_cast->operand->get_datatype(); if (op_type.is_variant() || !op_type.is_hard_type()) { @@ -3813,6 +4291,9 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) { valid = true; } else if (op_type.kind == GDScriptParser::DataType::BUILTIN && cast_type.kind == GDScriptParser::DataType::BUILTIN) { valid = Variant::can_convert_strict(op_type.builtin_type, cast_type.builtin_type); + } else if (cast_type.kind == GDScriptParser::DataType::TRAIT) { + mark_node_unsafe(p_cast); + valid = true; } else if (op_type.kind != GDScriptParser::DataType::BUILTIN && cast_type.kind != GDScriptParser::DataType::BUILTIN) { valid = is_type_compatible(cast_type, op_type) || is_type_compatible(op_type, cast_type); } @@ -3880,7 +4361,6 @@ void GDScriptAnalyzer::reduce_get_node(GDScriptParser::GetNodeNode *p_get_node) GDScriptParser::DataType GDScriptAnalyzer::make_global_class_meta_type(const StringName &p_class_name, const GDScriptParser::Node *p_source) { GDScriptParser::DataType type; - String path = ScriptServer::get_global_class_path(p_class_name); String ext = path.get_extension(); if (ext == GDScriptLanguage::get_singleton()->get_extension()) { @@ -3892,7 +4372,7 @@ GDScriptParser::DataType GDScriptAnalyzer::make_global_class_meta_type(const Str return type; } - Error err = ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED); + Error err = ref->raise_status(GDScriptParserRef::USES_SOLVED); if (err) { push_error(vformat(R"(Could not resolve class "%s", because of a parser error.)", p_class_name), p_source); type.type_source = GDScriptParser::DataType::UNDETECTED; @@ -4163,7 +4643,11 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod if (p_base == nullptr && script_class->identifier && script_class->identifier->name == name) { reduce_identifier_from_base_set_class(p_identifier, script_class->get_datatype()); if (script_class->outer != nullptr) { - p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CLASS; + if (script_class->type == GDScriptParser::Node::CLASS) { + p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CLASS; + } else if (script_class->type == GDScriptParser::Node::TRAIT) { + p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_TRAIT; + } } return; } @@ -4232,6 +4716,12 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod } } break; + case GDScriptParser::ClassNode::Member::TRAIT: { + reduce_identifier_from_base_set_class(p_identifier, member.get_datatype()); + p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_TRAIT; + return; + } + case GDScriptParser::ClassNode::Member::CLASS: { reduce_identifier_from_base_set_class(p_identifier, member.get_datatype()); p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CLASS; @@ -4436,6 +4926,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident } break; case GDScriptParser::IdentifierNode::UNDEFINED_SOURCE: case GDScriptParser::IdentifierNode::MEMBER_FUNCTION: + case GDScriptParser::IdentifierNode::MEMBER_TRAIT: case GDScriptParser::IdentifierNode::MEMBER_CLASS: case GDScriptParser::IdentifierNode::NATIVE_CLASS: break; @@ -4505,6 +4996,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident case GDScriptParser::IdentifierNode::MEMBER_CONSTANT: case GDScriptParser::IdentifierNode::MEMBER_FUNCTION: case GDScriptParser::IdentifierNode::MEMBER_SIGNAL: + case GDScriptParser::IdentifierNode::MEMBER_TRAIT: case GDScriptParser::IdentifierNode::MEMBER_CLASS: case GDScriptParser::IdentifierNode::INHERITED_VARIABLE: case GDScriptParser::IdentifierNode::STATIC_VARIABLE: @@ -4562,10 +5054,10 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident result.kind = GDScriptParser::DataType::NATIVE; result.builtin_type = Variant::OBJECT; result.native_type = SNAME("Node"); - if (ResourceLoader::get_resource_type(autoload.path) == "GDScript") { + if (ResourceLoader::get_resource_type(autoload.path) == "GDScript" || ResourceLoader::get_resource_type(autoload.path) == "GDScriptTrait") { Ref single_parser = parser->get_depended_parser_for(autoload.path); if (single_parser.is_valid()) { - Error err = single_parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED); + Error err = single_parser->raise_status(GDScriptParserRef::USES_SOLVED); if (err == OK) { result = type_from_metatype(single_parser->get_parser()->head->get_datatype()); } @@ -4579,7 +5071,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident if (scr.is_valid()) { Ref single_parser = parser->get_depended_parser_for(scr->get_script_path()); if (single_parser.is_valid()) { - Error err = single_parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED); + Error err = single_parser->raise_status(GDScriptParserRef::USES_SOLVED); if (err == OK) { result = type_from_metatype(single_parser->get_parser()->head->get_datatype()); } @@ -4728,7 +5220,7 @@ void GDScriptAnalyzer::reduce_preload(GDScriptParser::PreloadNode *p_preload) { // Must load GDScript separately to permit cyclic references // as ResourceLoader::load() detects and rejects those. const String &res_type = ResourceLoader::get_resource_type(p_preload->resolved_path); - if (res_type == "GDScript") { + if (res_type == "GDScript" || res_type == "GDScriptTrait") { Error err = OK; Ref res = get_depended_shallow_script(p_preload->resolved_path, err); p_preload->resource = res; @@ -4784,6 +5276,12 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri } GDScriptParser::DataType base_type = p_subscript->base->get_datatype(); + + if (base_type.is_constant && base_type.kind == GDScriptParser::DataType::TRAIT) { + push_error(vformat(R"*(Cannot access a trait's members directly; access through a class that uses it instead.)*"), p_subscript->attribute); + return; + } + bool valid = false; // If the base is a metatype, use the analyzer instead. @@ -5194,6 +5692,11 @@ void GDScriptAnalyzer::reduce_type_test(GDScriptParser::TypeTestNode *p_type_tes return; } + if (test_type.kind == GDScriptParser::DataType::TRAIT) { + push_error("Can't test for traits.", p_type_test->operand); + return; + } + if (p_type_test->operand->is_constant) { p_type_test->is_constant = true; p_type_test->reduced_value = false; @@ -5417,7 +5920,7 @@ Array GDScriptAnalyzer::make_array_from_element_datatype(const GDScriptParser::D if (p_element_datatype.builtin_type == Variant::OBJECT) { Ref