Repository: godotengine/godot (language: cpp)

Issue:
Upgrading project to 4.4 results in GDScript parser errors
### Tested versions

- Reproducible in: 4.4.beta3

### System information

Godot v4.3.stable - Windows 10.0.26100 - Vulkan (Forward+) - dedicated NVIDIA GeForce RTX 4070 Laptop GPU (NVIDIA; 32.0.15.6636) - AMD Ryzen 9 7940HS w/ Radeon 780M Graphics (16 Threads)

### Issue description

I can't quite explain this one, but I happened to run into this when reporting #102551.

In short, there's something strange going on with GDScript when upgrading this particular MRP from 4.3 to 4.4, where you'll be met with parser errors after upgrading, that then manifest themselves at runtime as well.

It seems that it has something to do with the stuff that's cached in the `.godot` folder, as deleting it resolves the errors, regardless if you delete it before or after upgrading. As such, I've included a more comprehensive set of reproduction steps to create the project from scratch, as opposed to providing an MRP.

I tried reducing down the script to something even more minimal, but for whatever reason this particular script seems to force the problem.

### Steps to reproduce

- Create a new project using 4.3-stable
- Add a 2D main scene, call it `main.tscn`
- Add a new GDScript script through the FileSystem dock, call it `my_resource.gd`
- Populate `my_resource.gd` with the following contents:

```gdscript
extends Resource
class_name MyResource
```

- Attach a new script to the root of `main.tscn`, call it `main.gd`
- Populate `main.gd` with the following contents:

```gdscript
extends Node2D

@export var my_resource: MyResource

func _ready() -> void:
	bytes_to_var_with_objects(var_to_bytes_with_objects(my_resource)) as MyResource
```

- Run the main scene
- Note that it runs without errors
- Close the project
- Open the project in Godot 4.4-beta3
- Go through the upgrade process
- Note errors in Output pane saying:

```
ERROR: res://main.gd:3 - Parse Error: Could not find type "MyResource" in the current scope.
ERROR: res://main.gd:3 - Parse Error: Cannot use simple "@export" annotation because the type of the initialized value can't be inferred.
ERROR: res://main.gd:6 - Parse Error: Could not find type "MyResource" in the current scope.
ERROR: modules/gdscript/gdscript.cpp:3022 - Failed to load script "res://main.gd" with error "Parse error".
```

- Reload the project
- Note the lack of errors
- Run the main scene
- Note debugger break with error saying `Parser Error: Could not find type "MyResource" in the current scope.`
- Close the project
- Delete the `.godot` folder
- Open the project again
- Run the main scene
- Note how it runs fine

### Minimal reproduction project (MRP)

N/A
Bad `global_script_class_cache.cfg` in project version upgrade
### Tested versions

v4.4.beta3.official [06acfccf8] - broken
v4.4.beta2.official [a013481b0] - worked fine

### System information

Godot v4.4.beta3 - Windows 10 (build 19045) - Multi-window, 1 monitor - Vulkan (Mobile) - dedicated AMD Radeon RX 6600 (Advanced Micro Devices, Inc.; 32.0.11027.2001) - AMD Ryzen 5 3600 6-Core Processor (12 threads)

### Issue description

On upgrading my project to 4.4-beta3, I get the error "Parser Error: 'Util' not defined in the current scope" in one of my scripts. This did not occur in 4.4-beta2 or earlier.

The error occurs in my `ConfigManager` autoload, which is near the start of my load order. The class `Util`, is a script which simply has a `class_name Util`, and a number of `static func`s, for general use.

My only assumption is that some sort of complex load order issue is causing it to fail to find `Util` at the time of `ConfigManager`. Attempting to reproduce this for an MRP has so far eluded me, so it seems unlikely to be trivial. `Util` does have some functions which reference other classes, ex. at least one of them checks for `is BaseScene`, referencing my `BaseScene` class; which itself references other autoloads that are after the ConfigManager? But attempting a similar setup in a fresh project didn't reproduce, so I'm really not sure exactly what's causing this.

### Steps to reproduce

Unknown; but something definitely regressed here. Will edit with steps if I manage to reproduce cleanly.

### Minimal reproduction project (MRP)

Will edit with MRP if I manage to reproduce cleanly.


Discussion / hints:

Appears not to occur in a fresh project, only a project upgraded from an older build. Was working on narrowing it down further but my power went out.
Deleting the `.godot` folder fixed the issue; so it's definitely some sort of bad cached value somewhere.

EDIT: Specifically `global_script_class_cache.cfg`.
Possibly related to:
* https://github.com/godotengine/godot/issues/102557

Fix patch:
diff --git a/editor/editor_data.cpp b/editor/editor_data.cpp
index aec082791ef5..d06472e3ce17 100644
--- a/editor/editor_data.cpp
+++ b/editor/editor_data.cpp
@@ -1046,24 +1046,23 @@ void EditorData::script_class_set_name(const String &p_path, const StringName &p
 	_script_class_file_to_path[p_path] = p_class;
 }
 
