Browse Source

GDScript: Allow utility functions to be used as `Callable`

Danil Alexeev 1 year ago
parent
commit
b31acb0cd5

+ 28 - 19
modules/gdscript/gdscript_analyzer.cpp

@@ -31,6 +31,7 @@
 #include "gdscript_analyzer.h"
 
 #include "gdscript.h"
+#include "gdscript_utility_callable.h"
 #include "gdscript_utility_functions.h"
 
 #include "core/config/engine.h"
@@ -3410,8 +3411,8 @@ 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))) {
 			String base_name = is_self && !p_call->is_super ? "self" : base_type.to_string();
 #ifdef SUGGEST_GODOT4_RENAMES
-			String rename_hint = String();
-			if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::Code::RENAMED_IN_GODOT_4_HINT)).booleanize()) {
+			String rename_hint;
+			if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::RENAMED_IN_GODOT_4_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) + "()");
@@ -3620,8 +3621,8 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
 				p_identifier->set_datatype(type_from_variant(result, p_identifier));
 			} else if (base.is_hard_type()) {
 #ifdef SUGGEST_GODOT4_RENAMES
-				String rename_hint = String();
-				if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::Code::RENAMED_IN_GODOT_4_HINT)).booleanize()) {
+				String rename_hint;
+				if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::RENAMED_IN_GODOT_4_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);
@@ -3664,8 +3665,8 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
 					}
 					if (base.is_hard_type()) {
 #ifdef SUGGEST_GODOT4_RENAMES
-						String rename_hint = String();
-						if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::Code::RENAMED_IN_GODOT_4_HINT)).booleanize()) {
+						String rename_hint;
+						if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::RENAMED_IN_GODOT_4_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);
@@ -4117,6 +4118,19 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
 		return;
 	}
 
+	if (Variant::has_utility_function(name) || GDScriptUtilityFunctions::function_exists(name)) {
+		p_identifier->is_constant = true;
+		p_identifier->reduced_value = Callable(memnew(GDScriptUtilityCallable(name)));
+		MethodInfo method_info;
+		if (GDScriptUtilityFunctions::function_exists(name)) {
+			method_info = GDScriptUtilityFunctions::get_function_info(name);
+		} else {
+			method_info = Variant::get_utility_function_info(name);
+		}
+		p_identifier->set_datatype(make_callable_type(method_info));
+		return;
+	}
+
 	// Allow "Variant" here since it might be used for nested enums.
 	if (can_be_builtin && name == SNAME("Variant")) {
 		GDScriptParser::DataType variant;
@@ -4129,23 +4143,18 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
 	}
 
 	// Not found.
-	// Check if it's a builtin function.
-	if (GDScriptUtilityFunctions::function_exists(name)) {
-		push_error(vformat(R"(Built-in function "%s" cannot be used as an identifier.)", name), p_identifier);
-	} else {
 #ifdef SUGGEST_GODOT4_RENAMES
-		String rename_hint = String();
-		if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::Code::RENAMED_IN_GODOT_4_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);
-			}
+	String rename_hint;
+	if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::RENAMED_IN_GODOT_4_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);
+	}
+	push_error(vformat(R"(Identifier "%s" not declared in the current scope.%s)", name, rename_hint), p_identifier);
 #else
-		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 // SUGGEST_GODOT4_RENAMES
-	}
 	GDScriptParser::DataType dummy;
 	dummy.kind = GDScriptParser::DataType::VARIANT;
 	p_identifier->set_datatype(dummy); // Just so type is set to something.

+ 108 - 0
modules/gdscript/gdscript_utility_callable.cpp

