Browse Source

Merge pull request #57520 from jordigcs/gd-rename-map

Add hint for identifiers renamed from 3.x to 4.0
Rémi Verschelde 2 years ago
parent
commit
361f3f1721

+ 3 - 0
doc/classes/ProjectSettings.xml

@@ -426,6 +426,9 @@
 		<member name="debug/gdscript/warnings/redundant_await" type="int" setter="" getter="" default="1">
 		<member name="debug/gdscript/warnings/redundant_await" type="int" setter="" getter="" default="1">
 			When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a function that is not a coroutine is called with await.
 			When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a function that is not a coroutine is called with await.
 		</member>
 		</member>
+		<member name="debug/gdscript/warnings/renamed_in_godot_4_hint" type="bool" setter="" getter="" default="1">
+			When enabled, using a property, enum, or function that was renamed since Godot 3 will produce a hint if an error occurs.
+		</member>
 		<member name="debug/gdscript/warnings/return_value_discarded" type="int" setter="" getter="" default="0">
 		<member name="debug/gdscript/warnings/return_value_discarded" type="int" setter="" getter="" default="0">
 			When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when calling a function without using its return value (by assigning it to a variable or using it as a function argument). Such return values are sometimes used to denote possible errors using the [enum Error] enum.
 			When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when calling a function without using its return value (by assigning it to a variable or using it as a function argument). Such return values are sometimes used to denote possible errors using the [enum Error] enum.
 		</member>
 		</member>

+ 17 - 15
editor/project_converter_3_to_4.cpp

@@ -32,10 +32,11 @@
 
 
 #include "modules/modules_enabled.gen.h"
 #include "modules/modules_enabled.gen.h"
 
 
-const int ERROR_CODE = 77;
-
+#ifndef DISABLE_DEPRECATED
 #ifdef MODULE_REGEX_ENABLED
 #ifdef MODULE_REGEX_ENABLED
 
 
+const int ERROR_CODE = 77;
+
 #include "modules/regex/regex.h"
 #include "modules/regex/regex.h"
 
 
 #include "core/io/dir_access.h"
 #include "core/io/dir_access.h"
@@ -44,7 +45,7 @@ const int ERROR_CODE = 77;
 #include "core/templates/list.h"
 #include "core/templates/list.h"
 #include "core/templates/local_vector.h"
 #include "core/templates/local_vector.h"
 
 
