diff options
Diffstat (limited to 'modules/gdscript')
32 files changed, 1013 insertions, 401 deletions
diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp index 420401cb79..0ebdbf090d 100644 --- a/modules/gdscript/editor/gdscript_highlighter.cpp +++ b/modules/gdscript/editor/gdscript_highlighter.cpp @@ -322,7 +322,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l } String word = str.substr(j, to - j); - Color col = Color(); + Color col; if (global_functions.has(word)) { // "assert" and "preload" are reserved, so highlight even if not followed by a bracket. if (word == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::ASSERT) || word == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::PRELOAD)) { diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index b4da9c1224..60230257e0 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -629,10 +629,6 @@ void GDScript::_update_doc() { } } - for (KeyValue<StringName, Ref<GDScript>> &E : subclasses) { - E.value->_update_doc(); - } - _add_doc(doc); } #endif @@ -674,36 +670,14 @@ bool GDScript::_update_exports(bool *r_err, bool p_recursive_call, PlaceHolderSc base_cache = Ref<GDScript>(); } - if (c->extends_used) { - String ext_path = ""; - if (String(c->extends_path) != "" && String(c->extends_path) != get_path()) { - ext_path = c->extends_path; - if (ext_path.is_relative_path()) { - String base_path = get_path(); - if (base_path.is_empty() || base_path.is_relative_path()) { - ERR_PRINT(("Could not resolve relative path for parent class: " + ext_path).utf8().get_data()); - } else { - ext_path = base_path.get_base_dir().path_join(ext_path); - } - } - } else if (c->extends.size() != 0) { - const StringName &base_class = c->extends[0]; - - if (ScriptServer::is_global_class(base_class)) { - ext_path = ScriptServer::get_global_class_path(base_class); - } - } - - if (!ext_path.is_empty()) { - if (ext_path != get_path()) { - Ref<GDScript> bf = ResourceLoader::load(ext_path); - - if (bf.is_valid()) { - base_cache = bf; - bf->inheriters_cache.insert(get_instance_id()); - } - } else { - ERR_PRINT(("Path extending itself in " + ext_path).utf8().get_data()); + GDScriptParser::DataType base_type = parser.get_tree()->base_type; + if (base_type.kind == GDScriptParser::DataType::CLASS) { + Ref<GDScript> bf = GDScriptCache::get_full_script(base_type.script_path, err, path); + if (err == OK) { + bf = Ref<GDScript>(bf->find_class(base_type.class_type->fqcn)); + if (bf.is_valid()) { + base_cache = bf; + bf->inheriters_cache.insert(get_instance_id()); } } } @@ -825,13 +799,6 @@ void GDScript::update_exports() { #endif } -void GDScript::_set_subclass_path(Ref<GDScript> &p_sc, const String &p_path) { - p_sc->path = p_path; - for (KeyValue<StringName, Ref<GDScript>> &E : p_sc->subclasses) { - _set_subclass_path(E.value, p_path); - } -} - String GDScript::_get_debug_path() const { if (is_built_in() && !get_name().is_empty()) { return get_name() + " (" + get_path() + ")"; @@ -841,6 +808,11 @@ String GDScript::_get_debug_path() const { } Error GDScript::reload(bool p_keep_state) { + if (reloading) { + return OK; + } + reloading = true; + bool has_instances; { MutexLock lock(GDScriptLanguage::singleton->mutex); @@ -860,9 +832,10 @@ Error GDScript::reload(bool p_keep_state) { basedir = basedir.get_base_dir(); } -// Loading a template, don't parse. + // Loading a template, don't parse. #ifdef TOOLS_ENABLED if (EditorPaths::get_singleton() && basedir.begins_with(EditorPaths::get_singleton()->get_project_script_templates_dir())) { + reloading = false; return OK; } #endif @@ -872,11 +845,10 @@ Error GDScript::reload(bool p_keep_state) { if (source_path.is_empty()) { source_path = get_path(); } - if (!source_path.is_empty()) { - MutexLock lock(GDScriptCache::singleton->lock); - if (!GDScriptCache::singleton->shallow_gdscript_cache.has(source_path)) { - GDScriptCache::singleton->shallow_gdscript_cache[source_path] = this; - } + Ref<GDScript> cached_script = GDScriptCache::get_cached_script(source_path); + if (!source_path.is_empty() && cached_script.is_null()) { + MutexLock lock(GDScriptCache::singleton->mutex); + GDScriptCache::singleton->shallow_gdscript_cache[source_path] = Ref<GDScript>(this); } } @@ -889,6 +861,7 @@ Error GDScript::reload(bool p_keep_state) { } // TODO: Show all error messages. _err_print_error("GDScript::reload", path.is_empty() ? "built-in" : (const char *)path.utf8().get_data(), parser.get_errors().front()->get().line, ("Parse Error: " + parser.get_errors().front()->get().message).utf8().get_data(), false, ERR_HANDLER_SCRIPT); + reloading = false; return ERR_PARSE_ERROR; } @@ -905,6 +878,7 @@ Error GDScript::reload(bool p_keep_state) { _err_print_error("GDScript::reload", path.is_empty() ? "built-in" : (const char *)path.utf8().get_data(), e->get().line, ("Parse Error: " + e->get().message).utf8().get_data(), false, ERR_HANDLER_SCRIPT); e = e->next(); } + reloading = false; return ERR_PARSE_ERROR; } @@ -913,18 +887,16 @@ Error GDScript::reload(bool p_keep_state) { GDScriptCompiler compiler; err = compiler.compile(&parser, this, p_keep_state); -#ifdef TOOLS_ENABLED - _update_doc(); -#endif - if (err) { if (can_run) { if (EngineDebugger::is_active()) { GDScriptLanguage::get_singleton()->debug_break_parse(_get_debug_path(), compiler.get_error_line(), "Parser Error: " + compiler.get_error()); } _err_print_error("GDScript::reload", path.is_empty() ? "built-in" : (const char *)path.utf8().get_data(), compiler.get_error_line(), ("Compile Error: " + compiler.get_error()).utf8().get_data(), false, ERR_HANDLER_SCRIPT); + reloading = false; return ERR_COMPILATION_FAILED; } else { + reloading = false; return err; } } @@ -937,14 +909,7 @@ Error GDScript::reload(bool p_keep_state) { } #endif - valid = true; - - for (KeyValue<StringName, Ref<GDScript>> &E : subclasses) { - _set_subclass_path(E.value, path); - } - - _init_rpc_methods_properties(); - + reloading = false; return OK; } @@ -1051,11 +1016,22 @@ Error GDScript::load_byte_code(const String &p_path) { } void GDScript::set_path(const String &p_path, bool p_take_over) { - Script::set_path(p_path, p_take_over); + String old_path = path; + if (is_root_script()) { + Script::set_path(p_path, p_take_over); + } this->path = p_path; + GDScriptCache::move_script(old_path, p_path); + for (KeyValue<StringName, Ref<GDScript>> &kv : subclasses) { + kv.value->set_path(p_path, p_take_over); + } } Error GDScript::load_source_code(const String &p_path) { + if (p_path.is_empty() || ResourceLoader::get_resource_type(p_path.get_slice("::", 0)) == "PackedScene") { + return OK; + } + Vector<uint8_t> sourcef; Error err; Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ, &err); @@ -1127,6 +1103,124 @@ bool GDScript::inherits_script(const Ref<Script> &p_script) const { return false; } +GDScript *GDScript::find_class(const String &p_qualified_name) { + String first = p_qualified_name.get_slice("::", 0); + + GDScript *result = nullptr; + if (first.is_empty() || first == name) { + result = this; + } else if (first == get_root_script()->path) { + result = get_root_script(); + } else if (HashMap<StringName, Ref<GDScript>>::Iterator E = subclasses.find(first)) { + result = E->value.ptr(); + } else if (_owner != nullptr) { + // Check parent scope. + return _owner->find_class(p_qualified_name); + } + + int name_count = p_qualified_name.get_slice_count("::"); + for (int i = 1; result != nullptr && i < name_count; i++) { + String current_name = p_qualified_name.get_slice("::", i); + if (HashMap<StringName, Ref<GDScript>>::Iterator E = result->subclasses.find(current_name)) { + result = E->value.ptr(); + } else { + // Couldn't find inner class. + return nullptr; + } + } + + return result; +} + +bool GDScript::is_subclass(const GDScript *p_script) { + String fqn = p_script->fully_qualified_name; + if (!fqn.is_empty() && fqn != fully_qualified_name && fqn.begins_with(fully_qualified_name)) { + String fqn_rest = fqn.substr(fully_qualified_name.length()); + return find_class(fqn_rest) == p_script; + } + return false; +} + +GDScript *GDScript::get_root_script() { + GDScript *result = this; + while (result->_owner) { + result = result->_owner; + } + return result; +} + +RBSet<GDScript *> GDScript::get_dependencies() { + RBSet<GDScript *> dependencies; + + _get_dependencies(dependencies, this); + dependencies.erase(this); + + return dependencies; +} + +RBSet<GDScript *> GDScript::get_inverted_dependencies() { + RBSet<GDScript *> inverted_dependencies; + + List<GDScript *> scripts; + { + MutexLock lock(GDScriptLanguage::singleton->mutex); + + SelfList<GDScript> *elem = GDScriptLanguage::singleton->script_list.first(); + while (elem) { + scripts.push_back(elem->self()); + elem = elem->next(); + } + } + + for (GDScript *scr : scripts) { + if (scr == nullptr || scr == this || scr->destructing) { + continue; + } + + RBSet<GDScript *> scr_dependencies = scr->get_dependencies(); + if (scr_dependencies.has(this)) { + inverted_dependencies.insert(scr); + } + } + + return inverted_dependencies; +} + +RBSet<GDScript *> GDScript::get_must_clear_dependencies() { + RBSet<GDScript *> dependencies = get_dependencies(); + RBSet<GDScript *> must_clear_dependencies; + HashMap<GDScript *, RBSet<GDScript *>> inverted_dependencies; + + for (GDScript *E : dependencies) { + inverted_dependencies.insert(E, E->get_inverted_dependencies()); + } + + RBSet<GDScript *> cant_clear; + for (KeyValue<GDScript *, RBSet<GDScript *>> &E : inverted_dependencies) { + for (GDScript *F : E.value) { + if (!dependencies.has(F)) { + cant_clear.insert(E.key); + for (GDScript *G : E.key->get_dependencies()) { + cant_clear.insert(G); + } + break; + } + } + } + + for (KeyValue<GDScript *, RBSet<GDScript *>> &E : inverted_dependencies) { + if (cant_clear.has(E.key) || ScriptServer::is_global_class(E.key->get_fully_qualified_name())) { + continue; + } + must_clear_dependencies.insert(E.key); + } + + cant_clear.clear(); + dependencies.clear(); + inverted_dependencies.clear(); + return must_clear_dependencies; +} + bool GDScript::has_script_signal(const StringName &p_signal) const { if (_signals.has(p_signal)) { return true; @@ -1188,6 +1282,69 @@ String GDScript::_get_gdscript_reference_class_name(const GDScript *p_gdscript) return class_name; } +GDScript *GDScript::_get_gdscript_from_variant(const Variant &p_variant) { + Variant::Type type = p_variant.get_type(); + if (type != Variant::Type::OBJECT) + return nullptr; + + Object *obj = p_variant; + if (obj == nullptr) { + return nullptr; + } + + return Object::cast_to<GDScript>(obj); +} + +void GDScript::_get_dependencies(RBSet<GDScript *> &p_dependencies, const GDScript *p_except) { + if (skip_dependencies || p_dependencies.has(this)) { + return; + } + p_dependencies.insert(this); + + for (const KeyValue<StringName, GDScriptFunction *> &E : member_functions) { + if (E.value == nullptr) { + continue; + } + for (const Variant &V : E.value->constants) { + GDScript *scr = _get_gdscript_from_variant(V); + if (scr != nullptr && scr != p_except) { + scr->_get_dependencies(p_dependencies, p_except); + } + } + } + + if (implicit_initializer) { + for (const Variant &V : implicit_initializer->constants) { + GDScript *scr = _get_gdscript_from_variant(V); + if (scr != nullptr && scr != p_except) { + scr->_get_dependencies(p_dependencies, p_except); + } + } + } + + if (implicit_ready) { + for (const Variant &V : implicit_ready->constants) { + GDScript *scr = _get_gdscript_from_variant(V); + if (scr != nullptr && scr != p_except) { + scr->_get_dependencies(p_dependencies, p_except); + } + } + } + + for (KeyValue<StringName, Ref<GDScript>> &E : subclasses) { + if (E.value != p_except) { + E.value->_get_dependencies(p_dependencies, p_except); + } + } + + for (const KeyValue<StringName, Variant> &E : constants) { + GDScript *scr = _get_gdscript_from_variant(E.value); + if (scr != nullptr && scr != p_except) { + scr->_get_dependencies(p_dependencies, p_except); + } + } +} + GDScript::GDScript() : script_list(this) { #ifdef DEBUG_ENABLED @@ -1238,56 +1395,67 @@ void GDScript::_init_rpc_methods_properties() { rpc_config = base->rpc_config.duplicate(); } - GDScript *cscript = this; - HashMap<StringName, Ref<GDScript>>::Iterator sub_E = subclasses.begin(); - while (cscript) { - // RPC Methods - for (KeyValue<StringName, GDScriptFunction *> &E : cscript->member_functions) { - Variant config = E.value->get_rpc_config(); - if (config.get_type() != Variant::NIL) { - rpc_config[E.value->get_name()] = config; - } - } - - if (cscript != this) { - ++sub_E; - } - - if (sub_E) { - cscript = sub_E->value.ptr(); - } else { - cscript = nullptr; + // RPC Methods + for (KeyValue<StringName, GDScriptFunction *> &E : member_functions) { + Variant config = E.value->get_rpc_config(); + if (config.get_type() != Variant::NIL) { + rpc_config[E.value->get_name()] = config; } } } -GDScript::~GDScript() { - { - MutexLock lock(GDScriptLanguage::get_singleton()->mutex); +void GDScript::clear() { + if (clearing) { + return; + } + clearing = true; - while (SelfList<GDScriptFunctionState> *E = pending_func_states.first()) { - // Order matters since clearing the stack may already cause - // the GDSCriptFunctionState to be destroyed and thus removed from the list. - pending_func_states.remove(E); - E->self()->_clear_stack(); + RBSet<GDScript *> must_clear_dependencies = get_must_clear_dependencies(); + HashMap<GDScript *, ObjectID> must_clear_dependencies_objectids; + + // Log the objectids before clearing, as a cascade of clear could + // remove instances that are still in the clear loop + for (GDScript *E : must_clear_dependencies) { + must_clear_dependencies_objectids.insert(E, E->get_instance_id()); + } + + for (GDScript *E : must_clear_dependencies) { + Object *obj = ObjectDB::get_instance(must_clear_dependencies_objectids[E]); + if (obj == nullptr) { + continue; } + + E->skip_dependencies = true; + E->clear(); + E->skip_dependencies = false; + GDScriptCache::remove_script(E->get_path()); } + RBSet<StringName> member_function_names; for (const KeyValue<StringName, GDScriptFunction *> &E : member_functions) { - memdelete(E.value); + member_function_names.insert(E.key); + } + for (const StringName &E : member_function_names) { + if (member_functions.has(E)) { + memdelete(member_functions[E]); + } + } + member_function_names.clear(); + member_functions.clear(); + + for (KeyValue<StringName, GDScript::MemberInfo> &E : member_indices) { + E.value.data_type.script_type_ref = Ref<Script>(); } if (implicit_initializer) { memdelete(implicit_initializer); } + implicit_initializer = nullptr; if (implicit_ready) { memdelete(implicit_ready); } - - if (GDScriptCache::singleton) { // Cache may have been already destroyed at engine shutdown. - GDScriptCache::remove_script(get_path()); - } + implicit_ready = nullptr; _save_orphaned_subclasses(); @@ -1297,6 +1465,27 @@ GDScript::~GDScript() { _clear_doc(); } #endif + clearing = false; +} + +GDScript::~GDScript() { + if (destructing) { + return; + } + destructing = true; + + clear(); + + { + MutexLock lock(GDScriptLanguage::get_singleton()->mutex); + + while (SelfList<GDScriptFunctionState> *E = pending_func_states.first()) { + // Order matters since clearing the stack may already cause + // the GDScriptFunctionState to be destroyed and thus removed from the list. + pending_func_states.remove(E); + E->self()->_clear_stack(); + } + } #ifdef DEBUG_ENABLED { @@ -1305,6 +1494,10 @@ GDScript::~GDScript() { GDScriptLanguage::get_singleton()->script_list.remove(&script_list); } #endif + + if (GDScriptCache::singleton) { // Cache may have been already destroyed at engine shutdown. + GDScriptCache::remove_script(get_path()); + } } ////////////////////////////// @@ -2344,26 +2537,27 @@ GDScriptLanguage::~GDScriptLanguage() { // Clear dependencies between scripts, to ensure cyclic references are broken (to avoid leaks at exit). SelfList<GDScript> *s = script_list.first(); while (s) { - GDScript *scr = s->self(); // This ensures the current script is not released before we can check what's the next one // in the list (we can't get the next upfront because we don't know if the reference breaking // will cause it -or any other after it, for that matter- to be released so the next one // is not the same as before). - scr->reference(); - - for (KeyValue<StringName, GDScriptFunction *> &E : scr->member_functions) { - GDScriptFunction *func = E.value; - for (int i = 0; i < func->argument_types.size(); i++) { - func->argument_types.write[i].script_type_ref = Ref<Script>(); + Ref<GDScript> scr = s->self(); + if (scr.is_valid()) { + for (KeyValue<StringName, GDScriptFunction *> &E : scr->member_functions) { + GDScriptFunction *func = E.value; + for (int i = 0; i < func->argument_types.size(); i++) { + func->argument_types.write[i].script_type_ref = Ref<Script>(); + } + func->return_type.script_type_ref = Ref<Script>(); + } + for (KeyValue<StringName, GDScript::MemberInfo> &E : scr->member_indices) { + E.value.data_type.script_type_ref = Ref<Script>(); } - func->return_type.script_type_ref = Ref<Script>(); - } - for (KeyValue<StringName, GDScript::MemberInfo> &E : scr->member_indices) { - E.value.data_type.script_type_ref = Ref<Script>(); - } + // Clear backup for scripts that could slip out of the cyclic reference check + scr->clear(); + } s = s->next(); - scr->unreference(); } singleton = nullptr; @@ -2387,6 +2581,27 @@ Ref<GDScript> GDScriptLanguage::get_orphan_subclass(const String &p_qualified_na return Ref<GDScript>(Object::cast_to<GDScript>(obj)); } +Ref<GDScript> GDScriptLanguage::get_script_by_fully_qualified_name(const String &p_name) { + { + MutexLock lock(mutex); + + SelfList<GDScript> *elem = script_list.first(); + while (elem) { + GDScript *scr = elem->self(); + scr = scr->find_class(p_name); + if (scr != nullptr) { + return scr; + } + elem = elem->next(); + } + } + + Ref<GDScript> scr; + scr.instantiate(); + scr->fully_qualified_name = p_name; + return scr; +} + /*************** RESOURCE ***************/ Ref<Resource> ResourceFormatLoaderGDScript::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index 72ad890fbc..2df89d812c 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -61,6 +61,8 @@ class GDScript : public Script { GDCLASS(GDScript, Script); bool tool = false; bool valid = false; + bool reloading = false; + bool skip_dependencies = false; struct MemberInfo { int index = 0; @@ -124,6 +126,8 @@ class GDScript : public Script { int subclass_count = 0; RBSet<Object *> instances; + bool destructing = false; + bool clearing = false; //exported members String source; String path; @@ -137,7 +141,6 @@ class GDScript : public Script { void _super_implicit_constructor(GDScript *p_script, GDScriptInstance *p_instance, Callable::CallError &r_error); GDScriptInstance *_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_is_ref_counted, Callable::CallError &r_error); - void _set_subclass_path(Ref<GDScript> &p_sc, const String &p_path); String _get_debug_path() const; #ifdef TOOLS_ENABLED @@ -164,6 +167,9 @@ class GDScript : public Script { // This method will map the class name from "RefCounted" to "MyClass.InnerClass". static String _get_gdscript_reference_class_name(const GDScript *p_gdscript); + GDScript *_get_gdscript_from_variant(const Variant &p_variant); + void _get_dependencies(RBSet<GDScript *> &p_dependencies, const GDScript *p_except); + protected: bool _get(const StringName &p_name, Variant &r_ret) const; bool _set(const StringName &p_name, const Variant &p_value); @@ -174,10 +180,17 @@ protected: static void _bind_methods(); public: + void clear(); + virtual bool is_valid() const override { return valid; } bool inherits_script(const Ref<Script> &p_script) const override; + GDScript *find_class(const String &p_qualified_name); + bool is_subclass(const GDScript *p_script); + GDScript *get_root_script(); + bool is_root_script() const { return _owner == nullptr; } + String get_fully_qualified_name() const { return fully_qualified_name; } const HashMap<StringName, Ref<GDScript>> &get_subclasses() const { return subclasses; } const HashMap<StringName, Variant> &get_constants() const { return constants; } const HashSet<StringName> &get_members() const { return members; } @@ -189,6 +202,10 @@ public: const Ref<GDScriptNativeClass> &get_native() const { return native; } const String &get_script_class_name() const { return name; } + RBSet<GDScript *> get_dependencies(); + RBSet<GDScript *> get_inverted_dependencies(); + RBSet<GDScript *> get_must_clear_dependencies(); + virtual bool has_script_signal(const StringName &p_signal) const override; virtual void get_script_signal_list(List<MethodInfo> *r_signals) const override; @@ -223,7 +240,6 @@ public: virtual Error reload(bool p_keep_state = false) override; virtual void set_path(const String &p_path, bool p_take_over = false) override; - void set_script_path(const String &p_path) { path = p_path; } //because subclasses need a path too... Error load_source_code(const String &p_path); Error load_byte_code(const String &p_path); @@ -267,6 +283,7 @@ class GDScriptInstance : public ScriptInstance { friend class GDScriptLambdaCallable; friend class GDScriptLambdaSelfCallable; friend class GDScriptCompiler; + friend class GDScriptCache; friend struct GDScriptUtilityFunctionsDefinitions; ObjectID owner_id; @@ -515,6 +532,8 @@ public: void add_orphan_subclass(const String &p_qualified_name, const ObjectID &p_subclass); Ref<GDScript> get_orphan_subclass(const String &p_qualified_name); + Ref<GDScript> get_script_by_fully_qualified_name(const String &p_name); + GDScriptLanguage(); ~GDScriptLanguage(); }; diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 704dda8045..584bb74e4f 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -32,6 +32,7 @@ #include "core/config/engine.h" #include "core/config/project_settings.h" +#include "core/core_string_names.h" #include "core/io/file_access.h" #include "core/io/resource_loader.h" #include "core/object/class_db.h" @@ -39,6 +40,7 @@ #include "core/templates/hash_map.h" #include "gdscript.h" #include "gdscript_utility_functions.h" +#include "scene/resources/packed_scene.h" static MethodInfo info_from_utility_func(const StringName &p_function) { ERR_FAIL_COND_V(!Variant::has_utility_function(p_function), MethodInfo()); @@ -132,7 +134,7 @@ static GDScriptParser::DataType make_builtin_meta_type(Variant::Type p_type) { return type; } -bool GDScriptAnalyzer::has_member_name_conflict_in_script_class(const StringName &p_member_name, const GDScriptParser::ClassNode *p_class) { +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]; @@ -145,6 +147,9 @@ bool GDScriptAnalyzer::has_member_name_conflict_in_script_class(const StringName member->type == GDScriptParser::ClassNode::Member::SIGNAL) { return true; } + if (p_member->type != GDScriptParser::Node::FUNCTION && member->type == GDScriptParser::ClassNode::Member::FUNCTION) { + return true; + } } return false; @@ -160,6 +165,9 @@ bool GDScriptAnalyzer::has_member_name_conflict_in_native_type(const StringName if (ClassDB::has_integer_constant(p_native_type_string, p_member_name)) { return true; } + if (p_member_name == CoreStringNames::get_singleton()->_script) { + return true; + } return false; } @@ -187,14 +195,15 @@ Error GDScriptAnalyzer::check_class_member_name_conflict(const GDScriptParser::C const GDScriptParser::DataType *current_data_type = &p_class_node->base_type; while (current_data_type && current_data_type->kind == GDScriptParser::DataType::Kind::CLASS) { GDScriptParser::ClassNode *current_class_node = current_data_type->class_type; - if (has_member_name_conflict_in_script_class(p_member_name, current_class_node)) { - push_error(vformat(R"(The member "%s" already exists in a parent class.)", p_member_name), + if (has_member_name_conflict_in_script_class(p_member_name, current_class_node, p_member_node)) { + push_error(vformat(R"(The member "%s" already exists in parent class %s.)", p_member_name, current_class_node->identifier->name), p_member_node); return ERR_PARSE_ERROR; } current_data_type = ¤t_class_node->base_type; } + // No need for native class recursion because Node exposes all Object's properties. if (current_data_type && current_data_type->kind == GDScriptParser::DataType::Kind::NATIVE) { if (current_data_type->native_type != StringName()) { return check_native_member_name_conflict( @@ -213,16 +222,6 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, return OK; } - if (p_class == parser->head) { - if (p_class->identifier) { - p_class->fqcn = p_class->identifier->name; - } else { - p_class->fqcn = parser->script_path; - } - } else { - p_class->fqcn = p_class->outer->fqcn + "::" + String(p_class->identifier->name); - } - if (p_class->identifier) { StringName class_name = p_class->identifier->name; if (class_exists(class_name)) { @@ -2866,6 +2865,9 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod p_identifier->variable_source = member.variable; member.variable->usages += 1; break; + case GDScriptParser::ClassNode::Member::SIGNAL: + p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_SIGNAL; + break; case GDScriptParser::ClassNode::Member::FUNCTION: resolve_function_signature(member.function); p_identifier->set_datatype(make_callable_type(member.function->info)); @@ -2922,7 +2924,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod base_class = base_class->base_type.class_type; } - // Check native members. + // Check native members. No need for native class recursion because Node exposes all Object's properties. const StringName &native = base.native_type; if (class_exists(native)) { @@ -3010,6 +3012,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident p_identifier->reduced_value = p_identifier->constant_source->initializer->reduced_value; found_source = true; break; + case GDScriptParser::IdentifierNode::MEMBER_SIGNAL: case GDScriptParser::IdentifierNode::INHERITED_VARIABLE: mark_lambda_use_self(); break; @@ -3113,7 +3116,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident GDScriptParser::DataType result; result.kind = GDScriptParser::DataType::NATIVE; result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; - if (autoload.path.to_lower().ends_with(GDScriptLanguage::get_singleton()->get_extension())) { + if (ResourceLoader::get_resource_type(autoload.path) == "GDScript") { Ref<GDScriptParserRef> singl_parser = get_parser_for(autoload.path); if (singl_parser.is_valid()) { Error err = singl_parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED); @@ -3121,6 +3124,18 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident result = type_from_metatype(singl_parser->get_parser()->head->get_datatype()); } } + } else if (ResourceLoader::get_resource_type(autoload.path) == "PackedScene") { + Error err = OK; + Ref<GDScript> scr = GDScriptCache::get_packed_scene_script(autoload.path, err); + if (err == OK && scr.is_valid()) { + Ref<GDScriptParserRef> singl_parser = get_parser_for(scr->get_path()); + if (singl_parser.is_valid()) { + err = singl_parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED); + if (err == OK) { + result = type_from_metatype(singl_parser->get_parser()->head->get_datatype()); + } + } + } } result.is_constant = true; p_identifier->set_datatype(result); @@ -3246,9 +3261,28 @@ void GDScriptAnalyzer::reduce_preload(GDScriptParser::PreloadNode *p_preload) { } } else { // TODO: Don't load if validating: use completion cache. - p_preload->resource = ResourceLoader::load(p_preload->resolved_path); - if (p_preload->resource.is_null()) { - push_error(vformat(R"(Could not preload resource file "%s".)", p_preload->resolved_path), p_preload->path); + + // Must load GDScript and PackedScenes separately to permit cyclic references + // as ResourceLoader::load() detect and reject those. + if (ResourceLoader::get_resource_type(p_preload->resolved_path) == "GDScript") { + Error err = OK; + Ref<GDScript> res = GDScriptCache::get_shallow_script(p_preload->resolved_path, err, parser->script_path); + p_preload->resource = res; + if (err != OK) { + push_error(vformat(R"(Could not preload resource script "%s".)", p_preload->resolved_path), p_preload->path); + } + } else if (ResourceLoader::get_resource_type(p_preload->resolved_path) == "PackedScene") { + Error err = OK; + Ref<PackedScene> res = GDScriptCache::get_packed_scene(p_preload->resolved_path, err, parser->script_path); + p_preload->resource = res; + if (err != OK) { + push_error(vformat(R"(Could not preload resource scene "%s".)", p_preload->resolved_path), p_preload->path); + } + } else { + p_preload->resource = ResourceLoader::load(p_preload->resolved_path); + if (p_preload->resource.is_null()) { + push_error(vformat(R"(Could not preload resource file "%s".)", p_preload->resolved_path), p_preload->path); + } } } } @@ -3290,6 +3324,17 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri // Just try to get it. bool valid = false; Variant value = p_subscript->base->reduced_value.get_named(p_subscript->attribute->name, valid); + + // If it's a GDScript instance, try to get the full script. Maybe it's not still completely loaded. + Ref<GDScript> gdscr = Ref<GDScript>(p_subscript->base->reduced_value); + if (!valid && gdscr.is_valid()) { + Error err = OK; + GDScriptCache::get_full_script(gdscr->get_path(), err); + if (err == OK) { + value = p_subscript->base->reduced_value.get_named(p_subscript->attribute->name, valid); + } + } + if (!valid) { push_error(vformat(R"(Cannot get member "%s" from "%s".)", p_subscript->attribute->name, p_subscript->base->reduced_value), p_subscript->index); result_type.kind = GDScriptParser::DataType::VARIANT; @@ -3672,50 +3717,43 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_va scr = obj->get_script(); } if (scr.is_valid()) { - if (scr->is_valid()) { - result.script_type = scr; - result.script_path = scr->get_path(); - Ref<GDScript> gds = scr; - if (gds.is_valid()) { - result.kind = GDScriptParser::DataType::CLASS; - // This might be an inner class, so we want to get the parser for the root. - // But still get the inner class from that tree. - GDScript *current = gds.ptr(); - List<StringName> class_chain; - while (current->_owner) { - // Push to front so it's in reverse. - class_chain.push_front(current->name); - current = current->_owner; - } - - Ref<GDScriptParserRef> ref = get_parser_for(current->get_path()); - if (ref.is_null()) { - push_error("Could not find script in path.", p_source); - GDScriptParser::DataType error_type; - error_type.kind = GDScriptParser::DataType::VARIANT; - return error_type; - } - ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED); + result.script_type = scr; + result.script_path = scr->get_path(); + Ref<GDScript> gds = scr; + if (gds.is_valid()) { + result.kind = GDScriptParser::DataType::CLASS; + // This might be an inner class, so we want to get the parser for the root. + // But still get the inner class from that tree. + GDScript *current = gds.ptr(); + List<StringName> class_chain; + while (current->_owner) { + // Push to front so it's in reverse. + class_chain.push_front(current->name); + current = current->_owner; + } - GDScriptParser::ClassNode *found = ref->get_parser()->head; + Ref<GDScriptParserRef> ref = get_parser_for(current->get_path()); + if (ref.is_null()) { + push_error("Could not find script in path.", p_source); + GDScriptParser::DataType error_type; + error_type.kind = GDScriptParser::DataType::VARIANT; + return error_type; + } + ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED); - // It should be okay to assume this exists, since we have a complete script already. - for (const StringName &E : class_chain) { - found = found->get_member(E).m_class; - } + GDScriptParser::ClassNode *found = ref->get_parser()->head; - result.class_type = found; - result.script_path = ref->get_parser()->script_path; - } else { - result.kind = GDScriptParser::DataType::SCRIPT; + // It should be okay to assume this exists, since we have a complete script already. + for (const StringName &E : class_chain) { + found = found->get_member(E).m_class; } - result.native_type = scr->get_instance_base_type(); + + result.class_type = found; + result.script_path = ref->get_parser()->script_path; } else { - push_error(vformat(R"(Constant value uses script from "%s" which is loaded but not compiled.)", scr->get_path()), p_source); - result.kind = GDScriptParser::DataType::VARIANT; - result.type_source = GDScriptParser::DataType::UNDETECTED; - result.is_meta_type = false; + result.kind = GDScriptParser::DataType::SCRIPT; } + result.native_type = scr->get_instance_base_type(); } else { result.kind = GDScriptParser::DataType::NATIVE; if (result.native_type == GDScriptNativeClass::get_class_static()) { diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index 217a856ce0..23a3ad39a5 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -45,7 +45,7 @@ class GDScriptAnalyzer { List<GDScriptParser::LambdaNode *> lambda_stack; // Tests for detecting invalid overloading of script members - static _FORCE_INLINE_ bool has_member_name_conflict_in_script_class(const StringName &p_name, const GDScriptParser::ClassNode *p_current_class_node); + static _FORCE_INLINE_ bool has_member_name_conflict_in_script_class(const StringName &p_name, const GDScriptParser::ClassNode *p_current_class_node, const GDScriptParser::Node *p_member); static _FORCE_INLINE_ bool has_member_name_conflict_in_native_type(const StringName &p_name, const StringName &p_native_type_string); Error check_native_member_name_conflict(const StringName &p_member_name, const GDScriptParser::Node *p_member_node, const StringName &p_native_type_string); Error check_class_member_name_conflict(const GDScriptParser::ClassNode *p_class_node, const StringName &p_member_name, const GDScriptParser::Node *p_member_node); diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp index 271296c2f9..f35318e4c6 100644 --- a/modules/gdscript/gdscript_cache.cpp +++ b/modules/gdscript/gdscript_cache.cpp @@ -34,7 +34,9 @@ #include "core/templates/vector.h" #include "gdscript.h" #include "gdscript_analyzer.h" +#include "gdscript_compiler.h" #include "gdscript_parser.h" +#include "scene/resources/packed_scene.h" bool GDScriptParserRef::is_valid() const { return parser != nullptr; @@ -95,27 +97,88 @@ Error GDScriptParserRef::raise_status(Status p_new_status) { return result; } -GDScriptParserRef::~GDScriptParserRef() { +void GDScriptParserRef::clear() { + if (cleared) { + return; + } + cleared = true; + if (parser != nullptr) { memdelete(parser); } + if (analyzer != nullptr) { memdelete(analyzer); } - MutexLock lock(GDScriptCache::singleton->lock); +} + +GDScriptParserRef::~GDScriptParserRef() { + clear(); + + MutexLock lock(GDScriptCache::singleton->mutex); GDScriptCache::singleton->parser_map.erase(path); } GDScriptCache *GDScriptCache::singleton = nullptr; +void GDScriptCache::move_script(const String &p_from, const String &p_to) { + if (singleton == nullptr || p_from == p_to) { + return; + } + + MutexLock lock(singleton->mutex); + + for (KeyValue<String, HashSet<String>> &E : singleton->packed_scene_dependencies) { + if (E.value.has(p_from)) { + E.value.insert(p_to); + E.value.erase(p_from); + } + } + + if (singleton->parser_map.has(p_from) && !p_from.is_empty()) { + singleton->parser_map[p_to] = singleton->parser_map[p_from]; + } + singleton->parser_map.erase(p_from); + + if (singleton->shallow_gdscript_cache.has(p_from) && !p_from.is_empty()) { + singleton->shallow_gdscript_cache[p_to] = singleton->shallow_gdscript_cache[p_from]; + } + singleton->shallow_gdscript_cache.erase(p_from); + + if (singleton->full_gdscript_cache.has(p_from) && !p_from.is_empty()) { + singleton->full_gdscript_cache[p_to] = singleton->full_gdscript_cache[p_from]; + } + singleton->full_gdscript_cache.erase(p_from); +} + void GDScriptCache::remove_script(const String &p_path) { - MutexLock lock(singleton->lock); + if (singleton == nullptr) { + return; + } + + MutexLock lock(singleton->mutex); + + for (KeyValue<String, HashSet<String>> &E : singleton->packed_scene_dependencies) { + if (!E.value.has(p_path)) { + continue; + } + E.value.erase(p_path); + } + + GDScriptCache::clear_unreferenced_packed_scenes(); + + if (singleton->parser_map.has(p_path)) { + singleton->parser_map[p_path]->clear(); + singleton->parser_map.erase(p_path); + } + + singleton->dependencies.erase(p_path); singleton->shallow_gdscript_cache.erase(p_path); singleton->full_gdscript_cache.erase(p_path); } Ref<GDScriptParserRef> GDScriptCache::get_parser(const String &p_path, GDScriptParserRef::Status p_status, Error &r_error, const String &p_owner) { - MutexLock lock(singleton->lock); + MutexLock lock(singleton->mutex); Ref<GDScriptParserRef> ref; if (!p_owner.is_empty()) { singleton->dependencies[p_owner].insert(p_path); @@ -161,8 +224,8 @@ String GDScriptCache::get_source_code(const String &p_path) { return source; } -Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, const String &p_owner) { - MutexLock lock(singleton->lock); +Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, Error &r_error, const String &p_owner) { + MutexLock lock(singleton->mutex); if (!p_owner.is_empty()) { singleton->dependencies[p_owner].insert(p_path); } @@ -176,15 +239,19 @@ Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, const Stri Ref<GDScript> script; script.instantiate(); script->set_path(p_path, true); - script->set_script_path(p_path); script->load_source_code(p_path); - singleton->shallow_gdscript_cache[p_path] = script.ptr(); + Ref<GDScriptParserRef> parser_ref = get_parser(p_path, GDScriptParserRef::PARSED, r_error); + if (r_error == OK) { + GDScriptCompiler::make_scripts(script.ptr(), parser_ref->get_parser()->get_tree(), true); + } + + singleton->shallow_gdscript_cache[p_path] = script; return script; } Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_error, const String &p_owner, bool p_update_from_disk) { - MutexLock lock(singleton->lock); + MutexLock lock(singleton->mutex); if (!p_owner.is_empty()) { singleton->dependencies[p_owner].insert(p_path); @@ -200,31 +267,53 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro } if (script.is_null()) { - script = get_shallow_script(p_path); - ERR_FAIL_COND_V(script.is_null(), Ref<GDScript>()); + script = get_shallow_script(p_path, r_error); + if (r_error) { + return script; + } } - r_error = script->load_source_code(p_path); + if (p_update_from_disk) { + r_error = script->load_source_code(p_path); + } if (r_error) { return script; } - r_error = script->reload(); + singleton->full_gdscript_cache[p_path] = script; + singleton->shallow_gdscript_cache.erase(p_path); + + r_error = script->reload(true); if (r_error) { + singleton->shallow_gdscript_cache[p_path] = script; + singleton->full_gdscript_cache.erase(p_path); return script; } - singleton->full_gdscript_cache[p_path] = script.ptr(); - singleton->shallow_gdscript_cache.erase(p_path); - return script; } +Ref<GDScript> GDScriptCache::get_cached_script(const String &p_path) { + MutexLock lock(singleton->mutex); + + if (singleton->full_gdscript_cache.has(p_path)) { + return singleton->full_gdscript_cache[p_path]; + } + + if (singleton->shallow_gdscript_cache.has(p_path)) { + return singleton->shallow_gdscript_cache[p_path]; + } + + return Ref<GDScript>(); +} + Error GDScriptCache::finish_compiling(const String &p_owner) { + MutexLock lock(singleton->mutex); + // Mark this as compiled. - Ref<GDScript> script = get_shallow_script(p_owner); - singleton->full_gdscript_cache[p_owner] = script.ptr(); + Ref<GDScript> script = get_cached_script(p_owner); + singleton->full_gdscript_cache[p_owner] = script; singleton->shallow_gdscript_cache.erase(p_owner); HashSet<String> depends = singleton->dependencies[p_owner]; @@ -245,13 +334,103 @@ Error GDScriptCache::finish_compiling(const String &p_owner) { return err; } +Ref<PackedScene> GDScriptCache::get_packed_scene(const String &p_path, Error &r_error, const String &p_owner) { + MutexLock lock(singleton->mutex); + + if (singleton->packed_scene_cache.has(p_path)) { + singleton->packed_scene_dependencies[p_path].insert(p_owner); + return singleton->packed_scene_cache[p_path]; + } + + Ref<PackedScene> scene = ResourceCache::get_ref(p_path); + if (scene.is_valid()) { + singleton->packed_scene_cache[p_path] = scene; + singleton->packed_scene_dependencies[p_path].insert(p_owner); + return scene; + } + scene.instantiate(); + + r_error = OK; + if (p_path.is_empty()) { + r_error = ERR_FILE_BAD_PATH; + return scene; + } + + scene->set_path(p_path); + singleton->packed_scene_cache[p_path] = scene; + singleton->packed_scene_dependencies[p_path].insert(p_owner); + + scene->recreate_state(); + scene->reload_from_file(); + return scene; +} + +Ref<GDScript> GDScriptCache::get_packed_scene_script(const String &p_path, Error &r_error) { + r_error = OK; + Ref<PackedScene> scene = get_packed_scene(p_path, r_error); + + if (r_error != OK) { + return Ref<GDScript>(); + } + + int node_count = scene->get_state()->get_node_count(); + if (node_count == 0) { + return Ref<GDScript>(); + } + + const int ROOT_NODE = 0; + for (int i = 0; i < scene->get_state()->get_node_property_count(ROOT_NODE); i++) { + if (scene->get_state()->get_node_property_name(ROOT_NODE, i) != SNAME("script")) { + continue; + } + + return scene->get_state()->get_node_property_value(ROOT_NODE, i); + } + + return Ref<GDScript>(); +} + +void GDScriptCache::clear_unreferenced_packed_scenes() { + if (singleton == nullptr) { + return; + } + + MutexLock lock(singleton->mutex); + + for (KeyValue<String, HashSet<String>> &E : singleton->packed_scene_dependencies) { + if (E.value.size() > 0 || !ResourceLoader::is_imported(E.key)) { + continue; + } + + singleton->packed_scene_dependencies.erase(E.key); + singleton->packed_scene_cache.erase(E.key); + } +} + GDScriptCache::GDScriptCache() { singleton = this; } GDScriptCache::~GDScriptCache() { + destructing = true; + + RBSet<Ref<GDScriptParserRef>> parser_map_refs; + for (KeyValue<String, GDScriptParserRef *> &E : parser_map) { + parser_map_refs.insert(E.value); + } + + for (Ref<GDScriptParserRef> &E : parser_map_refs) { + if (E.is_valid()) + E->clear(); + } + + parser_map_refs.clear(); parser_map.clear(); shallow_gdscript_cache.clear(); full_gdscript_cache.clear(); + + packed_scene_cache.clear(); + packed_scene_dependencies.clear(); + singleton = nullptr; } diff --git a/modules/gdscript/gdscript_cache.h b/modules/gdscript/gdscript_cache.h index 3d111ea229..0f9d87aa67 100644 --- a/modules/gdscript/gdscript_cache.h +++ b/modules/gdscript/gdscript_cache.h @@ -36,6 +36,7 @@ #include "core/templates/hash_map.h" #include "core/templates/hash_set.h" #include "gdscript.h" +#include "scene/resources/packed_scene.h" class GDScriptAnalyzer; class GDScriptParser; @@ -56,6 +57,7 @@ private: Status status = EMPTY; Error result = OK; String path; + bool cleared = false; friend class GDScriptCache; @@ -64,6 +66,7 @@ public: Status get_status() const; GDScriptParser *get_parser() const; Error raise_status(Status p_new_status); + void clear(); GDScriptParserRef() {} ~GDScriptParserRef(); @@ -72,25 +75,43 @@ public: class GDScriptCache { // String key is full path. HashMap<String, GDScriptParserRef *> parser_map; - HashMap<String, GDScript *> shallow_gdscript_cache; - HashMap<String, GDScript *> full_gdscript_cache; + HashMap<String, Ref<GDScript>> shallow_gdscript_cache; + HashMap<String, Ref<GDScript>> full_gdscript_cache; HashMap<String, HashSet<String>> dependencies; + HashMap<String, Ref<PackedScene>> packed_scene_cache; + HashMap<String, HashSet<String>> packed_scene_dependencies; friend class GDScript; friend class GDScriptParserRef; + friend class GDScriptInstance; static GDScriptCache *singleton; - Mutex lock; - static void remove_script(const String &p_path); + bool destructing = false; + + Mutex mutex; public: + static void move_script(const String &p_from, const String &p_to); + static void remove_script(const String &p_path); static Ref<GDScriptParserRef> get_parser(const String &p_path, GDScriptParserRef::Status status, Error &r_error, const String &p_owner = String()); static String get_source_code(const String &p_path); - static Ref<GDScript> get_shallow_script(const String &p_path, const String &p_owner = String()); + static Ref<GDScript> get_shallow_script(const String &p_path, Error &r_error, const String &p_owner = String()); static Ref<GDScript> get_full_script(const String &p_path, Error &r_error, const String &p_owner = String(), bool p_update_from_disk = false); + static Ref<GDScript> get_cached_script(const String &p_path); static Error finish_compiling(const String &p_owner); + static Ref<PackedScene> get_packed_scene(const String &p_path, Error &r_error, const String &p_owner = ""); + static Ref<GDScript> get_packed_scene_script(const String &p_path, Error &r_error); + static void clear_unreferenced_packed_scenes(); + + static bool is_destructing() { + if (singleton == nullptr) { + return true; + } + return singleton->destructing; + }; + GDScriptCache(); ~GDScriptCache(); }; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index fd13edc4cf..f0ceb42f89 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -80,7 +80,7 @@ void GDScriptCompiler::_set_error(const String &p_error, const GDScriptParser::N } } -GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner) const { +GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner) { if (!p_datatype.is_set() || !p_datatype.is_hard_type()) { return GDScriptDataType(); } @@ -103,75 +103,46 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D } break; case GDScriptParser::DataType::SCRIPT: { result.kind = GDScriptDataType::SCRIPT; - result.script_type_ref = Ref<Script>(p_datatype.script_type); + result.builtin_type = p_datatype.builtin_type; + result.script_type_ref = p_datatype.script_type; result.script_type = result.script_type_ref.ptr(); - result.native_type = result.script_type->get_instance_base_type(); + result.native_type = p_datatype.native_type; } break; case GDScriptParser::DataType::CLASS: { - // Locate class by constructing the path to it and following that path. - GDScriptParser::ClassNode *class_type = p_datatype.class_type; - if (class_type) { - result.kind = GDScriptDataType::GDSCRIPT; - result.builtin_type = p_datatype.builtin_type; - - String class_name = class_type->fqcn.split("::")[0]; - const bool is_inner_by_path = (!main_script->path.is_empty()) && (class_name == main_script->path); - const bool is_inner_by_name = (!main_script->name.is_empty()) && (class_name == main_script->name); - if (is_inner_by_path || is_inner_by_name) { - // Local class. - List<StringName> names; - while (class_type->outer) { - names.push_back(class_type->identifier->name); - class_type = class_type->outer; - } + result.kind = GDScriptDataType::GDSCRIPT; + result.builtin_type = p_datatype.builtin_type; + result.native_type = p_datatype.native_type; - Ref<GDScript> script = Ref<GDScript>(main_script); - while (names.back()) { - if (!script->subclasses.has(names.back()->get())) { - ERR_PRINT("Parser bug: Cannot locate datatype class."); - result.has_type = false; - return GDScriptDataType(); - } - script = script->subclasses[names.back()->get()]; - names.pop_back(); - } - result.script_type = script.ptr(); - result.native_type = script->get_instance_base_type(); - } else { - // Inner class. - PackedStringArray classes = class_type->fqcn.split("::"); - if (!classes.is_empty()) { - for (GDScript *script : parsed_classes) { - // Checking of inheritance structure of inner class to find a correct script link. - if (script->name == classes[classes.size() - 1]) { - PackedStringArray classes2 = script->fully_qualified_name.split("::"); - bool valid = true; - if (classes.size() != classes2.size()) { - valid = false; - } else { - for (int i = 0; i < classes.size(); i++) { - if (classes[i] != classes2[i]) { - valid = false; - break; - } - } - } - if (!valid) { - continue; - } - result.script_type_ref = Ref<GDScript>(script); - break; - } - } - } - if (result.script_type_ref.is_null()) { - result.script_type_ref = GDScriptCache::get_shallow_script(p_datatype.script_path, main_script->path); - } + String root_name = p_datatype.class_type->fqcn.get_slice("::", 0); + bool is_local_class = !root_name.is_empty() && root_name == main_script->fully_qualified_name; - result.script_type = result.script_type_ref.ptr(); - result.native_type = p_datatype.native_type; + Ref<GDScript> script; + if (is_local_class) { + script = Ref<GDScript>(main_script); + } else { + Error err = OK; + script = GDScriptCache::get_shallow_script(p_datatype.script_path, err, p_owner->path); + if (err) { + _set_error(vformat(R"(Could not find script "%s": %s)", p_datatype.script_path, error_names[err]), nullptr); } } + + if (script.is_valid()) { + script = Ref<GDScript>(script->find_class(p_datatype.class_type->fqcn)); + } + + if (script.is_null()) { + _set_error(vformat(R"(Could not find class "%s" in "%s".)", p_datatype.class_type->fqcn, p_datatype.script_path), nullptr); + return GDScriptDataType(); + } else { + // Only hold a strong reference if the owner of the element qualified with this type is not local, to avoid cyclic references (leaks). + // TODO: Might lead to use after free if script_type is a subclass and is used after its parent is freed. + if (!is_local_class) { + result.script_type_ref = script; + } + result.script_type = script.ptr(); + result.native_type = p_datatype.native_type; + } } break; case GDScriptParser::DataType::ENUM: result.has_type = true; @@ -189,13 +160,7 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D } if (p_datatype.has_container_element_type()) { - result.set_container_element_type(_gdtype_from_datatype(p_datatype.get_container_element_type())); - } - - // Only hold strong reference to the script if it's not the owner of the - // element qualified with this type, to avoid cyclic references (leaks). - if (result.script_type && result.script_type == p_owner) { - result.script_type_ref = Ref<Script>(); + result.set_container_element_type(_gdtype_from_datatype(p_datatype.get_container_element_type(), p_owner)); } return result; @@ -367,7 +332,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code // This is so one autoload doesn't try to load another before it's compiled. HashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); if (autoloads.has(identifier) && autoloads[identifier].is_singleton) { - GDScriptCodeGenerator::Address global = codegen.add_temporary(_gdtype_from_datatype(in->get_datatype())); + GDScriptCodeGenerator::Address global = codegen.add_temporary(_gdtype_from_datatype(in->get_datatype(), codegen.script)); int idx = GDScriptLanguage::get_singleton()->get_global_map()[identifier]; gen->write_store_global(global, idx); return global; @@ -390,11 +355,22 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code if (class_node->identifier && class_node->identifier->name == identifier) { res = Ref<GDScript>(main_script); } else { - res = ResourceLoader::load(ScriptServer::get_global_class_path(identifier)); - if (res.is_null()) { - _set_error("Can't load global class " + String(identifier) + ", cyclic reference?", p_expression); - r_error = ERR_COMPILATION_FAILED; - return GDScriptCodeGenerator::Address(); + String global_class_path = ScriptServer::get_global_class_path(identifier); + if (ResourceLoader::get_resource_type(global_class_path) == "GDScript") { + Error err = OK; + res = GDScriptCache::get_full_script(global_class_path, err); + if (err != OK) { + _set_error("Can't load global class " + String(identifier), p_expression); + r_error = ERR_COMPILATION_FAILED; + return GDScriptCodeGenerator::Address(); + } + } else { + res = ResourceLoader::load(global_class_path); + if (res.is_null()) { + _set_error("Can't load global class " + String(identifier) + ", cyclic reference?", p_expression); + r_error = ERR_COMPILATION_FAILED; + return GDScriptCodeGenerator::Address(); + } } } @@ -434,7 +410,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code Vector<GDScriptCodeGenerator::Address> values; // Create the result temporary first since it's the last to be killed. - GDScriptDataType array_type = _gdtype_from_datatype(an->get_datatype()); + GDScriptDataType array_type = _gdtype_from_datatype(an->get_datatype(), codegen.script); GDScriptCodeGenerator::Address result = codegen.add_temporary(array_type); for (int i = 0; i < an->elements.size(); i++) { @@ -511,7 +487,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code case GDScriptParser::Node::CAST: { const GDScriptParser::CastNode *cn = static_cast<const GDScriptParser::CastNode *>(p_expression); GDScriptParser::DataType og_cast_type = cn->cast_type->get_datatype(); - GDScriptDataType cast_type = _gdtype_from_datatype(og_cast_type); + GDScriptDataType cast_type = _gdtype_from_datatype(og_cast_type, codegen.script); if (og_cast_type.kind == GDScriptParser::DataType::ENUM) { // Enum types are usually treated as dictionaries, but in this case we want to cast to an integer. @@ -534,7 +510,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code } break; case GDScriptParser::Node::CALL: { const GDScriptParser::CallNode *call = static_cast<const GDScriptParser::CallNode *>(p_expression); - GDScriptDataType type = _gdtype_from_datatype(call->get_datatype()); + GDScriptDataType type = _gdtype_from_datatype(call->get_datatype(), codegen.script); GDScriptCodeGenerator::Address result = codegen.add_temporary(type); GDScriptCodeGenerator::Address nil = GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::NIL); @@ -670,7 +646,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code Vector<GDScriptCodeGenerator::Address> args; args.push_back(codegen.add_constant(NodePath(get_node->full_path))); - GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(get_node->get_datatype())); + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(get_node->get_datatype(), codegen.script)); MethodBind *get_node_method = ClassDB::get_method("Node", "get_node"); gen->write_call_ptrcall(result, GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF), get_node_method, args); @@ -686,7 +662,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code case GDScriptParser::Node::AWAIT: { const GDScriptParser::AwaitNode *await = static_cast<const GDScriptParser::AwaitNode *>(p_expression); - GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(p_expression->get_datatype())); + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(p_expression->get_datatype(), codegen.script)); within_await = true; GDScriptCodeGenerator::Address argument = _parse_expression(codegen, r_error, await->to_await); within_await = false; @@ -705,7 +681,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code // Indexing operator. case GDScriptParser::Node::SUBSCRIPT: { const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(p_expression); - GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(subscript->get_datatype())); + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(subscript->get_datatype(), codegen.script)); GDScriptCodeGenerator::Address base = _parse_expression(codegen, r_error, subscript->base); if (r_error) { @@ -735,7 +711,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code // Remove result temp as we don't need it. gen->pop_temporary(); // Faster than indexing self (as if no self. had been used). - return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::MEMBER, MI->value.index, _gdtype_from_datatype(subscript->get_datatype())); + return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::MEMBER, MI->value.index, _gdtype_from_datatype(subscript->get_datatype(), codegen.script)); } } @@ -773,7 +749,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code case GDScriptParser::Node::UNARY_OPERATOR: { const GDScriptParser::UnaryOpNode *unary = static_cast<const GDScriptParser::UnaryOpNode *>(p_expression); - GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(unary->get_datatype())); + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(unary->get_datatype(), codegen.script)); GDScriptCodeGenerator::Address operand = _parse_expression(codegen, r_error, unary->operand); if (r_error) { @@ -791,7 +767,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code case GDScriptParser::Node::BINARY_OPERATOR: { const GDScriptParser::BinaryOpNode *binary = static_cast<const GDScriptParser::BinaryOpNode *>(p_expression); - GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(binary->get_datatype())); + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(binary->get_datatype(), codegen.script)); switch (binary->operation) { case GDScriptParser::BinaryOpNode::OP_LOGIC_AND: { @@ -867,7 +843,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code case GDScriptParser::Node::TERNARY_OPERATOR: { // x IF a ELSE y operator with early out on failure. const GDScriptParser::TernaryOpNode *ternary = static_cast<const GDScriptParser::TernaryOpNode *>(p_expression); - GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(ternary->get_datatype())); + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(ternary->get_datatype(), codegen.script)); gen->write_start_ternary(result); @@ -985,7 +961,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code break; } const GDScriptParser::SubscriptNode *subscript_elem = E->get(); - GDScriptCodeGenerator::Address value = codegen.add_temporary(_gdtype_from_datatype(subscript_elem->get_datatype())); + GDScriptCodeGenerator::Address value = codegen.add_temporary(_gdtype_from_datatype(subscript_elem->get_datatype(), codegen.script)); GDScriptCodeGenerator::Address key; StringName name; @@ -1024,8 +1000,8 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code // Perform operator if any. if (assignment->operation != GDScriptParser::AssignmentNode::OP_NONE) { - GDScriptCodeGenerator::Address op_result = codegen.add_temporary(_gdtype_from_datatype(assignment->get_datatype())); - GDScriptCodeGenerator::Address value = codegen.add_temporary(_gdtype_from_datatype(subscript->get_datatype())); + GDScriptCodeGenerator::Address op_result = codegen.add_temporary(_gdtype_from_datatype(assignment->get_datatype(), codegen.script)); + GDScriptCodeGenerator::Address value = codegen.add_temporary(_gdtype_from_datatype(subscript->get_datatype(), codegen.script)); if (subscript->is_attribute) { gen->write_get_named(value, name, prev_base); } else { @@ -1130,8 +1106,8 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code StringName name = static_cast<GDScriptParser::IdentifierNode *>(assignment->assignee)->name; if (has_operation) { - GDScriptCodeGenerator::Address op_result = codegen.add_temporary(_gdtype_from_datatype(assignment->get_datatype())); - GDScriptCodeGenerator::Address member = codegen.add_temporary(_gdtype_from_datatype(assignment->assignee->get_datatype())); + GDScriptCodeGenerator::Address op_result = codegen.add_temporary(_gdtype_from_datatype(assignment->get_datatype(), codegen.script)); + GDScriptCodeGenerator::Address member = codegen.add_temporary(_gdtype_from_datatype(assignment->assignee->get_datatype(), codegen.script)); gen->write_get_member(member, name); gen->write_binary_operator(op_result, assignment->variant_op, member, assigned_value); gen->pop_temporary(); // Pop member temp. @@ -1184,7 +1160,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code bool has_operation = assignment->operation != GDScriptParser::AssignmentNode::OP_NONE; if (has_operation) { // Perform operation. - GDScriptCodeGenerator::Address op_result = codegen.add_temporary(_gdtype_from_datatype(assignment->get_datatype())); + GDScriptCodeGenerator::Address op_result = codegen.add_temporary(_gdtype_from_datatype(assignment->get_datatype(), codegen.script)); GDScriptCodeGenerator::Address og_value = _parse_expression(codegen, r_error, assignment->assignee); gen->write_binary_operator(op_result, assignment->variant_op, og_value, assigned_value); to_assign = op_result; @@ -1196,7 +1172,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code to_assign = assigned_value; } - GDScriptDataType assign_type = _gdtype_from_datatype(assignment->assignee->get_datatype()); + GDScriptDataType assign_type = _gdtype_from_datatype(assignment->assignee->get_datatype(), codegen.script); if (has_setter && !is_in_setter) { // Call setter. @@ -1226,7 +1202,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code } break; case GDScriptParser::Node::LAMBDA: { const GDScriptParser::LambdaNode *lambda = static_cast<const GDScriptParser::LambdaNode *>(p_expression); - GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(lambda->get_datatype())); + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(lambda->get_datatype(), codegen.script)); Vector<GDScriptCodeGenerator::Address> captures; captures.resize(lambda->captures.size()); @@ -1645,7 +1621,7 @@ void GDScriptCompiler::_add_locals_in_block(CodeGen &codegen, const GDScriptPars // Parameters are added directly from function and loop variables are declared explicitly. continue; } - codegen.add_local(p_block->locals[i].name, _gdtype_from_datatype(p_block->locals[i].get_datatype())); + codegen.add_local(p_block->locals[i].name, _gdtype_from_datatype(p_block->locals[i].get_datatype(), codegen.script)); } } @@ -1675,7 +1651,7 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui codegen.start_block(); // Evaluate the match expression. - GDScriptCodeGenerator::Address value = codegen.add_local("@match_value", _gdtype_from_datatype(match->test->get_datatype())); + GDScriptCodeGenerator::Address value = codegen.add_local("@match_value", _gdtype_from_datatype(match->test->get_datatype(), codegen.script)); GDScriptCodeGenerator::Address value_expr = _parse_expression(codegen, err, match->test); if (err) { return err; @@ -1784,9 +1760,9 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui const GDScriptParser::ForNode *for_n = static_cast<const GDScriptParser::ForNode *>(s); codegen.start_block(); - GDScriptCodeGenerator::Address iterator = codegen.add_local(for_n->variable->name, _gdtype_from_datatype(for_n->variable->get_datatype())); + GDScriptCodeGenerator::Address iterator = codegen.add_local(for_n->variable->name, _gdtype_from_datatype(for_n->variable->get_datatype(), codegen.script)); - gen->start_for(iterator.type, _gdtype_from_datatype(for_n->list->get_datatype())); + gen->start_for(iterator.type, _gdtype_from_datatype(for_n->list->get_datatype(), codegen.script)); GDScriptCodeGenerator::Address list = _parse_expression(codegen, err, for_n->list); if (err) { @@ -1897,13 +1873,13 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui const GDScriptParser::VariableNode *lv = static_cast<const GDScriptParser::VariableNode *>(s); // Should be already in stack when the block began. GDScriptCodeGenerator::Address local = codegen.locals[lv->identifier->name]; - GDScriptParser::DataType local_type = lv->get_datatype(); + GDScriptDataType local_type = _gdtype_from_datatype(lv->get_datatype(), codegen.script); if (lv->initializer != nullptr) { // For typed arrays we need to make sure this is already initialized correctly so typed assignment work. - if (local_type.is_hard_type() && local_type.builtin_type == Variant::ARRAY) { + if (local_type.has_type && local_type.builtin_type == Variant::ARRAY) { if (local_type.has_container_element_type()) { - codegen.generator->write_construct_typed_array(local, _gdtype_from_datatype(local_type.get_container_element_type(), codegen.script), Vector<GDScriptCodeGenerator::Address>()); + codegen.generator->write_construct_typed_array(local, local_type.get_container_element_type(), Vector<GDScriptCodeGenerator::Address>()); } else { codegen.generator->write_construct_array(local, Vector<GDScriptCodeGenerator::Address>()); } @@ -1920,11 +1896,11 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui if (src_address.mode == GDScriptCodeGenerator::Address::TEMPORARY) { codegen.generator->pop_temporary(); } - } else if (lv->get_datatype().is_hard_type()) { + } else if (local_type.has_type) { // Initialize with default for type. if (local_type.has_container_element_type()) { - codegen.generator->write_construct_typed_array(local, _gdtype_from_datatype(local_type.get_container_element_type(), codegen.script), Vector<GDScriptCodeGenerator::Address>()); - } else if (local_type.kind == GDScriptParser::DataType::BUILTIN) { + codegen.generator->write_construct_typed_array(local, local_type.get_container_element_type(), Vector<GDScriptCodeGenerator::Address>()); + } else if (local_type.kind == GDScriptDataType::BUILTIN) { codegen.generator->write_construct(local, local_type.builtin_type, Vector<GDScriptCodeGenerator::Address>()); } // The `else` branch is for objects, in such case we leave it as `null`. @@ -2033,17 +2009,17 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ continue; } - GDScriptParser::DataType field_type = field->get_datatype(); + GDScriptDataType field_type = _gdtype_from_datatype(field->get_datatype(), codegen.script); - GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, _gdtype_from_datatype(field->get_datatype())); + GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, field_type); if (field->initializer) { // Emit proper line change. codegen.generator->write_newline(field->initializer->start_line); // For typed arrays we need to make sure this is already initialized correctly so typed assignment work. - if (field_type.is_hard_type() && field_type.builtin_type == Variant::ARRAY) { + if (field_type.has_type && field_type.builtin_type == Variant::ARRAY) { if (field_type.has_container_element_type()) { - codegen.generator->write_construct_typed_array(dst_address, _gdtype_from_datatype(field_type.get_container_element_type(), codegen.script), Vector<GDScriptCodeGenerator::Address>()); + codegen.generator->write_construct_typed_array(dst_address, field_type.get_container_element_type(), Vector<GDScriptCodeGenerator::Address>()); } else { codegen.generator->write_construct_array(dst_address, Vector<GDScriptCodeGenerator::Address>()); } @@ -2062,13 +2038,13 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ if (src_address.mode == GDScriptCodeGenerator::Address::TEMPORARY) { codegen.generator->pop_temporary(); } - } else if (field->get_datatype().is_hard_type()) { + } else if (field_type.has_type) { codegen.generator->write_newline(field->start_line); // Initialize with default for type. if (field_type.has_container_element_type()) { - codegen.generator->write_construct_typed_array(dst_address, _gdtype_from_datatype(field_type.get_container_element_type(), codegen.script), Vector<GDScriptCodeGenerator::Address>()); - } else if (field_type.kind == GDScriptParser::DataType::BUILTIN) { + codegen.generator->write_construct_typed_array(dst_address, field_type.get_container_element_type(), Vector<GDScriptCodeGenerator::Address>()); + } else if (field_type.kind == GDScriptDataType::BUILTIN) { codegen.generator->write_construct(dst_address, field_type.builtin_type, Vector<GDScriptCodeGenerator::Address>()); } // The `else` branch is for objects, in such case we leave it as `null`. @@ -2195,23 +2171,20 @@ Error GDScriptCompiler::_parse_setter_getter(GDScript *p_script, const GDScriptP return err; } -Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { - parsing_classes.insert(p_script); +Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { + if (parsed_classes.has(p_script)) { + return OK; + } - if (p_class->outer && p_class->outer->outer) { - // Owner is not root - if (!parsed_classes.has(p_script->_owner)) { - if (parsing_classes.has(p_script->_owner)) { - _set_error("Cyclic class reference for '" + String(p_class->identifier->name) + "'.", p_class); - return ERR_PARSE_ERROR; - } - Error err = _parse_class_level(p_script->_owner, p_class->outer, p_keep_state); - if (err) { - return err; - } - } + if (parsing_classes.has(p_script)) { + String class_name = p_class->identifier ? String(p_class->identifier->name) : p_class->fqcn; + _set_error(vformat(R"(Cyclic class reference for "%s".)", class_name), p_class); + return ERR_PARSE_ERROR; } + parsing_classes.insert(p_script); + + p_script->clearing = true; #ifdef TOOLS_ENABLED p_script->doc_functions.clear(); p_script->doc_variables.clear(); @@ -2234,10 +2207,24 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar p_script->base = Ref<GDScript>(); p_script->_base = nullptr; p_script->members.clear(); + + // This makes possible to clear script constants and member_functions without heap-use-after-free errors. + HashMap<StringName, Variant> constants; + for (const KeyValue<StringName, Variant> &E : p_script->constants) { + constants.insert(E.key, E.value); + } p_script->constants.clear(); + constants.clear(); + HashMap<StringName, GDScriptFunction *> member_functions; for (const KeyValue<StringName, GDScriptFunction *> &E : p_script->member_functions) { + member_functions.insert(E.key, E.value); + } + p_script->member_functions.clear(); + for (const KeyValue<StringName, GDScriptFunction *> &E : member_functions) { memdelete(E.value); } + member_functions.clear(); + if (p_script->implicit_initializer) { memdelete(p_script->implicit_initializer); } @@ -2252,8 +2239,9 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar p_script->implicit_initializer = nullptr; p_script->implicit_ready = nullptr; + p_script->clearing = false; + p_script->tool = parser->is_tool(); - p_script->name = p_class->identifier ? p_class->identifier->name : ""; if (!p_script->name.is_empty()) { if (ClassDB::class_exists(p_script->name) && ClassDB::is_class_exposed(p_script->name)) { @@ -2262,53 +2250,50 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar } } - Ref<GDScriptNativeClass> native; + GDScriptDataType base_type = _gdtype_from_datatype(p_class->base_type, p_script); - GDScriptDataType base_type = _gdtype_from_datatype(p_class->base_type); // Inheritance switch (base_type.kind) { case GDScriptDataType::NATIVE: { int native_idx = GDScriptLanguage::get_singleton()->get_global_map()[base_type.native_type]; - native = GDScriptLanguage::get_singleton()->get_global_array()[native_idx]; - ERR_FAIL_COND_V(native.is_null(), ERR_BUG); - p_script->native = native; + p_script->native = GDScriptLanguage::get_singleton()->get_global_array()[native_idx]; + ERR_FAIL_COND_V(p_script->native.is_null(), ERR_BUG); } break; case GDScriptDataType::GDSCRIPT: { Ref<GDScript> base = Ref<GDScript>(base_type.script_type); - p_script->base = base; - p_script->_base = base.ptr(); + if (base.is_null()) { + return ERR_COMPILATION_FAILED; + } - if (p_class->base_type.kind == GDScriptParser::DataType::CLASS && p_class->base_type.class_type != nullptr) { - if (p_class->base_type.script_path == main_script->path) { - if (!parsed_classes.has(p_script->_base)) { - if (parsing_classes.has(p_script->_base)) { - String class_name = p_class->identifier ? p_class->identifier->name : "<main>"; - _set_error("Cyclic class reference for '" + class_name + "'.", p_class); - return ERR_PARSE_ERROR; - } - Error err = _parse_class_level(p_script->_base, p_class->base_type.class_type, p_keep_state); - if (err) { - return err; - } - } - } else { - Error err = OK; - base = GDScriptCache::get_full_script(p_class->base_type.script_path, err, main_script->path); - if (err) { - return err; - } - if (base.is_null() || !base->is_valid()) { - return ERR_COMPILATION_FAILED; - } + if (base.ptr() == main_script || main_script->is_subclass(base.ptr())) { + Error err = _populate_class_members(base.ptr(), p_class->base_type.class_type, p_keep_state); + if (err) { + return err; } + } else if (!base->is_valid()) { + Error err = OK; + Ref<GDScript> base_root = GDScriptCache::get_full_script(base->path, err, p_script->path); + if (err) { + _set_error(vformat(R"(Could not compile base class "%s" from "%s": %s)", base->fully_qualified_name, base->path, error_names[err]), nullptr); + return err; + } + if (base_root.is_valid()) { + base = Ref<GDScript>(base_root->find_class(base->fully_qualified_name)); + } + if (base.is_null()) { + _set_error(vformat(R"(Could not find class "%s" in "%s".)", base->fully_qualified_name, base->path), nullptr); + return ERR_COMPILATION_FAILED; + } + ERR_FAIL_COND_V(!base->is_valid(), ERR_BUG); } + p_script->base = base; + p_script->_base = base.ptr(); p_script->member_indices = base->member_indices; - native = base->native; - p_script->native = native; + p_script->native = base->native; } break; default: { - _set_error("Parser bug: invalid inheritance.", p_class); + _set_error("Parser bug: invalid inheritance.", nullptr); return ERR_BUG; } break; } @@ -2478,8 +2463,7 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar parsed_classes.insert(p_script); parsing_classes.erase(p_script); - //parse sub-classes - + // Populate sub-classes. for (int i = 0; i < p_class->members.size(); i++) { const GDScriptParser::ClassNode::Member &member = p_class->members[i]; if (member.type != member.CLASS) { @@ -2491,27 +2475,24 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar GDScript *subclass_ptr = subclass.ptr(); // Subclass might still be parsing, just skip it - if (!parsed_classes.has(subclass_ptr) && !parsing_classes.has(subclass_ptr)) { - Error err = _parse_class_level(subclass_ptr, inner_class, p_keep_state); + if (!parsing_classes.has(subclass_ptr)) { + Error err = _populate_class_members(subclass_ptr, inner_class, p_keep_state); if (err) { return err; } } #ifdef TOOLS_ENABLED - p_script->member_lines[name] = inner_class->start_line; #endif - p_script->constants.insert(name, subclass); //once parsed, goes to the list of constants } return OK; } -Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { - //parse methods - +Error GDScriptCompiler::_compile_class(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { + // Compile member functions, getters, and setters. for (int i = 0; i < p_class->members.size(); i++) { const GDScriptParser::ClassNode::Member &member = p_class->members[i]; if (member.type == member.FUNCTION) { @@ -2615,17 +2596,26 @@ Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptPa StringName name = inner_class->identifier->name; GDScript *subclass = p_script->subclasses[name].ptr(); - Error err = _parse_class_blocks(subclass, inner_class, p_keep_state); + Error err = _compile_class(subclass, inner_class, p_keep_state); if (err) { return err; } } +#ifdef TOOLS_ENABLED + p_script->_update_doc(); +#endif + + p_script->_init_rpc_methods_properties(); + p_script->valid = true; return OK; } -void GDScriptCompiler::_make_scripts(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { +void GDScriptCompiler::make_scripts(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { + p_script->fully_qualified_name = p_class->fqcn; + p_script->name = p_class->identifier ? p_class->identifier->name : ""; + HashMap<StringName, Ref<GDScript>> old_subclasses; if (p_keep_state) { @@ -2642,24 +2632,22 @@ void GDScriptCompiler::_make_scripts(GDScript *p_script, const GDScriptParser::C StringName name = inner_class->identifier->name; Ref<GDScript> subclass; - String fully_qualified_name = p_script->fully_qualified_name + "::" + name; if (old_subclasses.has(name)) { subclass = old_subclasses[name]; } else { - Ref<GDScript> orphan_subclass = GDScriptLanguage::get_singleton()->get_orphan_subclass(fully_qualified_name); - if (orphan_subclass.is_valid()) { - subclass = orphan_subclass; - } else { - subclass.instantiate(); - } + subclass = GDScriptLanguage::get_singleton()->get_orphan_subclass(inner_class->fqcn); + } + + if (subclass.is_null()) { + subclass.instantiate(); } subclass->_owner = p_script; - subclass->fully_qualified_name = fully_qualified_name; + subclass->path = p_script->path; p_script->subclasses.insert(name, subclass); - _make_scripts(subclass.ptr(), inner_class, false); + make_scripts(subclass.ptr(), inner_class, p_keep_state); } } @@ -2673,26 +2661,22 @@ Error GDScriptCompiler::compile(const GDScriptParser *p_parser, GDScript *p_scri source = p_script->get_path(); - // The best fully qualified name for a base level script is its file path - p_script->fully_qualified_name = p_script->path; - // Create scripts for subclasses beforehand so they can be referenced - _make_scripts(p_script, root, p_keep_state); + make_scripts(p_script, root, p_keep_state); - p_script->_owner = nullptr; - Error err = _parse_class_level(p_script, root, p_keep_state); + main_script->_owner = nullptr; + Error err = _populate_class_members(main_script, parser->get_tree(), p_keep_state); if (err) { return err; } - err = _parse_class_blocks(p_script, root, p_keep_state); - + err = _compile_class(main_script, root, p_keep_state); if (err) { return err; } - return GDScriptCache::finish_compiling(p_script->get_path()); + return GDScriptCache::finish_compiling(main_script->get_path()); } String GDScriptCompiler::get_error() const { diff --git a/modules/gdscript/gdscript_compiler.h b/modules/gdscript/gdscript_compiler.h index e4264ea55b..45ca4fe342 100644 --- a/modules/gdscript/gdscript_compiler.h +++ b/modules/gdscript/gdscript_compiler.h @@ -121,7 +121,7 @@ class GDScriptCompiler { Error _create_binary_operator(CodeGen &codegen, const GDScriptParser::BinaryOpNode *on, Variant::Operator op, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address()); Error _create_binary_operator(CodeGen &codegen, const GDScriptParser::ExpressionNode *p_left_operand, const GDScriptParser::ExpressionNode *p_right_operand, Variant::Operator op, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address()); - GDScriptDataType _gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner = nullptr) const; + GDScriptDataType _gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner); GDScriptCodeGenerator::Address _parse_assign_right_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::AssignmentNode *p_assignmentint, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address()); GDScriptCodeGenerator::Address _parse_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression, bool p_root = false, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address()); @@ -130,9 +130,8 @@ class GDScriptCompiler { Error _parse_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block, bool p_add_locals = true); GDScriptFunction *_parse_function(Error &r_error, GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready = false, bool p_for_lambda = false); Error _parse_setter_getter(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::VariableNode *p_variable, bool p_is_setter); - Error _parse_class_level(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); - Error _parse_class_blocks(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); - void _make_scripts(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); + Error _populate_class_members(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); + Error _compile_class(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); int err_line = 0; int err_column = 0; StringName source; @@ -140,6 +139,7 @@ class GDScriptCompiler { bool within_await = false; public: + static void make_scripts(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); Error compile(const GDScriptParser *p_parser, GDScript *p_script, bool p_keep_state = false); String get_error() const; diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index f33d1409bc..7628bffd22 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -2511,6 +2511,57 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c } } +static bool _get_subscript_type(GDScriptParser::CompletionContext &p_context, const GDScriptParser::SubscriptNode *p_subscript, GDScriptParser::DataType &r_base_type, Variant *r_base = nullptr) { + if (p_subscript->base->type == GDScriptParser::Node::IDENTIFIER && p_context.base != nullptr) { + const GDScriptParser::GetNodeNode *get_node = nullptr; + const GDScriptParser::IdentifierNode *identifier_node = static_cast<GDScriptParser::IdentifierNode *>(p_subscript->base); + + switch (identifier_node->source) { + case GDScriptParser::IdentifierNode::Source::MEMBER_VARIABLE: { + if (p_context.current_class != nullptr) { + const StringName &member_name = identifier_node->name; + const GDScriptParser::ClassNode *current_class = p_context.current_class; + + if (current_class->has_member(member_name)) { + const GDScriptParser::ClassNode::Member &member = current_class->get_member(member_name); + + if (member.type == GDScriptParser::ClassNode::Member::VARIABLE) { + const GDScriptParser::VariableNode *variable = static_cast<GDScriptParser::VariableNode *>(member.variable); + + if (variable->initializer && variable->initializer->type == GDScriptParser::Node::GET_NODE) { + get_node = static_cast<GDScriptParser::GetNodeNode *>(variable->initializer); + } + } + } + } + } break; + case GDScriptParser::IdentifierNode::Source::LOCAL_VARIABLE: { + if (identifier_node->next != nullptr && identifier_node->next->type == GDScriptParser::ClassNode::Node::GET_NODE) { + get_node = static_cast<GDScriptParser::GetNodeNode *>(identifier_node->next); + } + } break; + default: + break; + } + + if (get_node != nullptr) { + const Object *node = p_context.base->call("get_node_or_null", NodePath(get_node->full_path)); + if (node != nullptr) { + if (r_base != nullptr) { + *r_base = node; + } + r_base_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + r_base_type.kind = GDScriptParser::DataType::NATIVE; + r_base_type.native_type = node->get_class_name(); + r_base_type.builtin_type = Variant::OBJECT; + return true; + } + } + } + + return false; +} + static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, const GDScriptParser::Node *p_call, int p_argidx, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result, bool &r_forced, String &r_arghint) { if (p_call->type == GDScriptParser::Node::PRELOAD) { if (p_argidx == 0 && bool(EDITOR_GET("text_editor/completion/complete_file_paths"))) { @@ -2561,13 +2612,17 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c } } - if (subscript->is_attribute) { - GDScriptCompletionIdentifier ci; - if (_guess_expression_type(p_context, subscript->base, ci)) { - base_type = ci.type; - base = ci.value; - } else { - return; + if (p_context.base != nullptr && subscript->is_attribute) { + bool found_type = _get_subscript_type(p_context, subscript, base_type, &base); + + if (!found_type) { + GDScriptCompletionIdentifier ci; + if (_guess_expression_type(p_context, subscript->base, ci)) { + base_type = ci.type; + base = ci.value; + } else { + return; + } } _static = base_type.is_meta_type; @@ -2765,7 +2820,8 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c const GDScriptParser::SubscriptNode *attr = static_cast<const GDScriptParser::SubscriptNode *>(completion_context.node); if (attr->base) { GDScriptCompletionIdentifier base; - if (!_guess_expression_type(completion_context, attr->base, base)) { + bool found_type = _get_subscript_type(completion_context, attr, base.type); + if (!found_type && !_guess_expression_type(completion_context, attr->base, base)) { break; } @@ -3051,8 +3107,9 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co r_result.type = ScriptLanguage::LOOKUP_RESULT_SCRIPT_LOCATION; r_result.location = base_type.class_type->get_member(p_symbol).get_line(); r_result.class_path = base_type.script_path; - r_result.script = GDScriptCache::get_shallow_script(r_result.class_path); - return OK; + Error err = OK; + r_result.script = GDScriptCache::get_shallow_script(r_result.class_path, err); + return err; } base_type = base_type.class_type->base_type; } diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp index 98b3e40f1b..24a614b1ad 100644 --- a/modules/gdscript/gdscript_function.cpp +++ b/modules/gdscript/gdscript_function.cpp @@ -149,10 +149,17 @@ GDScriptFunction::GDScriptFunction() { } GDScriptFunction::~GDScriptFunction() { + get_script()->member_functions.erase(name); + for (int i = 0; i < lambdas.size(); i++) { memdelete(lambdas[i]); } + for (int i = 0; i < argument_types.size(); i++) { + argument_types.write[i].script_type_ref = Ref<Script>(); + } + return_type.script_type_ref = Ref<Script>(); + #ifdef DEBUG_ENABLED MutexLock lock(GDScriptLanguage::get_singleton()->mutex); diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index e44038d6da..6e5f7a8520 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -430,6 +430,7 @@ public: }; private: + friend class GDScript; friend class GDScriptCompiler; friend class GDScriptByteCodeGenerator; diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 6842a1ff49..d24cba4c59 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -147,6 +147,8 @@ GDScriptParser::GDScriptParser() { register_annotation(MethodInfo("@warning_ignore", PropertyInfo(Variant::STRING, "warning")), AnnotationInfo::CLASS | AnnotationInfo::VARIABLE | AnnotationInfo::SIGNAL | AnnotationInfo::CONSTANT | AnnotationInfo::FUNCTION | AnnotationInfo::STATEMENT, &GDScriptParser::warning_annotations, varray(), true); // Networking. register_annotation(MethodInfo("@rpc", PropertyInfo(Variant::STRING, "mode"), PropertyInfo(Variant::STRING, "sync"), PropertyInfo(Variant::STRING, "transfer_mode"), PropertyInfo(Variant::INT, "transfer_channel")), AnnotationInfo::FUNCTION, &GDScriptParser::rpc_annotation, varray("", "", "", 0), true); + + is_ignoring_warnings = !(bool)GLOBAL_GET("debug/gdscript/warnings/enable"); } GDScriptParser::~GDScriptParser() { @@ -230,7 +232,7 @@ void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_ warning.leftmost_column = p_source->leftmost_column; warning.rightmost_column = p_source->rightmost_column; - if (warn_level == GDScriptWarning::WarnLevel::ERROR) { + if (warn_level == GDScriptWarning::WarnLevel::ERROR || bool(GLOBAL_GET("debug/gdscript/warnings/treat_warnings_as_errors"))) { push_error(warning.get_message(), p_source); return; } @@ -534,6 +536,7 @@ void GDScriptParser::end_statement(const String &p_context) { void GDScriptParser::parse_program() { head = alloc_node<ClassNode>(); + head->fqcn = script_path; current_class = head; // If we happen to parse an annotation before extends or class_name keywords, track it. @@ -646,6 +649,15 @@ GDScriptParser::ClassNode *GDScriptParser::parse_class() { if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier for the class name after "class".)")) { n_class->identifier = parse_identifier(); + if (n_class->outer) { + String fqcn = n_class->outer->fqcn; + if (fqcn.is_empty()) { + fqcn = script_path; + } + n_class->fqcn = fqcn + "::" + n_class->identifier->name; + } else { + n_class->fqcn = n_class->identifier->name; + } } if (match(GDScriptTokenizer::Token::EXTENDS)) { @@ -684,6 +696,7 @@ GDScriptParser::ClassNode *GDScriptParser::parse_class() { void GDScriptParser::parse_class_name() { if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier for the global class name after "class_name".)")) { current_class->identifier = parse_identifier(); + current_class->fqcn = String(current_class->identifier->name); } if (match(GDScriptTokenizer::Token::EXTENDS)) { @@ -1530,7 +1543,7 @@ GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context, VariableNode *variable = static_cast<VariableNode *>(statement); const SuiteNode::Local &local = current_suite->get_local(variable->identifier->name); if (local.type != SuiteNode::Local::UNDEFINED) { - push_error(vformat(R"(There is already a %s named "%s" declared in this scope.)", local.get_name(), variable->identifier->name)); + push_error(vformat(R"(There is already a %s named "%s" declared in this scope.)", local.get_name(), variable->identifier->name), variable->identifier); } current_suite->add_local(variable, current_function); break; @@ -1545,7 +1558,7 @@ GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context, } else { name = "variable"; } - push_error(vformat(R"(There is already a %s named "%s" declared in this scope.)", name, constant->identifier->name)); + push_error(vformat(R"(There is already a %s named "%s" declared in this scope.)", name, constant->identifier->name), constant->identifier); } current_suite->add_local(constant, current_function); break; diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index d8f5b866aa..7baa3ca3d9 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -786,6 +786,7 @@ public: LOCAL_VARIABLE, LOCAL_ITERATOR, // `for` loop iterator. LOCAL_BIND, // Pattern bind. + MEMBER_SIGNAL, MEMBER_VARIABLE, MEMBER_CONSTANT, INHERITED_VARIABLE, diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp index a0c107aa53..2548d26e14 100644 --- a/modules/gdscript/gdscript_warning.cpp +++ b/modules/gdscript/gdscript_warning.cpp @@ -96,7 +96,7 @@ String GDScriptWarning::get_message() const { } break; case RETURN_VALUE_DISCARDED: { CHECK_SYMBOLS(1); - return "The function '" + symbols[0] + "()' returns a value, but this value is never used."; + return "The function '" + symbols[0] + "()' returns a value that will be discarded if not used."; } break; case PROPERTY_USED_AS_FUNCTION: { CHECK_SYMBOLS(2); diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp index 15131afde7..7f42643c8f 100644 --- a/modules/gdscript/tests/gdscript_test_runner.cpp +++ b/modules/gdscript/tests/gdscript_test_runner.cpp @@ -251,7 +251,10 @@ bool GDScriptTestRunner::make_tests_for_dir(const String &p_dir) { return false; } } else { - if (next.get_extension().to_lower() == "gd") { + if (next.ends_with(".notest.gd")) { + next = dir->get_next(); + continue; + } else if (next.get_extension().to_lower() == "gd") { #ifndef DEBUG_ENABLED // On release builds, skip tests marked as debug only. Error open_err = OK; @@ -461,7 +464,6 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) { Ref<GDScript> script; script.instantiate(); script->set_path(source_file); - script->set_script_path(source_file); err = script->load_source_code(source_file); if (err != OK) { enable_stdout(); @@ -598,6 +600,9 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) { } enable_stdout(); + + GDScriptCache::remove_script(script->get_path()); + return result; } diff --git a/modules/gdscript/tests/scripts/analyzer/errors/overload_script_variable.gd b/modules/gdscript/tests/scripts/analyzer/errors/overload_script_variable.gd new file mode 100644 index 0000000000..5c8b9fa4ae --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/overload_script_variable.gd @@ -0,0 +1,6 @@ +extends Node + +var script: int + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/overload_script_variable.out b/modules/gdscript/tests/scripts/analyzer/errors/overload_script_variable.out new file mode 100644 index 0000000000..8454aaa404 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/overload_script_variable.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Member "script" redefined (original in native class 'Node') diff --git a/modules/gdscript/tests/scripts/analyzer/errors/variable_overloads_superclass_function.gd b/modules/gdscript/tests/scripts/analyzer/errors/variable_overloads_superclass_function.gd new file mode 100644 index 0000000000..28561ff94b --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/variable_overloads_superclass_function.gd @@ -0,0 +1,9 @@ +func test(): + pass + +class A: + func overload_me(): + pass + +class B extends A: + var overload_me diff --git a/modules/gdscript/tests/scripts/analyzer/errors/variable_overloads_superclass_function.out b/modules/gdscript/tests/scripts/analyzer/errors/variable_overloads_superclass_function.out new file mode 100644 index 0000000000..32357f9f6a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/variable_overloads_superclass_function.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +The member "overload_me" already exists in parent class A. diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_inner_base.gd b/modules/gdscript/tests/scripts/analyzer/features/external_inner_base.gd new file mode 100644 index 0000000000..3f9bfe189c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/external_inner_base.gd @@ -0,0 +1,4 @@ +extends "inner_base.gd".InnerA.InnerAB + +func test(): + super.test() diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_inner_base.out b/modules/gdscript/tests/scripts/analyzer/features/external_inner_base.out new file mode 100644 index 0000000000..62f1383392 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/external_inner_base.out @@ -0,0 +1,3 @@ +GDTEST_OK +InnerA.InnerAB.test +InnerB.test diff --git a/modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.gd b/modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.notest.gd index ea744e3027..c3fc176679 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.notest.gd @@ -1,7 +1,4 @@ const A := 42 -func test(): - pass - func something(): return "OK" diff --git a/modules/gdscript/tests/scripts/analyzer/features/inner_base.gd b/modules/gdscript/tests/scripts/analyzer/features/inner_base.gd new file mode 100644 index 0000000000..a825b59255 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/inner_base.gd @@ -0,0 +1,18 @@ +extends InnerA + +func test(): + super.test() + +class InnerA extends InnerAB: + func test(): + print("InnerA.test") + super.test() + + class InnerAB extends InnerB: + func test(): + print("InnerA.InnerAB.test") + super.test() + +class InnerB: + func test(): + print("InnerB.test") diff --git a/modules/gdscript/tests/scripts/analyzer/features/inner_base.out b/modules/gdscript/tests/scripts/analyzer/features/inner_base.out new file mode 100644 index 0000000000..ddd5ffcfd3 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/inner_base.out @@ -0,0 +1,4 @@ +GDTEST_OK +InnerA.test +InnerA.InnerAB.test +InnerB.test diff --git a/modules/gdscript/tests/scripts/analyzer/features/preload_constant_types_are_inferred.gd b/modules/gdscript/tests/scripts/analyzer/features/preload_constant_types_are_inferred.gd index 276875dd5a..9d0324ead8 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/preload_constant_types_are_inferred.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/preload_constant_types_are_inferred.gd @@ -1,4 +1,4 @@ -const Constants = preload("gdscript_to_preload.gd") +const Constants = preload("gdscript_to_preload.notest.gd") func test(): var a := Constants.A diff --git a/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference.gd b/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference.gd new file mode 100644 index 0000000000..b730453a8a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference.gd @@ -0,0 +1,4 @@ +const A = preload("preload_cyclic_reference_a.notest.gd") + +func test(): + A.test_cyclic_reference() diff --git a/modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.out b/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference.out index d73c5eb7cd..14bb971221 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.out +++ b/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference.out @@ -1 +1,2 @@ GDTEST_OK +godot diff --git a/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference_a.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference_a.notest.gd new file mode 100644 index 0000000000..7a6035ded1 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference_a.notest.gd @@ -0,0 +1,12 @@ +const B = preload("preload_cyclic_reference_b.notest.gd") + +const WAITING_FOR = "godot" + +static func test_cyclic_reference(): + B.test_cyclic_reference() + +static func test_cyclic_reference_2(): + B.test_cyclic_reference_2() + +static func test_cyclic_reference_3(): + B.test_cyclic_reference_3() diff --git a/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference_b.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference_b.notest.gd new file mode 100644 index 0000000000..3ea5b01156 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference_b.notest.gd @@ -0,0 +1,10 @@ +const A = preload("preload_cyclic_reference_a.notest.gd") + +static func test_cyclic_reference(): + A.test_cyclic_reference_2() + +static func test_cyclic_reference_2(): + A.test_cyclic_reference_3() + +static func test_cyclic_reference_3(): + print(A.WAITING_FOR) diff --git a/modules/gdscript/tests/scripts/analyzer/features/use_preload_script_as_type.gd b/modules/gdscript/tests/scripts/analyzer/features/use_preload_script_as_type.gd index 5f73064cc0..beabf3d2e5 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/use_preload_script_as_type.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/use_preload_script_as_type.gd @@ -1,4 +1,4 @@ -const preloaded : GDScript = preload("gdscript_to_preload.gd") +const preloaded : GDScript = preload("gdscript_to_preload.notest.gd") func test(): var preloaded_instance: preloaded = preloaded.new() diff --git a/modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.out b/modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.out index 13f759dd46..e89bb9226f 100644 --- a/modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.out +++ b/modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.out @@ -2,4 +2,4 @@ GDTEST_OK >> WARNING >> Line: 6 >> RETURN_VALUE_DISCARDED ->> The function 'i_return_int()' returns a value, but this value is never used. +>> The function 'i_return_int()' returns a value that will be discarded if not used. |