diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index 4828906debe6..c49cb991a412 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -673,6 +673,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 3d57db7f6472..0c5d15687f17 100644
--- a/modules/gdscript/editor/gdscript_docgen.cpp
+++ b/modules/gdscript/editor/gdscript_docgen.cpp
@@ -124,6 +124,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();
@@ -369,6 +370,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 7026797c5fb0..638395d264bc 100644
--- a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp
+++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp
@@ -144,6 +144,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 d8664bb79663..7b85c53a308b 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -2752,7 +2752,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 08a6005faab2..0d2bf7ce0dfe 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -238,6 +238,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;
@@ -289,7 +290,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;
@@ -414,11 +415,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;
@@ -454,6 +459,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()) {
@@ -488,6 +494,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) {
@@ -515,6 +522,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)) {
@@ -549,6 +557,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);
@@ -604,6 +615,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) {
@@ -626,6 +643,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;
}
@@ -637,11 +681,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;
}
}
}
@@ -658,7 +707,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;
}
@@ -701,7 +750,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;
}
@@ -792,7 +841,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;
}
@@ -816,7 +865,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()) {
@@ -827,7 +876,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;
}
@@ -860,6 +909,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;
@@ -877,7 +927,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;
}
@@ -904,7 +954,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);
@@ -979,7 +1029,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;
}
@@ -1244,11 +1294,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:
@@ -1305,9 +1357,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);
}
@@ -1318,7 +1371,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;
}
}
@@ -1345,9 +1398,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;
}
}
}
@@ -1363,6 +1420,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()) {
@@ -1515,7 +1575,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
@@ -1573,9 +1633,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;
}
}
}
@@ -1587,9 +2046,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);
}
@@ -1663,6 +2124,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;
}
@@ -1731,7 +2193,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;
}
@@ -1779,7 +2241,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);
@@ -1988,19 +2450,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;
@@ -2015,7 +2477,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);
}
}
@@ -2659,6 +3121,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:
@@ -2677,6 +3140,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");
}
@@ -2911,9 +3375,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);
@@ -3605,6 +4074,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;
@@ -3805,6 +4279,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()) {
@@ -3821,6 +4299,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(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);
}
@@ -3888,7 +4369,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()) {
@@ -3900,7 +4380,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;
@@ -4177,7 +4657,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;
}
@@ -4246,6 +4730,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;
@@ -4450,6 +4940,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;
@@ -4519,6 +5010,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:
@@ -4576,10 +5068,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());
}
@@ -4593,7 +5085,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());
}
@@ -4742,7 +5234,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;
@@ -4798,6 +5290,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.
@@ -5208,6 +5706,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;
@@ -5431,7 +5934,7 @@ Array GDScriptAnalyzer::make_array_from_element_datatype(const GDScriptParser::D
if (p_element_datatype.builtin_type == Variant::OBJECT) {
Ref