-static const char *enum_renames[][2] = {
+const char *ProjectConverter3To4::enum_renames[][2] = {
 	//// constants
 	//// constants
 	{ "TYPE_COLOR_ARRAY", "TYPE_PACKED_COLOR_ARRAY" },
 	{ "TYPE_COLOR_ARRAY", "TYPE_PACKED_COLOR_ARRAY" },
 	{ "TYPE_FLOAT64_ARRAY", "TYPE_PACKED_FLOAT64_ARRAY" },
 	{ "TYPE_FLOAT64_ARRAY", "TYPE_PACKED_FLOAT64_ARRAY" },
@@ -164,7 +165,7 @@ static const char *enum_renames[][2] = {
 	{ nullptr, nullptr },
 	{ nullptr, nullptr },
 };
 };
 
 
-static const char *gdscript_function_renames[][2] = {
+const char *ProjectConverter3To4::gdscript_function_renames[][2] = {
 	// { "_set_name", "get_tracker_name"}, // XRPositionalTracker - CameraFeed use this
 	// { "_set_name", "get_tracker_name"}, // XRPositionalTracker - CameraFeed use this
 	// { "_unhandled_input", "_unhandled_key_input"}, // BaseButton, ViewportContainer broke Node, FileDialog,SubViewportContainer
 	// { "_unhandled_input", "_unhandled_key_input"}, // BaseButton, ViewportContainer broke Node, FileDialog,SubViewportContainer
 	// { "create_gizmo", "_create_gizmo"}, // EditorNode3DGizmoPlugin - may be used
 	// { "create_gizmo", "_create_gizmo"}, // EditorNode3DGizmoPlugin - may be used
@@ -619,7 +620,7 @@ static const char *gdscript_function_renames[][2] = {
 };
 };
 
 
 // gdscript_function_renames clone with CamelCase
 // gdscript_function_renames clone with CamelCase
-static const char *csharp_function_renames[][2] = {
+const char *ProjectConverter3To4::csharp_function_renames[][2] = {
 	// { "_SetName", "GetTrackerName"}, // XRPositionalTracker - CameraFeed use this
 	// { "_SetName", "GetTrackerName"}, // XRPositionalTracker - CameraFeed use this
 	// { "_UnhandledInput", "_UnhandledKeyInput"}, // BaseButton, ViewportContainer broke Node, FileDialog,SubViewportContainer
 	// { "_UnhandledInput", "_UnhandledKeyInput"}, // BaseButton, ViewportContainer broke Node, FileDialog,SubViewportContainer
 	// { "CreateGizmo", "_CreateGizmo"}, // EditorNode3DGizmoPlugin - may be used
 	// { "CreateGizmo", "_CreateGizmo"}, // EditorNode3DGizmoPlugin - may be used
@@ -1056,7 +1057,7 @@ static const char *csharp_function_renames[][2] = {
 };
 };
 
 
 // Some needs to be disabled, because users can use this names as variables
 // Some needs to be disabled, because users can use this names as variables
-static const char *gdscript_properties_renames[][2] = {
+const char *ProjectConverter3To4::gdscript_properties_renames[][2] = {
 	//	// { "d", "distance" }, //WorldMarginShape2D - TODO, looks that polish letters ą ę are treaten as space, not as letter, so `będą` are renamed to `będistanceą`
 	//	// { "d", "distance" }, //WorldMarginShape2D - TODO, looks that polish letters ą ę are treaten as space, not as letter, so `będą` are renamed to `będistanceą`
 	//	// {"alt","alt_pressed"}, // This may broke a lot of comments and user variables
 	//	// {"alt","alt_pressed"}, // This may broke a lot of comments and user variables
 	//	// {"command","command_pressed"},// This may broke a lot of comments and user variables
 	//	// {"command","command_pressed"},// This may broke a lot of comments and user variables
@@ -1173,7 +1174,7 @@ static const char *gdscript_properties_renames[][2] = {
 };
 };
 
 
 // Some needs to be disabled, because users can use this names as variables
 // Some needs to be disabled, because users can use this names as variables
-static const char *csharp_properties_renames[][2] = {
+const char *ProjectConverter3To4::csharp_properties_renames[][2] = {
 	//	// { "D", "Distance" }, //WorldMarginShape2D - TODO, looks that polish letters ą ę are treaten as space, not as letter, so `będą` are renamed to `będistanceą`
 	//	// { "D", "Distance" }, //WorldMarginShape2D - TODO, looks that polish letters ą ę are treaten as space, not as letter, so `będą` are renamed to `będistanceą`
 	//	// {"Alt","AltPressed"}, // This may broke a lot of comments and user variables
 	//	// {"Alt","AltPressed"}, // This may broke a lot of comments and user variables
 	//	// {"Command","CommandPressed"},// This may broke a lot of comments and user variables
 	//	// {"Command","CommandPressed"},// This may broke a lot of comments and user variables
@@ -1278,7 +1279,7 @@ static const char *csharp_properties_renames[][2] = {
 	{ nullptr, nullptr },
 	{ nullptr, nullptr },
 };
 };
 
 
-static const char *gdscript_signals_renames[][2] = {
+const char *ProjectConverter3To4::gdscript_signals_renames[][2] = {
 	//  {"instantiate","instance"}, // FileSystemDock
 	//  {"instantiate","instance"}, // FileSystemDock
 	// { "hide", "hidden" }, // CanvasItem - function with same name exists
 	// { "hide", "hidden" }, // CanvasItem - function with same name exists
 	// { "tween_all_completed","loop_finished"}, // Tween - TODO, not sure
 	// { "tween_all_completed","loop_finished"}, // Tween - TODO, not sure
@@ -1303,7 +1304,7 @@ static const char *gdscript_signals_renames[][2] = {
 	{ nullptr, nullptr },
 	{ nullptr, nullptr },
 };
 };
 
 
-static const char *csharp_signals_renames[][2] = {
+const char *ProjectConverter3To4::csharp_signals_renames[][2] = {
 	//  {"Instantiate","Instance"}, // FileSystemDock
 	//  {"Instantiate","Instance"}, // FileSystemDock
 	// { "Hide", "Hidden" }, // CanvasItem - function with same name exists
 	// { "Hide", "Hidden" }, // CanvasItem - function with same name exists
 	// { "TweenAllCompleted","LoopFinished"}, // Tween - TODO, not sure
 	// { "TweenAllCompleted","LoopFinished"}, // Tween - TODO, not sure
@@ -1327,7 +1328,7 @@ static const char *csharp_signals_renames[][2] = {
 
 
 };
 };
 
 
-static const char *project_settings_renames[][2] = {
+const char *ProjectConverter3To4::project_settings_renames[][2] = {
 	{ "audio/channel_disable_threshold_db", "audio/buses/channel_disable_threshold_db" },
 	{ "audio/channel_disable_threshold_db", "audio/buses/channel_disable_threshold_db" },
 	{ "audio/channel_disable_time", "audio/buses/channel_disable_time" },
 	{ "audio/channel_disable_time", "audio/buses/channel_disable_time" },
 	{ "audio/default_bus_layout", "audio/buses/default_bus_layout" },
 	{ "audio/default_bus_layout", "audio/buses/default_bus_layout" },
@@ -1372,7 +1373,7 @@ static const char *project_settings_renames[][2] = {
 	{ nullptr, nullptr },
 	{ nullptr, nullptr },
 };
 };
 
 
-static const char *input_map_renames[][2] = {
+const char *ProjectConverter3To4::input_map_renames[][2] = {
 	{ ",\"alt\":", ",\"alt_pressed\":" },
 	{ ",\"alt\":", ",\"alt_pressed\":" },
 	{ ",\"shift\":", ",\"shift_pressed\":" },
 	{ ",\"shift\":", ",\"shift_pressed\":" },
 	{ ",\"control\":", ",\"ctrl_pressed\":" },
 	{ ",\"control\":", ",\"ctrl_pressed\":" },
@@ -1384,7 +1385,7 @@ static const char *input_map_renames[][2] = {
 	{ nullptr, nullptr },
 	{ nullptr, nullptr },
 };
 };
 
 
-static const char *builtin_types_renames[][2] = {
+const char *ProjectConverter3To4::builtin_types_renames[][2] = {
 	{ "PoolByteArray", "PackedByteArray" },
 	{ "PoolByteArray", "PackedByteArray" },
 	{ "PoolColorArray", "PackedColorArray" },
 	{ "PoolColorArray", "PackedColorArray" },
 	{ "PoolIntArray", "PackedInt32Array" },
 	{ "PoolIntArray", "PackedInt32Array" },
@@ -1398,7 +1399,7 @@ static const char *builtin_types_renames[][2] = {
 	{ nullptr, nullptr },
 	{ nullptr, nullptr },
 };
 };
 
 
-static const char *shaders_renames[][2] = {
+const char *ProjectConverter3To4::shaders_renames[][2] = {
 	{ "ALPHA_SCISSOR", "ALPHA_SCISSOR_THRESHOLD" },
 	{ "ALPHA_SCISSOR", "ALPHA_SCISSOR_THRESHOLD" },
 	{ "CAMERA_MATRIX", "INV_VIEW_MATRIX" },
 	{ "CAMERA_MATRIX", "INV_VIEW_MATRIX" },
 	{ "INV_CAMERA_MATRIX", "VIEW_MATRIX" },
 	{ "INV_CAMERA_MATRIX", "VIEW_MATRIX" },
@@ -1416,7 +1417,7 @@ static const char *shaders_renames[][2] = {
 	{ nullptr, nullptr },
 	{ nullptr, nullptr },
 };
 };
 
 
-static const char *class_renames[][2] = {
+const char *ProjectConverter3To4::class_renames[][2] = {
 	// { "BulletPhysicsDirectBodyState", "BulletPhysicsDirectBodyState3D" }, // Class is not visible in ClassDB
 	// { "BulletPhysicsDirectBodyState", "BulletPhysicsDirectBodyState3D" }, // Class is not visible in ClassDB
 	// { "BulletPhysicsServer", "BulletPhysicsServer3D" }, // Class is not visible in ClassDB
 	// { "BulletPhysicsServer", "BulletPhysicsServer3D" }, // Class is not visible in ClassDB
 	// { "GDScriptFunctionState", "Node3D" }, // TODO - not sure to which should be changed
 	// { "GDScriptFunctionState", "Node3D" }, // TODO - not sure to which should be changed
@@ -1641,7 +1642,7 @@ static const char *class_renames[][2] = {
 	{ nullptr, nullptr },
 	{ nullptr, nullptr },
 };
 };
 
 
-static const char *color_renames[][2] = {
+const char *ProjectConverter3To4::ProjectConverter3To4::color_renames[][2] = {
 	{ "aliceblue", "ALICE_BLUE" },
 	{ "aliceblue", "ALICE_BLUE" },
 	{ "antiquewhite", "ANTIQUE_WHITE" },
 	{ "antiquewhite", "ANTIQUE_WHITE" },
 	{ "aqua", "AQUA" },
 	{ "aqua", "AQUA" },
@@ -4350,3 +4351,4 @@ int ProjectConverter3To4::validate_conversion() {
 }
 }
 
 
 #endif // MODULE_REGEX_ENABLED
 #endif // MODULE_REGEX_ENABLED
+#endif // DISABLE_DEPRECATED

+ 16 - 0
editor/project_converter_3_to_4.h

@@ -29,6 +29,7 @@
 /**************************************************************************/
 /**************************************************************************/
 
 
 #ifndef PROJECT_CONVERTER_3_TO_4_H
 #ifndef PROJECT_CONVERTER_3_TO_4_H
+#ifndef DISABLE_DEPRECATED
 #define PROJECT_CONVERTER_3_TO_4_H
 #define PROJECT_CONVERTER_3_TO_4_H
 
 
 #include "core/io/file_access.h"
 #include "core/io/file_access.h"
@@ -41,6 +42,19 @@ class RegEx;
 class ProjectConverter3To4 {
 class ProjectConverter3To4 {
 public:
 public:
 	class RegExContainer;
 	class RegExContainer;
+	static const char *enum_renames[][2];
+	static const char *gdscript_function_renames[][2];
+	static const char *csharp_function_renames[][2];
+	static const char *gdscript_properties_renames[][2];
+	static const char *csharp_properties_renames[][2];
+	static const char *gdscript_signals_renames[][2];
+	static const char *csharp_signals_renames[][2];
+	static const char *project_settings_renames[][2];
+	static const char *input_map_renames[][2];
+	static const char *builtin_types_renames[][2];
+	static const char *shaders_renames[][2];
+	static const char *class_renames[][2];
+	static const char *color_renames[][2];
 
 
 private:
 private:
 	uint64_t maximum_file_size;
 	uint64_t maximum_file_size;
@@ -97,4 +111,6 @@ public:
 	int convert();
 	int convert();
 };
 };
 
 
+#endif // DISABLE_DEPRECATED
+
 #endif // PROJECT_CONVERTER_3_TO_4_H
 #endif // PROJECT_CONVERTER_3_TO_4_H

+ 117 - 1
modules/gdscript/gdscript_analyzer.cpp

@@ -2464,6 +2464,62 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o
 	p_binary_op->set_datatype(result);
 	p_binary_op->set_datatype(result);
 }
 }
 
 
+#ifdef TOOLS_ENABLED
+#ifndef DISABLE_DEPRECATED
+const char *GDScriptAnalyzer::get_rename_from_map(const char *map[][2], String key) {
+	for (int index = 0; map[index][0]; index++) {
+		if (map[index][0] == key) {
+			return map[index][1];
+		}
+	}
+	return nullptr;
+}
+
+// Checks if an identifier/function name has been renamed in Godot 4, uses ProjectConverter3To4 for rename map.
+// Returns the new name if found, nullptr otherwise.
+const char *GDScriptAnalyzer::check_for_renamed_identifier(String identifier, GDScriptParser::Node::Type type) {
+	switch (type) {
+		case GDScriptParser::Node::IDENTIFIER: {
+			// Check properties
+			const char *result = get_rename_from_map(ProjectConverter3To4::gdscript_properties_renames, identifier);
+			if (result) {
+				return result;
+			}
+			// Check enum values
+			result = get_rename_from_map(ProjectConverter3To4::enum_renames, identifier);
+			if (result) {
+				return result;
+			}
+			// Check color constants
+			result = get_rename_from_map(ProjectConverter3To4::color_renames, identifier);
+			if (result) {
+				return result;
+			}
+			// Check type names
+			result = get_rename_from_map(ProjectConverter3To4::class_renames, identifier);
+			if (result) {
+				return result;
+			}
+			return get_rename_from_map(ProjectConverter3To4::builtin_types_renames, identifier);
+		}
+		case GDScriptParser::Node::CALL: {
+			const char *result = get_rename_from_map(ProjectConverter3To4::gdscript_function_renames, identifier);
+			if (result) {
+				return result;
+			}
+			// Built-in Types are mistaken for function calls when the built-in type is not found.
+			// Check built-in types if function rename not found
+			return get_rename_from_map(ProjectConverter3To4::builtin_types_renames, identifier);
+		}
+		// Signal references don't get parsed through the GDScriptAnalyzer. No support for signal rename hints.
+		default:
+			// No rename found, return null
+			return nullptr;
+	}
+}
+#endif // DISABLE_DEPRECATED
+#endif // TOOLS_ENABLED
+
 void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_await, bool p_is_root) {
 void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_await, bool p_is_root) {
 	bool all_is_constant = true;
 	bool all_is_constant = true;
 	HashMap<int, GDScriptParser::ArrayNode *> arrays; // For array literal to potentially type when passing.
 	HashMap<int, GDScriptParser::ArrayNode *> arrays; // For array literal to potentially type when passing.
@@ -2881,7 +2937,22 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
 		}
 		}
 		if (!found && (is_self || (base_type.is_hard_type() && base_type.kind == GDScriptParser::DataType::BUILTIN))) {
 		if (!found && (is_self || (base_type.is_hard_type() && base_type.kind == GDScriptParser::DataType::BUILTIN))) {
 			String base_name = is_self && !p_call->is_super ? "self" : base_type.to_string();
 			String base_name = is_self && !p_call->is_super ? "self" : base_type.to_string();
+#ifdef TOOLS_ENABLED
+#ifndef DISABLE_DEPRECATED
+			String rename_hint = String();
+			if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::Code::RENAMED_IN_GD4_HINT)).booleanize()) {
+				const char *renamed_function_name = check_for_renamed_identifier(p_call->function_name, p_call->type);
+				if (renamed_function_name) {
+					rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", String(renamed_function_name) + "()");
+				}
+			}
+			push_error(vformat(R"*(Function "%s()" not found in base %s.%s)*", p_call->function_name, base_name, rename_hint), p_call->is_super ? p_call : p_call->callee);
+#else // !DISABLE_DEPRECATED
 			push_error(vformat(R"*(Function "%s()" not found in base %s.)*", p_call->function_name, base_name), p_call->is_super ? p_call : p_call->callee);
 			push_error(vformat(R"*(Function "%s()" not found in base %s.)*", p_call->function_name, base_name), p_call->is_super ? p_call : p_call->callee);
+#endif // DISABLE_DEPRECATED
+#else
+			push_error(vformat(R"*(Function "%s()" not found in base %s.)*", p_call->function_name, base_name), p_call->is_super ? p_call : p_call->callee);
+#endif
 		} else if (!found && (!p_call->is_super && base_type.is_hard_type() && base_type.kind == GDScriptParser::DataType::NATIVE && base_type.is_meta_type)) {
 		} else if (!found && (!p_call->is_super && base_type.is_hard_type() && base_type.kind == GDScriptParser::DataType::NATIVE && base_type.is_meta_type)) {
 			push_error(vformat(R"*(Static function "%s()" not found in base "%s".)*", p_call->function_name, base_type.native_type), p_call);
 			push_error(vformat(R"*(Static function "%s()" not found in base "%s".)*", p_call->function_name, base_type.native_type), p_call);
 		}
 		}
@@ -3071,7 +3142,22 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
 				p_identifier->reduced_value = result;
 				p_identifier->reduced_value = result;
 				p_identifier->set_datatype(type_from_variant(result, p_identifier));
 				p_identifier->set_datatype(type_from_variant(result, p_identifier));
 			} else if (base.is_hard_type()) {
 			} else if (base.is_hard_type()) {
-				push_error(vformat(R"(Cannot find constant "%s" on type "%s".)", name, base.to_string()), p_identifier);
+#ifdef TOOLS_ENABLED
+#ifndef DISABLE_DEPRECATED
+				String rename_hint = String();
+				if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::Code::RENAMED_IN_GD4_HINT)).booleanize()) {
+					const char *renamed_identifier_name = check_for_renamed_identifier(name, p_identifier->type);
+					if (renamed_identifier_name) {
+						rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", renamed_identifier_name);
+					}
+				}
+				push_error(vformat(R"(Cannot find constant "%s" on base "%s".%s)", name, base.to_string(), rename_hint), p_identifier);
+#else // !DISABLE_DEPRECATED
+				push_error(vformat(R"(Cannot find constant "%s" on base "%s".)", name, base.to_string()), p_identifier);
+#endif // DISABLE_DEPRECATED
+#else
+				push_error(vformat(R"(Cannot find constant "%s" on base "%s".)", name, base.to_string()), p_identifier);
+#endif
 			}
 			}
 		} else {
 		} else {
 			switch (base.builtin_type) {
 			switch (base.builtin_type) {
@@ -3100,7 +3186,22 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
 						}
 						}
 					}
 					}
 					if (base.is_hard_type()) {
 					if (base.is_hard_type()) {
+#ifdef TOOLS_ENABLED
+#ifndef DISABLE_DEPRECATED
+						String rename_hint = String();
+						if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::Code::RENAMED_IN_GD4_HINT)).booleanize()) {
+							const char *renamed_identifier_name = check_for_renamed_identifier(name, p_identifier->type);
+							if (renamed_identifier_name) {
+								rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", renamed_identifier_name);
+							}
+						}
+						push_error(vformat(R"(Cannot find property "%s" on base "%s".%s)", name, base.to_string(), rename_hint), p_identifier);
+#else // !DISABLE_DEPRECATED
+						push_error(vformat(R"(Cannot find property "%s" on base "%s".)", name, base.to_string()), p_identifier);
+#endif // DISABLE_DEPRECATED
+#else
 						push_error(vformat(R"(Cannot find property "%s" on base "%s".)", name, base.to_string()), p_identifier);
 						push_error(vformat(R"(Cannot find property "%s" on base "%s".)", name, base.to_string()), p_identifier);
