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