summaryrefslogtreecommitdiffstats
path: root/modules/gdscript
diff options
context:
space:
mode:
Diffstat (limited to 'modules/gdscript')
-rw-r--r--modules/gdscript/doc_classes/@GDScript.xml6
-rw-r--r--modules/gdscript/editor/gdscript_docgen.cpp2
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.cpp4
-rw-r--r--modules/gdscript/gdscript.cpp47
-rw-r--r--modules/gdscript/gdscript.h1
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp29
-rw-r--r--modules/gdscript/gdscript_cache.cpp3
-rw-r--r--modules/gdscript/gdscript_editor.cpp24
-rw-r--r--modules/gdscript/gdscript_parser.cpp18
-rw-r--r--modules/gdscript/gdscript_parser.h4
-rw-r--r--modules/gdscript/gdscript_tokenizer_buffer.h2
-rw-r--r--modules/gdscript/gdscript_utility_functions.cpp492
-rw-r--r--modules/gdscript/gdscript_vm.cpp22
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/annotation_inapplicable.gd3
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/annotation_inapplicable.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/annotation_unrecognized.gd3
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/annotation_unrecognized.out2
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/gdscript_utility_implicit_conversion.gd12
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/gdscript_utility_implicit_conversion.out5
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/native_static_method_as_callable.gd8
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/native_static_method_as_callable.out3
21 files changed, 332 insertions, 360 deletions
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml
index 5fe47d69df..ede4ce6617 100644
--- a/modules/gdscript/doc_classes/@GDScript.xml
+++ b/modules/gdscript/doc_classes/@GDScript.xml
@@ -4,8 +4,8 @@
Built-in GDScript constants, functions, and annotations.
</brief_description>
<description>
- A list of GDScript-specific utility functions and annotations accessible from any script.
- For the list of the global functions and constants see [@GlobalScope].
+ A list of utility functions and annotations accessible from any script written in GDScript.
+ For the list of global functions and constants that can be accessed in any scripting language, see [@GlobalScope].
</description>
<tutorials>
<link title="GDScript exports">$DOCS_URL/tutorials/scripting/gdscript/gdscript_exports.html</link>
@@ -61,7 +61,7 @@
<method name="convert" deprecated="Use [method @GlobalScope.type_convert] instead.">
<return type="Variant" />
<param index="0" name="what" type="Variant" />
- <param index="1" name="type" type="int" />
+ <param index="1" name="type" type="int" enum="Variant.Type" />
<description>
Converts [param what] to [param type] in the best way possible. The [param type] uses the [enum Variant.Type] values.
[codeblock]
diff --git a/modules/gdscript/editor/gdscript_docgen.cpp b/modules/gdscript/editor/gdscript_docgen.cpp
index 32ef429b0d..758887a723 100644
--- a/modules/gdscript/editor/gdscript_docgen.cpp
+++ b/modules/gdscript/editor/gdscript_docgen.cpp
@@ -217,7 +217,7 @@ String GDScriptDocGen::_docvalue_from_variant(const Variant &p_variant, int p_re
List<Variant> keys;
dict.get_key_list(&keys);
- keys.sort();
+ keys.sort_custom<StringLikeVariantOrder>();
for (List<Variant>::Element *E = keys.front(); E; E = E->next()) {
if (E->prev()) {
diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp
index d765cfa1ea..0b12f2ff76 100644
--- a/modules/gdscript/editor/gdscript_highlighter.cpp
+++ b/modules/gdscript/editor/gdscript_highlighter.cpp
@@ -701,7 +701,9 @@ void GDScriptSyntaxHighlighter::_update_cache() {
List<StringName> types;
ClassDB::get_class_list(&types);
for (const StringName &E : types) {
- class_names[E] = types_color;
+ if (ClassDB::is_class_exposed(E)) {
+ class_names[E] = types_color;
+ }
}
/* User types. */
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index 7b9aa70686..aab9a5acf1 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -76,9 +76,16 @@ bool GDScriptNativeClass::_get(const StringName &p_name, Variant &r_ret) const {
if (ok) {
r_ret = v;
return true;
- } else {
- return false;
}
+
+ MethodBind *method = ClassDB::get_method(name, p_name);
+ if (method && method->is_static()) {
+ // Native static method.
+ r_ret = Callable(this, p_name);
+ return true;
+ }
+
+ return false;
}
void GDScriptNativeClass::_bind_methods() {
@@ -693,10 +700,16 @@ void GDScript::_static_default_init() {
continue;
}
if (type.builtin_type == Variant::ARRAY && type.has_container_element_type(0)) {
+ const GDScriptDataType element_type = type.get_container_element_type(0);
Array default_value;
- const GDScriptDataType &element_type = type.get_container_element_type(0);
default_value.set_typed(element_type.builtin_type, element_type.native_type, element_type.script_type);
static_variables.write[E.value.index] = default_value;
+ } else if (type.builtin_type == Variant::DICTIONARY && type.has_container_element_types()) {
+ const GDScriptDataType key_type = type.get_container_element_type_or_variant(0);
+ const GDScriptDataType value_type = type.get_container_element_type_or_variant(1);
+ Dictionary default_value;
+ default_value.set_typed(key_type.builtin_type, key_type.native_type, key_type.script_type, value_type.builtin_type, value_type.native_type, value_type.script_type);
+ static_variables.write[E.value.index] = default_value;
} else {
Variant default_value;
Callable::CallError err;
@@ -1062,6 +1075,26 @@ void GDScript::_bind_methods() {
ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "new", &GDScript::_new, MethodInfo("new"));
}
+void GDScript::set_path_cache(const String &p_path) {
+ if (ResourceCache::has(p_path)) {
+ set_path(p_path, true);
+ return;
+ }
+
+ if (is_root_script()) {
+ Script::set_path_cache(p_path);
+ }
+
+ String old_path = path;
+ path = p_path;
+ path_valid = true;
+ GDScriptCache::move_script(old_path, p_path);
+
+ for (KeyValue<StringName, Ref<GDScript>> &kv : subclasses) {
+ kv.value->set_path_cache(p_path);
+ }
+}
+
void GDScript::set_path(const String &p_path, bool p_take_over) {
if (is_root_script()) {
Script::set_path(p_path, p_take_over);
@@ -2540,11 +2573,11 @@ void GDScriptLanguage::reload_all_scripts() {
}
}
}
-#endif
+#endif // TOOLS_ENABLED
}
reload_scripts(scripts, true);
-#endif
+#endif // DEBUG_ENABLED
}
void GDScriptLanguage::reload_scripts(const Array &p_scripts, bool p_soft_reload) {
@@ -2614,7 +2647,7 @@ void GDScriptLanguage::reload_scripts(const Array &p_scripts, bool p_soft_reload
}
}
-#endif
+#endif // TOOLS_ENABLED
for (const KeyValue<ObjectID, List<Pair<StringName, Variant>>> &F : scr->pending_reload_state) {
map[F.key] = F.value; //pending to reload, use this one instead
@@ -2682,7 +2715,7 @@ void GDScriptLanguage::reload_scripts(const Array &p_scripts, bool p_soft_reload
//if instance states were saved, set them!
}
-#endif
+#endif // DEBUG_ENABLED
}
void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload) {
diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h
index 9bb39aac0f..006a09debb 100644
--- a/modules/gdscript/gdscript.h
+++ b/modules/gdscript/gdscript.h
@@ -300,6 +300,7 @@ public:
virtual Error reload(bool p_keep_state = false) override;
+ virtual void set_path_cache(const String &p_path) override;
virtual void set_path(const String &p_path, bool p_take_over = false) override;
String get_script_path() const;
Error load_source_code(const String &p_path);
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index 7f0d5005cb..93d4a512a9 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -148,6 +148,15 @@ static GDScriptParser::DataType make_enum_type(const StringName &p_enum_name, co
return type;
}
+static GDScriptParser::DataType make_class_enum_type(const StringName &p_enum_name, GDScriptParser::ClassNode *p_class, const String &p_script_path, bool p_meta = true) {
+ GDScriptParser::DataType type = make_enum_type(p_enum_name, p_class->fqcn, p_meta);
+
+ type.class_type = p_class;
+ type.script_path = p_script_path;
+
+ return type;
+}
+
static GDScriptParser::DataType make_native_enum_type(const StringName &p_enum_name, const StringName &p_native_class, bool p_meta = true) {
// Find out which base class declared the enum, so the name is always the same even when coming from other contexts.
StringName native_base = p_native_class;
@@ -931,8 +940,8 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
Finally finally([&]() {
ensure_cached_external_parser_for_class(member.get_datatype().class_type, p_class, "Trying to resolve datatype of class member", p_source);
GDScriptParser::DataType member_type = member.get_datatype();
- if (member_type.has_container_element_type(0)) {
- ensure_cached_external_parser_for_class(member_type.get_container_element_type(0).class_type, p_class, "Trying to resolve datatype of class member", p_source);
+ for (int i = 0; i < member_type.get_container_element_type_count(); ++i) {
+ ensure_cached_external_parser_for_class(member_type.get_container_element_type(i).class_type, p_class, "Trying to resolve datatype of class member", p_source);
}
});
@@ -1101,7 +1110,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
check_class_member_name_conflict(p_class, member.m_enum->identifier->name, member.m_enum);
member.m_enum->set_datatype(resolving_datatype);
- GDScriptParser::DataType enum_type = make_enum_type(member.m_enum->identifier->name, p_class->fqcn, true);
+ GDScriptParser::DataType enum_type = make_class_enum_type(member.m_enum->identifier->name, p_class, parser->script_path, true);
const GDScriptParser::EnumNode *prev_enum = current_enum;
current_enum = member.m_enum;
@@ -1194,7 +1203,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
// Also update the original references.
member.enum_value.parent_enum->values.set(member.enum_value.index, member.enum_value);
- member.enum_value.identifier->set_datatype(make_enum_type(UNNAMED_ENUM, p_class->fqcn, false));
+ member.enum_value.identifier->set_datatype(make_class_enum_type(UNNAMED_ENUM, p_class, parser->script_path, false));
} break;
case GDScriptParser::ClassNode::Member::CLASS:
check_class_member_name_conflict(p_class, member.m_class->identifier->name, member.m_class);
@@ -3807,6 +3816,12 @@ GDScriptParser::DataType GDScriptAnalyzer::make_global_class_meta_type(const Str
}
Ref<GDScriptParserRef> GDScriptAnalyzer::ensure_cached_external_parser_for_class(const GDScriptParser::ClassNode *p_class, const GDScriptParser::ClassNode *p_from_class, const char *p_context, const GDScriptParser::Node *p_source) {
+ // Delicate piece of code that intentionally doesn't use the GDScript cache or `get_depended_parser_for`.
+ // Search dependencies for the parser that owns `p_class` and make a cache entry for it.
+ // Required for how we store pointers to classes owned by other parser trees and need to call `resolve_class_member` and such on the same parser tree.
+ // Since https://github.com/godotengine/godot/pull/94871 there can technically be multiple parsers for the same script in the same parser tree.
+ // Even if unlikely, getting the wrong parser could lead to strange undefined behavior without errors.
+
if (p_class == nullptr) {
return nullptr;
}
@@ -3823,8 +3838,6 @@ Ref<GDScriptParserRef> GDScriptAnalyzer::ensure_cached_external_parser_for_class
p_from_class = parser->head;
}
- String script_path = p_class->get_datatype().script_path;
-
Ref<GDScriptParserRef> parser_ref;
for (const GDScriptParser::ClassNode *look_class = p_from_class; look_class != nullptr; look_class = look_class->base_type.class_type) {
if (parser->has_class(look_class)) {
@@ -4249,7 +4262,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
const GDScriptParser::EnumNode::Value &element = current_enum->values[i];
if (element.identifier->name == p_identifier->name) {
StringName enum_name = current_enum->identifier ? current_enum->identifier->name : UNNAMED_ENUM;
- GDScriptParser::DataType type = make_enum_type(enum_name, parser->current_class->fqcn, false);
+ GDScriptParser::DataType type = make_class_enum_type(enum_name, parser->current_class, parser->script_path, false);
if (element.parent_enum->identifier) {
type.enum_type = element.parent_enum->identifier->name;
}
@@ -5858,7 +5871,7 @@ void GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_identifier
parent = ClassDB::get_parent_class(parent);
}
}
-#endif
+#endif // DEBUG_ENABLED
GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, bool &r_valid, const GDScriptParser::Node *p_source) {
// Unary version.
diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp
index 3c022412bd..fa22798edf 100644
--- a/modules/gdscript/gdscript_cache.cpp
+++ b/modules/gdscript/gdscript_cache.cpp
@@ -312,7 +312,7 @@ Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, Error &r_e
Ref<GDScript> script;
script.instantiate();
- script->set_path(p_path, true);
+ script->set_path_cache(p_path);
if (remapped_path.get_extension().to_lower() == "gdc") {
Vector<uint8_t> buffer = get_binary_tokens(remapped_path);
if (buffer.is_empty()) {
@@ -360,6 +360,7 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro
return script;
}
}
+ script->set_path(p_path, true);
const String remapped_path = ResourceLoader::path_remap(p_path);
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index 0fd891aa80..951ae6ce99 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -3164,7 +3164,9 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
HashMap<String, ScriptLanguage::CodeCompletionOption> options;
GDScriptParser::CompletionContext completion_context = parser.get_completion_context();
- completion_context.base = p_owner;
+ if (completion_context.current_class != nullptr && completion_context.current_class->outer == nullptr) {
+ completion_context.base = p_owner;
+ }
bool is_function = false;
switch (completion_context.type) {
@@ -3534,13 +3536,13 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
return OK;
}
-#else
+#else // !TOOLS_ENABLED
Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path, Object *p_owner, List<ScriptLanguage::CodeCompletionOption> *r_options, bool &r_forced, String &r_call_hint) {
return OK;
}
-#endif
+#endif // TOOLS_ENABLED
//////// END COMPLETION //////////
@@ -3782,7 +3784,19 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
}
} break;
case GDScriptParser::DataType::ENUM: {
- if (base_type.enum_values.has(p_symbol)) {
+ if (base_type.class_type && base_type.class_type->has_member(base_type.enum_type)) {
+ GDScriptParser::EnumNode *base_enum = base_type.class_type->get_member(base_type.enum_type).m_enum;
+ for (const GDScriptParser::EnumNode::Value &value : base_enum->values) {
+ if (value.identifier && value.identifier->name == p_symbol) {
+ r_result.type = ScriptLanguage::LOOKUP_RESULT_SCRIPT_LOCATION;
+ r_result.class_path = base_type.script_path;
+ r_result.location = value.line;
+ Error err = OK;
+ r_result.script = GDScriptCache::get_shallow_script(r_result.class_path, err);
+ return err;
+ }
+ }
+ } else if (base_type.enum_values.has(p_symbol)) {
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT;
r_result.class_name = String(base_type.native_type).get_slicec('.', 0);
r_result.class_member = p_symbol;
@@ -4113,4 +4127,4 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
return ERR_CANT_RESOLVE;
}
-#endif
+#endif // TOOLS_ENABLED
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index 111a39d730..ee8d53639c 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -244,7 +244,7 @@ void GDScriptParser::apply_pending_warnings() {
pending_warnings.clear();
}
-#endif
+#endif // DEBUG_ENABLED
void GDScriptParser::override_completion_context(const Node *p_for_node, CompletionType p_type, Node *p_node, int p_argument) {
if (!for_completion) {
@@ -1624,15 +1624,17 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali
valid = false;
}
- annotation->info = &valid_annotations[annotation->name];
+ if (valid) {
+ annotation->info = &valid_annotations[annotation->name];
- if (!annotation->applies_to(p_valid_targets)) {
- if (annotation->applies_to(AnnotationInfo::SCRIPT)) {
- push_error(vformat(R"(Annotation "%s" must be at the top of the script, before "extends" and "class_name".)", annotation->name));
- } else {
- push_error(vformat(R"(Annotation "%s" is not allowed in this level.)", annotation->name));
+ if (!annotation->applies_to(p_valid_targets)) {
+ if (annotation->applies_to(AnnotationInfo::SCRIPT)) {
+ push_error(vformat(R"(Annotation "%s" must be at the top of the script, before "extends" and "class_name".)", annotation->name));
+ } else {
+ push_error(vformat(R"(Annotation "%s" is not allowed in this level.)", annotation->name));
+ }
+ valid = false;
}
- valid = false;
}
if (check(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) {
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index 7f64ae902b..2ec33831a2 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -165,6 +165,10 @@ public:
container_element_types.write[p_index] = DataType(p_type);
}
+ _FORCE_INLINE_ int get_container_element_type_count() const {
+ return container_element_types.size();
+ }
+
_FORCE_INLINE_ DataType get_container_element_type(int p_index) const {
ERR_FAIL_INDEX_V(p_index, container_element_types.size(), get_variant_type());
return container_element_types[p_index];
diff --git a/modules/gdscript/gdscript_tokenizer_buffer.h b/modules/gdscript/gdscript_tokenizer_buffer.h
index 55df66e50f..d5d2a4d096 100644
--- a/modules/gdscript/gdscript_tokenizer_buffer.h
+++ b/modules/gdscript/gdscript_tokenizer_buffer.h
@@ -79,7 +79,7 @@ public:
virtual bool is_past_cursor() const override;
virtual void push_expression_indented_block() override; // For lambdas, or blocks inside expressions.
virtual void pop_expression_indented_block() override; // For lambdas, or blocks inside expressions.
- virtual bool is_text() override { return false; };
+ virtual bool is_text() override { return false; }
#ifdef TOOLS_ENABLED
virtual const HashMap<int, CommentData> &get_comments() const override {
diff --git a/modules/gdscript/gdscript_utility_functions.cpp b/modules/gdscript/gdscript_utility_functions.cpp
index 59dd983ed2..8246069696 100644
--- a/modules/gdscript/gdscript_utility_functions.cpp
+++ b/modules/gdscript/gdscript_utility_functions.cpp
@@ -34,7 +34,6 @@
#include "core/io/resource_loader.h"
#include "core/object/class_db.h"
-#include "core/object/method_bind.h"
#include "core/object/object.h"
#include "core/templates/oa_hash_map.h"
#include "core/templates/vector.h"
@@ -42,101 +41,105 @@
#ifdef DEBUG_ENABLED
-#define VALIDATE_ARG_COUNT(m_count) \
- if (p_arg_count < m_count) { \
- r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; \
- r_error.expected = m_count; \
+#define DEBUG_VALIDATE_ARG_COUNT(m_min_count, m_max_count) \
+ if (unlikely(p_arg_count < m_min_count)) { \
*r_ret = Variant(); \
+ r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; \
+ r_error.expected = m_min_count; \
return; \
} \
- if (p_arg_count > m_count) { \
- r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; \
- r_error.expected = m_count; \
+ if (unlikely(p_arg_count > m_max_count)) { \
*r_ret = Variant(); \
+ r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; \
+ r_error.expected = m_max_count; \
return; \
}
-#define VALIDATE_ARG_INT(m_arg) \
- if (p_args[m_arg]->get_type() != Variant::INT) { \
+#define DEBUG_VALIDATE_ARG_TYPE(m_arg, m_type) \
+ if (unlikely(!Variant::can_convert_strict(p_args[m_arg]->get_type(), m_type))) { \
+ *r_ret = Variant(); \
+ r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; \
+ r_error.argument = m_arg; \
+ r_error.expected = m_type; \
+ return; \
+ }
+
+#define DEBUG_VALIDATE_ARG_CUSTOM(m_arg, m_type, m_cond, m_msg) \
+ if (unlikely(m_cond)) { \
+ *r_ret = m_msg; \
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; \
r_error.argument = m_arg; \
- r_error.expected = Variant::INT; \
- *r_ret = Variant(); \
+ r_error.expected = m_type; \
return; \
}
-#define VALIDATE_ARG_NUM(m_arg) \
- if (!p_args[m_arg]->is_num()) { \
+#else // !DEBUG_ENABLED
+
+#define DEBUG_VALIDATE_ARG_COUNT(m_min_count, m_max_count)
+#define DEBUG_VALIDATE_ARG_TYPE(m_arg, m_type)
+#define DEBUG_VALIDATE_ARG_CUSTOM(m_arg, m_type, m_cond, m_msg)
+
+#endif // DEBUG_ENABLED
+
+#define VALIDATE_ARG_CUSTOM(m_arg, m_type, m_cond, m_msg) \
+ if (unlikely(m_cond)) { \
+ *r_ret = m_msg; \
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; \
r_error.argument = m_arg; \
- r_error.expected = Variant::FLOAT; \
- *r_ret = Variant(); \
+ r_error.expected = m_type; \
return; \
}
-#else
-
-#define VALIDATE_ARG_COUNT(m_count)
-#define VALIDATE_ARG_INT(m_arg)
-#define VALIDATE_ARG_NUM(m_arg)
-
-#endif
+#define GDFUNC_FAIL_COND_MSG(m_cond, m_msg) \
+ if (unlikely(m_cond)) { \
+ *r_ret = m_msg; \
+ r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; \
+ return; \
+ }
struct GDScriptUtilityFunctionsDefinitions {
#ifndef DISABLE_DEPRECATED
static inline void convert(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
- VALIDATE_ARG_COUNT(2);
- VALIDATE_ARG_INT(1);
+ DEBUG_VALIDATE_ARG_COUNT(2, 2);
+ DEBUG_VALIDATE_ARG_TYPE(1, Variant::INT);
+
int type = *p_args[1];
- if (type < 0 || type >= Variant::VARIANT_MAX) {
- *r_ret = RTR("Invalid type argument to convert(), use TYPE_* constants.");
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::INT;
- return;
+ DEBUG_VALIDATE_ARG_CUSTOM(1, Variant::INT, type < 0 || type >= Variant::VARIANT_MAX,
+ RTR("Invalid type argument to convert(), use TYPE_* constants."));
- } else {
- Variant::construct(Variant::Type(type), *r_ret, p_args, 1, r_error);
- if (r_error.error != Callable::CallError::CALL_OK) {
- *r_ret = vformat(RTR(R"(Cannot convert "%s" to "%s".)"), Variant::get_type_name(p_args[0]->get_type()), Variant::get_type_name(Variant::Type(type)));
- }
- }
+ Variant::construct(Variant::Type(type), *r_ret, p_args, 1, r_error);
}
#endif // DISABLE_DEPRECATED
static inline void type_exists(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
- VALIDATE_ARG_COUNT(1);
+ DEBUG_VALIDATE_ARG_COUNT(1, 1);
+ DEBUG_VALIDATE_ARG_TYPE(0, Variant::STRING_NAME);
*r_ret = ClassDB::class_exists(*p_args[0]);
}
static inline void _char(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
- VALIDATE_ARG_COUNT(1);
- VALIDATE_ARG_INT(0);
+ DEBUG_VALIDATE_ARG_COUNT(1, 1);
+ DEBUG_VALIDATE_ARG_TYPE(0, Variant::INT);
char32_t result[2] = { *p_args[0], 0 };
*r_ret = String(result);
}
static inline void range(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
+ DEBUG_VALIDATE_ARG_COUNT(1, 3);
switch (p_arg_count) {
- case 0: {
- r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
- r_error.expected = 1;
- *r_ret = Variant();
- } break;
case 1: {
- VALIDATE_ARG_NUM(0);
+ DEBUG_VALIDATE_ARG_TYPE(0, Variant::INT);
+
int count = *p_args[0];
+
Array arr;
if (count <= 0) {
*r_ret = arr;
return;
}
+
Error err = arr.resize(count);
- if (err != OK) {
- *r_ret = RTR("Cannot resize array.");
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
- return;
- }
+ GDFUNC_FAIL_COND_MSG(err != OK, RTR("Cannot resize array."));
for (int i = 0; i < count; i++) {
arr[i] = i;
@@ -145,8 +148,8 @@ struct GDScriptUtilityFunctionsDefinitions {
*r_ret = arr;
} break;
case 2: {
- VALIDATE_ARG_NUM(0);
- VALIDATE_ARG_NUM(1);
+ DEBUG_VALIDATE_ARG_TYPE(0, Variant::INT);
+ DEBUG_VALIDATE_ARG_TYPE(1, Variant::INT);
int from = *p_args[0];
int to = *p_args[1];
@@ -156,30 +159,26 @@ struct GDScriptUtilityFunctionsDefinitions {
*r_ret = arr;
return;
}
+
Error err = arr.resize(to - from);
- if (err != OK) {
- *r_ret = RTR("Cannot resize array.");
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
- return;
- }
+ GDFUNC_FAIL_COND_MSG(err != OK, RTR("Cannot resize array."));
+
for (int i = from; i < to; i++) {
arr[i - from] = i;
}
+
*r_ret = arr;
} break;
case 3: {
- VALIDATE_ARG_NUM(0);
- VALIDATE_ARG_NUM(1);
- VALIDATE_ARG_NUM(2);
+ DEBUG_VALIDATE_ARG_TYPE(0, Variant::INT);
+ DEBUG_VALIDATE_ARG_TYPE(1, Variant::INT);
+ DEBUG_VALIDATE_ARG_TYPE(2, Variant::INT);
int from = *p_args[0];
int to = *p_args[1];
int incr = *p_args[2];
- if (incr == 0) {
- *r_ret = RTR("Step argument is zero!");
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
- return;
- }
+
+ VALIDATE_ARG_CUSTOM(2, Variant::INT, incr == 0, RTR("Step argument is zero!"));
Array arr;
if (from >= to && incr > 0) {
@@ -200,12 +199,7 @@ struct GDScriptUtilityFunctionsDefinitions {
}
Error err = arr.resize(count);
-
- if (err != OK) {
- *r_ret = RTR("Cannot resize array.");
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
- return;
- }
+ GDFUNC_FAIL_COND_MSG(err != OK, RTR("Cannot resize array."));
if (incr > 0) {
int idx = 0;
@@ -221,138 +215,79 @@ struct GDScriptUtilityFunctionsDefinitions {
*r_ret = arr;
} break;
- default: {
- r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS;
- r_error.expected = 3;
- *r_ret = Variant();
-
- } break;
}
}
static inline void load(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
- VALIDATE_ARG_COUNT(1);
- if (!p_args[0]->is_string()) {
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::STRING;
- *r_ret = Variant();
- } else {
- *r_ret = ResourceLoader::load(*p_args[0]);
- }
+ DEBUG_VALIDATE_ARG_COUNT(1, 1);
+ DEBUG_VALIDATE_ARG_TYPE(0, Variant::STRING);
+ *r_ret = ResourceLoader::load(*p_args[0]);
}
static inline void inst_to_dict(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
- VALIDATE_ARG_COUNT(1);
+ DEBUG_VALIDATE_ARG_COUNT(1, 1);
+ DEBUG_VALIDATE_ARG_TYPE(0, Variant::OBJECT);
if (p_args[0]->get_type() == Variant::NIL) {
*r_ret = Variant();
- } else if (p_args[0]->get_type() != Variant::OBJECT) {
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::OBJECT;
+ return;
+ }
+
+ Object *obj = *p_args[0];
+ if (!obj) {
*r_ret = Variant();
- } else {
- Object *obj = *p_args[0];
- if (!obj) {
- *r_ret = Variant();
+ return;
+ }
- } else if (!obj->get_script_instance() || obj->get_script_instance()->get_language() != GDScriptLanguage::get_singleton()) {
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::DICTIONARY;
- *r_ret = RTR("Not a script with an instance");
- return;
- } else {
- GDScriptInstance *ins = static_cast<GDScriptInstance *>(obj->get_script_instance());
- Ref<GDScript> base = ins->get_script();
- if (base.is_null()) {
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::DICTIONARY;
- *r_ret = RTR("Not based on a script");
- return;
- }
+ VALIDATE_ARG_CUSTOM(0, Variant::OBJECT,
+ !obj->get_script_instance() || obj->get_script_instance()->get_language() != GDScriptLanguage::get_singleton(),
+ RTR("Not a script with an instance."));
- GDScript *p = base.ptr();
- String path = p->get_script_path();
- Vector<StringName> sname;
+ GDScriptInstance *inst = static_cast<GDScriptInstance *>(obj->get_script_instance());
- while (p->_owner) {
- sname.push_back(p->local_name);
- p = p->_owner;
- }
- sname.reverse();
+ Ref<GDScript> base = inst->get_script();
+ VALIDATE_ARG_CUSTOM(0, Variant::OBJECT, base.is_null(), RTR("Not based on a script."));
- if (!path.is_resource_file()) {
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::DICTIONARY;
- *r_ret = Variant();
+ GDScript *p = base.ptr();
+ String path = p->get_script_path();
+ Vector<StringName> sname;
- *r_ret = RTR("Not based on a resource file");
+ while (p->_owner) {
+ sname.push_back(p->local_name);
+ p = p->_owner;
+ }
+ sname.reverse();
- return;
- }
+ VALIDATE_ARG_CUSTOM(0, Variant::OBJECT, !path.is_resource_file(), RTR("Not based on a resource file."));
- NodePath cp(sname, Vector<StringName>(), false);
+ NodePath cp(sname, Vector<StringName>(), false);
- Dictionary d;
- d["@subpath"] = cp;
- d["@path"] = path;
+ Dictionary d;
+ d["@subpath"] = cp;
+ d["@path"] = path;
- for (const KeyValue<StringName, GDScript::MemberInfo> &E : base->member_indices) {
- if (!d.has(E.key)) {
- d[E.key] = ins->members[E.value.index];
- }
- }
- *r_ret = d;
+ for (const KeyValue<StringName, GDScript::MemberInfo> &E : base->member_indices) {
+ if (!d.has(E.key)) {
+ d[E.key] = inst->members[E.value.index];
}
}
+
+ *r_ret = d;
}
static inline void dict_to_inst(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
- VALIDATE_ARG_COUNT(1);
-
- if (p_args[0]->get_type() != Variant::DICTIONARY) {
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::DICTIONARY;
- *r_ret = Variant();
-
- return;
- }
+ DEBUG_VALIDATE_ARG_COUNT(1, 1);
+ DEBUG_VALIDATE_ARG_TYPE(0, Variant::DICTIONARY);
Dictionary d = *p_args[0];
- if (!d.has("@path")) {
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::OBJECT;
- *r_ret = RTR("Invalid instance dictionary format (missing @path)");
-
- return;
- }
+ VALIDATE_ARG_CUSTOM(0, Variant::DICTIONARY, !d.has("@path"), RTR("Invalid instance dictionary format (missing @path)."));
Ref<Script> scr = ResourceLoader::load(d["@path"]);
- if (!scr.is_valid()) {
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::OBJECT;
- *r_ret = RTR("Invalid instance dictionary format (can't load script at @path)");
- return;
- }
+ VALIDATE_ARG_CUSTOM(0, Variant::DICTIONARY, !scr.is_valid(), RTR("Invalid instance dictionary format (can't load script at @path)."));
Ref<GDScript> gdscr = scr;
-
- if (!gdscr.is_valid()) {
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::OBJECT;
- *r_ret = Variant();
- *r_ret = RTR("Invalid instance dictionary format (invalid script at @path)");
- return;
- }
+ VALIDATE_ARG_CUSTOM(0, Variant::DICTIONARY, !gdscr.is_valid(), RTR("Invalid instance dictionary format (invalid script at @path)."));
NodePath sub;
if (d.has("@subpath")) {
@@ -361,54 +296,35 @@ struct GDScriptUtilityFunctionsDefinitions {
for (int i = 0; i < sub.get_name_count(); i++) {
gdscr = gdscr->subclasses[sub.get_name(i)];
- if (!gdscr.is_valid()) {
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::OBJECT;
- *r_ret = Variant();
- *r_ret = RTR("Invalid instance dictionary (invalid subclasses)");
- return;
- }
+ VALIDATE_ARG_CUSTOM(0, Variant::DICTIONARY, !gdscr.is_valid(), RTR("Invalid instance dictionary (invalid subclasses)."));
}
- *r_ret = gdscr->_new(nullptr, -1 /*skip initializer*/, r_error);
+ *r_ret = gdscr->_new(nullptr, -1 /* skip initializer */, r_error);
if (r_error.error != Callable::CallError::CALL_OK) {
*r_ret = RTR("Cannot instantiate GDScript class.");
return;
}
- GDScriptInstance *ins = static_cast<GDScriptInstance *>(static_cast<Object *>(*r_ret)->get_script_instance());
- Ref<GDScript> gd_ref = ins->get_script();
+ GDScriptInstance *inst = static_cast<GDScriptInstance *>(static_cast<Object *>(*r_ret)->get_script_instance());
+ Ref<GDScript> gd_ref = inst->get_script();
for (KeyValue<StringName, GDScript::MemberInfo> &E : gd_ref->member_indices) {
if (d.has(E.key)) {
- ins->members.write[E.value.index] = d[E.key];
+ inst->members.write[E.value.index] = d[E.key];
}
}
}
static inline void Color8(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
- if (p_arg_count < 3) {
- r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
- r_error.expected = 3;
- *r_ret = Variant();
- return;
- }
- if (p_arg_count > 4) {
- r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS;
- r_error.expected = 4;
- *r_ret = Variant();
- return;
- }
-
- VALIDATE_ARG_INT(0);
- VALIDATE_ARG_INT(1);
- VALIDATE_ARG_INT(2);
+ DEBUG_VALIDATE_ARG_COUNT(3, 4);
+ DEBUG_VALIDATE_ARG_TYPE(0, Variant::INT);
+ DEBUG_VALIDATE_ARG_TYPE(1, Variant::INT);
+ DEBUG_VALIDATE_ARG_TYPE(2, Variant::INT);
Color color((int64_t)*p_args[0] / 255.0f, (int64_t)*p_args[1] / 255.0f, (int64_t)*p_args[2] / 255.0f);
if (p_arg_count == 4) {
- VALIDATE_ARG_INT(3);
+ DEBUG_VALIDATE_ARG_TYPE(3, Variant::INT);
color.a = (int64_t)*p_args[3] / 255.0f;
}
@@ -435,7 +351,8 @@ struct GDScriptUtilityFunctionsDefinitions {
}
static inline void print_stack(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
- VALIDATE_ARG_COUNT(0);
+ DEBUG_VALIDATE_ARG_COUNT(0, 0);
+
if (Thread::get_caller_id() != Thread::get_main_id()) {
print_line("Cannot retrieve debug info outside the main thread. Thread ID: " + itos(Thread::get_caller_id()));
return;
@@ -449,7 +366,8 @@ struct GDScriptUtilityFunctionsDefinitions {
}
static inline void get_stack(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
- VALIDATE_ARG_COUNT(0);
+ DEBUG_VALIDATE_ARG_COUNT(0, 0);
+
if (Thread::get_caller_id() != Thread::get_main_id()) {
*r_ret = TypedArray<Dictionary>();
return;
@@ -468,7 +386,7 @@ struct GDScriptUtilityFunctionsDefinitions {
}
static inline void len(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
- VALIDATE_ARG_COUNT(1);
+ DEBUG_VALIDATE_ARG_COUNT(1, 1);
switch (p_args[0]->get_type()) {
case Variant::STRING:
case Variant::STRING_NAME: {
@@ -524,56 +442,34 @@ struct GDScriptUtilityFunctionsDefinitions {
*r_ret = d.size();
} break;
default: {
+ *r_ret = vformat(RTR("Value of type '%s' can't provide a length."), Variant::get_type_name(p_args[0]->get_type()));
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::NIL;
- *r_ret = vformat(RTR("Value of type '%s' can't provide a length."), Variant::get_type_name(p_args[0]->get_type()));
- }
+ } break;
}
}
static inline void is_instance_of(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
- VALIDATE_ARG_COUNT(2);
+ DEBUG_VALIDATE_ARG_COUNT(2, 2);
if (p_args[1]->get_type() == Variant::INT) {
int builtin_type = *p_args[1];
- if (builtin_type < 0 || builtin_type >= Variant::VARIANT_MAX) {
- *r_ret = RTR("Invalid type argument for is_instance_of(), use TYPE_* constants for built-in types.");
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 1;
- r_error.expected = Variant::NIL;
- return;
- }
+ DEBUG_VALIDATE_ARG_CUSTOM(1, Variant::NIL, builtin_type < 0 || builtin_type >= Variant::VARIANT_MAX,
+ RTR("Invalid type argument for is_instance_of(), use TYPE_* constants for built-in types."));
*r_ret = p_args[0]->get_type() == builtin_type;
return;
}
bool was_type_freed = false;
Object *type_object = p_args[1]->get_validated_object_with_check(was_type_freed);
- if (was_type_freed) {
- *r_ret = RTR("Type argument is a previously freed instance.");
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 1;
- r_error.expected = Variant::NIL;
- return;
- }
- if (!type_object) {
- *r_ret = RTR("Invalid type argument for is_instance_of(), should be a TYPE_* constant, a class or a script.");
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 1;
- r_error.expected = Variant::NIL;
- return;
- }
+ VALIDATE_ARG_CUSTOM(1, Variant::NIL, was_type_freed, RTR("Type argument is a previously freed instance."));
+ VALIDATE_ARG_CUSTOM(1, Variant::NIL, !type_object,
+ RTR("Invalid type argument for is_instance_of(), should be a TYPE_* constant, a class or a script."));
bool was_value_freed = false;
Object *value_object = p_args[0]->get_validated_object_with_check(was_value_freed);
- if (was_value_freed) {
- *r_ret = RTR("Value argument is a previously freed instance.");
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::NIL;
- return;
- }
+ VALIDATE_ARG_CUSTOM(0, Variant::NIL, was_value_freed, RTR("Value argument is a previously freed instance."));
if (!value_object) {
*r_ret = false;
return;
@@ -618,113 +514,77 @@ struct GDScriptUtilityFunctionInfo {
static OAHashMap<StringName, GDScriptUtilityFunctionInfo> utility_function_table;
static List<StringName> utility_function_name_table;
-static void _register_function(const String &p_name, const MethodInfo &p_method_info, GDScriptUtilityFunctions::FunctionPtr p_function, bool p_is_const) {
- StringName sname(p_name);
-
- ERR_FAIL_COND(utility_function_table.has(sname));
+static void _register_function(const StringName &p_name, const MethodInfo &p_method_info, GDScriptUtilityFunctions::FunctionPtr p_function, bool p_is_const) {
+ ERR_FAIL_COND(utility_function_table.has(p_name));
GDScriptUtilityFunctionInfo function;
function.function = p_function;
function.info = p_method_info;
function.is_constant = p_is_const;
- utility_function_table.insert(sname, function);
- utility_function_name_table.push_back(sname);
+ utility_function_table.insert(p_name, function);
+ utility_function_name_table.push_back(p_name);
}
-#define REGISTER_FUNC(m_func, m_is_const, m_return_type, ...) \
+#define REGISTER_FUNC(m_func, m_is_const, m_return, m_args, m_is_vararg, m_default_args) \
{ \
String name(#m_func); \
if (name.begins_with("_")) { \
- name = name.substr(1, name.length() - 1); \
+ name = name.substr(1); \
} \
- MethodInfo info = MethodInfo(name, __VA_ARGS__); \
- info.return_val.type = m_return_type; \
- _register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \
- }
-
-#define REGISTER_FUNC_NO_ARGS(m_func, m_is_const, m_return_type) \
- { \
- String name(#m_func); \
- if (name.begins_with("_")) { \
- name = name.substr(1, name.length() - 1); \
+ MethodInfo info = m_args; \
+ info.name = name; \
+ info.return_val = m_return; \
+ info.default_arguments = m_default_args; \
+ if (m_is_vararg) { \
+ info.flags |= METHOD_FLAG_VARARG; \
} \
- MethodInfo info = MethodInfo(name); \
- info.return_val.type = m_return_type; \
_register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \
}
-#define REGISTER_VARARG_FUNC(m_func, m_is_const, m_return_type) \
- { \
- String name(#m_func); \
- if (name.begins_with("_")) { \
- name = name.substr(1, name.length() - 1); \
- } \
- MethodInfo info = MethodInfo(name); \
- info.return_val.type = m_return_type; \
- info.flags |= METHOD_FLAG_VARARG; \
- _register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \
- }
+#define RET(m_type) \
+ PropertyInfo(Variant::m_type, "")
-#define REGISTER_VARIANT_FUNC(m_func, m_is_const, ...) \
- { \
- String name(#m_func); \
- if (name.begins_with("_")) { \
- name = name.substr(1, name.length() - 1); \
- } \
- MethodInfo info = MethodInfo(name, __VA_ARGS__); \
- info.return_val.type = Variant::NIL; \
- info.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; \
- _register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \
- }
+#define RETVAR \
+ PropertyInfo(Variant::NIL, "", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT)
-#define REGISTER_CLASS_FUNC(m_func, m_is_const, m_return_type, ...) \
- { \
- String name(#m_func); \
- if (name.begins_with("_")) { \
- name = name.substr(1, name.length() - 1); \
- } \
- MethodInfo info = MethodInfo(name, __VA_ARGS__); \
- info.return_val.type = Variant::OBJECT; \
- info.return_val.hint = PROPERTY_HINT_RESOURCE_TYPE; \
- info.return_val.class_name = m_return_type; \
- _register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \
- }
+#define RETCLS(m_class) \
+ PropertyInfo(Variant::OBJECT, "", PROPERTY_HINT_RESOURCE_TYPE, m_class)
-#define REGISTER_FUNC_DEF(m_func, m_is_const, m_default, m_return_type, ...) \
- { \
- String name(#m_func); \
- if (name.begins_with("_")) { \
- name = name.substr(1, name.length() - 1); \
- } \
- MethodInfo info = MethodInfo(name, __VA_ARGS__); \
- info.return_val.type = m_return_type; \
- info.default_arguments.push_back(m_default); \
- _register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \
- }
+#define NOARGS \
+ MethodInfo()
+
+#define ARGS(...) \
+ MethodInfo("", __VA_ARGS__)
#define ARG(m_name, m_type) \
- PropertyInfo(m_type, m_name)
+ PropertyInfo(Variant::m_type, m_name)
+
+#define ARGVAR(m_name) \
+ PropertyInfo(Variant::NIL, m_name, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT)
-#define VARARG(m_name) \
- PropertyInfo(Variant::NIL, m_name, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT)
+#define ARGTYPE(m_name) \
+ PropertyInfo(Variant::INT, m_name, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_CLASS_IS_ENUM, "Variant.Type")
void GDScriptUtilityFunctions::register_functions() {
+ /* clang-format off */
#ifndef DISABLE_DEPRECATED
- REGISTER_VARIANT_FUNC(convert, true, VARARG("what"), ARG("type", Variant::INT));
+ REGISTER_FUNC( convert, true, RETVAR, ARGS( ARGVAR("what"), ARGTYPE("type") ), false, varray( ));
#endif // DISABLE_DEPRECATED
- REGISTER_FUNC(type_exists, true, Variant::BOOL, ARG("type", Variant::STRING_NAME));
- REGISTER_FUNC(_char, true, Variant::STRING, ARG("char", Variant::INT));
- REGISTER_VARARG_FUNC(range, false, Variant::ARRAY);
- REGISTER_CLASS_FUNC(load, false, "Resource", ARG("path", Variant::STRING));
- REGISTER_FUNC(inst_to_dict, false, Variant::DICTIONARY, ARG("instance", Variant::OBJECT));
- REGISTER_FUNC(dict_to_inst, false, Variant::OBJECT, ARG("dictionary", Variant::DICTIONARY));
- REGISTER_FUNC_DEF(Color8, true, 255, Variant::COLOR, ARG("r8", Variant::INT), ARG("g8", Variant::INT), ARG("b8", Variant::INT), ARG("a8", Variant::INT));
- REGISTER_VARARG_FUNC(print_debug, false, Variant::NIL);
- REGISTER_FUNC_NO_ARGS(print_stack, false, Variant::NIL);
- REGISTER_FUNC_NO_ARGS(get_stack, false, Variant::ARRAY);
- REGISTER_FUNC(len, true, Variant::INT, VARARG("var"));
- REGISTER_FUNC(is_instance_of, true, Variant::BOOL, VARARG("value"), VARARG("type"));
+ REGISTER_FUNC( type_exists, true, RET(BOOL), ARGS( ARG("type", STRING_NAME) ), false, varray( ));
+ REGISTER_FUNC( _char, true, RET(STRING), ARGS( ARG("char", INT) ), false, varray( ));
+ REGISTER_FUNC( range, false, RET(ARRAY), NOARGS, true, varray( ));
+ REGISTER_FUNC( load, false, RETCLS("Resource"), ARGS( ARG("path", STRING) ), false, varray( ));
+ REGISTER_FUNC( inst_to_dict, false, RET(DICTIONARY), ARGS( ARG("instance", OBJECT) ), false, varray( ));
+ REGISTER_FUNC( dict_to_inst, false, RET(OBJECT), ARGS( ARG("dictionary", DICTIONARY) ), false, varray( ));
+ REGISTER_FUNC( Color8, true, RET(COLOR), ARGS( ARG("r8", INT), ARG("g8", INT),
+ ARG("b8", INT), ARG("a8", INT) ), false, varray( 255 ));
+ REGISTER_FUNC( print_debug, false, RET(NIL), NOARGS, true, varray( ));
+ REGISTER_FUNC( print_stack, false, RET(NIL), NOARGS, false, varray( ));
+ REGISTER_FUNC( get_stack, false, RET(ARRAY), NOARGS, false, varray( ));
+ REGISTER_FUNC( len, true, RET(INT), ARGS( ARGVAR("var") ), false, varray( ));
+ REGISTER_FUNC( is_instance_of, true, RET(BOOL), ARGS( ARGVAR("value"), ARGVAR("type") ), false, varray( ));
+ /* clang-format on */
}
void GDScriptUtilityFunctions::unregister_functions() {
diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp
index d8139d913a..26c5cfe23c 100644
--- a/modules/gdscript/gdscript_vm.cpp
+++ b/modules/gdscript/gdscript_vm.cpp
@@ -397,32 +397,36 @@ void (*type_init_function_table[])(Variant *) = {
#define OPCODES_OUT \
OPSOUT:
#define OPCODE_SWITCH(m_test) goto *switch_table_ops[m_test];
+
#ifdef DEBUG_ENABLED
#define DISPATCH_OPCODE \
last_opcode = _code_ptr[ip]; \
goto *switch_table_ops[last_opcode]
-#else
+#else // !DEBUG_ENABLED
#define DISPATCH_OPCODE goto *switch_table_ops[_code_ptr[ip]]
-#endif
+#endif // DEBUG_ENABLED
+
#define OPCODE_BREAK goto OPSEXIT
#define OPCODE_OUT goto OPSOUT
-#else
+#else // !(defined(__GNUC__) || defined(__clang__))
#define OPCODES_TABLE
#define OPCODE(m_op) case m_op:
#define OPCODE_WHILE(m_test) while (m_test)
#define OPCODES_END
#define OPCODES_OUT
#define DISPATCH_OPCODE continue
+
#ifdef _MSC_VER
#define OPCODE_SWITCH(m_test) \
__assume(m_test <= OPCODE_END); \
switch (m_test)
-#else
+#else // !_MSC_VER
#define OPCODE_SWITCH(m_test) switch (m_test)
-#endif
+#endif // _MSC_VER
+
#define OPCODE_BREAK break
#define OPCODE_OUT break
-#endif
+#endif // defined(__GNUC__) || defined(__clang__)
// Helpers for VariantInternal methods in macros.
#define OP_GET_BOOL get_bool
@@ -663,7 +667,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
OPCODE_BREAK; \
}
-#else
+#else // !DEBUG_ENABLED
#define GD_ERR_BREAK(m_cond)
#define CHECK_SPACE(m_space)
@@ -676,7 +680,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
OPCODE_BREAK; \
}
-#endif
+#endif // DEBUG_ENABLED
#define LOAD_INSTRUCTION_ARGS \
int instr_arg_count = _code_ptr[ip + 1]; \
@@ -1965,7 +1969,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
err_text = _get_call_error("function '" + methodstr + (is_callable ? "" : "' in base '" + basestr) + "'", (const Variant **)argptrs, temp_ret, err);
OPCODE_BREAK;
}
-#endif
+#endif // DEBUG_ENABLED
ip += 3;
}
diff --git a/modules/gdscript/tests/scripts/parser/errors/annotation_inapplicable.gd b/modules/gdscript/tests/scripts/parser/errors/annotation_inapplicable.gd
new file mode 100644
index 0000000000..cfacb6a010
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/annotation_inapplicable.gd
@@ -0,0 +1,3 @@
+@export
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/parser/errors/annotation_inapplicable.out b/modules/gdscript/tests/scripts/parser/errors/annotation_inapplicable.out
new file mode 100644
index 0000000000..ed677cd39a
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/annotation_inapplicable.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Annotation "@export" cannot be applied to a function.
diff --git a/modules/gdscript/tests/scripts/parser/errors/annotation_unrecognized.gd b/modules/gdscript/tests/scripts/parser/errors/annotation_unrecognized.gd
new file mode 100644
index 0000000000..a6b171a428
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/annotation_unrecognized.gd
@@ -0,0 +1,3 @@
+@hello_world
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/parser/errors/annotation_unrecognized.out b/modules/gdscript/tests/scripts/parser/errors/annotation_unrecognized.out
new file mode 100644
index 0000000000..540e66f15b
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/annotation_unrecognized.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Unrecognized annotation: "@hello_world".
diff --git a/modules/gdscript/tests/scripts/runtime/features/gdscript_utility_implicit_conversion.gd b/modules/gdscript/tests/scripts/runtime/features/gdscript_utility_implicit_conversion.gd
new file mode 100644
index 0000000000..59bdb6eceb
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/gdscript_utility_implicit_conversion.gd
@@ -0,0 +1,12 @@
+func test():
+ const COLOR = Color8(255, 0.0, false)
+ var false_value := false
+ @warning_ignore("narrowing_conversion")
+ var color = Color8(255, 0.0, false_value)
+ print(var_to_str(COLOR))
+ print(var_to_str(color))
+
+ var string := "Node"
+ var string_name := &"Node"
+ print(type_exists(string))
+ print(type_exists(string_name))
diff --git a/modules/gdscript/tests/scripts/runtime/features/gdscript_utility_implicit_conversion.out b/modules/gdscript/tests/scripts/runtime/features/gdscript_utility_implicit_conversion.out
new file mode 100644
index 0000000000..00913faa49
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/gdscript_utility_implicit_conversion.out
@@ -0,0 +1,5 @@
+GDTEST_OK
+Color(1, 0, 0, 1)
+Color(1, 0, 0, 1)
+true
+true
diff --git a/modules/gdscript/tests/scripts/runtime/features/native_static_method_as_callable.gd b/modules/gdscript/tests/scripts/runtime/features/native_static_method_as_callable.gd
new file mode 100644
index 0000000000..63d5935d1e
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/native_static_method_as_callable.gd
@@ -0,0 +1,8 @@
+func get_parse_string(t: Variant):
+ return t.parse_string
+
+func test():
+ var a: Callable = JSON.parse_string
+ var b: Callable = get_parse_string(JSON)
+ prints(a.call("{\"test\": \"a\"}"), a.is_valid())
+ prints(b.call("{\"test\": \"b\"}"), b.is_valid())
diff --git a/modules/gdscript/tests/scripts/runtime/features/native_static_method_as_callable.out b/modules/gdscript/tests/scripts/runtime/features/native_static_method_as_callable.out
new file mode 100644
index 0000000000..a2cb4b9a07
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/native_static_method_as_callable.out
@@ -0,0 +1,3 @@
+GDTEST_OK
+{ "test": "a" } false
+{ "test": "b" } false