+#endif
 					}
 					}
 				}
 				}
 			}
 			}
@@ -3439,7 +3540,22 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
 	if (GDScriptUtilityFunctions::function_exists(name)) {
 	if (GDScriptUtilityFunctions::function_exists(name)) {
 		push_error(vformat(R"(Built-in function "%s" cannot be used as an identifier.)", name), p_identifier);
 		push_error(vformat(R"(Built-in function "%s" cannot be used as an identifier.)", name), p_identifier);
 	} else {
 	} else {
+#ifdef TOOLS_ENABLED
+#ifndef DISABLE_DEPRECATED
+		String rename_hint = String();
+		if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::Code::RENAMED_IN_GD4_HINT)).booleanize()) {
+			const char *renamed_identifier_name = check_for_renamed_identifier(name, p_identifier->type);
+			if (renamed_identifier_name) {
+				rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", renamed_identifier_name);
+			}
+		}
+		push_error(vformat(R"(Identifier "%s" not declared in the current scope.%s)", name, rename_hint), p_identifier);
+#else // !DISABLE_DEPRECATED
 		push_error(vformat(R"(Identifier "%s" not declared in the current scope.)", name), p_identifier);
 		push_error(vformat(R"(Identifier "%s" not declared in the current scope.)", name), p_identifier);