-void EditorData::script_class_save_icon_paths() {
-	Array script_classes = ProjectSettings::get_singleton()->get_global_class_list();
-
-	Dictionary d;
-	for (const KeyValue<StringName, String> &E : _script_class_icon_paths) {
-		if (ScriptServer::is_global_class(E.key)) {
-			d[E.key] = E.value;
-		}
-	}
-
-	for (int i = 0; i < script_classes.size(); i++) {
-		Dictionary d2 = script_classes[i];
-		if (!d2.has("class")) {
-			continue;
-		}
-		d2["icon"] = d.get(d2["class"], "");
-	}
-	ProjectSettings::get_singleton()->store_global_class_list(script_classes);
+void EditorData::script_class_save_global_classes() {
+	List<StringName> global_classes;
+	ScriptServer::get_global_class_list(&global_classes);
+	Array array_classes;
+	for (const StringName &class_name : global_classes) {
+		Dictionary d;
+		String *icon = _script_class_icon_paths.getptr(class_name);
+		d["class"] = class_name;
+		d["language"] = ScriptServer::get_global_class_language(class_name);
+		d["path"] = ScriptServer::get_global_class_path(class_name);
+		d["base"] = ScriptServer::get_global_class_base(class_name);
+		d["icon"] = icon ? *icon : String();
+		d["is_abstract"] = ScriptServer::is_global_class_abstract(class_name);
+		d["is_tool"] = ScriptServer::is_global_class_tool(class_name);
+		array_classes.push_back(d);
+	}
+	ProjectSettings::get_singleton()->store_global_class_list(array_classes);
 }
 
 void EditorData::script_class_load_icon_paths() {
diff --git a/editor/editor_data.h b/editor/editor_data.h
index 943c661b6b2e..bc5a8daed814 100644
--- a/editor/editor_data.h
+++ b/editor/editor_data.h
@@ -251,7 +251,7 @@ class EditorData {
 	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();
+	void script_class_save_global_classes();
 	void script_class_load_icon_paths();
 
 	Ref<Texture2D> extension_class_get_icon(const String &p_class) const;
diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp
index d5c39bd4ef24..e78f77b5bf19 100644
--- a/editor/editor_file_system.cpp
+++ b/editor/editor_file_system.cpp
@@ -319,7 +319,12 @@ void EditorFileSystem::_first_scan_filesystem() {
 	_first_scan_process_scripts(first_scan_root_dir, gdextension_extensions, existing_class_names, extensions);
 
 	// Removing invalid global class to prevent having invalid paths in ScriptServer.
-	_remove_invalid_global_class_names(existing_class_names);
+	bool save_scripts = _remove_invalid_global_class_names(existing_class_names);
+
+	// If a global class is found or removed, we sync global_script_class_cache.cfg with the ScriptServer
+	if (!existing_class_names.is_empty() || save_scripts) {
+		EditorNode::get_editor_data().script_class_save_global_classes();
+	}
 
 	// Processing extensions to add new extensions or remove invalid ones.
 	// Important to do it in the first scan so custom types, new class names, custom importers, etc...
@@ -1618,7 +1623,7 @@ void EditorFileSystem::_thread_func_sources(void *_userdata) {
 	efs->scanning_changes_done.set();
 }
 
-void EditorFileSystem::_remove_invalid_global_class_names(const HashSet<String> &p_existing_class_names) {
+bool EditorFileSystem::_remove_invalid_global_class_names(const HashSet<String> &p_existing_class_names) {
 	List<StringName> global_classes;
 	bool must_save = false;
 	ScriptServer::get_global_class_list(&global_classes);
@@ -1628,9 +1633,7 @@ void EditorFileSystem::_remove_invalid_global_class_names(const HashSet<String>
 			must_save = true;
 		}
 	}
-	if (must_save) {
-		ScriptServer::save_global_classes();
-	}
+	return must_save;
 }
 
 String EditorFileSystem::_get_file_by_class_name(EditorFileSystemDirectory *p_dir, const String &p_class_name, EditorFileSystemDirectory::FileInfo *&r_file_info) {
@@ -2043,6 +2046,7 @@ EditorFileSystem::ScriptClassInfo EditorFileSystem::_get_global_script_class(con
 	for (int i = 0; i < ScriptServer::get_language_count(); i++) {
 		if (ScriptServer::get_language(i)->handles_global_class_type(p_type)) {
 			info.name = ScriptServer::get_language(i)->get_global_class_name(p_path, &info.extends, &info.icon_path, &info.is_abstract, &info.is_tool);
+			break;
 		}
 	}
 	return info;
@@ -2130,8 +2134,7 @@ void EditorFileSystem::_update_script_classes() {
 		update_script_paths.clear();
 	}
 
-	ScriptServer::save_global_classes();
-	EditorNode::get_editor_data().script_class_save_icon_paths();
+	EditorNode::get_editor_data().script_class_save_global_classes();
 
 	emit_signal("script_classes_updated");
 
diff --git a/editor/editor_file_system.h b/editor/editor_file_system.h
index 82ae983d413c..2e483a542575 100644
--- a/editor/editor_file_system.h
+++ b/editor/editor_file_system.h
@@ -374,7 +374,7 @@ class EditorFileSystem : public Node {
 
 	void _update_file_icon_path(EditorFileSystemDirectory::FileInfo *file_info);
 	void _update_files_icon_path(EditorFileSystemDirectory *edp = nullptr);
-	void _remove_invalid_global_class_names(const HashSet<String> &p_existing_class_names);
+	bool _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 ScriptClassInfoUpdate &p_script_update);
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index a733e1ce4e1d..84a4b42f5363 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -649,17 +649,11 @@ void EditorNode::_notification(int p_what) {
 
 				OS::get_singleton()->benchmark_begin_measure("Editor", "First Scan");
 
-				if (run_surface_upgrade_tool) {
-					run_surface_upgrade_tool = false;
-					SurfaceUpgradeTool::get_singleton()->connect("upgrade_finished", callable_mp(EditorFileSystem::get_singleton(), &EditorFileSystem::scan), CONNECT_ONE_SHOT);
-					SurfaceUpgradeTool::get_singleton()->finish_upgrade();
-				} else if (run_uid_upgrade_tool) {
-					run_uid_upgrade_tool = false;
-					UIDUpgradeTool::get_singleton()->connect("upgrade_finished", callable_mp(EditorFileSystem::get_singleton(), &EditorFileSystem::scan), CONNECT_ONE_SHOT);
-					UIDUpgradeTool::get_singleton()->finish_upgrade();
-				} else {
-					EditorFileSystem::get_singleton()->scan();
+				if (run_surface_upgrade_tool || run_uid_upgrade_tool) {
+					EditorFileSystem::get_singleton()->connect("filesystem_changed", callable_mp(this, &EditorNode::_execute_upgrades), CONNECT_ONE_SHOT);
 				}
+
+				EditorFileSystem::get_singleton()->scan();
 			}
 		} break;
 
@@ -901,6 +895,20 @@ void EditorNode::_update_update_spinner() {
 	OS::get_singleton()->set_low_processor_usage_mode(!update_continuously);
 }
 
+void EditorNode::_execute_upgrades() {
+	if (run_surface_upgrade_tool) {
+		run_surface_upgrade_tool = false;
+		// Execute another scan to reimport the modified files.
+		SurfaceUpgradeTool::get_singleton()->connect("upgrade_finished", callable_mp(EditorFileSystem::get_singleton(), &EditorFileSystem::scan), CONNECT_ONE_SHOT);
+		SurfaceUpgradeTool::get_singleton()->finish_upgrade();
+	} else if (run_uid_upgrade_tool) {
+		run_uid_upgrade_tool = false;
+		// Execute another scan to reimport the modified files.
+		UIDUpgradeTool::get_singleton()->connect("upgrade_finished", callable_mp(EditorFileSystem::get_singleton(), &EditorFileSystem::scan), CONNECT_ONE_SHOT);
+		UIDUpgradeTool::get_singleton()->finish_upgrade();
+	}
+}
+
 void EditorNode::init_plugins() {
 	_initializing_plugins = true;
 	Vector<String> addons;
diff --git a/editor/editor_node.h b/editor/editor_node.h
index 8e77a34e0121..3ace2e868c5b 100644
--- a/editor/editor_node.h
+++ b/editor/editor_node.h
@@ -675,6 +675,8 @@ class EditorNode : public Node {
 	void _progress_dialog_visibility_changed();
 	void _load_error_dialog_visibility_changed();
 
+	void _execute_upgrades();
+
 protected:
 	friend class FileSystemDock;
 
diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp
index 4d14bd7e3d13..4fafdefbb598 100644
--- a/editor/filesystem_dock.cpp
+++ b/editor/filesystem_dock.cpp
@@ -1573,8 +1573,7 @@ void FileSystemDock::_update_resource_paths_after_move(const HashMap<String, Str
 		}
 	}
 
-	ScriptServer::save_global_classes();
-	EditorNode::get_editor_data().script_class_save_icon_paths();
+	EditorNode::get_editor_data().script_class_save_global_classes();
 	EditorFileSystem::get_singleton()->emit_signal(SNAME("script_classes_updated"));
 }
 
@@ -1708,8 +1707,7 @@ void FileSystemDock::_resource_removed(const Ref<Resource> &p_resource) {
 	const Ref<Script> &scr = p_resource;
 	if (scr.is_valid()) {
 		ScriptServer::remove_global_class_by_path(scr->get_path());
-		ScriptServer::save_global_classes();
-		EditorNode::get_editor_data().script_class_save_icon_paths();
+		EditorNode::get_editor_data().script_class_save_global_classes();
 		EditorFileSystem::get_singleton()->emit_signal(SNAME("script_classes_updated"));
 	}
 	emit_signal(SNAME("resource_removed"), p_resource);


Test patch: