diff --git a/core/object/script_language.h b/core/object/script_language.h
index 3ddfbb3e7d35..d0e7fbf23d2c 100644
--- a/core/object/script_language.h
+++ b/core/object/script_language.h
@@ -296,6 +296,7 @@ class ScriptLanguage : public Object {
CODE_COMPLETION_KIND_NODE_PATH,
CODE_COMPLETION_KIND_FILE_PATH,
CODE_COMPLETION_KIND_PLAIN_TEXT,
+ CODE_COMPLETION_KIND_STRUCT,
CODE_COMPLETION_KIND_MAX
};
diff --git a/doc/classes/CodeEdit.xml b/doc/classes/CodeEdit.xml
index 136b8e5fc5d8..2149a7827fbb 100644
--- a/doc/classes/CodeEdit.xml
+++ b/doc/classes/CodeEdit.xml
@@ -620,31 +620,31 @@
Marks the option as a class.
-
+
Marks the option as a function.
-
+
Marks the option as a Godot signal.
-
+
Marks the option as a variable.
-
+
Marks the option as a member.
-
+
Marks the option as an enum entry.
-
+
Marks the option as a constant.
-
+
Marks the option as a Godot node path.
-
+
Marks the option as a file path.
-
+
Marks the option as unclassified or plain text.
diff --git a/doc/classes/ScriptLanguageExtension.xml b/doc/classes/ScriptLanguageExtension.xml
index f9d9e4f513e1..b89369b089cb 100644
--- a/doc/classes/ScriptLanguageExtension.xml
+++ b/doc/classes/ScriptLanguageExtension.xml
@@ -407,25 +407,25 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
diff --git a/modules/gdscript/editor/gdscript_docgen.cpp b/modules/gdscript/editor/gdscript_docgen.cpp
index 32ef429b0d47..f9b8c51ea395 100644
--- a/modules/gdscript/editor/gdscript_docgen.cpp
+++ b/modules/gdscript/editor/gdscript_docgen.cpp
@@ -146,6 +146,18 @@ void GDScriptDocGen::_doctype_from_gdtype(const GDType &p_gdtype, String &r_type
}
}
return;
+ case GDType::STRUCT:
+ r_type = "Array";
+ // TODO: just copied from enum for now, probably needs Struct specific logic
+ r_enum = String(p_gdtype.native_type).replace("::", ".");
+ if (r_enum.begins_with("res://")) {
+ r_enum = r_enum.trim_prefix("res://");
+ int dot_pos = r_enum.rfind(".");
+ if (dot_pos >= 0) {
+ r_enum = r_enum.left(dot_pos).quote() + r_enum.substr(dot_pos);
+ }
+ }
+ return;
case GDType::VARIANT:
case GDType::RESOLVING:
case GDType::UNRESOLVED:
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index 7b9aa70686a7..d23ae4e9acda 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -1236,6 +1236,33 @@ GDScript *GDScript::get_root_script() {
return result;
}
+const StructInfo *GDScript::get_script_struct_info(const String &p_struct_name, bool p_no_inheritance) const {
+ if (const StructInfo *info = structs.getptr(p_struct_name)) {
+ return info;
+ }
+
+ Vector names = String(p_struct_name).split(".");
+ // TODO: this is to handle cases where the name is something like U"res://main.gd.MyStruct" but there should probably be a better solution
+ const StructInfo *info = structs.getptr(names[names.size() - 1]);
+ if (p_no_inheritance) {
+ return info;
+ }
+ // TODO: walk through inheritance chain to look for struct info.
+ return info;
+}
+
+void GDScript::set_script_struct_info(const StructInfo &p_struct_info) {
+ if (ClassDB::get_struct_info(p_struct_info.name)) {
+ // TODO: warn about shadowing native struct?
+ return;
+ }
+ if (structs.has(p_struct_info.name)) {
+ // TODO: warn about shadowing script struct?
+ return;
+ }
+ structs.insert(p_struct_info.name, p_struct_info);
+}
+
RBSet GDScript::get_dependencies() {
RBSet dependencies;
@@ -1361,6 +1388,7 @@ void GDScript::_collect_function_dependencies(GDScriptFunction *p_func, RBSet &p_dependencies, const GDScript *p_except) {
+ // TODO: Do I need Struct logic here?
if (p_dependencies.has(this)) {
return;
}
@@ -1410,6 +1438,7 @@ GDScript::GDScript() :
}
void GDScript::_save_orphaned_subclasses(ClearData *p_clear_data) {
+ // TODO: Do I need Struct logic here?
struct ClassRefWithName {
ObjectID id;
String fully_qualified_name;
@@ -2253,6 +2282,8 @@ void GDScriptLanguage::init() {
_add_global(n, nc);
}
+ // TODO: Structs?
+
//populate singletons
List singletons;
@@ -2741,6 +2772,7 @@ void GDScriptLanguage::get_reserved_words(List *p_words) const {
"namespace", // Reserved for potential future use.
"signal",
"static",
+ "struct",
"trait", // Reserved for potential future use.
"var",
// Other keywords.
diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h
index 9bb39aac0f7e..da62d3515663 100644
--- a/modules/gdscript/gdscript.h
+++ b/modules/gdscript/gdscript.h
@@ -107,6 +107,7 @@ class GDScript : public Script {
HashMap constants;
HashMap member_functions;
HashMap> subclasses;
+ HashMap structs;
HashMap _signals;
Dictionary rpc_config;
@@ -259,6 +260,8 @@ class GDScript : public Script {
}
const HashMap &get_member_functions() const { return member_functions; }
const Ref &get_native() const { return native; }
+ const StructInfo *get_script_struct_info(const String &p_struct_name, bool p_no_inheritance = false) const;
+ void set_script_struct_info(const StructInfo &p_struct_info);
RBSet get_dependencies();
HashMap> get_all_dependencies();
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index 7f0d5005cb0f..73e5bd60d7ea 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -51,6 +51,7 @@
#define UNNAMED_ENUM ""
#define ENUM_SEPARATOR "."
+#define STRUCT_SEPARATOR "."
static MethodInfo info_from_utility_func(const StringName &p_function) {
ERR_FAIL_COND_V(!Variant::has_utility_function(p_function), MethodInfo());
@@ -199,25 +200,44 @@ static GDScriptParser::DataType make_builtin_meta_type(Variant::Type p_type) {
return type;
}
+// In struct types, native_type is used to store the class (native or otherwise) that the struct belongs to.
+// This disambiguates between similarly named structs in base classes or outer classes
+static GDScriptParser::DataType make_struct_type(const StringName &p_struct_name, const String &p_base_name, const bool p_meta = false) {
+ GDScriptParser::DataType type;
+ type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ type.kind = GDScriptParser::DataType::STRUCT;
+ type.builtin_type = Variant::ARRAY;
+ type.is_constant = p_meta; // TODO: not sure about this
+ type.is_meta_type = p_meta;
+
+ if (p_base_name.is_empty()) {
+ type.native_type = p_struct_name;
+ } else {
+ type.native_type = p_base_name + STRUCT_SEPARATOR + p_struct_name;
+ }
+ return type;
+}
+
bool GDScriptAnalyzer::has_member_name_conflict_in_script_class(const StringName &p_member_name, const GDScriptParser::ClassNode *p_class, const GDScriptParser::Node *p_member) {
- if (p_class->members_indices.has(p_member_name)) {
- int index = p_class->members_indices[p_member_name];
- const GDScriptParser::ClassNode::Member *member = &p_class->members[index];
-
- if (member->type == GDScriptParser::ClassNode::Member::VARIABLE ||
- member->type == GDScriptParser::ClassNode::Member::CONSTANT ||
- member->type == GDScriptParser::ClassNode::Member::ENUM ||
- member->type == GDScriptParser::ClassNode::Member::ENUM_VALUE ||
- member->type == GDScriptParser::ClassNode::Member::CLASS ||
- member->type == GDScriptParser::ClassNode::Member::SIGNAL) {
- return true;
- }
- if (p_member->type != GDScriptParser::Node::FUNCTION && member->type == GDScriptParser::ClassNode::Member::FUNCTION) {
+ const int *index = p_class->members_indices.getptr(p_member_name);
+ if (!index) {
+ return false;
+ }
+ const GDScriptParser::ClassNode::Member *member = &p_class->members[*index];
+ switch (member->type) {
+ case GDScriptParser::ClassNode::Member::VARIABLE:
+ case GDScriptParser::ClassNode::Member::CONSTANT:
+ case GDScriptParser::ClassNode::Member::ENUM:
+ case GDScriptParser::ClassNode::Member::ENUM_VALUE:
+ case GDScriptParser::ClassNode::Member::CLASS:
+ case GDScriptParser::ClassNode::Member::SIGNAL:
+ case GDScriptParser::ClassNode::Member::STRUCT:
return true;
- }
+ case GDScriptParser::ClassNode::Member::FUNCTION:
+ return p_member->type != GDScriptParser::Node::FUNCTION;
+ default:
+ return false;
}
-
- return false;
}
bool GDScriptAnalyzer::has_member_name_conflict_in_native_type(const StringName &p_member_name, const StringName &p_native_type_string) {
@@ -233,7 +253,9 @@ bool GDScriptAnalyzer::has_member_name_conflict_in_native_type(const StringName
if (p_member_name == CoreStringName(script)) {
return true;
}
-
+ if (const StructInfo *info = ClassDB::get_struct_info(p_native_type_string)) {
+ return info->names.has(p_member_name);
+ }
return false;
}
@@ -248,6 +270,11 @@ Error GDScriptAnalyzer::check_native_member_name_conflict(const StringName &p_me
return ERR_PARSE_ERROR;
}
+ if (ClassDB::get_struct_info(p_member_name)) {
+ push_error(vformat(R"(The member "%s" shadows a native struct.)", p_member_name), p_member_node);
+ return ERR_PARSE_ERROR;
+ }
+
if (GDScriptParser::get_builtin_type(p_member_name) < Variant::VARIANT_MAX) {
push_error(vformat(R"(The member "%s" cannot have the same name as a builtin type.)", p_member_name), p_member_node);
return ERR_PARSE_ERROR;
@@ -718,6 +745,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
result.builtin_type = GDScriptParser::get_builtin_type(first);
if (result.builtin_type == Variant::ARRAY) {
+ // TODO: Struct logic here?
GDScriptParser::DataType container_type = type_from_metatype(resolve_datatype(p_type->get_container_type_or_null(0)));
if (container_type.kind != GDScriptParser::DataType::VARIANT) {
container_type.is_constant = false;
@@ -798,6 +826,10 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
return bad_type;
}
result = make_global_enum_type(first, StringName());
+ } else if (const StructInfo *info = ClassDB::get_struct_info(parser->current_class->base_type.native_type, first)) {
+ result.kind = GDScriptParser::DataType::STRUCT;
+ result.builtin_type = Variant::ARRAY;
+ result.native_type = info->name;
} else {
// Classes in current scope.
List script_classes;
@@ -818,10 +850,8 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
GDScriptParser::ClassNode::Member member = script_class->get_member(first);
switch (member.type) {
case GDScriptParser::ClassNode::Member::CLASS:
- result = member.get_datatype();
- found = true;
- break;
case GDScriptParser::ClassNode::Member::ENUM:
+ case GDScriptParser::ClassNode::Member::STRUCT:
result = member.get_datatype();
found = true;
break;
@@ -1203,6 +1233,33 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
resolve_class_inheritance(member.m_class, p_source);
}
break;
+ case GDScriptParser::ClassNode::Member::STRUCT: {
+ check_class_member_name_conflict(p_class, member.m_struct->identifier->name, member.m_struct);
+ member.m_struct->set_datatype(resolving_datatype);
+ GDScriptParser::DataType struct_type = make_struct_type(member.m_struct->identifier->name, p_class->fqcn, false);
+ struct_type.struct_type = member.m_struct;
+ // struct_type.script_type = p_class->get_datatype().script_type;
+ // struct_type.script_path = p_class->get_datatype().script_path;
+ member.m_struct->set_datatype(struct_type);
+
+ // Resolve each struct member
+ for (int i = 0; i < member.m_struct->members.size(); i++) {
+ GDScriptParser::VariableNode *struct_member = member.m_struct->members[i];
+
+ check_class_member_name_conflict(p_class, struct_member->identifier->name, struct_member);
+
+ struct_member->set_datatype(resolving_datatype);
+ resolve_struct_member(struct_member);
+ resolve_pending_lambda_bodies(); // TODO: not sure if needed
+#ifdef DEBUG_ENABLED
+ if (struct_member->initializer && !struct_member->initializer->is_constant) {
+ push_error("Struct member defaults must be constant.", struct_member); // TODO: better error message
+ }
+#endif
+ }
+
+ // TODO: need to handle nested structs
+ } break;
case GDScriptParser::ClassNode::Member::GROUP:
// No-op, but needed to silence warnings.
break;
@@ -1574,6 +1631,7 @@ void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node, bool p_is_root
case GDScriptParser::Node::BREAKPOINT:
case GDScriptParser::Node::CONTINUE:
case GDScriptParser::Node::ENUM:
+ case GDScriptParser::Node::STRUCT:
case GDScriptParser::Node::FUNCTION:
case GDScriptParser::Node::PASS:
case GDScriptParser::Node::SIGNAL:
@@ -1913,7 +1971,7 @@ void GDScriptAnalyzer::resolve_suite(GDScriptParser::SuiteNode *p_suite) {
}
}
-void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assignable, const char *p_kind) {
+void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assignable, const char *p_kind, bool p_require_constant) {
GDScriptParser::DataType type;
type.kind = GDScriptParser::DataType::VARIANT;
@@ -1950,7 +2008,7 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi
}
}
- if (is_constant && !p_assignable->initializer->is_constant) {
+ if ((is_constant || p_require_constant) && !p_assignable->initializer->is_constant) {
bool is_initializer_value_reduced = false;
Variant initializer_value = make_expression_reduced_value(p_assignable->initializer, is_initializer_value_reduced);
if (is_initializer_value_reduced) {
@@ -2081,6 +2139,11 @@ void GDScriptAnalyzer::resolve_constant(GDScriptParser::ConstantNode *p_constant
#endif
}
+void GDScriptAnalyzer::resolve_struct_member(GDScriptParser::VariableNode *p_member) {
+ static constexpr const char *kind = "struct member";
+ resolve_assignable(p_member, kind, true);
+}
+
void GDScriptAnalyzer::resolve_parameter(GDScriptParser::ParameterNode *p_parameter) {
static constexpr const char *kind = "parameter";
resolve_assignable(p_parameter, kind);
@@ -2572,6 +2635,7 @@ void GDScriptAnalyzer::reduce_expression(GDScriptParser::ExpressionNode *p_expre
case GDScriptParser::Node::CONSTANT:
case GDScriptParser::Node::CONTINUE:
case GDScriptParser::Node::ENUM:
+ case GDScriptParser::Node::STRUCT:
case GDScriptParser::Node::FOR:
case GDScriptParser::Node::FUNCTION:
case GDScriptParser::Node::IF:
@@ -2629,7 +2693,7 @@ void GDScriptAnalyzer::update_const_expression_builtin_type(GDScriptParser::Expr
if (p_expression->get_datatype() == p_type) {
return;
}
- if (p_type.kind != GDScriptParser::DataType::BUILTIN && p_type.kind != GDScriptParser::DataType::ENUM) {
+ if (p_type.kind != GDScriptParser::DataType::BUILTIN && p_type.kind != GDScriptParser::DataType::ENUM && p_type.kind != GDScriptParser::DataType::STRUCT) {
return;
}
@@ -2832,7 +2896,7 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
while (sub) {
const GDScriptParser::DataType &base_type = sub->base->datatype;
if (base_type.is_hard_type() && base_type.is_read_only) {
- if (base_type.kind == GDScriptParser::DataType::BUILTIN && !Variant::is_type_shared(base_type.builtin_type)) {
+ if ((base_type.kind == GDScriptParser::DataType::BUILTIN || base_type.kind == GDScriptParser::DataType::STRUCT) && !Variant::is_type_shared(base_type.builtin_type)) {
push_error("Cannot assign a new value to a read-only property.", p_assignment->assignee);
return;
}
@@ -3452,6 +3516,11 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
}
p_call->set_datatype(type_from_property(function_info.return_val));
return;
+ } else if (const StructInfo *native_struct_info = ClassDB::get_struct_info(parser->current_class->base_type.native_type, function_name)) {
+ validate_call_arg(*native_struct_info, p_call);
+ p_call->set_datatype(type_from_struct_info(*native_struct_info));
+ p_call->is_static = true;
+ return;
}
}
@@ -3636,14 +3705,14 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
push_error(vformat(R"*(Name "%s" called as a function but is a "%s".)*", p_call->function_name, callee_datatype.to_string()), p_call->callee);
}
#ifdef DEBUG_ENABLED
- } else if (!is_self && !(base_type.is_hard_type() && base_type.kind == GDScriptParser::DataType::BUILTIN)) {
+ } else if (!is_self && !(base_type.is_hard_type() && (base_type.kind == GDScriptParser::DataType::BUILTIN || base_type.kind == GDScriptParser::DataType::STRUCT))) {
parser->push_warning(p_call, GDScriptWarning::UNSAFE_METHOD_ACCESS, p_call->function_name, base_type.to_string());
mark_node_unsafe(p_call);
#endif
}
}
}
- if (!found && (is_self || (base_type.is_hard_type() && base_type.kind == GDScriptParser::DataType::BUILTIN))) {
+ if (!found && (is_self || (base_type.is_hard_type() && (base_type.kind == GDScriptParser::DataType::BUILTIN || base_type.kind == GDScriptParser::DataType::STRUCT)))) {
String base_name = is_self && !p_call->is_super ? "self" : base_type.to_string();
#ifdef SUGGEST_GODOT4_RENAMES
String rename_hint;
@@ -3706,6 +3775,7 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) {
#endif
} else {
bool valid = false;
+ // TODO: should I mark Struct <-> Array as unsafe?
if (op_type.builtin_type == Variant::INT && cast_type.kind == GDScriptParser::DataType::ENUM) {
mark_node_unsafe(p_cast);
valid = true;
@@ -3903,7 +3973,7 @@ Ref GDScriptAnalyzer::find_cached_external_parser_for_class(c
return nullptr;
}
-Ref GDScriptAnalyzer::get_depended_shallow_script(const String &p_path, Error &r_error) {
+Ref GDScriptAnalyzer::get_depended_shallow_script(const String &p_path, Error &r_error) const {
// To keep a local cache of the parser for resolving external nodes later.
parser->get_depended_parser_for(p_path);
Ref scr = GDScriptCache::get_shallow_script(p_path, r_error, parser->script_path);
@@ -3938,46 +4008,70 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
StringName name = p_identifier->name;
- if (base.kind == GDScriptParser::DataType::ENUM) {
- if (base.is_meta_type) {
- if (base.enum_values.has(name)) {
- p_identifier->set_datatype(type_from_metatype(base));
- p_identifier->is_constant = true;
- p_identifier->reduced_value = base.enum_values[name];
+ switch (base.kind) {
+ case GDScriptParser::DataType::ENUM: {
+ if (base.is_meta_type) {
+ if (base.enum_values.has(name)) {
+ p_identifier->set_datatype(type_from_metatype(base));
+ p_identifier->is_constant = true;
+ p_identifier->reduced_value = base.enum_values[name];
+ return;
+ }
+
+ // Enum does not have this value, return.
+ return;
+ } else {
+ push_error(R"(Cannot get property from enum value.)", p_identifier);
return;
}
-
- // Enum does not have this value, return.
- return;
- } else {
- push_error(R"(Cannot get property from enum value.)", p_identifier);
+ }
+ case GDScriptParser::DataType::STRUCT: {
+ if (const GDScriptParser::StructNode *struct_node = base.struct_type) { // User-defined struct
+ // TODO: is linear search okay here?
+ for (int i = 0; i < struct_node->members.size(); i++) {
+ if (struct_node->members[i]->identifier->name == name) {
+ GDScriptParser::DataType member_type = struct_node->members[i]->get_datatype();
+ p_identifier->set_datatype(member_type);
+ return;
+ }
+ }
+ }
+ if (const StructInfo *info = ClassDB::get_struct_info(base.native_type)) { // Native struct
+ // TODO: is linear search okay here?
+ for (int i = 0; i < info->count; i++) {
+ if (info->names[i] == name) {
+ p_identifier->set_datatype(type_from_struct_member(*info, i));
+ return;
+ }
+ }
+ }
+ push_error(vformat(R"(Cannot find property "%s" on struct "%s")", name, base.to_string()), p_identifier);
return;
}
- }
-
- if (base.kind == GDScriptParser::DataType::BUILTIN) {
- if (base.is_meta_type) {
- bool valid = true;
- Variant result = Variant::get_constant_value(base.builtin_type, name, &valid);
- if (valid) {
- p_identifier->is_constant = true;
- p_identifier->reduced_value = result;
- p_identifier->set_datatype(type_from_variant(result, p_identifier));
- } else if (base.is_hard_type()) {
+ case GDScriptParser::DataType::BUILTIN: {
+ if (base.is_meta_type) {
+ bool valid = true;
+ Variant result = Variant::get_constant_value(base.builtin_type, name, &valid);
+ if (valid) {
+ p_identifier->is_constant = true;
+ p_identifier->reduced_value = result;
+ p_identifier->set_datatype(type_from_variant(result, p_identifier));
+ } else if (base.is_hard_type()) {
#ifdef SUGGEST_GODOT4_RENAMES
- String rename_hint;
- if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::RENAMED_IN_GODOT_4_HINT)).booleanize()) {
- const char *renamed_identifier_name = check_for_renamed_identifier(name, p_identifier->type);
- if (renamed_identifier_name) {
- rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", renamed_identifier_name);
+ String rename_hint;
+ if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::RENAMED_IN_GODOT_4_HINT)).booleanize()) {
+ const char *renamed_identifier_name = check_for_renamed_identifier(name, p_identifier->type);
+ if (renamed_identifier_name) {
+ rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", renamed_identifier_name);
+ }
}
- }
- push_error(vformat(R"(Cannot find constant "%s" on base "%s".%s)", name, base.to_string(), rename_hint), p_identifier);
+ push_error(vformat(R"(Cannot find constant "%s" on base "%s".%s)", name, base.to_string(), rename_hint), p_identifier);
#else
- push_error(vformat(R"(Cannot find constant "%s" on base "%s".)", name, base.to_string()), p_identifier);
+ push_error(vformat(R"(Cannot find constant "%s" on base "%s".)", name, base.to_string()), p_identifier);
#endif // SUGGEST_GODOT4_RENAMES
+ }
+ return;
}
- } else {
switch (base.builtin_type) {
case Variant::NIL: {
if (base.is_hard_type()) {
@@ -4023,208 +4117,232 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
}
}
}
- }
- return;
- }
-
- GDScriptParser::ClassNode *base_class = base.class_type;
- List script_classes;
- bool is_base = true;
-
- if (base_class != nullptr) {
- get_class_node_current_scope_classes(base_class, &script_classes, p_identifier);
- }
-
- bool is_constructor = base.is_meta_type && p_identifier->name == SNAME("new");
-
- for (GDScriptParser::ClassNode *script_class : script_classes) {
- 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;
- }
return;
}
+ case GDScriptParser::DataType::NATIVE:
+ case GDScriptParser::DataType::SCRIPT:
+ case GDScriptParser::DataType::CLASS:
+ case GDScriptParser::DataType::VARIANT:
+ case GDScriptParser::DataType::RESOLVING:
+ case GDScriptParser::DataType::UNRESOLVED: {
+ GDScriptParser::ClassNode *base_class = base.class_type;
+ List script_classes;
+ bool is_base = true;
- if (is_constructor) {
- name = "_init";
- }
+ if (base_class != nullptr) {
+ get_class_node_current_scope_classes(base_class, &script_classes, p_identifier);
+ }
- if (script_class->has_member(name)) {
- resolve_class_member(script_class, name, p_identifier);
+ // Struct constructors do not use "new"
+ bool is_constructor = base.is_meta_type && p_identifier->name == SNAME("new");
- GDScriptParser::ClassNode::Member member = script_class->get_member(name);
- switch (member.type) {
- case GDScriptParser::ClassNode::Member::CONSTANT: {
- p_identifier->set_datatype(member.get_datatype());
- p_identifier->is_constant = true;
- p_identifier->reduced_value = member.constant->initializer->reduced_value;
- p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CONSTANT;
- p_identifier->constant_source = member.constant;
+ for (GDScriptParser::ClassNode *script_class : script_classes) {
+ 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;
+ }
return;
}
- case GDScriptParser::ClassNode::Member::ENUM_VALUE: {
- p_identifier->set_datatype(member.get_datatype());
- p_identifier->is_constant = true;
- p_identifier->reduced_value = member.enum_value.value;
- p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CONSTANT;
- return;
+ if (is_constructor) {
+ name = "_init";
}
- case GDScriptParser::ClassNode::Member::ENUM: {
- p_identifier->set_datatype(member.get_datatype());
- p_identifier->is_constant = true;
- p_identifier->reduced_value = member.m_enum->dictionary;
- p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CONSTANT;
- return;
- }
+ if (script_class->has_member(name)) {
+ resolve_class_member(script_class, name, p_identifier);
- case GDScriptParser::ClassNode::Member::VARIABLE: {
- if (is_base && (!base.is_meta_type || member.variable->is_static)) {
- p_identifier->set_datatype(member.get_datatype());
- p_identifier->source = member.variable->is_static ? GDScriptParser::IdentifierNode::STATIC_VARIABLE : GDScriptParser::IdentifierNode::MEMBER_VARIABLE;
- p_identifier->variable_source = member.variable;
- member.variable->usages += 1;
- return;
- }
- } break;
-
- case GDScriptParser::ClassNode::Member::SIGNAL: {
- if (is_base && !base.is_meta_type) {
- p_identifier->set_datatype(member.get_datatype());
- p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_SIGNAL;
- p_identifier->signal_source = member.signal;
- member.signal->usages += 1;
- return;
- }
- } break;
-
- case GDScriptParser::ClassNode::Member::FUNCTION: {
- if (is_base && (!base.is_meta_type || member.function->is_static || is_constructor)) {
- p_identifier->set_datatype(make_callable_type(member.function->info));
- p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_FUNCTION;
- p_identifier->function_source = member.function;
- p_identifier->function_source_is_static = member.function->is_static;
- return;
- }
- } break;
+ GDScriptParser::ClassNode::Member member = script_class->get_member(name);
+ switch (member.type) {
+ case GDScriptParser::ClassNode::Member::CONSTANT: {
+ p_identifier->set_datatype(member.get_datatype());
+ p_identifier->is_constant = true;
+ p_identifier->reduced_value = member.constant->initializer->reduced_value;
+ p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CONSTANT;
+ p_identifier->constant_source = member.constant;
+ return;
+ }
- case GDScriptParser::ClassNode::Member::CLASS: {
- reduce_identifier_from_base_set_class(p_identifier, member.get_datatype());
- p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CLASS;
- return;
+ case GDScriptParser::ClassNode::Member::ENUM_VALUE: {
+ p_identifier->set_datatype(member.get_datatype());
+ p_identifier->is_constant = true;
+ p_identifier->reduced_value = member.enum_value.value;
+ p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CONSTANT;
+ return;
+ }
+
+ case GDScriptParser::ClassNode::Member::ENUM: {
+ p_identifier->set_datatype(member.get_datatype());
+ p_identifier->is_constant = true;
+ p_identifier->reduced_value = member.m_enum->dictionary;
+ p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CONSTANT;
+ return;
+ }
+
+ case GDScriptParser::ClassNode::Member::VARIABLE: {
+ if (is_base && (!base.is_meta_type || member.variable->is_static)) {
+ p_identifier->set_datatype(member.get_datatype());
+ p_identifier->source = member.variable->is_static ? GDScriptParser::IdentifierNode::STATIC_VARIABLE : GDScriptParser::IdentifierNode::MEMBER_VARIABLE;
+ p_identifier->variable_source = member.variable;
+ member.variable->usages += 1;
+ return;
+ }
+ } break;
+
+ case GDScriptParser::ClassNode::Member::SIGNAL: {
+ if (is_base && !base.is_meta_type) {
+ p_identifier->set_datatype(member.get_datatype());
+ p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_SIGNAL;
+ p_identifier->signal_source = member.signal;
+ member.signal->usages += 1;
+ return;
+ }
+ } break;
+
+ case GDScriptParser::ClassNode::Member::FUNCTION: {
+ if (is_base && (!base.is_meta_type || member.function->is_static || is_constructor)) {
+ p_identifier->set_datatype(make_callable_type(member.function->info));
+ p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_FUNCTION;
+ p_identifier->function_source = member.function;
+ p_identifier->function_source_is_static = member.function->is_static;
+ return;
+ }
+ } break;
+
+ case GDScriptParser::ClassNode::Member::CLASS: {
+ reduce_identifier_from_base_set_class(p_identifier, member.get_datatype());
+ p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CLASS;
+ return;
+ }
+
+ case GDScriptParser::ClassNode::Member::STRUCT: {
+ p_identifier->set_datatype(member.get_datatype());
+ p_identifier->is_constant = true;
+ p_identifier->reduced_value = member.m_struct;
+ p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_STRUCT;
+ p_identifier->struct_source = member.m_struct;
+ return;
+ }
+
+ default: {
+ // Do nothing
+ }
+ }
}
- default: {
- // Do nothing
+ if (is_base) {
+ is_base = script_class->base_type.class_type != nullptr;
+ if (!is_base && p_base != nullptr) {
+ break;
+ }
}
}
- }
- if (is_base) {
- is_base = script_class->base_type.class_type != nullptr;
- if (!is_base && p_base != nullptr) {
- break;
- }
- }
- }
+ // Check non-GDScript scripts.
+ Ref