@@ -0,0 +1,108 @@
+/**************************************************************************/
+/*  gdscript_utility_callable.cpp                                         */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+#include "gdscript_utility_callable.h"
+
+#include "core/templates/hashfuncs.h"
+
+bool GDScriptUtilityCallable::compare_equal(const CallableCustom *p_a, const CallableCustom *p_b) {
+	return p_a->hash() == p_b->hash();
+}
+
+bool GDScriptUtilityCallable::compare_less(const CallableCustom *p_a, const CallableCustom *p_b) {
+	return p_a->hash() < p_b->hash();
+}
+
+uint32_t GDScriptUtilityCallable::hash() const {
+	return h;
+}
+
+String GDScriptUtilityCallable::get_as_text() const {
+	String scope;
+	switch (type) {
+		case TYPE_INVALID:
+			scope = "<invalid scope>";
+			break;
+		case TYPE_GLOBAL:
+			scope = "@GlobalScope";
+			break;
+		case TYPE_GDSCRIPT:
+			scope = "@GDScript";
+			break;
+	}
+	return vformat("%s::%s (Callable)", scope, function_name);
+}
+
+CallableCustom::CompareEqualFunc GDScriptUtilityCallable::get_compare_equal_func() const {
+	return compare_equal;
+}
+
+CallableCustom::CompareLessFunc GDScriptUtilityCallable::get_compare_less_func() const {
+	return compare_less;
+}
+
+bool GDScriptUtilityCallable::is_valid() const {
+	return type != TYPE_INVALID;
+}
+
+StringName GDScriptUtilityCallable::get_method() const {
+	return function_name;
+}
+
+ObjectID GDScriptUtilityCallable::get_object() const {
+	return ObjectID();
+}
+
+void GDScriptUtilityCallable::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const {
+	switch (type) {
+		case TYPE_INVALID:
+			ERR_PRINT(vformat(R"(Trying to call invalid utility function "%s".)", function_name));
+			break;
+		case TYPE_GLOBAL:
+			Variant::call_utility_function(function_name, &r_return_value, p_arguments, p_argcount, r_call_error);
+			break;
+		case TYPE_GDSCRIPT:
+			gdscript_function(&r_return_value, p_arguments, p_argcount, r_call_error);
+			break;
+	}
+}
+
+GDScriptUtilityCallable::GDScriptUtilityCallable(const StringName &p_function_name) {
+	function_name = p_function_name;
+	if (GDScriptUtilityFunctions::function_exists(p_function_name)) {
+		type = TYPE_GDSCRIPT;
+		gdscript_function = GDScriptUtilityFunctions::get_function(p_function_name);
+	} else if (Variant::has_utility_function(p_function_name)) {
+		type = TYPE_GLOBAL;
+	} else {
+		ERR_PRINT(vformat(R"(Unknown utility function "%s".)", p_function_name));
+	}
+	h = p_function_name.hash();
+}

+ 65 - 0
modules/gdscript/gdscript_utility_callable.h

@@ -0,0 +1,65 @@
+/**************************************************************************/
+/*  gdscript_utility_callable.h                                           */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+#ifndef GDSCRIPT_UTILITY_CALLABLE_H
+#define GDSCRIPT_UTILITY_CALLABLE_H
+
+#include "gdscript_utility_functions.h"
+
+#include "core/variant/callable.h"
+
+class GDScriptUtilityCallable : public CallableCustom {
+	StringName function_name;
+	enum Type {
+		TYPE_INVALID,
+		TYPE_GLOBAL,
+		TYPE_GDSCRIPT,
+	};
+	Type type = TYPE_INVALID;
+	GDScriptUtilityFunctions::FunctionPtr gdscript_function = nullptr;
+	uint32_t h = 0;
+
+	static bool compare_equal(const CallableCustom *p_a, const CallableCustom *p_b);
+	static bool compare_less(const CallableCustom *p_a, const CallableCustom *p_b);
+
+public:
+	uint32_t hash() const override;
+	String get_as_text() const override;
+	CompareEqualFunc get_compare_equal_func() const override;
+	CompareLessFunc get_compare_less_func() const override;
+	bool is_valid() const override;
+	StringName get_method() const override;
+	ObjectID get_object() const override;
+	void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override;
+
+	GDScriptUtilityCallable(const StringName &p_function_name);
+};
+
+#endif // GDSCRIPT_UTILITY_CALLABLE_H

+ 10 - 0
modules/gdscript/tests/scripts/runtime/features/utility_func_as_callable.gd

@@ -0,0 +1,10 @@
+func test():
+	print(print)
+	print(len)
+
+	prints.callv([1, 2, 3])
+	print(mini.call(1, 2))
+	print(len.bind("abc").call())
+
+	const ABSF = absf
+	print(ABSF.call(-1.2))

+ 7 - 0
modules/gdscript/tests/scripts/runtime/features/utility_func_as_callable.out

@@ -0,0 +1,7 @@
+GDTEST_OK
+@GlobalScope::print (Callable)
+@GDScript::len (Callable)
+1 2 3
+1
+3
+1.2