diff --git a/core/object/script_language.h b/core/object/script_language.h index 223f1141507b..1936593a0070 100644 --- a/core/object/script_language.h +++ b/core/object/script_language.h @@ -178,6 +178,7 @@ class Script : public Resource { virtual void get_constants(HashMap *p_constants) {} virtual void get_members(HashSet *p_constants) {} + virtual const StructInfo *get_struct_info(const String &p_struct_name) const { return ClassDB::get_struct_info(p_struct_name); } virtual bool is_placeholder_fallback_enabled() const { return false; } @@ -292,6 +293,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/core/variant/struct_generator.h b/core/variant/struct_generator.h index 9da8d272da84..082fa1fd6495 100644 --- a/core/variant/struct_generator.h +++ b/core/variant/struct_generator.h @@ -182,18 +182,33 @@ struct StructInfo { Vector default_values; StructInfo(){}; - StructInfo(const StringName &p_name, const int32_t p_count, const Vector &p_names, const Vector &p_types, const Vector &p_class_names, const Vector &p_struct_member_infos, const Vector &p_default_values) { - name = p_name; - count = p_count; - names = p_names; - types = p_types; - class_names = p_class_names; - struct_member_infos = p_struct_member_infos; - default_values = p_default_values; - }; + StructInfo(const StringName &p_name, const int32_t p_count) : + name(p_name), count(p_count) { + names.resize(p_count); + types.resize(p_count); + class_names.resize(p_count); + struct_member_infos.resize(p_count); + default_values.resize(p_count); + } + StructInfo(const StringName &p_name, const int32_t p_count, const Vector &p_names, const Vector &p_types, const Vector &p_class_names, const Vector &p_struct_member_infos, const Vector &p_default_values) : + name(p_name), + count(p_count), + names(p_names), + types(p_types), + class_names(p_class_names), + struct_member_infos(p_struct_member_infos), + default_values(p_default_values){}; Dictionary to_dict() const; + _FORCE_INLINE_ void set(int32_t p_index, const StringName &p_name, const Variant::Type &p_type, const StringName &p_class_name, const StructInfo *p_struct_member_info, const Variant &p_default_value) { + names.write[p_index] = p_name; + types.write[p_index] = p_type; + class_names.write[p_index] = p_class_name; + struct_member_infos.write[p_index] = p_struct_member_info; + default_values.write[p_index] = p_default_value; + } + _FORCE_INLINE_ bool operator==(const StructInfo &p_struct_info) const { return name == p_struct_info.name; } diff --git a/doc/classes/CodeEdit.xml b/doc/classes/CodeEdit.xml index 7c6f1a51c438..632f9e52728e 100644 --- a/doc/classes/CodeEdit.xml +++ b/doc/classes/CodeEdit.xml @@ -590,31 +590,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 a453866e2763..c0bd37706e0b 100644 --- a/doc/classes/ScriptLanguageExtension.xml +++ b/doc/classes/ScriptLanguageExtension.xml @@ -393,25 +393,25 @@ - + - + - + - + - + - + - + - + - + - + diff --git a/modules/gdscript/editor/gdscript_docgen.cpp b/modules/gdscript/editor/gdscript_docgen.cpp index 601db5414b8d..1d03951c59fb 100644 --- a/modules/gdscript/editor/gdscript_docgen.cpp +++ b/modules/gdscript/editor/gdscript_docgen.cpp @@ -137,6 +137,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 8e74de424212..6f37cb496f4c 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -1182,6 +1182,33 @@ GDScript *GDScript::get_root_script() { return result; } +const StructInfo *GDScript::get_struct_info(const String &p_struct_name) const { + if (const StructInfo *info = structs.getptr(p_struct_name)) { + return info; + } + + Vector names = String(p_struct_name).split("."); + if (names.size() == 2) { + if (const StructInfo *info = ClassDB::get_struct_info(names[0], names[1])) { + return info; + } + } + // 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 + return structs.getptr(names[names.size() - 1]); +} + +void GDScript::set_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; @@ -1307,6 +1334,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; } @@ -1356,6 +1384,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; @@ -2158,6 +2187,8 @@ void GDScriptLanguage::init() { _add_global(n, nc); } + // TODO: Structs? + //populate singletons List singletons; @@ -2598,6 +2629,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 781e284bfc81..5330262d5020 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; @@ -258,6 +259,8 @@ class GDScript : public Script { } const HashMap &get_member_functions() const { return member_functions; } const Ref &get_native() const { return native; } + virtual const StructInfo *get_struct_info(const String &p_struct_name) const override; + void set_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 ec75663e97dd..9d3562a5de49 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -52,6 +52,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()); @@ -200,6 +201,23 @@ 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) { + GDScriptParser::DataType type; + type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + type.kind = GDScriptParser::DataType::STRUCT; + type.builtin_type = Variant::ARRAY; + type.is_constant = false; // TODO: not sure about this + + 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]; @@ -210,7 +228,8 @@ bool GDScriptAnalyzer::has_member_name_conflict_in_script_class(const StringName 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) { + member->type == GDScriptParser::ClassNode::Member::SIGNAL || + member->type == GDScriptParser::ClassNode::Member::STRUCT) { return true; } if (p_member->type != GDScriptParser::Node::FUNCTION && member->type == GDScriptParser::ClassNode::Member::FUNCTION) { @@ -692,6 +711,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; @@ -741,6 +761,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; @@ -761,10 +785,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; @@ -1135,6 +1157,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); + struct_type.struct_type = member.m_struct; + 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) { + break; + } + if (!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; @@ -1508,6 +1557,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: @@ -1847,7 +1897,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; @@ -1879,7 +1929,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) { @@ -2005,6 +2055,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); @@ -2489,6 +2544,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: @@ -2546,7 +2602,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; } @@ -2663,7 +2719,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; } @@ -3413,14 +3469,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; @@ -3478,6 +3534,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; @@ -3606,46 +3663,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()) { @@ -3691,205 +3772,214 @@ 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); - } - - 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); + } - if (script_class->has_member(name)) { - resolve_class_member(script_class, name, p_identifier); + 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; + 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::FUNCTION: { - if (is_base && (!base.is_meta_type || member.function->is_static)) { - p_identifier->set_datatype(make_callable_type(member.function->info)); - p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_FUNCTION; - return; - } - } break; + 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::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: { + 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)) { + p_identifier->set_datatype(make_callable_type(member.function->info)); + p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_FUNCTION; + 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; + } + + 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