Repository: godotengine/godot (language: cpp)

Issue:
Excessive error spam when a global script is not valid
### Tested versions

4.4 dev7

### System information

W10

### Issue description

If you have a global script (with `class_name`) and it has an error, the error will be spammed occasionally when using editor. I observed it e.g. when opening CreateDialog to add a node (happens once), or every time you try to autocomplete something.
https://github.com/user-attachments/assets/97e7b7f2-e148-42ee-a481-025a07a2d38f
What's relevant though is that the error appears at all. It means the script is loaded and/or parsed needlessly *very often* and it probably applies to all global scripts. We should find a way to avoid that. The scripts should be parsed only after they change and use some cache whenever the editor needs something script-related.

### Steps to reproduce

1. Add a script with class name
2. Introduce some parsing error, e.g. a random word wherever
3. In another script, try autocompleting something

### Minimal reproduction project (MRP)

N/A


Discussion / hints:

Fix patch:
diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp
index fdcd4f9b6064..a12b89683118 100644
--- a/core/config/project_settings.cpp
+++ b/core/config/project_settings.cpp
@@ -1263,10 +1263,10 @@ void ProjectSettings::refresh_global_class_list() {
 	Array script_classes = get_global_class_list();
 	for (int i = 0; i < script_classes.size(); i++) {
 		Dictionary c = script_classes[i];
-		if (!c.has("class") || !c.has("language") || !c.has("path") || !c.has("base")) {
+		if (!c.has("class") || !c.has("language") || !c.has("path") || !c.has("base") || !c.has("is_abstract") || !c.has("is_tool")) {
 			continue;
 		}
-		ScriptServer::add_global_class(c["class"], c["base"], c["language"], c["path"]);
+		ScriptServer::add_global_class(c["class"], c["base"], c["language"], c["path"], c["is_abstract"], c["is_tool"]);
 	}
 }
 
diff --git a/core/object/script_language.cpp b/core/object/script_language.cpp
index 4ef53dba1dff..33bf7ab48a0f 100644
--- a/core/object/script_language.cpp
+++ b/core/object/script_language.cpp
@@ -269,10 +269,10 @@ void ScriptServer::init_languages() {
 
 			for (const Variant &script_class : script_classes) {
 				Dictionary c = script_class;
-				if (!c.has("class") || !c.has("language") || !c.has("path") || !c.has("base")) {
+				if (!c.has("class") || !c.has("language") || !c.has("path") || !c.has("base") || !c.has("is_abstract") || !c.has("is_tool")) {
 					continue;
 				}
-				add_global_class(c["class"], c["base"], c["language"], c["path"]);
+				add_global_class(c["class"], c["base"], c["language"], c["path"], c["is_abstract"], c["is_tool"]);
 			}
 			ProjectSettings::get_singleton()->clear("_global_script_classes");
 		}
@@ -281,10 +281,10 @@ void ScriptServer::init_languages() {
 		Array script_classes = ProjectSettings::get_singleton()->get_global_class_list();
 		for (const Variant &script_class : script_classes) {
 			Dictionary c = script_class;
-			if (!c.has("class") || !c.has("language") || !c.has("path") || !c.has("base")) {
+			if (!c.has("class") || !c.has("language") || !c.has("path") || !c.has("base") || !c.has("is_abstract") || !c.has("is_tool")) {
 				continue;
 			}
-			add_global_class(c["class"], c["base"], c["language"], c["path"]);
+			add_global_class(c["class"], c["base"], c["language"], c["path"], c["is_abstract"], c["is_tool"]);
 		}
 	}
 
@@ -390,7 +390,7 @@ void ScriptServer::global_classes_clear() {
 	inheriters_cache.clear();
 }
 
-void ScriptServer::add_global_class(const StringName &p_class, const StringName &p_base, const StringName &p_language, const String &p_path) {
+void ScriptServer::add_global_class(const StringName &p_class, const StringName &p_base, const StringName &p_language, const String &p_path, bool p_is_abstract, bool p_is_tool) {
 	ERR_FAIL_COND_MSG(p_class == p_base || (global_classes.has(p_base) && get_global_class_native_base(p_base) == p_class), "Cyclic inheritance in script class.");
 	GlobalScriptClass *existing = global_classes.getptr(p_class);
 	if (existing) {
@@ -399,6 +399,8 @@ void ScriptServer::add_global_class(const StringName &p_class, const StringName
 			existing->base = p_base;
 			existing->path = p_path;
 			existing->language = p_language;
+			existing->is_abstract = p_is_abstract;
+			existing->is_tool = p_is_tool;
 			inheriters_cache_dirty = true;
 		}
 	} else {
@@ -407,6 +409,8 @@ void ScriptServer::add_global_class(const StringName &p_class, const StringName
 		g.language = p_language;
 		g.path = p_path;
 		g.base = p_base;
+		g.is_abstract = p_is_abstract;
+		g.is_tool = p_is_tool;
 		global_classes[p_class] = g;
 		inheriters_cache_dirty = true;
 	}
@@ -480,6 +484,16 @@ StringName ScriptServer::get_global_class_native_base(const String &p_class) {
 	return base;
 }
 
+bool ScriptServer::is_global_class_abstract(const String &p_class) {
+	ERR_FAIL_COND_V(!global_classes.has(p_class), false);
+	return global_classes[p_class].is_abstract;
+}
+
+bool ScriptServer::is_global_class_tool(const String &p_class) {
+	ERR_FAIL_COND_V(!global_classes.has(p_class), false);
+	return global_classes[p_class].is_tool;
+}
+
 void ScriptServer::get_global_class_list(List<StringName> *r_global_classes) {
 	List<StringName> classes;
 	for (const KeyValue<StringName, GlobalScriptClass> &E : global_classes) {
@@ -507,12 +521,15 @@ void ScriptServer::save_global_classes() {
 	get_global_class_list(&gc);
 	Array gcarr;
 	for (const StringName &E : gc) {
+		const GlobalScriptClass &global_class = global_classes[E];
 		Dictionary d;
 		d["class"] = E;
-		d["language"] = global_classes[E].language;
-		d["path"] = global_classes[E].path;
-		d["base"] = global_classes[E].base;
+		d["language"] = global_class.language;
+		d["path"] = global_class.path;
+		d["base"] = global_class.base;
 		d["icon"] = class_icons.get(E, "");
+		d["is_abstract"] = global_class.is_abstract;
+		d["is_tool"] = global_class.is_tool;
 		gcarr.push_back(d);
 	}
 	ProjectSettings::get_singleton()->store_global_class_list(gcarr);
diff --git a/core/object/script_language.h b/core/object/script_language.h
index 6ceeb4287512..8158d633ae14 100644
--- a/core/object/script_language.h
+++ b/core/object/script_language.h
@@ -62,6 +62,8 @@ class ScriptServer {
 		StringName language;
 		String path;
 		StringName base;
+		bool is_abstract = false;
+		bool is_tool = false;
 	};
 
 	static HashMap<StringName, GlobalScriptClass> global_classes;
@@ -86,7 +88,7 @@ class ScriptServer {
 	static void thread_exit();
 
 	static void global_classes_clear();
-	static void add_global_class(const StringName &p_class, const StringName &p_base, const StringName &p_language, const String &p_path);
+	static void add_global_class(const StringName &p_class, const StringName &p_base, const StringName &p_language, const String &p_path, bool p_is_abstract, bool p_is_tool);
 	static void remove_global_class(const StringName &p_class);
 	static void remove_global_class_by_path(const String &p_path);
 	static bool is_global_class(const StringName &p_class);
@@ -94,6 +96,8 @@ class ScriptServer {
 	static String get_global_class_path(const String &p_class);
 	static StringName get_global_class_base(const String &p_class);
 	static StringName get_global_class_native_base(const String &p_class);
+	static bool is_global_class_abstract(const String &p_class);
+	static bool is_global_class_tool(const String &p_class);
 	static void get_global_class_list(List<StringName> *r_global_classes);
 	static void get_inheriters_list(const StringName &p_base_type, List<StringName> *r_classes);
 	static void save_global_classes();
@@ -443,7 +447,7 @@ class ScriptLanguage : public Object {
 	virtual void frame();
 
 	virtual bool handles_global_class_type(const String &p_type) const { return false; }
-	virtual String get_global_class_name(const String &p_path, String *r_base_type = nullptr, String *r_icon_path = nullptr) const { return String(); }
+	virtual String get_global_class_name(const String &p_path, String *r_base_type = nullptr, String *r_icon_path = nullptr, bool *r_is_abstract = nullptr, bool *r_is_tool = nullptr) const { return String(); }
 
 	virtual ~ScriptLanguage() {}
 };
diff --git a/core/object/script_language_extension.h b/core/object/script_language_extension.h
index 09c395e71e8d..715ec43fb35a 100644
--- a/core/object/script_language_extension.h
+++ b/core/object/script_language_extension.h
@@ -672,7 +672,7 @@ class ScriptLanguageExtension : public ScriptLanguage {
 
 	GDVIRTUAL1RC_REQUIRED(Dictionary, _get_global_class_name, const String &)
 
-	virtual String get_global_class_name(const String &p_path, String *r_base_type = nullptr, String *r_icon_path = nullptr) const override {
+	virtual String get_global_class_name(const String &p_path, String *r_base_type = nullptr, String *r_icon_path = nullptr, bool *r_is_abstract = nullptr, bool *r_is_tool = nullptr) const override {
 		Dictionary ret;
 		GDVIRTUAL_CALL(_get_global_class_name, p_path, ret);
 		if (!ret.has("name")) {
@@ -684,6 +684,12 @@ class ScriptLanguageExtension : public ScriptLanguage {
 		if (r_icon_path != nullptr && ret.has("icon_path")) {
 			*r_icon_path = ret["icon_path"];
 		}
+		if (r_is_abstract != nullptr && ret.has("is_abstract")) {
+			*r_is_abstract = ret["is_abstract"];
+		}
+		if (r_is_tool != nullptr && ret.has("is_tool")) {
+			*r_is_tool = ret["is_tool"];
+		}
 		return ret["name"];
 	}
 };
diff --git a/editor/connections_dialog.cpp b/editor/connections_dialog.cpp
index 429467d3403c..dca43411e10b 100644
--- a/editor/connections_dialog.cpp
+++ b/editor/connections_dialog.cpp
@@ -1446,7 +1446,7 @@ void ConnectionsDock::update_tree() {
 				doc_class_name = String();
 			}
 
-			class_icon = editor_data.get_script_icon(script_base);
+			class_icon = editor_data.get_script_icon(script_base->get_path());
 			if (class_icon.is_null() && has_theme_icon(native_base, EditorStringName(EditorIcons))) {
 				class_icon = get_editor_theme_icon(native_base);
 			}
diff --git a/editor/create_dialog.cpp b/editor/create_dialog.cpp
index 7f1670bcb648..5eebb1c492e5 100644
--- a/editor/create_dialog.cpp
+++ b/editor/create_dialog.cpp
@@ -257,14 +257,9 @@ void CreateDialog::_add_type(const StringName &p_type, TypeCategory p_type_categ
 		inherits = ClassDB::get_parent_class(p_type);
 		inherited_type = TypeCategory::CPP_TYPE;
 	} else {
-		if (p_type_category == TypeCategory::PATH_TYPE || ScriptServer::is_global_class(p_type)) {
-			Ref<Script> scr;
-			if (p_type_category == TypeCategory::PATH_TYPE) {
-				ERR_FAIL_COND(!ResourceLoader::exists(p_type, "Script"));
-				scr = ResourceLoader::load(p_type, "Script");
-			} else {
-				scr = EditorNode::get_editor_data().script_class_load_script(p_type);
-			}
+		if (p_type_category == TypeCategory::PATH_TYPE) {
+			ERR_FAIL_COND(!ResourceLoader::exists(p_type, "Script"));
+			Ref<Script> scr = ResourceLoader::load(p_type, "Script");
 			ERR_FAIL_COND(scr.is_null());
 
 			Ref<Script> base = scr->get_base_script();
@@ -286,6 +281,10 @@ void CreateDialog::_add_type(const StringName &p_type, TypeCategory p_type_categ
 					inherited_type = TypeCategory::PATH_TYPE;
 				}
 			}
+		} else if (ScriptServer::is_global_class(p_type)) {
+			inherits = ScriptServer::get_global_class_base(p_type);
+			bool is_native_class = ClassDB::class_exists(inherits);
+			inherited_type = is_native_class ? TypeCategory::CPP_TYPE : TypeCategory::OTHER_TYPE;
 		} else {
 			inherits = custom_type_parents[p_type];
 			if (ClassDB::class_exists(inherits)) {
@@ -315,18 +314,14 @@ void CreateDialog::_configure_search_option_item(TreeItem *r_item, const StringN
 		r_item->set_metadata(0, p_type);
 		r_item->set_text(0, p_type);
 
-		String script_path = ScriptServer::get_global_class_path(p_type);
-		Ref<Script> scr = ResourceLoader::load(script_path, "Script");
-
-		ERR_FAIL_COND(scr.is_null());
-		is_abstract = scr->is_abstract();
+		is_abstract = ScriptServer::is_global_class_abstract(p_type);
 
 		String tooltip = TTR("Script path: %s");
-		bool is_tool = scr->is_tool();
+		bool is_tool = ScriptServer::is_global_class_tool(p_type);
 		if (is_tool) {
 			tooltip = TTR("The script will run in the editor.") + "\n" + tooltip;
 		}
-		r_item->add_button(0, get_editor_theme_icon(SNAME("Script")), 1, false, vformat(tooltip, script_path));
+		r_item->add_button(0, get_editor_theme_icon(SNAME("Script")), 1, false, vformat(tooltip, ScriptServer::get_global_class_path(p_type)));
 		if (is_tool) {
 			int button_index = r_item->get_button_count(0) - 1;
 			r_item->set_button_color(0, button_index, get_theme_color(SNAME("accent_color"), EditorStringName(Editor)));
diff --git a/editor/editor_data.cpp b/editor/editor_data.cpp
index 73af9f39a6f5..f31e6aba4169 100644
--- a/editor/editor_data.cpp
+++ b/editor/editor_data.cpp
@@ -983,20 +983,6 @@ bool EditorData::script_class_is_parent(const String &p_class, const String &p_i
 	return true;
 }
 
-StringName EditorData::script_class_get_base(const String &p_class) const {
-	Ref<Script> script = script_class_load_script(p_class);
-	if (script.is_null()) {
-		return StringName();
-	}
-
-	Ref<Script> base_script = script->get_base_script();
-	if (base_script.is_null()) {
-		return ScriptServer::get_global_class_base(p_class);
-	}
-
-	return script->get_language()->get_global_class_name(base_script->get_path());
-}
-
 Variant EditorData::script_class_instance(const String &p_class) {
 	if (ScriptServer::is_global_class(p_class)) {
 		Ref<Script> script = script_class_load_script(p_class);
@@ -1026,22 +1012,25 @@ void EditorData::script_class_set_icon_path(const String &p_class, const String
 	_script_class_icon_paths[p_class] = p_icon_path;
 }
 
-String EditorData::script_class_get_icon_path(const String &p_class) const {
-	if (!ScriptServer::is_global_class(p_class)) {
-		return String();
-	}
-
+String EditorData::script_class_get_icon_path(const String &p_class, bool *r_valid) const {
 	String current = p_class;
-	String ret = _script_class_icon_paths[current];
-	while (ret.is_empty()) {
-		current = script_class_get_base(current);
+	while (true) {
 		if (!ScriptServer::is_global_class(current)) {
+			// If the classnames chain has a native class ancestor, we're done with success.
+			if (r_valid) {
+				*r_valid = ClassDB::class_exists(current);
+			}
 			return String();
 		}
-		ret = _script_class_icon_paths.has(current) ? _script_class_icon_paths[current] : String();
+		HashMap<StringName, String>::ConstIterator E = _script_class_icon_paths.find(current);
+		if ((bool)E) {
+			if (r_valid) {
+				*r_valid = true;
+			}
+			return E->value;
+		}
+		current = ScriptServer::get_global_class_base(current);
 	}
-
-	return ret;
 }
 
 StringName EditorData::script_class_get_name(const String &p_path) const {
@@ -1126,28 +1115,42 @@ Ref<Texture2D> EditorData::_load_script_icon(const String &p_path) const {
 	return nullptr;
 }
 
-Ref<Texture2D> EditorData::get_script_icon(const Ref<Script> &p_script) {
+Ref<Texture2D> EditorData::get_script_icon(const String &p_script_path) {
 	// Take from the local cache, if available.
-	if (_script_icon_cache.has(p_script)) {
+	if (_script_icon_cache.has(p_script_path)) {
 		// Can be an empty value if we can't resolve any icon for this script.
 		// An empty value is still cached to avoid unnecessary attempts at resolving it again.
-		return _script_icon_cache[p_script];
+		return _script_icon_cache[p_script_path];
+	}
+
+	// Fast path in case the whole hierarchy is made of global classes.
+	StringName class_name = script_class_get_name(p_script_path);
+	{
+		if (class_name != StringName()) {
+			bool icon_valid = false;
+			String icon_path = script_class_get_icon_path(class_name, &icon_valid);
+			if (icon_valid) {
+				Ref<Texture2D> icon = _load_script_icon(icon_path);
+				_script_icon_cache[p_script_path] = icon;
+				return icon;
+			}
+		}
 	}
 
-	Ref<Script> base_scr = p_script;
+	Ref<Script> base_scr = ResourceLoader::load(p_script_path, "Script");
 	while (base_scr.is_valid()) {
 		// Check for scripted classes.
 		String icon_path;
-		StringName class_name = script_class_get_name(base_scr->get_path());
-		if (base_scr->is_built_in() || class_name == StringName()) {
+		StringName base_class_name = script_class_get_name(base_scr->get_path());
+		if (base_scr->is_built_in() || base_class_name == StringName()) {
 			icon_path = base_scr->get_class_icon_path();
 		} else {
-			icon_path = script_class_get_icon_path(class_name);
+			icon_path = script_class_get_icon_path(base_class_name);
 		}
 
 		Ref<Texture2D> icon = _load_script_icon(icon_path);
 		if (icon.is_valid()) {
-			_script_icon_cache[p_script] = icon;
+			_script_icon_cache[p_script_path] = icon;
 			return icon;
 		}
 
@@ -1155,7 +1158,7 @@ Ref<Texture2D> EditorData::get_script_icon(const Ref<Script> &p_script) {
 		// TODO: Should probably be deprecated in 4.x
 		const EditorData::CustomType *ctype = get_custom_type_by_path(base_scr->get_path());
 		if (ctype && ctype->icon.is_valid()) {
-			_script_icon_cache[p_script] = ctype->icon;
+			_script_icon_cache[p_script_path] = ctype->icon;
 			return ctype->icon;
 		}
 
@@ -1163,21 +1166,16 @@ Ref<Texture2D> EditorData::get_script_icon(const Ref<Script> &p_script) {
 		base_scr = base_scr->get_base_script();
 	}
 
-	// No custom icon was found in the inheritance chain, so check the base
-	// class of the script instead.
-	String base_type;
-	p_script->get_language()->get_global_class_name(p_script->get_path(), &base_type);
-
 	// Check if the base type is an extension-defined type.
-	Ref<Texture2D> ext_icon = extension_class_get_icon(base_type);
+	Ref<Texture2D> ext_icon = extension_class_get_icon(class_name);
 	if (ext_icon.is_valid()) {
-		_script_icon_cache[p_script] = ext_icon;
+		_script_icon_cache[p_script_path] = ext_icon;
 		return ext_icon;
 	}
 
 	// If no icon found, cache it as null.
-	_script_icon_cache[p_script] = Ref<Texture>();
-	return nullptr;
+	_script_icon_cache[p_script_path] = Ref<Texture2D>();
+	return Ref<Texture2D>();
 }
 
 void EditorData::clear_script_icon_cache() {
diff --git a/editor/editor_data.h b/editor/editor_data.h
index 5b49304c73f2..943c661b6b2e 100644
--- a/editor/editor_data.h
+++ b/editor/editor_data.h
@@ -147,7 +147,7 @@ class EditorData {
 
 	HashMap<StringName, String> _script_class_icon_paths;
 	HashMap<String, StringName> _script_class_file_to_path;
-	HashMap<Ref<Script>, Ref<Texture>> _script_icon_cache;
+	HashMap<String, Ref<Texture>> _script_icon_cache;
 
 	Ref<Texture2D> _load_script_icon(const String &p_path) const;
 
@@ -241,7 +241,6 @@ class EditorData {
 	void notify_scene_saved(const String &p_path);
 
 	bool script_class_is_parent(const String &p_class, const String &p_inherits);
-	StringName script_class_get_base(const String &p_class) const;
 	Variant script_class_instance(const String &p_class);
 
 	Ref<Script> script_class_load_script(const String &p_class) const;
@@ -249,7 +248,7 @@ class EditorData {
 	StringName script_class_get_name(const String &p_path) const;
 	void script_class_set_name(const String &p_path, const StringName &p_class);
 
-	String script_class_get_icon_path(const String &p_class) const;
+	String script_class_get_icon_path(const String &p_class, bool *r_valid = nullptr) const;
 	void script_class_set_icon_path(const String &p_class, const String &p_icon_path);
 	void script_class_clear_icon_paths() { _script_class_icon_paths.clear(); }
 	void script_class_save_icon_paths();
@@ -257,7 +256,7 @@ class EditorData {
 
 	Ref<Texture2D> extension_class_get_icon(const String &p_class) const;
 
-	Ref<Texture2D> get_script_icon(const Ref<Script> &p_script);
+	Ref<Texture2D> get_script_icon(const String &p_script_path);
 	void clear_script_icon_cache();
 
 	EditorData();
diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp
index b8ce3ff65bf7..1db6c2a188e4 100644
--- a/editor/editor_file_system.cpp
+++ b/editor/editor_file_system.cpp
@@ -49,7 +49,7 @@
 
 EditorFileSystem *EditorFileSystem::singleton = nullptr;
 //the name is the version, to keep compatibility with different versions of Godot
-#define CACHE_FILE_NAME "filesystem_cache8"
+#define CACHE_FILE_NAME "filesystem_cache10"
 
 int EditorFileSystemDirectory::find_file_index(const String &p_file) const {
 	for (int i = 0; i < files.size(); i++) {
@@ -166,19 +166,19 @@ uint64_t EditorFileSystemDirectory::get_file_import_modified_time(int p_idx) con
 }
 
 String EditorFileSystemDirectory::get_file_script_class_name(int p_idx) const {
-	return files[p_idx]->script_class_name;
+	return files[p_idx]->class_info.name;
 }
 
 String EditorFileSystemDirectory::get_file_script_class_extends(int p_idx) const {
-	return files[p_idx]->script_class_extends;
+	return files[p_idx]->class_info.extends;
 }
 
 String EditorFileSystemDirectory::get_file_script_class_icon_path(int p_idx) const {
-	return files[p_idx]->script_class_icon_path;
+	return files[p_idx]->class_info.icon_path;
 }
 
 String EditorFileSystemDirectory::get_file_icon_path(int p_idx) const {
-	return files[p_idx]->icon_path;
+	return files[p_idx]->class_info.icon_path;
 }
 
 StringName EditorFileSystemDirectory::get_file_type(int p_idx) const {
@@ -303,13 +303,13 @@ void EditorFileSystem::_first_scan_process_scripts(const ScannedDirectory *p_sca
 			const String type = ResourceLoader::get_resource_type(path);
 
 			if (ClassDB::is_parent_class(type, SNAME("Script"))) {
-				String script_class_extends;
-				String script_class_icon_path;
-				String script_class_name = _get_global_script_class(type, path, &script_class_extends, &script_class_icon_path);
-				_register_global_class_script(path, path, type, script_class_name, script_class_extends, script_class_icon_path);
+				const ScriptClassInfo &info = _get_global_script_class(type, path);
+				ScriptClassInfoUpdate update(info);
+				update.type = type;
+				_register_global_class_script(path, path, update);
 
-				if (!script_class_name.is_empty()) {
-					p_existing_class_names.insert(script_class_name);
+				if (!info.name.is_empty()) {
+					p_existing_class_names.insert(info.name);
 				}
 			}
 		}
@@ -383,33 +383,25 @@ void EditorFileSystem::_scan_filesystem() {
 					name = cpath.path_join(name);
 
 					FileCache fc;
-					fc.type = split[1];
-					if (fc.type.contains_char('/')) {
-						fc.type = split[1].get_slice("/", 0);
-						fc.resource_script_class = split[1].get_slice("/", 1);
-					}
+					fc.type = split[1].get_slice("/", 0);
+					fc.resource_script_class = split[1].get_slice("/", 1);
 					fc.uid = split[2].to_int();
 					fc.modification_time = split[3].to_int();
 					fc.import_modification_time = split[4].to_int();
 					fc.import_valid = split[5].to_int() != 0;
 					fc.import_group_file = split[6].strip_edges();
-					fc.script_class_name = split[7].get_slice("<>", 0);
-					fc.script_class_extends = split[7].get_slice("<>", 1);
-					fc.script_class_icon_path = split[7].get_slice("<>", 2);
-					fc.import_md5 = split[7].get_slice("<>", 3);
-					String dest_paths = split[7].get_slice("<>", 4);
-					if (!dest_paths.is_empty()) {
-						fc.import_dest_paths = dest_paths.split("<*>");
-					}
-
-					String deps = split[8].strip_edges();
-					if (deps.length()) {
-						Vector<String> dp = deps.split("<>");
-						for (int i = 0; i < dp.size(); i++) {
-							const String &path = dp[i];
-							fc.deps.push_back(path);
-						}
+					{
+						const Vector<String> &slices = split[7].split("<>");
+						ERR_CONTINUE(slices.size() < 7);
+						fc.class_info.name = slices[0];
+						fc.class_info.extends = slices[1];
+						fc.class_info.icon_path = slices[2];
+						fc.class_info.is_abstract = slices[3].to_int();
+						fc.class_info.is_tool = slices[4].to_int();
+						fc.import_md5 = slices[5];
+						fc.import_dest_paths = slices[6].split("<*>");
 					}
+					fc.deps = split[8].strip_edges().split("<>");
 
 					file_cache[name] = fc;
 				}
@@ -859,7 +851,7 @@ bool EditorFileSystem::_update_scan_actions() {
 				}
 
 				if (ClassDB::is_parent_class(ia.new_file->type, SNAME("Script"))) {
-					_queue_update_script_class(new_file_path, ia.new_file->type, ia.new_file->script_class_name, ia.new_file->script_class_extends, ia.new_file->script_class_icon_path);
+					_queue_update_script_class(new_file_path, ScriptClassInfoUpdate::from_file_info(ia.new_file));
 				}
 				if (ia.new_file->type == SNAME("PackedScene")) {
 					_queue_update_scene_groups(new_file_path);
@@ -870,9 +862,9 @@ bool EditorFileSystem::_update_scan_actions() {
 				int idx = ia.dir->find_file_index(ia.file);
 				ERR_CONTINUE(idx == -1);
 
-				String script_class_name = ia.dir->files[idx]->script_class_name;
+				String class_name = ia.dir->files[idx]->class_info.name;
 				if (ClassDB::is_parent_class(ia.dir->files[idx]->type, SNAME("Script"))) {
-					_queue_update_script_class(ia.dir->get_file_path(idx), "", "", "", "");
+					_queue_update_script_class(ia.dir->get_file_path(idx), ScriptClassInfoUpdate());
 				}
 				if (ia.dir->files[idx]->type == SNAME("PackedScene")) {
 					_queue_update_scene_groups(ia.dir->get_file_path(idx));
@@ -883,11 +875,11 @@ bool EditorFileSystem::_update_scan_actions() {
 				ia.dir->files.remove_at(idx);
 
 				// Restore another script with the same global class name if it exists.
-				if (!script_class_name.is_empty()) {
+				if (!class_name.is_empty()) {
 					EditorFileSystemDirectory::FileInfo *old_fi = nullptr;
-					String old_file = _get_file_by_class_name(filesystem, script_class_name, old_fi);
+					String old_file = _get_file_by_class_name(filesystem, class_name, old_fi);
 					if (!old_file.is_empty() && old_fi) {
-						_queue_update_script_class(old_file, old_fi->type, old_fi->script_class_name, old_fi->script_class_extends, old_fi->script_class_icon_path);
+						_queue_update_script_class(old_file, ScriptClassInfoUpdate::from_file_info(old_fi));
 					}
 				}
 
@@ -1161,10 +1153,8 @@ void EditorFileSystem::_process_file_system(const ScannedDirectory *p_scan_dir,
 				fi->import_md5 = fc->import_md5;
 				fi->import_dest_paths = fc->import_dest_paths;
 				fi->import_valid = fc->import_valid;
-				fi->script_class_name = fc->script_class_name;
 				fi->import_group_file = fc->import_group_file;
-				fi->script_class_extends = fc->script_class_extends;
-				fi->script_class_icon_path = fc->script_class_icon_path;
+				fi->class_info = fc->class_info;
 
 				// Ensures backward compatibility when the project is loaded for the first time with the added import_md5
 				// and import_dest_paths properties in the file cache.
@@ -1202,7 +1192,7 @@ void EditorFileSystem::_process_file_system(const ScannedDirectory *p_scan_dir,
 			} else {
 				// Using get_resource_import_info() to prevent calling 3 times ResourceFormatImporter::_get_path_and_type.
 				ResourceFormatImporter::get_singleton()->get_resource_import_info(path, fi->type, fi->uid, fi->import_group_file);
-				fi->script_class_name = _get_global_script_class(fi->type, path, &fi->script_class_extends, &fi->script_class_icon_path);
+				fi->class_info = _get_global_script_class(fi->type, path);
 				fi->modified_time = 0;
 				fi->import_modified_time = 0;
 				fi->import_md5 = "";
@@ -1227,22 +1217,20 @@ void EditorFileSystem::_process_file_system(const ScannedDirectory *p_scan_dir,
 				fi->import_md5 = "";
 				fi->import_dest_paths = Vector<String>();
 				fi->import_valid = true;
-				fi->script_class_name = fc->script_class_name;
-				fi->script_class_extends = fc->script_class_extends;
-				fi->script_class_icon_path = fc->script_class_icon_path;
+				fi->class_info = fc->class_info;
 
 				if (first_scan && ClassDB::is_parent_class(fi->type, SNAME("Script"))) {
 					bool update_script = false;
-					String old_class_name = fi->script_class_name;
-					fi->script_class_name = _get_global_script_class(fi->type, path, &fi->script_class_extends, &fi->script_class_icon_path);
-					if (old_class_name != fi->script_class_name) {
+					String old_class_name = fi->class_info.name;
+					fi->class_info = _get_global_script_class(fi->type, path);
+					if (old_class_name != fi->class_info.name) {
 						update_script = true;
-					} else if (!fi->script_class_name.is_empty() && (!ScriptServer::is_global_class(fi->script_class_name) || ScriptServer::get_global_class_path(fi->script_class_name) != path)) {
+					} else if (!fi->class_info.name.is_empty() && (!ScriptServer::is_global_class(fi->class_info.name) || ScriptServer::get_global_class_path(fi->class_info.name) != path)) {
 						// This script has a class name but is not in the global class names or the path of the class has changed.
 						update_script = true;
 					}
 					if (update_script) {
-						_queue_update_script_class(path, fi->type, fi->script_class_name, fi->script_class_extends, fi->script_class_icon_path);
+						_queue_update_script_class(path, ScriptClassInfoUpdate::from_file_info(fi));
 					}
 				}
 			} else {
@@ -1256,7 +1244,7 @@ void EditorFileSystem::_process_file_system(const ScannedDirectory *p_scan_dir,
 					fi->type = "OtherFile";
 				}
 				fi->uid = ResourceLoader::get_resource_uid(path);
-				fi->script_class_name = _get_global_script_class(fi->type, path, &fi->script_class_extends, &fi->script_class_icon_path);
+				fi->class_info = _get_global_script_class(fi->type, path);
 				fi->deps = _get_dependencies(path);
 				fi->modified_time = mt;
 				fi->import_modified_time = 0;
@@ -1267,7 +1255,7 @@ void EditorFileSystem::_process_file_system(const ScannedDirectory *p_scan_dir,
 				// Files in dep_update_list are forced for rescan to update dependencies. They don't need other updates.
 				if (!dep_update_list.has(path)) {
 					if (ClassDB::is_parent_class(fi->type, SNAME("Script"))) {
-						_queue_update_script_class(path, fi->type, fi->script_class_name, fi->script_class_extends, fi->script_class_icon_path);
+						_queue_update_script_class(path, ScriptClassInfoUpdate::from_file_info(fi));
 					}
 					if (fi->type == SNAME("PackedScene")) {
 						_queue_update_scene_groups(path);
@@ -1428,7 +1416,7 @@ void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, ScanPr
 					if (fi->type == "" && other_file_extensions.has(ext)) {
 						fi->type = "OtherFile";
 					}
-					fi->script_class_name = _get_global_script_class(fi->type, path, &fi->script_class_extends, &fi->script_class_icon_path);
+					fi->class_info = _get_global_script_class(fi->type, path);
 					fi->import_valid = (fi->type == "TextFile" || fi->type == "OtherFile") ? true : ResourceLoader::is_import_valid(path);
 					fi->import_group_file = ResourceLoader::get_import_group_file(path);
 
@@ -1587,7 +1575,7 @@ void EditorFileSystem::_remove_invalid_global_class_names(const HashSet<String>
 
 String EditorFileSystem::_get_file_by_class_name(EditorFileSystemDirectory *p_dir, const String &p_class_name, EditorFileSystemDirectory::FileInfo *&r_file_info) {
 	for (EditorFileSystemDirectory::FileInfo *fi : p_dir->files) {
-		if (fi->script_class_name == p_class_name) {
+		if (fi->class_info.name == p_class_name) {
 			r_file_info = fi;
 			return p_dir->get_path().path_join(fi->file);
 		}
@@ -1768,7 +1756,7 @@ void EditorFileSystem::_save_filesystem_cache(EditorFileSystemDirectory *p_dir,
 		cache_string.append(itos(file_info->import_modified_time));
 		cache_string.append(itos(file_info->import_valid));
 		cache_string.append(file_info->import_group_file);
-		cache_string.append(String("<>").join({ file_info->script_class_name, file_info->script_class_extends, file_info->script_class_icon_path, file_info->import_md5, String("<*>").join(file_info->import_dest_paths) }));
+		cache_string.append(String("<>").join({ file_info->class_info.name, file_info->class_info.extends, file_info->class_info.icon_path, itos(file_info->class_info.is_abstract), itos(file_info->class_info.is_tool), file_info->import_md5, String("<*>").join(file_info->import_dest_paths) }));
 		cache_string.append(String("<>").join(file_info->deps));
 
 		p_file->store_line(String("::").join(cache_string));
@@ -1980,29 +1968,21 @@ Vector<String> EditorFileSystem::_get_dependencies(const String &p_path) {
 	return ret;
 }
 
-String EditorFileSystem::_get_global_script_class(const String &p_type, const String &p_path, String *r_extends, String *r_icon_path) const {
+EditorFileSystem::ScriptClassInfo EditorFileSystem::_get_global_script_class(const String &p_type, const String &p_path) const {
+	ScriptClassInfo info;
 	for (int i = 0; i < ScriptServer::get_language_count(); i++) {
 		if (ScriptServer::get_language(i)->handles_global_class_type(p_type)) {
-			String global_name;
-			String extends;
-			String icon_path;
-
-			global_name = ScriptServer::get_language(i)->get_global_class_name(p_path, &extends, &icon_path);
-			*r_extends = extends;
-			*r_icon_path = icon_path;
-			return global_name;
+			info.name = ScriptServer::get_language(i)->get_global_class_name(p_path, &info.extends, &info.icon_path, &info.is_abstract, &info.is_tool);
 		}
 	}
-	*r_extends = String();
-	*r_icon_path = String();
-	return String();
+	return info;
 }
 
 void EditorFileSystem::_update_file_icon_path(EditorFileSystemDirectory::FileInfo *file_info) {
 	String icon_path;
 	if (file_info->resource_script_class != StringName()) {
 		icon_path = EditorNode::get_editor_data().script_class_get_icon_path(file_info->resource_script_class);
-	} else if (file_info->script_class_icon_path.is_empty() && !file_info->deps.is_empty()) {
+	} else if (file_info->class_info.icon_path.is_empty() && !file_info->deps.is_empty()) {
 		const String &script_dep = file_info->deps[0]; // Assuming the first dependency is a script.
 		const String &script_path = script_dep.contains("::") ? script_dep.get_slice("::", 2) : script_dep;
 		if (!script_path.is_empty()) {
@@ -2014,7 +1994,7 @@ void EditorFileSystem::_update_file_icon_path(EditorFileSystemDirectory::FileInf
 					int script_file;
 					EditorFileSystemDirectory *efsd = find_file(script_path, &script_file);
 					if (efsd) {
-						icon_path = efsd->files[script_file]->script_class_icon_path;
+						icon_path = efsd->files[script_file]->class_info.icon_path;
 					}
 				}
 				file_icon_cache.insert(script_path, icon_path);
@@ -2029,7 +2009,7 @@ void EditorFileSystem::_update_file_icon_path(EditorFileSystemDirectory::FileInf
 		}
 	}
 
-	file_info->icon_path = icon_path;
+	file_info->class_info.icon_path = icon_path;
 }
 
 void EditorFileSystem::_update_files_icon_path(EditorFileSystemDirectory *edp) {
@@ -2068,10 +2048,10 @@ void EditorFileSystem::_update_script_classes() {
 		}
 
 		int step_count = 0;
-		for (const KeyValue<String, ScriptInfo> &E : update_script_paths) {
-			_register_global_class_script(E.key, E.key, E.value.type, E.value.script_class_name, E.value.script_class_extends, E.value.script_class_icon_path);
+		for (const KeyValue<String, ScriptClassInfoUpdate> &E : update_script_paths) {
+			_register_global_class_script(E.key, E.key, E.value);
 			if (ep) {
-				ep->step(E.value.script_class_name, step_count++, false);
+				ep->step(E.value.name, step_count++, false);
 			}
 		}
 
@@ -2181,16 +2161,10 @@ void EditorFileSystem::_process_update_pending() {
 	_update_pending_scene_groups();
 }
 
-void EditorFileSystem::_queue_update_script_class(const String &p_path, const String &p_type, const String &p_script_class_name, const String &p_script_class_extends, const String &p_script_class_icon_path) {
+void EditorFileSystem::_queue_update_script_class(const String &p_path, const ScriptClassInfoUpdate &p_script_update) {
 	MutexLock update_script_lock(update_script_mutex);
 
-	ScriptInfo si;
-	si.type = p_type;
-	si.script_class_name = p_script_class_name;
-	si.script_class_extends = p_script_class_extends;
-	si.script_class_icon_path = p_script_class_icon_path;
-	update_script_paths.insert(p_path, si);
-
+	update_script_paths.insert(p_path, p_script_update);
 	update_script_paths_documentation.insert(p_path);
 }
 
@@ -2291,8 +2265,10 @@ void EditorFileSystem::update_files(const Vector<String> &p_script_paths) {
 					}
 				}
 				if (ClassDB::is_parent_class(fs->files[cpos]->type, SNAME("Script"))) {
-					_queue_update_script_class(file, fs->files[cpos]->type, "", "", "");
-					if (!fs->files[cpos]->script_class_icon_path.is_empty()) {
+					ScriptClassInfoUpdate update;
+					update.type = fs->files[cpos]->type;
+					_queue_update_script_class(file, update);
+					if (!fs->files[cpos]->class_info.icon_path.is_empty()) {
 						update_files_icon_cache = true;
 					}
 				}
@@ -2349,16 +2325,16 @@ void EditorFileSystem::update_files(const Vector<String> &p_script_paths) {
 			}
 
 			EditorFileSystemDirectory::FileInfo *fi = fs->files[cpos];
-			const String old_script_class_icon_path = fi->script_class_icon_path;
-			const String old_class_name = fi->script_class_name;
+			const String old_script_class_icon_path = fi->class_info.icon_path;
+			const String old_class_name = fi->class_info.name;
 			fi->type = type;
 			fi->resource_script_class = script_class;
 			fi->uid = uid;
-			fi->script_class_name = _get_global_script_class(type, file, &fi->script_class_extends, &fi->script_class_icon_path);
+			fi->class_info = _get_global_script_class(type, file);
 			fi->import_group_file = ResourceLoader::get_import_group_file(file);
 			fi->modified_time = FileAccess::get_modified_time(file);
 			fi->deps = _get_dependencies(file);
-			fi->import_valid = type == "TextFile" || type == "OtherFile" || ResourceLoader::is_import_valid(file);
+			fi->import_valid = (type == "TextFile" || type == "OtherFile") ? true : ResourceLoader::is_import_valid(file);
 
 			if (uid != ResourceUID::INVALID_ID) {
 				if (ResourceUID::get_singleton()->has_id(uid)) {
@@ -2384,7 +2360,7 @@ void EditorFileSystem::update_files(const Vector<String> &p_script_paths) {
 			EditorResourcePreview::get_singleton()->check_for_invalidation(file);
 
 			if (ClassDB::is_parent_class(fi->type, SNAME("Script"))) {
-				_queue_update_script_class(file, fi->type, fi->script_class_name, fi->script_class_extends, fi->script_class_icon_path);
+				_queue_update_script_class(file, ScriptClassInfoUpdate::from_file_info(fi));
 			}
 			if (fi->type == SNAME("PackedScene")) {
 				_queue_update_scene_groups(file);
@@ -2392,16 +2368,16 @@ void EditorFileSystem::update_files(const Vector<String> &p_script_paths) {
 
 			if (ClassDB::is_parent_class(fi->type, SNAME("Resource"))) {
 				files_to_update_icon_path.push_back(fi);
-			} else if (old_script_class_icon_path != fi->script_class_icon_path) {
+			} else if (old_script_class_icon_path != fi->class_info.icon_path) {
 				update_files_icon_cache = true;
 			}
 
 			// Restore another script as the global class name if multiple scripts had the same old class name.
-			if (!old_class_name.is_empty() && fi->script_class_name != old_class_name && ClassDB::is_parent_class(type, SNAME("Script"))) {
+			if (!old_class_name.is_empty() && fi->class_info.name != old_class_name && ClassDB::is_parent_class(type, SNAME("Script"))) {
 				EditorFileSystemDirectory::FileInfo *old_fi = nullptr;
 				String old_file = _get_file_by_class_name(filesystem, old_class_name, old_fi);
 				if (!old_file.is_empty() && old_fi) {
-					_queue_update_script_class(old_file, old_fi->type, old_fi->script_class_name, old_fi->script_class_extends, old_fi->script_class_icon_path);
+					_queue_update_script_class(old_file, ScriptClassInfoUpdate::from_file_info(old_fi));
 				}
 			}
 			updated = true;
@@ -2435,16 +2411,16 @@ HashSet<String> EditorFileSystem::get_valid_extensions() const {
 	return valid_extensions;
 }
 
-void EditorFileSystem::_register_global_class_script(const String &p_search_path, const String &p_target_path, const String &p_type, const String &p_script_class_name, const String &p_script_class_extends, const String &p_script_class_icon_path) {
+void EditorFileSystem::_register_global_class_script(const String &p_search_path, const String &p_target_path, const ScriptClassInfoUpdate &p_script_update) {
 	ScriptServer::remove_global_class_by_path(p_search_path); // First remove, just in case it changed
 
-	if (p_script_class_name.is_empty()) {
+	if (p_script_update.name.is_empty()) {
 		return;
 	}
 
 	String lang;
 	for (int j = 0; j < ScriptServer::get_language_count(); j++) {
-		if (ScriptServer::get_language(j)->handles_global_class_type(p_type)) {
+		if (ScriptServer::get_language(j)->handles_global_class_type(p_script_update.type)) {
 			lang = ScriptServer::get_language(j)->get_name();
 			break;
 		}
@@ -2453,9 +2429,9 @@ void EditorFileSystem::_register_global_class_script(const String &p_search_path
 		return; // No lang found that can handle this global class
 	}
 
-	ScriptServer::add_global_class(p_script_class_name, p_script_class_extends, lang, p_target_path);
-	EditorNode::get_editor_data().script_class_set_icon_path(p_script_class_name, p_script_class_icon_path);
-	EditorNode::get_editor_data().script_class_set_name(p_target_path, p_script_class_name);
+	ScriptServer::add_global_class(p_script_update.name, p_script_update.extends, lang, p_target_path, p_script_update.is_abstract, p_script_update.is_tool);
+	EditorNode::get_editor_data().script_class_set_icon_path(p_script_update.name, p_script_update.icon_path);
+	EditorNode::get_editor_data().script_class_set_name(p_target_path, p_script_update.name);
 }
 
 void EditorFileSystem::register_global_class_script(const String &p_search_path, const String &p_target_path) {
@@ -2463,7 +2439,7 @@ void EditorFileSystem::register_global_class_script(const String &p_search_path,
 	EditorFileSystemDirectory *efsd = find_file(p_search_path, &index_file);
 	if (efsd) {
 		const EditorFileSystemDirectory::FileInfo *fi = efsd->files[index_file];
-		EditorFileSystem::get_singleton()->_register_global_class_script(p_search_path, p_target_path, fi->type, fi->script_class_name, fi->script_class_extends, fi->script_class_icon_path);
+		EditorFileSystem::get_singleton()->_register_global_class_script(p_search_path, p_target_path, ScriptClassInfoUpdate::from_file_info(fi));
 	} else {
 		ScriptServer::remove_global_class_by_path(p_search_path);
 	}
diff --git a/editor/editor_file_system.h b/editor/editor_file_system.h
index 61041209a7fd..5b78b86a3f3f 100644
--- a/editor/editor_file_system.h
+++ b/editor/editor_file_system.h
@@ -66,11 +66,15 @@ class EditorFileSystemDirectory : public Object {
 		String import_group_file;
 		Vector<String> deps;
 		bool verified = false; //used for checking changes
-		// These are for script resources only.
-		String script_class_name;
-		String script_class_extends;
-		String script_class_icon_path;
-		String icon_path;
+		// This is for script resources only.
+		struct ScriptClassInfo {
+			String name;
+			String extends;
+			String icon_path;
+			bool is_abstract = false;
+			bool is_tool = false;
+		};
+		ScriptClassInfo class_info;
 	};
 
 	Vector<FileInfo *> files;
@@ -203,9 +207,11 @@ class EditorFileSystem : public Node {
 
 	static EditorFileSystem *singleton;
 
+	using ScriptClassInfo = EditorFileSystemDirectory::FileInfo::ScriptClassInfo;
+
 	/* Used for reading the filesystem cache file */
 	struct FileCache {
-		String type;
+		StringName type;
 		String resource_script_class;
 		ResourceUID::ID uid = ResourceUID::INVALID_ID;
 		uint64_t modification_time = 0;
@@ -215,9 +221,7 @@ class EditorFileSystem : public Node {
 		Vector<String> deps;
 		bool import_valid = false;
 		String import_group_file;
-		String script_class_name;
-		String script_class_extends;
-		String script_class_icon_path;
+		ScriptClassInfo class_info;
 	};
 
 	HashMap<String, FileCache> file_cache;
@@ -288,17 +292,27 @@ class EditorFileSystem : public Node {
 		}
 	};
 
-	struct ScriptInfo {
-		String type;
-		String script_class_name;
-		String script_class_extends;
-		String script_class_icon_path;
+	struct ScriptClassInfoUpdate : public ScriptClassInfo {
+		StringName type;
+		ScriptClassInfoUpdate() = default;
+		explicit ScriptClassInfoUpdate(const ScriptClassInfo &p_info) :
+				ScriptClassInfo(p_info) {}
+		static ScriptClassInfoUpdate from_file_info(const EditorFileSystemDirectory::FileInfo *p_fi) {
+			ScriptClassInfoUpdate update;
+			update.type = p_fi->type;
+			update.name = p_fi->class_info.name;
+			update.extends = p_fi->class_info.extends;
+			update.icon_path = p_fi->class_info.icon_path;
+			update.is_abstract = p_fi->class_info.is_abstract;
+			update.is_tool = p_fi->class_info.is_tool;
+			return update;
+		}
 	};
 
 	Mutex update_script_mutex;
-	HashMap<String, ScriptInfo> update_script_paths;
+	HashMap<String, ScriptClassInfoUpdate> update_script_paths;
 	HashSet<String> update_script_paths_documentation;
-	void _queue_update_script_class(const String &p_path, const String &p_type, const String &p_script_class_name, const String &p_script_class_extends, const String &p_script_class_icon_path);
+	void _queue_update_script_class(const String &p_path, const ScriptClassInfoUpdate &p_script_update);
 	void _update_script_classes();
 	void _update_script_documentation();
 	void _process_update_pending();
@@ -312,7 +326,7 @@ class EditorFileSystem : public Node {
 	void _update_pending_scene_groups();
 	void _get_all_scenes(EditorFileSystemDirectory *p_dir, HashSet<String> &r_list);
 
-	String _get_global_script_class(const String &p_type, const String &p_path, String *r_extends, String *r_icon_path) const;
+	ScriptClassInfo _get_global_script_class(const String &p_type, const String &p_path) const;
 
 	static Error _resource_import(const String &p_path);
 	static Ref<Resource> _load_resource_on_startup(ResourceFormatImporter *p_importer, const String &p_path, Error *r_error, bool p_use_sub_threads, float *r_progress, ResourceFormatLoader::CacheMode p_cache_mode);
@@ -359,7 +373,7 @@ class EditorFileSystem : public Node {
 	void _remove_invalid_global_class_names(const HashSet<String> &p_existing_class_names);
 	String _get_file_by_class_name(EditorFileSystemDirectory *p_dir, const String &p_class_name, EditorFileSystemDirectory::FileInfo *&r_file_info);
 
-	void _register_global_class_script(const String &p_search_path, const String &p_target_path, const String &p_type, const String &p_script_class_name, const String &p_script_class_extends, const String &p_script_class_icon_path);
+	void _register_global_class_script(const String &p_search_path, const String &p_target_path, const ScriptClassInfoUpdate &p_script_update);
 
 protected:
 	void _notification(int p_what);
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index 4b7abdf2b072..3ada85d3de91 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -4805,13 +4805,13 @@ void EditorNode::_pick_main_scene_custom_action(const String &p_custom_action_na
 	}
 }
 
-Ref<Texture2D> EditorNode::_get_class_or_script_icon(const String &p_class, const Ref<Script> &p_script, const String &p_fallback, bool p_fallback_script_to_theme) {
+Ref<Texture2D> EditorNode::_get_class_or_script_icon(const String &p_class, const String &p_script_path, const String &p_fallback, bool p_fallback_script_to_theme) {
 	ERR_FAIL_COND_V_MSG(p_class.is_empty(), nullptr, "Class name cannot be empty.");
 	EditorData &ed = EditorNode::get_editor_data();
 
 	// Check for a script icon first.
-	if (p_script.is_valid()) {
-		Ref<Texture2D> script_icon = ed.get_script_icon(p_script);
+	if (!p_script_path.is_empty()) {
+		Ref<Texture2D> script_icon = ed.get_script_icon(p_script_path);
 		if (script_icon.is_valid()) {
 			return script_icon;
 		}
@@ -4819,14 +4819,15 @@ Ref<Texture2D> EditorNode::_get_class_or_script_icon(const String &p_class, cons
 		if (p_fallback_script_to_theme) {
 			// Look for the native base type in the editor theme. This is relevant for
 			// scripts extending other scripts and for built-in classes.
-			String script_class_name = p_script->get_language()->get_global_class_name(p_script->get_path());
 			String base_type;
-			if (script_class_name.is_empty()) {
-				base_type = p_script->get_instance_base_type();
+			if (ScriptServer::is_global_class(p_class)) {
+				base_type = ScriptServer::get_global_class_native_base(p_class);
 			} else {
-				base_type = ScriptServer::get_global_class_native_base(script_class_name);
+				Ref<Script> scr = ResourceLoader::load(p_script_path, "Script");
+				if (scr.is_valid()) {
+					base_type = scr->get_instance_base_type();
+				}
 			}
-
 			if (theme.is_valid() && theme->has_icon(base_type, EditorStringName(EditorIcons))) {
 				return theme->get_icon(base_type, EditorStringName(EditorIcons));
 			}
@@ -4899,21 +4900,21 @@ Ref<Texture2D> EditorNode::get_object_icon(const Object *p_object, const String
 	if (Object::cast_to<MultiNodeEdit>(p_object)) {
 		return get_class_icon(Object::cast_to<MultiNodeEdit>(p_object)->get_edited_class_name(), p_fallback);
 	} else {
-		return _get_class_or_script_icon(p_object->get_class(), scr, p_fallback);
+		return _get_class_or_script_icon(p_object->get_class(), scr.is_valid() ? scr->get_path() : String(), p_fallback);
 	}
 }
 
 Ref<Texture2D> EditorNode::get_class_icon(const String &p_class, const String &p_fallback) {
 	ERR_FAIL_COND_V_MSG(p_class.is_empty(), nullptr, "Class name cannot be empty.");
 
-	Ref<Script> scr;
+	String script_path;
 	if (ScriptServer::is_global_class(p_class)) {
-		scr = EditorNode::get_editor_data().script_class_load_script(p_class);
+		script_path = ScriptServer::get_global_class_path(p_class);
 	} else if (ResourceLoader::exists(p_class)) { // If the script is not a class_name we check if the script resource exists.
-		scr = ResourceLoader::load(p_class);
+		script_path = p_class;
 	}
 
-	return _get_class_or_script_icon(p_class, scr, p_fallback, true);
+	return _get_class_or_script_icon(p_class, script_path, p_fallback, true);
 }
 
 bool EditorNode::is_object_of_custom_type(const Object *p_object, const StringName &p_class) {
diff --git a/editor/editor_node.h b/editor/editor_node.h
index b77f6553fe96..2410996c948e 100644
--- a/editor/editor_node.h
+++ b/editor/editor_node.h
@@ -648,7 +648,7 @@ class EditorNode : public Node {
 	void _feature_profile_changed();
 	bool _is_class_editor_disabled_by_feature_profile(const StringName &p_class);
 
-	Ref<Texture2D> _get_class_or_script_icon(const String &p_class, const Ref<Script> &p_script, const String &p_fallback = "Object", bool p_fallback_script_to_theme = false);
+	Ref<Texture2D> _get_class_or_script_icon(const String &p_class, const String &p_script_path, const String &p_fallback = "Object", bool p_fallback_script_to_theme = false);
 
 	void _pick_main_scene_custom_action(const String &p_custom_action_name);
 
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index db5a3b65ccb0..2383401630c4 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -2831,7 +2831,7 @@ bool GDScriptLanguage::handles_global_class_type(const String &p_type) const {
 	return p_type == "GDScript";
 }
 
-String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_base_type, String *r_icon_path) const {
+String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_base_type, String *r_icon_path, bool *r_is_abstract, bool *r_is_tool) const {
 	Error err;
 	Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ, &err);
 	if (err) {
@@ -2932,6 +2932,12 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b
 	if (r_icon_path) {
 		*r_icon_path = c->simplified_icon_path;
 	}
+	if (r_is_abstract) {
+		*r_is_abstract = false;
+	}
+	if (r_is_tool) {
+		*r_is_tool = parser.is_tool();
+	}
 	return c->identifier != nullptr ? String(c->identifier->name) : String();
 }
 
diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h
index ed04482f4811..9e96b8833496 100644
--- a/modules/gdscript/gdscript.h
+++ b/modules/gdscript/gdscript.h
@@ -638,7 +638,7 @@ class GDScriptLanguage : public ScriptLanguage {
 	/* GLOBAL CLASSES */
 
 	virtual bool handles_global_class_type(const String &p_type) const override;
-	virtual String get_global_class_name(const String &p_path, String *r_base_type = nullptr, String *r_icon_path = nullptr) const override;
+	virtual String get_global_class_name(const String &p_path, String *r_base_type = nullptr, String *r_icon_path = nullptr, bool *r_is_abstract = nullptr, bool *r_is_tool = nullptr) const override;
 
 	void add_orphan_subclass(const String &p_qualified_name, const ObjectID &p_subclass);
 	Ref<GDScript> get_orphan_subclass(const String &p_qualified_name);
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp
index e9a821c65a95..76c3ca3cb3eb 100644
--- a/modules/mono/csharp_script.cpp
+++ b/modules/mono/csharp_script.cpp
@@ -445,9 +445,9 @@ bool CSharpLanguage::handles_global_class_type(const String &p_type) const {
 	return p_type == get_type();
 }
 
-String CSharpLanguage::get_global_class_name(const String &p_path, String *r_base_type, String *r_icon_path) const {
+String CSharpLanguage::get_global_class_name(const String &p_path, String *r_base_type, String *r_icon_path, bool *r_is_abstract, bool *r_is_tool) const {
 	String class_name;
-	GDMonoCache::managed_callbacks.ScriptManagerBridge_GetGlobalClassName(&p_path, r_base_type, r_icon_path, &class_name);
+	GDMonoCache::managed_callbacks.ScriptManagerBridge_GetGlobalClassName(&p_path, r_base_type, r_icon_path, r_is_abstract, r_is_tool, &class_name);
 	return class_name;
 }
 
diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h
index 525357a4c413..4dfed135f861 100644
--- a/modules/mono/csharp_script.h
+++ b/modules/mono/csharp_script.h
@@ -530,7 +530,7 @@ class CSharpLanguage : public ScriptLanguage {
 
 	/* SCRIPT GLOBAL CLASS FUNCTIONS */
 	virtual bool handles_global_class_type(const String &p_type) const override;
-	virtual String get_global_class_name(const String &p_path, String *r_base_type = nullptr, String *r_icon_path = nullptr) const override;
+	virtual String get_global_class_name(const String &p_path, String *r_base_type = nullptr, String *r_icon_path = nullptr, bool *r_is_abstract = nullptr, bool *r_is_tool = nullptr) const override;
 
 	/* DEBUGGER FUNCTIONS */
 	String debug_get_error() const override;
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs
index c7be0464b6c8..b0ff041fa49a 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs
@@ -19,7 +19,7 @@ public unsafe struct ManagedCallbacks
         public delegate* unmanaged<godot_string_name*, IntPtr, IntPtr> ScriptManagerBridge_CreateManagedForGodotObjectBinding;
         public delegate* unmanaged<IntPtr, IntPtr, godot_variant**, int, godot_bool> ScriptManagerBridge_CreateManagedForGodotObjectScriptInstance;
         public delegate* unmanaged<IntPtr, godot_string_name*, void> ScriptManagerBridge_GetScriptNativeName;
-        public delegate* unmanaged<godot_string*, godot_string*, godot_string*, godot_string*, void> ScriptManagerBridge_GetGlobalClassName;
+        public delegate* unmanaged<godot_string*, godot_string*, godot_string*, godot_bool*, godot_bool*, godot_string*, void> ScriptManagerBridge_GetGlobalClassName;
         public delegate* unmanaged<IntPtr, IntPtr, void> ScriptManagerBridge_SetGodotObjectPtr;
         public delegate* unmanaged<IntPtr, godot_string_name*, godot_variant**, int, godot_bool*, void> ScriptManagerBridge_RaiseEventSignal;
         public delegate* unmanaged<IntPtr, IntPtr, godot_bool> ScriptManagerBridge_ScriptIsOrInherits;
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
index e53f05f05825..a327b9cd354b 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
@@ -199,7 +199,7 @@ internal static unsafe void GetScriptNativeName(IntPtr scriptPtr, godot_string_n
         }
 
         [UnmanagedCallersOnly]
-        internal static unsafe void GetGlobalClassName(godot_string* scriptPath, godot_string* outBaseType, godot_string* outIconPath, godot_string* outClassName)
+        internal static unsafe void GetGlobalClassName(godot_string* scriptPath, godot_string* outBaseType, godot_string* outIconPath, godot_bool* outIsAbstract, godot_bool* outIsTool, godot_string* outClassName)
         {
             // This method must always return the outBaseType for every script, even if the script is
             // not a global class. But if the script is not a global class it must return an empty
@@ -254,6 +254,16 @@ internal static unsafe void GetGlobalClassName(godot_string* scriptPath, godot_s
                 }
             }
 
+            if (outIsAbstract != null)
+            {
+                *outIsAbstract = scriptType.IsAbstract.ToGodotBool();
+            }
+
+            if (outIsTool != null)
+            {
+                *outIsTool = Attribute.IsDefined(scriptType, typeof(ToolAttribute)).ToGodotBool();
+            }
+
             if (!IsGlobalClass(scriptType))
             {
                 // Scripts that are not global classes should not have a name.
diff --git a/modules/mono/mono_gd/gd_mono_cache.h b/modules/mono/mono_gd/gd_mono_cache.h
index 7c24f28b3a2a..f618921cd02b 100644
--- a/modules/mono/mono_gd/gd_mono_cache.h
+++ b/modules/mono/mono_gd/gd_mono_cache.h
@@ -85,7 +85,7 @@ struct ManagedCallbacks {
 	using FuncScriptManagerBridge_CreateManagedForGodotObjectBinding = GCHandleIntPtr(GD_CLR_STDCALL *)(const StringName *, Object *);
 	using FuncScriptManagerBridge_CreateManagedForGodotObjectScriptInstance = bool(GD_CLR_STDCALL *)(const CSharpScript *, Object *, const Variant **, int32_t);
 	using FuncScriptManagerBridge_GetScriptNativeName = void(GD_CLR_STDCALL *)(const CSharpScript *, StringName *);
-	using FuncScriptManagerBridge_GetGlobalClassName = void(GD_CLR_STDCALL *)(const String *, String *, String *, String *);
+	using FuncScriptManagerBridge_GetGlobalClassName = void(GD_CLR_STDCALL *)(const String *, String *, String *, bool *, bool *, String *);
 	using FuncScriptManagerBridge_SetGodotObjectPtr = void(GD_CLR_STDCALL *)(GCHandleIntPtr, Object *);
 	using FuncScriptManagerBridge_RaiseEventSignal = void(GD_CLR_STDCALL *)(GCHandleIntPtr, const StringName *, const Variant **, int32_t, bool *);
 	using FuncScriptManagerBridge_ScriptIsOrInherits = bool(GD_CLR_STDCALL *)(const CSharpScript *, const CSharpScript *);


Test patch:
diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp
index 14dd79da9048..c7f92daa646a 100644
--- a/modules/gdscript/tests/gdscript_test_runner.cpp
+++ b/modules/gdscript/tests/gdscript_test_runner.cpp
@@ -368,7 +368,9 @@ static bool generate_class_index_recursive(const String &p_dir) {
 			}
 			String base_type;
 			String source_file = current_dir.path_join(next);
-			String class_name = GDScriptLanguage::get_singleton()->get_global_class_name(source_file, &base_type);
+			bool is_abstract = false;
+			bool is_tool = false;
+			String class_name = GDScriptLanguage::get_singleton()->get_global_class_name(source_file, &base_type, nullptr, &is_abstract, &is_tool);
 			if (class_name.is_empty()) {
 				next = dir->get_next();
 				continue;
@@ -376,7 +378,7 @@ static bool generate_class_index_recursive(const String &p_dir) {
 			ERR_FAIL_COND_V_MSG(ScriptServer::is_global_class(class_name), false,
 					"Class name '" + class_name + "' from " + source_file + " is already used in " + ScriptServer::get_global_class_path(class_name));
 
-			ScriptServer::add_global_class(class_name, base_type, gdscript_name, source_file);
+			ScriptServer::add_global_class(class_name, base_type, gdscript_name, source_file, is_abstract, is_tool);
 		}
 
 		next = dir->get_next();
diff --git a/modules/gdscript/tests/test_gdscript.cpp b/modules/gdscript/tests/test_gdscript.cpp
index 7e93569e3901..ec20ed19fb9b 100644
--- a/modules/gdscript/tests/test_gdscript.cpp
+++ b/modules/gdscript/tests/test_gdscript.cpp
@@ -297,10 +297,10 @@ void test(TestType p_type) {
 	TypedArray<Dictionary> script_classes = ProjectSettings::get_singleton()->get_global_class_list();
 	for (int i = 0; i < script_classes.size(); i++) {
 		Dictionary c = script_classes[i];
-		if (!c.has("class") || !c.has("language") || !c.has("path") || !c.has("base")) {
+		if (!c.has("class") || !c.has("language") || !c.has("path") || !c.has("base") || !c.has("is_abstract") || !c.has("is_tool")) {
 			continue;
 		}
-		ScriptServer::add_global_class(c["class"], c["base"], c["language"], c["path"]);
+		ScriptServer::add_global_class(c["class"], c["base"], c["language"], c["path"], c["is_abstract"], c["is_tool"]);
 	}
 
 	Vector<uint8_t> buf;