+#endif // DISABLE_DEPRECATED
+#else
+		push_error(vformat(R"(Identifier "%s" not declared in the current scope.)", name), p_identifier);
+#endif
 	}
 	}
 	GDScriptParser::DataType dummy;
 	GDScriptParser::DataType dummy;
 	dummy.kind = GDScriptParser::DataType::VARIANT;
 	dummy.kind = GDScriptParser::DataType::VARIANT;

+ 11 - 0
modules/gdscript/gdscript_analyzer.h

@@ -37,6 +37,10 @@
 #include "gdscript_cache.h"
 #include "gdscript_cache.h"
 #include "gdscript_parser.h"
 #include "gdscript_parser.h"
 
 
+#ifdef TOOLS_ENABLED
+#include "editor/project_converter_3_to_4.h"
+#endif
+
 class GDScriptAnalyzer {
 class GDScriptAnalyzer {
 	GDScriptParser *parser = nullptr;
 	GDScriptParser *parser = nullptr;
 	HashMap<String, Ref<GDScriptParserRef>> depended_parsers;
 	HashMap<String, Ref<GDScriptParserRef>> depended_parsers;
@@ -133,6 +137,13 @@ class GDScriptAnalyzer {
 	bool is_shadowing(GDScriptParser::IdentifierNode *p_local, const String &p_context);
 	bool is_shadowing(GDScriptParser::IdentifierNode *p_local, const String &p_context);
 #endif
 #endif
 
 
+#ifdef TOOLS_ENABLED
+#ifndef DISABLE_DEPRECATED
+	const char *get_rename_from_map(const char *map[][2], String key);
+	const char *check_for_renamed_identifier(String identifier, GDScriptParser::Node::Type type);
+#endif // DISABLE_DEPRECATED
+#endif // TOOLS_ENABLED
+
 public:
 public:
 	Error resolve_inheritance();
 	Error resolve_inheritance();
 	Error resolve_interface();
 	Error resolve_interface();

+ 7 - 0
modules/gdscript/gdscript_warning.cpp

@@ -163,6 +163,9 @@ String GDScriptWarning::get_message() const {
 			CHECK_SYMBOLS(1);
 			CHECK_SYMBOLS(1);
 			return vformat(R"(The identifier "%s" has misleading characters and might be confused with something else.)", symbols[0]);
 			return vformat(R"(The identifier "%s" has misleading characters and might be confused with something else.)", symbols[0]);
 		}
 		}
+		case RENAMED_IN_GD4_HINT: {
+			break; // Renamed identifier hint is taken care of by the GDScriptAnalyzer. No message needed here.
+		}
 		case WARNING_MAX:
 		case WARNING_MAX:
 			break; // Can't happen, but silences warning
 			break; // Can't happen, but silences warning
 	}
 	}
