summaryrefslogtreecommitdiffstats
path: root/modules/gdscript
diff options
context:
space:
mode:
Diffstat (limited to 'modules/gdscript')
-rw-r--r--modules/gdscript/doc_classes/@GDScript.xml12
-rw-r--r--modules/gdscript/editor/gdscript_docgen.cpp1
-rw-r--r--modules/gdscript/editor/gdscript_docgen.h1
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.cpp2
-rw-r--r--modules/gdscript/editor/gdscript_translation_parser_plugin.cpp11
-rw-r--r--modules/gdscript/editor/gdscript_translation_parser_plugin.h3
-rw-r--r--modules/gdscript/gdscript.cpp317
-rw-r--r--modules/gdscript/gdscript.h4
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp117
-rw-r--r--modules/gdscript/gdscript_analyzer.h9
-rw-r--r--modules/gdscript/gdscript_byte_codegen.cpp17
-rw-r--r--modules/gdscript/gdscript_byte_codegen.h5
-rw-r--r--modules/gdscript/gdscript_cache.cpp11
-rw-r--r--modules/gdscript/gdscript_cache.h3
-rw-r--r--modules/gdscript/gdscript_codegen.h8
-rw-r--r--modules/gdscript/gdscript_compiler.cpp249
-rw-r--r--modules/gdscript/gdscript_compiler.h3
-rw-r--r--modules/gdscript/gdscript_disassembler.cpp34
-rw-r--r--modules/gdscript/gdscript_editor.cpp58
-rw-r--r--modules/gdscript/gdscript_function.h10
-rw-r--r--modules/gdscript/gdscript_lambda_callable.cpp3
-rw-r--r--modules/gdscript/gdscript_parser.cpp333
-rw-r--r--modules/gdscript/gdscript_parser.h12
-rw-r--r--modules/gdscript/gdscript_tokenizer.cpp81
-rw-r--r--modules/gdscript/gdscript_tokenizer.h2
-rw-r--r--modules/gdscript/gdscript_utility_functions.cpp3
-rw-r--r--modules/gdscript/gdscript_vm.cpp52
-rw-r--r--modules/gdscript/language_server/gdscript_extend_parser.h3
-rw-r--r--modules/gdscript/language_server/gdscript_language_protocol.h7
-rw-r--r--modules/gdscript/language_server/gdscript_language_server.h3
-rw-r--r--modules/gdscript/language_server/gdscript_text_document.cpp5
-rw-r--r--modules/gdscript/language_server/gdscript_text_document.h3
-rw-r--r--modules/gdscript/language_server/gdscript_workspace.cpp3
-rw-r--r--modules/gdscript/language_server/gdscript_workspace.h5
-rw-r--r--modules/gdscript/register_types.cpp40
-rw-r--r--modules/gdscript/tests/gdscript_test_runner.cpp18
-rw-r--r--modules/gdscript/tests/gdscript_test_runner.h1
-rw-r--r--modules/gdscript/tests/gdscript_test_runner_suite.h1
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static.gd8
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/static_var_export_annotation.gd8
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/static_var_export_annotation.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/virtual_method_not_implemented.gd2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/virtual_method_not_implemented.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/virtual_super_not_implemented.gd5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/virtual_super_not_implemented.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/boolean_operators_for_all_types.gd347
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/boolean_operators_for_all_types.out229
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/virtual_method_implemented.gd11
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/virtual_method_implemented.out1
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/virtual_super_implemented.gd10
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/virtual_super_implemented.out1
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/multiple_number_separators.gd2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/multiple_number_separators.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/multiple_number_separators_after_decimal.gd3
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/multiple_number_separators_after_decimal.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/number_literals_with_sign.gd17
-rw-r--r--modules/gdscript/tests/scripts/parser/features/number_literals_with_sign.out16
-rw-r--r--modules/gdscript/tests/scripts/parser/features/number_separators.gd32
-rw-r--r--modules/gdscript/tests/scripts/parser/features/number_separators.out23
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_wrong_to_typed.gd7
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_wrong_to_typed.out4
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/ctor_as_callable.gd10
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/ctor_as_callable.out3
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/match_string_stringname_equivalent.gd14
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/match_string_stringname_equivalent.out3
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/static_access_via_instance.gd58
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/static_access_via_instance.out25
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/static_func_as_callable.gd17
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/static_func_as_callable.out3
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/static_variables.gd18
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/static_variables.out18
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/static_variables_2.gd56
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/static_variables_2.out15
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.gd28
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.out4
-rw-r--r--modules/gdscript/tests/test_gdscript.cpp10
-rw-r--r--modules/gdscript/tests/test_gdscript.h1
79 files changed, 1969 insertions, 506 deletions
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml
index e3f5502391..f2a65451a7 100644
--- a/modules/gdscript/doc_classes/@GDScript.xml
+++ b/modules/gdscript/doc_classes/@GDScript.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="@GDScript" version="4.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
- Built-in GDScript functions.
+ Built-in GDScript constants, functions, and annotations.
</brief_description>
<description>
A list of GDScript-specific utility functions and annotations accessible from any script.
@@ -274,7 +274,7 @@
[b]Warning:[/b] Numeric infinity is only a concept with floating-point numbers, and has no equivalent for integers. Dividing an integer number by [code]0[/code] will not result in [constant INF] and will result in a run-time error instead.
</constant>
<constant name="NAN" value="nan">
- "Not a Number", an invalid floating-point value. [constant NAN] has special properties, including that it is not equal to itself ([code]NAN == NAN[/code] returns [code]false[/code]). It is output by some invalid operations, such as dividing floating-point [code]0.0[/code] by [code]0.0[/code].
+ "Not a Number", an invalid floating-point value. [constant NAN] has special properties, including that [code]!=[/code] always returns [code]true[/code], while other comparison operators always return [code]false[/code]. This is true even when comparing with itself ([code]NAN == NAN[/code] returns [code]false[/code] and [code]NAN != NAN[/code] returns [code]true[/code]). It is returned by some invalid operations, such as dividing floating-point [code]0.0[/code] by [code]0.0[/code].
[b]Warning:[/b] "Not a Number" is only a concept with floating-point numbers, and has no equivalent for integers. Dividing an integer [code]0[/code] by [code]0[/code] will not result in [constant NAN] and will result in a run-time error instead.
</constant>
</constants>
@@ -612,7 +612,7 @@
[/codeblock]
</description>
</annotation>
- <annotation name="@rpc" qualifiers="vararg">
+ <annotation name="@rpc">
<return type="void" />
<param index="0" name="mode" type="String" default="&quot;authority&quot;" />
<param index="1" name="sync" type="String" default="&quot;call_remote&quot;" />
@@ -620,7 +620,11 @@
<param index="3" name="transfer_channel" type="int" default="0" />
<description>
Mark the following method for remote procedure calls. See [url=$DOCS_URL/tutorials/networking/high_level_multiplayer.html]High-level multiplayer[/url].
- The order of [param mode], [param sync] and [param transfer_mode] does not matter and all arguments can be omitted, but [param transfer_channel] always has to be the last argument. The accepted values for [param mode] are [code]"any_peer"[/code] or [code]"authority"[/code], for [param sync] are [code]"call_remote"[/code] or [code]"call_local"[/code] and for [param transfer_mode] are [code]"unreliable"[/code], [code]"unreliable_ordered"[/code] or [code]"reliable"[/code].
+ The accepted values:
+ - for [param mode] are [code]"any_peer"[/code] or [code]"authority"[/code];
+ - for [param sync] are [code]"call_remote"[/code] or [code]"call_local"[/code];
+ - and for [param transfer_mode] are [code]"unreliable"[/code], [code]"unreliable_ordered"[/code], or [code]"reliable"[/code].
+ The order of [param mode], [param sync] and [param transfer_mode] does not matter, but values related to the same argument must not be used more than once. [param transfer_channel] always has to be the 4th argument (you must specify 3 preceding arguments).
[codeblock]
@rpc
func fn(): pass
diff --git a/modules/gdscript/editor/gdscript_docgen.cpp b/modules/gdscript/editor/gdscript_docgen.cpp
index ce64d79747..df17581ad1 100644
--- a/modules/gdscript/editor/gdscript_docgen.cpp
+++ b/modules/gdscript/editor/gdscript_docgen.cpp
@@ -29,6 +29,7 @@
/**************************************************************************/
#include "gdscript_docgen.h"
+
#include "../gdscript.h"
using GDP = GDScriptParser;
diff --git a/modules/gdscript/editor/gdscript_docgen.h b/modules/gdscript/editor/gdscript_docgen.h
index bb3647196a..3357fb680c 100644
--- a/modules/gdscript/editor/gdscript_docgen.h
+++ b/modules/gdscript/editor/gdscript_docgen.h
@@ -32,6 +32,7 @@
#define GDSCRIPT_DOCGEN_H
#include "../gdscript_parser.h"
+
#include "core/doc_data.h"
class GDScriptDocGen {
diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp
index bba11363d5..b54dc502ae 100644
--- a/modules/gdscript/editor/gdscript_highlighter.cpp
+++ b/modules/gdscript/editor/gdscript_highlighter.cpp
@@ -29,8 +29,10 @@
/**************************************************************************/
#include "gdscript_highlighter.h"
+
#include "../gdscript.h"
#include "../gdscript_tokenizer.h"
+
#include "core/config/project_settings.h"
#include "editor/editor_settings.h"
diff --git a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp
index 00d50d1737..e17e748d7b 100644
--- a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp
+++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp
@@ -30,8 +30,9 @@
#include "gdscript_translation_parser_plugin.h"
+#include "../gdscript.h"
+
#include "core/io/resource_loader.h"
-#include "modules/gdscript/gdscript.h"
void GDScriptEditorTranslationParserPlugin::get_recognized_extensions(List<String> *r_extensions) const {
GDScriptLanguage::get_singleton()->get_recognized_extensions(r_extensions);
@@ -311,6 +312,14 @@ void GDScriptEditorTranslationParserPlugin::_extract_from_call(GDScriptParser::C
}
}
}
+
+ if (p_call->callee && p_call->callee->type == GDScriptParser::Node::SUBSCRIPT) {
+ GDScriptParser::SubscriptNode *subscript_node = static_cast<GDScriptParser::SubscriptNode *>(p_call->callee);
+ if (subscript_node->base && subscript_node->base->type == GDScriptParser::Node::CALL) {
+ GDScriptParser::CallNode *call_node = static_cast<GDScriptParser::CallNode *>(subscript_node->base);
+ _extract_from_call(call_node);
+ }
+ }
}
void GDScriptEditorTranslationParserPlugin::_extract_fd_literals(GDScriptParser::ExpressionNode *p_expression) {
diff --git a/modules/gdscript/editor/gdscript_translation_parser_plugin.h b/modules/gdscript/editor/gdscript_translation_parser_plugin.h
index 7e6e381e3f..421030e49a 100644
--- a/modules/gdscript/editor/gdscript_translation_parser_plugin.h
+++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.h
@@ -31,9 +31,10 @@
#ifndef GDSCRIPT_TRANSLATION_PARSER_PLUGIN_H
#define GDSCRIPT_TRANSLATION_PARSER_PLUGIN_H
+#include "../gdscript_parser.h"
+
#include "core/templates/hash_set.h"
#include "editor/editor_translation_parser.h"
-#include "modules/gdscript/gdscript_parser.h"
class GDScriptEditorTranslationParserPlugin : public EditorTranslationParserPlugin {
GDCLASS(GDScriptEditorTranslationParserPlugin, EditorTranslationParserPlugin);
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index d0790aba25..7a3b9dc597 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -30,15 +30,6 @@
#include "gdscript.h"
-#include <stdint.h>
-
-#include "core/config/engine.h"
-#include "core/config/project_settings.h"
-#include "core/core_constants.h"
-#include "core/core_string_names.h"
-#include "core/io/file_access.h"
-#include "core/io/file_access_encrypted.h"
-#include "core/os/os.h"
#include "gdscript_analyzer.h"
#include "gdscript_cache.h"
#include "gdscript_compiler.h"
@@ -46,15 +37,28 @@
#include "gdscript_rpc_callable.h"
#include "gdscript_warning.h"
+#ifdef TOOLS_ENABLED
+#include "editor/gdscript_docgen.h"
+#endif
+
#ifdef TESTS_ENABLED
#include "tests/gdscript_test_runner.h"
#endif
+#include "core/config/engine.h"
+#include "core/config/project_settings.h"
+#include "core/core_constants.h"
+#include "core/core_string_names.h"
+#include "core/io/file_access.h"
+#include "core/io/file_access_encrypted.h"
+#include "core/os/os.h"
+
#ifdef TOOLS_ENABLED
#include "editor/editor_paths.h"
-#include "editor/gdscript_docgen.h"
#endif
+#include <stdint.h>
+
///////////////////////////
GDScriptNativeClass::GDScriptNativeClass(const StringName &p_name) {
@@ -874,44 +878,55 @@ Variant GDScript::callp(const StringName &p_method, const Variant **p_args, int
}
bool GDScript::_get(const StringName &p_name, Variant &r_ret) const {
- {
- const GDScript *top = this;
- while (top) {
- {
- HashMap<StringName, Variant>::ConstIterator E = top->constants.find(p_name);
- if (E) {
- r_ret = E->value;
- return true;
- }
+ if (p_name == GDScriptLanguage::get_singleton()->strings._script_source) {
+ r_ret = get_source_code();
+ return true;
+ }
+
+ const GDScript *top = this;
+ while (top) {
+ {
+ HashMap<StringName, Variant>::ConstIterator E = top->constants.find(p_name);
+ if (E) {
+ r_ret = E->value;
+ return true;
}
+ }
- {
- HashMap<StringName, Ref<GDScript>>::ConstIterator E = subclasses.find(p_name);
- if (E) {
- r_ret = E->value;
+ {
+ HashMap<StringName, MemberInfo>::ConstIterator E = top->static_variables_indices.find(p_name);
+ if (E) {
+ if (E->value.getter) {
+ Callable::CallError ce;
+ r_ret = const_cast<GDScript *>(this)->callp(E->value.getter, nullptr, 0, ce);
return true;
}
+ r_ret = top->static_variables[E->value.index];
+ return true;
}
+ }
- {
- HashMap<StringName, MemberInfo>::ConstIterator E = static_variables_indices.find(p_name);
- if (E) {
- if (E->value.getter) {
- Callable::CallError ce;
- r_ret = const_cast<GDScript *>(this)->callp(E->value.getter, nullptr, 0, ce);
- return true;
- }
- r_ret = static_variables[E->value.index];
- return true;
+ {
+ HashMap<StringName, GDScriptFunction *>::ConstIterator E = top->member_functions.find(p_name);
+ if (E && E->value->is_static()) {
+ if (top->rpc_config.has(p_name)) {
+ r_ret = Callable(memnew(GDScriptRPCCallable(const_cast<GDScript *>(top), E->key)));
+ } else {
+ r_ret = Callable(const_cast<GDScript *>(top), E->key);
}
+ return true;
}
- top = top->_base;
}
- if (p_name == GDScriptLanguage::get_singleton()->strings._script_source) {
- r_ret = get_source_code();
- return true;
+ {
+ HashMap<StringName, Ref<GDScript>>::ConstIterator E = top->subclasses.find(p_name);
+ if (E) {
+ r_ret = E->value;
+ return true;
+ }
}
+
+ top = top->_base;
}
return false;
@@ -921,40 +936,60 @@ bool GDScript::_set(const StringName &p_name, const Variant &p_value) {
if (p_name == GDScriptLanguage::get_singleton()->strings._script_source) {
set_source_code(p_value);
reload();
- } else {
- const GDScript *top = this;
- while (top) {
- HashMap<StringName, MemberInfo>::ConstIterator E = static_variables_indices.find(p_name);
- if (E) {
- const GDScript::MemberInfo *member = &E->value;
- Variant value = p_value;
- if (member->data_type.has_type && !member->data_type.is_type(value)) {
- const Variant *args = &p_value;
- Callable::CallError err;
- Variant::construct(member->data_type.builtin_type, value, &args, 1, err);
- if (err.error != Callable::CallError::CALL_OK || !member->data_type.is_type(value)) {
- return false;
- }
- }
- if (member->setter) {
- const Variant *args = &value;
- Callable::CallError err;
- callp(member->setter, &args, 1, err);
- return err.error == Callable::CallError::CALL_OK;
- } else {
- static_variables.write[member->index] = value;
- return true;
+ return true;
+ }
+
+ GDScript *top = this;
+ while (top) {
+ HashMap<StringName, MemberInfo>::ConstIterator E = top->static_variables_indices.find(p_name);
+ if (E) {
+ const MemberInfo *member = &E->value;
+ Variant value = p_value;
+ if (member->data_type.has_type && !member->data_type.is_type(value)) {
+ const Variant *args = &p_value;
+ Callable::CallError err;
+ Variant::construct(member->data_type.builtin_type, value, &args, 1, err);
+ if (err.error != Callable::CallError::CALL_OK || !member->data_type.is_type(value)) {
+ return false;
}
}
- top = top->_base;
+ if (member->setter) {
+ const Variant *args = &value;
+ Callable::CallError err;
+ callp(member->setter, &args, 1, err);
+ return err.error == Callable::CallError::CALL_OK;
+ } else {
+ top->static_variables.write[member->index] = value;
+ return true;
+ }
}
+
+ top = top->_base;
}
- return true;
+ return false;
}
void GDScript::_get_property_list(List<PropertyInfo> *p_properties) const {
p_properties->push_back(PropertyInfo(Variant::STRING, "script/source", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
+
+ List<PropertyInfo> property_list;
+
+ const GDScript *top = this;
+ while (top) {
+ for (const KeyValue<StringName, MemberInfo> &E : top->static_variables_indices) {
+ PropertyInfo pi = PropertyInfo(E.value.data_type);
+ pi.name = E.key;
+ pi.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE; // For the script (as a class) it is a non-static property.
+ property_list.push_back(pi);
+ }
+
+ top = top->_base;
+ }
+
+ for (const List<PropertyInfo>::Element *E = property_list.back(); E; E = E->prev()) {
+ p_properties->push_back(E->get());
+ }
}
void GDScript::_bind_methods() {
@@ -1033,6 +1068,16 @@ StringName GDScript::debug_get_member_by_index(int p_idx) const {
return "<error>";
}
+StringName GDScript::debug_get_static_var_by_index(int p_idx) const {
+ for (const KeyValue<StringName, MemberInfo> &E : static_variables_indices) {
+ if (E.value.index == p_idx) {
+ return E.key;
+ }
+ }
+
+ return "<error>";
+}
+
Ref<GDScript> GDScript::get_base() const {
return base;
}
@@ -1372,8 +1417,8 @@ void GDScript::clear(GDScript::ClearData *p_clear_data) {
}
clearing = true;
- GDScript::ClearData data;
- GDScript::ClearData *clear_data = p_clear_data;
+ ClearData data;
+ ClearData *clear_data = p_clear_data;
bool is_root = false;
// If `clear_data` is `nullptr`, it means that it's the root.
@@ -1394,12 +1439,12 @@ void GDScript::clear(GDScript::ClearData *p_clear_data) {
}
member_functions.clear();
- for (KeyValue<StringName, GDScript::MemberInfo> &E : member_indices) {
+ for (KeyValue<StringName, MemberInfo> &E : member_indices) {
clear_data->scripts.insert(E.value.data_type.script_type_ref);
E.value.data_type.script_type_ref = Ref<Script>();
}
- for (KeyValue<StringName, GDScript::MemberInfo> &E : static_variables_indices) {
+ for (KeyValue<StringName, MemberInfo> &E : static_variables_indices) {
clear_data->scripts.insert(E.value.data_type.script_type_ref);
E.value.data_type.script_type_ref = Ref<Script>();
}
@@ -1482,7 +1527,6 @@ GDScript::~GDScript() {
//////////////////////////////
bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) {
- //member
{
HashMap<StringName, GDScript::MemberInfo>::Iterator E = script->member_indices.find(p_name);
if (E) {
@@ -1510,17 +1554,45 @@ bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) {
GDScript *sptr = script.ptr();
while (sptr) {
- HashMap<StringName, GDScriptFunction *>::Iterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._set);
- if (E) {
- Variant name = p_name;
- const Variant *args[2] = { &name, &p_value };
+ {
+ HashMap<StringName, GDScript::MemberInfo>::ConstIterator E = sptr->static_variables_indices.find(p_name);
+ if (E) {
+ const GDScript::MemberInfo *member = &E->value;
+ Variant value = p_value;
+ if (member->data_type.has_type && !member->data_type.is_type(value)) {
+ const Variant *args = &p_value;
+ Callable::CallError err;
+ Variant::construct(member->data_type.builtin_type, value, &args, 1, err);
+ if (err.error != Callable::CallError::CALL_OK || !member->data_type.is_type(value)) {
+ return false;
+ }
+ }
+ if (member->setter) {
+ const Variant *args = &value;
+ Callable::CallError err;
+ callp(member->setter, &args, 1, err);
+ return err.error == Callable::CallError::CALL_OK;
+ } else {
+ sptr->static_variables.write[member->index] = value;
+ return true;
+ }
+ }
+ }
- Callable::CallError err;
- Variant ret = E->value->call(this, (const Variant **)args, 2, err);
- if (err.error == Callable::CallError::CALL_OK && ret.get_type() == Variant::BOOL && ret.operator bool()) {
- return true;
+ {
+ HashMap<StringName, GDScriptFunction *>::Iterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._set);
+ if (E) {
+ Variant name = p_name;
+ const Variant *args[2] = { &name, &p_value };
+
+ Callable::CallError err;
+ Variant ret = E->value->call(this, (const Variant **)args, 2, err);
+ if (err.error == Callable::CallError::CALL_OK && ret.get_type() == Variant::BOOL && ret.operator bool()) {
+ return true;
+ }
}
}
+
sptr = sptr->_base;
}
@@ -1528,62 +1600,69 @@ bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) {
}
bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const {
+ {
+ HashMap<StringName, GDScript::MemberInfo>::ConstIterator E = script->member_indices.find(p_name);
+ if (E) {
+ if (E->value.getter) {
+ Callable::CallError err;
+ r_ret = const_cast<GDScriptInstance *>(this)->callp(E->value.getter, nullptr, 0, err);
+ if (err.error == Callable::CallError::CALL_OK) {
+ return true;
+ }
+ }
+ r_ret = members[E->value.index];
+ return true;
+ }
+ }
+
const GDScript *sptr = script.ptr();
while (sptr) {
{
- HashMap<StringName, GDScript::MemberInfo>::ConstIterator E = script->member_indices.find(p_name);
+ HashMap<StringName, Variant>::ConstIterator E = sptr->constants.find(p_name);
+ if (E) {
+ r_ret = E->value;
+ return true;
+ }
+ }
+
+ {
+ HashMap<StringName, GDScript::MemberInfo>::ConstIterator E = sptr->static_variables_indices.find(p_name);
if (E) {
if (E->value.getter) {
- Callable::CallError err;
- r_ret = const_cast<GDScriptInstance *>(this)->callp(E->value.getter, nullptr, 0, err);
- if (err.error == Callable::CallError::CALL_OK) {
- return true;
- }
+ Callable::CallError ce;
+ r_ret = const_cast<GDScript *>(sptr)->callp(E->value.getter, nullptr, 0, ce);
+ return true;
}
- r_ret = members[E->value.index];
- return true; //index found
+ r_ret = sptr->static_variables[E->value.index];
+ return true;
}
}
{
- const GDScript *sl = sptr;
- while (sl) {
- HashMap<StringName, Variant>::ConstIterator E = sl->constants.find(p_name);
- if (E) {
- r_ret = E->value;
- return true; //index found
- }
- sl = sl->_base;
+ HashMap<StringName, Vector<StringName>>::ConstIterator E = sptr->_signals.find(p_name);
+ if (E) {
+ r_ret = Signal(this->owner, E->key);
+ return true;
}
}
{
- // Signals.
- const GDScript *sl = sptr;
- while (sl) {
- HashMap<StringName, Vector<StringName>>::ConstIterator E = sl->_signals.find(p_name);
- if (E) {
- r_ret = Signal(this->owner, E->key);
- return true; //index found
+ HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(p_name);
+ if (E) {
+ if (sptr->rpc_config.has(p_name)) {
+ r_ret = Callable(memnew(GDScriptRPCCallable(this->owner, E->key)));
+ } else {
+ r_ret = Callable(this->owner, E->key);
}
- sl = sl->_base;
+ return true;
}
}
{
- // Methods.
- const GDScript *sl = sptr;
- while (sl) {
- HashMap<StringName, GDScriptFunction *>::ConstIterator E = sl->member_functions.find(p_name);
- if (E) {
- if (sptr->rpc_config.has(p_name)) {
- r_ret = Callable(memnew(GDScriptRPCCallable(this->owner, E->key)));
- } else {
- r_ret = Callable(this->owner, E->key);
- }
- return true; //index found
- }
- sl = sl->_base;
+ HashMap<StringName, Ref<GDScript>>::ConstIterator E = sptr->subclasses.find(p_name);
+ if (E) {
+ r_ret = E->value;
+ return true;
}
}
@@ -2025,6 +2104,8 @@ void GDScriptLanguage::finish() {
}
s = s->next();
}
+ script_list.clear();
+ function_list.clear();
}
void GDScriptLanguage::profiling_start() {
@@ -2590,20 +2671,12 @@ Ref<GDScript> GDScriptLanguage::get_script_by_fully_qualified_name(const String
/*************** RESOURCE ***************/
Ref<Resource> ResourceFormatLoaderGDScript::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) {
- if (r_error) {
- *r_error = ERR_FILE_CANT_OPEN;
- }
-
Error err;
Ref<GDScript> scr = GDScriptCache::get_full_script(p_path, err, "", p_cache_mode == CACHE_MODE_IGNORE);
- if (scr.is_null()) {
- // Don't fail loading because of parsing error.
- scr.instantiate();
- }
-
if (r_error) {
- *r_error = OK;
+ // Don't fail loading because of parsing error.
+ *r_error = scr.is_valid() ? OK : err;
}
return scr;
diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h
index 60bd9eef53..9cf545c41d 100644
--- a/modules/gdscript/gdscript.h
+++ b/modules/gdscript/gdscript.h
@@ -31,6 +31,8 @@
#ifndef GDSCRIPT_H
#define GDSCRIPT_H
+#include "gdscript_function.h"
+
#include "core/debugger/engine_debugger.h"
#include "core/debugger/script_debugger.h"
#include "core/doc_data.h"
@@ -38,7 +40,6 @@
#include "core/io/resource_saver.h"
#include "core/object/script_language.h"
#include "core/templates/rb_set.h"
-#include "gdscript_function.h"
class GDScriptNativeClass : public RefCounted {
GDCLASS(GDScriptNativeClass, RefCounted);
@@ -226,6 +227,7 @@ public:
const HashMap<StringName, MemberInfo> &debug_get_member_indices() const { return member_indices; }
const HashMap<StringName, GDScriptFunction *> &debug_get_member_functions() const; //this is debug only
StringName debug_get_member_by_index(int p_idx) const;
+ StringName debug_get_static_var_by_index(int p_idx) const;
Variant _new(const Variant **p_args, int p_argcount, Callable::CallError &r_error);
virtual bool can_instantiate() const override;
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index 96bd8aafad..e1033c2aab 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -30,6 +30,9 @@
#include "gdscript_analyzer.h"
+#include "gdscript.h"
+#include "gdscript_utility_functions.h"
+
#include "core/config/engine.h"
#include "core/config/project_settings.h"
#include "core/core_constants.h"
@@ -39,8 +42,6 @@
#include "core/object/class_db.h"
#include "core/object/script_language.h"
#include "core/templates/hash_map.h"
-#include "gdscript.h"
-#include "gdscript_utility_functions.h"
#include "scene/resources/packed_scene.h"
#if defined(TOOLS_ENABLED) && !defined(DISABLE_DEPRECATED)
@@ -1610,11 +1611,10 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *
GDScriptParser::DataType parent_return_type;
List<GDScriptParser::DataType> parameters_types;
int default_par_count = 0;
- bool is_static = false;
- bool is_vararg = false;
+ BitField<MethodFlags> method_flags;
StringName native_base;
- if (!p_is_lambda && get_function_signature(p_function, false, base_type, function_name, parent_return_type, parameters_types, default_par_count, is_static, is_vararg, &native_base)) {
- bool valid = p_function->is_static == is_static;
+ if (!p_is_lambda && get_function_signature(p_function, false, base_type, function_name, parent_return_type, parameters_types, default_par_count, method_flags, &native_base)) {
+ bool valid = p_function->is_static == method_flags.has_flag(METHOD_FLAG_STATIC);
valid = valid && parent_return_type == p_function->get_datatype();
int par_count_diff = p_function->parameters.size() - parameters_types.size();
@@ -1692,6 +1692,9 @@ void GDScriptAnalyzer::resolve_function_body(GDScriptParser::FunctionNode *p_fun
GDScriptParser::FunctionNode *previous_function = parser->current_function;
parser->current_function = p_function;
+ bool previous_static_context = static_context;
+ static_context = p_function->is_static;
+
resolve_suite(p_function->body);
if (!p_function->get_datatype().is_hard_type() && p_function->body->get_datatype().is_set()) {
@@ -1707,6 +1710,7 @@ void GDScriptAnalyzer::resolve_function_body(GDScriptParser::FunctionNode *p_fun
parser->ignored_warnings = previously_ignored_warnings;
#endif
parser->current_function = previous_function;
+ static_context = previous_static_context;
}
void GDScriptAnalyzer::decide_suite_type(GDScriptParser::Node *p_suite, GDScriptParser::Node *p_statement) {
@@ -2025,9 +2029,8 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) {
GDScriptParser::DataType return_type;
List<GDScriptParser::DataType> par_types;
int default_arg_count = 0;
- bool is_static = false;
- bool is_vararg = false;
- if (get_function_signature(p_for->list, false, list_type, CoreStringNames::get_singleton()->_iter_get, return_type, par_types, default_arg_count, is_static, is_vararg)) {
+ BitField<MethodFlags> method_flags;
+ if (get_function_signature(p_for->list, false, list_type, CoreStringNames::get_singleton()->_iter_get, return_type, par_types, default_arg_count, method_flags)) {
variable_type = return_type;
variable_type.type_source = list_type.type_source;
} else if (!list_type.is_hard_type()) {
@@ -2442,6 +2445,10 @@ void GDScriptAnalyzer::update_array_literal_element_type(GDScriptParser::ArrayNo
continue;
}
if (!is_type_compatible(p_element_type, element_type, true, p_array)) {
+ if (is_type_compatible(element_type, p_element_type)) {
+ mark_node_unsafe(element_node);
+ continue;
+ }
push_error(vformat(R"(Cannot have an element of type "%s" in an array of type "Array[%s]".)", element_type.to_string(), p_element_type.to_string()), element_node);
return;
}
@@ -2462,9 +2469,15 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
GDScriptParser::DataType assignee_type = p_assignment->assignee->get_datatype();
- if (assignee_type.is_constant || (p_assignment->assignee->type == GDScriptParser::Node::SUBSCRIPT && static_cast<GDScriptParser::SubscriptNode *>(p_assignment->assignee)->base->is_constant)) {
+ if (assignee_type.is_constant) {
push_error("Cannot assign a new value to a constant.", p_assignment->assignee);
return;
+ } else if (p_assignment->assignee->type == GDScriptParser::Node::SUBSCRIPT && static_cast<GDScriptParser::SubscriptNode *>(p_assignment->assignee)->base->is_constant) {
+ const GDScriptParser::DataType &base_type = static_cast<GDScriptParser::SubscriptNode *>(p_assignment->assignee)->base->datatype;
+ if (base_type.kind != GDScriptParser::DataType::SCRIPT && base_type.kind != GDScriptParser::DataType::CLASS) { // Static variables.
+ push_error("Cannot assign a new value to a constant.", p_assignment->assignee);
+ return;
+ }
} else if (assignee_type.is_read_only) {
push_error("Cannot assign a new value to a read-only property.", p_assignment->assignee);
return;
@@ -3076,15 +3089,24 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
return;
}
- bool is_static = false;
- bool is_vararg = false;
int default_arg_count = 0;
+ BitField<MethodFlags> method_flags;
GDScriptParser::DataType return_type;
List<GDScriptParser::DataType> par_types;
bool is_constructor = (base_type.is_meta_type || (p_call->callee && p_call->callee->type == GDScriptParser::Node::IDENTIFIER)) && p_call->function_name == SNAME("new");
- if (get_function_signature(p_call, is_constructor, base_type, p_call->function_name, return_type, par_types, default_arg_count, is_static, is_vararg)) {
+ if (get_function_signature(p_call, is_constructor, base_type, p_call->function_name, return_type, par_types, default_arg_count, method_flags)) {
+ // If the method is implemented in the class hierarchy, the virtual flag will not be set for that MethodInfo and the search stops there.
+ // MethodInfo's above the class that defines the method might still have the virtual flag set.
+ if (method_flags.has_flag(METHOD_FLAG_VIRTUAL)) {
+ if (p_call->is_super) {
+ push_error(vformat(R"*(Cannot call the parent class' virtual function "%s()" because it hasn't been defined.)*", p_call->function_name), p_call);
+ } else {
+ push_error(vformat(R"*(Cannot call virtual function "%s()" because it hasn't been defined.)*", p_call->function_name), p_call);
+ }
+ }
+
// If the function require typed arrays we must make literals be typed.
for (const KeyValue<int, GDScriptParser::ArrayNode *> &E : arrays) {
int index = E.key;
@@ -3092,14 +3114,14 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
update_array_literal_element_type(E.value, par_types[index].get_container_element_type());
}
}
- validate_call_arg(par_types, default_arg_count, is_vararg, p_call);
+ validate_call_arg(par_types, default_arg_count, method_flags.has_flag(METHOD_FLAG_VARARG), p_call);
if (base_type.kind == GDScriptParser::DataType::ENUM && base_type.is_meta_type) {
// Enum type is treated as a dictionary value for function calls.
base_type.is_meta_type = false;
}
- if (is_self && static_context && !is_static) {
+ if (is_self && static_context && !method_flags.has_flag(METHOD_FLAG_STATIC)) {
if (parser->current_function) {
// Get the parent function above any lambda.
GDScriptParser::FunctionNode *parent_function = parser->current_function;
@@ -3110,10 +3132,10 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
} else {
push_error(vformat(R"*(Cannot call non-static function "%s()" for static variable initializer.)*", p_call->function_name), p_call);
}
- } else if (!is_self && base_type.is_meta_type && !is_static) {
+ } else if (!is_self && base_type.is_meta_type && !method_flags.has_flag(METHOD_FLAG_STATIC)) {
base_type.is_meta_type = false; // For `to_string()`.
push_error(vformat(R"*(Cannot call non-static function "%s()" on the class "%s" directly. Make an instance instead.)*", p_call->function_name, base_type.to_string()), p_call);
- } else if (is_self && !is_static) {
+ } else if (is_self && !method_flags.has_flag(METHOD_FLAG_STATIC)) {
mark_lambda_use_self();
}
@@ -3122,11 +3144,12 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
}
#ifdef DEBUG_ENABLED
- if (p_is_root && return_type.kind != GDScriptParser::DataType::UNRESOLVED && return_type.builtin_type != Variant::NIL) {
+ if (p_is_root && return_type.kind != GDScriptParser::DataType::UNRESOLVED && return_type.builtin_type != Variant::NIL &&
+ !(p_call->is_super && p_call->function_name == GDScriptLanguage::get_singleton()->strings._init)) {
parser->push_warning(p_call, GDScriptWarning::RETURN_VALUE_DISCARDED, p_call->function_name);
}
- if (is_static && !is_constructor && !base_type.is_meta_type && !(is_self && static_context)) {
+ if (method_flags.has_flag(METHOD_FLAG_STATIC) && !is_constructor && !base_type.is_meta_type && !(is_self && static_context)) {
String caller_type = String(base_type.native_type);
if (caller_type.is_empty()) {
@@ -3349,7 +3372,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
base = *p_base;
}
- const StringName &name = p_identifier->name;
+ StringName name = p_identifier->name;
if (base.kind == GDScriptParser::DataType::ENUM) {
if (base.is_meta_type) {
@@ -3444,12 +3467,18 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
get_class_node_current_scope_classes(base_class, &script_classes);
}
+ bool is_constructor = base.is_meta_type && p_identifier->name == SNAME("new");
+
for (GDScriptParser::ClassNode *script_class : script_classes) {
if (p_base == nullptr && script_class->identifier && script_class->identifier->name == name) {
reduce_identifier_from_base_set_class(p_identifier, script_class->get_datatype());
return;
}
+ if (is_constructor) {
+ name = "_init";
+ }
+
if (script_class->has_member(name)) {
resolve_class_member(script_class, name, p_identifier);
@@ -3499,7 +3528,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
} break;
case GDScriptParser::ClassNode::Member::FUNCTION: {
- if (is_base && !base.is_meta_type) {
+ if (is_base && (!base.is_meta_type || member.function->is_static)) {
p_identifier->set_datatype(make_callable_type(member.function->info));
return;
}
@@ -3528,6 +3557,10 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
const StringName &native = base.native_type;
if (class_exists(native)) {
+ if (is_constructor) {
+ name = "_init";
+ }
+
MethodInfo method_info;
if (ClassDB::has_property(native, name)) {
StringName getter_name = ClassDB::get_property_getter(native, name);
@@ -4642,9 +4675,8 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo
return result;
}
-bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bool p_is_constructor, GDScriptParser::DataType p_base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg, StringName *r_native_class) {
- r_static = false;
- r_vararg = false;
+bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bool p_is_constructor, GDScriptParser::DataType p_base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, BitField<MethodFlags> &r_method_flags, StringName *r_native_class) {
+ r_method_flags = METHOD_FLAGS_DEFAULT;
r_default_arg_count = 0;
if (r_native_class) {
*r_native_class = StringName();
@@ -4677,10 +4709,9 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bo
for (const MethodInfo &E : methods) {
if (E.name == p_function) {
- function_signature_from_info(E, r_return_type, r_par_types, r_default_arg_count, r_static, r_vararg);
- r_static = Variant::is_builtin_method_static(p_base_type.builtin_type, function_name);
+ function_signature_from_info(E, r_return_type, r_par_types, r_default_arg_count, r_method_flags);
// Cannot use non-const methods on enums.
- if (!r_static && was_enum && !(E.flags & METHOD_FLAG_CONST)) {
+ if (!r_method_flags.has_flag(METHOD_FLAG_STATIC) && was_enum && !(E.flags & METHOD_FLAG_CONST)) {
push_error(vformat(R"*(Cannot call non-const Dictionary function "%s()" on enum "%s".)*", p_function, p_base_type.enum_type), p_source);
}
return true;
@@ -4710,8 +4741,8 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bo
}
if (p_is_constructor) {
- function_name = "_init";
- r_static = true;
+ function_name = GDScriptLanguage::get_singleton()->strings._init;
+ r_method_flags.set_flag(METHOD_FLAG_STATIC);
}
GDScriptParser::ClassNode *base_class = p_base_type.class_type;
@@ -4734,7 +4765,9 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bo
}
if (found_function != nullptr) {
- r_static = p_is_constructor || found_function->is_static;
+ if (p_is_constructor || found_function->is_static) {
+ r_method_flags.set_flag(METHOD_FLAG_STATIC);
+ }
for (int i = 0; i < found_function->parameters.size(); i++) {
r_par_types.push_back(found_function->parameters[i]->get_datatype());
if (found_function->parameters[i]->initializer != nullptr) {
@@ -4754,7 +4787,7 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bo
MethodInfo info = base_script->get_method_info(function_name);
if (!(info == MethodInfo())) {
- return function_signature_from_info(info, r_return_type, r_par_types, r_default_arg_count, r_static, r_vararg);
+ return function_signature_from_info(info, r_return_type, r_par_types, r_default_arg_count, r_method_flags);
}
base_script = base_script->get_base_script();
}
@@ -4765,7 +4798,7 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bo
StringName script_class = p_base_type.kind == GDScriptParser::DataType::SCRIPT ? p_base_type.script_type->get_class_name() : StringName(GDScript::get_class_static());
if (ClassDB::get_method_info(script_class, function_name, &info)) {
- return function_signature_from_info(info, r_return_type, r_par_types, r_default_arg_count, r_static, r_vararg);
+ return function_signature_from_info(info, r_return_type, r_par_types, r_default_arg_count, r_method_flags);
}
}
@@ -4779,9 +4812,9 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bo
MethodInfo info;
if (ClassDB::get_method_info(base_native, function_name, &info)) {
- bool valid = function_signature_from_info(info, r_return_type, r_par_types, r_default_arg_count, r_static, r_vararg);
+ bool valid = function_signature_from_info(info, r_return_type, r_par_types, r_default_arg_count, r_method_flags);
if (valid && Engine::get_singleton()->has_singleton(base_native)) {
- r_static = true;
+ r_method_flags.set_flag(METHOD_FLAG_STATIC);
}
#ifdef DEBUG_ENABLED
MethodBind *native_method = ClassDB::get_method(base_native, function_name);
@@ -4795,11 +4828,10 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bo
return false;
}
-bool GDScriptAnalyzer::function_signature_from_info(const MethodInfo &p_info, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg) {
+bool GDScriptAnalyzer::function_signature_from_info(const MethodInfo &p_info, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, BitField<MethodFlags> &r_method_flags) {
r_return_type = type_from_property(p_info.return_val);
r_default_arg_count = p_info.default_arguments.size();
- r_vararg = (p_info.flags & METHOD_FLAG_VARARG) != 0;
- r_static = (p_info.flags & METHOD_FLAG_STATIC) != 0;
+ r_method_flags = p_info.flags;
for (const PropertyInfo &E : p_info.arguments) {
r_par_types.push_back(type_from_property(E, true));
@@ -4929,6 +4961,17 @@ GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator
}
GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, const GDScriptParser::DataType &p_b, bool &r_valid, const GDScriptParser::Node *p_source) {
+ if (p_operation == Variant::OP_AND || p_operation == Variant::OP_OR) {
+ // Those work for any type of argument and always return a boolean.
+ // They don't use the Variant operator since they have short-circuit semantics.
+ r_valid = true;
+ GDScriptParser::DataType result;
+ result.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED;
+ result.kind = GDScriptParser::DataType::BUILTIN;
+ result.builtin_type = Variant::BOOL;
+ return result;
+ }
+
Variant::Type a_type = p_a.builtin_type;
Variant::Type b_type = p_b.builtin_type;
diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h
index 0c7bf4125b..195e23b503 100644
--- a/modules/gdscript/gdscript_analyzer.h
+++ b/modules/gdscript/gdscript_analyzer.h
@@ -31,11 +31,12 @@
#ifndef GDSCRIPT_ANALYZER_H
#define GDSCRIPT_ANALYZER_H
+#include "gdscript_cache.h"
+#include "gdscript_parser.h"
+
#include "core/object/object.h"
#include "core/object/ref_counted.h"
#include "core/templates/hash_set.h"
-#include "gdscript_cache.h"
-#include "gdscript_parser.h"
class GDScriptAnalyzer {
GDScriptParser *parser = nullptr;
@@ -115,8 +116,8 @@ class GDScriptAnalyzer {
static GDScriptParser::DataType type_from_metatype(const GDScriptParser::DataType &p_meta_type);
GDScriptParser::DataType type_from_property(const PropertyInfo &p_property, bool p_is_arg = false, bool p_is_readonly = false) const;
GDScriptParser::DataType make_global_class_meta_type(const StringName &p_class_name, const GDScriptParser::Node *p_source);
- bool get_function_signature(GDScriptParser::Node *p_source, bool p_is_constructor, GDScriptParser::DataType base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg, StringName *r_native_class = nullptr);
- bool function_signature_from_info(const MethodInfo &p_info, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg);
+ bool get_function_signature(GDScriptParser::Node *p_source, bool p_is_constructor, GDScriptParser::DataType base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, BitField<MethodFlags> &r_method_flags, StringName *r_native_class = nullptr);
+ bool function_signature_from_info(const MethodInfo &p_info, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, BitField<MethodFlags> &r_method_flags);
void validate_call_arg(const List<GDScriptParser::DataType> &p_par_types, int p_default_args_count, bool p_is_vararg, const GDScriptParser::CallNode *p_call);
void validate_call_arg(const MethodInfo &p_method, const GDScriptParser::CallNode *p_call);
GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, const GDScriptParser::DataType &p_b, bool &r_valid, const GDScriptParser::Node *p_source);
diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp
index 1414075ba8..47cd3f768b 100644
--- a/modules/gdscript/gdscript_byte_codegen.cpp
+++ b/modules/gdscript/gdscript_byte_codegen.cpp
@@ -30,9 +30,10 @@
#include "gdscript_byte_codegen.h"
-#include "core/debugger/engine_debugger.h"
#include "gdscript.h"
+#include "core/debugger/engine_debugger.h"
+
uint32_t GDScriptByteCodeGenerator::add_parameter(const StringName &p_name, bool p_is_optional, const GDScriptDataType &p_type) {
#ifdef TOOLS_ENABLED
function->arg_names.push_back(p_name);
@@ -852,6 +853,20 @@ void GDScriptByteCodeGenerator::write_get_member(const Address &p_target, const
append(p_name);
}
+void GDScriptByteCodeGenerator::write_set_static_variable(const Address &p_value, const Address &p_class, int p_index) {
+ append_opcode(GDScriptFunction::OPCODE_SET_STATIC_VARIABLE);
+ append(p_value);
+ append(p_class);
+ append(p_index);
+}
+
+void GDScriptByteCodeGenerator::write_get_static_variable(const Address &p_target, const Address &p_class, int p_index) {
+ append_opcode(GDScriptFunction::OPCODE_GET_STATIC_VARIABLE);
+ append(p_target);
+ append(p_class);
+ append(p_index);
+}
+
void GDScriptByteCodeGenerator::write_assign_with_conversion(const Address &p_target, const Address &p_source) {
switch (p_target.type.kind) {
case GDScriptDataType::BUILTIN: {
diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h
index fc684e4d8f..bbcd252b13 100644
--- a/modules/gdscript/gdscript_byte_codegen.h
+++ b/modules/gdscript/gdscript_byte_codegen.h
@@ -32,7 +32,6 @@
#define GDSCRIPT_BYTE_CODEGEN_H
#include "gdscript_codegen.h"
-
#include "gdscript_function.h"
#include "gdscript_utility_functions.h"
@@ -366,8 +365,6 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
return p_address.address | (GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS);
case Address::CONSTANT:
return p_address.address | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS);
- case Address::STATIC_VARIABLE:
- return p_address.address | (GDScriptFunction::ADDR_TYPE_STATIC_VAR << GDScriptFunction::ADDR_BITS);
case Address::LOCAL_VARIABLE:
case Address::FUNCTION_PARAMETER:
return p_address.address | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS);
@@ -503,6 +500,8 @@ public:
virtual void write_get_named(const Address &p_target, const StringName &p_name, const Address &p_source) override;
virtual void write_set_member(const Address &p_value, const StringName &p_name) override;
virtual void write_get_member(const Address &p_target, const StringName &p_name) override;
+ virtual void write_set_static_variable(const Address &p_value, const Address &p_class, int p_index) override;
+ virtual void write_get_static_variable(const Address &p_target, const Address &p_class, int p_index) override;
virtual void write_assign(const Address &p_target, const Address &p_source) override;
virtual void write_assign_with_conversion(const Address &p_target, const Address &p_source) override;
virtual void write_assign_true(const Address &p_target) override;
diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp
index 126fccbbf0..e5bb93e3c8 100644
--- a/modules/gdscript/gdscript_cache.cpp
+++ b/modules/gdscript/gdscript_cache.cpp
@@ -30,12 +30,13 @@
#include "gdscript_cache.h"
-#include "core/io/file_access.h"
-#include "core/templates/vector.h"
#include "gdscript.h"
#include "gdscript_analyzer.h"
#include "gdscript_compiler.h"
#include "gdscript_parser.h"
+
+#include "core/io/file_access.h"
+#include "core/templates/vector.h"
#include "scene/resources/packed_scene.h"
bool GDScriptParserRef::is_valid() const {
@@ -253,7 +254,11 @@ 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->load_source_code(p_path);
+ r_error = script->load_source_code(p_path);
+
+ if (r_error) {
+ return Ref<GDScript>(); // Returns null and does not cache when the script fails to load.
+ }
Ref<GDScriptParserRef> parser_ref = get_parser(p_path, GDScriptParserRef::PARSED, r_error);
if (r_error == OK) {
diff --git a/modules/gdscript/gdscript_cache.h b/modules/gdscript/gdscript_cache.h
index 28266a1c0b..0a0f403e44 100644
--- a/modules/gdscript/gdscript_cache.h
+++ b/modules/gdscript/gdscript_cache.h
@@ -31,11 +31,12 @@
#ifndef GDSCRIPT_CACHE_H
#define GDSCRIPT_CACHE_H
+#include "gdscript.h"
+
#include "core/object/ref_counted.h"
#include "core/os/mutex.h"
#include "core/templates/hash_map.h"
#include "core/templates/hash_set.h"
-#include "gdscript.h"
#include "scene/resources/packed_scene.h"
class GDScriptAnalyzer;
diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h
index dbc2466393..9810f5395a 100644
--- a/modules/gdscript/gdscript_codegen.h
+++ b/modules/gdscript/gdscript_codegen.h
@@ -31,11 +31,12 @@
#ifndef GDSCRIPT_CODEGEN_H
#define GDSCRIPT_CODEGEN_H
-#include "core/string/string_name.h"
-#include "core/variant/variant.h"
#include "gdscript_function.h"
#include "gdscript_utility_functions.h"
+#include "core/string/string_name.h"
+#include "core/variant/variant.h"
+
class GDScriptCodeGenerator {
public:
struct Address {
@@ -44,7 +45,6 @@ public:
CLASS,
MEMBER,
CONSTANT,
- STATIC_VARIABLE,
LOCAL_VARIABLE,
FUNCTION_PARAMETER,
TEMPORARY,
@@ -110,6 +110,8 @@ public:
virtual void write_get_named(const Address &p_target, const StringName &p_name, const Address &p_source) = 0;
virtual void write_set_member(const Address &p_value, const StringName &p_name) = 0;
virtual void write_get_member(const Address &p_target, const StringName &p_name) = 0;
+ virtual void write_set_static_variable(const Address &p_value, const Address &p_class, int p_index) = 0;
+ virtual void write_get_static_variable(const Address &p_target, const Address &p_class, int p_index) = 0;
virtual void write_assign(const Address &p_target, const Address &p_source) = 0;
virtual void write_assign_with_conversion(const Address &p_target, const Address &p_source) = 0;
virtual void write_assign_true(const Address &p_target) = 0;
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index 327e24ef11..90ee56b9de 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -262,18 +262,27 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
}
// Try static variables.
- if (codegen.script->static_variables_indices.has(identifier)) {
- if (codegen.script->static_variables_indices[identifier].getter != StringName() && codegen.script->static_variables_indices[identifier].getter != codegen.function_name) {
- // Perform getter.
- GDScriptCodeGenerator::Address temp = codegen.add_temporary(codegen.script->static_variables_indices[identifier].data_type);
- GDScriptCodeGenerator::Address class_addr(GDScriptCodeGenerator::Address::CLASS);
- Vector<GDScriptCodeGenerator::Address> args; // No argument needed.
- gen->write_call(temp, class_addr, codegen.script->static_variables_indices[identifier].getter, args);
- return temp;
- } else {
- // No getter or inside getter: direct variable access.
- int idx = codegen.script->static_variables_indices[identifier].index;
- return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::STATIC_VARIABLE, idx, codegen.script->static_variables_indices[identifier].data_type);
+ {
+ GDScript *scr = codegen.script;
+ while (scr) {
+ if (scr->static_variables_indices.has(identifier)) {
+ if (scr->static_variables_indices[identifier].getter != StringName() && scr->static_variables_indices[identifier].getter != codegen.function_name) {
+ // Perform getter.
+ GDScriptCodeGenerator::Address temp = codegen.add_temporary(scr->static_variables_indices[identifier].data_type);
+ GDScriptCodeGenerator::Address class_addr(GDScriptCodeGenerator::Address::CLASS);
+ Vector<GDScriptCodeGenerator::Address> args; // No argument needed.
+ gen->write_call(temp, class_addr, scr->static_variables_indices[identifier].getter, args);
+ return temp;
+ } else {
+ // No getter or inside getter: direct variable access.
+ GDScriptCodeGenerator::Address temp = codegen.add_temporary(scr->static_variables_indices[identifier].data_type);
+ GDScriptCodeGenerator::Address _class = codegen.add_constant(scr);
+ int index = scr->static_variables_indices[identifier].index;
+ gen->write_get_static_variable(temp, _class, index);
+ return temp;
+ }
+ }
+ scr = scr->_base;
}
}
@@ -926,6 +935,10 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
bool member_property_has_setter = false;
bool member_property_is_in_setter = false;
bool is_static = false;
+ GDScriptCodeGenerator::Address static_var_class;
+ int static_var_index = 0;
+ GDScriptDataType static_var_data_type;
+ StringName var_name;
StringName member_property_setter_function;
List<const GDScriptParser::SubscriptNode *> chain;
@@ -939,19 +952,39 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
// Check for a property.
if (n->base->type == GDScriptParser::Node::IDENTIFIER) {
GDScriptParser::IdentifierNode *identifier = static_cast<GDScriptParser::IdentifierNode *>(n->base);
- StringName var_name = identifier->name;
+ var_name = identifier->name;
if (_is_class_member_property(codegen, var_name)) {
assign_class_member_property = var_name;
- } else if (!_is_local_or_parameter(codegen, var_name) && (codegen.script->member_indices.has(var_name) || codegen.script->static_variables_indices.has(var_name))) {
- is_member_property = true;
- is_static = codegen.script->static_variables_indices.has(var_name);
- const GDScript::MemberInfo &minfo = is_static ? codegen.script->static_variables_indices[var_name] : codegen.script->member_indices[var_name];
- member_property_setter_function = minfo.setter;
- member_property_has_setter = member_property_setter_function != StringName();
- member_property_is_in_setter = member_property_has_setter && member_property_setter_function == codegen.function_name;
- target_member_property.mode = is_static ? GDScriptCodeGenerator::Address::STATIC_VARIABLE : GDScriptCodeGenerator::Address::MEMBER;
- target_member_property.address = minfo.index;
- target_member_property.type = minfo.data_type;
+ } else if (!_is_local_or_parameter(codegen, var_name)) {
+ if (codegen.script->member_indices.has(var_name)) {
+ is_member_property = true;
+ is_static = false;
+ const GDScript::MemberInfo &minfo = codegen.script->member_indices[var_name];
+ member_property_setter_function = minfo.setter;
+ member_property_has_setter = member_property_setter_function != StringName();
+ member_property_is_in_setter = member_property_has_setter && member_property_setter_function == codegen.function_name;
+ target_member_property.mode = GDScriptCodeGenerator::Address::MEMBER;
+ target_member_property.address = minfo.index;
+ target_member_property.type = minfo.data_type;
+ } else {
+ // Try static variables.
+ GDScript *scr = codegen.script;
+ while (scr) {
+ if (scr->static_variables_indices.has(var_name)) {
+ is_member_property = true;
+ is_static = true;
+ const GDScript::MemberInfo &minfo = scr->static_variables_indices[var_name];
+ member_property_setter_function = minfo.setter;
+ member_property_has_setter = member_property_setter_function != StringName();
+ member_property_is_in_setter = member_property_has_setter && member_property_setter_function == codegen.function_name;
+ static_var_class = codegen.add_constant(scr);
+ static_var_index = minfo.index;
+ static_var_data_type = minfo.data_type;
+ break;
+ }
+ scr = scr->_base;
+ }
+ }
}
}
break;
@@ -1104,8 +1137,13 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
if (member_property_has_setter && !member_property_is_in_setter) {
Vector<GDScriptCodeGenerator::Address> args;
args.push_back(assigned);
- GDScriptCodeGenerator::Address self = is_static ? GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::CLASS) : GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF);
- gen->write_call(GDScriptCodeGenerator::Address(), self, member_property_setter_function, args);
+ GDScriptCodeGenerator::Address call_base = is_static ? GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::CLASS) : GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF);
+ gen->write_call(GDScriptCodeGenerator::Address(), call_base, member_property_setter_function, args);
+ } else if (is_static) {
+ GDScriptCodeGenerator::Address temp = codegen.add_temporary(static_var_data_type);
+ gen->write_assign(temp, assigned);
+ gen->write_set_static_variable(temp, static_var_class, static_var_index);
+ gen->pop_temporary();
} else {
gen->write_assign(target_member_property, assigned);
}
@@ -1155,18 +1193,42 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
bool has_setter = false;
bool is_in_setter = false;
bool is_static = false;
+ GDScriptCodeGenerator::Address static_var_class;
+ int static_var_index = 0;
+ GDScriptDataType static_var_data_type;
+ StringName var_name;
StringName setter_function;
- StringName var_name = static_cast<const GDScriptParser::IdentifierNode *>(assignment->assignee)->name;
- if (!_is_local_or_parameter(codegen, var_name) && (codegen.script->member_indices.has(var_name) || codegen.script->static_variables_indices.has(var_name))) {
- is_member = true;
- is_static = codegen.script->static_variables_indices.has(var_name);
- GDScript::MemberInfo &minfo = is_static ? codegen.script->static_variables_indices[var_name] : codegen.script->member_indices[var_name];
- setter_function = minfo.setter;
- has_setter = setter_function != StringName();
- is_in_setter = has_setter && setter_function == codegen.function_name;
- member.mode = is_static ? GDScriptCodeGenerator::Address::STATIC_VARIABLE : GDScriptCodeGenerator::Address::MEMBER;
- member.address = minfo.index;
- member.type = minfo.data_type;
+ var_name = static_cast<const GDScriptParser::IdentifierNode *>(assignment->assignee)->name;
+ if (!_is_local_or_parameter(codegen, var_name)) {
+ if (codegen.script->member_indices.has(var_name)) {
+ is_member = true;
+ is_static = false;
+ GDScript::MemberInfo &minfo = codegen.script->member_indices[var_name];
+ setter_function = minfo.setter;
+ has_setter = setter_function != StringName();
+ is_in_setter = has_setter && setter_function == codegen.function_name;
+ member.mode = GDScriptCodeGenerator::Address::MEMBER;
+ member.address = minfo.index;
+ member.type = minfo.data_type;
+ } else {
+ // Try static variables.
+ GDScript *scr = codegen.script;
+ while (scr) {
+ if (scr->static_variables_indices.has(var_name)) {
+ is_member = true;
+ is_static = true;
+ GDScript::MemberInfo &minfo = scr->static_variables_indices[var_name];
+ setter_function = minfo.setter;
+ has_setter = setter_function != StringName();
+ is_in_setter = has_setter && setter_function == codegen.function_name;
+ static_var_class = codegen.add_constant(scr);
+ static_var_index = minfo.index;
+ static_var_data_type = minfo.data_type;
+ break;
+ }
+ scr = scr->_base;
+ }
+ }
}
GDScriptCodeGenerator::Address target;
@@ -1200,13 +1262,21 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
to_assign = assigned_value;
}
- GDScriptDataType assign_type = _gdtype_from_datatype(assignment->assignee->get_datatype(), codegen.script);
-
if (has_setter && !is_in_setter) {
// Call setter.
Vector<GDScriptCodeGenerator::Address> args;
args.push_back(to_assign);
- gen->write_call(GDScriptCodeGenerator::Address(), GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF), setter_function, args);
+ GDScriptCodeGenerator::Address call_base = is_static ? GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::CLASS) : GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF);
+ gen->write_call(GDScriptCodeGenerator::Address(), call_base, setter_function, args);
+ } else if (is_static) {
+ GDScriptCodeGenerator::Address temp = codegen.add_temporary(static_var_data_type);
+ if (assignment->use_conversion_assign) {
+ gen->write_assign_with_conversion(temp, to_assign);
+ } else {
+ gen->write_assign(temp, to_assign);
+ }
+ gen->write_set_static_variable(temp, static_var_class, static_var_index);
+ gen->pop_temporary();
} else {
// Just assign.
if (assignment->use_conversion_assign) {
@@ -1272,7 +1342,8 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_match_pattern(CodeGen &c
}
// Get literal type into constant map.
- GDScriptCodeGenerator::Address literal_type_addr = codegen.add_constant((int)p_pattern->literal->value.get_type());
+ Variant::Type literal_type = p_pattern->literal->value.get_type();
+ GDScriptCodeGenerator::Address literal_type_addr = codegen.add_constant(literal_type);
// Equality is always a boolean.
GDScriptDataType equality_type;
@@ -1280,29 +1351,31 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_match_pattern(CodeGen &c
equality_type.kind = GDScriptDataType::BUILTIN;
equality_type.builtin_type = Variant::BOOL;
- GDScriptCodeGenerator::Address type_string_addr = codegen.add_constant(Variant::STRING);
- GDScriptCodeGenerator::Address type_string_name_addr = codegen.add_constant(Variant::STRING_NAME);
-
// Check type equality.
GDScriptCodeGenerator::Address type_equality_addr = codegen.add_temporary(equality_type);
codegen.generator->write_binary_operator(type_equality_addr, Variant::OP_EQUAL, p_type_addr, literal_type_addr);
- // Check if StringName <-> String comparison is possible.
- GDScriptCodeGenerator::Address type_comp_addr_1 = codegen.add_temporary(equality_type);
- GDScriptCodeGenerator::Address type_comp_addr_2 = codegen.add_temporary(equality_type);
+ if (literal_type == Variant::STRING) {
+ GDScriptCodeGenerator::Address type_stringname_addr = codegen.add_constant(Variant::STRING_NAME);
+
+ // Check StringName <-> String type equality.
+ GDScriptCodeGenerator::Address tmp_comp_addr = codegen.add_temporary(equality_type);
+
+ codegen.generator->write_binary_operator(tmp_comp_addr, Variant::OP_EQUAL, p_type_addr, type_stringname_addr);
+ codegen.generator->write_binary_operator(type_equality_addr, Variant::OP_OR, type_equality_addr, tmp_comp_addr);
- codegen.generator->write_binary_operator(type_comp_addr_1, Variant::OP_EQUAL, p_type_addr, type_string_addr);
- codegen.generator->write_binary_operator(type_comp_addr_2, Variant::OP_EQUAL, literal_type_addr, type_string_name_addr);
- codegen.generator->write_binary_operator(type_comp_addr_1, Variant::OP_AND, type_comp_addr_1, type_comp_addr_2);
- codegen.generator->write_binary_operator(type_equality_addr, Variant::OP_OR, type_equality_addr, type_comp_addr_1);
+ codegen.generator->pop_temporary(); // Remove tmp_comp_addr from stack.
+ } else if (literal_type == Variant::STRING_NAME) {
+ GDScriptCodeGenerator::Address type_string_addr = codegen.add_constant(Variant::STRING);
- codegen.generator->write_binary_operator(type_comp_addr_1, Variant::OP_EQUAL, p_type_addr, type_string_name_addr);
- codegen.generator->write_binary_operator(type_comp_addr_2, Variant::OP_EQUAL, literal_type_addr, type_string_addr);
- codegen.generator->write_binary_operator(type_comp_addr_1, Variant::OP_AND, type_comp_addr_1, type_comp_addr_2);
- codegen.generator->write_binary_operator(type_equality_addr, Variant::OP_OR, type_equality_addr, type_comp_addr_1);
+ // Check String <-> StringName type equality.
+ GDScriptCodeGenerator::Address tmp_comp_addr = codegen.add_temporary(equality_type);
- codegen.generator->pop_temporary(); // Remove type_comp_addr_2 from stack.
- codegen.generator->pop_temporary(); // Remove type_comp_addr_1 from stack.
+ codegen.generator->write_binary_operator(tmp_comp_addr, Variant::OP_EQUAL, p_type_addr, type_string_addr);
+ codegen.generator->write_binary_operator(type_equality_addr, Variant::OP_OR, type_equality_addr, tmp_comp_addr);
+
+ codegen.generator->pop_temporary(); // Remove tmp_comp_addr from stack.
+ }
codegen.generator->write_and_left_operand(type_equality_addr);
@@ -1349,9 +1422,22 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_match_pattern(CodeGen &c
} else if (!p_is_first) {
codegen.generator->write_or_left_operand(p_previous_test);
}
+
+ GDScriptCodeGenerator::Address type_string_addr = codegen.add_constant(Variant::STRING);
+ GDScriptCodeGenerator::Address type_stringname_addr = codegen.add_constant(Variant::STRING_NAME);
+
+ // Equality is always a boolean.
+ GDScriptDataType equality_type;
+ equality_type.has_type = true;
+ equality_type.kind = GDScriptDataType::BUILTIN;
+ equality_type.builtin_type = Variant::BOOL;
+
// Create the result temps first since it's the last to go away.
- GDScriptCodeGenerator::Address result_addr = codegen.add_temporary();
- GDScriptCodeGenerator::Address equality_test_addr = codegen.add_temporary();
+ GDScriptCodeGenerator::Address result_addr = codegen.add_temporary(equality_type);
+ GDScriptCodeGenerator::Address equality_test_addr = codegen.add_temporary(equality_type);
+ GDScriptCodeGenerator::Address stringy_comp_addr = codegen.add_temporary(equality_type);
+ GDScriptCodeGenerator::Address stringy_comp_addr_2 = codegen.add_temporary(equality_type);
+ GDScriptCodeGenerator::Address expr_type_addr = codegen.add_temporary();
// Evaluate expression.
GDScriptCodeGenerator::Address expr_addr;
@@ -1363,10 +1449,27 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_match_pattern(CodeGen &c
// Evaluate expression type.
Vector<GDScriptCodeGenerator::Address> typeof_args;
typeof_args.push_back(expr_addr);
- codegen.generator->write_call_utility(result_addr, "typeof", typeof_args);
+ codegen.generator->write_call_utility(expr_type_addr, "typeof", typeof_args);
// Check type equality.
- codegen.generator->write_binary_operator(result_addr, Variant::OP_EQUAL, p_type_addr, result_addr);
+ codegen.generator->write_binary_operator(result_addr, Variant::OP_EQUAL, p_type_addr, expr_type_addr);
+
+ // Check for String <-> StringName comparison.
+ codegen.generator->write_binary_operator(stringy_comp_addr, Variant::OP_EQUAL, p_type_addr, type_string_addr);
+ codegen.generator->write_binary_operator(stringy_comp_addr_2, Variant::OP_EQUAL, expr_type_addr, type_stringname_addr);
+ codegen.generator->write_binary_operator(stringy_comp_addr, Variant::OP_AND, stringy_comp_addr, stringy_comp_addr_2);
+ codegen.generator->write_binary_operator(result_addr, Variant::OP_OR, result_addr, stringy_comp_addr);
+
+ // Check for StringName <-> String comparison.
+ codegen.generator->write_binary_operator(stringy_comp_addr, Variant::OP_EQUAL, p_type_addr, type_stringname_addr);
+ codegen.generator->write_binary_operator(stringy_comp_addr_2, Variant::OP_EQUAL, expr_type_addr, type_string_addr);
+ codegen.generator->write_binary_operator(stringy_comp_addr, Variant::OP_AND, stringy_comp_addr, stringy_comp_addr_2);
+ codegen.generator->write_binary_operator(result_addr, Variant::OP_OR, result_addr, stringy_comp_addr);
+
+ codegen.generator->pop_temporary(); // Remove expr_type_addr from stack.
+ codegen.generator->pop_temporary(); // Remove stringy_comp_addr_2 from stack.
+ codegen.generator->pop_temporary(); // Remove stringy_comp_addr from stack.
+
codegen.generator->write_and_left_operand(result_addr);
// Check value equality.
@@ -1380,7 +1483,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_match_pattern(CodeGen &c
if (expr_addr.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
codegen.generator->pop_temporary();
}
- codegen.generator->pop_temporary(); // Remove type equality temporary.
+ codegen.generator->pop_temporary(); // Remove equality_test_addr from stack.
// If this isn't the first, we need to OR with the previous pattern. If it's nested, we use AND instead.
if (p_is_nested) {
@@ -2062,11 +2165,11 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
}
GDScriptDataType field_type = _gdtype_from_datatype(field->get_datatype(), codegen.script);
- GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, field_type);
-
if (field_type.has_type) {
codegen.generator->write_newline(field->start_line);
+ GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, field_type);
+
if (field_type.has_container_element_type()) {
codegen.generator->write_construct_typed_array(dst_address, field_type.get_container_element_type(), Vector<GDScriptCodeGenerator::Address>());
} else if (field_type.kind == GDScriptDataType::BUILTIN) {
@@ -2093,9 +2196,6 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
continue;
}
- GDScriptDataType field_type = _gdtype_from_datatype(field->get_datatype(), codegen.script);
-
- GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, field_type);
if (field->initializer) {
// Emit proper line change.
codegen.generator->write_newline(field->initializer->start_line);
@@ -2106,6 +2206,9 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
return nullptr;
}
+ GDScriptDataType field_type = _gdtype_from_datatype(field->get_datatype(), codegen.script);
+ GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, field_type);
+
if (field->use_conversion_assign) {
codegen.generator->write_assign_with_conversion(dst_address, src_address);
} else {
@@ -2235,6 +2338,8 @@ GDScriptFunction *GDScriptCompiler::_make_static_initializer(Error &r_error, GDS
codegen.is_static = is_static;
codegen.generator->write_start(p_script, func_name, is_static, rpc_config, return_type);
+ // The static initializer is always called on the same class where the static variables are defined,
+ // so the CLASS address (current class) can be used instead of `codegen.add_constant(p_script)`.
GDScriptCodeGenerator::Address class_addr(GDScriptCodeGenerator::Address::CLASS);
// Initialize the default values for typed variables before anything.
@@ -2251,20 +2356,18 @@ GDScriptFunction *GDScriptCompiler::_make_static_initializer(Error &r_error, GDS
}
GDScriptDataType field_type = _gdtype_from_datatype(field->get_datatype(), codegen.script);
-
if (field_type.has_type) {
codegen.generator->write_newline(field->start_line);
if (field_type.has_container_element_type()) {
GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type);
codegen.generator->write_construct_typed_array(temp, field_type.get_container_element_type(), Vector<GDScriptCodeGenerator::Address>());
- codegen.generator->write_set_named(class_addr, field->identifier->name, temp);
+ codegen.generator->write_set_static_variable(temp, class_addr, p_script->static_variables_indices[field->identifier->name].index);
codegen.generator->pop_temporary();
-
} else if (field_type.kind == GDScriptDataType::BUILTIN) {
GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type);
codegen.generator->write_construct(temp, field_type.builtin_type, Vector<GDScriptCodeGenerator::Address>());
- codegen.generator->write_set_named(class_addr, field->identifier->name, temp);
+ codegen.generator->write_set_static_variable(temp, class_addr, p_script->static_variables_indices[field->identifier->name].index);
codegen.generator->pop_temporary();
}
// The `else` branch is for objects, in such case we leave it as `null`.
@@ -2281,8 +2384,6 @@ GDScriptFunction *GDScriptCompiler::_make_static_initializer(Error &r_error, GDS
continue;
}
- GDScriptDataType field_type = _gdtype_from_datatype(field->get_datatype(), codegen.script);
-
if (field->initializer) {
// Emit proper line change.
codegen.generator->write_newline(field->initializer->start_line);
@@ -2293,7 +2394,9 @@ GDScriptFunction *GDScriptCompiler::_make_static_initializer(Error &r_error, GDS
return nullptr;
}
+ GDScriptDataType field_type = _gdtype_from_datatype(field->get_datatype(), codegen.script);
GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type);
+
if (field->use_conversion_assign) {
codegen.generator->write_assign_with_conversion(temp, src_address);
} else {
@@ -2303,7 +2406,7 @@ GDScriptFunction *GDScriptCompiler::_make_static_initializer(Error &r_error, GDS
codegen.generator->pop_temporary();
}
- codegen.generator->write_set_named(class_addr, field->identifier->name, temp);
+ codegen.generator->write_set_static_variable(temp, class_addr, p_script->static_variables_indices[field->identifier->name].index);
codegen.generator->pop_temporary();
}
}
diff --git a/modules/gdscript/gdscript_compiler.h b/modules/gdscript/gdscript_compiler.h
index 2d15d461fb..494eef41d9 100644
--- a/modules/gdscript/gdscript_compiler.h
+++ b/modules/gdscript/gdscript_compiler.h
@@ -31,12 +31,13 @@
#ifndef GDSCRIPT_COMPILER_H
#define GDSCRIPT_COMPILER_H
-#include "core/templates/hash_set.h"
#include "gdscript.h"
#include "gdscript_codegen.h"
#include "gdscript_function.h"
#include "gdscript_parser.h"
+#include "core/templates/hash_set.h"
+
class GDScriptCompiler {
const GDScriptParser *parser = nullptr;
HashSet<GDScript *> parsed_classes;
diff --git a/modules/gdscript/gdscript_disassembler.cpp b/modules/gdscript/gdscript_disassembler.cpp
index 45ad8792d9..0438bd8903 100644
--- a/modules/gdscript/gdscript_disassembler.cpp
+++ b/modules/gdscript/gdscript_disassembler.cpp
@@ -30,10 +30,10 @@
#ifdef DEBUG_ENABLED
+#include "gdscript.h"
#include "gdscript_function.h"
#include "core/string/string_builder.h"
-#include "gdscript.h"
static String _get_variant_string(const Variant &p_variant) {
String txt;
@@ -312,6 +312,36 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
incr += 3;
} break;
+ case OPCODE_SET_STATIC_VARIABLE: {
+ text += "set_static_variable script(";
+ Ref<GDScript> gdscript = get_constant(_code_ptr[ip + 2] & GDScriptFunction::ADDR_MASK);
+ text += gdscript.is_valid() ? gdscript->get_fully_qualified_name().get_file() : "<unknown script>";
+ text += ")";
+ if (gdscript.is_valid()) {
+ text += "[\"" + gdscript->debug_get_static_var_by_index(_code_ptr[ip + 3]) + "\"]";
+ } else {
+ text += "[<index " + itos(_code_ptr[ip + 3]) + ">]";
+ }
+ text += " = ";
+ text += DADDR(1);
+
+ incr += 4;
+ } break;
+ case OPCODE_GET_STATIC_VARIABLE: {
+ text += "get_static_variable ";
+ text += DADDR(1);
+ text += " = script(";
+ Ref<GDScript> gdscript = get_constant(_code_ptr[ip + 2] & GDScriptFunction::ADDR_MASK);
+ text += gdscript.is_valid() ? gdscript->get_fully_qualified_name().get_file() : "<unknown script>";
+ text += ")";
+ if (gdscript.is_valid()) {
+ text += "[\"" + gdscript->debug_get_static_var_by_index(_code_ptr[ip + 3]) + "\"]";
+ } else {
+ text += "[<index " + itos(_code_ptr[ip + 3]) + ">]";
+ }
+
+ incr += 4;
+ } break;
case OPCODE_ASSIGN: {
text += "assign ";
text += DADDR(1);
@@ -1130,4 +1160,4 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
}
}
-#endif
+#endif // DEBUG_ENABLED
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index 829567d734..cd34feb8b3 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -30,9 +30,6 @@
#include "gdscript.h"
-#include "core/config/engine.h"
-#include "core/core_constants.h"
-#include "core/io/file_access.h"
#include "gdscript_analyzer.h"
#include "gdscript_compiler.h"
#include "gdscript_parser.h"
@@ -40,10 +37,17 @@
#include "gdscript_utility_functions.h"
#ifdef TOOLS_ENABLED
+#include "editor/script_templates/templates.gen.h"
+#endif
+
+#include "core/config/engine.h"
+#include "core/core_constants.h"
+#include "core/io/file_access.h"
+
+#ifdef TOOLS_ENABLED
#include "core/config/project_settings.h"
#include "editor/editor_file_system.h"
#include "editor/editor_settings.h"
-#include "editor/script_templates/templates.gen.h"
#endif
void GDScriptLanguage::get_comment_delimiters(List<String> *p_delimiters) const {
@@ -906,19 +910,20 @@ static void _list_available_types(bool p_inherit_only, GDScriptParser::Completio
}
}
-static void _find_identifiers_in_suite(const GDScriptParser::SuiteNode *p_suite, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result) {
+static void _find_identifiers_in_suite(const GDScriptParser::SuiteNode *p_suite, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result, int p_recursion_depth = 0) {
for (int i = 0; i < p_suite->locals.size(); i++) {
ScriptLanguage::CodeCompletionOption option;
+ int location = p_recursion_depth == 0 ? ScriptLanguage::LOCATION_LOCAL : (p_recursion_depth | ScriptLanguage::LOCATION_PARENT_MASK);
if (p_suite->locals[i].type == GDScriptParser::SuiteNode::Local::CONSTANT) {
- option = ScriptLanguage::CodeCompletionOption(p_suite->locals[i].name, ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT, ScriptLanguage::LOCATION_LOCAL);
+ option = ScriptLanguage::CodeCompletionOption(p_suite->locals[i].name, ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT, location);
option.default_value = p_suite->locals[i].constant->initializer->reduced_value;
} else {
- option = ScriptLanguage::CodeCompletionOption(p_suite->locals[i].name, ScriptLanguage::CODE_COMPLETION_KIND_VARIABLE, ScriptLanguage::LOCATION_LOCAL);
+ option = ScriptLanguage::CodeCompletionOption(p_suite->locals[i].name, ScriptLanguage::CODE_COMPLETION_KIND_VARIABLE, location);
}
r_result.insert(option.display, option);
}
if (p_suite->parent_block) {
- _find_identifiers_in_suite(p_suite->parent_block, r_result);
+ _find_identifiers_in_suite(p_suite->parent_block, r_result, p_recursion_depth + 1);
}
}
@@ -933,7 +938,7 @@ static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class,
int classes_processed = 0;
while (clss) {
for (int i = 0; i < clss->members.size(); i++) {
- const int location = (classes_processed + p_recursion_depth) | ScriptLanguage::LOCATION_PARENT_MASK;
+ const int location = p_recursion_depth == 0 ? classes_processed : (p_recursion_depth | ScriptLanguage::LOCATION_PARENT_MASK);
const GDScriptParser::ClassNode::Member &member = clss->members[i];
ScriptLanguage::CodeCompletionOption option;
switch (member.type) {
@@ -1025,7 +1030,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base
while (!base_type.has_no_type()) {
switch (base_type.kind) {
case GDScriptParser::DataType::CLASS: {
- _find_identifiers_in_class(base_type.class_type, p_only_functions, base_type.is_meta_type, false, r_result, p_recursion_depth + 1);
+ _find_identifiers_in_class(base_type.class_type, p_only_functions, base_type.is_meta_type, false, r_result, p_recursion_depth);
// This already finds all parent identifiers, so we are done.
base_type = GDScriptParser::DataType();
} break;
@@ -1205,7 +1210,7 @@ static void _find_identifiers(const GDScriptParser::CompletionContext &p_context
}
if (p_context.current_class) {
- _find_identifiers_in_class(p_context.current_class, p_only_functions, (!p_context.current_function || p_context.current_function->is_static), false, r_result, p_recursion_depth + 1);
+ _find_identifiers_in_class(p_context.current_class, p_only_functions, (!p_context.current_function || p_context.current_function->is_static), false, r_result, p_recursion_depth);
}
List<StringName> functions;
@@ -3398,7 +3403,8 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
}
case GDScriptParser::COMPLETION_ASSIGN:
case GDScriptParser::COMPLETION_CALL_ARGUMENTS:
- case GDScriptParser::COMPLETION_IDENTIFIER: {
+ case GDScriptParser::COMPLETION_IDENTIFIER:
+ case GDScriptParser::COMPLETION_PROPERTY_METHOD: {
GDScriptParser::DataType base_type;
if (context.current_class) {
if (context.type != GDScriptParser::COMPLETION_SUPER_METHOD) {
@@ -3527,6 +3533,33 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
return OK;
}
} break;
+ case GDScriptParser::COMPLETION_TYPE_ATTRIBUTE: {
+ if (context.node == nullptr || context.node->type != GDScriptParser::Node::TYPE) {
+ break;
+ }
+ const GDScriptParser::TypeNode *type = static_cast<const GDScriptParser::TypeNode *>(context.node);
+
+ GDScriptParser::DataType base_type;
+ const GDScriptParser::IdentifierNode *prev = nullptr;
+ for (const GDScriptParser::IdentifierNode *E : type->type_chain) {
+ if (E->name == p_symbol && prev != nullptr) {
+ base_type = prev->get_datatype();
+ break;
+ }
+ prev = E;
+ }
+ if (base_type.kind != GDScriptParser::DataType::CLASS) {
+ GDScriptCompletionIdentifier base;
+ if (!_guess_expression_type(context, prev, base)) {
+ break;
+ }
+ base_type = base.type;
+ }
+
+ if (_lookup_symbol_from_base(base_type, p_symbol, is_function, r_result) == OK) {
+ return OK;
+ }
+ } break;
case GDScriptParser::COMPLETION_OVERRIDE_METHOD: {
GDScriptParser::DataType base_type = context.current_class->base_type;
@@ -3534,6 +3567,7 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
return OK;
}
} break;
+ case GDScriptParser::COMPLETION_PROPERTY_DECLARATION_OR_TYPE:
case GDScriptParser::COMPLETION_TYPE_NAME_OR_VOID:
case GDScriptParser::COMPLETION_TYPE_NAME: {
GDScriptParser::DataType base_type = context.current_class->get_datatype();
diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h
index 390e562e6f..9bbfb14f31 100644
--- a/modules/gdscript/gdscript_function.h
+++ b/modules/gdscript/gdscript_function.h
@@ -31,6 +31,8 @@
#ifndef GDSCRIPT_FUNCTION_H
#define GDSCRIPT_FUNCTION_H
+#include "gdscript_utility_functions.h"
+
#include "core/object/ref_counted.h"
#include "core/object/script_language.h"
#include "core/os/thread.h"
@@ -38,7 +40,6 @@
#include "core/templates/pair.h"
#include "core/templates/self_list.h"
#include "core/variant/variant.h"
-#include "gdscript_utility_functions.h"
class GDScriptInstance;
class GDScript;
@@ -148,6 +149,7 @@ public:
operator PropertyInfo() const {
PropertyInfo info;
+ info.usage = PROPERTY_USAGE_NONE;
if (has_type) {
switch (kind) {
case UNINITIALIZED:
@@ -237,6 +239,8 @@ public:
OPCODE_GET_NAMED_VALIDATED,
OPCODE_SET_MEMBER,
OPCODE_GET_MEMBER,
+ OPCODE_SET_STATIC_VARIABLE, // Only for GDScript.
+ OPCODE_GET_STATIC_VARIABLE, // Only for GDScript.
OPCODE_ASSIGN,
OPCODE_ASSIGN_TRUE,
OPCODE_ASSIGN_FALSE,
@@ -409,14 +413,14 @@ public:
ADDR_TYPE_STACK = 0,
ADDR_TYPE_CONSTANT = 1,
ADDR_TYPE_MEMBER = 2,
- ADDR_TYPE_STATIC_VAR = 3,
- ADDR_TYPE_MAX = 4,
+ ADDR_TYPE_MAX = 3,
};
enum FixedAddresses {
ADDR_STACK_SELF = 0,
ADDR_STACK_CLASS = 1,
ADDR_STACK_NIL = 2,
+ FIXED_ADDRESSES_MAX = 3,
ADDR_SELF = ADDR_STACK_SELF | (ADDR_TYPE_STACK << ADDR_BITS),
ADDR_CLASS = ADDR_STACK_CLASS | (ADDR_TYPE_STACK << ADDR_BITS),
ADDR_NIL = ADDR_STACK_NIL | (ADDR_TYPE_STACK << ADDR_BITS),
diff --git a/modules/gdscript/gdscript_lambda_callable.cpp b/modules/gdscript/gdscript_lambda_callable.cpp
index e9fe17bb17..9e14e43a58 100644
--- a/modules/gdscript/gdscript_lambda_callable.cpp
+++ b/modules/gdscript/gdscript_lambda_callable.cpp
@@ -30,9 +30,10 @@
#include "gdscript_lambda_callable.h"
-#include "core/templates/hashfuncs.h"
#include "gdscript.h"
+#include "core/templates/hashfuncs.h"
+
bool GDScriptLambdaCallable::compare_equal(const CallableCustom *p_a, const CallableCustom *p_b) {
// Lambda callables are only compared by reference.
return p_a == p_b;
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index fdbd505975..cf750958ee 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -30,23 +30,27 @@
#include "gdscript_parser.h"
+#include "gdscript.h"
+
+#ifdef DEBUG_ENABLED
+#include "gdscript_warning.h"
+#endif
+
#include "core/config/project_settings.h"
#include "core/io/file_access.h"
#include "core/io/resource_loader.h"
#include "core/math/math_defs.h"
-#include "gdscript.h"
#include "scene/main/multiplayer_api.h"
#ifdef DEBUG_ENABLED
#include "core/os/os.h"
#include "core/string/string_builder.h"
-#include "gdscript_warning.h"
#include "servers/text_server.h"
-#endif // DEBUG_ENABLED
+#endif
#ifdef TOOLS_ENABLED
#include "editor/editor_settings.h"
-#endif // TOOLS_ENABLED
+#endif
static HashMap<StringName, Variant::Type> builtin_types;
Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) {
@@ -112,7 +116,7 @@ GDScriptParser::GDScriptParser() {
// Warning annotations.
register_annotation(MethodInfo("@warning_ignore", PropertyInfo(Variant::STRING, "warning")), AnnotationInfo::CLASS | AnnotationInfo::VARIABLE | AnnotationInfo::SIGNAL | AnnotationInfo::CONSTANT | AnnotationInfo::FUNCTION | AnnotationInfo::STATEMENT, &GDScriptParser::warning_annotations, varray(), true);
// Networking.
- register_annotation(MethodInfo("@rpc", PropertyInfo(Variant::STRING, "mode"), PropertyInfo(Variant::STRING, "sync"), PropertyInfo(Variant::STRING, "transfer_mode"), PropertyInfo(Variant::INT, "transfer_channel")), AnnotationInfo::FUNCTION, &GDScriptParser::rpc_annotation, varray("authority", "call_remote", "unreliable", 0), true);
+ register_annotation(MethodInfo("@rpc", PropertyInfo(Variant::STRING, "mode"), PropertyInfo(Variant::STRING, "sync"), PropertyInfo(Variant::STRING, "transfer_mode"), PropertyInfo(Variant::INT, "transfer_channel")), AnnotationInfo::FUNCTION, &GDScriptParser::rpc_annotation, varray("authority", "call_remote", "unreliable", 0));
#ifdef DEBUG_ENABLED
is_ignoring_warnings = !(bool)GLOBAL_GET("debug/gdscript/warnings/enable");
@@ -2760,12 +2764,12 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_dictionary(ExpressionNode
switch (dictionary->style) {
case DictionaryNode::LUA_TABLE:
if (key != nullptr && key->type != Node::IDENTIFIER && key->type != Node::LITERAL) {
- push_error("Expected identifier or string as LUA-style dictionary key.");
+ push_error(R"(Expected identifier or string as Lua-style dictionary key (e.g "{ key = value }").)");
advance();
break;
}
if (key != nullptr && key->type == Node::LITERAL && static_cast<LiteralNode *>(key)->value.get_type() != Variant::STRING) {
- push_error("Expected identifier or string as LUA-style dictionary key.");
+ push_error(R"(Expected identifier or string as Lua-style dictionary key (e.g "{ key = value }").)");
advance();
break;
}
@@ -3293,31 +3297,114 @@ GDScriptParser::TypeNode *GDScriptParser::parse_type(bool p_allow_void) {
}
#ifdef TOOLS_ENABLED
-static bool _in_codeblock(String p_line, bool p_already_in, int *r_block_begins = nullptr) {
- int start_block = p_line.rfind("[codeblock]");
- int end_block = p_line.rfind("[/codeblock]");
-
- if (start_block != -1 && r_block_begins) {
- *r_block_begins = start_block;
+enum DocLineState {
+ DOC_LINE_NORMAL,
+ DOC_LINE_IN_CODE,
+ DOC_LINE_IN_CODEBLOCK,
+};
+
+static String _process_doc_line(const String &p_line, const String &p_text, const String &p_space_prefix, DocLineState &r_state) {
+ String line = p_line;
+ if (r_state == DOC_LINE_NORMAL) {
+ line = line.strip_edges(true, false);
+ } else {
+ line = line.trim_prefix(p_space_prefix);
}
- if (p_already_in) {
- if (end_block == -1) {
- return true;
- } else if (start_block == -1) {
- return false;
+ String line_join;
+ if (!p_text.is_empty()) {
+ if (r_state == DOC_LINE_NORMAL) {
+ if (p_text.ends_with("[/codeblock]")) {
+ line_join = "\n";
+ } else if (!p_text.ends_with("[br]")) {
+ line_join = " ";
+ }
} else {
- return start_block > end_block;
+ line_join = "\n";
}
- } else {
- if (start_block == -1) {
- return false;
- } else if (end_block == -1) {
- return true;
- } else {
- return start_block > end_block;
+ }
+
+ String result;
+ int from = 0;
+ int buffer_start = 0;
+ const int len = line.length();
+ bool process = true;
+ while (process) {
+ switch (r_state) {
+ case DOC_LINE_NORMAL: {
+ int lb_pos = line.find_char('[', from);
+ if (lb_pos < 0) {
+ process = false;
+ break;
+ }
+ int rb_pos = line.find_char(']', lb_pos + 1);
+ if (rb_pos < 0) {
+ process = false;
+ break;
+ }
+
+ from = rb_pos + 1;
+
+ String tag = line.substr(lb_pos + 1, rb_pos - lb_pos - 1);
+ if (tag == "code") {
+ r_state = DOC_LINE_IN_CODE;
+ } else if (tag == "codeblock") {
+ if (lb_pos == 0) {
+ line_join = "\n";
+ } else {
+ result += line.substr(buffer_start, lb_pos - buffer_start) + '\n';
+ }
+ result += "[codeblock]";
+ if (from < len) {
+ result += '\n';
+ }
+
+ r_state = DOC_LINE_IN_CODEBLOCK;
+ buffer_start = from;
+ }
+ } break;
+ case DOC_LINE_IN_CODE: {
+ int pos = line.find("[/code]", from);
+ if (pos < 0) {
+ process = false;
+ break;
+ }
+
+ from = pos + 7;
+
+ r_state = DOC_LINE_NORMAL;
+ } break;
+ case DOC_LINE_IN_CODEBLOCK: {
+ int pos = line.find("[/codeblock]", from);
+ if (pos < 0) {
+ process = false;
+ break;
+ }
+
+ from = pos + 12;
+
+ if (pos == 0) {
+ line_join = "\n";
+ } else {
+ result += line.substr(buffer_start, pos - buffer_start) + '\n';
+ }
+ result += "[/codeblock]";
+ if (from < len) {
+ result += '\n';
+ }
+
+ r_state = DOC_LINE_NORMAL;
+ buffer_start = from;
+ } break;
}
}
+
+ result += line.substr(buffer_start);
+ if (r_state == DOC_LINE_NORMAL) {
+ result = result.strip_edges(false, true);
+ }
+
+ return line_join + result;
}
bool GDScriptParser::has_comment(int p_line, bool p_must_be_doc) {
@@ -3345,7 +3432,7 @@ String GDScriptParser::get_doc_comment(int p_line, bool p_single_line) {
String doc;
int line = p_line;
- bool in_codeblock = false;
+ DocLineState state = DOC_LINE_NORMAL;
while (comments.has(line - 1)) {
if (!comments[line - 1].new_line || !comments[line - 1].comment.begins_with("##")) {
@@ -3354,29 +3441,24 @@ String GDScriptParser::get_doc_comment(int p_line, bool p_single_line) {
line--;
}
- int codeblock_begins = 0;
+ String space_prefix;
+ if (comments.has(line) && comments[line].comment.begins_with("##")) {
+ int i = 2;
+ for (; i < comments[line].comment.length(); i++) {
+ if (comments[line].comment[i] != ' ') {
+ break;
+ }
+ }
+ space_prefix = String(" ").repeat(i - 2);
+ }
+
while (comments.has(line)) {
if (!comments[line].new_line || !comments[line].comment.begins_with("##")) {
break;
}
- String doc_line = comments[line].comment.trim_prefix("##");
- in_codeblock = _in_codeblock(doc_line, in_codeblock, &codeblock_begins);
-
- if (in_codeblock) {
- int i = 0;
- for (; i < codeblock_begins; i++) {
- if (doc_line[i] != ' ') {
- break;
- }
- }
- doc_line = doc_line.substr(i);
- } else {
- doc_line = doc_line.strip_edges();
- }
- String line_join = (in_codeblock) ? "\n" : " ";
-
- doc = (doc.is_empty()) ? doc_line : doc + line_join + doc_line;
+ String doc_line = comments[line].comment.trim_prefix("##");
+ doc += _process_doc_line(doc_line, doc, space_prefix, state);
line++;
}
@@ -3391,7 +3473,7 @@ void GDScriptParser::get_class_doc_comment(int p_line, String &p_brief, String &
ERR_FAIL_COND(!p_brief.is_empty() || !p_desc.is_empty() || p_tutorials.size() != 0);
int line = p_line;
- bool in_codeblock = false;
+ DocLineState state = DOC_LINE_NORMAL;
enum Mode {
BRIEF,
DESC,
@@ -3409,96 +3491,87 @@ void GDScriptParser::get_class_doc_comment(int p_line, String &p_brief, String &
}
}
- int codeblock_begins = 0;
+ String space_prefix;
+ if (comments.has(line) && comments[line].comment.begins_with("##")) {
+ int i = 2;
+ for (; i < comments[line].comment.length(); i++) {
+ if (comments[line].comment[i] != ' ') {
+ break;
+ }
+ }
+ space_prefix = String(" ").repeat(i - 2);
+ }
+
while (comments.has(line)) {
if (!comments[line].new_line || !comments[line].comment.begins_with("##")) {
break;
}
- String title, link; // For tutorials.
String doc_line = comments[line++].comment.trim_prefix("##");
- String stripped_line = doc_line.strip_edges();
-
- // Set the read mode.
- if (stripped_line.is_empty() && mode == BRIEF && !p_brief.is_empty()) {
- mode = DESC;
- continue;
-
- } else if (stripped_line.begins_with("@tutorial")) {
- int begin_scan = String("@tutorial").length();
- if (begin_scan >= stripped_line.length()) {
- continue; // invalid syntax.
- }
-
- if (stripped_line[begin_scan] == ':') { // No title.
- // Syntax: ## @tutorial: https://godotengine.org/ // The title argument is optional.
- title = "";
- link = stripped_line.trim_prefix("@tutorial:").strip_edges();
-
- } else {
- /* Syntax:
- * @tutorial ( The Title Here ) : https://the.url/
- * ^ open ^ close ^ colon ^ url
- */
- int open_bracket_pos = begin_scan, close_bracket_pos = 0;
- while (open_bracket_pos < stripped_line.length() && (stripped_line[open_bracket_pos] == ' ' || stripped_line[open_bracket_pos] == '\t')) {
- open_bracket_pos++;
- }
- if (open_bracket_pos == stripped_line.length() || stripped_line[open_bracket_pos++] != '(') {
- continue; // invalid syntax.
- }
- close_bracket_pos = open_bracket_pos;
- while (close_bracket_pos < stripped_line.length() && stripped_line[close_bracket_pos] != ')') {
- close_bracket_pos++;
- }
- if (close_bracket_pos == stripped_line.length()) {
- continue; // invalid syntax.
- }
+ String title, link; // For tutorials.
- int colon_pos = close_bracket_pos + 1;
- while (colon_pos < stripped_line.length() && (stripped_line[colon_pos] == ' ' || stripped_line[colon_pos] == '\t')) {
- colon_pos++;
+ if (state == DOC_LINE_NORMAL) {
+ // Set the read mode.
+ String stripped_line = doc_line.strip_edges();
+ if (stripped_line.is_empty()) {
+ if (mode == BRIEF && !p_brief.is_empty()) {
+ mode = DESC;
}
- if (colon_pos == stripped_line.length() || stripped_line[colon_pos++] != ':') {
- continue; // invalid syntax.
+ continue;
+ } else if (stripped_line.begins_with("@tutorial")) {
+ int begin_scan = String("@tutorial").length();
+ if (begin_scan >= stripped_line.length()) {
+ continue; // Invalid syntax.
}
- title = stripped_line.substr(open_bracket_pos, close_bracket_pos - open_bracket_pos).strip_edges();
- link = stripped_line.substr(colon_pos).strip_edges();
- }
-
- mode = TUTORIALS;
- in_codeblock = false;
- } else if (stripped_line.is_empty()) {
- continue;
- } else {
- // Tutorial docs are single line, we need a @tag after it.
- if (mode == TUTORIALS) {
- mode = DONE;
- }
+ if (stripped_line[begin_scan] == ':') { // No title.
+ // Syntax: ## @tutorial: https://godotengine.org/ // The title argument is optional.
+ title = "";
+ link = stripped_line.trim_prefix("@tutorial:").strip_edges();
+ } else {
+ /* Syntax:
+ * @tutorial ( The Title Here ) : https://the.url/
+ * ^ open ^ close ^ colon ^ url
+ */
+ int open_bracket_pos = begin_scan, close_bracket_pos = 0;
+ while (open_bracket_pos < stripped_line.length() && (stripped_line[open_bracket_pos] == ' ' || stripped_line[open_bracket_pos] == '\t')) {
+ open_bracket_pos++;
+ }
+ if (open_bracket_pos == stripped_line.length() || stripped_line[open_bracket_pos++] != '(') {
+ continue; // Invalid syntax.
+ }
+ close_bracket_pos = open_bracket_pos;
+ while (close_bracket_pos < stripped_line.length() && stripped_line[close_bracket_pos] != ')') {
+ close_bracket_pos++;
+ }
+ if (close_bracket_pos == stripped_line.length()) {
+ continue; // Invalid syntax.
+ }
- in_codeblock = _in_codeblock(doc_line, in_codeblock, &codeblock_begins);
- }
+ int colon_pos = close_bracket_pos + 1;
+ while (colon_pos < stripped_line.length() && (stripped_line[colon_pos] == ' ' || stripped_line[colon_pos] == '\t')) {
+ colon_pos++;
+ }
+ if (colon_pos == stripped_line.length() || stripped_line[colon_pos++] != ':') {
+ continue; // Invalid syntax.
+ }
- if (in_codeblock) {
- int i = 0;
- for (; i < codeblock_begins; i++) {
- if (doc_line[i] != ' ') {
- break;
+ title = stripped_line.substr(open_bracket_pos, close_bracket_pos - open_bracket_pos).strip_edges();
+ link = stripped_line.substr(colon_pos).strip_edges();
}
+
+ mode = TUTORIALS;
+ } else if (mode == TUTORIALS) { // Tutorial docs are single line, we need a @tag after it.
+ mode = DONE;
}
- doc_line = doc_line.substr(i);
- } else {
- doc_line = stripped_line;
}
- String line_join = (in_codeblock) ? "\n" : " ";
switch (mode) {
case BRIEF:
- p_brief = (p_brief.length() == 0) ? doc_line : p_brief + line_join + doc_line;
+ p_brief += _process_doc_line(doc_line, p_brief, space_prefix, state);
break;
case DESC:
- p_desc = (p_desc.length() == 0) ? doc_line : p_desc + line_join + doc_line;
+ p_desc += _process_doc_line(doc_line, p_desc, space_prefix, state);
break;
case TUTORIALS:
p_tutorials.append(Pair<String, String>(title, link));
@@ -3507,6 +3580,7 @@ void GDScriptParser::get_class_doc_comment(int p_line, String &p_brief, String &
break;
}
}
+
if (current_class->members.size() > 0) {
const ClassNode::Member &m = current_class->members[0];
int first_member_line = m.get_line();
@@ -3753,8 +3827,12 @@ bool GDScriptParser::onready_annotation(const AnnotationNode *p_annotation, Node
}
VariableNode *variable = static_cast<VariableNode *>(p_node);
+ if (variable->is_static) {
+ push_error(R"("@onready" annotation cannot be applied to a static variable.)", p_annotation);
+ return false;
+ }
if (variable->onready) {
- push_error(R"("@onready" annotation can only be used once per variable.)");
+ push_error(R"("@onready" annotation can only be used once per variable.)", p_annotation);
return false;
}
variable->onready = true;
@@ -3767,6 +3845,10 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name));
VariableNode *variable = static_cast<VariableNode *>(p_node);
+ if (variable->is_static) {
+ push_error(vformat(R"(Annotation "%s" cannot be applied to a static variable.)", p_annotation->name), p_annotation);
+ return false;
+ }
if (variable->exported) {
push_error(vformat(R"(Annotation "%s" cannot be used with another "@export" annotation.)", p_annotation->name), p_annotation);
return false;
@@ -4067,21 +4149,16 @@ bool GDScriptParser::rpc_annotation(const AnnotationNode *p_annotation, Node *p_
Dictionary rpc_config;
rpc_config["rpc_mode"] = MultiplayerAPI::RPC_MODE_AUTHORITY;
if (!p_annotation->resolved_arguments.is_empty()) {
- int last = p_annotation->resolved_arguments.size() - 1;
- if (p_annotation->resolved_arguments[last].get_type() == Variant::INT) {
- rpc_config["channel"] = p_annotation->resolved_arguments[last].operator int();
- last -= 1;
- }
- if (last > 3) {
- push_error(R"(Invalid RPC arguments. At most 4 arguments are allowed, where only the last argument can be an integer to specify the channel.')", p_annotation);
- return false;
- }
-
unsigned char locality_args = 0;
unsigned char permission_args = 0;
unsigned char transfer_mode_args = 0;
- for (int i = last; i >= 0; i--) {
+ for (int i = 0; i < p_annotation->resolved_arguments.size(); i++) {
+ if (i == 3) {
+ rpc_config["channel"] = p_annotation->resolved_arguments[i].operator int();
+ continue;
+ }
+
String arg = p_annotation->resolved_arguments[i].operator String();
if (arg == "call_local") {
locality_args++;
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index 8f0265510f..ad08c3bfd6 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -31,6 +31,13 @@
#ifndef GDSCRIPT_PARSER_H
#define GDSCRIPT_PARSER_H
+#include "gdscript_cache.h"
+#include "gdscript_tokenizer.h"
+
+#ifdef DEBUG_ENABLED
+#include "gdscript_warning.h"
+#endif
+
#include "core/io/resource.h"
#include "core/object/ref_counted.h"
#include "core/object/script_language.h"
@@ -41,13 +48,10 @@
#include "core/templates/rb_map.h"
#include "core/templates/vector.h"
#include "core/variant/variant.h"
-#include "gdscript_cache.h"
-#include "gdscript_tokenizer.h"
#ifdef DEBUG_ENABLED
#include "core/string/string_builder.h"
-#include "gdscript_warning.h"
-#endif // DEBUG_ENABLED
+#endif
class GDScriptParser {
struct AnnotationInfo;
diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp
index a45a73a8d5..3927a4dd3e 100644
--- a/modules/gdscript/gdscript_tokenizer.cpp
+++ b/modules/gdscript/gdscript_tokenizer.cpp
@@ -33,13 +33,14 @@
#include "core/error/error_macros.h"
#include "core/string/char_utils.h"
-#ifdef TOOLS_ENABLED
-#include "editor/editor_settings.h"
-#endif
#ifdef DEBUG_ENABLED
#include "servers/text_server.h"
#endif
+#ifdef TOOLS_ENABLED
+#include "editor/editor_settings.h"
+#endif
+
static const char *token_names[] = {
"Empty", // EMPTY,
// Basic
@@ -162,6 +163,24 @@ const char *GDScriptTokenizer::Token::get_name() const {
return token_names[type];
}
+bool GDScriptTokenizer::Token::can_precede_bin_op() const {
+ switch (type) {
+ case IDENTIFIER:
+ case LITERAL:
+ case SELF:
+ case BRACKET_CLOSE:
+ case BRACE_CLOSE:
+ case PARENTHESIS_CLOSE:
+ case CONST_PI:
+ case CONST_TAU:
+ case CONST_INF:
+ case CONST_NAN:
+ return true;
+ default:
+ return false;
+ }
+}
+
bool GDScriptTokenizer::Token::is_identifier() const {
// Note: Most keywords should not be recognized as identifiers.
// These are only exceptions for stuff that already is on the engine's API.
@@ -382,6 +401,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::make_token(Token::Type p_type) {
}
}
+ last_token = token;
return token;
}
@@ -627,6 +647,7 @@ void GDScriptTokenizer::newline(bool p_make_token) {
newline.leftmost_column = newline.start_column;
newline.rightmost_column = newline.end_column;
pending_newline = true;
+ last_token = newline;
last_newline = newline;
}
@@ -643,6 +664,11 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() {
bool has_error = false;
bool (*digit_check_func)(char32_t) = is_digit;
+ // Sign before hexadecimal or binary.
+ if ((_peek(-1) == '+' || _peek(-1) == '-') && _peek() == '0') {
+ _advance();
+ }
+
if (_peek(-1) == '.') {
has_decimal = true;
} else if (_peek(-1) == '0') {
@@ -659,12 +685,20 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() {
}
}
- // Allow '_' to be used in a number, for readability.
- bool previous_was_underscore = false;
+ if (base != 10 && is_underscore(_peek())) { // Disallow `0x_` and `0b_`.
+ Token error = make_error(vformat(R"(Unexpected underscore after "0%c".)", _peek(-1)));
+ error.start_column = column;
+ error.leftmost_column = column;
+ error.end_column = column + 1;
+ error.rightmost_column = column + 1;
+ push_error(error);
+ has_error = true;
+ }
+ bool previous_was_underscore = false; // Allow `_` to be used in a number, for readability.
while (digit_check_func(_peek()) || is_underscore(_peek())) {
if (is_underscore(_peek())) {
if (previous_was_underscore) {
- Token error = make_error(R"(Only one underscore can be used as a numeric separator.)");
+ Token error = make_error(R"(Multiple underscores cannot be adjacent in a numeric literal.)");
error.start_column = column;
error.leftmost_column = column;
error.end_column = column + 1;
@@ -711,7 +745,30 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() {
_advance();
// Consume decimal digits.
+ if (is_underscore(_peek())) { // Disallow `10._`, but allow `10.`.
+ Token error = make_error(R"(Unexpected underscore after decimal point.)");
+ error.start_column = column;
+ error.leftmost_column = column;
+ error.end_column = column + 1;
+ error.rightmost_column = column + 1;
+ push_error(error);
+ has_error = true;
+ }
+ previous_was_underscore = false;
while (is_digit(_peek()) || is_underscore(_peek())) {
+ if (is_underscore(_peek())) {
+ if (previous_was_underscore) {
+ Token error = make_error(R"(Multiple underscores cannot be adjacent in a numeric literal.)");
+ error.start_column = column;
+ error.leftmost_column = column;
+ error.end_column = column + 1;
+ error.rightmost_column = column + 1;
+ push_error(error);
+ }
+ previous_was_underscore = true;
+ } else {
+ previous_was_underscore = false;
+ }
_advance();
}
}
@@ -737,7 +794,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() {
while (is_digit(_peek()) || is_underscore(_peek())) {
if (is_underscore(_peek())) {
if (previous_was_underscore) {
- Token error = make_error(R"(Only one underscore can be used as a numeric separator.)");
+ Token error = make_error(R"(Multiple underscores cannot be adjacent in a numeric literal.)");
error.start_column = column;
error.leftmost_column = column;
error.end_column = column + 1;
@@ -1431,6 +1488,9 @@ GDScriptTokenizer::Token GDScriptTokenizer::scan() {
if (_peek() == '=') {
_advance();
return make_token(Token::PLUS_EQUAL);
+ } else if (is_digit(_peek()) && !last_token.can_precede_bin_op()) {
+ // Number starting with '+'.
+ return number();
} else {
return make_token(Token::PLUS);
}
@@ -1438,6 +1498,9 @@ GDScriptTokenizer::Token GDScriptTokenizer::scan() {
if (_peek() == '=') {
_advance();
return make_token(Token::MINUS_EQUAL);
+ } else if (is_digit(_peek()) && !last_token.can_precede_bin_op()) {
+ // Number starting with '-'.
+ return number();
} else if (_peek() == '>') {
_advance();
return make_token(Token::FORWARD_ARROW);
@@ -1547,9 +1610,9 @@ GDScriptTokenizer::Token GDScriptTokenizer::scan() {
default:
if (is_whitespace(c)) {
- return make_error(vformat(R"(Invalid white space character "\\u%X".)", static_cast<int32_t>(c)));
+ return make_error(vformat(R"(Invalid white space character U+%04X.)", static_cast<int32_t>(c)));
} else {
- return make_error(vformat(R"(Unknown character "%s".)", String(&c, 1)));
+ return make_error(vformat(R"(Invalid character "%c" (U+%04X).)", c, static_cast<int32_t>(c)));
}
}
}
diff --git a/modules/gdscript/gdscript_tokenizer.h b/modules/gdscript/gdscript_tokenizer.h
index 608840d3f1..068393cee9 100644
--- a/modules/gdscript/gdscript_tokenizer.h
+++ b/modules/gdscript/gdscript_tokenizer.h
@@ -171,6 +171,7 @@ public:
String source;
const char *get_name() const;
+ bool can_precede_bin_op() const;
bool is_identifier() const;
bool is_node_name() const;
StringName get_identifier() const { return source; }
@@ -216,6 +217,7 @@ private:
bool multiline_mode = false;
List<Token> error_stack;
bool pending_newline = false;
+ Token last_token;
Token last_newline;
int pending_indents = 0;
List<int> indent_stack;
diff --git a/modules/gdscript/gdscript_utility_functions.cpp b/modules/gdscript/gdscript_utility_functions.cpp
index 8862450121..030950267d 100644
--- a/modules/gdscript/gdscript_utility_functions.cpp
+++ b/modules/gdscript/gdscript_utility_functions.cpp
@@ -30,6 +30,8 @@
#include "gdscript_utility_functions.h"
+#include "gdscript.h"
+
#include "core/io/resource_loader.h"
#include "core/object/class_db.h"
#include "core/object/method_bind.h"
@@ -37,7 +39,6 @@
#include "core/templates/oa_hash_map.h"
#include "core/templates/vector.h"
#include "core/variant/typed_array.h"
-#include "gdscript.h"
#ifdef DEBUG_ENABLED
diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp
index bebf34cbb3..4545689bd9 100644
--- a/modules/gdscript/gdscript_vm.cpp
+++ b/modules/gdscript/gdscript_vm.cpp
@@ -28,12 +28,12 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
+#include "gdscript.h"
#include "gdscript_function.h"
+#include "gdscript_lambda_callable.h"
#include "core/core_string_names.h"
#include "core/os/os.h"
-#include "gdscript.h"
-#include "gdscript_lambda_callable.h"
#ifdef DEBUG_ENABLED
static String _get_script_name(const Ref<Script> p_script) {
@@ -217,6 +217,8 @@ void (*type_init_function_table[])(Variant *) = {
&&OPCODE_GET_NAMED_VALIDATED, \
&&OPCODE_SET_MEMBER, \
&&OPCODE_GET_MEMBER, \
+ &&OPCODE_SET_STATIC_VARIABLE, \
+ &&OPCODE_GET_STATIC_VARIABLE, \
&&OPCODE_ASSIGN, \
&&OPCODE_ASSIGN_TRUE, \
&&OPCODE_ASSIGN_FALSE, \
@@ -666,7 +668,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
Variant *m_v = instruction_args[m_idx]
#ifdef DEBUG_ENABLED
-
uint64_t function_start_time = 0;
uint64_t function_call_time = 0;
@@ -679,11 +680,12 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
bool exit_ok = false;
bool awaited = false;
#endif
+
#ifdef DEBUG_ENABLED
- int variant_address_limits[ADDR_TYPE_MAX] = { _stack_size, _constant_count, p_instance ? p_instance->members.size() : 0, script->static_variables.size() };
+ int variant_address_limits[ADDR_TYPE_MAX] = { _stack_size, _constant_count, p_instance ? p_instance->members.size() : 0 };
#endif
- Variant *variant_addresses[ADDR_TYPE_MAX] = { stack, _constants_ptr, p_instance ? p_instance->members.ptrw() : nullptr, script->static_variables.ptrw() };
+ Variant *variant_addresses[ADDR_TYPE_MAX] = { stack, _constants_ptr, p_instance ? p_instance->members.ptrw() : nullptr };
#ifdef DEBUG_ENABLED
OPCODE_WHILE(ip < _code_size) {
@@ -1171,6 +1173,42 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
}
DISPATCH_OPCODE;
+ OPCODE(OPCODE_SET_STATIC_VARIABLE) {
+ CHECK_SPACE(4);
+
+ GET_VARIANT_PTR(value, 0);
+
+ GET_VARIANT_PTR(_class, 1);
+ GDScript *gdscript = Object::cast_to<GDScript>(_class->operator Object *());
+ GD_ERR_BREAK(!gdscript);
+
+ int index = _code_ptr[ip + 3];
+ GD_ERR_BREAK(index < 0 || index >= gdscript->static_variables.size());
+
+ gdscript->static_variables.write[index] = *value;
+
+ ip += 4;
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_GET_STATIC_VARIABLE) {
+ CHECK_SPACE(4);
+
+ GET_VARIANT_PTR(target, 0);
+
+ GET_VARIANT_PTR(_class, 1);
+ GDScript *gdscript = Object::cast_to<GDScript>(_class->operator Object *());
+ GD_ERR_BREAK(!gdscript);
+
+ int index = _code_ptr[ip + 3];
+ GD_ERR_BREAK(index < 0 || index >= gdscript->static_variables.size());
+
+ *target = gdscript->static_variables[index];
+
+ ip += 4;
+ }
+ DISPATCH_OPCODE;
+
OPCODE(OPCODE_ASSIGN) {
CHECK_SPACE(3);
GET_VARIANT_PTR(dst, 0);
@@ -3620,7 +3658,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
#endif
// Free stack, except reserved addresses.
- for (int i = 3; i < _stack_size; i++) {
+ for (int i = FIXED_ADDRESSES_MAX; i < _stack_size; i++) {
stack[i].~Variant();
}
#ifdef DEBUG_ENABLED
@@ -3628,7 +3666,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
#endif
// Always free reserved addresses, since they are never copied.
- for (int i = 0; i < 3; i++) {
+ for (int i = 0; i < FIXED_ADDRESSES_MAX; i++) {
stack[i].~Variant();
}
diff --git a/modules/gdscript/language_server/gdscript_extend_parser.h b/modules/gdscript/language_server/gdscript_extend_parser.h
index 4500cb01f3..4fd27de081 100644
--- a/modules/gdscript/language_server/gdscript_extend_parser.h
+++ b/modules/gdscript/language_server/gdscript_extend_parser.h
@@ -32,9 +32,10 @@
#define GDSCRIPT_EXTEND_PARSER_H
#include "../gdscript_parser.h"
-#include "core/variant/variant.h"
#include "godot_lsp.h"
+#include "core/variant/variant.h"
+
#ifndef LINE_NUMBER_TO_INDEX
#define LINE_NUMBER_TO_INDEX(p_line) ((p_line)-1)
#endif
diff --git a/modules/gdscript/language_server/gdscript_language_protocol.h b/modules/gdscript/language_server/gdscript_language_protocol.h
index d25814f8aa..a4d9dc6b1d 100644
--- a/modules/gdscript/language_server/gdscript_language_protocol.h
+++ b/modules/gdscript/language_server/gdscript_language_protocol.h
@@ -31,13 +31,14 @@
#ifndef GDSCRIPT_LANGUAGE_PROTOCOL_H
#define GDSCRIPT_LANGUAGE_PROTOCOL_H
-#include "core/io/stream_peer.h"
-#include "core/io/stream_peer_tcp.h"
-#include "core/io/tcp_server.h"
#include "gdscript_text_document.h"
#include "gdscript_workspace.h"
#include "godot_lsp.h"
+#include "core/io/stream_peer.h"
+#include "core/io/stream_peer_tcp.h"
+#include "core/io/tcp_server.h"
+
#include "modules/modules_enabled.gen.h" // For jsonrpc.
#ifdef MODULE_JSONRPC_ENABLED
#include "modules/jsonrpc/jsonrpc.h"
diff --git a/modules/gdscript/language_server/gdscript_language_server.h b/modules/gdscript/language_server/gdscript_language_server.h
index a130dc3ac8..75f9403a74 100644
--- a/modules/gdscript/language_server/gdscript_language_server.h
+++ b/modules/gdscript/language_server/gdscript_language_server.h
@@ -32,9 +32,10 @@
#define GDSCRIPT_LANGUAGE_SERVER_H
#include "../gdscript_parser.h"
-#include "editor/editor_plugin.h"
#include "gdscript_language_protocol.h"
+#include "editor/editor_plugin.h"
+
class GDScriptLanguageServer : public EditorPlugin {
GDCLASS(GDScriptLanguageServer, EditorPlugin);
diff --git a/modules/gdscript/language_server/gdscript_text_document.cpp b/modules/gdscript/language_server/gdscript_text_document.cpp
index 35fbdca949..92a5f55978 100644
--- a/modules/gdscript/language_server/gdscript_text_document.cpp
+++ b/modules/gdscript/language_server/gdscript_text_document.cpp
@@ -31,11 +31,12 @@
#include "gdscript_text_document.h"
#include "../gdscript.h"
+#include "gdscript_extend_parser.h"
+#include "gdscript_language_protocol.h"
+
#include "core/os/os.h"
#include "editor/editor_settings.h"
#include "editor/plugins/script_text_editor.h"
-#include "gdscript_extend_parser.h"
-#include "gdscript_language_protocol.h"
#include "servers/display_server.h"
void GDScriptTextDocument::_bind_methods() {
diff --git a/modules/gdscript/language_server/gdscript_text_document.h b/modules/gdscript/language_server/gdscript_text_document.h
index aeda10de89..0121101db2 100644
--- a/modules/gdscript/language_server/gdscript_text_document.h
+++ b/modules/gdscript/language_server/gdscript_text_document.h
@@ -31,9 +31,10 @@
#ifndef GDSCRIPT_TEXT_DOCUMENT_H
#define GDSCRIPT_TEXT_DOCUMENT_H
+#include "godot_lsp.h"
+
#include "core/io/file_access.h"
#include "core/object/ref_counted.h"
-#include "godot_lsp.h"
class GDScriptTextDocument : public RefCounted {
GDCLASS(GDScriptTextDocument, RefCounted)
diff --git a/modules/gdscript/language_server/gdscript_workspace.cpp b/modules/gdscript/language_server/gdscript_workspace.cpp
index 7fc2962341..9f848b02f5 100644
--- a/modules/gdscript/language_server/gdscript_workspace.cpp
+++ b/modules/gdscript/language_server/gdscript_workspace.cpp
@@ -32,6 +32,8 @@
#include "../gdscript.h"
#include "../gdscript_parser.h"
+#include "gdscript_language_protocol.h"
+
#include "core/config/project_settings.h"
#include "core/object/script_language.h"
#include "editor/doc_tools.h"
@@ -39,7 +41,6 @@
#include "editor/editor_help.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
-#include "gdscript_language_protocol.h"
#include "scene/resources/packed_scene.h"
void GDScriptWorkspace::_bind_methods() {
diff --git a/modules/gdscript/language_server/gdscript_workspace.h b/modules/gdscript/language_server/gdscript_workspace.h
index a849ef8a8d..80653778fb 100644
--- a/modules/gdscript/language_server/gdscript_workspace.h
+++ b/modules/gdscript/language_server/gdscript_workspace.h
@@ -32,11 +32,12 @@
#define GDSCRIPT_WORKSPACE_H
#include "../gdscript_parser.h"
-#include "core/variant/variant.h"
-#include "editor/editor_file_system.h"
#include "gdscript_extend_parser.h"
#include "godot_lsp.h"
+#include "core/variant/variant.h"
+#include "editor/editor_file_system.h"
+
class GDScriptWorkspace : public RefCounted {
GDCLASS(GDScriptWorkspace, RefCounted);
diff --git a/modules/gdscript/register_types.cpp b/modules/gdscript/register_types.cpp
index b6feaadccf..e23bd50b8b 100644
--- a/modules/gdscript/register_types.cpp
+++ b/modules/gdscript/register_types.cpp
@@ -30,39 +30,51 @@
#include "register_types.h"
-#include "core/io/dir_access.h"
-#include "core/io/file_access.h"
-#include "core/io/file_access_encrypted.h"
-#include "core/io/resource_loader.h"
#include "gdscript.h"
#include "gdscript_analyzer.h"
#include "gdscript_cache.h"
#include "gdscript_tokenizer.h"
#include "gdscript_utility_functions.h"
+#ifdef TOOLS_ENABLED
+#include "editor/gdscript_highlighter.h"
+#include "editor/gdscript_translation_parser_plugin.h"
+
+#ifndef GDSCRIPT_NO_LSP
+#include "language_server/gdscript_language_server.h"
+#endif
+#endif // TOOLS_ENABLED
+
#ifdef TESTS_ENABLED
#include "tests/test_gdscript.h"
-#include "tests/test_macros.h"
#endif
-GDScriptLanguage *script_language_gd = nullptr;
-Ref<ResourceFormatLoaderGDScript> resource_loader_gd;
-Ref<ResourceFormatSaverGDScript> resource_saver_gd;
-GDScriptCache *gdscript_cache = nullptr;
+#include "core/io/dir_access.h"
+#include "core/io/file_access.h"
+#include "core/io/file_access_encrypted.h"
+#include "core/io/resource_loader.h"
#ifdef TOOLS_ENABLED
-
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/editor_translation_parser.h"
#include "editor/export/editor_export.h"
-#include "editor/gdscript_highlighter.h"
-#include "editor/gdscript_translation_parser_plugin.h"
#ifndef GDSCRIPT_NO_LSP
#include "core/config/engine.h"
-#include "language_server/gdscript_language_server.h"
-#endif // !GDSCRIPT_NO_LSP
+#endif
+#endif // TOOLS_ENABLED
+
+#ifdef TESTS_ENABLED
+#include "tests/test_macros.h"
+#endif
+
+GDScriptLanguage *script_language_gd = nullptr;
+Ref<ResourceFormatLoaderGDScript> resource_loader_gd;
+Ref<ResourceFormatSaverGDScript> resource_saver_gd;
+GDScriptCache *gdscript_cache = nullptr;
+
+#ifdef TOOLS_ENABLED
Ref<GDScriptEditorTranslationParserPlugin> gdscript_translation_parser_plugin;
diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp
index b8448d16c2..874cbc6ee8 100644
--- a/modules/gdscript/tests/gdscript_test_runner.cpp
+++ b/modules/gdscript/tests/gdscript_test_runner.cpp
@@ -392,6 +392,9 @@ void GDScriptTest::error_handler(void *p_this, const char *p_function, const cha
StringBuilder builder;
builder.append(">> ");
+ // Only include the function, file and line for script errors, otherwise the
+ // test outputs changes based on the platform/compiler.
+ bool include_source_info = false;
switch (p_type) {
case ERR_HANDLER_ERROR:
builder.append("ERROR");
@@ -401,6 +404,7 @@ void GDScriptTest::error_handler(void *p_this, const char *p_function, const cha
break;
case ERR_HANDLER_SCRIPT:
builder.append("SCRIPT ERROR");
+ include_source_info = true;
break;
case ERR_HANDLER_SHADER:
builder.append("SHADER ERROR");
@@ -410,12 +414,14 @@ void GDScriptTest::error_handler(void *p_this, const char *p_function, const cha
break;
}
- builder.append("\n>> on function: ");
- builder.append(String::utf8(p_function));
- builder.append("()\n>> ");
- builder.append(String::utf8(p_file).trim_prefix(self->base_dir));
- builder.append("\n>> ");
- builder.append(itos(p_line));
+ if (include_source_info) {
+ builder.append("\n>> on function: ");
+ builder.append(String::utf8(p_function));
+ builder.append("()\n>> ");
+ builder.append(String::utf8(p_file).trim_prefix(self->base_dir).replace("\\", "/"));
+ builder.append("\n>> ");
+ builder.append(itos(p_line));
+ }
builder.append("\n>> ");
builder.append(String::utf8(p_error));
if (strlen(p_explanation) > 0) {
diff --git a/modules/gdscript/tests/gdscript_test_runner.h b/modules/gdscript/tests/gdscript_test_runner.h
index 60b48c6a57..b1190604ad 100644
--- a/modules/gdscript/tests/gdscript_test_runner.h
+++ b/modules/gdscript/tests/gdscript_test_runner.h
@@ -32,6 +32,7 @@
#define GDSCRIPT_TEST_RUNNER_H
#include "../gdscript.h"
+
#include "core/error/error_macros.h"
#include "core/string/print_string.h"
#include "core/string/ustring.h"
diff --git a/modules/gdscript/tests/gdscript_test_runner_suite.h b/modules/gdscript/tests/gdscript_test_runner_suite.h
index e27b6218f1..5fd7d942d2 100644
--- a/modules/gdscript/tests/gdscript_test_runner_suite.h
+++ b/modules/gdscript/tests/gdscript_test_runner_suite.h
@@ -32,6 +32,7 @@
#define GDSCRIPT_TEST_RUNNER_SUITE_H
#include "gdscript_test_runner.h"
+
#include "tests/test_macros.h"
namespace GDScriptTests {
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static.gd b/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static.gd
new file mode 100644
index 0000000000..1d8f076857
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static.gd
@@ -0,0 +1,8 @@
+static func static_func():
+ non_static_func()
+
+func non_static_func():
+ pass
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static.out b/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static.out
new file mode 100644
index 0000000000..b78f131345
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot call non-static function "non_static_func()" from static function "static_func()".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_var_export_annotation.gd b/modules/gdscript/tests/scripts/analyzer/errors/static_var_export_annotation.gd
new file mode 100644
index 0000000000..66697cbb29
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/static_var_export_annotation.gd
@@ -0,0 +1,8 @@
+# GH-77098 p.3
+
+@static_unload
+
+@export static var a: int
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_var_export_annotation.out b/modules/gdscript/tests/scripts/analyzer/errors/static_var_export_annotation.out
new file mode 100644
index 0000000000..4111aa07af
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/static_var_export_annotation.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Annotation "@export" cannot be applied to a static variable.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/virtual_method_not_implemented.gd b/modules/gdscript/tests/scripts/analyzer/errors/virtual_method_not_implemented.gd
new file mode 100644
index 0000000000..c34d927035
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/virtual_method_not_implemented.gd
@@ -0,0 +1,2 @@
+func test():
+ _get_property_list()
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/virtual_method_not_implemented.out b/modules/gdscript/tests/scripts/analyzer/errors/virtual_method_not_implemented.out
new file mode 100644
index 0000000000..ce2f49a5e5
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/virtual_method_not_implemented.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot call virtual function "_get_property_list()" because it hasn't been defined.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/virtual_super_not_implemented.gd b/modules/gdscript/tests/scripts/analyzer/errors/virtual_super_not_implemented.gd
new file mode 100644
index 0000000000..57dfffdbee
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/virtual_super_not_implemented.gd
@@ -0,0 +1,5 @@
+func _init():
+ super()
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/virtual_super_not_implemented.out b/modules/gdscript/tests/scripts/analyzer/errors/virtual_super_not_implemented.out
new file mode 100644
index 0000000000..e68759223c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/virtual_super_not_implemented.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot call the parent class' virtual function "_init()" because it hasn't been defined.
diff --git a/modules/gdscript/tests/scripts/analyzer/features/boolean_operators_for_all_types.gd b/modules/gdscript/tests/scripts/analyzer/features/boolean_operators_for_all_types.gd
new file mode 100644
index 0000000000..73d0f9096c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/boolean_operators_for_all_types.gd
@@ -0,0 +1,347 @@
+extends Resource
+
+signal foo
+
+func test():
+ var x
+ # TYPE_NIL
+ x = null
+ prints("TYPE_NIL")
+ prints(not x)
+ prints(x and false)
+ prints(x and true)
+ prints(x or false)
+ prints(x or true)
+
+ # TYPE_BOOL
+ x = true
+ prints("TYPE_BOOL")
+ prints(not x)
+ prints(x and false)
+ prints(x and true)
+ prints(x or false)
+ prints(x or true)
+
+ # TYPE_INT
+ x = 1
+ prints("TYPE_INT")
+ prints(not x)
+ prints(x and false)
+ prints(x and true)
+ prints(x or false)
+ prints(x or true)
+
+ # TYPE_FLOAT
+ x = 1.1
+ prints("TYPE_FLOAT")
+ prints(not x)
+ prints(x and false)
+ prints(x and true)
+ prints(x or false)
+ prints(x or true)
+
+ # TYPE_STRING
+ x = "foo"
+ prints("TYPE_STRING")
+ prints(not x)
+ prints(x and false)
+ prints(x and true)
+ prints(x or false)
+ prints(x or true)
+
+ # TYPE_VECTOR2
+ x = Vector2(1, 1)
+ prints("TYPE_VECTOR2")
+ prints(not x)
+ prints(x and false)
+ prints(x and true)
+ prints(x or false)
+ prints(x or true)
+
+ # TYPE_VECTOR2I
+ x = Vector2i(1, 1)
+ prints("TYPE_VECTOR2I")
+ prints(not x)
+ prints(x and false)
+ prints(x and true)
+ prints(x or false)
+ prints(x or true)
+
+ # TYPE_RECT2
+ x = Rect2(1, 1, 1, 1)
+ prints("TYPE_RECT2")
+ prints(not x)
+ prints(x and false)
+ prints(x and true)
+ prints(x or false)
+ prints(x or true)
+
+ # TYPE_RECT2I
+ x = Rect2i(1, 1, 1, 1)
+ prints("TYPE_RECT2I")
+ prints(not x)
+ prints(x and false)
+ prints(x and true)
+ prints(x or false)
+ prints(x or true)
+
+ # TYPE_VECTOR3
+ x = Vector3(1, 1, 1)
+ prints("TYPE_VECTOR3")
+ prints(not x)
+ prints(x and false)
+ prints(x and true)
+ prints(x or false)
+ prints(x or true)
+
+ # TYPE_VECTOR3I
+ x = Vector3i(1, 1, 1)
+ prints("TYPE_VECTOR3I")
+ prints(not x)
+ prints(x and false)
+ prints(x and true)
+ prints(x or false)
+ prints(x or true)
+
+ # TYPE_TRANSFORM2D
+ x = Transform2D.IDENTITY
+ prints("TYPE_TRANSFORM2D")
+ prints(not x)
+ prints(x and false)
+ prints(x and true)
+ prints(x or false)
+ prints(x or true)
+
+ # TYPE_VECTOR4
+ x = Vector4(1, 1, 1, 1)
+ prints("TYPE_VECTOR4")
+ prints(not x)
+ prints(x and false)
+ prints(x and true)
+ prints(x or false)
+ prints(x or true)
+
+ # TYPE_VECTOR4I
+ x = Vector4i(1, 1, 1, 1)
+ prints("TYPE_VECTOR4I")
+ prints(not x)
+ prints(x and false)
+ prints(x and true)
+ prints(x or false)
+ prints(x or true)
+
+ # TYPE_PLANE
+ x = Plane.PLANE_XY
+ prints("TYPE_PLANE")
+ prints(not x)
+ prints(x and false)
+ prints(x and true)
+ prints(x or false)
+ prints(x or true)
+
+ # TYPE_QUATERNION
+ x = Quaternion.IDENTITY
+ prints("TYPE_QUATERNION")
+ prints(not x)
+ prints(x and false)
+ prints(x and true)
+ prints(x or false)
+ prints(x or true)
+
+ # TYPE_AABB
+ x = AABB(Vector3.ONE, Vector3.ONE)
+ prints("TYPE_AABB")
+ prints(not x)
+ prints(x and false)
+ prints(x and true)
+ prints(x or false)
+ prints(x or true)
+
+ # TYPE_BASIS
+ x = Basis.IDENTITY
+ prints("TYPE_BASIS")
+ prints(not x)
+ prints(x and false)
+ prints(x and true)
+ prints(x or false)
+ prints(x or true)
+
+ # TYPE_TRANSFORM3D
+ x = Transform3D.IDENTITY
+ prints("TYPE_TRANSFORM3D")
+ prints(not x)
+ prints(x and false)
+ prints(x and true)
+ prints(x or false)
+ prints(x or true)
+
+ # TYPE_PROJECTION
+ x = Projection.IDENTITY
+ prints("TYPE_PROJECTION")
+ prints(not x)
+ prints(x and false)
+ prints(x and true)
+ prints(x or false)
+ prints(x or true)
+
+ # TYPE_COLOR
+ x = Color.WHITE
+ prints("TYPE_COLOR")
+ prints(not x)
+ prints(x and false)
+ prints(x and true)
+ prints(x or false)
+ prints(x or true)
+
+ # TYPE_STRING_NAME
+ x = &"name"
+ prints("TYPE_STRING_NAME")
+ prints(not x)
+ prints(x and false)
+ prints(x and true)
+ prints(x or false)
+ prints(x or true)
+
+ # TYPE_NODE_PATH
+ x = ^"path"
+ prints("TYPE_NODE_PATH")
+ prints(not x)
+ prints(x and false)
+ prints(x and true)
+ prints(x or false)
+ prints(x or true)
+
+ # TYPE_RID
+ x = get_rid()
+ prints("TYPE_RID")
+ prints(not x)
+ prints(x and false)
+ prints(x and true)
+ prints(x or false)
+ prints(x or true)
+
+ # TYPE_OBJECT
+ x = self
+ prints("TYPE_OBJECT")
+ prints(not x)
+ prints(x and false)
+ prints(x and true)
+ prints(x or false)
+ prints(x or true)
+
+ # TYPE_CALLABLE
+ x = test
+ prints("TYPE_CALLABLE")
+ prints(not x)
+ prints(x and false)
+ prints(x and true)
+ prints(x or false)
+ prints(x or true)
+
+ # TYPE_SIGNAL
+ x = foo
+ prints("TYPE_SIGNAL")
+ prints(not x)
+ prints(x and false)
+ prints(x and true)
+ prints(x or false)
+ prints(x or true)
+
+ # TYPE_DICTIONARY
+ x = { a = 1}
+ prints("TYPE_DICTIONARY")
+ prints(not x)
+ prints(x and false)
+ prints(x and true)
+ prints(x or false)
+ prints(x or true)
+
+ # TYPE_ARRAY
+ x = [1]
+ prints("TYPE_ARRAY")
+ prints(not x)
+ prints(x and false)
+ prints(x and true)
+ prints(x or false)
+ prints(x or true)
+
+ # TYPE_PACKED_BYTE_ARRAY
+ x = PackedByteArray([1])
+ prints("TYPE_PACKED_BYTE_ARRAY")
+ prints(not x)
+ prints(x and false)
+ prints(x and true)
+ prints(x or false)
+ prints(x or true)
+
+ # TYPE_PACKED_INT32_ARRAY
+ x = PackedInt32Array([1])
+ prints("TYPE_PACKED_INT32_ARRAY")
+ prints(not x)
+ prints(x and false)
+ prints(x and true)
+ prints(x or false)
+ prints(x or true)
+
+ # TYPE_PACKED_INT64_ARRAY
+ x = PackedInt64Array([1])
+ prints("TYPE_PACKED_INT64_ARRAY")
+ prints(not x)
+ prints(x and false)
+ prints(x and true)
+ prints(x or false)
+ prints(x or true)
+
+ # TYPE_PACKED_FLOAT32_ARRAY
+ x = PackedFloat32Array([1])
+ prints("TYPE_PACKED_FLOAT32_ARRAY")
+ prints(not x)
+ prints(x and false)
+ prints(x and true)
+ prints(x or false)
+ prints(x or true)
+
+ # TYPE_PACKED_FLOAT64_ARRAY
+ x = PackedFloat64Array([1])
+ prints("TYPE_PACKED_FLOAT64_ARRAY")
+ prints(not x)
+ prints(x and false)
+ prints(x and true)
+ prints(x or false)
+ prints(x or true)
+
+ # TYPE_PACKED_STRING_ARRAY
+ x = PackedStringArray(["1"])
+ prints("TYPE_PACKED_STRING_ARRAY")
+ prints(not x)
+ prints(x and false)
+ prints(x and true)
+ prints(x or false)
+ prints(x or true)
+
+ # TYPE_PACKED_VECTOR2_ARRAY
+ x = PackedVector2Array([Vector2.ONE])
+ prints("TYPE_PACKED_VECTOR2_ARRAY")
+ prints(not x)
+ prints(x and false)
+ prints(x and true)
+ prints(x or false)
+ prints(x or true)
+
+ # TYPE_PACKED_VECTOR3_ARRAY
+ x = PackedVector3Array([Vector3.ONE])
+ prints("TYPE_PACKED_VECTOR3_ARRAY")
+ prints(not x)
+ prints(x and false)
+ prints(x and true)
+ prints(x or false)
+ prints(x or true)
+
+ # TYPE_PACKED_COLOR_ARRAY
+ x = PackedColorArray([Color.WHITE])
+ prints("TYPE_PACKED_COLOR_ARRAY")
+ prints(not x)
+ prints(x and false)
+ prints(x and true)
+ prints(x or false)
+ prints(x or true)
diff --git a/modules/gdscript/tests/scripts/analyzer/features/boolean_operators_for_all_types.out b/modules/gdscript/tests/scripts/analyzer/features/boolean_operators_for_all_types.out
new file mode 100644
index 0000000000..e2945c910a
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/boolean_operators_for_all_types.out
@@ -0,0 +1,229 @@
+GDTEST_OK
+TYPE_NIL
+true
+false
+false
+false
+true
+TYPE_BOOL
+false
+false
+true
+true
+true
+TYPE_INT
+false
+false
+true
+true
+true
+TYPE_FLOAT
+false
+false
+true
+true
+true
+TYPE_STRING
+false
+false
+true
+true
+true
+TYPE_VECTOR2
+false
+false
+true
+true
+true
+TYPE_VECTOR2I
+false
+false
+true
+true
+true
+TYPE_RECT2
+false
+false
+true
+true
+true
+TYPE_RECT2I
+false
+false
+true
+true
+true
+TYPE_VECTOR3
+false
+false
+true
+true
+true
+TYPE_VECTOR3I
+false
+false
+true
+true
+true
+TYPE_TRANSFORM2D
+true
+false
+false
+false
+true
+TYPE_VECTOR4
+false
+false
+true
+true
+true
+TYPE_VECTOR4I
+false
+false
+true
+true
+true
+TYPE_PLANE
+false
+false
+true
+true
+true
+TYPE_QUATERNION
+true
+false
+false
+false
+true
+TYPE_AABB
+false
+false
+true
+true
+true
+TYPE_BASIS
+true
+false
+false
+false
+true
+TYPE_TRANSFORM3D
+true
+false
+false
+false
+true
+TYPE_PROJECTION
+true
+false
+false
+false
+true
+TYPE_COLOR
+false
+false
+true
+true
+true
+TYPE_STRING_NAME
+false
+false
+true
+true
+true
+TYPE_NODE_PATH
+false
+false
+true
+true
+true
+TYPE_RID
+true
+false
+false
+false
+true
+TYPE_OBJECT
+false
+false
+true
+true
+true
+TYPE_CALLABLE
+false
+false
+true
+true
+true
+TYPE_SIGNAL
+false
+false
+true
+true
+true
+TYPE_DICTIONARY
+false
+false
+true
+true
+true
+TYPE_ARRAY
+false
+false
+true
+true
+true
+TYPE_PACKED_BYTE_ARRAY
+false
+false
+true
+true
+true
+TYPE_PACKED_INT32_ARRAY
+false
+false
+true
+true
+true
+TYPE_PACKED_INT64_ARRAY
+false
+false
+true
+true
+true
+TYPE_PACKED_FLOAT32_ARRAY
+false
+false
+true
+true
+true
+TYPE_PACKED_FLOAT64_ARRAY
+false
+false
+true
+true
+true
+TYPE_PACKED_STRING_ARRAY
+false
+false
+true
+true
+true
+TYPE_PACKED_VECTOR2_ARRAY
+false
+false
+true
+true
+true
+TYPE_PACKED_VECTOR3_ARRAY
+false
+false
+true
+true
+true
+TYPE_PACKED_COLOR_ARRAY
+false
+false
+true
+true
+true
diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd
index 26542a9e2f..c4108f50de 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd
@@ -116,8 +116,8 @@ func test():
assert(duplicated_floats.get_typed_builtin() == TYPE_FLOAT)
- var b_objects: Array[B] = [B.new(), null]
- assert(b_objects.size() == 2)
+ var b_objects: Array[B] = [B.new(), B.new() as A, null]
+ assert(b_objects.size() == 3)
assert(b_objects.get_typed_builtin() == TYPE_OBJECT)
assert(b_objects.get_typed_script() == B)
diff --git a/modules/gdscript/tests/scripts/analyzer/features/virtual_method_implemented.gd b/modules/gdscript/tests/scripts/analyzer/features/virtual_method_implemented.gd
new file mode 100644
index 0000000000..1aacd1d11c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/virtual_method_implemented.gd
@@ -0,0 +1,11 @@
+class TestOne:
+ func _get_property_list():
+ return {}
+
+class TestTwo extends TestOne:
+ func _init():
+ var _x = _get_property_list()
+
+func test():
+ var x = TestTwo.new()
+ var _x = x._get_property_list()
diff --git a/modules/gdscript/tests/scripts/analyzer/features/virtual_method_implemented.out b/modules/gdscript/tests/scripts/analyzer/features/virtual_method_implemented.out
new file mode 100644
index 0000000000..d73c5eb7cd
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/virtual_method_implemented.out
@@ -0,0 +1 @@
+GDTEST_OK
diff --git a/modules/gdscript/tests/scripts/analyzer/features/virtual_super_implemented.gd b/modules/gdscript/tests/scripts/analyzer/features/virtual_super_implemented.gd
new file mode 100644
index 0000000000..c447003619
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/virtual_super_implemented.gd
@@ -0,0 +1,10 @@
+class TestOne:
+ func _init():
+ pass
+
+class TestTwo extends TestOne:
+ func _init():
+ super()
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/features/virtual_super_implemented.out b/modules/gdscript/tests/scripts/analyzer/features/virtual_super_implemented.out
new file mode 100644
index 0000000000..d73c5eb7cd
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/virtual_super_implemented.out
@@ -0,0 +1 @@
+GDTEST_OK
diff --git a/modules/gdscript/tests/scripts/parser/errors/multiple_number_separators.gd b/modules/gdscript/tests/scripts/parser/errors/multiple_number_separators.gd
index 71a03fbc0d..f322783776 100644
--- a/modules/gdscript/tests/scripts/parser/errors/multiple_number_separators.gd
+++ b/modules/gdscript/tests/scripts/parser/errors/multiple_number_separators.gd
@@ -1,3 +1,3 @@
func test():
# Number separators may not be placed right next to each other.
- var __ = 1__23
+ var _num = 1__23
diff --git a/modules/gdscript/tests/scripts/parser/errors/multiple_number_separators.out b/modules/gdscript/tests/scripts/parser/errors/multiple_number_separators.out
index 71a3c2fd6a..b308994ae2 100644
--- a/modules/gdscript/tests/scripts/parser/errors/multiple_number_separators.out
+++ b/modules/gdscript/tests/scripts/parser/errors/multiple_number_separators.out
@@ -1,2 +1,2 @@
GDTEST_PARSER_ERROR
-Only one underscore can be used as a numeric separator.
+Multiple underscores cannot be adjacent in a numeric literal.
diff --git a/modules/gdscript/tests/scripts/parser/errors/multiple_number_separators_after_decimal.gd b/modules/gdscript/tests/scripts/parser/errors/multiple_number_separators_after_decimal.gd
new file mode 100644
index 0000000000..3140999aa9
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/multiple_number_separators_after_decimal.gd
@@ -0,0 +1,3 @@
+func test():
+ # Number separators may not be placed right next to each other.
+ var _num = 123.45__67
diff --git a/modules/gdscript/tests/scripts/parser/errors/multiple_number_separators_after_decimal.out b/modules/gdscript/tests/scripts/parser/errors/multiple_number_separators_after_decimal.out
new file mode 100644
index 0000000000..b308994ae2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/multiple_number_separators_after_decimal.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Multiple underscores cannot be adjacent in a numeric literal.
diff --git a/modules/gdscript/tests/scripts/parser/features/number_literals_with_sign.gd b/modules/gdscript/tests/scripts/parser/features/number_literals_with_sign.gd
new file mode 100644
index 0000000000..cf7fb1518c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/number_literals_with_sign.gd
@@ -0,0 +1,17 @@
+func test():
+ print(-9223372036854775808 == (1 << 63))
+ print(-2)
+ print(- 2)
+ print(---2)
+ print(3 - 2)
+ print(3-2)
+ print(3---2)
+ print(-3 - 2)
+ print(-3 - -2)
+ print(-(3 - 2)-2)
+ print([1, 2, 3][0]-1)
+ var t = 1
+ print(t-1)
+ print(-0xFF)
+ print(1--0xFF)
+ print(floor(PI-1))
diff --git a/modules/gdscript/tests/scripts/parser/features/number_literals_with_sign.out b/modules/gdscript/tests/scripts/parser/features/number_literals_with_sign.out
new file mode 100644
index 0000000000..c5958365ec
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/number_literals_with_sign.out
@@ -0,0 +1,16 @@
+GDTEST_OK
+true
+-2
+-2
+-2
+1
+1
+1
+-5
+-1
+-3
+0
+0
+-255
+256
+2
diff --git a/modules/gdscript/tests/scripts/parser/features/number_separators.gd b/modules/gdscript/tests/scripts/parser/features/number_separators.gd
index f5f5661cae..a534c4fde1 100644
--- a/modules/gdscript/tests/scripts/parser/features/number_separators.gd
+++ b/modules/gdscript/tests/scripts/parser/features/number_separators.gd
@@ -1,12 +1,26 @@
func test():
# `_` can be used as a separator for numbers in GDScript.
# It can be placed anywhere in the number, except at the beginning.
- # Currently, GDScript in the `master` branch only allows using one separator
- # per number.
- # Results are assigned to variables to avoid warnings.
- var __ = 1_23
- __ = 123_ # Trailing number separators are OK.
- __ = 12_3
- __ = 123_456
- __ = 0x1234_5678
- __ = 0b1001_0101
+ print(1_23)
+ print(12_3)
+ print(1_2_3)
+ print(123_) # Trailing number separators are OK.
+ print(123_456)
+ print(123_45_6_)
+ print("---")
+ print(0x1234_00ff)
+ print(0x1234_00_f_f_)
+ print(0b1001_0101)
+ print(0b1001_01_0_1_)
+ print("---")
+ print(-1_234.456_7)
+ print(-1_23_4_.4_56_7_)
+ print(-1_234.)
+ print(-1_23_4_.)
+ print(.456_7)
+ print(.4_56_7_)
+ print("---")
+ print(-1_234.5e000_3)
+ print(-1_23_4_.5e0_00_3_)
+ print(-1_234.5e+000_3)
+ print(-1_23_4_.5e+0_00_3_)
diff --git a/modules/gdscript/tests/scripts/parser/features/number_separators.out b/modules/gdscript/tests/scripts/parser/features/number_separators.out
index d73c5eb7cd..b0d2fd94fe 100644
--- a/modules/gdscript/tests/scripts/parser/features/number_separators.out
+++ b/modules/gdscript/tests/scripts/parser/features/number_separators.out
@@ -1 +1,24 @@
GDTEST_OK
+123
+123
+123
+123
+123456
+123456
+---
+305398015
+305398015
+149
+149
+---
+-1234.4567
+-1234.4567
+-1234
+-1234
+0.4567
+0.4567
+---
+-1234500
+-1234500
+-1234500
+-1234500
diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_wrong_to_typed.gd b/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_wrong_to_typed.gd
new file mode 100644
index 0000000000..83ec6573df
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_wrong_to_typed.gd
@@ -0,0 +1,7 @@
+class Foo: pass
+class Bar extends Foo: pass
+class Baz extends Foo: pass
+
+func test():
+ var typed: Array[Bar] = [Baz.new() as Foo]
+ print('not ok')
diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_wrong_to_typed.out b/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_wrong_to_typed.out
new file mode 100644
index 0000000000..7b9f1066b0
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_wrong_to_typed.out
@@ -0,0 +1,4 @@
+GDTEST_RUNTIME_ERROR
+>> ERROR
+>> Method/function failed.
+not ok
diff --git a/modules/gdscript/tests/scripts/runtime/features/ctor_as_callable.gd b/modules/gdscript/tests/scripts/runtime/features/ctor_as_callable.gd
new file mode 100644
index 0000000000..515efbc9ce
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/ctor_as_callable.gd
@@ -0,0 +1,10 @@
+# https://github.com/godotengine/godot/issues/70319
+
+class InnerClass:
+ pass
+
+func test():
+ var inner_ctor : Callable = InnerClass.new
+ print(inner_ctor)
+ var native_ctor : Callable = Node.new
+ print(native_ctor)
diff --git a/modules/gdscript/tests/scripts/runtime/features/ctor_as_callable.out b/modules/gdscript/tests/scripts/runtime/features/ctor_as_callable.out
new file mode 100644
index 0000000000..527e3e8f84
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/ctor_as_callable.out
@@ -0,0 +1,3 @@
+GDTEST_OK
+GDScript::new
+GDScriptNativeClass::new
diff --git a/modules/gdscript/tests/scripts/runtime/features/match_string_stringname_equivalent.gd b/modules/gdscript/tests/scripts/runtime/features/match_string_stringname_equivalent.gd
deleted file mode 100644
index 55be021a90..0000000000
--- a/modules/gdscript/tests/scripts/runtime/features/match_string_stringname_equivalent.gd
+++ /dev/null
@@ -1,14 +0,0 @@
-# https://github.com/godotengine/godot/issues/60145
-
-func test():
- match "abc":
- &"abc":
- print("String matched StringName")
- _:
- print("no match")
-
- match &"abc":
- "abc":
- print("StringName matched String")
- _:
- print("no match")
diff --git a/modules/gdscript/tests/scripts/runtime/features/match_string_stringname_equivalent.out b/modules/gdscript/tests/scripts/runtime/features/match_string_stringname_equivalent.out
deleted file mode 100644
index 9d5a18da3d..0000000000
--- a/modules/gdscript/tests/scripts/runtime/features/match_string_stringname_equivalent.out
+++ /dev/null
@@ -1,3 +0,0 @@
-GDTEST_OK
-String matched StringName
-StringName matched String
diff --git a/modules/gdscript/tests/scripts/runtime/features/static_access_via_instance.gd b/modules/gdscript/tests/scripts/runtime/features/static_access_via_instance.gd
new file mode 100644
index 0000000000..77d01cf00c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/static_access_via_instance.gd
@@ -0,0 +1,58 @@
+# GH-77098 p.4
+
+@static_unload
+
+class A:
+ class InnerClass:
+ pass
+
+ enum NamedEnum { VALUE = 111 }
+ enum { UNNAMED_ENUM_VALUE = 222 }
+ const CONSTANT = 333
+ static var static_var := 1
+
+ static func static_func() -> int:
+ return 444
+
+class B extends A:
+ func test_self():
+ print(self.InnerClass is GDScript)
+ print(self.NamedEnum)
+ print(self.NamedEnum.VALUE)
+ print(self.UNNAMED_ENUM_VALUE)
+ print(self.CONSTANT)
+ @warning_ignore("static_called_on_instance")
+ print(self.static_func())
+
+ prints("test_self before:", self.static_var)
+ self.static_var = 2
+ prints("test_self after:", self.static_var)
+
+func test():
+ var hard := B.new()
+ hard.test_self()
+
+ print(hard.InnerClass is GDScript)
+ print(hard.NamedEnum)
+ print(hard.NamedEnum.VALUE)
+ print(hard.UNNAMED_ENUM_VALUE)
+ print(hard.CONSTANT)
+ @warning_ignore("static_called_on_instance")
+ print(hard.static_func())
+
+ prints("hard before:", hard.static_var)
+ hard.static_var = 3
+ prints("hard after:", hard.static_var)
+
+ var weak: Variant = B.new()
+ print(weak.InnerClass is GDScript)
+ print(weak.NamedEnum)
+ print(weak.NamedEnum.VALUE)
+ print(weak.UNNAMED_ENUM_VALUE)
+ print(weak.CONSTANT)
+ @warning_ignore("unsafe_method_access")
+ print(weak.static_func())
+
+ prints("weak before:", weak.static_var)
+ weak.static_var = 4
+ prints("weak after:", weak.static_var)
diff --git a/modules/gdscript/tests/scripts/runtime/features/static_access_via_instance.out b/modules/gdscript/tests/scripts/runtime/features/static_access_via_instance.out
new file mode 100644
index 0000000000..7d7ad04df4
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/static_access_via_instance.out
@@ -0,0 +1,25 @@
+GDTEST_OK
+true
+{ "VALUE": 111 }
+111
+222
+333
+444
+test_self before: 1
+test_self after: 2
+true
+{ "VALUE": 111 }
+111
+222
+333
+444
+hard before: 2
+hard after: 3
+true
+{ "VALUE": 111 }
+111
+222
+333
+444
+weak before: 3
+weak after: 4
diff --git a/modules/gdscript/tests/scripts/runtime/features/static_func_as_callable.gd b/modules/gdscript/tests/scripts/runtime/features/static_func_as_callable.gd
new file mode 100644
index 0000000000..65635daa36
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/static_func_as_callable.gd
@@ -0,0 +1,17 @@
+# GH-41919
+
+class_name TestStaticFuncAsCallable
+
+class InnerClass:
+ static func inner_my_func():
+ print("inner_my_func")
+
+static func my_func():
+ print("my_func")
+
+var a: Callable = TestStaticFuncAsCallable.my_func
+var b: Callable = InnerClass.inner_my_func
+
+func test():
+ a.call()
+ b.call()
diff --git a/modules/gdscript/tests/scripts/runtime/features/static_func_as_callable.out b/modules/gdscript/tests/scripts/runtime/features/static_func_as_callable.out
new file mode 100644
index 0000000000..360bb9f322
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/static_func_as_callable.out
@@ -0,0 +1,3 @@
+GDTEST_OK
+my_func
+inner_my_func
diff --git a/modules/gdscript/tests/scripts/runtime/features/static_variables.gd b/modules/gdscript/tests/scripts/runtime/features/static_variables.gd
index e193312381..8da8bb7e53 100644
--- a/modules/gdscript/tests/scripts/runtime/features/static_variables.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/static_variables.gd
@@ -33,24 +33,24 @@ func test():
prints("perm:", perm)
prints("prop:", prop)
- print("other.perm:", StaticVariablesOther.perm)
- print("other.prop:", StaticVariablesOther.prop)
+ prints("other.perm:", StaticVariablesOther.perm)
+ prints("other.prop:", StaticVariablesOther.prop)
StaticVariablesOther.perm = 2
StaticVariablesOther.prop = "foo"
- print("other.perm:", StaticVariablesOther.perm)
- print("other.prop:", StaticVariablesOther.prop)
+ prints("other.perm:", StaticVariablesOther.perm)
+ prints("other.prop:", StaticVariablesOther.prop)
@warning_ignore("unsafe_method_access")
var path = get_script().get_path().get_base_dir()
- var other = load(path + "/static_variables_load.gd")
+ var other = load(path + "/static_variables_load.gd")
- print("load.perm:", other.perm)
- print("load.prop:", other.prop)
+ prints("load.perm:", other.perm)
+ prints("load.prop:", other.prop)
other.perm = 3
other.prop = "bar"
- print("load.perm:", other.perm)
- print("load.prop:", other.prop)
+ prints("load.perm:", other.perm)
+ prints("load.prop:", other.prop)
diff --git a/modules/gdscript/tests/scripts/runtime/features/static_variables.out b/modules/gdscript/tests/scripts/runtime/features/static_variables.out
index d2491aef5e..650e1d9578 100644
--- a/modules/gdscript/tests/scripts/runtime/features/static_variables.out
+++ b/modules/gdscript/tests/scripts/runtime/features/static_variables.out
@@ -3,14 +3,14 @@ Inner._static_init inner
InnerInner._static_init inner inner
data: data
perm: 0
-prop: prefix Hello! suffix
+prop: Hello! suffix
perm: 1
prop: prefix World! suffix
-other.perm:0
-other.prop:prefix Hello! suffix
-other.perm:2
-other.prop:prefix foo suffix
-load.perm:0
-load.prop:prefix Hello! suffix
-load.perm:3
-load.prop:prefix bar suffix
+other.perm: 0
+other.prop: Hello! suffix
+other.perm: 2
+other.prop: prefix foo suffix
+load.perm: 0
+load.prop: Hello! suffix
+load.perm: 3
+load.prop: prefix bar suffix
diff --git a/modules/gdscript/tests/scripts/runtime/features/static_variables_2.gd b/modules/gdscript/tests/scripts/runtime/features/static_variables_2.gd
new file mode 100644
index 0000000000..7a75d119ed
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/static_variables_2.gd
@@ -0,0 +1,56 @@
+@static_unload
+
+class A:
+ static var x: int = 1
+
+ static var y: int = 42:
+ set(_value):
+ print("The setter is NOT called on initialization.") # GH-77098 p.1
+
+ static func _static_init() -> void:
+ prints("A _static_init begin:", x)
+ x = -1
+ prints("A _static_init end:", x)
+
+ static func sf(p_x: int) -> void:
+ x = p_x
+ prints("sf:", x)
+
+ # GH-77331
+ func f(p_x: int) -> void:
+ x = p_x
+ prints("f:", x)
+
+class B extends A:
+ static func _static_init() -> void:
+ prints("B _static_init begin:", x)
+ x = -2
+ prints("B _static_init end:", x)
+
+ static func sg(p_x: int) -> void:
+ x = p_x
+ prints("sg:", x)
+
+ func g(p_x: int) -> void:
+ x = p_x
+ prints("g:", x)
+
+ func h(p_x: int) -> void:
+ print("h: call f(%d)" % p_x)
+ f(p_x)
+
+func test():
+ prints(A.x, B.x)
+ A.x = 1 # GH-77098 p.2
+ prints(A.x, B.x)
+ B.x = 2
+ prints(A.x, B.x)
+
+ A.sf(3)
+ B.sf(4)
+ B.sg(5)
+
+ var b := B.new()
+ b.f(6)
+ b.g(7)
+ b.h(8)
diff --git a/modules/gdscript/tests/scripts/runtime/features/static_variables_2.out b/modules/gdscript/tests/scripts/runtime/features/static_variables_2.out
new file mode 100644
index 0000000000..b833911d95
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/static_variables_2.out
@@ -0,0 +1,15 @@
+GDTEST_OK
+A _static_init begin: 1
+A _static_init end: -1
+B _static_init begin: -1
+B _static_init end: -2
+-2 -2
+1 1
+2 2
+sf: 3
+sf: 4
+sg: 5
+f: 6
+g: 7
+h: call f(8)
+f: 8
diff --git a/modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.gd b/modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.gd
index f8bd46523e..0dd40520b0 100644
--- a/modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.gd
@@ -1,4 +1,8 @@
# https://github.com/godotengine/godot/issues/64171
+# https://github.com/godotengine/godot/issues/60145
+
+var s = "abc"
+var sn = &"abc"
func test():
print("Compare ==: ", "abc" == &"abc")
@@ -9,3 +13,27 @@ func test():
print("Concat: ", "abc" + &"def")
print("Concat: ", &"abc" + "def")
print("Concat: ", &"abc" + &"def")
+
+ match "abc":
+ &"abc":
+ print("String matched StringName literal")
+ _:
+ print("no Match")
+
+ match &"abc":
+ "abc":
+ print("StringName matched String literal")
+ _:
+ print("no Match")
+
+ match "abc":
+ sn:
+ print("String matched StringName")
+ _:
+ print("no match")
+
+ match &"abc":
+ s:
+ print("StringName matched String")
+ _:
+ print("no match")
diff --git a/modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.out b/modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.out
index 7e9c364b60..440b613099 100644
--- a/modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.out
+++ b/modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.out
@@ -6,3 +6,7 @@ Compare !=: false
Concat: abcdef
Concat: abcdef
Concat: abcdef
+String matched StringName literal
+StringName matched String literal
+String matched StringName
+StringName matched String
diff --git a/modules/gdscript/tests/test_gdscript.cpp b/modules/gdscript/tests/test_gdscript.cpp
index ad38312abe..0446a7aad6 100644
--- a/modules/gdscript/tests/test_gdscript.cpp
+++ b/modules/gdscript/tests/test_gdscript.cpp
@@ -30,6 +30,11 @@
#include "test_gdscript.h"
+#include "../gdscript_analyzer.h"
+#include "../gdscript_compiler.h"
+#include "../gdscript_parser.h"
+#include "../gdscript_tokenizer.h"
+
#include "core/config/project_settings.h"
#include "core/io/file_access.h"
#include "core/io/file_access_pack.h"
@@ -38,11 +43,6 @@
#include "core/string/string_builder.h"
#include "scene/resources/packed_scene.h"
-#include "modules/gdscript/gdscript_analyzer.h"
-#include "modules/gdscript/gdscript_compiler.h"
-#include "modules/gdscript/gdscript_parser.h"
-#include "modules/gdscript/gdscript_tokenizer.h"
-
#ifdef TOOLS_ENABLED
#include "editor/editor_settings.h"
#endif
diff --git a/modules/gdscript/tests/test_gdscript.h b/modules/gdscript/tests/test_gdscript.h
index d719e3d94a..b39dfe2b5a 100644
--- a/modules/gdscript/tests/test_gdscript.h
+++ b/modules/gdscript/tests/test_gdscript.h
@@ -32,6 +32,7 @@
#define TEST_GDSCRIPT_H
#include "gdscript_test_runner.h"
+
#include "tests/test_macros.h"
namespace GDScriptTests {