summaryrefslogtreecommitdiff
path: root/modules/mono
diff options
context:
space:
mode:
Diffstat (limited to 'modules/mono')
-rw-r--r--modules/mono/class_db_api_json.cpp2
-rw-r--r--modules/mono/csharp_script.cpp6
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs4
-rw-r--r--modules/mono/editor/bindings_generator.cpp77
-rw-r--r--modules/mono/editor/bindings_generator.h14
-rw-r--r--modules/mono/mono_gd/gd_mono.cpp60
-rw-r--r--modules/mono/mono_gd/gd_mono.h4
-rw-r--r--modules/mono/mono_gd/gd_mono_log.cpp2
-rw-r--r--modules/mono/utils/string_utils.cpp2
9 files changed, 111 insertions, 60 deletions
diff --git a/modules/mono/class_db_api_json.cpp b/modules/mono/class_db_api_json.cpp
index 7580911a0a..bbc779601e 100644
--- a/modules/mono/class_db_api_json.cpp
+++ b/modules/mono/class_db_api_json.cpp
@@ -236,7 +236,7 @@ void class_db_api_to_json(const String &p_output_file, ClassDB::APIType p_api) {
}
FileAccessRef f = FileAccess::open(p_output_file, FileAccess::WRITE);
- ERR_FAIL_COND(!f);
+ ERR_FAIL_COND_MSG(!f, "Cannot open file '" + p_output_file + "'.");
f->store_string(JSON::print(classes_dict, /*indent: */ "\t"));
f->close();
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp
index e14e919f92..83be10dee3 100644
--- a/modules/mono/csharp_script.cpp
+++ b/modules/mono/csharp_script.cpp
@@ -1195,7 +1195,7 @@ void CSharpLanguage::release_script_gchandle(MonoObject *p_expected_obj, Ref<Mon
CSharpLanguage::CSharpLanguage() {
- ERR_FAIL_COND(singleton);
+ ERR_FAIL_COND_MSG(singleton, "C# singleton already exist.");
singleton = this;
finalizing = false;
@@ -3242,7 +3242,7 @@ RES ResourceFormatLoaderCSharpScript::load(const String &p_path, const String &p
#if defined(DEBUG_ENABLED) || defined(TOOLS_ENABLED)
Error err = script->load_source_code(p_path);
- ERR_FAIL_COND_V(err != OK, RES());
+ ERR_FAIL_COND_V_MSG(err != OK, RES(), "Cannot load C# script file '" + p_path + "'.");
#endif
script->set_path(p_original_path);
@@ -3325,7 +3325,7 @@ Error ResourceFormatSaverCSharpScript::save(const String &p_path, const RES &p_r
Error err;
FileAccess *file = FileAccess::open(p_path, FileAccess::WRITE, &err);
- ERR_FAIL_COND_V(err, err);
+ ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot save C# script file '" + p_path + "'.");
file->store_string(source);
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs
index 4c1e47ecad..eb2c2dd77c 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs
@@ -34,7 +34,7 @@ namespace GodotTools.Build
if (_msbuildToolsPath.Empty())
{
- throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMsbuildVs}'. Tried with path: {_msbuildToolsPath}");
+ throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMsbuildVs}'.");
}
}
@@ -142,7 +142,7 @@ namespace GodotTools.Build
int exitCode = Godot.OS.Execute(vsWherePath, vsWhereArgs,
blocking: true, output: (Godot.Collections.Array) outputArray);
- if (exitCode == 0)
+ if (exitCode != 0)
return string.Empty;
if (outputArray.Count == 0)
diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp
index 74710db224..28cab2ab61 100644
--- a/modules/mono/editor/bindings_generator.cpp
+++ b/modules/mono/editor/bindings_generator.cpp
@@ -30,7 +30,7 @@
#include "bindings_generator.h"
-#ifdef DEBUG_METHODS_ENABLED
+#if defined(DEBUG_METHODS_ENABLED) && defined(TOOLS_ENABLED)
#include "core/engine.h"
#include "core/global_constants.h"
@@ -863,12 +863,14 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) {
Error BindingsGenerator::generate_cs_core_project(const String &p_proj_dir, Vector<String> &r_compile_items) {
+ ERR_FAIL_COND_V(!initialized, ERR_UNCONFIGURED);
+
DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
ERR_FAIL_COND_V(!da, ERR_CANT_CREATE);
if (!DirAccess::exists(p_proj_dir)) {
Error err = da->make_dir_recursive(p_proj_dir);
- ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE);
+ ERR_FAIL_COND_V_MSG(err != OK, ERR_CANT_CREATE, "Cannot create directory '" + p_proj_dir + "'.");
}
da->change_dir(p_proj_dir);
@@ -984,6 +986,8 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_proj_dir, Vect
Error BindingsGenerator::generate_cs_editor_project(const String &p_proj_dir, Vector<String> &r_compile_items) {
+ ERR_FAIL_COND_V(!initialized, ERR_UNCONFIGURED);
+
DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
ERR_FAIL_COND_V(!da, ERR_CANT_CREATE);
@@ -1064,6 +1068,8 @@ Error BindingsGenerator::generate_cs_editor_project(const String &p_proj_dir, Ve
Error BindingsGenerator::generate_cs_api(const String &p_output_dir) {
+ ERR_FAIL_COND_V(!initialized, ERR_UNCONFIGURED);
+
String output_dir = path::abspath(path::realpath(p_output_dir));
DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
@@ -1703,6 +1709,8 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
Error BindingsGenerator::generate_glue(const String &p_output_dir) {
+ ERR_FAIL_COND_V(!initialized, ERR_UNCONFIGURED);
+
bool dir_exists = DirAccess::exists(p_output_dir);
ERR_FAIL_COND_V_MSG(!dir_exists, ERR_FILE_BAD_PATH, "The output directory does not exist.");
@@ -2151,7 +2159,7 @@ StringName BindingsGenerator::_get_float_type_name_from_meta(GodotTypeInfo::Meta
}
}
-void BindingsGenerator::_populate_object_type_interfaces() {
+bool BindingsGenerator::_populate_object_type_interfaces() {
obj_types.clear();
@@ -2229,7 +2237,7 @@ void BindingsGenerator::_populate_object_type_interfaces() {
bool valid = false;
iprop.index = ClassDB::get_property_index(type_cname, iprop.cname, &valid);
- ERR_FAIL_COND(!valid);
+ ERR_FAIL_COND_V(!valid, false);
iprop.proxy_name = escape_csharp_keyword(snake_to_pascal_case(iprop.cname));
@@ -2293,7 +2301,7 @@ void BindingsGenerator::_populate_object_type_interfaces() {
imethod.is_vararg = m && m->is_vararg();
if (!m && !imethod.is_virtual) {
- ERR_FAIL_COND_MSG(!virtual_method_list.find(method_info),
+ ERR_FAIL_COND_V_MSG(!virtual_method_list.find(method_info), false,
"Missing MethodBind for non-virtual method: '" + itype.name + "." + imethod.name + "'.");
// A virtual method without the virtual flag. This is a special case.
@@ -2310,9 +2318,9 @@ void BindingsGenerator::_populate_object_type_interfaces() {
// which could actually will return something different.
// Let's put this to notify us if that ever happens.
if (itype.cname != name_cache.type_Object || imethod.name != "free") {
- ERR_PRINTS("Notification: New unexpected virtual non-overridable method found."
- " We only expected Object.free, but found '" +
- itype.name + "." + imethod.name + "'.");
+ WARN_PRINTS("Notification: New unexpected virtual non-overridable method found."
+ " We only expected Object.free, but found '" +
+ itype.name + "." + imethod.name + "'.");
}
} else if (return_info.type == Variant::INT && return_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM) {
imethod.return_type.cname = return_info.class_name;
@@ -2324,7 +2332,7 @@ void BindingsGenerator::_populate_object_type_interfaces() {
ERR_PRINTS("Return type is reference but hint is not '" _STR(PROPERTY_HINT_RESOURCE_TYPE) "'."
" Are you returning a reference type by pointer? Method: '" + itype.name + "." + imethod.name + "'.");
/* clang-format on */
- ERR_FAIL();
+ ERR_FAIL_V(false);
}
} else if (return_info.hint == PROPERTY_HINT_RESOURCE_TYPE) {
imethod.return_type.cname = return_info.hint_string;
@@ -2345,8 +2353,10 @@ void BindingsGenerator::_populate_object_type_interfaces() {
for (int i = 0; i < argc; i++) {
PropertyInfo arginfo = method_info.arguments[i];
+ String orig_arg_name = arginfo.name;
+
ArgumentInterface iarg;
- iarg.name = arginfo.name;
+ iarg.name = orig_arg_name;
if (arginfo.type == Variant::INT && arginfo.usage & PROPERTY_USAGE_CLASS_IS_ENUM) {
iarg.type.cname = arginfo.class_name;
@@ -2370,7 +2380,9 @@ void BindingsGenerator::_populate_object_type_interfaces() {
iarg.name = escape_csharp_keyword(snake_to_camel_case(iarg.name));
if (m && m->has_default_argument(i)) {
- _default_argument_from_variant(m->get_default_argument(i), iarg);
+ bool defval_ok = _arg_default_value_from_variant(m->get_default_argument(i), iarg);
+ ERR_FAIL_COND_V_MSG(!defval_ok, false,
+ "Cannot determine default value for argument '" + orig_arg_name + "' of method '" + itype.name + "." + imethod.name + "'.");
}
imethod.add_argument(iarg);
@@ -2450,7 +2462,7 @@ void BindingsGenerator::_populate_object_type_interfaces() {
const StringName &constant_cname = E->get();
String constant_name = constant_cname.operator String();
int *value = class_info->constant_map.getptr(constant_cname);
- ERR_FAIL_NULL(value);
+ ERR_FAIL_NULL_V(value, false);
constants.erase(constant_name);
ConstantInterface iconstant(constant_name, snake_to_pascal_case(constant_name, true), *value);
@@ -2486,7 +2498,7 @@ void BindingsGenerator::_populate_object_type_interfaces() {
for (const List<String>::Element *E = constants.front(); E; E = E->next()) {
const String &constant_name = E->get();
int *value = class_info->constant_map.getptr(StringName(E->get()));
- ERR_FAIL_NULL(value);
+ ERR_FAIL_NULL_V(value, false);
ConstantInterface iconstant(constant_name, snake_to_pascal_case(constant_name, true), *value);
@@ -2507,9 +2519,11 @@ void BindingsGenerator::_populate_object_type_interfaces() {
class_list.pop_front();
}
+
+ return true;
}
-void BindingsGenerator::_default_argument_from_variant(const Variant &p_val, ArgumentInterface &r_iarg) {
+bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, ArgumentInterface &r_iarg) {
r_iarg.default_argument = p_val;
@@ -2555,16 +2569,24 @@ void BindingsGenerator::_default_argument_from_variant(const Variant &p_val, Arg
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
break;
case Variant::OBJECT:
- if (p_val.is_zero()) {
- r_iarg.default_argument = "null";
- break;
- }
- FALLTHROUGH;
+ ERR_FAIL_COND_V_MSG(!p_val.is_zero(), false,
+ "Parameter of type '" + String(r_iarg.type.cname) + "' can only have null/zero as the default value.");
+
+ r_iarg.default_argument = "null";
+ break;
case Variant::DICTIONARY:
- case Variant::_RID:
r_iarg.default_argument = "new %s()";
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_REF;
break;
+ case Variant::_RID:
+ ERR_FAIL_COND_V_MSG(r_iarg.type.cname != name_cache.type_RID, false,
+ "Parameter of type '" + String(r_iarg.type.cname) + "' cannot have a default value of type '" + String(name_cache.type_RID) + "'.");
+
+ ERR_FAIL_COND_V_MSG(!p_val.is_zero(), false,
+ "Parameter of type '" + String(r_iarg.type.cname) + "' can only have null/zero as the default value.");
+
+ r_iarg.default_argument = "null";
+ break;
case Variant::ARRAY:
case Variant::POOL_BYTE_ARRAY:
case Variant::POOL_INT_ARRAY:
@@ -2588,6 +2610,8 @@ void BindingsGenerator::_default_argument_from_variant(const Variant &p_val, Arg
if (r_iarg.def_param_mode == ArgumentInterface::CONSTANT && r_iarg.type.cname == name_cache.type_Variant && r_iarg.default_argument != "null")
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_REF;
+
+ return true;
}
void BindingsGenerator::_populate_builtin_type_interfaces() {
@@ -2973,13 +2997,17 @@ void BindingsGenerator::_log(const char *p_format, ...) {
void BindingsGenerator::_initialize() {
+ initialized = false;
+
EditorHelp::generate_doc();
enum_types.clear();
_initialize_blacklisted_methods();
- _populate_object_type_interfaces();
+ bool obj_type_ok = _populate_object_type_interfaces();
+ ERR_FAIL_COND_MSG(!obj_type_ok, "Failed to generate object type interfaces");
+
_populate_builtin_type_interfaces();
_populate_global_constants();
@@ -2991,6 +3019,8 @@ void BindingsGenerator::_initialize() {
for (OrderedHashMap<StringName, TypeInterface>::Element E = obj_types.front(); E; E = E.next())
_generate_method_icalls(E.get());
+
+ initialized = true;
}
void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args) {
@@ -3051,6 +3081,11 @@ void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args)
BindingsGenerator bindings_generator;
bindings_generator.set_log_print_enabled(true);
+ if (!bindings_generator.initialized) {
+ ERR_PRINTS("Failed to initialize the bindings generator");
+ ::exit(0);
+ }
+
if (glue_dir_path.length()) {
if (bindings_generator.generate_glue(glue_dir_path) != OK)
ERR_PRINTS(generate_all_glue_option + ": Failed to generate the C++ glue.");
diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h
index 6f0c297575..8f3676940b 100644
--- a/modules/mono/editor/bindings_generator.h
+++ b/modules/mono/editor/bindings_generator.h
@@ -36,7 +36,7 @@
#include "editor/doc/doc_data.h"
#include "editor/editor_help.h"
-#ifdef DEBUG_METHODS_ENABLED
+#if defined(DEBUG_METHODS_ENABLED) && defined(TOOLS_ENABLED)
#include "core/ustring.h"
@@ -472,6 +472,7 @@ class BindingsGenerator {
};
bool log_print_enabled;
+ bool initialized;
OrderedHashMap<StringName, TypeInterface> obj_types;
@@ -502,6 +503,7 @@ class BindingsGenerator {
StringName type_VarArg;
StringName type_Object;
StringName type_Reference;
+ StringName type_RID;
StringName type_String;
StringName type_at_GlobalScope;
StringName enum_Error;
@@ -525,6 +527,7 @@ class BindingsGenerator {
type_VarArg = StaticCString::create("VarArg");
type_Object = StaticCString::create("Object");
type_Reference = StaticCString::create("Reference");
+ type_RID = StaticCString::create("RID");
type_String = StaticCString::create("String");
type_at_GlobalScope = StaticCString::create("@GlobalScope");
enum_Error = StaticCString::create("Error");
@@ -590,9 +593,9 @@ class BindingsGenerator {
StringName _get_int_type_name_from_meta(GodotTypeInfo::Metadata p_meta);
StringName _get_float_type_name_from_meta(GodotTypeInfo::Metadata p_meta);
- void _default_argument_from_variant(const Variant &p_val, ArgumentInterface &r_iarg);
+ bool _arg_default_value_from_variant(const Variant &p_val, ArgumentInterface &r_iarg);
- void _populate_object_type_interfaces();
+ bool _populate_object_type_interfaces();
void _populate_builtin_type_interfaces();
void _populate_global_constants();
@@ -621,12 +624,15 @@ public:
_FORCE_INLINE_ bool is_log_print_enabled() { return log_print_enabled; }
_FORCE_INLINE_ void set_log_print_enabled(bool p_enabled) { log_print_enabled = p_enabled; }
+ _FORCE_INLINE_ bool is_initialized() { return initialized; }
+
static uint32_t get_version();
static void handle_cmdline_args(const List<String> &p_cmdline_args);
BindingsGenerator() :
- log_print_enabled(true) {
+ log_print_enabled(true),
+ initialized(false) {
_initialize();
}
};
diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp
index 915a01af7e..544bfc4615 100644
--- a/modules/mono/mono_gd/gd_mono.cpp
+++ b/modules/mono/mono_gd/gd_mono.cpp
@@ -119,26 +119,29 @@ void gdmono_debug_init() {
mono_debug_init(MONO_DEBUG_FORMAT_MONO);
+ CharString da_args = OS::get_singleton()->get_environment("GODOT_MONO_DEBUGGER_AGENT").utf8();
+
+#ifdef TOOLS_ENABLED
int da_port = GLOBAL_DEF("mono/debugger_agent/port", 23685);
bool da_suspend = GLOBAL_DEF("mono/debugger_agent/wait_for_debugger", false);
int da_timeout = GLOBAL_DEF("mono/debugger_agent/wait_timeout", 3000);
- CharString da_args = OS::get_singleton()->get_environment("GODOT_MONO_DEBUGGER_AGENT").utf8();
-
-#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint() ||
ProjectSettings::get_singleton()->get_resource_path().empty() ||
Main::is_project_manager()) {
if (da_args.size() == 0)
return;
}
-#endif
if (da_args.length() == 0) {
da_args = String("--debugger-agent=transport=dt_socket,address=127.0.0.1:" + itos(da_port) +
",embedding=1,server=y,suspend=" + (da_suspend ? "y,timeout=" + itos(da_timeout) : "n"))
.utf8();
}
+#else
+ if (da_args.length() == 0)
+ return; // Exported games don't use the project settings to setup the debugger agent
+#endif
// --debugger-agent=help
const char *options[] = {
@@ -427,8 +430,8 @@ void GDMono::_register_internal_calls() {
}
void GDMono::_initialize_and_check_api_hashes() {
-
#ifdef MONO_GLUE_ENABLED
+#ifdef DEBUG_METHODS_ENABLED
if (get_api_core_hash() != GodotSharpBindings::get_core_api_hash()) {
ERR_PRINT("Mono: Core API hash mismatch.");
}
@@ -438,6 +441,7 @@ void GDMono::_initialize_and_check_api_hashes() {
ERR_PRINT("Mono: Editor API hash mismatch.");
}
#endif // TOOLS_ENABLED
+#endif // DEBUG_METHODS_ENABLED
#endif // MONO_GLUE_ENABLED
}
@@ -761,7 +765,9 @@ bool GDMono::_try_load_api_assemblies() {
void GDMono::_load_api_assemblies() {
- if (!_try_load_api_assemblies()) {
+ bool api_assemblies_loaded = _try_load_api_assemblies();
+
+ if (!api_assemblies_loaded) {
#ifdef TOOLS_ENABLED
// The API assemblies are out of sync. Fine, try one more time, but this time
// update them from the prebuilt assemblies directory before trying to load them.
@@ -782,28 +788,30 @@ void GDMono::_load_api_assemblies() {
CRASH_COND_MSG(domain_load_err != OK, "Mono: Failed to load scripts domain.");
// 4. Try loading the updated assemblies
- if (!_try_load_api_assemblies()) {
- // welp... too bad
-
- if (_are_api_assemblies_out_of_sync()) {
- if (core_api_assembly_out_of_sync) {
- ERR_PRINT("The assembly '" CORE_API_ASSEMBLY_NAME "' is out of sync.");
- } else if (!GDMonoUtils::mono_cache.godot_api_cache_updated) {
- ERR_PRINT("The loaded assembly '" CORE_API_ASSEMBLY_NAME "' is in sync, but the cache update failed.");
- }
-
- if (editor_api_assembly_out_of_sync) {
- ERR_PRINT("The assembly '" EDITOR_API_ASSEMBLY_NAME "' is out of sync.");
- }
-
- CRASH_NOW();
- } else {
- CRASH_NOW_MSG("Failed to load one of the API assemblies.");
+ api_assemblies_loaded = _try_load_api_assemblies();
+#endif
+ }
+
+ if (!api_assemblies_loaded) {
+ // welp... too bad
+
+ if (_are_api_assemblies_out_of_sync()) {
+ if (core_api_assembly_out_of_sync) {
+ ERR_PRINT("The assembly '" CORE_API_ASSEMBLY_NAME "' is out of sync.");
+ } else if (!GDMonoUtils::mono_cache.godot_api_cache_updated) {
+ ERR_PRINT("The loaded assembly '" CORE_API_ASSEMBLY_NAME "' is in sync, but the cache update failed.");
+ }
+
+#ifdef TOOLS_ENABLED
+ if (editor_api_assembly_out_of_sync) {
+ ERR_PRINT("The assembly '" EDITOR_API_ASSEMBLY_NAME "' is out of sync.");
}
- }
-#else
- CRASH_NOW_MSG("Failed to load one of the API assemblies.");
#endif
+
+ CRASH_NOW();
+ } else {
+ CRASH_NOW_MSG("Failed to load one of the API assemblies.");
+ }
}
}
diff --git a/modules/mono/mono_gd/gd_mono.h b/modules/mono/mono_gd/gd_mono.h
index 4f7d3791f7..343d68bc2d 100644
--- a/modules/mono/mono_gd/gd_mono.h
+++ b/modules/mono/mono_gd/gd_mono.h
@@ -151,6 +151,7 @@ protected:
static GDMono *singleton;
public:
+#ifdef DEBUG_METHODS_ENABLED
uint64_t get_api_core_hash() {
if (api_core_hash == 0)
api_core_hash = ClassDB::get_api_hash(ClassDB::API_CORE);
@@ -162,7 +163,8 @@ public:
api_editor_hash = ClassDB::get_api_hash(ClassDB::API_EDITOR);
return api_editor_hash;
}
-#endif
+#endif // TOOLS_ENABLED
+#endif // DEBUG_METHODS_ENABLED
#ifdef TOOLS_ENABLED
bool copy_prebuilt_api_assembly(APIAssembly::Type p_api_type, const String &p_config);
diff --git a/modules/mono/mono_gd/gd_mono_log.cpp b/modules/mono/mono_gd/gd_mono_log.cpp
index 6d91075ce3..7b3421fdb3 100644
--- a/modules/mono/mono_gd/gd_mono_log.cpp
+++ b/modules/mono/mono_gd/gd_mono_log.cpp
@@ -104,7 +104,7 @@ void GDMonoLog::_delete_old_log_files(const String &p_logs_dir) {
ERR_FAIL_COND(!da);
Error err = da->change_dir(p_logs_dir);
- ERR_FAIL_COND(err != OK);
+ ERR_FAIL_COND_MSG(err != OK, "Cannot change directory to '" + p_logs_dir + "'.");
ERR_FAIL_COND(da->list_dir_begin() != OK);
diff --git a/modules/mono/utils/string_utils.cpp b/modules/mono/utils/string_utils.cpp
index 716c712ccc..e9efc7626d 100644
--- a/modules/mono/utils/string_utils.cpp
+++ b/modules/mono/utils/string_utils.cpp
@@ -165,7 +165,7 @@ Error read_all_file_utf8(const String &p_path, String &r_content) {
PoolVector<uint8_t> sourcef;
Error err;
FileAccess *f = FileAccess::open(p_path, FileAccess::READ, &err);
- ERR_FAIL_COND_V(err != OK, err);
+ ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot open file '" + p_path + "'.");
int len = f->get_len();
sourcef.resize(len + 1);