@@ -184,6 +187,9 @@ int GDScriptWarning::get_default_value(Code p_code) {
 
 
 PropertyInfo GDScriptWarning::get_property_info(Code p_code) {
 PropertyInfo GDScriptWarning::get_property_info(Code p_code) {
 	// Making this a separate function in case a warning needs different PropertyInfo in the future.
 	// Making this a separate function in case a warning needs different PropertyInfo in the future.
+	if (p_code == Code::RENAMED_IN_GD4_HINT) {
+		return PropertyInfo(Variant::BOOL, get_settings_path_from_code(p_code));
+	}
 	return PropertyInfo(Variant::INT, get_settings_path_from_code(p_code), PROPERTY_HINT_ENUM, "Ignore,Warn,Error");
 	return PropertyInfo(Variant::INT, get_settings_path_from_code(p_code), PROPERTY_HINT_ENUM, "Ignore,Warn,Error");
 }
 }
 
 
@@ -229,6 +235,7 @@ String GDScriptWarning::get_name_from_code(Code p_code) {
 		"INT_AS_ENUM_WITHOUT_MATCH",
 		"INT_AS_ENUM_WITHOUT_MATCH",
 		"STATIC_CALLED_ON_INSTANCE",
 		"STATIC_CALLED_ON_INSTANCE",
 		"CONFUSABLE_IDENTIFIER",
 		"CONFUSABLE_IDENTIFIER",
+		"RENAMED_IN_GODOT_4_HINT"
 	};
 	};
 
 
 	static_assert((sizeof(names) / sizeof(*names)) == WARNING_MAX, "Amount of warning types don't match the amount of warning names.");
 	static_assert((sizeof(names) / sizeof(*names)) == WARNING_MAX, "Amount of warning types don't match the amount of warning names.");

+ 1 - 0
modules/gdscript/gdscript_warning.h

@@ -80,6 +80,7 @@ public:
 		INT_AS_ENUM_WITHOUT_MATCH, // An integer value was used as an enum value without matching enum member.
 		INT_AS_ENUM_WITHOUT_MATCH, // An integer value was used as an enum value without matching enum member.
 		STATIC_CALLED_ON_INSTANCE, // A static method was called on an instance of a class instead of on the class itself.
 		STATIC_CALLED_ON_INSTANCE, // A static method was called on an instance of a class instead of on the class itself.
 		CONFUSABLE_IDENTIFIER, // The identifier contains misleading characters that can be confused. E.g. "usеr" (has Cyrillic "е" instead of Latin "e").
 		CONFUSABLE_IDENTIFIER, // The identifier contains misleading characters that can be confused. E.g. "usеr" (has Cyrillic "е" instead of Latin "e").
+		RENAMED_IN_GD4_HINT, // A variable or function that could not be found has been renamed in Godot 4
 		WARNING_MAX,
 		WARNING_MAX,
 	};
 	};