summaryrefslogtreecommitdiffstats
path: root/modules/gdscript
diff options
context:
space:
mode:
Diffstat (limited to 'modules/gdscript')
-rw-r--r--modules/gdscript/.editorconfig5
-rw-r--r--modules/gdscript/SCsub1
-rw-r--r--modules/gdscript/doc_classes/@GDScript.xml55
-rw-r--r--modules/gdscript/doc_classes/GDScript.xml3
-rw-r--r--modules/gdscript/editor/gdscript_docgen.cpp93
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.cpp9
-rw-r--r--modules/gdscript/editor/gdscript_translation_parser_plugin.cpp94
-rw-r--r--modules/gdscript/editor/gdscript_translation_parser_plugin.h15
-rw-r--r--modules/gdscript/editor/script_templates/SCsub1
-rw-r--r--modules/gdscript/gdscript.cpp60
-rw-r--r--modules/gdscript/gdscript.h3
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp385
-rw-r--r--modules/gdscript/gdscript_analyzer.h2
-rw-r--r--modules/gdscript/gdscript_byte_codegen.cpp105
-rw-r--r--modules/gdscript/gdscript_byte_codegen.h1
-rw-r--r--modules/gdscript/gdscript_cache.cpp15
-rw-r--r--modules/gdscript/gdscript_codegen.h1
-rw-r--r--modules/gdscript/gdscript_compiler.cpp31
-rw-r--r--modules/gdscript/gdscript_disassembler.cpp113
-rw-r--r--modules/gdscript/gdscript_editor.cpp82
-rw-r--r--modules/gdscript/gdscript_function.h43
-rw-r--r--modules/gdscript/gdscript_lambda_callable.cpp24
-rw-r--r--modules/gdscript/gdscript_parser.cpp319
-rw-r--r--modules/gdscript/gdscript_parser.h9
-rw-r--r--modules/gdscript/gdscript_tokenizer_buffer.cpp1
-rw-r--r--modules/gdscript/gdscript_tokenizer_buffer.h2
-rw-r--r--modules/gdscript/gdscript_utility_functions.cpp492
-rw-r--r--modules/gdscript/gdscript_vm.cpp218
-rw-r--r--modules/gdscript/gdscript_warning.cpp7
-rw-r--r--modules/gdscript/gdscript_warning.h4
-rw-r--r--modules/gdscript/language_server/gdscript_extend_parser.h4
-rw-r--r--modules/gdscript/language_server/gdscript_language_server.cpp1
-rw-r--r--modules/gdscript/language_server/gdscript_text_document.cpp15
-rw-r--r--modules/gdscript/language_server/godot_lsp.h32
-rw-r--r--modules/gdscript/tests/scripts/.editorconfig10
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_dictionary.gd3
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_dictionary.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assign_differently_typed.gd4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assign_differently_typed.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assignment.gd2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assignment.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_init_with_unconvertible_in_literal.gd4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_init_with_unconvertible_in_literal.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_pass_differently_to_typed.gd7
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_pass_differently_to_typed.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/class_inference_is_weak.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd10
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/external_inner_class_as_constant.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd30
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_as_default_parameter.gd20
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_as_default_parameter.out11
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_inferred_access_isnt_constant.gd4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_inferred_access_isnt_constant.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.gd214
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_with_custom_class.gd9
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_with_custom_class.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_initializer.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_loop.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/lambda_shadowing_arg.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/shadowing_base.notest.gd7
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out32
-rw-r--r--modules/gdscript/tests/scripts/completion/argument_options/argument_options.tscn13
-rw-r--r--modules/gdscript/tests/scripts/completion/argument_options/play_inferred.cfg6
-rw-r--r--modules/gdscript/tests/scripts/completion/argument_options/play_inferred.gd5
-rw-r--r--modules/gdscript/tests/scripts/completion/argument_options/play_typed.cfg6
-rw-r--r--modules/gdscript/tests/scripts/completion/argument_options/play_typed.gd5
-rw-r--r--modules/gdscript/tests/scripts/completion/argument_options/play_untyped.cfg6
-rw-r--r--modules/gdscript/tests/scripts/completion/argument_options/play_untyped.gd5
-rw-r--r--modules/gdscript/tests/scripts/completion/get_node/literal_scene/dollar_unique.cfg9
-rw-r--r--modules/gdscript/tests/scripts/completion/get_node/literal_scene/dollar_unique.gd5
-rw-r--r--modules/gdscript/tests/scripts/parser/.editorconfig2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/annotation_inapplicable.gd3
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/annotation_inapplicable.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/annotation_unrecognized.gd3
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/annotation_unrecognized.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/export_tool_button_requires_tool_mode.gd1
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/export_tool_button_requires_tool_mode.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/export_arrays.out26
-rw-r--r--modules/gdscript/tests/scripts/parser/features/export_variable.gd5
-rw-r--r--modules/gdscript/tests/scripts/parser/features/export_variable.out10
-rw-r--r--modules/gdscript/tests/scripts/parser/features/number_literals_with_sign.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/number_separators.out12
-rw-r--r--modules/gdscript/tests/scripts/parser/features/operator_assign.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/typed_dictionaries.gd5
-rw-r--r--modules/gdscript/tests/scripts/parser/features/typed_dictionaries.out3
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/shadowed_constant.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_class.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_function.out2
-rw-r--r--modules/gdscript/tests/scripts/project.godot2
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/division_by_zero.gd3
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/division_by_zero.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/modulo_by_zero.gd3
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/modulo_by_zero.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_basic_to_typed.gd4
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_basic_to_typed.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed.gd4
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed_key.gd7
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed_key.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed_value.gd7
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed_value.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_wrong_to_typed.gd7
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_wrong_to_typed.out5
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_basic_to_typed.gd7
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_basic_to_typed.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_differently_to_typed.gd7
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_differently_to_typed.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/chain_assignment_works.out12
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/conversion_for_default_parameter.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd9
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out4
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/gdscript_utility_implicit_conversion.gd12
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/gdscript_utility_implicit_conversion.out5
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/member_info.gd24
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/member_info.out24
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/native_static_method_as_callable.gd8
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/native_static_method_as_callable.out3
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out12
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/self_destruction.gd50
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/self_destruction.out5
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.out16
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/stringify.out22
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/typed_dictionary_implicit_cast_param.gd6
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/typed_dictionary_implicit_cast_param.out3
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/typed_dictionary_init_with_untyped_in_literal.gd7
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/typed_dictionary_init_with_untyped_in_literal.out2
-rw-r--r--modules/gdscript/tests/scripts/utils.notest.gd11
131 files changed, 2490 insertions, 652 deletions
diff --git a/modules/gdscript/.editorconfig b/modules/gdscript/.editorconfig
index 640c205093..b380846f86 100644
--- a/modules/gdscript/.editorconfig
+++ b/modules/gdscript/.editorconfig
@@ -1,8 +1,3 @@
[*.gd]
-indent_style = tab
indent_size = 4
-insert_final_newline = true
trim_trailing_whitespace = true
-
-[*.out]
-insert_final_newline = true
diff --git a/modules/gdscript/SCsub b/modules/gdscript/SCsub
index 61accd4fc9..8f50bf9588 100644
--- a/modules/gdscript/SCsub
+++ b/modules/gdscript/SCsub
@@ -1,4 +1,5 @@
#!/usr/bin/env python
+from misc.utility.scons_hints import *
Import("env")
Import("env_modules")
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml
index f539f27848..0355119442 100644
--- a/modules/gdscript/doc_classes/@GDScript.xml
+++ b/modules/gdscript/doc_classes/@GDScript.xml
@@ -4,8 +4,8 @@
Built-in GDScript constants, functions, and annotations.
</brief_description>
<description>
- A list of GDScript-specific utility functions and annotations accessible from any script.
- For the list of the global functions and constants see [@GlobalScope].
+ A list of utility functions and annotations accessible from any script written in GDScript.
+ For the list of global functions and constants that can be accessed in any scripting language, see [@GlobalScope].
</description>
<tutorials>
<link title="GDScript exports">$DOCS_URL/tutorials/scripting/gdscript/gdscript_exports.html</link>
@@ -61,7 +61,7 @@
<method name="convert" deprecated="Use [method @GlobalScope.type_convert] instead.">
<return type="Variant" />
<param index="0" name="what" type="Variant" />
- <param index="1" name="type" type="int" />
+ <param index="1" name="type" type="int" enum="Variant.Type" />
<description>
Converts [param what] to [param type] in the best way possible. The [param type] uses the [enum Variant.Type] values.
[codeblock]
@@ -666,7 +666,54 @@
@export var car_label = "Speedy"
@export var car_number = 3
[/codeblock]
- [b]Note:[/b] Subgroups cannot be nested, they only provide one extra level of depth. Just like the next group ends the previous group, so do the subsequent subgroups.
+ [b]Note:[/b] Subgroups cannot be nested, but you can use the slash separator ([code]/[/code]) to achieve the desired effect:
+ [codeblock]
+ @export_group("Car Properties")
+ @export_subgroup("Wheels", "wheel_")
+ @export_subgroup("Wheels/Front", "front_wheel_")
+ @export var front_wheel_strength = 10
+ @export var front_wheel_mobility = 5
+ @export_subgroup("Wheels/Rear", "rear_wheel_")
+ @export var rear_wheel_strength = 8
+ @export var rear_wheel_mobility = 3
+ @export_subgroup("Wheels", "wheel_")
+ @export var wheel_material: PhysicsMaterial
+ [/codeblock]
+ </description>
+ </annotation>
+ <annotation name="@export_tool_button">
+ <return type="void" />
+ <param index="0" name="text" type="String" />
+ <param index="1" name="icon" type="String" default="&quot;&quot;" />
+ <description>
+ Export a [Callable] property as a clickable button with the label [param text]. When the button is pressed, the callable is called.
+ If [param icon] is specified, it is used to fetch an icon for the button via [method Control.get_theme_icon], from the [code]"EditorIcons"[/code] theme type. If [param icon] is omitted, the default [code]"Callable"[/code] icon is used instead.
+ Consider using the [EditorUndoRedoManager] to allow the action to be reverted safely.
+ See also [constant PROPERTY_HINT_TOOL_BUTTON].
+ [codeblock]
+ @tool
+ extends Sprite2D
+
+ @export_tool_button("Hello") var hello_action = hello
+ @export_tool_button("Randomize the color!", "ColorRect")
+ var randomize_color_action = randomize_color
+
+ func hello():
+ print("Hello world!")
+
+ func randomize_color():
+ var undo_redo = EditorInterface.get_editor_undo_redo()
+ undo_redo.create_action("Randomized Sprite2D Color")
+ undo_redo.add_do_property(self, &amp;"self_modulate", Color(randf(), randf(), randf()))
+ undo_redo.add_undo_property(self, &amp;"self_modulate", self_modulate)
+ undo_redo.commit_action()
+ [/codeblock]
+ [b]Note:[/b] The property is exported without the [constant PROPERTY_USAGE_STORAGE] flag because a [Callable] cannot be properly serialized and stored in a file.
+ [b]Note:[/b] In an exported project neither [EditorInterface] nor [EditorUndoRedoManager] exist, which may cause some scripts to break. To prevent this, you can use [method Engine.get_singleton] and omit the static type from the variable declaration:
+ [codeblock]
+ var undo_redo = Engine.get_singleton(&amp;"EditorInterface").get_editor_undo_redo()
+ [/codeblock]
+ [b]Note:[/b] Avoid storing lambda callables in member variables of [RefCounted]-based classes (e.g. resources), as this can lead to memory leaks. Use only method callables and optionally [method Callable.bind] or [method Callable.unbind].
</description>
</annotation>
<annotation name="@icon">
diff --git a/modules/gdscript/doc_classes/GDScript.xml b/modules/gdscript/doc_classes/GDScript.xml
index 5f7a7e2915..c3fa59dc23 100644
--- a/modules/gdscript/doc_classes/GDScript.xml
+++ b/modules/gdscript/doc_classes/GDScript.xml
@@ -16,11 +16,10 @@
<return type="Variant" />
<description>
Returns a new instance of the script.
- For example:
[codeblock]
var MyClass = load("myclass.gd")
var instance = MyClass.new()
- assert(instance.get_script() == MyClass)
+ print(instance.get_script() == MyClass) # Prints true
[/codeblock]
</description>
</method>
diff --git a/modules/gdscript/editor/gdscript_docgen.cpp b/modules/gdscript/editor/gdscript_docgen.cpp
index 35b69fab8c..3a5a88d356 100644
--- a/modules/gdscript/editor/gdscript_docgen.cpp
+++ b/modules/gdscript/editor/gdscript_docgen.cpp
@@ -84,6 +84,15 @@ void GDScriptDocGen::_doctype_from_gdtype(const GDType &p_gdtype, String &r_type
return;
}
}
+ if (p_gdtype.builtin_type == Variant::DICTIONARY && p_gdtype.has_container_element_types()) {
+ String key, value;
+ _doctype_from_gdtype(p_gdtype.get_container_element_type_or_variant(0), key, r_enum);
+ _doctype_from_gdtype(p_gdtype.get_container_element_type_or_variant(1), value, r_enum);
+ if (key != "Variant" || value != "Variant") {
+ r_type = "Dictionary[" + key + ", " + value + "]";
+ return;
+ }
+ }
r_type = Variant::get_type_name(p_gdtype.builtin_type);
return;
case GDType::NATIVE:
@@ -131,7 +140,7 @@ void GDScriptDocGen::_doctype_from_gdtype(const GDType &p_gdtype, String &r_type
r_enum = String(p_gdtype.native_type).replace("::", ".");
if (r_enum.begins_with("res://")) {
r_enum = r_enum.trim_prefix("res://");
- int dot_pos = r_enum.rfind(".");
+ int dot_pos = r_enum.rfind_char('.');
if (dot_pos >= 0) {
r_enum = r_enum.left(dot_pos).quote() + r_enum.substr(dot_pos);
}
@@ -155,34 +164,82 @@ String GDScriptDocGen::_docvalue_from_variant(const Variant &p_variant, int p_re
return "<Object>";
case Variant::DICTIONARY: {
const Dictionary dict = p_variant;
+ String result;
- if (dict.is_empty()) {
- return "{}";
- }
+ if (dict.is_typed()) {
+ result += "Dictionary[";
+
+ Ref<Script> key_script = dict.get_typed_key_script();
+ if (key_script.is_valid()) {
+ if (key_script->get_global_name() != StringName()) {
+ result += key_script->get_global_name();
+ } else if (!key_script->get_path().get_file().is_empty()) {
+ result += key_script->get_path().get_file();
+ } else {
+ result += dict.get_typed_key_class_name();
+ }
+ } else if (dict.get_typed_key_class_name() != StringName()) {
+ result += dict.get_typed_key_class_name();
+ } else if (dict.is_typed_key()) {
+ result += Variant::get_type_name((Variant::Type)dict.get_typed_key_builtin());
+ } else {
+ result += "Variant";
+ }
+
+ result += ", ";
- if (p_recursion_level > MAX_RECURSION_LEVEL) {
- return "{...}";
+ Ref<Script> value_script = dict.get_typed_value_script();
+ if (value_script.is_valid()) {
+ if (value_script->get_global_name() != StringName()) {
+ result += value_script->get_global_name();
+ } else if (!value_script->get_path().get_file().is_empty()) {
+ result += value_script->get_path().get_file();
+ } else {
+ result += dict.get_typed_value_class_name();
+ }
+ } else if (dict.get_typed_value_class_name() != StringName()) {
+ result += dict.get_typed_value_class_name();
+ } else if (dict.is_typed_value()) {
+ result += Variant::get_type_name((Variant::Type)dict.get_typed_value_builtin());
+ } else {
+ result += "Variant";
+ }
+
+ result += "](";
}
- List<Variant> keys;
- dict.get_key_list(&keys);
- keys.sort();
+ if (dict.is_empty()) {
+ result += "{}";
+ } else if (p_recursion_level > MAX_RECURSION_LEVEL) {
+ result += "{...}";
+ } else {
+ result += "{";
+
+ List<Variant> keys;
+ dict.get_key_list(&keys);
+ keys.sort_custom<StringLikeVariantOrder>();
- String data;
- for (List<Variant>::Element *E = keys.front(); E; E = E->next()) {
- if (E->prev()) {
- data += ", ";
+ for (List<Variant>::Element *E = keys.front(); E; E = E->next()) {
+ if (E->prev()) {
+ result += ", ";
+ }
+ result += _docvalue_from_variant(E->get(), p_recursion_level + 1) + ": " + _docvalue_from_variant(dict[E->get()], p_recursion_level + 1);
}
- data += _docvalue_from_variant(E->get(), p_recursion_level + 1) + ": " + _docvalue_from_variant(dict[E->get()], p_recursion_level + 1);
+
+ result += "}";
+ }
+
+ if (dict.is_typed()) {
+ result += ")";
}
- return "{" + data + "}";
+ return result;
} break;
case Variant::ARRAY: {
const Array array = p_variant;
String result;
- if (array.get_typed_builtin() != Variant::NIL) {
+ if (array.is_typed()) {
result += "Array[";
Ref<Script> script = array.get_typed_script();
@@ -209,16 +266,18 @@ String GDScriptDocGen::_docvalue_from_variant(const Variant &p_variant, int p_re
result += "[...]";
} else {
result += "[";
+
for (int i = 0; i < array.size(); i++) {
if (i > 0) {
result += ", ";
}
result += _docvalue_from_variant(array[i], p_recursion_level + 1);
}
+
result += "]";
}
- if (array.get_typed_builtin() != Variant::NIL) {
+ if (array.is_typed()) {
result += ")";
}
diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp
index eecce70202..629581bd6c 100644
--- a/modules/gdscript/editor/gdscript_highlighter.cpp
+++ b/modules/gdscript/editor/gdscript_highlighter.cpp
@@ -163,7 +163,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
}
if (from + end_key_length > line_length) {
// If it's key length and there is a '\', dont skip to highlight esc chars.
- if (str.find("\\", from) >= 0) {
+ if (str.find_char('\\', from) >= 0) {
break;
}
}
@@ -236,7 +236,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
for (; from < line_length; from++) {
if (line_length - from < end_key_length) {
// Don't break if '\' to highlight esc chars.
- if (str.find("\\", from) < 0) {
+ if (str.find_char('\\', from) < 0) {
break;
}
}
@@ -701,7 +701,9 @@ void GDScriptSyntaxHighlighter::_update_cache() {
List<StringName> types;
ClassDB::get_class_list(&types);
for (const StringName &E : types) {
- class_names[E] = types_color;
+ if (ClassDB::is_class_exposed(E)) {
+ class_names[E] = types_color;
+ }
}
/* User types. */
@@ -852,6 +854,7 @@ void GDScriptSyntaxHighlighter::_update_cache() {
comment_marker_colors[COMMENT_MARKER_NOTICE] = Color(0.24, 0.54, 0.09);
}
+ // TODO: Move to editor_settings.cpp
EDITOR_DEF("text_editor/theme/highlighting/gdscript/function_definition_color", function_definition_color);
EDITOR_DEF("text_editor/theme/highlighting/gdscript/global_function_color", global_function_color);
EDITOR_DEF("text_editor/theme/highlighting/gdscript/node_path_color", node_path_color);
diff --git a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp
index b31ae878ce..172ad6be9f 100644
--- a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp
+++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp
@@ -51,6 +51,10 @@ Error GDScriptEditorTranslationParserPlugin::parse_file(const String &p_path, Ve
ids = r_ids;
ids_ctx_plural = r_ids_ctx_plural;
+
+ ids_comment.clear();
+ ids_ctx_plural_comment.clear();
+
Ref<GDScript> gdscript = loaded_res;
String source_code = gdscript->get_source_code();
@@ -62,18 +66,90 @@ Error GDScriptEditorTranslationParserPlugin::parse_file(const String &p_path, Ve
err = analyzer.analyze();
ERR_FAIL_COND_V_MSG(err, err, "Failed to analyze GDScript with GDScriptAnalyzer.");
+ comment_data = &parser.comment_data;
+
// Traverse through the parsed tree from GDScriptParser.
GDScriptParser::ClassNode *c = parser.get_tree();
_traverse_class(c);
+ comment_data = nullptr;
+
return OK;
}
+void GDScriptEditorTranslationParserPlugin::get_comments(Vector<String> *r_ids_comment, Vector<String> *r_ids_ctx_plural_comment) {
+ r_ids_comment->append_array(ids_comment);
+ r_ids_ctx_plural_comment->append_array(ids_ctx_plural_comment);
+}
+
bool GDScriptEditorTranslationParserPlugin::_is_constant_string(const GDScriptParser::ExpressionNode *p_expression) {
ERR_FAIL_NULL_V(p_expression, false);
return p_expression->is_constant && p_expression->reduced_value.is_string();
}
+String GDScriptEditorTranslationParserPlugin::_parse_comment(int p_line, bool &r_skip) const {
+ // Parse inline comment.
+ if (comment_data->has(p_line)) {
+ const String stripped_comment = comment_data->get(p_line).comment.trim_prefix("#").strip_edges();
+
+ if (stripped_comment.begins_with("TRANSLATORS:")) {
+ return stripped_comment.trim_prefix("TRANSLATORS:").strip_edges(true, false);
+ }
+ if (stripped_comment == "NO_TRANSLATE" || stripped_comment.begins_with("NO_TRANSLATE:")) {
+ r_skip = true;
+ return String();
+ }
+ }
+
+ // Parse multiline comment.
+ String multiline_comment;
+ for (int line = p_line - 1; comment_data->has(line) && comment_data->get(line).new_line; line--) {
+ const String stripped_comment = comment_data->get(line).comment.trim_prefix("#").strip_edges();
+
+ if (stripped_comment.is_empty()) {
+ continue;
+ }
+
+ if (multiline_comment.is_empty()) {
+ multiline_comment = stripped_comment;
+ } else {
+ multiline_comment = stripped_comment + "\n" + multiline_comment;
+ }
+
+ if (stripped_comment.begins_with("TRANSLATORS:")) {
+ return multiline_comment.trim_prefix("TRANSLATORS:").strip_edges(true, false);
+ }
+ if (stripped_comment == "NO_TRANSLATE" || stripped_comment.begins_with("NO_TRANSLATE:")) {
+ r_skip = true;
+ return String();
+ }
+ }
+
+ return String();
+}
+
+void GDScriptEditorTranslationParserPlugin::_add_id(const String &p_id, int p_line) {
+ bool skip = false;
+ const String comment = _parse_comment(p_line, skip);
+ if (skip) {
+ return;
+ }
+
+ ids->push_back(p_id);
+ ids_comment.push_back(comment);
+}
+
+void GDScriptEditorTranslationParserPlugin::_add_id_ctx_plural(const Vector<String> &p_id_ctx_plural, int p_line) {
+ bool skip = false;
+ const String comment = _parse_comment(p_line, skip);
+ if (skip) {
+ return;
+ }
+
+ ids_ctx_plural->push_back(p_id_ctx_plural);
+ ids_ctx_plural_comment.push_back(comment);
+}
+
void GDScriptEditorTranslationParserPlugin::_traverse_class(const GDScriptParser::ClassNode *p_class) {
for (int i = 0; i < p_class->members.size(); i++) {
const GDScriptParser::ClassNode::Member &m = p_class->members[i];
@@ -253,7 +329,7 @@ void GDScriptEditorTranslationParserPlugin::_assess_assignment(const GDScriptPar
if (assignee_name != StringName() && assignment_patterns.has(assignee_name) && _is_constant_string(p_assignment->assigned_value)) {
// If the assignment is towards one of the extract patterns (text, tooltip_text etc.), and the value is a constant string, we collect the string.
- ids->push_back(p_assignment->assigned_value->reduced_value);
+ _add_id(p_assignment->assigned_value->reduced_value, p_assignment->assigned_value->start_line);
} else if (assignee_name == fd_filters) {
// Extract from `get_node("FileDialog").filters = <filter array>`.
_extract_fd_filter_array(p_assignment->assigned_value);
@@ -287,7 +363,7 @@ void GDScriptEditorTranslationParserPlugin::_assess_call(const GDScriptParser::C
}
}
if (extract_id_ctx_plural) {
- ids_ctx_plural->push_back(id_ctx_plural);
+ _add_id_ctx_plural(id_ctx_plural, p_call->start_line);
}
} else if (function_name == trn_func || function_name == atrn_func) {
// Extract from `tr_n(id, plural, n, ctx)` or `atr_n(id, plural, n, ctx)`.
@@ -307,20 +383,20 @@ void GDScriptEditorTranslationParserPlugin::_assess_call(const GDScriptParser::C
}
}
if (extract_id_ctx_plural) {
- ids_ctx_plural->push_back(id_ctx_plural);
+ _add_id_ctx_plural(id_ctx_plural, p_call->start_line);
}
} else if (first_arg_patterns.has(function_name)) {
if (!p_call->arguments.is_empty() && _is_constant_string(p_call->arguments[0])) {
- ids->push_back(p_call->arguments[0]->reduced_value);
+ _add_id(p_call->arguments[0]->reduced_value, p_call->arguments[0]->start_line);
}
} else if (second_arg_patterns.has(function_name)) {
if (p_call->arguments.size() > 1 && _is_constant_string(p_call->arguments[1])) {
- ids->push_back(p_call->arguments[1]->reduced_value);
+ _add_id(p_call->arguments[1]->reduced_value, p_call->arguments[1]->start_line);
}
} else if (function_name == fd_add_filter) {
// Extract the 'JPE Images' in this example - get_node("FileDialog").add_filter("*.jpg; JPE Images").
if (!p_call->arguments.is_empty()) {
- _extract_fd_filter_string(p_call->arguments[0]);
+ _extract_fd_filter_string(p_call->arguments[0], p_call->arguments[0]->start_line);
}
} else if (function_name == fd_set_filter) {
// Extract from `get_node("FileDialog").set_filters(<filter array>)`.
@@ -330,12 +406,12 @@ void GDScriptEditorTranslationParserPlugin::_assess_call(const GDScriptParser::C
}
}
-void GDScriptEditorTranslationParserPlugin::_extract_fd_filter_string(const GDScriptParser::ExpressionNode *p_expression) {
+void GDScriptEditorTranslationParserPlugin::_extract_fd_filter_string(const GDScriptParser::ExpressionNode *p_expression, int p_line) {
// Extract the name in "extension ; name".
if (_is_constant_string(p_expression)) {
PackedStringArray arr = p_expression->reduced_value.operator String().split(";", true);
ERR_FAIL_COND_MSG(arr.size() != 2, "Argument for setting FileDialog has bad format.");
- ids->push_back(arr[1].strip_edges());
+ _add_id(arr[1].strip_edges(), p_line);
}
}
@@ -355,7 +431,7 @@ void GDScriptEditorTranslationParserPlugin::_extract_fd_filter_array(const GDScr
if (array_node) {
for (int i = 0; i < array_node->elements.size(); i++) {
- _extract_fd_filter_string(array_node->elements[i]);
+ _extract_fd_filter_string(array_node->elements[i], array_node->elements[i]->start_line);
}
}
}
diff --git a/modules/gdscript/editor/gdscript_translation_parser_plugin.h b/modules/gdscript/editor/gdscript_translation_parser_plugin.h
index 61ff81ed66..73e8f53110 100644
--- a/modules/gdscript/editor/gdscript_translation_parser_plugin.h
+++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.h
@@ -32,16 +32,23 @@
#define GDSCRIPT_TRANSLATION_PARSER_PLUGIN_H
#include "../gdscript_parser.h"
+#include "../gdscript_tokenizer.h"
+#include "core/templates/hash_map.h"
#include "core/templates/hash_set.h"
#include "editor/editor_translation_parser.h"
class GDScriptEditorTranslationParserPlugin : public EditorTranslationParserPlugin {
GDCLASS(GDScriptEditorTranslationParserPlugin, EditorTranslationParserPlugin);
+ const HashMap<int, GDScriptTokenizer::CommentData> *comment_data = nullptr;
+
Vector<String> *ids = nullptr;
Vector<Vector<String>> *ids_ctx_plural = nullptr;
+ Vector<String> ids_comment;
+ Vector<String> ids_ctx_plural_comment;
+
// List of patterns used for extracting translation strings.
StringName tr_func = "tr";
StringName trn_func = "tr_n";
@@ -57,6 +64,11 @@ class GDScriptEditorTranslationParserPlugin : public EditorTranslationParserPlug
static bool _is_constant_string(const GDScriptParser::ExpressionNode *p_expression);
+ String _parse_comment(int p_line, bool &r_skip) const;
+
+ void _add_id(const String &p_id, int p_line);
+ void _add_id_ctx_plural(const Vector<String> &p_id_ctx_plural, int p_line);
+
void _traverse_class(const GDScriptParser::ClassNode *p_class);
void _traverse_function(const GDScriptParser::FunctionNode *p_func);
void _traverse_block(const GDScriptParser::SuiteNode *p_suite);
@@ -65,11 +77,12 @@ class GDScriptEditorTranslationParserPlugin : public EditorTranslationParserPlug
void _assess_assignment(const GDScriptParser::AssignmentNode *p_assignment);
void _assess_call(const GDScriptParser::CallNode *p_call);
- void _extract_fd_filter_string(const GDScriptParser::ExpressionNode *p_expression);
+ void _extract_fd_filter_string(const GDScriptParser::ExpressionNode *p_expression, int p_line);
void _extract_fd_filter_array(const GDScriptParser::ExpressionNode *p_expression);
public:
virtual Error parse_file(const String &p_path, Vector<String> *r_ids, Vector<Vector<String>> *r_ids_ctx_plural) override;
+ virtual void get_comments(Vector<String> *r_ids_comment, Vector<String> *r_ids_ctx_plural_comment) override;
virtual void get_recognized_extensions(List<String> *r_extensions) const override;
GDScriptEditorTranslationParserPlugin();
diff --git a/modules/gdscript/editor/script_templates/SCsub b/modules/gdscript/editor/script_templates/SCsub
index 5db7e3fc3b..28a27db3fa 100644
--- a/modules/gdscript/editor/script_templates/SCsub
+++ b/modules/gdscript/editor/script_templates/SCsub
@@ -1,4 +1,5 @@
#!/usr/bin/env python
+from misc.utility.scons_hints import *
Import("env")
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index 276a12f5de..8c094c0ab0 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -76,9 +76,16 @@ bool GDScriptNativeClass::_get(const StringName &p_name, Variant &r_ret) const {
if (ok) {
r_ret = v;
return true;
- } else {
- return false;
}
+
+ MethodBind *method = ClassDB::get_method(name, p_name);
+ if (method && method->is_static()) {
+ // Native static method.
+ r_ret = Callable(this, p_name);
+ return true;
+ }
+
+ return false;
}
void GDScriptNativeClass::_bind_methods() {
@@ -693,10 +700,16 @@ void GDScript::_static_default_init() {
continue;
}
if (type.builtin_type == Variant::ARRAY && type.has_container_element_type(0)) {
+ const GDScriptDataType element_type = type.get_container_element_type(0);
Array default_value;
- const GDScriptDataType &element_type = type.get_container_element_type(0);
default_value.set_typed(element_type.builtin_type, element_type.native_type, element_type.script_type);
static_variables.write[E.value.index] = default_value;
+ } else if (type.builtin_type == Variant::DICTIONARY && type.has_container_element_types()) {
+ const GDScriptDataType key_type = type.get_container_element_type_or_variant(0);
+ const GDScriptDataType value_type = type.get_container_element_type_or_variant(1);
+ Dictionary default_value;
+ default_value.set_typed(key_type.builtin_type, key_type.native_type, key_type.script_type, value_type.builtin_type, value_type.native_type, value_type.script_type);
+ static_variables.write[E.value.index] = default_value;
} else {
Variant default_value;
Callable::CallError err;
@@ -880,6 +893,11 @@ Error GDScript::reload(bool p_keep_state) {
if (can_run && p_keep_state) {
_restore_old_static_data();
}
+
+ if (p_keep_state) {
+ // Update the properties in the inspector.
+ update_exports();
+ }
#endif
reloading = false;
@@ -906,7 +924,7 @@ void GDScript::get_members(HashSet<StringName> *p_members) {
}
}
-const Variant GDScript::get_rpc_config() const {
+Variant GDScript::get_rpc_config() const {
return rpc_config;
}
@@ -1057,6 +1075,26 @@ void GDScript::_bind_methods() {
ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "new", &GDScript::_new, MethodInfo("new"));
}
+void GDScript::set_path_cache(const String &p_path) {
+ if (ResourceCache::has(p_path)) {
+ set_path(p_path, true);
+ return;
+ }
+
+ if (is_root_script()) {
+ Script::set_path_cache(p_path);
+ }
+
+ String old_path = path;
+ path = p_path;
+ path_valid = true;
+ GDScriptCache::move_script(old_path, p_path);
+
+ for (KeyValue<StringName, Ref<GDScript>> &kv : subclasses) {
+ kv.value->set_path_cache(p_path);
+ }
+}
+
void GDScript::set_path(const String &p_path, bool p_take_over) {
if (is_root_script()) {
Script::set_path(p_path, p_take_over);
@@ -1824,14 +1862,14 @@ Variant::Type GDScriptInstance::get_property_type(const StringName &p_name, bool
}
void GDScriptInstance::validate_property(PropertyInfo &p_property) const {
- Variant property = (Dictionary)p_property;
- const Variant *args[1] = { &property };
-
const GDScript *sptr = script.ptr();
while (sptr) {
if (likely(sptr->valid)) {
HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._validate_property);
if (E) {
+ Variant property = (Dictionary)p_property;
+ const Variant *args[1] = { &property };
+
Callable::CallError err;
Variant ret = E->value->call(const_cast<GDScriptInstance *>(this), args, 1, err);
if (err.error == Callable::CallError::CALL_OK) {
@@ -2535,11 +2573,11 @@ void GDScriptLanguage::reload_all_scripts() {
}
}
}
-#endif
+#endif // TOOLS_ENABLED
}
reload_scripts(scripts, true);
-#endif
+#endif // DEBUG_ENABLED
}
void GDScriptLanguage::reload_scripts(const Array &p_scripts, bool p_soft_reload) {
@@ -2609,7 +2647,7 @@ void GDScriptLanguage::reload_scripts(const Array &p_scripts, bool p_soft_reload
}
}
-#endif
+#endif // TOOLS_ENABLED
for (const KeyValue<ObjectID, List<Pair<StringName, Variant>>> &F : scr->pending_reload_state) {
map[F.key] = F.value; //pending to reload, use this one instead
@@ -2677,7 +2715,7 @@ void GDScriptLanguage::reload_scripts(const Array &p_scripts, bool p_soft_reload
//if instance states were saved, set them!
}
-#endif
+#endif // DEBUG_ENABLED
}
void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload) {
diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h
index 4d21651365..006a09debb 100644
--- a/modules/gdscript/gdscript.h
+++ b/modules/gdscript/gdscript.h
@@ -300,6 +300,7 @@ public:
virtual Error reload(bool p_keep_state = false) override;
+ virtual void set_path_cache(const String &p_path) override;
virtual void set_path(const String &p_path, bool p_take_over = false) override;
String get_script_path() const;
Error load_source_code(const String &p_path);
@@ -334,7 +335,7 @@ public:
virtual void get_constants(HashMap<StringName, Variant> *p_constants) override;
virtual void get_members(HashSet<StringName> *p_members) override;
- virtual const Variant get_rpc_config() const override;
+ virtual Variant get_rpc_config() const override;
void unload_static() const;
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index 2c5e6d46e7..6241ada06a 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -148,6 +148,15 @@ static GDScriptParser::DataType make_enum_type(const StringName &p_enum_name, co
return type;
}
+static GDScriptParser::DataType make_class_enum_type(const StringName &p_enum_name, GDScriptParser::ClassNode *p_class, const String &p_script_path, bool p_meta = true) {
+ GDScriptParser::DataType type = make_enum_type(p_enum_name, p_class->fqcn, p_meta);
+
+ type.class_type = p_class;
+ type.script_path = p_script_path;
+
+ return type;
+}
+
static GDScriptParser::DataType make_native_enum_type(const StringName &p_enum_name, const StringName &p_native_class, bool p_meta = true) {
// Find out which base class declared the enum, so the name is always the same even when coming from other contexts.
StringName native_base = p_native_class;
@@ -724,6 +733,18 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
result.set_container_element_type(0, container_type);
}
}
+ if (result.builtin_type == Variant::DICTIONARY) {
+ GDScriptParser::DataType key_type = type_from_metatype(resolve_datatype(p_type->get_container_type_or_null(0)));
+ if (key_type.kind != GDScriptParser::DataType::VARIANT) {
+ key_type.is_constant = false;
+ result.set_container_element_type(0, key_type);
+ }
+ GDScriptParser::DataType value_type = type_from_metatype(resolve_datatype(p_type->get_container_type_or_null(1)));
+ if (value_type.kind != GDScriptParser::DataType::VARIANT) {
+ value_type.is_constant = false;
+ result.set_container_element_type(1, value_type);
+ }
+ }
} else if (class_exists(first)) {
// Native engine classes.
result.kind = GDScriptParser::DataType::NATIVE;
@@ -884,11 +905,16 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
if (!p_type->container_types.is_empty()) {
if (result.builtin_type == Variant::ARRAY) {
if (p_type->container_types.size() != 1) {
- push_error("Arrays require exactly one collection element type.", p_type);
+ push_error(R"(Typed arrays require exactly one collection element type.)", p_type);
+ return bad_type;
+ }
+ } else if (result.builtin_type == Variant::DICTIONARY) {
+ if (p_type->container_types.size() != 2) {
+ push_error(R"(Typed dictionaries require exactly two collection element types.)", p_type);
return bad_type;
}
} else {
- push_error("Only arrays can specify collection element types.", p_type);
+ push_error(R"(Only arrays and dictionaries can specify collection element types.)", p_type);
return bad_type;
}
}
@@ -914,8 +940,8 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
Finally finally([&]() {
ensure_cached_external_parser_for_class(member.get_datatype().class_type, p_class, "Trying to resolve datatype of class member", p_source);
GDScriptParser::DataType member_type = member.get_datatype();
- if (member_type.has_container_element_type(0)) {
- ensure_cached_external_parser_for_class(member_type.get_container_element_type(0).class_type, p_class, "Trying to resolve datatype of class member", p_source);
+ for (int i = 0; i < member_type.get_container_element_type_count(); ++i) {
+ ensure_cached_external_parser_for_class(member_type.get_container_element_type(i).class_type, p_class, "Trying to resolve datatype of class member", p_source);
}
});
@@ -1084,7 +1110,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
check_class_member_name_conflict(p_class, member.m_enum->identifier->name, member.m_enum);
member.m_enum->set_datatype(resolving_datatype);
- GDScriptParser::DataType enum_type = make_enum_type(member.m_enum->identifier->name, p_class->fqcn, true);
+ GDScriptParser::DataType enum_type = make_class_enum_type(member.m_enum->identifier->name, p_class, parser->script_path, true);
const GDScriptParser::EnumNode *prev_enum = current_enum;
current_enum = member.m_enum;
@@ -1177,7 +1203,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
// Also update the original references.
member.enum_value.parent_enum->values.set(member.enum_value.index, member.enum_value);
- member.enum_value.identifier->set_datatype(make_enum_type(UNNAMED_ENUM, p_class->fqcn, false));
+ member.enum_value.identifier->set_datatype(make_class_enum_type(UNNAMED_ENUM, p_class, parser->script_path, false));
} break;
case GDScriptParser::ClassNode::Member::CLASS:
check_class_member_name_conflict(p_class, member.m_class->identifier->name, member.m_class);
@@ -1926,6 +1952,11 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi
if (has_specified_type && specified_type.has_container_element_type(0)) {
update_array_literal_element_type(array, specified_type.get_container_element_type(0));
}
+ } else if (p_assignable->initializer->type == GDScriptParser::Node::DICTIONARY) {
+ GDScriptParser::DictionaryNode *dictionary = static_cast<GDScriptParser::DictionaryNode *>(p_assignable->initializer);
+ if (has_specified_type && specified_type.has_container_element_types()) {
+ update_dictionary_literal_element_type(dictionary, specified_type.get_container_element_type_or_variant(0), specified_type.get_container_element_type_or_variant(1));
+ }
}
if (is_constant && !p_assignable->initializer->is_constant) {
@@ -1987,7 +2018,7 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi
} else {
push_error(vformat(R"(Cannot assign a value of type %s to %s "%s" with specified type %s.)", initializer_type.to_string(), p_kind, p_assignable->identifier->name, specified_type.to_string()), p_assignable->initializer);
}
- } else if (specified_type.has_container_element_type(0) && !initializer_type.has_container_element_type(0)) {
+ } else if ((specified_type.has_container_element_type(0) && !initializer_type.has_container_element_type(0)) || (specified_type.has_container_element_type(1) && !initializer_type.has_container_element_type(1))) {
mark_node_unsafe(p_assignable->initializer);
#ifdef DEBUG_ENABLED
} else if (specified_type.builtin_type == Variant::INT && initializer_type.builtin_type == Variant::FLOAT) {
@@ -2229,8 +2260,12 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) {
} else if (!is_type_compatible(specified_type, variable_type)) {
p_for->use_conversion_assign = true;
}
- if (p_for->list && p_for->list->type == GDScriptParser::Node::ARRAY) {
- update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_for->list), specified_type);
+ if (p_for->list) {
+ if (p_for->list->type == GDScriptParser::Node::ARRAY) {
+ update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_for->list), specified_type);
+ } else if (p_for->list->type == GDScriptParser::Node::DICTIONARY) {
+ update_dictionary_literal_element_type(static_cast<GDScriptParser::DictionaryNode *>(p_for->list), specified_type, GDScriptParser::DataType::get_variant_type());
+ }
}
}
p_for->variable->set_datatype(specified_type);
@@ -2432,6 +2467,9 @@ void GDScriptAnalyzer::resolve_return(GDScriptParser::ReturnNode *p_return) {
} else {
if (p_return->return_value->type == GDScriptParser::Node::ARRAY && has_expected_type && expected_type.has_container_element_type(0)) {
update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_return->return_value), expected_type.get_container_element_type(0));
+ } else if (p_return->return_value->type == GDScriptParser::Node::DICTIONARY && has_expected_type && expected_type.has_container_element_types()) {
+ update_dictionary_literal_element_type(static_cast<GDScriptParser::DictionaryNode *>(p_return->return_value),
+ expected_type.get_container_element_type_or_variant(0), expected_type.get_container_element_type_or_variant(1));
}
if (has_expected_type && expected_type.is_hard_type() && p_return->return_value->is_constant) {
update_const_expression_builtin_type(p_return->return_value, expected_type, "return");
@@ -2678,6 +2716,54 @@ void GDScriptAnalyzer::update_array_literal_element_type(GDScriptParser::ArrayNo
p_array->set_datatype(array_type);
}
+// When a dictionary literal is stored (or passed as function argument) to a typed context, we then assume the dictionary is typed.
+// This function determines which type is that (if any).
+void GDScriptAnalyzer::update_dictionary_literal_element_type(GDScriptParser::DictionaryNode *p_dictionary, const GDScriptParser::DataType &p_key_element_type, const GDScriptParser::DataType &p_value_element_type) {
+ GDScriptParser::DataType expected_key_type = p_key_element_type;
+ GDScriptParser::DataType expected_value_type = p_value_element_type;
+ expected_key_type.container_element_types.clear(); // Nested types (like `Dictionary[String, Array[int]]`) are not currently supported.
+ expected_value_type.container_element_types.clear();
+
+ for (int i = 0; i < p_dictionary->elements.size(); i++) {
+ GDScriptParser::ExpressionNode *key_element_node = p_dictionary->elements[i].key;
+ if (key_element_node->is_constant) {
+ update_const_expression_builtin_type(key_element_node, expected_key_type, "include");
+ }
+ const GDScriptParser::DataType &actual_key_type = key_element_node->get_datatype();
+ if (actual_key_type.has_no_type() || actual_key_type.is_variant() || !actual_key_type.is_hard_type()) {
+ mark_node_unsafe(key_element_node);
+ } else if (!is_type_compatible(expected_key_type, actual_key_type, true, p_dictionary)) {
+ if (is_type_compatible(actual_key_type, expected_key_type)) {
+ mark_node_unsafe(key_element_node);
+ } else {
+ push_error(vformat(R"(Cannot have a key of type "%s" in a dictionary of type "Dictionary[%s, %s]".)", actual_key_type.to_string(), expected_key_type.to_string(), expected_value_type.to_string()), key_element_node);
+ return;
+ }
+ }
+
+ GDScriptParser::ExpressionNode *value_element_node = p_dictionary->elements[i].value;
+ if (value_element_node->is_constant) {
+ update_const_expression_builtin_type(value_element_node, expected_value_type, "include");
+ }
+ const GDScriptParser::DataType &actual_value_type = value_element_node->get_datatype();
+ if (actual_value_type.has_no_type() || actual_value_type.is_variant() || !actual_value_type.is_hard_type()) {
+ mark_node_unsafe(value_element_node);
+ } else if (!is_type_compatible(expected_value_type, actual_value_type, true, p_dictionary)) {
+ if (is_type_compatible(actual_value_type, expected_value_type)) {
+ mark_node_unsafe(value_element_node);
+ } else {
+ push_error(vformat(R"(Cannot have a value of type "%s" in a dictionary of type "Dictionary[%s, %s]".)", actual_value_type.to_string(), expected_key_type.to_string(), expected_value_type.to_string()), value_element_node);
+ return;
+ }
+ }
+ }
+
+ GDScriptParser::DataType dictionary_type = p_dictionary->get_datatype();
+ dictionary_type.set_container_element_type(0, expected_key_type);
+ dictionary_type.set_container_element_type(1, expected_value_type);
+ p_dictionary->set_datatype(dictionary_type);
+}
+
void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assignment) {
reduce_expression(p_assignment->assigned_value);
@@ -2770,9 +2856,12 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
}
}
- // Check if assigned value is an array literal, so we can make it a typed array too if appropriate.
+ // Check if assigned value is an array/dictionary literal, so we can make it a typed container too if appropriate.
if (p_assignment->assigned_value->type == GDScriptParser::Node::ARRAY && assignee_type.is_hard_type() && assignee_type.has_container_element_type(0)) {
update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_assignment->assigned_value), assignee_type.get_container_element_type(0));
+ } else if (p_assignment->assigned_value->type == GDScriptParser::Node::DICTIONARY && assignee_type.is_hard_type() && assignee_type.has_container_element_types()) {
+ update_dictionary_literal_element_type(static_cast<GDScriptParser::DictionaryNode *>(p_assignment->assigned_value),
+ assignee_type.get_container_element_type_or_variant(0), assignee_type.get_container_element_type_or_variant(1));
}
if (p_assignment->operation == GDScriptParser::AssignmentNode::OP_NONE && assignee_type.is_hard_type() && p_assignment->assigned_value->is_constant) {
@@ -2850,8 +2939,8 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
// weak non-variant assignee and incompatible result
downgrades_assignee = true;
}
- } else if (assignee_type.has_container_element_type(0) && !op_type.has_container_element_type(0)) {
- // typed array assignee and untyped array result
+ } else if ((assignee_type.has_container_element_type(0) && !op_type.has_container_element_type(0)) || (assignee_type.has_container_element_type(1) && !op_type.has_container_element_type(1))) {
+ // Typed assignee and untyped result.
mark_node_unsafe(p_assignment);
}
}
@@ -3049,10 +3138,13 @@ const char *check_for_renamed_identifier(String identifier, GDScriptParser::Node
void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_await, bool p_is_root) {
bool all_is_constant = true;
HashMap<int, GDScriptParser::ArrayNode *> arrays; // For array literal to potentially type when passing.
+ HashMap<int, GDScriptParser::DictionaryNode *> dictionaries; // Same, but for dictionaries.
for (int i = 0; i < p_call->arguments.size(); i++) {
reduce_expression(p_call->arguments[i]);
if (p_call->arguments[i]->type == GDScriptParser::Node::ARRAY) {
arrays[i] = static_cast<GDScriptParser::ArrayNode *>(p_call->arguments[i]);
+ } else if (p_call->arguments[i]->type == GDScriptParser::Node::DICTIONARY) {
+ dictionaries[i] = static_cast<GDScriptParser::DictionaryNode *>(p_call->arguments[i]);
}
all_is_constant = all_is_constant && p_call->arguments[i]->is_constant;
}
@@ -3457,6 +3549,14 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
update_array_literal_element_type(E.value, par_types.get(index).get_container_element_type(0));
}
}
+ for (const KeyValue<int, GDScriptParser::DictionaryNode *> &E : dictionaries) {
+ int index = E.key;
+ if (index < par_types.size() && par_types.get(index).is_hard_type() && par_types.get(index).has_container_element_types()) {
+ GDScriptParser::DataType key = par_types.get(index).get_container_element_type_or_variant(0);
+ GDScriptParser::DataType value = par_types.get(index).get_container_element_type_or_variant(1);
+ update_dictionary_literal_element_type(E.value, key, value);
+ }
+ }
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) {
@@ -3601,6 +3701,11 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) {
update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_cast->operand), cast_type.get_container_element_type(0));
}
+ if (p_cast->operand->type == GDScriptParser::Node::DICTIONARY && cast_type.has_container_element_types()) {
+ update_dictionary_literal_element_type(static_cast<GDScriptParser::DictionaryNode *>(p_cast->operand),
+ cast_type.get_container_element_type_or_variant(0), cast_type.get_container_element_type_or_variant(1));
+ }
+
if (!cast_type.is_variant()) {
GDScriptParser::DataType op_type = p_cast->operand->get_datatype();
if (op_type.is_variant() || !op_type.is_hard_type()) {
@@ -3711,6 +3816,12 @@ GDScriptParser::DataType GDScriptAnalyzer::make_global_class_meta_type(const Str
}
Ref<GDScriptParserRef> GDScriptAnalyzer::ensure_cached_external_parser_for_class(const GDScriptParser::ClassNode *p_class, const GDScriptParser::ClassNode *p_from_class, const char *p_context, const GDScriptParser::Node *p_source) {
+ // Delicate piece of code that intentionally doesn't use the GDScript cache or `get_depended_parser_for`.
+ // Search dependencies for the parser that owns `p_class` and make a cache entry for it.
+ // Required for how we store pointers to classes owned by other parser trees and need to call `resolve_class_member` and such on the same parser tree.
+ // Since https://github.com/godotengine/godot/pull/94871 there can technically be multiple parsers for the same script in the same parser tree.
+ // Even if unlikely, getting the wrong parser could lead to strange undefined behavior without errors.
+
if (p_class == nullptr) {
return nullptr;
}
@@ -3727,8 +3838,6 @@ Ref<GDScriptParserRef> GDScriptAnalyzer::ensure_cached_external_parser_for_class
p_from_class = parser->head;
}
- String script_path = p_class->get_datatype().script_path;
-
Ref<GDScriptParserRef> parser_ref;
for (const GDScriptParser::ClassNode *look_class = p_from_class; look_class != nullptr; look_class = look_class->base_type.class_type) {
if (parser->has_class(look_class)) {
@@ -4153,7 +4262,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
const GDScriptParser::EnumNode::Value &element = current_enum->values[i];
if (element.identifier->name == p_identifier->name) {
StringName enum_name = current_enum->identifier ? current_enum->identifier->name : UNNAMED_ENUM;
- GDScriptParser::DataType type = make_enum_type(enum_name, parser->current_class->fqcn, false);
+ GDScriptParser::DataType type = make_class_enum_type(enum_name, parser->current_class, parser->script_path, false);
if (element.parent_enum->identifier) {
type.enum_type = element.parent_enum->identifier->name;
}
@@ -4625,10 +4734,23 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
reduce_identifier_from_base(p_subscript->attribute, &base_type);
GDScriptParser::DataType attr_type = p_subscript->attribute->get_datatype();
if (attr_type.is_set()) {
- valid = !attr_type.is_pseudo_type || p_can_be_pseudo_type;
- result_type = attr_type;
- p_subscript->is_constant = p_subscript->attribute->is_constant;
- p_subscript->reduced_value = p_subscript->attribute->reduced_value;
+ if (base_type.builtin_type == Variant::DICTIONARY && base_type.has_container_element_types()) {
+ Variant::Type key_type = base_type.get_container_element_type_or_variant(0).builtin_type;
+ valid = key_type == Variant::NIL || key_type == Variant::STRING || key_type == Variant::STRING_NAME;
+ if (base_type.has_container_element_type(1)) {
+ result_type = base_type.get_container_element_type(1);
+ result_type.type_source = base_type.type_source;
+ } else {
+ result_type.builtin_type = Variant::NIL;
+ result_type.kind = GDScriptParser::DataType::VARIANT;
+ result_type.type_source = GDScriptParser::DataType::UNDETECTED;
+ }
+ } else {
+ valid = !attr_type.is_pseudo_type || p_can_be_pseudo_type;
+ result_type = attr_type;
+ p_subscript->is_constant = p_subscript->attribute->is_constant;
+ p_subscript->reduced_value = p_subscript->attribute->reduced_value;
+ }
} else if (!base_type.is_meta_type || !base_type.is_constant) {
valid = base_type.kind != GDScriptParser::DataType::BUILTIN;
#ifdef DEBUG_ENABLED
@@ -4735,8 +4857,40 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
case Variant::SIGNAL:
case Variant::STRING_NAME:
break;
- // Here for completeness.
+ // Support depends on if the dictionary has a typed key, otherwise anything is valid.
case Variant::DICTIONARY:
+ if (base_type.has_container_element_type(0)) {
+ GDScriptParser::DataType key_type = base_type.get_container_element_type(0);
+ switch (index_type.builtin_type) {
+ // Null value will be treated as an empty object, allow.
+ case Variant::NIL:
+ error = key_type.builtin_type != Variant::OBJECT;
+ break;
+ // Objects are parsed for validity in a similar manner to container types.
+ case Variant::OBJECT:
+ if (key_type.builtin_type == Variant::OBJECT) {
+ error = !key_type.can_reference(index_type);
+ } else {
+ error = key_type.builtin_type != Variant::NIL;
+ }
+ break;
+ // String and StringName interchangeable in this context.
+ case Variant::STRING:
+ case Variant::STRING_NAME:
+ error = key_type.builtin_type != Variant::STRING_NAME && key_type.builtin_type != Variant::STRING;
+ break;
+ // Ints are valid indices for floats, but not the other way around.
+ case Variant::INT:
+ error = key_type.builtin_type != Variant::INT && key_type.builtin_type != Variant::FLOAT;
+ break;
+ // All other cases require the types to match exactly.
+ default:
+ error = key_type.builtin_type != index_type.builtin_type;
+ break;
+ }
+ }
+ break;
+ // Here for completeness.
case Variant::VARIANT_MAX:
break;
}
@@ -4825,7 +4979,6 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
case Variant::PROJECTION:
case Variant::PLANE:
case Variant::COLOR:
- case Variant::DICTIONARY:
case Variant::OBJECT:
result_type.kind = GDScriptParser::DataType::VARIANT;
result_type.type_source = GDScriptParser::DataType::UNDETECTED;
@@ -4840,6 +4993,16 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
result_type.type_source = GDScriptParser::DataType::UNDETECTED;
}
break;
+ // Can have two element types, but we only care about the value.
+ case Variant::DICTIONARY:
+ if (base_type.has_container_element_type(1)) {
+ result_type = base_type.get_container_element_type(1);
+ result_type.type_source = base_type.type_source;
+ } else {
+ result_type.kind = GDScriptParser::DataType::VARIANT;
+ result_type.type_source = GDScriptParser::DataType::UNDETECTED;
+ }
+ break;
// Here for completeness.
case Variant::VARIANT_MAX:
break;
@@ -5019,7 +5182,9 @@ Variant GDScriptAnalyzer::make_array_reduced_value(GDScriptParser::ArrayNode *p_
}
Variant GDScriptAnalyzer::make_dictionary_reduced_value(GDScriptParser::DictionaryNode *p_dictionary, bool &is_reduced) {
- Dictionary dictionary;
+ Dictionary dictionary = p_dictionary->get_datatype().has_container_element_types()
+ ? make_dictionary_from_element_datatype(p_dictionary->get_datatype().get_container_element_type_or_variant(0), p_dictionary->get_datatype().get_container_element_type_or_variant(1))
+ : Dictionary();
for (int i = 0; i < p_dictionary->elements.size(); i++) {
const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i];
@@ -5106,6 +5271,49 @@ Array GDScriptAnalyzer::make_array_from_element_datatype(const GDScriptParser::D
return array;
}
+Dictionary GDScriptAnalyzer::make_dictionary_from_element_datatype(const GDScriptParser::DataType &p_key_element_datatype, const GDScriptParser::DataType &p_value_element_datatype, const GDScriptParser::Node *p_source_node) {
+ Dictionary dictionary;
+ StringName key_name;
+ Variant key_script;
+ StringName value_name;
+ Variant value_script;
+
+ if (p_key_element_datatype.builtin_type == Variant::OBJECT) {
+ Ref<Script> script_type = p_key_element_datatype.script_type;
+ if (p_key_element_datatype.kind == GDScriptParser::DataType::CLASS && script_type.is_null()) {
+ Error err = OK;
+ Ref<GDScript> scr = get_depended_shallow_script(p_key_element_datatype.script_path, err);
+ if (err) {
+ push_error(vformat(R"(Error while getting cache for script "%s".)", p_key_element_datatype.script_path), p_source_node);
+ return dictionary;
+ }
+ script_type.reference_ptr(scr->find_class(p_key_element_datatype.class_type->fqcn));
+ }
+
+ key_name = p_key_element_datatype.native_type;
+ key_script = script_type;
+ }
+
+ if (p_value_element_datatype.builtin_type == Variant::OBJECT) {
+ Ref<Script> script_type = p_value_element_datatype.script_type;
+ if (p_value_element_datatype.kind == GDScriptParser::DataType::CLASS && script_type.is_null()) {
+ Error err = OK;
+ Ref<GDScript> scr = get_depended_shallow_script(p_value_element_datatype.script_path, err);
+ if (err) {
+ push_error(vformat(R"(Error while getting cache for script "%s".)", p_value_element_datatype.script_path), p_source_node);
+ return dictionary;
+ }
+ script_type.reference_ptr(scr->find_class(p_value_element_datatype.class_type->fqcn));
+ }
+
+ value_name = p_value_element_datatype.native_type;
+ value_script = script_type;
+ }
+
+ dictionary.set_typed(p_key_element_datatype.builtin_type, key_name, key_script, p_value_element_datatype.builtin_type, value_name, value_script);
+ return dictionary;
+}
+
Variant GDScriptAnalyzer::make_variable_default_value(GDScriptParser::VariableNode *p_variable) {
Variant result = Variant();
@@ -5121,6 +5329,10 @@ Variant GDScriptAnalyzer::make_variable_default_value(GDScriptParser::VariableNo
if (datatype.kind == GDScriptParser::DataType::BUILTIN && datatype.builtin_type != Variant::OBJECT) {
if (datatype.builtin_type == Variant::ARRAY && datatype.has_container_element_type(0)) {
result = make_array_from_element_datatype(datatype.get_container_element_type(0));
+ } else if (datatype.builtin_type == Variant::DICTIONARY && datatype.has_container_element_types()) {
+ GDScriptParser::DataType key = datatype.get_container_element_type_or_variant(0);
+ GDScriptParser::DataType value = datatype.get_container_element_type_or_variant(1);
+ result = make_dictionary_from_element_datatype(key, value);
} else {
VariantInternal::initialize(&result, datatype.builtin_type);
}
@@ -5149,6 +5361,22 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_va
} else if (array.get_typed_builtin() != Variant::NIL) {
result.set_container_element_type(0, type_from_metatype(make_builtin_meta_type((Variant::Type)array.get_typed_builtin())));
}
+ } else if (p_value.get_type() == Variant::DICTIONARY) {
+ const Dictionary &dict = p_value;
+ if (dict.get_typed_key_script()) {
+ result.set_container_element_type(0, type_from_metatype(make_script_meta_type(dict.get_typed_key_script())));
+ } else if (dict.get_typed_key_class_name()) {
+ result.set_container_element_type(0, type_from_metatype(make_native_meta_type(dict.get_typed_key_class_name())));
+ } else if (dict.get_typed_key_builtin() != Variant::NIL) {
+ result.set_container_element_type(0, type_from_metatype(make_builtin_meta_type((Variant::Type)dict.get_typed_key_builtin())));
+ }
+ if (dict.get_typed_value_script()) {
+ result.set_container_element_type(1, type_from_metatype(make_script_meta_type(dict.get_typed_value_script())));
+ } else if (dict.get_typed_value_class_name()) {
+ result.set_container_element_type(1, type_from_metatype(make_native_meta_type(dict.get_typed_value_class_name())));
+ } else if (dict.get_typed_value_builtin() != Variant::NIL) {
+ result.set_container_element_type(1, type_from_metatype(make_builtin_meta_type((Variant::Type)dict.get_typed_value_builtin())));
+ }
} else if (p_value.get_type() == Variant::OBJECT) {
// Object is treated as a native type, not a builtin type.
result.kind = GDScriptParser::DataType::NATIVE;
@@ -5281,6 +5509,60 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo
}
elem_type.is_constant = false;
result.set_container_element_type(0, elem_type);
+ } else if (p_property.type == Variant::DICTIONARY && p_property.hint == PROPERTY_HINT_DICTIONARY_TYPE) {
+ // Check element type.
+ StringName key_elem_type_name = p_property.hint_string.get_slice(";", 0);
+ GDScriptParser::DataType key_elem_type;
+ key_elem_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+
+ Variant::Type key_elem_builtin_type = GDScriptParser::get_builtin_type(key_elem_type_name);
+ if (key_elem_builtin_type < Variant::VARIANT_MAX) {
+ // Builtin type.
+ key_elem_type.kind = GDScriptParser::DataType::BUILTIN;
+ key_elem_type.builtin_type = key_elem_builtin_type;
+ } else if (class_exists(key_elem_type_name)) {
+ key_elem_type.kind = GDScriptParser::DataType::NATIVE;
+ key_elem_type.builtin_type = Variant::OBJECT;
+ key_elem_type.native_type = key_elem_type_name;
+ } else if (ScriptServer::is_global_class(key_elem_type_name)) {
+ // Just load this as it shouldn't be a GDScript.
+ Ref<Script> script = ResourceLoader::load(ScriptServer::get_global_class_path(key_elem_type_name));
+ key_elem_type.kind = GDScriptParser::DataType::SCRIPT;
+ key_elem_type.builtin_type = Variant::OBJECT;
+ key_elem_type.native_type = script->get_instance_base_type();
+ key_elem_type.script_type = script;
+ } else {
+ ERR_FAIL_V_MSG(result, "Could not find element type from property hint of a typed dictionary.");
+ }
+ key_elem_type.is_constant = false;
+
+ StringName value_elem_type_name = p_property.hint_string.get_slice(";", 1);
+ GDScriptParser::DataType value_elem_type;
+ value_elem_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+
+ Variant::Type value_elem_builtin_type = GDScriptParser::get_builtin_type(value_elem_type_name);
+ if (value_elem_builtin_type < Variant::VARIANT_MAX) {
+ // Builtin type.
+ value_elem_type.kind = GDScriptParser::DataType::BUILTIN;
+ value_elem_type.builtin_type = value_elem_builtin_type;
+ } else if (class_exists(value_elem_type_name)) {
+ value_elem_type.kind = GDScriptParser::DataType::NATIVE;
+ value_elem_type.builtin_type = Variant::OBJECT;
+ value_elem_type.native_type = value_elem_type_name;
+ } else if (ScriptServer::is_global_class(value_elem_type_name)) {
+ // Just load this as it shouldn't be a GDScript.
+ Ref<Script> script = ResourceLoader::load(ScriptServer::get_global_class_path(value_elem_type_name));
+ value_elem_type.kind = GDScriptParser::DataType::SCRIPT;
+ value_elem_type.builtin_type = Variant::OBJECT;
+ value_elem_type.native_type = script->get_instance_base_type();
+ value_elem_type.script_type = script;
+ } else {
+ ERR_FAIL_V_MSG(result, "Could not find element type from property hint of a typed dictionary.");
+ }
+ value_elem_type.is_constant = false;
+
+ result.set_container_element_type(0, key_elem_type);
+ result.set_container_element_type(1, value_elem_type);
} else if (p_property.type == Variant::INT) {
// Check if it's enum.
if ((p_property.usage & PROPERTY_USAGE_CLASS_IS_ENUM) && p_property.class_name != StringName()) {
@@ -5527,8 +5809,6 @@ void GDScriptAnalyzer::validate_call_arg(const List<GDScriptParser::DataType> &p
#ifdef DEBUG_ENABLED
void GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_identifier, const String &p_context, const bool p_in_local_scope) {
const StringName &name = p_identifier->name;
- GDScriptParser::DataType base = parser->current_class->get_datatype();
- GDScriptParser::ClassNode *base_class = base.class_type;
{
List<MethodInfo> gdscript_funcs;
@@ -5556,40 +5836,56 @@ void GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_identifier
}
}
+ const GDScriptParser::DataType current_class_type = parser->current_class->get_datatype();
if (p_in_local_scope) {
- while (base_class != nullptr) {
+ GDScriptParser::ClassNode *base_class = current_class_type.class_type;
+
+ if (base_class != nullptr) {
if (base_class->has_member(name)) {
parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE, p_context, p_identifier->name, base_class->get_member(name).get_type_name(), itos(base_class->get_member(name).get_line()));
return;
}
base_class = base_class->base_type.class_type;
}
+
+ while (base_class != nullptr) {
+ if (base_class->has_member(name)) {
+ String base_class_name = base_class->get_global_name();
+ if (base_class_name.is_empty()) {
+ base_class_name = base_class->fqcn;
+ }
+
+ parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, base_class->get_member(name).get_type_name(), itos(base_class->get_member(name).get_line()), base_class_name);
+ return;
+ }
+ base_class = base_class->base_type.class_type;
+ }
}
- StringName parent = base.native_type;
- while (parent != StringName()) {
- ERR_FAIL_COND_MSG(!class_exists(parent), "Non-existent native base class.");
+ StringName native_base_class = current_class_type.native_type;
+ while (native_base_class != StringName()) {
+ ERR_FAIL_COND_MSG(!class_exists(native_base_class), "Non-existent native base class.");
- if (ClassDB::has_method(parent, name, true)) {
- parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "method", parent);
+ if (ClassDB::has_method(native_base_class, name, true)) {
+ parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "method", native_base_class);
return;
- } else if (ClassDB::has_signal(parent, name, true)) {
- parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "signal", parent);
+ } else if (ClassDB::has_signal(native_base_class, name, true)) {
+ parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "signal", native_base_class);
return;
- } else if (ClassDB::has_property(parent, name, true)) {
- parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "property", parent);
+ } else if (ClassDB::has_property(native_base_class, name, true)) {
+ parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "property", native_base_class);
return;
- } else if (ClassDB::has_integer_constant(parent, name, true)) {
- parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "constant", parent);
+ } else if (ClassDB::has_integer_constant(native_base_class, name, true)) {
+ parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "constant", native_base_class);
return;
- } else if (ClassDB::has_enum(parent, name, true)) {
- parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "enum", parent);
+ } else if (ClassDB::has_enum(native_base_class, name, true)) {
+ parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "enum", native_base_class);
return;
}
- parent = ClassDB::get_parent_class(parent);
+ native_base_class = ClassDB::get_parent_class(native_base_class);
}
}
-#endif
+#endif // DEBUG_ENABLED
GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, bool &r_valid, const GDScriptParser::Node *p_source) {
// Unary version.
@@ -5701,6 +5997,15 @@ bool GDScriptAnalyzer::check_type_compatibility(const GDScriptParser::DataType &
valid = p_target.get_container_element_type(0) == p_source.get_container_element_type(0);
}
}
+ if (valid && p_target.builtin_type == Variant::DICTIONARY && p_source.builtin_type == Variant::DICTIONARY) {
+ // Check the element types.
+ if (p_target.has_container_element_type(0) && p_source.has_container_element_type(0)) {
+ valid = p_target.get_container_element_type(0) == p_source.get_container_element_type(0);
+ }
+ if (valid && p_target.has_container_element_type(1) && p_source.has_container_element_type(1)) {
+ valid = p_target.get_container_element_type(1) == p_source.get_container_element_type(1);
+ }
+ }
return valid;
}
diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h
index 25e5aa9a2c..3b781409a4 100644
--- a/modules/gdscript/gdscript_analyzer.h
+++ b/modules/gdscript/gdscript_analyzer.h
@@ -125,6 +125,7 @@ class GDScriptAnalyzer {
// Helpers.
Array make_array_from_element_datatype(const GDScriptParser::DataType &p_element_datatype, const GDScriptParser::Node *p_source_node = nullptr);
+ Dictionary make_dictionary_from_element_datatype(const GDScriptParser::DataType &p_key_element_datatype, const GDScriptParser::DataType &p_value_element_datatype, const GDScriptParser::Node *p_source_node = nullptr);
GDScriptParser::DataType type_from_variant(const Variant &p_value, const GDScriptParser::Node *p_source);
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;
@@ -137,6 +138,7 @@ class GDScriptAnalyzer {
GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, bool &r_valid, const GDScriptParser::Node *p_source);
void update_const_expression_builtin_type(GDScriptParser::ExpressionNode *p_expression, const GDScriptParser::DataType &p_type, const char *p_usage, bool p_is_cast = false);
void update_array_literal_element_type(GDScriptParser::ArrayNode *p_array, const GDScriptParser::DataType &p_element_type);
+ void update_dictionary_literal_element_type(GDScriptParser::DictionaryNode *p_dictionary, const GDScriptParser::DataType &p_key_element_type, const GDScriptParser::DataType &p_value_element_type);
bool is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false, const GDScriptParser::Node *p_source_node = nullptr);
void push_error(const String &p_message, const GDScriptParser::Node *p_origin = nullptr);
void mark_node_unsafe(const GDScriptParser::Node *p_node);
diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp
index 4cda3d3037..fb4d27caab 100644
--- a/modules/gdscript/gdscript_byte_codegen.cpp
+++ b/modules/gdscript/gdscript_byte_codegen.cpp
@@ -585,8 +585,25 @@ void GDScriptByteCodeGenerator::write_unary_operator(const Address &p_target, Va
}
void GDScriptByteCodeGenerator::write_binary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand, const Address &p_right_operand) {
- // Avoid validated evaluator for modulo and division when operands are int, since there's no check for division by zero.
- if (HAS_BUILTIN_TYPE(p_left_operand) && HAS_BUILTIN_TYPE(p_right_operand) && ((p_operator != Variant::OP_DIVIDE && p_operator != Variant::OP_MODULE) || p_left_operand.type.builtin_type != Variant::INT || p_right_operand.type.builtin_type != Variant::INT)) {
+ bool valid = HAS_BUILTIN_TYPE(p_left_operand) && HAS_BUILTIN_TYPE(p_right_operand);
+
+ // Avoid validated evaluator for modulo and division when operands are int or integer vector, since there's no check for division by zero.
+ if (valid && (p_operator == Variant::OP_DIVIDE || p_operator == Variant::OP_MODULE)) {
+ switch (p_left_operand.type.builtin_type) {
+ case Variant::INT:
+ valid = p_right_operand.type.builtin_type != Variant::INT;
+ break;
+ case Variant::VECTOR2I:
+ case Variant::VECTOR3I:
+ case Variant::VECTOR4I:
+ valid = p_right_operand.type.builtin_type != Variant::INT && p_right_operand.type.builtin_type != p_left_operand.type.builtin_type;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (valid) {
if (p_target.mode == Address::TEMPORARY) {
Variant::Type result_type = Variant::get_operator_return_type(p_operator, p_left_operand.type.builtin_type, p_right_operand.type.builtin_type);
Variant::Type temp_type = temporaries[p_target.address].type;
@@ -634,6 +651,18 @@ void GDScriptByteCodeGenerator::write_type_test(const Address &p_target, const A
append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(element_type.builtin_type);
append(element_type.native_type);
+ } else if (p_type.builtin_type == Variant::DICTIONARY && p_type.has_container_element_types()) {
+ const GDScriptDataType &key_element_type = p_type.get_container_element_type_or_variant(0);
+ const GDScriptDataType &value_element_type = p_type.get_container_element_type_or_variant(1);
+ append_opcode(GDScriptFunction::OPCODE_TYPE_TEST_DICTIONARY);
+ append(p_target);
+ append(p_source);
+ append(get_constant_pos(key_element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
+ append(get_constant_pos(value_element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
+ append(key_element_type.builtin_type);
+ append(key_element_type.native_type);
+ append(value_element_type.builtin_type);
+ append(value_element_type.native_type);
} else {
append_opcode(GDScriptFunction::OPCODE_TYPE_TEST_BUILTIN);
append(p_target);
@@ -889,6 +918,18 @@ void GDScriptByteCodeGenerator::write_assign_with_conversion(const Address &p_ta
append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(element_type.builtin_type);
append(element_type.native_type);
+ } else if (p_target.type.builtin_type == Variant::DICTIONARY && p_target.type.has_container_element_types()) {
+ const GDScriptDataType &key_type = p_target.type.get_container_element_type_or_variant(0);
+ const GDScriptDataType &value_type = p_target.type.get_container_element_type_or_variant(1);
+ append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_DICTIONARY);
+ append(p_target);
+ append(p_source);
+ append(get_constant_pos(key_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
+ append(get_constant_pos(value_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
+ append(key_type.builtin_type);
+ append(key_type.native_type);
+ append(value_type.builtin_type);
+ append(value_type.native_type);
} else {
append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN);
append(p_target);
@@ -935,6 +976,18 @@ void GDScriptByteCodeGenerator::write_assign(const Address &p_target, const Addr
append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(element_type.builtin_type);
append(element_type.native_type);
+ } else if (p_target.type.kind == GDScriptDataType::BUILTIN && p_target.type.builtin_type == Variant::DICTIONARY && p_target.type.has_container_element_types()) {
+ const GDScriptDataType &key_type = p_target.type.get_container_element_type_or_variant(0);
+ const GDScriptDataType &value_type = p_target.type.get_container_element_type_or_variant(1);
+ append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_DICTIONARY);
+ append(p_target);
+ append(p_source);
+ append(get_constant_pos(key_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
+ append(get_constant_pos(value_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
+ append(key_type.builtin_type);
+ append(key_type.native_type);
+ append(value_type.builtin_type);
+ append(value_type.native_type);
} else if (p_target.type.kind == GDScriptDataType::BUILTIN && p_source.type.kind == GDScriptDataType::BUILTIN && p_target.type.builtin_type != p_source.type.builtin_type) {
// Need conversion.
append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN);
@@ -1434,6 +1487,23 @@ void GDScriptByteCodeGenerator::write_construct_dictionary(const Address &p_targ
ct.cleanup();
}
+void GDScriptByteCodeGenerator::write_construct_typed_dictionary(const Address &p_target, const GDScriptDataType &p_key_type, const GDScriptDataType &p_value_type, const Vector<Address> &p_arguments) {
+ append_opcode_and_argcount(GDScriptFunction::OPCODE_CONSTRUCT_TYPED_DICTIONARY, 3 + p_arguments.size());
+ for (int i = 0; i < p_arguments.size(); i++) {
+ append(p_arguments[i]);
+ }
+ CallTarget ct = get_call_target(p_target);
+ append(ct.target);
+ append(get_constant_pos(p_key_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
+ append(get_constant_pos(p_value_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
+ append(p_arguments.size() / 2); // This is number of key-value pairs, so only half of actual arguments.
+ append(p_key_type.builtin_type);
+ append(p_key_type.native_type);
+ append(p_value_type.builtin_type);
+ append(p_value_type.native_type);
+ ct.cleanup();
+}
+
void GDScriptByteCodeGenerator::write_await(const Address &p_target, const Address &p_operand) {
append_opcode(GDScriptFunction::OPCODE_AWAIT);
append(p_operand);
@@ -1711,6 +1781,19 @@ void GDScriptByteCodeGenerator::write_return(const Address &p_return_value) {
append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(element_type.builtin_type);
append(element_type.native_type);
+ } else if (function->return_type.kind == GDScriptDataType::BUILTIN && function->return_type.builtin_type == Variant::DICTIONARY &&
+ function->return_type.has_container_element_types()) {
+ // Typed dictionary.
+ const GDScriptDataType &key_type = function->return_type.get_container_element_type_or_variant(0);
+ const GDScriptDataType &value_type = function->return_type.get_container_element_type_or_variant(1);
+ append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_DICTIONARY);
+ append(p_return_value);
+ append(get_constant_pos(key_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
+ append(get_constant_pos(value_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
+ append(key_type.builtin_type);
+ append(key_type.native_type);
+ append(value_type.builtin_type);
+ append(value_type.native_type);
} else if (function->return_type.kind == GDScriptDataType::BUILTIN && p_return_value.type.kind == GDScriptDataType::BUILTIN && function->return_type.builtin_type != p_return_value.type.builtin_type) {
// Add conversion.
append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_BUILTIN);
@@ -1735,6 +1818,17 @@ void GDScriptByteCodeGenerator::write_return(const Address &p_return_value) {
append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(element_type.builtin_type);
append(element_type.native_type);
+ } else if (function->return_type.builtin_type == Variant::DICTIONARY && function->return_type.has_container_element_types()) {
+ const GDScriptDataType &key_type = function->return_type.get_container_element_type_or_variant(0);
+ const GDScriptDataType &value_type = function->return_type.get_container_element_type_or_variant(1);
+ append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_DICTIONARY);
+ append(p_return_value);
+ append(get_constant_pos(key_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
+ append(get_constant_pos(value_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
+ append(key_type.builtin_type);
+ append(key_type.native_type);
+ append(value_type.builtin_type);
+ append(value_type.native_type);
} else {
append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_BUILTIN);
append(p_return_value);
@@ -1803,6 +1897,13 @@ void GDScriptByteCodeGenerator::clear_address(const Address &p_address) {
case Variant::BOOL:
write_assign_false(p_address);
break;
+ case Variant::DICTIONARY:
+ if (p_address.type.has_container_element_types()) {
+ write_construct_typed_dictionary(p_address, p_address.type.get_container_element_type_or_variant(0), p_address.type.get_container_element_type_or_variant(1), Vector<GDScriptCodeGenerator::Address>());
+ } else {
+ write_construct(p_address, p_address.type.builtin_type, Vector<GDScriptCodeGenerator::Address>());
+ }
+ break;
case Variant::ARRAY:
if (p_address.type.has_container_element_type(0)) {
write_construct_typed_array(p_address, p_address.type.get_container_element_type(0), Vector<GDScriptCodeGenerator::Address>());
diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h
index 34f56a2f5c..6303db71fd 100644
--- a/modules/gdscript/gdscript_byte_codegen.h
+++ b/modules/gdscript/gdscript_byte_codegen.h
@@ -529,6 +529,7 @@ public:
virtual void write_construct_array(const Address &p_target, const Vector<Address> &p_arguments) override;
virtual void write_construct_typed_array(const Address &p_target, const GDScriptDataType &p_element_type, const Vector<Address> &p_arguments) override;
virtual void write_construct_dictionary(const Address &p_target, const Vector<Address> &p_arguments) override;
+ virtual void write_construct_typed_dictionary(const Address &p_target, const GDScriptDataType &p_key_type, const GDScriptDataType &p_value_type, const Vector<Address> &p_arguments) override;
virtual void write_await(const Address &p_target, const Address &p_operand) override;
virtual void write_if(const Address &p_condition) override;
virtual void write_else() override;
diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp
index b3c0744bdf..fa22798edf 100644
--- a/modules/gdscript/gdscript_cache.cpp
+++ b/modules/gdscript/gdscript_cache.cpp
@@ -297,6 +297,7 @@ Vector<uint8_t> GDScriptCache::get_binary_tokens(const String &p_path) {
Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, Error &r_error, const String &p_owner) {
MutexLock lock(singleton->mutex);
+
if (!p_owner.is_empty()) {
singleton->dependencies[p_owner].insert(p_path);
}
@@ -307,11 +308,11 @@ Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, Error &r_e
return singleton->shallow_gdscript_cache[p_path];
}
- String remapped_path = ResourceLoader::path_remap(p_path);
+ const String remapped_path = ResourceLoader::path_remap(p_path);
Ref<GDScript> script;
script.instantiate();
- script->set_path(p_path, true);
+ script->set_path_cache(p_path);
if (remapped_path.get_extension().to_lower() == "gdc") {
Vector<uint8_t> buffer = get_binary_tokens(remapped_path);
if (buffer.is_empty()) {
@@ -332,6 +333,7 @@ Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, Error &r_e
}
singleton->shallow_gdscript_cache[p_path] = script;
+
return script;
}
@@ -358,17 +360,20 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro
return script;
}
}
+ script->set_path(p_path, true);
+
+ const String remapped_path = ResourceLoader::path_remap(p_path);
if (p_update_from_disk) {
- if (p_path.get_extension().to_lower() == "gdc") {
- Vector<uint8_t> buffer = get_binary_tokens(p_path);
+ if (remapped_path.get_extension().to_lower() == "gdc") {
+ Vector<uint8_t> buffer = get_binary_tokens(remapped_path);
if (buffer.is_empty()) {
r_error = ERR_FILE_CANT_READ;
return script;
}
script->set_binary_tokens_source(buffer);
} else {
- r_error = script->load_source_code(p_path);
+ r_error = script->load_source_code(remapped_path);
if (r_error) {
return script;
}
diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h
index c1c0b61395..f3c4acf1c3 100644
--- a/modules/gdscript/gdscript_codegen.h
+++ b/modules/gdscript/gdscript_codegen.h
@@ -142,6 +142,7 @@ public:
virtual void write_construct_array(const Address &p_target, const Vector<Address> &p_arguments) = 0;
virtual void write_construct_typed_array(const Address &p_target, const GDScriptDataType &p_element_type, const Vector<Address> &p_arguments) = 0;
virtual void write_construct_dictionary(const Address &p_target, const Vector<Address> &p_arguments) = 0;
+ virtual void write_construct_typed_dictionary(const Address &p_target, const GDScriptDataType &p_key_type, const GDScriptDataType &p_value_type, const Vector<Address> &p_arguments) = 0;
virtual void write_await(const Address &p_target, const Address &p_operand) = 0;
virtual void write_if(const Address &p_condition) = 0;
virtual void write_else() = 0;
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index d8b44a558f..f4f445e096 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -532,10 +532,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
Vector<GDScriptCodeGenerator::Address> elements;
// Create the result temporary first since it's the last to be killed.
- GDScriptDataType dict_type;
- dict_type.has_type = true;
- dict_type.kind = GDScriptDataType::BUILTIN;
- dict_type.builtin_type = Variant::DICTIONARY;
+ GDScriptDataType dict_type = _gdtype_from_datatype(dn->get_datatype(), codegen.script);
GDScriptCodeGenerator::Address result = codegen.add_temporary(dict_type);
for (int i = 0; i < dn->elements.size(); i++) {
@@ -566,7 +563,11 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
elements.push_back(element);
}
- gen->write_construct_dictionary(result, elements);
+ if (dict_type.has_container_element_types()) {
+ gen->write_construct_typed_dictionary(result, dict_type.get_container_element_type_or_variant(0), dict_type.get_container_element_type_or_variant(1), elements);
+ } else {
+ gen->write_construct_dictionary(result, elements);
+ }
for (int i = 0; i < elements.size(); i++) {
if (elements[i].mode == GDScriptCodeGenerator::Address::TEMPORARY) {
@@ -2325,8 +2326,11 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, field_type);
- if (field_type.has_container_element_type(0)) {
+ if (field_type.builtin_type == Variant::ARRAY && field_type.has_container_element_type(0)) {
codegen.generator->write_construct_typed_array(dst_address, field_type.get_container_element_type(0), Vector<GDScriptCodeGenerator::Address>());
+ } else if (field_type.builtin_type == Variant::DICTIONARY && field_type.has_container_element_types()) {
+ codegen.generator->write_construct_typed_dictionary(dst_address, field_type.get_container_element_type_or_variant(0),
+ field_type.get_container_element_type_or_variant(1), Vector<GDScriptCodeGenerator::Address>());
} else if (field_type.kind == GDScriptDataType::BUILTIN) {
codegen.generator->write_construct(dst_address, field_type.builtin_type, Vector<GDScriptCodeGenerator::Address>());
}
@@ -2515,11 +2519,17 @@ GDScriptFunction *GDScriptCompiler::_make_static_initializer(Error &r_error, GDS
if (field_type.has_type) {
codegen.generator->write_newline(field->start_line);
- if (field_type.has_container_element_type(0)) {
+ if (field_type.builtin_type == Variant::ARRAY && field_type.has_container_element_type(0)) {
GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type);
codegen.generator->write_construct_typed_array(temp, field_type.get_container_element_type(0), Vector<GDScriptCodeGenerator::Address>());
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.builtin_type == Variant::DICTIONARY && field_type.has_container_element_types()) {
+ GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type);
+ codegen.generator->write_construct_typed_dictionary(temp, field_type.get_container_element_type_or_variant(0),
+ field_type.get_container_element_type_or_variant(1), Vector<GDScriptCodeGenerator::Address>());
+ 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>());
@@ -2619,9 +2629,10 @@ Error GDScriptCompiler::_parse_setter_getter(GDScript *p_script, const GDScriptP
return err;
}
-// Prepares given script, and inner class scripts, for compilation. It populates class members and initializes method
-// RPC info for its base classes first, then for itself, then for inner classes.
-// Warning: this function cannot initiate compilation of other classes, or it will result in cyclic dependency issues.
+// Prepares given script, and inner class scripts, for compilation. It populates class members and
+// initializes method RPC info for its base classes first, then for itself, then for inner classes.
+// WARNING: This function cannot initiate compilation of other classes, or it will result in
+// cyclic dependency issues.
Error GDScriptCompiler::_prepare_compilation(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) {
if (parsed_classes.has(p_script)) {
return OK;
diff --git a/modules/gdscript/gdscript_disassembler.cpp b/modules/gdscript/gdscript_disassembler.cpp
index 0331045078..d94a6dfda2 100644
--- a/modules/gdscript/gdscript_disassembler.cpp
+++ b/modules/gdscript/gdscript_disassembler.cpp
@@ -176,6 +176,47 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
incr += 6;
} break;
+ case OPCODE_TYPE_TEST_DICTIONARY: {
+ text += "type test ";
+ text += DADDR(1);
+ text += " = ";
+ text += DADDR(2);
+ text += " is Dictionary[";
+
+ Ref<Script> key_script_type = get_constant(_code_ptr[ip + 3] & ADDR_MASK);
+ Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + 5];
+ StringName key_native_type = get_global_name(_code_ptr[ip + 6]);
+
+ if (key_script_type.is_valid() && key_script_type->is_valid()) {
+ text += "script(";
+ text += GDScript::debug_get_script_name(key_script_type);
+ text += ")";
+ } else if (key_native_type != StringName()) {
+ text += key_native_type;
+ } else {
+ text += Variant::get_type_name(key_builtin_type);
+ }
+
+ text += ", ";
+
+ Ref<Script> value_script_type = get_constant(_code_ptr[ip + 4] & ADDR_MASK);
+ Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + 7];
+ StringName value_native_type = get_global_name(_code_ptr[ip + 8]);
+
+ if (value_script_type.is_valid() && value_script_type->is_valid()) {
+ text += "script(";
+ text += GDScript::debug_get_script_name(value_script_type);
+ text += ")";
+ } else if (value_native_type != StringName()) {
+ text += value_native_type;
+ } else {
+ text += Variant::get_type_name(value_builtin_type);
+ }
+
+ text += "]";
+
+ incr += 9;
+ } break;
case OPCODE_TYPE_TEST_NATIVE: {
text += "type test ";
text += DADDR(1);
@@ -399,6 +440,14 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
incr += 6;
} break;
+ case OPCODE_ASSIGN_TYPED_DICTIONARY: {
+ text += "assign typed dictionary ";
+ text += DADDR(1);
+ text += " = ";
+ text += DADDR(2);
+
+ incr += 9;
+ } break;
case OPCODE_ASSIGN_TYPED_NATIVE: {
text += "assign typed native (";
text += DADDR(3);
@@ -564,6 +613,58 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
incr += 3 + argc * 2;
} break;
+ case OPCODE_CONSTRUCT_TYPED_DICTIONARY: {
+ int instr_var_args = _code_ptr[++ip];
+ int argc = _code_ptr[ip + 1 + instr_var_args];
+
+ Ref<Script> key_script_type = get_constant(_code_ptr[ip + argc * 2 + 2] & ADDR_MASK);
+ Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + argc * 2 + 5];
+ StringName key_native_type = get_global_name(_code_ptr[ip + argc * 2 + 6]);
+
+ String key_type_name;
+ if (key_script_type.is_valid() && key_script_type->is_valid()) {
+ key_type_name = "script(" + GDScript::debug_get_script_name(key_script_type) + ")";
+ } else if (key_native_type != StringName()) {
+ key_type_name = key_native_type;
+ } else {
+ key_type_name = Variant::get_type_name(key_builtin_type);
+ }
+
+ Ref<Script> value_script_type = get_constant(_code_ptr[ip + argc * 2 + 3] & ADDR_MASK);
+ Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + argc * 2 + 7];
+ StringName value_native_type = get_global_name(_code_ptr[ip + argc * 2 + 8]);
+
+ String value_type_name;
+ if (value_script_type.is_valid() && value_script_type->is_valid()) {
+ value_type_name = "script(" + GDScript::debug_get_script_name(value_script_type) + ")";
+ } else if (value_native_type != StringName()) {
+ value_type_name = value_native_type;
+ } else {
+ value_type_name = Variant::get_type_name(value_builtin_type);
+ }
+
+ text += "make_typed_dict (";
+ text += key_type_name;
+ text += ", ";
+ text += value_type_name;
+ text += ") ";
+
+ text += DADDR(1 + argc * 2);
+ text += " = {";
+
+ for (int i = 0; i < argc; i++) {
+ if (i > 0) {
+ text += ", ";
+ }
+ text += DADDR(1 + i * 2 + 0);
+ text += ": ";
+ text += DADDR(1 + i * 2 + 1);
+ }
+
+ text += "}";
+
+ incr += 9 + argc * 2;
+ } break;
case OPCODE_CALL:
case OPCODE_CALL_RETURN:
case OPCODE_CALL_ASYNC: {
@@ -689,8 +790,9 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
text += method->get_name();
text += "(";
for (int i = 0; i < argc; i++) {
- if (i > 0)
+ if (i > 0) {
text += ", ";
+ }
text += DADDR(1 + i);
}
text += ")";
@@ -732,8 +834,9 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
text += method->get_name();
text += "(";
for (int i = 0; i < argc; i++) {
- if (i > 0)
+ if (i > 0) {
text += ", ";
+ }
text += DADDR(1 + i);
}
text += ")";
@@ -978,6 +1081,12 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
incr += 5;
} break;
+ case OPCODE_RETURN_TYPED_DICTIONARY: {
+ text += "return typed dictionary ";
+ text += DADDR(1);
+
+ incr += 8;
+ } break;
case OPCODE_RETURN_TYPED_NATIVE: {
text += "return typed native (";
text += DADDR(2);
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index db7bef2f07..d58cd2c3f7 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -43,6 +43,7 @@
#include "core/config/engine.h"
#include "core/core_constants.h"
#include "core/io/file_access.h"
+#include "core/math/expression.h"
#ifdef TOOLS_ENABLED
#include "core/config/project_settings.h"
@@ -97,8 +98,8 @@ Ref<Script> GDScriptLanguage::make_template(const String &p_template, const Stri
}
processed_template = processed_template.replace("_BASE_", p_base_class_name)
- .replace("_CLASS_SNAKE_CASE_", p_class_name.to_snake_case().validate_ascii_identifier())
- .replace("_CLASS_", p_class_name.to_pascal_case().validate_ascii_identifier())
+ .replace("_CLASS_SNAKE_CASE_", p_class_name.to_snake_case().validate_unicode_identifier())
+ .replace("_CLASS_", p_class_name.to_pascal_case().validate_unicode_identifier())
.replace("_TS_", _get_indentation());
scr->set_source_code(processed_template);
return scr;
@@ -427,7 +428,30 @@ void GDScriptLanguage::debug_get_globals(List<String> *p_globals, List<Variant>
}
String GDScriptLanguage::debug_parse_stack_level_expression(int p_level, const String &p_expression, int p_max_subitems, int p_max_depth) {
- return "";
+ List<String> names;
+ List<Variant> values;
+ debug_get_stack_level_locals(p_level, &names, &values, p_max_subitems, p_max_depth);
+
+ Vector<String> name_vector;
+ for (const String &name : names) {
+ name_vector.push_back(name);
+ }
+
+ Array value_array;
+ for (const Variant &value : values) {
+ value_array.push_back(value);
+ }
+
+ Expression expression;
+ if (expression.parse(p_expression, name_vector) == OK) {
+ ScriptInstance *instance = debug_get_stack_level_instance(p_level);
+ if (instance) {
+ Variant return_val = expression.execute(value_array, instance->get_owner());
+ return return_val.get_construct_string();
+ }
+ }
+
+ return String();
}
void GDScriptLanguage::get_recognized_extensions(List<String> *p_extensions) const {
@@ -697,6 +721,10 @@ static String _get_visual_datatype(const PropertyInfo &p_info, bool p_is_arg, co
return _trim_parent_class(class_name, p_base_class);
} else if (p_info.type == Variant::ARRAY && p_info.hint == PROPERTY_HINT_ARRAY_TYPE && !p_info.hint_string.is_empty()) {
return "Array[" + _trim_parent_class(p_info.hint_string, p_base_class) + "]";
+ } else if (p_info.type == Variant::DICTIONARY && p_info.hint == PROPERTY_HINT_DICTIONARY_TYPE && !p_info.hint_string.is_empty()) {
+ const String key = p_info.hint_string.get_slice(";", 0);
+ const String value = p_info.hint_string.get_slice(";", 1);
+ return "Dictionary[" + _trim_parent_class(key, p_base_class) + ", " + _trim_parent_class(value, p_base_class) + "]";
} else if (p_info.type == Variant::NIL) {
if (p_is_arg || (p_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT)) {
return "Variant";
@@ -860,7 +888,8 @@ static void _get_directory_contents(EditorFileSystemDirectory *p_dir, HashMap<St
}
}
-static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_annotation, int p_argument, const String p_quote_style, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result) {
+static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_annotation, int p_argument, const String p_quote_style, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result, String &r_arghint) {
+ r_arghint = _make_arguments_hint(p_annotation->info->info, p_argument, true);
if (p_annotation->name == SNAME("@export_range")) {
if (p_argument == 3 || p_argument == 4 || p_argument == 5) {
// Slider hint.
@@ -2971,11 +3000,6 @@ static bool _get_subscript_type(GDScriptParser::CompletionContext &p_context, co
} break;
case GDScriptParser::Node::IDENTIFIER: {
- if (p_subscript->base->datatype.type_source == GDScriptParser::DataType::ANNOTATED_EXPLICIT) {
- // Annotated type takes precedence.
- return false;
- }
-
const GDScriptParser::IdentifierNode *identifier_node = static_cast<GDScriptParser::IdentifierNode *>(p_subscript->base);
switch (identifier_node->source) {
@@ -3013,6 +3037,14 @@ static bool _get_subscript_type(GDScriptParser::CompletionContext &p_context, co
if (get_node != nullptr) {
const Object *node = p_context.base->call("get_node_or_null", NodePath(get_node->full_path));
if (node != nullptr) {
+ GDScriptParser::DataType assigned_type = _type_from_variant(node, p_context).type;
+ GDScriptParser::DataType base_type = p_subscript->base->datatype;
+
+ if (p_subscript->base->type == GDScriptParser::Node::IDENTIFIER && base_type.type_source == GDScriptParser::DataType::ANNOTATED_EXPLICIT && (assigned_type.kind != base_type.kind || assigned_type.script_path != base_type.script_path || assigned_type.native_type != base_type.native_type)) {
+ // Annotated type takes precedence.
+ return false;
+ }
+
if (r_base != nullptr) {
*r_base = node;
}
@@ -3156,7 +3188,9 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
HashMap<String, ScriptLanguage::CodeCompletionOption> options;
GDScriptParser::CompletionContext completion_context = parser.get_completion_context();
- completion_context.base = p_owner;
+ if (completion_context.current_class != nullptr && completion_context.current_class->outer == nullptr) {
+ completion_context.base = p_owner;
+ }
bool is_function = false;
switch (completion_context.type) {
@@ -3179,7 +3213,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
break;
}
const GDScriptParser::AnnotationNode *annotation = static_cast<const GDScriptParser::AnnotationNode *>(completion_context.node);
- _find_annotation_arguments(annotation, completion_context.current_argument, quote_style, options);
+ _find_annotation_arguments(annotation, completion_context.current_argument, quote_style, options, r_call_hint);
r_forced = true;
} break;
case GDScriptParser::COMPLETION_BUILT_IN_TYPE_CONSTANT_OR_STATIC_METHOD: {
@@ -3438,7 +3472,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
}
String arg = arg_itr->name;
if (arg.contains(":")) {
- arg = arg.substr(0, arg.find(":"));
+ arg = arg.substr(0, arg.find_char(':'));
}
method_hint += arg;
if (use_type_hint) {
@@ -3482,10 +3516,10 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
opt = opt.substr(1);
}
- // The path needs quotes if at least one of its components (excluding `/` separations)
+ // The path needs quotes if at least one of its components (excluding `%` prefix and `/` separations)
// is not a valid identifier.
bool path_needs_quote = false;
- for (const String &part : opt.split("/")) {
+ for (const String &part : opt.trim_prefix("%").split("/")) {
if (!part.is_valid_ascii_identifier()) {
path_needs_quote = true;
break;
@@ -3526,13 +3560,13 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
return OK;
}
-#else
+#else // !TOOLS_ENABLED
Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path, Object *p_owner, List<ScriptLanguage::CodeCompletionOption> *r_options, bool &r_forced, String &r_call_hint) {
return OK;
}
-#endif
+#endif // TOOLS_ENABLED
//////// END COMPLETION //////////
@@ -3774,7 +3808,19 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
}
} break;
case GDScriptParser::DataType::ENUM: {
- if (base_type.enum_values.has(p_symbol)) {
+ if (base_type.class_type && base_type.class_type->has_member(base_type.enum_type)) {
+ GDScriptParser::EnumNode *base_enum = base_type.class_type->get_member(base_type.enum_type).m_enum;
+ for (const GDScriptParser::EnumNode::Value &value : base_enum->values) {
+ if (value.identifier && value.identifier->name == p_symbol) {
+ r_result.type = ScriptLanguage::LOOKUP_RESULT_SCRIPT_LOCATION;
+ r_result.class_path = base_type.script_path;
+ r_result.location = value.line;
+ Error err = OK;
+ r_result.script = GDScriptCache::get_shallow_script(r_result.class_path, err);
+ return err;
+ }
+ }
+ } else if (base_type.enum_values.has(p_symbol)) {
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT;
r_result.class_name = String(base_type.native_type).get_slicec('.', 0);
r_result.class_member = p_symbol;
@@ -4105,4 +4151,4 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
return ERR_CANT_RESOLVE;
}
-#endif
+#endif // TOOLS_ENABLED
diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h
index ac4bab6d84..6433072b55 100644
--- a/modules/gdscript/gdscript_function.h
+++ b/modules/gdscript/gdscript_function.h
@@ -93,6 +93,41 @@ public:
} else {
valid = false;
}
+ } else if (valid && builtin_type == Variant::DICTIONARY && has_container_element_types()) {
+ Dictionary dictionary = p_variant;
+ if (dictionary.is_typed()) {
+ if (dictionary.is_typed_key()) {
+ GDScriptDataType key = get_container_element_type_or_variant(0);
+ Variant::Type key_builtin_type = (Variant::Type)dictionary.get_typed_key_builtin();
+ StringName key_native_type = dictionary.get_typed_key_class_name();
+ Ref<Script> key_script_type_ref = dictionary.get_typed_key_script();
+
+ if (key_script_type_ref.is_valid()) {
+ valid = (key.kind == SCRIPT || key.kind == GDSCRIPT) && key.script_type == key_script_type_ref.ptr();
+ } else if (key_native_type != StringName()) {
+ valid = key.kind == NATIVE && key.native_type == key_native_type;
+ } else {
+ valid = key.kind == BUILTIN && key.builtin_type == key_builtin_type;
+ }
+ }
+
+ if (valid && dictionary.is_typed_value()) {
+ GDScriptDataType value = get_container_element_type_or_variant(1);
+ Variant::Type value_builtin_type = (Variant::Type)dictionary.get_typed_value_builtin();
+ StringName value_native_type = dictionary.get_typed_value_class_name();
+ Ref<Script> value_script_type_ref = dictionary.get_typed_value_script();
+
+ if (value_script_type_ref.is_valid()) {
+ valid = (value.kind == SCRIPT || value.kind == GDSCRIPT) && value.script_type == value_script_type_ref.ptr();
+ } else if (value_native_type != StringName()) {
+ valid = value.kind == NATIVE && value.native_type == value_native_type;
+ } else {
+ valid = value.kind == BUILTIN && value.builtin_type == value_builtin_type;
+ }
+ }
+ } else {
+ valid = false;
+ }
} else if (!valid && p_allow_implicit_conversion) {
valid = Variant::can_convert_strict(var_type, builtin_type);
}
@@ -156,6 +191,10 @@ public:
}
return true;
case Variant::DICTIONARY:
+ if (has_container_element_types()) {
+ return get_container_element_type_or_variant(0).can_contain_object() || get_container_element_type_or_variant(1).can_contain_object();
+ }
+ return true;
case Variant::NIL:
case Variant::OBJECT:
return true;
@@ -220,6 +259,7 @@ public:
OPCODE_OPERATOR_VALIDATED,
OPCODE_TYPE_TEST_BUILTIN,
OPCODE_TYPE_TEST_ARRAY,
+ OPCODE_TYPE_TEST_DICTIONARY,
OPCODE_TYPE_TEST_NATIVE,
OPCODE_TYPE_TEST_SCRIPT,
OPCODE_SET_KEYED,
@@ -242,6 +282,7 @@ public:
OPCODE_ASSIGN_FALSE,
OPCODE_ASSIGN_TYPED_BUILTIN,
OPCODE_ASSIGN_TYPED_ARRAY,
+ OPCODE_ASSIGN_TYPED_DICTIONARY,
OPCODE_ASSIGN_TYPED_NATIVE,
OPCODE_ASSIGN_TYPED_SCRIPT,
OPCODE_CAST_TO_BUILTIN,
@@ -252,6 +293,7 @@ public:
OPCODE_CONSTRUCT_ARRAY,
OPCODE_CONSTRUCT_TYPED_ARRAY,
OPCODE_CONSTRUCT_DICTIONARY,
+ OPCODE_CONSTRUCT_TYPED_DICTIONARY,
OPCODE_CALL,
OPCODE_CALL_RETURN,
OPCODE_CALL_ASYNC,
@@ -280,6 +322,7 @@ public:
OPCODE_RETURN,
OPCODE_RETURN_TYPED_BUILTIN,
OPCODE_RETURN_TYPED_ARRAY,
+ OPCODE_RETURN_TYPED_DICTIONARY,
OPCODE_RETURN_TYPED_NATIVE,
OPCODE_RETURN_TYPED_SCRIPT,
OPCODE_ITERATE_BEGIN,
diff --git a/modules/gdscript/gdscript_lambda_callable.cpp b/modules/gdscript/gdscript_lambda_callable.cpp
index 2de5811bca..d6fd5d043b 100644
--- a/modules/gdscript/gdscript_lambda_callable.cpp
+++ b/modules/gdscript/gdscript_lambda_callable.cpp
@@ -97,25 +97,25 @@ void GDScriptLambdaCallable::call(const Variant **p_arguments, int p_argcount, V
}
if (captures_amount > 0) {
- Vector<const Variant *> args;
- args.resize(p_argcount + captures_amount);
+ const int total_argcount = p_argcount + captures_amount;
+ const Variant **args = (const Variant **)alloca(sizeof(Variant *) * total_argcount);
for (int i = 0; i < captures_amount; i++) {
- args.write[i] = &captures[i];
+ args[i] = &captures[i];
if (captures[i].get_type() == Variant::OBJECT) {
bool was_freed = false;
captures[i].get_validated_object_with_check(was_freed);
if (was_freed) {
ERR_PRINT(vformat(R"(Lambda capture at index %d was freed. Passed "null" instead.)", i));
static Variant nil;
- args.write[i] = &nil;
+ args[i] = &nil;
}
}
}
for (int i = 0; i < p_argcount; i++) {
- args.write[i + captures_amount] = p_arguments[i];
+ args[i + captures_amount] = p_arguments[i];
}
- r_return_value = function->call(nullptr, args.ptrw(), args.size(), r_call_error);
+ r_return_value = function->call(nullptr, args, total_argcount, r_call_error);
switch (r_call_error.error) {
case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT:
r_call_error.argument -= captures_amount;
@@ -229,25 +229,25 @@ void GDScriptLambdaSelfCallable::call(const Variant **p_arguments, int p_argcoun
}
if (captures_amount > 0) {
- Vector<const Variant *> args;
- args.resize(p_argcount + captures_amount);
+ const int total_argcount = p_argcount + captures_amount;
+ const Variant **args = (const Variant **)alloca(sizeof(Variant *) * total_argcount);
for (int i = 0; i < captures_amount; i++) {
- args.write[i] = &captures[i];
+ args[i] = &captures[i];
if (captures[i].get_type() == Variant::OBJECT) {
bool was_freed = false;
captures[i].get_validated_object_with_check(was_freed);
if (was_freed) {
ERR_PRINT(vformat(R"(Lambda capture at index %d was freed. Passed "null" instead.)", i));
static Variant nil;
- args.write[i] = &nil;
+ args[i] = &nil;
}
}
}
for (int i = 0; i < p_argcount; i++) {
- args.write[i + captures_amount] = p_arguments[i];
+ args[i + captures_amount] = p_arguments[i];
}
- r_return_value = function->call(static_cast<GDScriptInstance *>(object->get_script_instance()), args.ptrw(), args.size(), r_call_error);
+ r_return_value = function->call(static_cast<GDScriptInstance *>(object->get_script_instance()), args, total_argcount, r_call_error);
switch (r_call_error.error) {
case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT:
r_call_error.argument -= captures_amount;
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index 92f9c5fa11..12e71004db 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -122,6 +122,7 @@ GDScriptParser::GDScriptParser() {
register_annotation(MethodInfo("@export_flags_avoidance"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_AVOIDANCE, Variant::INT>);
register_annotation(MethodInfo("@export_storage"), AnnotationInfo::VARIABLE, &GDScriptParser::export_storage_annotation);
register_annotation(MethodInfo("@export_custom", PropertyInfo(Variant::INT, "hint", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_CLASS_IS_ENUM, "PropertyHint"), PropertyInfo(Variant::STRING, "hint_string"), PropertyInfo(Variant::INT, "usage", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_CLASS_IS_BITFIELD, "PropertyUsageFlags")), AnnotationInfo::VARIABLE, &GDScriptParser::export_custom_annotation, varray(PROPERTY_USAGE_DEFAULT));
+ register_annotation(MethodInfo("@export_tool_button", PropertyInfo(Variant::STRING, "text"), PropertyInfo(Variant::STRING, "icon")), AnnotationInfo::VARIABLE, &GDScriptParser::export_tool_button_annotation, varray(""));
// Export grouping annotations.
register_annotation(MethodInfo("@export_category", PropertyInfo(Variant::STRING, "name")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_CATEGORY>);
register_annotation(MethodInfo("@export_group", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::STRING, "prefix")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_GROUP>, varray(""));
@@ -243,7 +244,7 @@ void GDScriptParser::apply_pending_warnings() {
pending_warnings.clear();
}
-#endif
+#endif // DEBUG_ENABLED
void GDScriptParser::override_completion_context(const Node *p_for_node, CompletionType p_type, Node *p_node, int p_argument) {
if (!for_completion) {
@@ -408,6 +409,10 @@ Error GDScriptParser::parse(const String &p_source_code, const String &p_script_
parse_program();
pop_multiline();
+#ifdef TOOLS_ENABLED
+ comment_data = tokenizer->get_comments();
+#endif
+
memdelete(text_tokenizer);
tokenizer = nullptr;
@@ -1623,15 +1628,17 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali
valid = false;
}
- annotation->info = &valid_annotations[annotation->name];
+ if (valid) {
+ annotation->info = &valid_annotations[annotation->name];
- if (!annotation->applies_to(p_valid_targets)) {
- if (annotation->applies_to(AnnotationInfo::SCRIPT)) {
- push_error(vformat(R"(Annotation "%s" must be at the top of the script, before "extends" and "class_name".)", annotation->name));
- } else {
- push_error(vformat(R"(Annotation "%s" is not allowed in this level.)", annotation->name));
+ if (!annotation->applies_to(p_valid_targets)) {
+ if (annotation->applies_to(AnnotationInfo::SCRIPT)) {
+ push_error(vformat(R"(Annotation "%s" must be at the top of the script, before "extends" and "class_name".)", annotation->name));
+ } else {
+ push_error(vformat(R"(Annotation "%s" is not allowed in this level.)", annotation->name));
+ }
+ valid = false;
}
- valid = false;
}
if (check(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) {
@@ -1639,23 +1646,29 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali
advance();
// Arguments.
push_completion_call(annotation);
- make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, 0);
int argument_index = 0;
do {
+ make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, argument_index);
+ set_last_completion_call_arg(argument_index);
if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) {
// Allow for trailing comma.
break;
}
- make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, argument_index);
- set_last_completion_call_arg(argument_index++);
ExpressionNode *argument = parse_expression(false);
+
if (argument == nullptr) {
push_error("Expected expression as the annotation argument.");
valid = false;
- continue;
+ } else {
+ annotation->arguments.push_back(argument);
+
+ if (argument->type == Node::LITERAL) {
+ override_completion_context(argument, COMPLETION_ANNOTATION_ARGUMENTS, annotation, argument_index);
+ }
}
- annotation->arguments.push_back(argument);
+
+ argument_index++;
} while (match(GDScriptTokenizer::Token::COMMA) && !is_at_end());
pop_multiline();
@@ -3568,7 +3581,7 @@ GDScriptParser::TypeNode *GDScriptParser::parse_type(bool p_allow_void) {
type->type_chain.push_back(type_element);
if (match(GDScriptTokenizer::Token::BRACKET_OPEN)) {
- // Typed collection (like Array[int]).
+ // Typed collection (like Array[int], Dictionary[String, int]).
bool first_pass = true;
do {
TypeNode *container_type = parse_type(false); // Don't allow void for element type.
@@ -4385,6 +4398,14 @@ bool GDScriptParser::export_annotations(AnnotationNode *p_annotation, Node *p_ta
export_type.type_source = variable->datatype.type_source;
}
+ bool is_dict = false;
+ if (export_type.builtin_type == Variant::DICTIONARY && export_type.has_container_element_types()) {
+ is_dict = true;
+ DataType inner_type = export_type.get_container_element_type_or_variant(1);
+ export_type = export_type.get_container_element_type_or_variant(0);
+ export_type.set_container_element_type(0, inner_type); // Store earlier extracted value within key to separately parse after.
+ }
+
bool use_default_variable_type_check = true;
if (p_annotation->name == SNAME("@export_range")) {
@@ -4412,8 +4433,13 @@ bool GDScriptParser::export_annotations(AnnotationNode *p_annotation, Node *p_ta
}
if (export_type.is_variant() || export_type.has_no_type()) {
- push_error(R"(Cannot use simple "@export" annotation because the type of the initialized value can't be inferred.)", p_annotation);
- return false;
+ if (is_dict) {
+ // Dictionary allowed to have a variant key/value.
+ export_type.kind = GDScriptParser::DataType::BUILTIN;
+ } else {
+ push_error(R"(Cannot use simple "@export" annotation because the type of the initialized value can't be inferred.)", p_annotation);
+ return false;
+ }
}
switch (export_type.kind) {
@@ -4473,6 +4499,90 @@ bool GDScriptParser::export_annotations(AnnotationNode *p_annotation, Node *p_ta
push_error(vformat(R"(Node export is only supported in Node-derived classes, but the current class inherits "%s".)", p_class->base_type.to_string()), p_annotation);
return false;
}
+
+ if (is_dict) {
+ String key_prefix = itos(variable->export_info.type);
+ if (variable->export_info.hint) {
+ key_prefix += "/" + itos(variable->export_info.hint);
+ }
+ key_prefix += ":" + variable->export_info.hint_string;
+
+ // Now parse value.
+ export_type = export_type.get_container_element_type(0);
+
+ if (export_type.is_variant() || export_type.has_no_type()) {
+ export_type.kind = GDScriptParser::DataType::BUILTIN;
+ }
+ switch (export_type.kind) {
+ case GDScriptParser::DataType::BUILTIN:
+ variable->export_info.type = export_type.builtin_type;
+ variable->export_info.hint = PROPERTY_HINT_NONE;
+ variable->export_info.hint_string = String();
+ break;
+ case GDScriptParser::DataType::NATIVE:
+ case GDScriptParser::DataType::SCRIPT:
+ case GDScriptParser::DataType::CLASS: {
+ const StringName class_name = _find_narrowest_native_or_global_class(export_type);
+ if (ClassDB::is_parent_class(export_type.native_type, SNAME("Resource"))) {
+ variable->export_info.type = Variant::OBJECT;
+ variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE;
+ variable->export_info.hint_string = class_name;
+ } else if (ClassDB::is_parent_class(export_type.native_type, SNAME("Node"))) {
+ variable->export_info.type = Variant::OBJECT;
+ variable->export_info.hint = PROPERTY_HINT_NODE_TYPE;
+ variable->export_info.hint_string = class_name;
+ } else {
+ push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", p_annotation);
+ return false;
+ }
+ } break;
+ case GDScriptParser::DataType::ENUM: {
+ if (export_type.is_meta_type) {
+ variable->export_info.type = Variant::DICTIONARY;
+ } else {
+ variable->export_info.type = Variant::INT;
+ variable->export_info.hint = PROPERTY_HINT_ENUM;
+
+ String enum_hint_string;
+ bool first = true;
+ for (const KeyValue<StringName, int64_t> &E : export_type.enum_values) {
+ if (!first) {
+ enum_hint_string += ",";
+ } else {
+ first = false;
+ }
+ enum_hint_string += E.key.operator String().capitalize().xml_escape();
+ enum_hint_string += ":";
+ enum_hint_string += String::num_int64(E.value).xml_escape();
+ }
+
+ variable->export_info.hint_string = enum_hint_string;
+ variable->export_info.usage |= PROPERTY_USAGE_CLASS_IS_ENUM;
+ variable->export_info.class_name = String(export_type.native_type).replace("::", ".");
+ }
+ } break;
+ default:
+ push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", p_annotation);
+ return false;
+ }
+
+ if (variable->export_info.hint == PROPERTY_HINT_NODE_TYPE && !ClassDB::is_parent_class(p_class->base_type.native_type, SNAME("Node"))) {
+ push_error(vformat(R"(Node export is only supported in Node-derived classes, but the current class inherits "%s".)", p_class->base_type.to_string()), p_annotation);
+ return false;
+ }
+
+ String value_prefix = itos(variable->export_info.type);
+ if (variable->export_info.hint) {
+ value_prefix += "/" + itos(variable->export_info.hint);
+ }
+ value_prefix += ":" + variable->export_info.hint_string;
+
+ variable->export_info.type = Variant::DICTIONARY;
+ variable->export_info.hint = PROPERTY_HINT_TYPE_STRING;
+ variable->export_info.hint_string = key_prefix + ";" + value_prefix;
+ variable->export_info.usage = PROPERTY_USAGE_DEFAULT;
+ variable->export_info.class_name = StringName();
+ }
} else if (p_annotation->name == SNAME("@export_enum")) {
use_default_variable_type_check = false;
@@ -4521,10 +4631,10 @@ bool GDScriptParser::export_annotations(AnnotationNode *p_annotation, Node *p_ta
// For `@export_storage` and `@export_custom`, there is no need to check the variable type, argument values,
// or handle array exports in a special way, so they are implemented as separate methods.
-bool GDScriptParser::export_storage_annotation(AnnotationNode *p_annotation, Node *p_node, ClassNode *p_class) {
- ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name));
+bool GDScriptParser::export_storage_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
+ ERR_FAIL_COND_V_MSG(p_target->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name));
- VariableNode *variable = static_cast<VariableNode *>(p_node);
+ VariableNode *variable = static_cast<VariableNode *>(p_target);
if (variable->is_static) {
push_error(vformat(R"(Annotation "%s" cannot be applied to a static variable.)", p_annotation->name), p_annotation);
return false;
@@ -4543,11 +4653,11 @@ bool GDScriptParser::export_storage_annotation(AnnotationNode *p_annotation, Nod
return true;
}
-bool GDScriptParser::export_custom_annotation(AnnotationNode *p_annotation, Node *p_node, ClassNode *p_class) {
- ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name));
+bool GDScriptParser::export_custom_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
+ ERR_FAIL_COND_V_MSG(p_target->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name));
ERR_FAIL_COND_V_MSG(p_annotation->resolved_arguments.size() < 2, false, R"(Annotation "@export_custom" requires 2 arguments.)");
- VariableNode *variable = static_cast<VariableNode *>(p_node);
+ VariableNode *variable = static_cast<VariableNode *>(p_target);
if (variable->is_static) {
push_error(vformat(R"(Annotation "%s" cannot be applied to a static variable.)", p_annotation->name), p_annotation);
return false;
@@ -4571,12 +4681,56 @@ bool GDScriptParser::export_custom_annotation(AnnotationNode *p_annotation, Node
return true;
}
-template <PropertyUsageFlags t_usage>
-bool GDScriptParser::export_group_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
- if (p_annotation->resolved_arguments.is_empty()) {
+bool GDScriptParser::export_tool_button_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
+#ifdef TOOLS_ENABLED
+ ERR_FAIL_COND_V_MSG(p_target->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name));
+ ERR_FAIL_COND_V(p_annotation->resolved_arguments.is_empty(), false);
+
+ if (!is_tool()) {
+ push_error(R"(Tool buttons can only be used in tool scripts (add "@tool" to the top of the script).)", p_annotation);
+ return false;
+ }
+
+ VariableNode *variable = static_cast<VariableNode *>(p_target);
+
+ 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;
}
+ const DataType variable_type = variable->get_datatype();
+ if (!variable_type.is_variant() && variable_type.is_hard_type()) {
+ if (variable_type.kind != DataType::BUILTIN || variable_type.builtin_type != Variant::CALLABLE) {
+ push_error(vformat(R"("@export_tool_button" annotation requires a variable of type "Callable", but type "%s" was given instead.)", variable_type.to_string()), p_annotation);
+ return false;
+ }
+ }
+
+ variable->exported = true;
+
+ // Build the hint string (format: `<text>[,<icon>]`).
+ String hint_string = p_annotation->resolved_arguments[0].operator String(); // Button text.
+ if (p_annotation->resolved_arguments.size() > 1) {
+ hint_string += "," + p_annotation->resolved_arguments[1].operator String(); // Button icon.
+ }
+
+ variable->export_info.type = Variant::CALLABLE;
+ variable->export_info.hint = PROPERTY_HINT_TOOL_BUTTON;
+ variable->export_info.hint_string = hint_string;
+ variable->export_info.usage = PROPERTY_USAGE_EDITOR;
+#endif // TOOLS_ENABLED
+
+ return true; // Only available in editor.
+}
+
+template <PropertyUsageFlags t_usage>
+bool GDScriptParser::export_group_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
+ ERR_FAIL_COND_V(p_annotation->resolved_arguments.is_empty(), false);
+
p_annotation->export_info.name = p_annotation->resolved_arguments[0];
switch (t_usage) {
@@ -4794,7 +4948,10 @@ String GDScriptParser::DataType::to_string() const {
return "null";
}
if (builtin_type == Variant::ARRAY && has_container_element_type(0)) {
- return vformat("Array[%s]", container_element_types[0].to_string());
+ return vformat("Array[%s]", get_container_element_type(0).to_string());
+ }
+ if (builtin_type == Variant::DICTIONARY && has_container_element_types()) {
+ return vformat("Dictionary[%s, %s]", get_container_element_type_or_variant(0).to_string(), get_container_element_type_or_variant(1).to_string());
}
return Variant::get_type_name(builtin_type);
case NATIVE:
@@ -4883,6 +5040,72 @@ PropertyInfo GDScriptParser::DataType::to_property_info(const String &p_name) co
case UNRESOLVED:
break;
}
+ } else if (builtin_type == Variant::DICTIONARY && has_container_element_types()) {
+ const DataType key_type = get_container_element_type_or_variant(0);
+ const DataType value_type = get_container_element_type_or_variant(1);
+ if ((key_type.kind == VARIANT && value_type.kind == VARIANT) || key_type.kind == RESOLVING ||
+ key_type.kind == UNRESOLVED || value_type.kind == RESOLVING || value_type.kind == UNRESOLVED) {
+ break;
+ }
+ String key_hint, value_hint;
+ switch (key_type.kind) {
+ case BUILTIN:
+ key_hint = Variant::get_type_name(key_type.builtin_type);
+ break;
+ case NATIVE:
+ key_hint = key_type.native_type;
+ break;
+ case SCRIPT:
+ if (key_type.script_type.is_valid() && key_type.script_type->get_global_name() != StringName()) {
+ key_hint = key_type.script_type->get_global_name();
+ } else {
+ key_hint = key_type.native_type;
+ }
+ break;
+ case CLASS:
+ if (key_type.class_type != nullptr && key_type.class_type->get_global_name() != StringName()) {
+ key_hint = key_type.class_type->get_global_name();
+ } else {
+ key_hint = key_type.native_type;
+ }
+ break;
+ case ENUM:
+ key_hint = String(key_type.native_type).replace("::", ".");
+ break;
+ default:
+ key_hint = "Variant";
+ break;
+ }
+ switch (value_type.kind) {
+ case BUILTIN:
+ value_hint = Variant::get_type_name(value_type.builtin_type);
+ break;
+ case NATIVE:
+ value_hint = value_type.native_type;
+ break;
+ case SCRIPT:
+ if (value_type.script_type.is_valid() && value_type.script_type->get_global_name() != StringName()) {
+ value_hint = value_type.script_type->get_global_name();
+ } else {
+ value_hint = value_type.native_type;
+ }
+ break;
+ case CLASS:
+ if (value_type.class_type != nullptr && value_type.class_type->get_global_name() != StringName()) {
+ value_hint = value_type.class_type->get_global_name();
+ } else {
+ value_hint = value_type.native_type;
+ }
+ break;
+ case ENUM:
+ value_hint = String(value_type.native_type).replace("::", ".");
+ break;
+ default:
+ value_hint = "Variant";
+ break;
+ }
+ result.hint = PROPERTY_HINT_DICTIONARY_TYPE;
+ result.hint_string = key_hint + ";" + value_hint;
}
break;
case NATIVE:
@@ -4967,6 +5190,50 @@ GDScriptParser::DataType GDScriptParser::DataType::get_typed_container_type() co
return type;
}
+bool GDScriptParser::DataType::can_reference(const GDScriptParser::DataType &p_other) const {
+ if (p_other.is_meta_type) {
+ return false;
+ } else if (builtin_type != p_other.builtin_type) {
+ return false;
+ } else if (builtin_type != Variant::OBJECT) {
+ return true;
+ }
+
+ if (native_type == StringName()) {
+ return true;
+ } else if (p_other.native_type == StringName()) {
+ return false;
+ } else if (native_type != p_other.native_type && !ClassDB::is_parent_class(p_other.native_type, native_type)) {
+ return false;
+ }
+
+ Ref<Script> script = script_type;
+ if (kind == GDScriptParser::DataType::CLASS && script.is_null()) {
+ Error err = OK;
+ Ref<GDScript> scr = GDScriptCache::get_shallow_script(script_path, err);
+ ERR_FAIL_COND_V_MSG(err, false, vformat(R"(Error while getting cache for script "%s".)", script_path));
+ script.reference_ptr(scr->find_class(class_type->fqcn));
+ }
+
+ Ref<Script> script_other = p_other.script_type;
+ if (p_other.kind == GDScriptParser::DataType::CLASS && script_other.is_null()) {
+ Error err = OK;
+ Ref<GDScript> scr = GDScriptCache::get_shallow_script(p_other.script_path, err);
+ ERR_FAIL_COND_V_MSG(err, false, vformat(R"(Error while getting cache for script "%s".)", p_other.script_path));
+ script_other.reference_ptr(scr->find_class(p_other.class_type->fqcn));
+ }
+
+ if (script.is_null()) {
+ return true;
+ } else if (script_other.is_null()) {
+ return false;
+ } else if (script != script_other && !script_other->inherits_script(script)) {
+ return false;
+ }
+
+ return true;
+}
+
void GDScriptParser::complete_extents(Node *p_node) {
while (!nodes_in_progress.is_empty() && nodes_in_progress.back()->get() != p_node) {
ERR_PRINT("Parser bug: Mismatch in extents tracking stack.");
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index 2c7e730772..d40ba217c1 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -165,6 +165,10 @@ public:
container_element_types.write[p_index] = DataType(p_type);
}
+ _FORCE_INLINE_ int get_container_element_type_count() const {
+ return container_element_types.size();
+ }
+
_FORCE_INLINE_ DataType get_container_element_type(int p_index) const {
ERR_FAIL_INDEX_V(p_index, container_element_types.size(), get_variant_type());
return container_element_types[p_index];
@@ -189,6 +193,8 @@ public:
GDScriptParser::DataType get_typed_container_type() const;
+ bool can_reference(const DataType &p_other) const;
+
bool operator==(const DataType &p_other) const {
if (type_source == UNDETECTED || p_other.type_source == UNDETECTED) {
return true; // Can be considered equal for parsing purposes.
@@ -1505,6 +1511,7 @@ private:
bool export_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
bool export_storage_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
bool export_custom_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
+ bool export_tool_button_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
template <PropertyUsageFlags t_usage>
bool export_group_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
bool warning_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
@@ -1594,6 +1601,8 @@ public:
#ifdef TOOLS_ENABLED
static HashMap<String, String> theme_color_names;
+
+ HashMap<int, GDScriptTokenizer::CommentData> comment_data;
#endif // TOOLS_ENABLED
GDScriptParser();
diff --git a/modules/gdscript/gdscript_tokenizer_buffer.cpp b/modules/gdscript/gdscript_tokenizer_buffer.cpp
index e53bc5bc41..2046480f0e 100644
--- a/modules/gdscript/gdscript_tokenizer_buffer.cpp
+++ b/modules/gdscript/gdscript_tokenizer_buffer.cpp
@@ -296,6 +296,7 @@ Vector<uint8_t> GDScriptTokenizerBuffer::parse_code_string(const String &p_code,
encode_uint32(identifier_map.size(), &contents.write[0]);
encode_uint32(constant_map.size(), &contents.write[4]);
encode_uint32(token_lines.size(), &contents.write[8]);
+ encode_uint32(0, &contents.write[12]); // Unused, kept for compatibility. Please remove at next `TOKENIZER_VERSION` increment.
encode_uint32(token_counter, &contents.write[16]);
int buf_pos = 20;
diff --git a/modules/gdscript/gdscript_tokenizer_buffer.h b/modules/gdscript/gdscript_tokenizer_buffer.h
index 55df66e50f..d5d2a4d096 100644
--- a/modules/gdscript/gdscript_tokenizer_buffer.h
+++ b/modules/gdscript/gdscript_tokenizer_buffer.h
@@ -79,7 +79,7 @@ public:
virtual bool is_past_cursor() const override;
virtual void push_expression_indented_block() override; // For lambdas, or blocks inside expressions.
virtual void pop_expression_indented_block() override; // For lambdas, or blocks inside expressions.
- virtual bool is_text() override { return false; };
+ virtual bool is_text() override { return false; }
#ifdef TOOLS_ENABLED
virtual const HashMap<int, CommentData> &get_comments() const override {
diff --git a/modules/gdscript/gdscript_utility_functions.cpp b/modules/gdscript/gdscript_utility_functions.cpp
index 59dd983ed2..8246069696 100644
--- a/modules/gdscript/gdscript_utility_functions.cpp
+++ b/modules/gdscript/gdscript_utility_functions.cpp
@@ -34,7 +34,6 @@
#include "core/io/resource_loader.h"
#include "core/object/class_db.h"
-#include "core/object/method_bind.h"
#include "core/object/object.h"
#include "core/templates/oa_hash_map.h"
#include "core/templates/vector.h"
@@ -42,101 +41,105 @@
#ifdef DEBUG_ENABLED
-#define VALIDATE_ARG_COUNT(m_count) \
- if (p_arg_count < m_count) { \
- r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; \
- r_error.expected = m_count; \
+#define DEBUG_VALIDATE_ARG_COUNT(m_min_count, m_max_count) \
+ if (unlikely(p_arg_count < m_min_count)) { \
*r_ret = Variant(); \
+ r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; \
+ r_error.expected = m_min_count; \
return; \
} \
- if (p_arg_count > m_count) { \
- r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; \
- r_error.expected = m_count; \
+ if (unlikely(p_arg_count > m_max_count)) { \
*r_ret = Variant(); \
+ r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; \
+ r_error.expected = m_max_count; \
return; \
}
-#define VALIDATE_ARG_INT(m_arg) \
- if (p_args[m_arg]->get_type() != Variant::INT) { \
+#define DEBUG_VALIDATE_ARG_TYPE(m_arg, m_type) \
+ if (unlikely(!Variant::can_convert_strict(p_args[m_arg]->get_type(), m_type))) { \
+ *r_ret = Variant(); \
+ r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; \
+ r_error.argument = m_arg; \
+ r_error.expected = m_type; \
+ return; \
+ }
+
+#define DEBUG_VALIDATE_ARG_CUSTOM(m_arg, m_type, m_cond, m_msg) \
+ if (unlikely(m_cond)) { \
+ *r_ret = m_msg; \
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; \
r_error.argument = m_arg; \
- r_error.expected = Variant::INT; \
- *r_ret = Variant(); \
+ r_error.expected = m_type; \
return; \
}
-#define VALIDATE_ARG_NUM(m_arg) \
- if (!p_args[m_arg]->is_num()) { \
+#else // !DEBUG_ENABLED
+
+#define DEBUG_VALIDATE_ARG_COUNT(m_min_count, m_max_count)
+#define DEBUG_VALIDATE_ARG_TYPE(m_arg, m_type)
+#define DEBUG_VALIDATE_ARG_CUSTOM(m_arg, m_type, m_cond, m_msg)
+
+#endif // DEBUG_ENABLED
+
+#define VALIDATE_ARG_CUSTOM(m_arg, m_type, m_cond, m_msg) \
+ if (unlikely(m_cond)) { \
+ *r_ret = m_msg; \
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; \
r_error.argument = m_arg; \
- r_error.expected = Variant::FLOAT; \
- *r_ret = Variant(); \
+ r_error.expected = m_type; \
return; \
}
-#else
-
-#define VALIDATE_ARG_COUNT(m_count)
-#define VALIDATE_ARG_INT(m_arg)
-#define VALIDATE_ARG_NUM(m_arg)
-
-#endif
+#define GDFUNC_FAIL_COND_MSG(m_cond, m_msg) \
+ if (unlikely(m_cond)) { \
+ *r_ret = m_msg; \
+ r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; \
+ return; \
+ }
struct GDScriptUtilityFunctionsDefinitions {
#ifndef DISABLE_DEPRECATED
static inline void convert(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
- VALIDATE_ARG_COUNT(2);
- VALIDATE_ARG_INT(1);
+ DEBUG_VALIDATE_ARG_COUNT(2, 2);
+ DEBUG_VALIDATE_ARG_TYPE(1, Variant::INT);
+
int type = *p_args[1];
- if (type < 0 || type >= Variant::VARIANT_MAX) {
- *r_ret = RTR("Invalid type argument to convert(), use TYPE_* constants.");
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::INT;
- return;
+ DEBUG_VALIDATE_ARG_CUSTOM(1, Variant::INT, type < 0 || type >= Variant::VARIANT_MAX,
+ RTR("Invalid type argument to convert(), use TYPE_* constants."));
- } else {
- Variant::construct(Variant::Type(type), *r_ret, p_args, 1, r_error);
- if (r_error.error != Callable::CallError::CALL_OK) {
- *r_ret = vformat(RTR(R"(Cannot convert "%s" to "%s".)"), Variant::get_type_name(p_args[0]->get_type()), Variant::get_type_name(Variant::Type(type)));
- }
- }
+ Variant::construct(Variant::Type(type), *r_ret, p_args, 1, r_error);
}
#endif // DISABLE_DEPRECATED
static inline void type_exists(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
- VALIDATE_ARG_COUNT(1);
+ DEBUG_VALIDATE_ARG_COUNT(1, 1);
+ DEBUG_VALIDATE_ARG_TYPE(0, Variant::STRING_NAME);
*r_ret = ClassDB::class_exists(*p_args[0]);
}
static inline void _char(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
- VALIDATE_ARG_COUNT(1);
- VALIDATE_ARG_INT(0);
+ DEBUG_VALIDATE_ARG_COUNT(1, 1);
+ DEBUG_VALIDATE_ARG_TYPE(0, Variant::INT);
char32_t result[2] = { *p_args[0], 0 };
*r_ret = String(result);
}
static inline void range(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
+ DEBUG_VALIDATE_ARG_COUNT(1, 3);
switch (p_arg_count) {
- case 0: {
- r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
- r_error.expected = 1;
- *r_ret = Variant();
- } break;
case 1: {
- VALIDATE_ARG_NUM(0);
+ DEBUG_VALIDATE_ARG_TYPE(0, Variant::INT);
+
int count = *p_args[0];
+
Array arr;
if (count <= 0) {
*r_ret = arr;
return;
}
+
Error err = arr.resize(count);
- if (err != OK) {
- *r_ret = RTR("Cannot resize array.");
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
- return;
- }
+ GDFUNC_FAIL_COND_MSG(err != OK, RTR("Cannot resize array."));
for (int i = 0; i < count; i++) {
arr[i] = i;
@@ -145,8 +148,8 @@ struct GDScriptUtilityFunctionsDefinitions {
*r_ret = arr;
} break;
case 2: {
- VALIDATE_ARG_NUM(0);
- VALIDATE_ARG_NUM(1);
+ DEBUG_VALIDATE_ARG_TYPE(0, Variant::INT);
+ DEBUG_VALIDATE_ARG_TYPE(1, Variant::INT);
int from = *p_args[0];
int to = *p_args[1];
@@ -156,30 +159,26 @@ struct GDScriptUtilityFunctionsDefinitions {
*r_ret = arr;
return;
}
+
Error err = arr.resize(to - from);
- if (err != OK) {
- *r_ret = RTR("Cannot resize array.");
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
- return;
- }
+ GDFUNC_FAIL_COND_MSG(err != OK, RTR("Cannot resize array."));
+
for (int i = from; i < to; i++) {
arr[i - from] = i;
}
+
*r_ret = arr;
} break;
case 3: {
- VALIDATE_ARG_NUM(0);
- VALIDATE_ARG_NUM(1);
- VALIDATE_ARG_NUM(2);
+ DEBUG_VALIDATE_ARG_TYPE(0, Variant::INT);
+ DEBUG_VALIDATE_ARG_TYPE(1, Variant::INT);
+ DEBUG_VALIDATE_ARG_TYPE(2, Variant::INT);
int from = *p_args[0];
int to = *p_args[1];
int incr = *p_args[2];
- if (incr == 0) {
- *r_ret = RTR("Step argument is zero!");
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
- return;
- }
+
+ VALIDATE_ARG_CUSTOM(2, Variant::INT, incr == 0, RTR("Step argument is zero!"));
Array arr;
if (from >= to && incr > 0) {
@@ -200,12 +199,7 @@ struct GDScriptUtilityFunctionsDefinitions {
}
Error err = arr.resize(count);
-
- if (err != OK) {
- *r_ret = RTR("Cannot resize array.");
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
- return;
- }
+ GDFUNC_FAIL_COND_MSG(err != OK, RTR("Cannot resize array."));
if (incr > 0) {
int idx = 0;
@@ -221,138 +215,79 @@ struct GDScriptUtilityFunctionsDefinitions {
*r_ret = arr;
} break;
- default: {
- r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS;
- r_error.expected = 3;
- *r_ret = Variant();
-
- } break;
}
}
static inline void load(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
- VALIDATE_ARG_COUNT(1);
- if (!p_args[0]->is_string()) {
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::STRING;
- *r_ret = Variant();
- } else {
- *r_ret = ResourceLoader::load(*p_args[0]);
- }
+ DEBUG_VALIDATE_ARG_COUNT(1, 1);
+ DEBUG_VALIDATE_ARG_TYPE(0, Variant::STRING);
+ *r_ret = ResourceLoader::load(*p_args[0]);
}
static inline void inst_to_dict(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
- VALIDATE_ARG_COUNT(1);
+ DEBUG_VALIDATE_ARG_COUNT(1, 1);
+ DEBUG_VALIDATE_ARG_TYPE(0, Variant::OBJECT);
if (p_args[0]->get_type() == Variant::NIL) {
*r_ret = Variant();
- } else if (p_args[0]->get_type() != Variant::OBJECT) {
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::OBJECT;
+ return;
+ }
+
+ Object *obj = *p_args[0];
+ if (!obj) {
*r_ret = Variant();
- } else {
- Object *obj = *p_args[0];
- if (!obj) {
- *r_ret = Variant();
+ return;
+ }
- } else if (!obj->get_script_instance() || obj->get_script_instance()->get_language() != GDScriptLanguage::get_singleton()) {
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::DICTIONARY;
- *r_ret = RTR("Not a script with an instance");
- return;
- } else {
- GDScriptInstance *ins = static_cast<GDScriptInstance *>(obj->get_script_instance());
- Ref<GDScript> base = ins->get_script();
- if (base.is_null()) {
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::DICTIONARY;
- *r_ret = RTR("Not based on a script");
- return;
- }
+ VALIDATE_ARG_CUSTOM(0, Variant::OBJECT,
+ !obj->get_script_instance() || obj->get_script_instance()->get_language() != GDScriptLanguage::get_singleton(),
+ RTR("Not a script with an instance."));
- GDScript *p = base.ptr();
- String path = p->get_script_path();
- Vector<StringName> sname;
+ GDScriptInstance *inst = static_cast<GDScriptInstance *>(obj->get_script_instance());
- while (p->_owner) {
- sname.push_back(p->local_name);
- p = p->_owner;
- }
- sname.reverse();
+ Ref<GDScript> base = inst->get_script();
+ VALIDATE_ARG_CUSTOM(0, Variant::OBJECT, base.is_null(), RTR("Not based on a script."));
- if (!path.is_resource_file()) {
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::DICTIONARY;
- *r_ret = Variant();
+ GDScript *p = base.ptr();
+ String path = p->get_script_path();
+ Vector<StringName> sname;
- *r_ret = RTR("Not based on a resource file");
+ while (p->_owner) {
+ sname.push_back(p->local_name);
+ p = p->_owner;
+ }
+ sname.reverse();
- return;
- }
+ VALIDATE_ARG_CUSTOM(0, Variant::OBJECT, !path.is_resource_file(), RTR("Not based on a resource file."));
- NodePath cp(sname, Vector<StringName>(), false);
+ NodePath cp(sname, Vector<StringName>(), false);
- Dictionary d;
- d["@subpath"] = cp;
- d["@path"] = path;
+ Dictionary d;
+ d["@subpath"] = cp;
+ d["@path"] = path;
- for (const KeyValue<StringName, GDScript::MemberInfo> &E : base->member_indices) {
- if (!d.has(E.key)) {
- d[E.key] = ins->members[E.value.index];
- }
- }
- *r_ret = d;
+ for (const KeyValue<StringName, GDScript::MemberInfo> &E : base->member_indices) {
+ if (!d.has(E.key)) {
+ d[E.key] = inst->members[E.value.index];
}
}
+
+ *r_ret = d;
}
static inline void dict_to_inst(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
- VALIDATE_ARG_COUNT(1);
-
- if (p_args[0]->get_type() != Variant::DICTIONARY) {
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::DICTIONARY;
- *r_ret = Variant();
-
- return;
- }
+ DEBUG_VALIDATE_ARG_COUNT(1, 1);
+ DEBUG_VALIDATE_ARG_TYPE(0, Variant::DICTIONARY);
Dictionary d = *p_args[0];
- if (!d.has("@path")) {
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::OBJECT;
- *r_ret = RTR("Invalid instance dictionary format (missing @path)");
-
- return;
- }
+ VALIDATE_ARG_CUSTOM(0, Variant::DICTIONARY, !d.has("@path"), RTR("Invalid instance dictionary format (missing @path)."));
Ref<Script> scr = ResourceLoader::load(d["@path"]);
- if (!scr.is_valid()) {
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::OBJECT;
- *r_ret = RTR("Invalid instance dictionary format (can't load script at @path)");
- return;
- }
+ VALIDATE_ARG_CUSTOM(0, Variant::DICTIONARY, !scr.is_valid(), RTR("Invalid instance dictionary format (can't load script at @path)."));
Ref<GDScript> gdscr = scr;
-
- if (!gdscr.is_valid()) {
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::OBJECT;
- *r_ret = Variant();
- *r_ret = RTR("Invalid instance dictionary format (invalid script at @path)");
- return;
- }
+ VALIDATE_ARG_CUSTOM(0, Variant::DICTIONARY, !gdscr.is_valid(), RTR("Invalid instance dictionary format (invalid script at @path)."));
NodePath sub;
if (d.has("@subpath")) {
@@ -361,54 +296,35 @@ struct GDScriptUtilityFunctionsDefinitions {
for (int i = 0; i < sub.get_name_count(); i++) {
gdscr = gdscr->subclasses[sub.get_name(i)];
- if (!gdscr.is_valid()) {
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::OBJECT;
- *r_ret = Variant();
- *r_ret = RTR("Invalid instance dictionary (invalid subclasses)");
- return;
- }
+ VALIDATE_ARG_CUSTOM(0, Variant::DICTIONARY, !gdscr.is_valid(), RTR("Invalid instance dictionary (invalid subclasses)."));
}
- *r_ret = gdscr->_new(nullptr, -1 /*skip initializer*/, r_error);
+ *r_ret = gdscr->_new(nullptr, -1 /* skip initializer */, r_error);
if (r_error.error != Callable::CallError::CALL_OK) {
*r_ret = RTR("Cannot instantiate GDScript class.");
return;
}
- GDScriptInstance *ins = static_cast<GDScriptInstance *>(static_cast<Object *>(*r_ret)->get_script_instance());
- Ref<GDScript> gd_ref = ins->get_script();
+ GDScriptInstance *inst = static_cast<GDScriptInstance *>(static_cast<Object *>(*r_ret)->get_script_instance());
+ Ref<GDScript> gd_ref = inst->get_script();
for (KeyValue<StringName, GDScript::MemberInfo> &E : gd_ref->member_indices) {
if (d.has(E.key)) {
- ins->members.write[E.value.index] = d[E.key];
+ inst->members.write[E.value.index] = d[E.key];
}
}
}
static inline void Color8(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
- if (p_arg_count < 3) {
- r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
- r_error.expected = 3;
- *r_ret = Variant();
- return;
- }
- if (p_arg_count > 4) {
- r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS;
- r_error.expected = 4;
- *r_ret = Variant();
- return;
- }
-
- VALIDATE_ARG_INT(0);
- VALIDATE_ARG_INT(1);
- VALIDATE_ARG_INT(2);
+ DEBUG_VALIDATE_ARG_COUNT(3, 4);
+ DEBUG_VALIDATE_ARG_TYPE(0, Variant::INT);
+ DEBUG_VALIDATE_ARG_TYPE(1, Variant::INT);
+ DEBUG_VALIDATE_ARG_TYPE(2, Variant::INT);
Color color((int64_t)*p_args[0] / 255.0f, (int64_t)*p_args[1] / 255.0f, (int64_t)*p_args[2] / 255.0f);
if (p_arg_count == 4) {
- VALIDATE_ARG_INT(3);
+ DEBUG_VALIDATE_ARG_TYPE(3, Variant::INT);
color.a = (int64_t)*p_args[3] / 255.0f;
}
@@ -435,7 +351,8 @@ struct GDScriptUtilityFunctionsDefinitions {
}
static inline void print_stack(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
- VALIDATE_ARG_COUNT(0);
+ DEBUG_VALIDATE_ARG_COUNT(0, 0);
+
if (Thread::get_caller_id() != Thread::get_main_id()) {
print_line("Cannot retrieve debug info outside the main thread. Thread ID: " + itos(Thread::get_caller_id()));
return;
@@ -449,7 +366,8 @@ struct GDScriptUtilityFunctionsDefinitions {
}
static inline void get_stack(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
- VALIDATE_ARG_COUNT(0);
+ DEBUG_VALIDATE_ARG_COUNT(0, 0);
+
if (Thread::get_caller_id() != Thread::get_main_id()) {
*r_ret = TypedArray<Dictionary>();
return;
@@ -468,7 +386,7 @@ struct GDScriptUtilityFunctionsDefinitions {
}
static inline void len(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
- VALIDATE_ARG_COUNT(1);
+ DEBUG_VALIDATE_ARG_COUNT(1, 1);
switch (p_args[0]->get_type()) {
case Variant::STRING:
case Variant::STRING_NAME: {
@@ -524,56 +442,34 @@ struct GDScriptUtilityFunctionsDefinitions {
*r_ret = d.size();
} break;
default: {
+ *r_ret = vformat(RTR("Value of type '%s' can't provide a length."), Variant::get_type_name(p_args[0]->get_type()));
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::NIL;
- *r_ret = vformat(RTR("Value of type '%s' can't provide a length."), Variant::get_type_name(p_args[0]->get_type()));
- }
+ } break;
}
}
static inline void is_instance_of(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
- VALIDATE_ARG_COUNT(2);
+ DEBUG_VALIDATE_ARG_COUNT(2, 2);
if (p_args[1]->get_type() == Variant::INT) {
int builtin_type = *p_args[1];
- if (builtin_type < 0 || builtin_type >= Variant::VARIANT_MAX) {
- *r_ret = RTR("Invalid type argument for is_instance_of(), use TYPE_* constants for built-in types.");
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 1;
- r_error.expected = Variant::NIL;
- return;
- }
+ DEBUG_VALIDATE_ARG_CUSTOM(1, Variant::NIL, builtin_type < 0 || builtin_type >= Variant::VARIANT_MAX,
+ RTR("Invalid type argument for is_instance_of(), use TYPE_* constants for built-in types."));
*r_ret = p_args[0]->get_type() == builtin_type;
return;
}
bool was_type_freed = false;
Object *type_object = p_args[1]->get_validated_object_with_check(was_type_freed);
- if (was_type_freed) {
- *r_ret = RTR("Type argument is a previously freed instance.");
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 1;
- r_error.expected = Variant::NIL;
- return;
- }
- if (!type_object) {
- *r_ret = RTR("Invalid type argument for is_instance_of(), should be a TYPE_* constant, a class or a script.");
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 1;
- r_error.expected = Variant::NIL;
- return;
- }
+ VALIDATE_ARG_CUSTOM(1, Variant::NIL, was_type_freed, RTR("Type argument is a previously freed instance."));
+ VALIDATE_ARG_CUSTOM(1, Variant::NIL, !type_object,
+ RTR("Invalid type argument for is_instance_of(), should be a TYPE_* constant, a class or a script."));
bool was_value_freed = false;
Object *value_object = p_args[0]->get_validated_object_with_check(was_value_freed);
- if (was_value_freed) {
- *r_ret = RTR("Value argument is a previously freed instance.");
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::NIL;
- return;
- }
+ VALIDATE_ARG_CUSTOM(0, Variant::NIL, was_value_freed, RTR("Value argument is a previously freed instance."));
if (!value_object) {
*r_ret = false;
return;
@@ -618,113 +514,77 @@ struct GDScriptUtilityFunctionInfo {
static OAHashMap<StringName, GDScriptUtilityFunctionInfo> utility_function_table;
static List<StringName> utility_function_name_table;
-static void _register_function(const String &p_name, const MethodInfo &p_method_info, GDScriptUtilityFunctions::FunctionPtr p_function, bool p_is_const) {
- StringName sname(p_name);
-
- ERR_FAIL_COND(utility_function_table.has(sname));
+static void _register_function(const StringName &p_name, const MethodInfo &p_method_info, GDScriptUtilityFunctions::FunctionPtr p_function, bool p_is_const) {
+ ERR_FAIL_COND(utility_function_table.has(p_name));
GDScriptUtilityFunctionInfo function;
function.function = p_function;
function.info = p_method_info;
function.is_constant = p_is_const;
- utility_function_table.insert(sname, function);
- utility_function_name_table.push_back(sname);
+ utility_function_table.insert(p_name, function);
+ utility_function_name_table.push_back(p_name);
}
-#define REGISTER_FUNC(m_func, m_is_const, m_return_type, ...) \
+#define REGISTER_FUNC(m_func, m_is_const, m_return, m_args, m_is_vararg, m_default_args) \
{ \
String name(#m_func); \
if (name.begins_with("_")) { \
- name = name.substr(1, name.length() - 1); \
+ name = name.substr(1); \
} \
- MethodInfo info = MethodInfo(name, __VA_ARGS__); \
- info.return_val.type = m_return_type; \
- _register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \
- }
-
-#define REGISTER_FUNC_NO_ARGS(m_func, m_is_const, m_return_type) \
- { \
- String name(#m_func); \
- if (name.begins_with("_")) { \
- name = name.substr(1, name.length() - 1); \
+ MethodInfo info = m_args; \
+ info.name = name; \
+ info.return_val = m_return; \
+ info.default_arguments = m_default_args; \
+ if (m_is_vararg) { \
+ info.flags |= METHOD_FLAG_VARARG; \
} \
- MethodInfo info = MethodInfo(name); \
- info.return_val.type = m_return_type; \
_register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \
}
-#define REGISTER_VARARG_FUNC(m_func, m_is_const, m_return_type) \
- { \
- String name(#m_func); \
- if (name.begins_with("_")) { \
- name = name.substr(1, name.length() - 1); \
- } \
- MethodInfo info = MethodInfo(name); \
- info.return_val.type = m_return_type; \
- info.flags |= METHOD_FLAG_VARARG; \
- _register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \
- }
+#define RET(m_type) \
+ PropertyInfo(Variant::m_type, "")
-#define REGISTER_VARIANT_FUNC(m_func, m_is_const, ...) \
- { \
- String name(#m_func); \
- if (name.begins_with("_")) { \
- name = name.substr(1, name.length() - 1); \
- } \
- MethodInfo info = MethodInfo(name, __VA_ARGS__); \
- info.return_val.type = Variant::NIL; \
- info.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; \
- _register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \
- }
+#define RETVAR \
+ PropertyInfo(Variant::NIL, "", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT)
-#define REGISTER_CLASS_FUNC(m_func, m_is_const, m_return_type, ...) \
- { \
- String name(#m_func); \
- if (name.begins_with("_")) { \
- name = name.substr(1, name.length() - 1); \
- } \
- MethodInfo info = MethodInfo(name, __VA_ARGS__); \
- info.return_val.type = Variant::OBJECT; \
- info.return_val.hint = PROPERTY_HINT_RESOURCE_TYPE; \
- info.return_val.class_name = m_return_type; \
- _register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \
- }
+#define RETCLS(m_class) \
+ PropertyInfo(Variant::OBJECT, "", PROPERTY_HINT_RESOURCE_TYPE, m_class)
-#define REGISTER_FUNC_DEF(m_func, m_is_const, m_default, m_return_type, ...) \
- { \
- String name(#m_func); \
- if (name.begins_with("_")) { \
- name = name.substr(1, name.length() - 1); \
- } \
- MethodInfo info = MethodInfo(name, __VA_ARGS__); \
- info.return_val.type = m_return_type; \
- info.default_arguments.push_back(m_default); \
- _register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \
- }
+#define NOARGS \
+ MethodInfo()
+
+#define ARGS(...) \
+ MethodInfo("", __VA_ARGS__)
#define ARG(m_name, m_type) \
- PropertyInfo(m_type, m_name)
+ PropertyInfo(Variant::m_type, m_name)
+
+#define ARGVAR(m_name) \
+ PropertyInfo(Variant::NIL, m_name, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT)
-#define VARARG(m_name) \
- PropertyInfo(Variant::NIL, m_name, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT)
+#define ARGTYPE(m_name) \
+ PropertyInfo(Variant::INT, m_name, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_CLASS_IS_ENUM, "Variant.Type")
void GDScriptUtilityFunctions::register_functions() {
+ /* clang-format off */
#ifndef DISABLE_DEPRECATED
- REGISTER_VARIANT_FUNC(convert, true, VARARG("what"), ARG("type", Variant::INT));
+ REGISTER_FUNC( convert, true, RETVAR, ARGS( ARGVAR("what"), ARGTYPE("type") ), false, varray( ));
#endif // DISABLE_DEPRECATED
- REGISTER_FUNC(type_exists, true, Variant::BOOL, ARG("type", Variant::STRING_NAME));
- REGISTER_FUNC(_char, true, Variant::STRING, ARG("char", Variant::INT));
- REGISTER_VARARG_FUNC(range, false, Variant::ARRAY);
- REGISTER_CLASS_FUNC(load, false, "Resource", ARG("path", Variant::STRING));
- REGISTER_FUNC(inst_to_dict, false, Variant::DICTIONARY, ARG("instance", Variant::OBJECT));
- REGISTER_FUNC(dict_to_inst, false, Variant::OBJECT, ARG("dictionary", Variant::DICTIONARY));
- REGISTER_FUNC_DEF(Color8, true, 255, Variant::COLOR, ARG("r8", Variant::INT), ARG("g8", Variant::INT), ARG("b8", Variant::INT), ARG("a8", Variant::INT));
- REGISTER_VARARG_FUNC(print_debug, false, Variant::NIL);
- REGISTER_FUNC_NO_ARGS(print_stack, false, Variant::NIL);
- REGISTER_FUNC_NO_ARGS(get_stack, false, Variant::ARRAY);
- REGISTER_FUNC(len, true, Variant::INT, VARARG("var"));
- REGISTER_FUNC(is_instance_of, true, Variant::BOOL, VARARG("value"), VARARG("type"));
+ REGISTER_FUNC( type_exists, true, RET(BOOL), ARGS( ARG("type", STRING_NAME) ), false, varray( ));
+ REGISTER_FUNC( _char, true, RET(STRING), ARGS( ARG("char", INT) ), false, varray( ));
+ REGISTER_FUNC( range, false, RET(ARRAY), NOARGS, true, varray( ));
+ REGISTER_FUNC( load, false, RETCLS("Resource"), ARGS( ARG("path", STRING) ), false, varray( ));
+ REGISTER_FUNC( inst_to_dict, false, RET(DICTIONARY), ARGS( ARG("instance", OBJECT) ), false, varray( ));
+ REGISTER_FUNC( dict_to_inst, false, RET(OBJECT), ARGS( ARG("dictionary", DICTIONARY) ), false, varray( ));
+ REGISTER_FUNC( Color8, true, RET(COLOR), ARGS( ARG("r8", INT), ARG("g8", INT),
+ ARG("b8", INT), ARG("a8", INT) ), false, varray( 255 ));
+ REGISTER_FUNC( print_debug, false, RET(NIL), NOARGS, true, varray( ));
+ REGISTER_FUNC( print_stack, false, RET(NIL), NOARGS, false, varray( ));
+ REGISTER_FUNC( get_stack, false, RET(ARRAY), NOARGS, false, varray( ));
+ REGISTER_FUNC( len, true, RET(INT), ARGS( ARGVAR("var") ), false, varray( ));
+ REGISTER_FUNC( is_instance_of, true, RET(BOOL), ARGS( ARGVAR("value"), ARGVAR("type") ), false, varray( ));
+ /* clang-format on */
}
void GDScriptUtilityFunctions::unregister_functions() {
diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp
index ddb0cf9502..26c5cfe23c 100644
--- a/modules/gdscript/gdscript_vm.cpp
+++ b/modules/gdscript/gdscript_vm.cpp
@@ -84,9 +84,15 @@ static String _get_var_type(const Variant *p_var) {
if (p_var->get_type() == Variant::ARRAY) {
basestr = "Array";
const Array *p_array = VariantInternal::get_array(p_var);
- Variant::Type builtin_type = (Variant::Type)p_array->get_typed_builtin();
- if (builtin_type != Variant::NIL) {
- basestr += "[" + _get_element_type(builtin_type, p_array->get_typed_class_name(), p_array->get_typed_script()) + "]";
+ if (p_array->is_typed()) {
+ basestr += "[" + _get_element_type((Variant::Type)p_array->get_typed_builtin(), p_array->get_typed_class_name(), p_array->get_typed_script()) + "]";
+ }
+ } else if (p_var->get_type() == Variant::DICTIONARY) {
+ basestr = "Dictionary";
+ const Dictionary *p_dictionary = VariantInternal::get_dictionary(p_var);
+ if (p_dictionary->is_typed()) {
+ basestr += "[" + _get_element_type((Variant::Type)p_dictionary->get_typed_key_builtin(), p_dictionary->get_typed_key_class_name(), p_dictionary->get_typed_key_script()) +
+ ", " + _get_element_type((Variant::Type)p_dictionary->get_typed_value_builtin(), p_dictionary->get_typed_value_class_name(), p_dictionary->get_typed_value_script()) + "]";
}
} else {
basestr = Variant::get_type_name(p_var->get_type());
@@ -120,6 +126,16 @@ Variant GDScriptFunction::_get_default_variant_for_data_type(const GDScriptDataT
}
return array;
+ } else if (p_data_type.builtin_type == Variant::DICTIONARY) {
+ Dictionary dict;
+ // Typed dictionary.
+ if (p_data_type.has_container_element_types()) {
+ const GDScriptDataType &key_type = p_data_type.get_container_element_type_or_variant(0);
+ const GDScriptDataType &value_type = p_data_type.get_container_element_type_or_variant(1);
+ dict.set_typed(key_type.builtin_type, key_type.native_type, key_type.script_type, value_type.builtin_type, value_type.native_type, value_type.script_type);
+ }
+
+ return dict;
} else {
Callable::CallError ce;
Variant variant;
@@ -153,6 +169,9 @@ String GDScriptFunction::_get_call_error(const String &p_where, const Variant **
if (p_err.expected == Variant::ARRAY && p_argptrs[p_err.argument]->get_type() == p_err.expected) {
return "Invalid type in " + p_where + ". The array of argument " + itos(p_err.argument + 1) + " (" + _get_var_type(p_argptrs[p_err.argument]) + ") does not have the same element type as the expected typed array argument.";
}
+ if (p_err.expected == Variant::DICTIONARY && p_argptrs[p_err.argument]->get_type() == p_err.expected) {
+ return "Invalid type in " + p_where + ". The dictionary of argument " + itos(p_err.argument + 1) + " (" + _get_var_type(p_argptrs[p_err.argument]) + ") does not have the same element type as the expected typed dictionary argument.";
+ }
#endif // DEBUG_ENABLED
return "Invalid type in " + p_where + ". Cannot convert argument " + itos(p_err.argument + 1) + " from " + Variant::get_type_name(p_argptrs[p_err.argument]->get_type()) + " to " + Variant::get_type_name(Variant::Type(p_err.expected)) + ".";
case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS:
@@ -208,13 +227,14 @@ void (*type_init_function_table[])(Variant *) = {
&VariantInitializer<PackedVector4Array>::init, // PACKED_VECTOR4_ARRAY.
};
-#if defined(__GNUC__)
+#if defined(__GNUC__) || defined(__clang__)
#define OPCODES_TABLE \
static const void *switch_table_ops[] = { \
&&OPCODE_OPERATOR, \
&&OPCODE_OPERATOR_VALIDATED, \
&&OPCODE_TYPE_TEST_BUILTIN, \
&&OPCODE_TYPE_TEST_ARRAY, \
+ &&OPCODE_TYPE_TEST_DICTIONARY, \
&&OPCODE_TYPE_TEST_NATIVE, \
&&OPCODE_TYPE_TEST_SCRIPT, \
&&OPCODE_SET_KEYED, \
@@ -237,6 +257,7 @@ void (*type_init_function_table[])(Variant *) = {
&&OPCODE_ASSIGN_FALSE, \
&&OPCODE_ASSIGN_TYPED_BUILTIN, \
&&OPCODE_ASSIGN_TYPED_ARRAY, \
+ &&OPCODE_ASSIGN_TYPED_DICTIONARY, \
&&OPCODE_ASSIGN_TYPED_NATIVE, \
&&OPCODE_ASSIGN_TYPED_SCRIPT, \
&&OPCODE_CAST_TO_BUILTIN, \
@@ -247,6 +268,7 @@ void (*type_init_function_table[])(Variant *) = {
&&OPCODE_CONSTRUCT_ARRAY, \
&&OPCODE_CONSTRUCT_TYPED_ARRAY, \
&&OPCODE_CONSTRUCT_DICTIONARY, \
+ &&OPCODE_CONSTRUCT_TYPED_DICTIONARY, \
&&OPCODE_CALL, \
&&OPCODE_CALL_RETURN, \
&&OPCODE_CALL_ASYNC, \
@@ -275,6 +297,7 @@ void (*type_init_function_table[])(Variant *) = {
&&OPCODE_RETURN, \
&&OPCODE_RETURN_TYPED_BUILTIN, \
&&OPCODE_RETURN_TYPED_ARRAY, \
+ &&OPCODE_RETURN_TYPED_DICTIONARY, \
&&OPCODE_RETURN_TYPED_NATIVE, \
&&OPCODE_RETURN_TYPED_SCRIPT, \
&&OPCODE_ITERATE_BEGIN, \
@@ -374,32 +397,36 @@ void (*type_init_function_table[])(Variant *) = {
#define OPCODES_OUT \
OPSOUT:
#define OPCODE_SWITCH(m_test) goto *switch_table_ops[m_test];
+
#ifdef DEBUG_ENABLED
#define DISPATCH_OPCODE \
last_opcode = _code_ptr[ip]; \
goto *switch_table_ops[last_opcode]
-#else
+#else // !DEBUG_ENABLED
#define DISPATCH_OPCODE goto *switch_table_ops[_code_ptr[ip]]
-#endif
+#endif // DEBUG_ENABLED
+
#define OPCODE_BREAK goto OPSEXIT
#define OPCODE_OUT goto OPSOUT
-#else
+#else // !(defined(__GNUC__) || defined(__clang__))
#define OPCODES_TABLE
#define OPCODE(m_op) case m_op:
#define OPCODE_WHILE(m_test) while (m_test)
#define OPCODES_END
#define OPCODES_OUT
#define DISPATCH_OPCODE continue
+
#ifdef _MSC_VER
#define OPCODE_SWITCH(m_test) \
__assume(m_test <= OPCODE_END); \
switch (m_test)
-#else
+#else // !_MSC_VER
#define OPCODE_SWITCH(m_test) switch (m_test)
-#endif
+#endif // _MSC_VER
+
#define OPCODE_BREAK break
#define OPCODE_OUT break
-#endif
+#endif // defined(__GNUC__) || defined(__clang__)
// Helpers for VariantInternal methods in macros.
#define OP_GET_BOOL get_bool
@@ -548,7 +575,12 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
return _get_default_variant_for_data_type(return_type);
}
if (argument_types[i].kind == GDScriptDataType::BUILTIN) {
- if (argument_types[i].builtin_type == Variant::ARRAY && argument_types[i].has_container_element_type(0)) {
+ if (argument_types[i].builtin_type == Variant::DICTIONARY && argument_types[i].has_container_element_types()) {
+ const GDScriptDataType &arg_key_type = argument_types[i].get_container_element_type_or_variant(0);
+ const GDScriptDataType &arg_value_type = argument_types[i].get_container_element_type_or_variant(1);
+ Dictionary dict(p_args[i]->operator Dictionary(), arg_key_type.builtin_type, arg_key_type.native_type, arg_key_type.script_type, arg_value_type.builtin_type, arg_value_type.native_type, arg_value_type.script_type);
+ memnew_placement(&stack[i + 3], Variant(dict));
+ } else if (argument_types[i].builtin_type == Variant::ARRAY && argument_types[i].has_container_element_type(0)) {
const GDScriptDataType &arg_type = argument_types[i].container_element_types[0];
Array array(p_args[i]->operator Array(), arg_type.builtin_type, arg_type.native_type, arg_type.script_type);
memnew_placement(&stack[i + 3], Variant(array));
@@ -635,7 +667,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
OPCODE_BREAK; \
}
-#else
+#else // !DEBUG_ENABLED
#define GD_ERR_BREAK(m_cond)
#define CHECK_SPACE(m_space)
@@ -648,7 +680,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
OPCODE_BREAK; \
}
-#endif
+#endif // DEBUG_ENABLED
#define LOAD_INSTRUCTION_ARGS \
int instr_arg_count = _code_ptr[ip + 1]; \
@@ -827,6 +859,36 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
}
DISPATCH_OPCODE;
+ OPCODE(OPCODE_TYPE_TEST_DICTIONARY) {
+ CHECK_SPACE(9);
+
+ GET_VARIANT_PTR(dst, 0);
+ GET_VARIANT_PTR(value, 1);
+
+ GET_VARIANT_PTR(key_script_type, 2);
+ Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + 5];
+ int key_native_type_idx = _code_ptr[ip + 6];
+ GD_ERR_BREAK(key_native_type_idx < 0 || key_native_type_idx >= _global_names_count);
+ const StringName key_native_type = _global_names_ptr[key_native_type_idx];
+
+ GET_VARIANT_PTR(value_script_type, 3);
+ Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + 7];
+ int value_native_type_idx = _code_ptr[ip + 8];
+ GD_ERR_BREAK(value_native_type_idx < 0 || value_native_type_idx >= _global_names_count);
+ const StringName value_native_type = _global_names_ptr[value_native_type_idx];
+
+ bool result = false;
+ if (value->get_type() == Variant::DICTIONARY) {
+ Dictionary *dictionary = VariantInternal::get_dictionary(value);
+ result = dictionary->get_typed_key_builtin() == ((uint32_t)key_builtin_type) && dictionary->get_typed_key_class_name() == key_native_type && dictionary->get_typed_key_script() == *key_script_type &&
+ dictionary->get_typed_value_builtin() == ((uint32_t)value_builtin_type) && dictionary->get_typed_value_class_name() == value_native_type && dictionary->get_typed_value_script() == *value_script_type;
+ }
+
+ *dst = result;
+ ip += 9;
+ }
+ DISPATCH_OPCODE;
+
OPCODE(OPCODE_TYPE_TEST_NATIVE) {
CHECK_SPACE(4);
@@ -1384,6 +1446,50 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
}
DISPATCH_OPCODE;
+ OPCODE(OPCODE_ASSIGN_TYPED_DICTIONARY) {
+ CHECK_SPACE(9);
+ GET_VARIANT_PTR(dst, 0);
+ GET_VARIANT_PTR(src, 1);
+
+ GET_VARIANT_PTR(key_script_type, 2);
+ Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + 5];
+ int key_native_type_idx = _code_ptr[ip + 6];
+ GD_ERR_BREAK(key_native_type_idx < 0 || key_native_type_idx >= _global_names_count);
+ const StringName key_native_type = _global_names_ptr[key_native_type_idx];
+
+ GET_VARIANT_PTR(value_script_type, 3);
+ Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + 7];
+ int value_native_type_idx = _code_ptr[ip + 8];
+ GD_ERR_BREAK(value_native_type_idx < 0 || value_native_type_idx >= _global_names_count);
+ const StringName value_native_type = _global_names_ptr[value_native_type_idx];
+
+ if (src->get_type() != Variant::DICTIONARY) {
+#ifdef DEBUG_ENABLED
+ err_text = vformat(R"(Trying to assign a value of type "%s" to a variable of type "Dictionary[%s, %s]".)",
+ _get_var_type(src), _get_element_type(key_builtin_type, key_native_type, *key_script_type),
+ _get_element_type(value_builtin_type, value_native_type, *value_script_type));
+#endif // DEBUG_ENABLED
+ OPCODE_BREAK;
+ }
+
+ Dictionary *dictionary = VariantInternal::get_dictionary(src);
+
+ if (dictionary->get_typed_key_builtin() != ((uint32_t)key_builtin_type) || dictionary->get_typed_key_class_name() != key_native_type || dictionary->get_typed_key_script() != *key_script_type ||
+ dictionary->get_typed_value_builtin() != ((uint32_t)value_builtin_type) || dictionary->get_typed_value_class_name() != value_native_type || dictionary->get_typed_value_script() != *value_script_type) {
+#ifdef DEBUG_ENABLED
+ err_text = vformat(R"(Trying to assign a dictionary of type "%s" to a variable of type "Dictionary[%s, %s]".)",
+ _get_var_type(src), _get_element_type(key_builtin_type, key_native_type, *key_script_type),
+ _get_element_type(value_builtin_type, value_native_type, *value_script_type));
+#endif // DEBUG_ENABLED
+ OPCODE_BREAK;
+ }
+
+ *dst = *src;
+
+ ip += 9;
+ }
+ DISPATCH_OPCODE;
+
OPCODE(OPCODE_ASSIGN_TYPED_NATIVE) {
CHECK_SPACE(4);
GET_VARIANT_PTR(dst, 0);
@@ -1703,12 +1809,51 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
GET_INSTRUCTION_ARG(dst, argc * 2);
+ *dst = Variant(); // Clear potential previous typed dictionary.
+
*dst = dict;
ip += 2;
}
DISPATCH_OPCODE;
+ OPCODE(OPCODE_CONSTRUCT_TYPED_DICTIONARY) {
+ LOAD_INSTRUCTION_ARGS
+ CHECK_SPACE(6 + instr_arg_count);
+ ip += instr_arg_count;
+
+ int argc = _code_ptr[ip + 1];
+
+ GET_INSTRUCTION_ARG(key_script_type, argc * 2 + 1);
+ Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + 2];
+ int key_native_type_idx = _code_ptr[ip + 3];
+ GD_ERR_BREAK(key_native_type_idx < 0 || key_native_type_idx >= _global_names_count);
+ const StringName key_native_type = _global_names_ptr[key_native_type_idx];
+
+ GET_INSTRUCTION_ARG(value_script_type, argc * 2 + 2);
+ Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + 4];
+ int value_native_type_idx = _code_ptr[ip + 5];
+ GD_ERR_BREAK(value_native_type_idx < 0 || value_native_type_idx >= _global_names_count);
+ const StringName value_native_type = _global_names_ptr[value_native_type_idx];
+
+ Dictionary dict;
+
+ for (int i = 0; i < argc; i++) {
+ GET_INSTRUCTION_ARG(k, i * 2 + 0);
+ GET_INSTRUCTION_ARG(v, i * 2 + 1);
+ dict[*k] = *v;
+ }
+
+ GET_INSTRUCTION_ARG(dst, argc * 2);
+
+ *dst = Variant(); // Clear potential previous typed dictionary.
+
+ *dst = Dictionary(dict, key_builtin_type, key_native_type, *key_script_type, value_builtin_type, value_native_type, *value_script_type);
+
+ ip += 6;
+ }
+ DISPATCH_OPCODE;
+
OPCODE(OPCODE_CALL_ASYNC)
OPCODE(OPCODE_CALL_RETURN)
OPCODE(OPCODE_CALL) {
@@ -1824,7 +1969,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
err_text = _get_call_error("function '" + methodstr + (is_callable ? "" : "' in base '" + basestr) + "'", (const Variant **)argptrs, temp_ret, err);
OPCODE_BREAK;
}
-#endif
+#endif // DEBUG_ENABLED
ip += 3;
}
@@ -2657,6 +2802,51 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
OPCODE_BREAK;
}
+ OPCODE(OPCODE_RETURN_TYPED_DICTIONARY) {
+ CHECK_SPACE(8);
+ GET_VARIANT_PTR(r, 0);
+
+ GET_VARIANT_PTR(key_script_type, 1);
+ Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + 4];
+ int key_native_type_idx = _code_ptr[ip + 5];
+ GD_ERR_BREAK(key_native_type_idx < 0 || key_native_type_idx >= _global_names_count);
+ const StringName key_native_type = _global_names_ptr[key_native_type_idx];
+
+ GET_VARIANT_PTR(value_script_type, 2);
+ Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + 6];
+ int value_native_type_idx = _code_ptr[ip + 7];
+ GD_ERR_BREAK(value_native_type_idx < 0 || value_native_type_idx >= _global_names_count);
+ const StringName value_native_type = _global_names_ptr[value_native_type_idx];
+
+ if (r->get_type() != Variant::DICTIONARY) {
+#ifdef DEBUG_ENABLED
+ err_text = vformat(R"(Trying to return a value of type "%s" where expected return type is "Dictionary[%s, %s]".)",
+ _get_var_type(r), _get_element_type(key_builtin_type, key_native_type, *key_script_type),
+ _get_element_type(value_builtin_type, value_native_type, *value_script_type));
+#endif // DEBUG_ENABLED
+ OPCODE_BREAK;
+ }
+
+ Dictionary *dictionary = VariantInternal::get_dictionary(r);
+
+ if (dictionary->get_typed_key_builtin() != ((uint32_t)key_builtin_type) || dictionary->get_typed_key_class_name() != key_native_type || dictionary->get_typed_key_script() != *key_script_type ||
+ dictionary->get_typed_value_builtin() != ((uint32_t)value_builtin_type) || dictionary->get_typed_value_class_name() != value_native_type || dictionary->get_typed_value_script() != *value_script_type) {
+#ifdef DEBUG_ENABLED
+ err_text = vformat(R"(Trying to return a dictionary of type "%s" where expected return type is "Dictionary[%s, %s]".)",
+ _get_var_type(r), _get_element_type(key_builtin_type, key_native_type, *key_script_type),
+ _get_element_type(value_builtin_type, value_native_type, *value_script_type));
+#endif // DEBUG_ENABLED
+ OPCODE_BREAK;
+ }
+
+ retvalue = *dictionary;
+
+#ifdef DEBUG_ENABLED
+ exit_ok = true;
+#endif // DEBUG_ENABLED
+ OPCODE_BREAK;
+ }
+
OPCODE(OPCODE_RETURN_TYPED_NATIVE) {
CHECK_SPACE(3);
GET_VARIANT_PTR(r, 0);
diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp
index 4ffb4bd9d1..a601cc4993 100644
--- a/modules/gdscript/gdscript_warning.cpp
+++ b/modules/gdscript/gdscript_warning.cpp
@@ -61,10 +61,13 @@ String GDScriptWarning::get_message() const {
return vformat(R"(The signal "%s" is declared but never explicitly used in the class.)", symbols[0]);
case SHADOWED_VARIABLE:
CHECK_SYMBOLS(4);
- return vformat(R"(The local %s "%s" is shadowing an already-declared %s at line %s.)", symbols[0], symbols[1], symbols[2], symbols[3]);
+ return vformat(R"(The local %s "%s" is shadowing an already-declared %s at line %s in the current class.)", symbols[0], symbols[1], symbols[2], symbols[3]);
case SHADOWED_VARIABLE_BASE_CLASS:
CHECK_SYMBOLS(4);
- return vformat(R"(The local %s "%s" is shadowing an already-declared %s at the base class "%s".)", symbols[0], symbols[1], symbols[2], symbols[3]);
+ if (symbols.size() > 4) {
+ return vformat(R"(The local %s "%s" is shadowing an already-declared %s at line %s in the base class "%s".)", symbols[0], symbols[1], symbols[2], symbols[3], symbols[4]);
+ }
+ return vformat(R"(The local %s "%s" is shadowing an already-declared %s in the base class "%s".)", symbols[0], symbols[1], symbols[2], symbols[3]);
case SHADOWED_GLOBAL_IDENTIFIER:
CHECK_SYMBOLS(3);
return vformat(R"(The %s "%s" has the same name as a %s.)", symbols[0], symbols[1], symbols[2]);
diff --git a/modules/gdscript/gdscript_warning.h b/modules/gdscript/gdscript_warning.h
index ffcf00a830..99e9b30af5 100644
--- a/modules/gdscript/gdscript_warning.h
+++ b/modules/gdscript/gdscript_warning.h
@@ -53,8 +53,8 @@ public:
UNUSED_PRIVATE_CLASS_VARIABLE, // Class variable is declared private ("_" prefix) but never used in the class.
UNUSED_PARAMETER, // Function parameter is never used.
UNUSED_SIGNAL, // Signal is defined but never explicitly used in the class.
- SHADOWED_VARIABLE, // Variable name shadowed by other variable in same class.
- SHADOWED_VARIABLE_BASE_CLASS, // Variable name shadowed by other variable in some base class.
+ SHADOWED_VARIABLE, // A local variable/constant shadows a current class member.
+ SHADOWED_VARIABLE_BASE_CLASS, // A local variable/constant shadows a base class member.
SHADOWED_GLOBAL_IDENTIFIER, // A global class or function has the same name as variable.
UNREACHABLE_CODE, // Code after a return statement.
UNREACHABLE_PATTERN, // Pattern in a match statement after a catch all pattern (wildcard or bind).
diff --git a/modules/gdscript/language_server/gdscript_extend_parser.h b/modules/gdscript/language_server/gdscript_extend_parser.h
index a808f19e5b..239f7d9f43 100644
--- a/modules/gdscript/language_server/gdscript_extend_parser.h
+++ b/modules/gdscript/language_server/gdscript_extend_parser.h
@@ -37,10 +37,10 @@
#include "core/variant/variant.h"
#ifndef LINE_NUMBER_TO_INDEX
-#define LINE_NUMBER_TO_INDEX(p_line) ((p_line)-1)
+#define LINE_NUMBER_TO_INDEX(p_line) ((p_line) - 1)
#endif
#ifndef COLUMN_NUMBER_TO_INDEX
-#define COLUMN_NUMBER_TO_INDEX(p_column) ((p_column)-1)
+#define COLUMN_NUMBER_TO_INDEX(p_column) ((p_column) - 1)
#endif
#ifndef SYMBOL_SEPERATOR
diff --git a/modules/gdscript/language_server/gdscript_language_server.cpp b/modules/gdscript/language_server/gdscript_language_server.cpp
index 3df26ea576..731988148d 100644
--- a/modules/gdscript/language_server/gdscript_language_server.cpp
+++ b/modules/gdscript/language_server/gdscript_language_server.cpp
@@ -39,6 +39,7 @@
int GDScriptLanguageServer::port_override = -1;
GDScriptLanguageServer::GDScriptLanguageServer() {
+ // TODO: Move to editor_settings.cpp
_EDITOR_DEF("network/language_server/remote_host", host);
_EDITOR_DEF("network/language_server/remote_port", port);
_EDITOR_DEF("network/language_server/enable_smart_resolve", true);
diff --git a/modules/gdscript/language_server/gdscript_text_document.cpp b/modules/gdscript/language_server/gdscript_text_document.cpp
index fa5f279db9..06e9775360 100644
--- a/modules/gdscript/language_server/gdscript_text_document.cpp
+++ b/modules/gdscript/language_server/gdscript_text_document.cpp
@@ -229,19 +229,6 @@ Array GDScriptTextDocument::completion(const Dictionary &p_params) {
arr[i] = item.to_json();
i++;
}
- } else if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) {
- arr = native_member_completions.duplicate();
-
- for (KeyValue<String, ExtendGDScriptParser *> &E : GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts) {
- ExtendGDScriptParser *scr = E.value;
- const Array &items = scr->get_member_completions();
-
- const int start_size = arr.size();
- arr.resize(start_size + items.size());
- for (int i = start_size; i < arr.size(); i++) {
- arr[i] = items[i - start_size];
- }
- }
}
return arr;
}
@@ -485,8 +472,6 @@ GDScriptTextDocument::GDScriptTextDocument() {
void GDScriptTextDocument::sync_script_content(const String &p_path, const String &p_content) {
String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(p_path);
GDScriptLanguageProtocol::get_singleton()->get_workspace()->parse_script(path, p_content);
-
- EditorFileSystem::get_singleton()->update_file(path);
}
void GDScriptTextDocument::show_native_symbol_in_editor(const String &p_symbol_id) {
diff --git a/modules/gdscript/language_server/godot_lsp.h b/modules/gdscript/language_server/godot_lsp.h
index bdf339f5fe..6e19cd7a23 100644
--- a/modules/gdscript/language_server/godot_lsp.h
+++ b/modules/gdscript/language_server/godot_lsp.h
@@ -958,28 +958,30 @@ struct CompletionItem {
/**
* A string that should be used when comparing this item
- * with other items. When `falsy` the label is used.
+ * with other items. When omitted the label is used
+ * as the filter text for this item.
*/
String sortText;
/**
* A string that should be used when filtering a set of
- * completion items. When `falsy` the label is used.
+ * completion items. When omitted the label is used as the
+ * filter text for this item.
*/
String filterText;
/**
* A string that should be inserted into a document when selecting
- * this completion. When `falsy` the label is used.
+ * this completion. When omitted the label is used as the insert text
+ * for this item.
*
* The `insertText` is subject to interpretation by the client side.
* Some tools might not take the string literally. For example
- * VS Code when code complete is requested in this example `con<cursor position>`
- * and a completion item with an `insertText` of `console` is provided it
- * will only insert `sole`. Therefore it is recommended to use `textEdit` instead
- * since it avoids additional client side interpretation.
- *
- * @deprecated Use textEdit instead.
+ * VS Code when code complete is requested in this example
+ * `con<cursor position>` and a completion item with an `insertText` of
+ * `console` is provided it will only insert `sole`. Therefore it is
+ * recommended to use `textEdit` instead since it avoids additional client
+ * side interpretation.
*/
String insertText;
@@ -1034,14 +1036,20 @@ struct CompletionItem {
dict["label"] = label;
dict["kind"] = kind;
dict["data"] = data;
- dict["insertText"] = insertText;
+ if (!insertText.is_empty()) {
+ dict["insertText"] = insertText;
+ }
if (resolved) {
dict["detail"] = detail;
dict["documentation"] = documentation.to_json();
dict["deprecated"] = deprecated;
dict["preselect"] = preselect;
- dict["sortText"] = sortText;
- dict["filterText"] = filterText;
+ if (!sortText.is_empty()) {
+ dict["sortText"] = sortText;
+ }
+ if (!filterText.is_empty()) {
+ dict["filterText"] = filterText;
+ }
if (commitCharacters.size()) {
dict["commitCharacters"] = commitCharacters;
}
diff --git a/modules/gdscript/tests/scripts/.editorconfig b/modules/gdscript/tests/scripts/.editorconfig
new file mode 100644
index 0000000000..34fff8d1de
--- /dev/null
+++ b/modules/gdscript/tests/scripts/.editorconfig
@@ -0,0 +1,10 @@
+# Some tests handle invalid syntax deliberately; exclude relevant attributes.
+
+[parser/features/mixed_indentation_on_blank_lines.gd]
+trim_trailing_whitespace = false
+
+[parser/warnings/empty_file_newline.notest.gd]
+insert_final_newline = false
+
+[parser/warnings/empty_file_newline_comment.notest.gd]
+insert_final_newline = false
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_dictionary.gd b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_dictionary.gd
new file mode 100644
index 0000000000..c180cca03c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_dictionary.gd
@@ -0,0 +1,3 @@
+func test():
+ for key: int in { "a": 1 }:
+ print(key)
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_dictionary.out b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_dictionary.out
new file mode 100644
index 0000000000..8530783673
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_dictionary.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot include a value of type "String" as "int".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assign_differently_typed.gd b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assign_differently_typed.gd
new file mode 100644
index 0000000000..75d1b7fe62
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assign_differently_typed.gd
@@ -0,0 +1,4 @@
+func test():
+ var differently: Dictionary[float, float] = { 1.0: 0.0 }
+ var typed: Dictionary[int, int] = differently
+ print('not ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assign_differently_typed.out b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assign_differently_typed.out
new file mode 100644
index 0000000000..e05d4be8c9
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assign_differently_typed.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot assign a value of type Dictionary[float, float] to variable "typed" with specified type Dictionary[int, int].
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assignment.gd b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assignment.gd
new file mode 100644
index 0000000000..e0af71823a
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assignment.gd
@@ -0,0 +1,2 @@
+func test():
+ const dict: Dictionary[int, int] = { "Hello": "World" }
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assignment.out b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assignment.out
new file mode 100644
index 0000000000..8530783673
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assignment.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot include a value of type "String" as "int".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_init_with_unconvertible_in_literal.gd b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_init_with_unconvertible_in_literal.gd
new file mode 100644
index 0000000000..814ba12aef
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_init_with_unconvertible_in_literal.gd
@@ -0,0 +1,4 @@
+func test():
+ var unconvertible := 1
+ var typed: Dictionary[Object, Object] = { unconvertible: unconvertible }
+ print('not ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_init_with_unconvertible_in_literal.out b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_init_with_unconvertible_in_literal.out
new file mode 100644
index 0000000000..9d6c9d9144
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_init_with_unconvertible_in_literal.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot have a key of type "int" in a dictionary of type "Dictionary[Object, Object]".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_pass_differently_to_typed.gd b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_pass_differently_to_typed.gd
new file mode 100644
index 0000000000..73d8ce2b96
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_pass_differently_to_typed.gd
@@ -0,0 +1,7 @@
+func expect_typed(typed: Dictionary[int, int]):
+ print(typed.size())
+
+func test():
+ var differently: Dictionary[float, float] = { 1.0: 0.0 }
+ expect_typed(differently)
+ print('not ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_pass_differently_to_typed.out b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_pass_differently_to_typed.out
new file mode 100644
index 0000000000..302109cf8a
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_pass_differently_to_typed.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Invalid argument for "expect_typed()" function: argument 1 should be "Dictionary[int, int]" but is "Dictionary[float, float]".
diff --git a/modules/gdscript/tests/scripts/analyzer/features/class_inference_is_weak.out b/modules/gdscript/tests/scripts/analyzer/features/class_inference_is_weak.out
index 94e2ec2af8..fb616f1e94 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/class_inference_is_weak.out
+++ b/modules/gdscript/tests/scripts/analyzer/features/class_inference_is_weak.out
@@ -1,2 +1,2 @@
GDTEST_OK
-0
+0.0
diff --git a/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd b/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd
index 5318d11f33..e91c7386fe 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd
@@ -7,17 +7,17 @@ const const_packed_ints: PackedFloat64Array = [52]
func test():
Utils.check(typeof(const_float_int) == TYPE_FLOAT)
- Utils.check(str(const_float_int) == '19')
+ Utils.check(str(const_float_int) == '19.0')
Utils.check(typeof(const_float_plus) == TYPE_FLOAT)
- Utils.check(str(const_float_plus) == '34')
+ Utils.check(str(const_float_plus) == '34.0')
Utils.check(typeof(const_float_cast) == TYPE_FLOAT)
- Utils.check(str(const_float_cast) == '76')
+ Utils.check(str(const_float_cast) == '76.0')
Utils.check(typeof(const_packed_empty) == TYPE_PACKED_FLOAT64_ARRAY)
Utils.check(str(const_packed_empty) == '[]')
Utils.check(typeof(const_packed_ints) == TYPE_PACKED_FLOAT64_ARRAY)
- Utils.check(str(const_packed_ints) == '[52]')
+ Utils.check(str(const_packed_ints) == '[52.0]')
Utils.check(typeof(const_packed_ints[0]) == TYPE_FLOAT)
- Utils.check(str(const_packed_ints[0]) == '52')
+ Utils.check(str(const_packed_ints[0]) == '52.0')
print('ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_inner_class_as_constant.out b/modules/gdscript/tests/scripts/analyzer/features/external_inner_class_as_constant.out
index 15666c46ad..abf11548cb 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/external_inner_class_as_constant.out
+++ b/modules/gdscript/tests/scripts/analyzer/features/external_inner_class_as_constant.out
@@ -1,2 +1,2 @@
GDTEST_OK
-4
+4.0
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 fe0274c27b..eb53d0a700 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd
@@ -54,39 +54,39 @@ func test():
untyped_basic.push_back(430.0)
inferred_basic.push_back(263.0)
typed_basic.push_back(518.0)
- Utils.check(str(empty_floats) == '[705, 430, 263, 518]')
- Utils.check(str(untyped_basic) == '[705, 430, 263, 518]')
- Utils.check(str(inferred_basic) == '[705, 430, 263, 518]')
- Utils.check(str(typed_basic) == '[705, 430, 263, 518]')
+ Utils.check(str(empty_floats) == '[705.0, 430.0, 263.0, 518.0]')
+ Utils.check(str(untyped_basic) == '[705.0, 430.0, 263.0, 518.0]')
+ Utils.check(str(inferred_basic) == '[705.0, 430.0, 263.0, 518.0]')
+ Utils.check(str(typed_basic) == '[705.0, 430.0, 263.0, 518.0]')
const constant_float := 950.0
const constant_int := 170
var typed_float := 954.0
var filled_floats: Array[float] = [constant_float, constant_int, typed_float, empty_floats[1] + empty_floats[2]]
- Utils.check(str(filled_floats) == '[950, 170, 954, 693]')
+ Utils.check(str(filled_floats) == '[950.0, 170.0, 954.0, 693.0]')
Utils.check(filled_floats.get_typed_builtin() == TYPE_FLOAT)
var casted_floats := [empty_floats[2] * 2] as Array[float]
- Utils.check(str(casted_floats) == '[526]')
+ Utils.check(str(casted_floats) == '[526.0]')
Utils.check(casted_floats.get_typed_builtin() == TYPE_FLOAT)
var returned_floats = (func () -> Array[float]: return [554]).call()
- Utils.check(str(returned_floats) == '[554]')
+ Utils.check(str(returned_floats) == '[554.0]')
Utils.check(returned_floats.get_typed_builtin() == TYPE_FLOAT)
var passed_floats = floats_identity([663.0 if randf() > 0.5 else 663.0])
- Utils.check(str(passed_floats) == '[663]')
+ Utils.check(str(passed_floats) == '[663.0]')
Utils.check(passed_floats.get_typed_builtin() == TYPE_FLOAT)
var default_floats = (func (floats: Array[float] = [364.0]): return floats).call()
- Utils.check(str(default_floats) == '[364]')
+ Utils.check(str(default_floats) == '[364.0]')
Utils.check(default_floats.get_typed_builtin() == TYPE_FLOAT)
var typed_int := 556
var converted_floats: Array[float] = [typed_int]
converted_floats.push_back(498)
- Utils.check(str(converted_floats) == '[556, 498]')
+ Utils.check(str(converted_floats) == '[556.0, 498.0]')
Utils.check(converted_floats.get_typed_builtin() == TYPE_FLOAT)
@@ -95,7 +95,7 @@ func test():
Utils.check(constant_basic.get_typed_builtin() == TYPE_NIL)
const constant_floats: Array[float] = [constant_float - constant_basic[0] - constant_int]
- Utils.check(str(constant_floats) == '[552]')
+ Utils.check(str(constant_floats) == '[552.0]')
Utils.check(constant_floats.get_typed_builtin() == TYPE_FLOAT)
@@ -103,15 +103,15 @@ func test():
untyped_basic = source_floats
var destination_floats: Array[float] = untyped_basic
destination_floats[0] -= 0.74
- Utils.check(str(source_floats) == '[999]')
- Utils.check(str(untyped_basic) == '[999]')
- Utils.check(str(destination_floats) == '[999]')
+ Utils.check(str(source_floats) == '[999.0]')
+ Utils.check(str(untyped_basic) == '[999.0]')
+ Utils.check(str(destination_floats) == '[999.0]')
Utils.check(destination_floats.get_typed_builtin() == TYPE_FLOAT)
var duplicated_floats := empty_floats.duplicate().slice(2, 3)
duplicated_floats[0] *= 3
- Utils.check(str(duplicated_floats) == '[789]')
+ Utils.check(str(duplicated_floats) == '[789.0]')
Utils.check(duplicated_floats.get_typed_builtin() == TYPE_FLOAT)
diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_as_default_parameter.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_as_default_parameter.gd
new file mode 100644
index 0000000000..65f5e7da07
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_as_default_parameter.gd
@@ -0,0 +1,20 @@
+func print_untyped(dictionary = { 0: 1 }) -> void:
+ print(dictionary)
+ print(dictionary.get_typed_key_builtin())
+ print(dictionary.get_typed_value_builtin())
+
+func print_inferred(dictionary := { 2: 3 }) -> void:
+ print(dictionary)
+ print(dictionary.get_typed_key_builtin())
+ print(dictionary.get_typed_value_builtin())
+
+func print_typed(dictionary: Dictionary[int, int] = { 4: 5 }) -> void:
+ print(dictionary)
+ print(dictionary.get_typed_key_builtin())
+ print(dictionary.get_typed_value_builtin())
+
+func test():
+ print_untyped()
+ print_inferred()
+ print_typed()
+ print('ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_as_default_parameter.out b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_as_default_parameter.out
new file mode 100644
index 0000000000..c31561bee3
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_as_default_parameter.out
@@ -0,0 +1,11 @@
+GDTEST_OK
+{ 0: 1 }
+0
+0
+{ 2: 3 }
+0
+0
+{ 4: 5 }
+2
+2
+ok
diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_inferred_access_isnt_constant.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_inferred_access_isnt_constant.gd
new file mode 100644
index 0000000000..0aa3de2c4a
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_inferred_access_isnt_constant.gd
@@ -0,0 +1,4 @@
+func test():
+ var dict := { 0: 0 }
+ dict[0] = 1
+ print(dict[0])
diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_inferred_access_isnt_constant.out b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_inferred_access_isnt_constant.out
new file mode 100644
index 0000000000..a7f1357bb2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_inferred_access_isnt_constant.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+1
diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.gd
new file mode 100644
index 0000000000..c9ab368f45
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.gd
@@ -0,0 +1,214 @@
+class A: pass
+class B extends A: pass
+
+enum E { E0 = 391, E1 = 193 }
+
+func floats_identity(floats: Dictionary[float, float]): return floats
+
+class Members:
+ var one: Dictionary[int, int] = { 104: 401 }
+ var two: Dictionary[int, int] = one
+
+ func check_passing() -> bool:
+ Utils.check(str(one) == '{ 104: 401 }')
+ Utils.check(str(two) == '{ 104: 401 }')
+ two[582] = 285
+ Utils.check(str(one) == '{ 104: 401, 582: 285 }')
+ Utils.check(str(two) == '{ 104: 401, 582: 285 }')
+ two = { 486: 684 }
+ Utils.check(str(one) == '{ 104: 401, 582: 285 }')
+ Utils.check(str(two) == '{ 486: 684 }')
+ return true
+
+
+@warning_ignore("unsafe_method_access")
+@warning_ignore("assert_always_true")
+@warning_ignore("return_value_discarded")
+func test():
+ var untyped_basic = { 459: 954 }
+ Utils.check(str(untyped_basic) == '{ 459: 954 }')
+ Utils.check(untyped_basic.get_typed_key_builtin() == TYPE_NIL)
+ Utils.check(untyped_basic.get_typed_value_builtin() == TYPE_NIL)
+
+ var inferred_basic := { 366: 663 }
+ Utils.check(str(inferred_basic) == '{ 366: 663 }')
+ Utils.check(inferred_basic.get_typed_key_builtin() == TYPE_NIL)
+ Utils.check(inferred_basic.get_typed_value_builtin() == TYPE_NIL)
+
+ var typed_basic: Dictionary = { 521: 125 }
+ Utils.check(str(typed_basic) == '{ 521: 125 }')
+ Utils.check(typed_basic.get_typed_key_builtin() == TYPE_NIL)
+ Utils.check(typed_basic.get_typed_value_builtin() == TYPE_NIL)
+
+
+ var empty_floats: Dictionary[float, float] = {}
+ Utils.check(str(empty_floats) == '{ }')
+ Utils.check(empty_floats.get_typed_key_builtin() == TYPE_FLOAT)
+ Utils.check(empty_floats.get_typed_value_builtin() == TYPE_FLOAT)
+
+ untyped_basic = empty_floats
+ Utils.check(untyped_basic.get_typed_key_builtin() == TYPE_FLOAT)
+ Utils.check(untyped_basic.get_typed_value_builtin() == TYPE_FLOAT)
+
+ inferred_basic = empty_floats
+ Utils.check(inferred_basic.get_typed_key_builtin() == TYPE_FLOAT)
+ Utils.check(inferred_basic.get_typed_value_builtin() == TYPE_FLOAT)
+
+ typed_basic = empty_floats
+ Utils.check(typed_basic.get_typed_key_builtin() == TYPE_FLOAT)
+ Utils.check(typed_basic.get_typed_value_builtin() == TYPE_FLOAT)
+
+ empty_floats[705.0] = 507.0
+ untyped_basic[430.0] = 34.0
+ inferred_basic[263.0] = 362.0
+ typed_basic[518.0] = 815.0
+ Utils.check(str(empty_floats) == '{ 705.0: 507.0, 430.0: 34.0, 263.0: 362.0, 518.0: 815.0 }')
+ Utils.check(str(untyped_basic) == '{ 705.0: 507.0, 430.0: 34.0, 263.0: 362.0, 518.0: 815.0 }')
+ Utils.check(str(inferred_basic) == '{ 705.0: 507.0, 430.0: 34.0, 263.0: 362.0, 518.0: 815.0 }')
+ Utils.check(str(typed_basic) == '{ 705.0: 507.0, 430.0: 34.0, 263.0: 362.0, 518.0: 815.0 }')
+
+
+ const constant_float := 950.0
+ const constant_int := 170
+ var typed_float := 954.0
+ var filled_floats: Dictionary[float, float] = { constant_float: constant_int, typed_float: empty_floats[430.0] + empty_floats[263.0] }
+ Utils.check(str(filled_floats) == '{ 950.0: 170.0, 954.0: 396.0 }')
+ Utils.check(filled_floats.get_typed_key_builtin() == TYPE_FLOAT)
+ Utils.check(filled_floats.get_typed_value_builtin() == TYPE_FLOAT)
+
+ var casted_floats := { empty_floats[263.0] * 2: empty_floats[263.0] / 2 } as Dictionary[float, float]
+ Utils.check(str(casted_floats) == '{ 724.0: 181.0 }')
+ Utils.check(casted_floats.get_typed_key_builtin() == TYPE_FLOAT)
+ Utils.check(casted_floats.get_typed_value_builtin() == TYPE_FLOAT)
+
+ var returned_floats = (func () -> Dictionary[float, float]: return { 554: 455 }).call()
+ Utils.check(str(returned_floats) == '{ 554.0: 455.0 }')
+ Utils.check(returned_floats.get_typed_key_builtin() == TYPE_FLOAT)
+ Utils.check(returned_floats.get_typed_value_builtin() == TYPE_FLOAT)
+
+ var passed_floats = floats_identity({ 663.0 if randf() > 0.5 else 663.0: 366.0 if randf() <= 0.5 else 366.0 })
+ Utils.check(str(passed_floats) == '{ 663.0: 366.0 }')
+ Utils.check(passed_floats.get_typed_key_builtin() == TYPE_FLOAT)
+ Utils.check(passed_floats.get_typed_value_builtin() == TYPE_FLOAT)
+
+ var default_floats = (func (floats: Dictionary[float, float] = { 364.0: 463.0 }): return floats).call()
+ Utils.check(str(default_floats) == '{ 364.0: 463.0 }')
+ Utils.check(default_floats.get_typed_key_builtin() == TYPE_FLOAT)
+ Utils.check(default_floats.get_typed_value_builtin() == TYPE_FLOAT)
+
+ var typed_int := 556
+ var converted_floats: Dictionary[float, float] = { typed_int: typed_int }
+ converted_floats[498.0] = 894
+ Utils.check(str(converted_floats) == '{ 556.0: 556.0, 498.0: 894.0 }')
+ Utils.check(converted_floats.get_typed_key_builtin() == TYPE_FLOAT)
+ Utils.check(converted_floats.get_typed_value_builtin() == TYPE_FLOAT)
+
+
+ const constant_basic = { 228: 822 }
+ Utils.check(str(constant_basic) == '{ 228: 822 }')
+ Utils.check(constant_basic.get_typed_key_builtin() == TYPE_NIL)
+ Utils.check(constant_basic.get_typed_value_builtin() == TYPE_NIL)
+
+ const constant_floats: Dictionary[float, float] = { constant_float - constant_basic[228] - constant_int: constant_float + constant_basic[228] + constant_int }
+ Utils.check(str(constant_floats) == '{ -42.0: 1942.0 }')
+ Utils.check(constant_floats.get_typed_key_builtin() == TYPE_FLOAT)
+ Utils.check(constant_floats.get_typed_value_builtin() == TYPE_FLOAT)
+
+
+ var source_floats: Dictionary[float, float] = { 999.74: 47.999 }
+ untyped_basic = source_floats
+ var destination_floats: Dictionary[float, float] = untyped_basic
+ destination_floats[999.74] -= 0.999
+ Utils.check(str(source_floats) == '{ 999.74: 47.0 }')
+ Utils.check(str(untyped_basic) == '{ 999.74: 47.0 }')
+ Utils.check(str(destination_floats) == '{ 999.74: 47.0 }')
+ Utils.check(destination_floats.get_typed_key_builtin() == TYPE_FLOAT)
+ Utils.check(destination_floats.get_typed_value_builtin() == TYPE_FLOAT)
+
+
+ var duplicated_floats := empty_floats.duplicate()
+ duplicated_floats.erase(705.0)
+ duplicated_floats.erase(430.0)
+ duplicated_floats.erase(518.0)
+ duplicated_floats[263.0] *= 3
+ Utils.check(str(duplicated_floats) == '{ 263.0: 1086.0 }')
+ Utils.check(duplicated_floats.get_typed_key_builtin() == TYPE_FLOAT)
+ Utils.check(duplicated_floats.get_typed_value_builtin() == TYPE_FLOAT)
+
+
+ var b_objects: Dictionary[int, B] = { 0: B.new(), 1: B.new() as A, 2: null }
+ Utils.check(b_objects.size() == 3)
+ Utils.check(b_objects.get_typed_value_builtin() == TYPE_OBJECT)
+ Utils.check(b_objects.get_typed_value_script() == B)
+
+ var a_objects: Dictionary[int, A] = { 0: A.new(), 1: B.new(), 2: null, 3: b_objects[0] }
+ Utils.check(a_objects.size() == 4)
+ Utils.check(a_objects.get_typed_value_builtin() == TYPE_OBJECT)
+ Utils.check(a_objects.get_typed_value_script() == A)
+
+ var a_passed = (func check_a_passing(p_objects: Dictionary[int, A]): return p_objects.size()).call(a_objects)
+ Utils.check(a_passed == 4)
+
+ var b_passed = (func check_b_passing(basic: Dictionary): return basic[0] != null).call(b_objects)
+ Utils.check(b_passed == true)
+
+
+ var empty_strings: Dictionary[String, String] = {}
+ var empty_bools: Dictionary[bool, bool] = {}
+ var empty_basic_one := {}
+ var empty_basic_two := {}
+ Utils.check(empty_strings == empty_bools)
+ Utils.check(empty_basic_one == empty_basic_two)
+ Utils.check(empty_strings.hash() == empty_bools.hash())
+ Utils.check(empty_basic_one.hash() == empty_basic_two.hash())
+
+
+ var assign_source: Dictionary[int, int] = { 527: 725 }
+ var assign_target: Dictionary[int, int] = {}
+ assign_target.assign(assign_source)
+ Utils.check(str(assign_source) == '{ 527: 725 }')
+ Utils.check(str(assign_target) == '{ 527: 725 }')
+ assign_source[657] = 756
+ Utils.check(str(assign_source) == '{ 527: 725, 657: 756 }')
+ Utils.check(str(assign_target) == '{ 527: 725 }')
+
+
+ var defaults_passed = (func check_defaults_passing(one: Dictionary[int, int] = {}, two := one):
+ one[887] = 788
+ two[198] = 891
+ Utils.check(str(one) == '{ 887: 788, 198: 891 }')
+ Utils.check(str(two) == '{ 887: 788, 198: 891 }')
+ two = {130: 31}
+ Utils.check(str(one) == '{ 887: 788, 198: 891 }')
+ Utils.check(str(two) == '{ 130: 31 }')
+ return true
+ ).call()
+ Utils.check(defaults_passed == true)
+
+
+ var members := Members.new()
+ var members_passed := members.check_passing()
+ Utils.check(members_passed == true)
+
+
+ var typed_enums: Dictionary[E, E] = {}
+ typed_enums[E.E0] = E.E1
+ Utils.check(str(typed_enums) == '{ 391: 193 }')
+ Utils.check(typed_enums.get_typed_key_builtin() == TYPE_INT)
+ Utils.check(typed_enums.get_typed_value_builtin() == TYPE_INT)
+
+ const const_enums: Dictionary[E, E] = {}
+ Utils.check(const_enums.get_typed_key_builtin() == TYPE_INT)
+ Utils.check(const_enums.get_typed_key_class_name() == &'')
+ Utils.check(const_enums.get_typed_value_builtin() == TYPE_INT)
+ Utils.check(const_enums.get_typed_value_class_name() == &'')
+
+
+ var a := A.new()
+ var b := B.new()
+ var typed_natives: Dictionary[RefCounted, RefCounted] = { a: b }
+ var typed_scripts = Dictionary(typed_natives, TYPE_OBJECT, "RefCounted", A, TYPE_OBJECT, "RefCounted", B)
+ Utils.check(typed_scripts[a] == b)
+
+
+ print('ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.out b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.out
new file mode 100644
index 0000000000..1b47ed10dc
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+ok
diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_with_custom_class.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_with_custom_class.gd
new file mode 100644
index 0000000000..f4a23ade14
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_with_custom_class.gd
@@ -0,0 +1,9 @@
+class Inner:
+ var prop = "Inner"
+
+var dict: Dictionary[int, Inner] = { 0: Inner.new() }
+
+
+func test():
+ var element: Inner = dict[0]
+ print(element.prop)
diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_with_custom_class.out b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_with_custom_class.out
new file mode 100644
index 0000000000..8f250d2632
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_with_custom_class.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+Inner
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage.out b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage.out
index 0e0d607831..cfe91e00bd 100644
--- a/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage.out
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage.out
@@ -6,6 +6,6 @@ GDTEST_OK
>> WARNING
>> Line: 5
>> SHADOWED_VARIABLE
->> The local variable "a" is shadowing an already-declared variable at line 1.
+>> The local variable "a" is shadowing an already-declared variable at line 1 in the current class.
1
2
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_initializer.out b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_initializer.out
index 228a510490..ae0f2d8b8b 100644
--- a/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_initializer.out
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_initializer.out
@@ -10,6 +10,6 @@ GDTEST_OK
>> WARNING
>> Line: 5
>> SHADOWED_VARIABLE
->> The local variable "a" is shadowing an already-declared variable at line 1.
+>> The local variable "a" is shadowing an already-declared variable at line 1 in the current class.
1
2
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_loop.out b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_loop.out
index 0d20e9f7a0..101d27df9d 100644
--- a/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_loop.out
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_loop.out
@@ -6,7 +6,7 @@ GDTEST_OK
>> WARNING
>> Line: 6
>> SHADOWED_VARIABLE
->> The local variable "a" is shadowing an already-declared variable at line 1.
+>> The local variable "a" is shadowing an already-declared variable at line 1 in the current class.
1
2
1
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/lambda_shadowing_arg.out b/modules/gdscript/tests/scripts/analyzer/warnings/lambda_shadowing_arg.out
index a98d80514c..5d059b9193 100644
--- a/modules/gdscript/tests/scripts/analyzer/warnings/lambda_shadowing_arg.out
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/lambda_shadowing_arg.out
@@ -2,5 +2,5 @@ GDTEST_OK
>> WARNING
>> Line: 4
>> SHADOWED_VARIABLE
->> The local function parameter "shadow" is shadowing an already-declared variable at line 1.
+>> The local function parameter "shadow" is shadowing an already-declared variable at line 1 in the current class.
shadow
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/shadowing_base.notest.gd b/modules/gdscript/tests/scripts/analyzer/warnings/shadowing_base.notest.gd
new file mode 100644
index 0000000000..5819246ded
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/shadowing_base.notest.gd
@@ -0,0 +1,7 @@
+class_name ShadowingBase
+
+const base_const_member = 1
+var base_variable_member
+
+func base_function_member():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd b/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd
index 939e787ea5..6a16ae6bcc 100644
--- a/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd
@@ -1,4 +1,5 @@
class_name ShadowedClass
+extends ShadowingBase
var member: int = 0
@@ -7,6 +8,7 @@ var print_debug := 'print_debug'
var print := 'print'
@warning_ignore("unused_variable")
+@warning_ignore("unused_local_constant")
func test():
var Array := 'Array'
var Node := 'Node'
@@ -15,5 +17,8 @@ func test():
var member := 'member'
var reference := 'reference'
var ShadowedClass := 'ShadowedClass'
+ var base_variable_member
+ const base_function_member = 1
+ var base_const_member
print('warn')
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out b/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out
index 8297eed4b8..075f5d3225 100644
--- a/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out
@@ -1,34 +1,46 @@
GDTEST_OK
>> WARNING
->> Line: 5
+>> Line: 6
>> SHADOWED_GLOBAL_IDENTIFIER
>> The variable "print_debug" has the same name as a built-in function.
>> WARNING
->> Line: 11
+>> Line: 13
>> SHADOWED_GLOBAL_IDENTIFIER
>> The variable "Array" has the same name as a built-in type.
>> WARNING
->> Line: 12
+>> Line: 14
>> SHADOWED_GLOBAL_IDENTIFIER
>> The variable "Node" has the same name as a native class.
>> WARNING
->> Line: 13
+>> Line: 15
>> SHADOWED_GLOBAL_IDENTIFIER
>> The variable "is_same" has the same name as a built-in function.
>> WARNING
->> Line: 14
+>> Line: 16
>> SHADOWED_GLOBAL_IDENTIFIER
>> The variable "sqrt" has the same name as a built-in function.
>> WARNING
->> Line: 15
+>> Line: 17
>> SHADOWED_VARIABLE
->> The local variable "member" is shadowing an already-declared variable at line 3.
+>> The local variable "member" is shadowing an already-declared variable at line 4 in the current class.
>> WARNING
->> Line: 16
+>> Line: 18
>> SHADOWED_VARIABLE_BASE_CLASS
->> The local variable "reference" is shadowing an already-declared method at the base class "RefCounted".
+>> The local variable "reference" is shadowing an already-declared method in the base class "RefCounted".
>> WARNING
->> Line: 17
+>> Line: 19
>> SHADOWED_GLOBAL_IDENTIFIER
>> The variable "ShadowedClass" has the same name as a global class defined in "shadowning.gd".
+>> WARNING
+>> Line: 20
+>> SHADOWED_VARIABLE_BASE_CLASS
+>> The local variable "base_variable_member" is shadowing an already-declared variable at line 4 in the base class "ShadowingBase".
+>> WARNING
+>> Line: 21
+>> SHADOWED_VARIABLE_BASE_CLASS
+>> The local constant "base_function_member" is shadowing an already-declared function at line 6 in the base class "ShadowingBase".
+>> WARNING
+>> Line: 22
+>> SHADOWED_VARIABLE_BASE_CLASS
+>> The local variable "base_const_member" is shadowing an already-declared constant at line 3 in the base class "ShadowingBase".
warn
diff --git a/modules/gdscript/tests/scripts/completion/argument_options/argument_options.tscn b/modules/gdscript/tests/scripts/completion/argument_options/argument_options.tscn
index d3dea6b12b..082c87e708 100644
--- a/modules/gdscript/tests/scripts/completion/argument_options/argument_options.tscn
+++ b/modules/gdscript/tests/scripts/completion/argument_options/argument_options.tscn
@@ -1,3 +1,16 @@
[gd_scene load_steps=1 format=3 uid="uid://dl28pdkxcjvym"]
+[sub_resource type="Animation" id="Animation_d1pub"]
+resource_name = "bounce"
+
+[sub_resource type="AnimationLibrary" id="AnimationLibrary_gs7mj"]
+_data = {
+"bounce": SubResource("Animation_d1pub")
+}
+
[node name="GetNode" type="Node"]
+
+[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
+libraries = {
+"": SubResource("AnimationLibrary_gs7mj")
+}
diff --git a/modules/gdscript/tests/scripts/completion/argument_options/play_inferred.cfg b/modules/gdscript/tests/scripts/completion/argument_options/play_inferred.cfg
new file mode 100644
index 0000000000..ca108a18c2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/argument_options/play_inferred.cfg
@@ -0,0 +1,6 @@
+[input]
+scene="res://completion/argument_options/argument_options.tscn"
+[output]
+include=[
+ {"display": "\"bounce\""},
+]
diff --git a/modules/gdscript/tests/scripts/completion/argument_options/play_inferred.gd b/modules/gdscript/tests/scripts/completion/argument_options/play_inferred.gd
new file mode 100644
index 0000000000..abeadbe5ee
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/argument_options/play_inferred.gd
@@ -0,0 +1,5 @@
+@onready var anim := $AnimationPlayer
+
+func test():
+ anim.play(➡)
+ pass
diff --git a/modules/gdscript/tests/scripts/completion/argument_options/play_typed.cfg b/modules/gdscript/tests/scripts/completion/argument_options/play_typed.cfg
new file mode 100644
index 0000000000..ca108a18c2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/argument_options/play_typed.cfg
@@ -0,0 +1,6 @@
+[input]
+scene="res://completion/argument_options/argument_options.tscn"
+[output]
+include=[
+ {"display": "\"bounce\""},
+]
diff --git a/modules/gdscript/tests/scripts/completion/argument_options/play_typed.gd b/modules/gdscript/tests/scripts/completion/argument_options/play_typed.gd
new file mode 100644
index 0000000000..d11f81e985
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/argument_options/play_typed.gd
@@ -0,0 +1,5 @@
+@onready var anim: AnimationPlayer = $AnimationPlayer
+
+func test():
+ anim.play(➡)
+ pass
diff --git a/modules/gdscript/tests/scripts/completion/argument_options/play_untyped.cfg b/modules/gdscript/tests/scripts/completion/argument_options/play_untyped.cfg
new file mode 100644
index 0000000000..ca108a18c2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/argument_options/play_untyped.cfg
@@ -0,0 +1,6 @@
+[input]
+scene="res://completion/argument_options/argument_options.tscn"
+[output]
+include=[
+ {"display": "\"bounce\""},
+]
diff --git a/modules/gdscript/tests/scripts/completion/argument_options/play_untyped.gd b/modules/gdscript/tests/scripts/completion/argument_options/play_untyped.gd
new file mode 100644
index 0000000000..4ddfd21ac6
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/argument_options/play_untyped.gd
@@ -0,0 +1,5 @@
+@onready var anim = $AnimationPlayer
+
+func test():
+ anim.play(➡)
+ pass
diff --git a/modules/gdscript/tests/scripts/completion/get_node/literal_scene/dollar_unique.cfg b/modules/gdscript/tests/scripts/completion/get_node/literal_scene/dollar_unique.cfg
new file mode 100644
index 0000000000..36c150f6e3
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/get_node/literal_scene/dollar_unique.cfg
@@ -0,0 +1,9 @@
+[input]
+scene="res://completion/get_node/get_node.tscn"
+[output]
+include=[
+ {"display": "%UniqueA"},
+]
+exclude=[
+ {"display": "\"%UniqueA\""},
+]
diff --git a/modules/gdscript/tests/scripts/completion/get_node/literal_scene/dollar_unique.gd b/modules/gdscript/tests/scripts/completion/get_node/literal_scene/dollar_unique.gd
new file mode 100644
index 0000000000..def050e938
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/get_node/literal_scene/dollar_unique.gd
@@ -0,0 +1,5 @@
+extends Node
+
+func a():
+ $➡
+ pass
diff --git a/modules/gdscript/tests/scripts/parser/.editorconfig b/modules/gdscript/tests/scripts/parser/.editorconfig
deleted file mode 100644
index fa43b3ad78..0000000000
--- a/modules/gdscript/tests/scripts/parser/.editorconfig
+++ /dev/null
@@ -1,2 +0,0 @@
-[*.{gd,out}]
-trim_trailing_whitespace = false
diff --git a/modules/gdscript/tests/scripts/parser/errors/annotation_inapplicable.gd b/modules/gdscript/tests/scripts/parser/errors/annotation_inapplicable.gd
new file mode 100644
index 0000000000..cfacb6a010
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/annotation_inapplicable.gd
@@ -0,0 +1,3 @@
+@export
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/parser/errors/annotation_inapplicable.out b/modules/gdscript/tests/scripts/parser/errors/annotation_inapplicable.out
new file mode 100644
index 0000000000..ed677cd39a
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/annotation_inapplicable.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Annotation "@export" cannot be applied to a function.
diff --git a/modules/gdscript/tests/scripts/parser/errors/annotation_unrecognized.gd b/modules/gdscript/tests/scripts/parser/errors/annotation_unrecognized.gd
new file mode 100644
index 0000000000..a6b171a428
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/annotation_unrecognized.gd
@@ -0,0 +1,3 @@
+@hello_world
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/parser/errors/annotation_unrecognized.out b/modules/gdscript/tests/scripts/parser/errors/annotation_unrecognized.out
new file mode 100644
index 0000000000..540e66f15b
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/annotation_unrecognized.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Unrecognized annotation: "@hello_world".
diff --git a/modules/gdscript/tests/scripts/parser/errors/export_tool_button_requires_tool_mode.gd b/modules/gdscript/tests/scripts/parser/errors/export_tool_button_requires_tool_mode.gd
new file mode 100644
index 0000000000..48be5b2541
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/export_tool_button_requires_tool_mode.gd
@@ -0,0 +1 @@
+@export_tool_button("Click me!") var action
diff --git a/modules/gdscript/tests/scripts/parser/errors/export_tool_button_requires_tool_mode.out b/modules/gdscript/tests/scripts/parser/errors/export_tool_button_requires_tool_mode.out
new file mode 100644
index 0000000000..fb148308e4
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/export_tool_button_requires_tool_mode.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Tool buttons can only be used in tool scripts (add "@tool" to the top of the script).
diff --git a/modules/gdscript/tests/scripts/parser/features/export_arrays.out b/modules/gdscript/tests/scripts/parser/features/export_arrays.out
index f1522d096f..7201d8082d 100644
--- a/modules/gdscript/tests/scripts/parser/features/export_arrays.out
+++ b/modules/gdscript/tests/scripts/parser/features/export_arrays.out
@@ -80,21 +80,21 @@ var test_placeholder: Array
var test_placeholder_packed: PackedStringArray
hint=TYPE_STRING hint_string="<String>/<PLACEHOLDER_TEXT>:Placeholder" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_int: Array
- hint=TYPE_STRING hint_string="<int>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<int>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_int_packed_byte: PackedByteArray
- hint=TYPE_STRING hint_string="<int>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<int>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_int_packed32: PackedInt32Array
- hint=TYPE_STRING hint_string="<int>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<int>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_int_packed64: PackedInt64Array
- hint=TYPE_STRING hint_string="<int>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<int>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_int_float_step: Array
- hint=TYPE_STRING hint_string="<int>/<RANGE>:1,10,0.01" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<int>/<RANGE>:1.0,10.0,0.01" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_float: Array
- hint=TYPE_STRING hint_string="<float>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<float>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_float_packed32: PackedFloat32Array
- hint=TYPE_STRING hint_string="<float>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<float>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_float_packed64: PackedFloat64Array
- hint=TYPE_STRING hint_string="<float>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<float>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_exp_easing: Array
hint=TYPE_STRING hint_string="<float>/<EXP_EASING>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_exp_easing_packed32: PackedFloat32Array
@@ -126,14 +126,14 @@ var test_weak_packed_vector3_array: PackedVector3Array
var test_weak_packed_vector4_array: PackedVector4Array
hint=TYPE_STRING hint_string="<Vector4>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_weak_packed_byte_array: PackedByteArray
- hint=TYPE_STRING hint_string="<int>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<int>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_weak_packed_int32_array: PackedInt32Array
- hint=TYPE_STRING hint_string="<int>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<int>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_weak_packed_int64_array: PackedInt64Array
- hint=TYPE_STRING hint_string="<int>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<int>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_weak_packed_float32_array: PackedFloat32Array
- hint=TYPE_STRING hint_string="<float>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<float>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_weak_packed_float64_array: PackedFloat64Array
- hint=TYPE_STRING hint_string="<float>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<float>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_noalpha_weak_packed_color_array: PackedColorArray
hint=TYPE_STRING hint_string="<Color>/<COLOR_NO_ALPHA>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
diff --git a/modules/gdscript/tests/scripts/parser/features/export_variable.gd b/modules/gdscript/tests/scripts/parser/features/export_variable.gd
index 1e134d0e0e..8aa449f602 100644
--- a/modules/gdscript/tests/scripts/parser/features/export_variable.gd
+++ b/modules/gdscript/tests/scripts/parser/features/export_variable.gd
@@ -1,3 +1,4 @@
+@tool
class_name ExportVariableTest
extends Node
@@ -47,6 +48,10 @@ const PreloadedUnnamedClass = preload("./export_variable_unnamed.notest.gd")
@export_custom(PROPERTY_HINT_ENUM, "A,B,C") var test_export_custom_weak_int = 5
@export_custom(PROPERTY_HINT_ENUM, "A,B,C") var test_export_custom_hard_int: int = 6
+# `@export_tool_button`.
+@export_tool_button("Click me!") var test_tool_button_1: Callable
+@export_tool_button("Click me!", "ColorRect") var test_tool_button_2: Callable
+
func test():
for property in get_property_list():
if str(property.name).begins_with("test_"):
diff --git a/modules/gdscript/tests/scripts/parser/features/export_variable.out b/modules/gdscript/tests/scripts/parser/features/export_variable.out
index d10462bb8d..c0bf4d6e06 100644
--- a/modules/gdscript/tests/scripts/parser/features/export_variable.out
+++ b/modules/gdscript/tests/scripts/parser/features/export_variable.out
@@ -4,11 +4,11 @@ var test_weak_int: int = 1
var test_hard_int: int = 2
hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range: int = 100
- hint=RANGE hint_string="0,100" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=RANGE hint_string="0.0,100.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_step: int = 101
- hint=RANGE hint_string="0,100,1" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=RANGE hint_string="0.0,100.0,1.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_step_or_greater: int = 102
- hint=RANGE hint_string="0,100,1,or_greater" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=RANGE hint_string="0.0,100.0,1.0,or_greater" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_color: Color = Color(0, 0, 0, 1)
hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_color_no_alpha: Color = Color(0, 0, 0, 1)
@@ -55,3 +55,7 @@ var test_export_custom_weak_int: int = 5
hint=ENUM hint_string="A,B,C" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_export_custom_hard_int: int = 6
hint=ENUM hint_string="A,B,C" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+var test_tool_button_1: Callable = Callable()
+ hint=TOOL_BUTTON hint_string="Click me!" usage=EDITOR|SCRIPT_VARIABLE class_name=&""
+var test_tool_button_2: Callable = Callable()
+ hint=TOOL_BUTTON hint_string="Click me!,ColorRect" usage=EDITOR|SCRIPT_VARIABLE class_name=&""
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
index c5958365ec..d94cbe5556 100644
--- a/modules/gdscript/tests/scripts/parser/features/number_literals_with_sign.out
+++ b/modules/gdscript/tests/scripts/parser/features/number_literals_with_sign.out
@@ -13,4 +13,4 @@ true
0
-255
256
-2
+2.0
diff --git a/modules/gdscript/tests/scripts/parser/features/number_separators.out b/modules/gdscript/tests/scripts/parser/features/number_separators.out
index b0d2fd94fe..9407af9cd8 100644
--- a/modules/gdscript/tests/scripts/parser/features/number_separators.out
+++ b/modules/gdscript/tests/scripts/parser/features/number_separators.out
@@ -13,12 +13,12 @@ GDTEST_OK
---
-1234.4567
-1234.4567
--1234
--1234
+-1234.0
+-1234.0
0.4567
0.4567
---
--1234500
--1234500
--1234500
--1234500
+-1234500.0
+-1234500.0
+-1234500.0
+-1234500.0
diff --git a/modules/gdscript/tests/scripts/parser/features/operator_assign.out b/modules/gdscript/tests/scripts/parser/features/operator_assign.out
index b0cb63ef59..29910adf38 100644
--- a/modules/gdscript/tests/scripts/parser/features/operator_assign.out
+++ b/modules/gdscript/tests/scripts/parser/features/operator_assign.out
@@ -1,2 +1,2 @@
GDTEST_OK
-8
+8.0
diff --git a/modules/gdscript/tests/scripts/parser/features/typed_dictionaries.gd b/modules/gdscript/tests/scripts/parser/features/typed_dictionaries.gd
new file mode 100644
index 0000000000..57e6489484
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/typed_dictionaries.gd
@@ -0,0 +1,5 @@
+func test():
+ var my_dictionary: Dictionary[int, String] = { 1: "one", 2: "two", 3: "three" }
+ var inferred_dictionary := { 1: "one", 2: "two", 3: "three" } # This is Dictionary[int, String].
+ print(my_dictionary)
+ print(inferred_dictionary)
diff --git a/modules/gdscript/tests/scripts/parser/features/typed_dictionaries.out b/modules/gdscript/tests/scripts/parser/features/typed_dictionaries.out
new file mode 100644
index 0000000000..6021c338ee
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/typed_dictionaries.out
@@ -0,0 +1,3 @@
+GDTEST_OK
+{ 1: "one", 2: "two", 3: "three" }
+{ 1: "one", 2: "two", 3: "three" }
diff --git a/modules/gdscript/tests/scripts/parser/warnings/shadowed_constant.out b/modules/gdscript/tests/scripts/parser/warnings/shadowed_constant.out
index 75fa01f928..04df229f66 100644
--- a/modules/gdscript/tests/scripts/parser/warnings/shadowed_constant.out
+++ b/modules/gdscript/tests/scripts/parser/warnings/shadowed_constant.out
@@ -6,4 +6,4 @@ GDTEST_OK
>> WARNING
>> Line: 8
>> SHADOWED_VARIABLE
->> The local constant "TEST" is shadowing an already-declared constant at line 2.
+>> The local constant "TEST" is shadowing an already-declared constant at line 2 in the current class.
diff --git a/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_class.out b/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_class.out
index aab27e78e2..4a6964f503 100644
--- a/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_class.out
+++ b/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_class.out
@@ -6,4 +6,4 @@ GDTEST_OK
>> WARNING
>> Line: 8
>> SHADOWED_VARIABLE
->> The local variable "foo" is shadowing an already-declared variable at line 1.
+>> The local variable "foo" is shadowing an already-declared variable at line 1 in the current class.
diff --git a/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_function.out b/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_function.out
index e3cd358126..45fb771829 100644
--- a/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_function.out
+++ b/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_function.out
@@ -6,4 +6,4 @@ GDTEST_OK
>> WARNING
>> Line: 2
>> SHADOWED_VARIABLE
->> The local variable "test" is shadowing an already-declared function at line 1.
+>> The local variable "test" is shadowing an already-declared function at line 1 in the current class.
diff --git a/modules/gdscript/tests/scripts/project.godot b/modules/gdscript/tests/scripts/project.godot
index c9035ecab9..0757bec5c4 100644
--- a/modules/gdscript/tests/scripts/project.godot
+++ b/modules/gdscript/tests/scripts/project.godot
@@ -12,6 +12,6 @@ config/name="GDScript Integration Test Suite"
[input]
test_input_action={
-"deadzone": 0.5,
+"deadzone": 0.2,
"events": []
}
diff --git a/modules/gdscript/tests/scripts/runtime/errors/division_by_zero.gd b/modules/gdscript/tests/scripts/runtime/errors/division_by_zero.gd
new file mode 100644
index 0000000000..ace5397f40
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/division_by_zero.gd
@@ -0,0 +1,3 @@
+func test():
+ var integer: int = 1
+ integer /= 0
diff --git a/modules/gdscript/tests/scripts/runtime/errors/division_by_zero.out b/modules/gdscript/tests/scripts/runtime/errors/division_by_zero.out
new file mode 100644
index 0000000000..6a9d11cd77
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/division_by_zero.out
@@ -0,0 +1,6 @@
+GDTEST_RUNTIME_ERROR
+>> SCRIPT ERROR
+>> on function: test()
+>> runtime/errors/division_by_zero.gd
+>> 3
+>> Division by zero error in operator '/'.
diff --git a/modules/gdscript/tests/scripts/runtime/errors/modulo_by_zero.gd b/modules/gdscript/tests/scripts/runtime/errors/modulo_by_zero.gd
new file mode 100644
index 0000000000..99792e4e32
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/modulo_by_zero.gd
@@ -0,0 +1,3 @@
+func test():
+ var integer: int = 1
+ integer %= 0
diff --git a/modules/gdscript/tests/scripts/runtime/errors/modulo_by_zero.out b/modules/gdscript/tests/scripts/runtime/errors/modulo_by_zero.out
new file mode 100644
index 0000000000..79c512888f
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/modulo_by_zero.out
@@ -0,0 +1,6 @@
+GDTEST_RUNTIME_ERROR
+>> SCRIPT ERROR
+>> on function: test()
+>> runtime/errors/modulo_by_zero.gd
+>> 3
+>> Modulo by zero error in operator '%'.
diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_basic_to_typed.gd b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_basic_to_typed.gd
new file mode 100644
index 0000000000..75004742a2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_basic_to_typed.gd
@@ -0,0 +1,4 @@
+func test():
+ var basic := { 1: 1 }
+ var typed: Dictionary[int, int] = basic
+ print('not ok')
diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_basic_to_typed.out b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_basic_to_typed.out
new file mode 100644
index 0000000000..cadb17f570
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_basic_to_typed.out
@@ -0,0 +1,6 @@
+GDTEST_RUNTIME_ERROR
+>> SCRIPT ERROR
+>> on function: test()
+>> runtime/errors/typed_dictionary_assign_basic_to_typed.gd
+>> 3
+>> Trying to assign a dictionary of type "Dictionary" to a variable of type "Dictionary[int, int]".
diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed.gd b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed.gd
new file mode 100644
index 0000000000..e5ab4a1a85
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed.gd
@@ -0,0 +1,4 @@
+func test():
+ var differently: Variant = { 1.0: 0.0 } as Dictionary[float, float]
+ var typed: Dictionary[int, int] = differently
+ print('not ok')
diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed.out b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed.out
new file mode 100644
index 0000000000..fe1e5d1285
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed.out
@@ -0,0 +1,6 @@
+GDTEST_RUNTIME_ERROR
+>> SCRIPT ERROR
+>> on function: test()
+>> runtime/errors/typed_dictionary_assign_differently_typed.gd
+>> 3
+>> Trying to assign a dictionary of type "Dictionary[float, float]" to a variable of type "Dictionary[int, int]".
diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed_key.gd b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed_key.gd
new file mode 100644
index 0000000000..2f0b3bd0eb
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed_key.gd
@@ -0,0 +1,7 @@
+func get_key() -> Variant:
+ return "key"
+
+func test():
+ var typed: Dictionary[int, int]
+ typed[get_key()] = 0
+ print('not ok')
diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed_key.out b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed_key.out
new file mode 100644
index 0000000000..5f6dd7f641
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed_key.out
@@ -0,0 +1,6 @@
+GDTEST_RUNTIME_ERROR
+>> SCRIPT ERROR
+>> on function: test()
+>> runtime/errors/typed_dictionary_assign_differently_typed_key.gd
+>> 6
+>> Invalid assignment of property or key 'key' with value of type 'int' on a base object of type 'Dictionary[int, int]'.
diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed_value.gd b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed_value.gd
new file mode 100644
index 0000000000..b171159aed
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed_value.gd
@@ -0,0 +1,7 @@
+func get_value() -> Variant:
+ return "value"
+
+func test():
+ var typed: Dictionary[int, int]
+ typed[0] = get_value()
+ print("not ok")
diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed_value.out b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed_value.out
new file mode 100644
index 0000000000..f766d14261
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed_value.out
@@ -0,0 +1,6 @@
+GDTEST_RUNTIME_ERROR
+>> SCRIPT ERROR
+>> on function: test()
+>> runtime/errors/typed_dictionary_assign_differently_typed_value.gd
+>> 6
+>> Invalid assignment of property or key '0' with value of type 'String' on a base object of type 'Dictionary[int, int]'.
diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_wrong_to_typed.gd b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_wrong_to_typed.gd
new file mode 100644
index 0000000000..6cc0e57255
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_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: Dictionary[Bar, Bar] = { Baz.new() as Foo: Baz.new() as Foo }
+ print('not ok')
diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_wrong_to_typed.out b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_wrong_to_typed.out
new file mode 100644
index 0000000000..18a4c360e2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_wrong_to_typed.out
@@ -0,0 +1,5 @@
+GDTEST_RUNTIME_ERROR
+>> ERROR
+>> Method/function failed.
+>> Unable to convert key from "Object" to "Object".
+not ok
diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_basic_to_typed.gd b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_basic_to_typed.gd
new file mode 100644
index 0000000000..8f7d732584
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_basic_to_typed.gd
@@ -0,0 +1,7 @@
+func expect_typed(typed: Dictionary[int, int]):
+ print(typed.size())
+
+func test():
+ var basic := { 1: 1 }
+ expect_typed(basic)
+ print('not ok')
diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_basic_to_typed.out b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_basic_to_typed.out
new file mode 100644
index 0000000000..fb45461701
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_basic_to_typed.out
@@ -0,0 +1,6 @@
+GDTEST_RUNTIME_ERROR
+>> SCRIPT ERROR
+>> on function: test()
+>> runtime/errors/typed_dictionary_pass_basic_to_typed.gd
+>> 6
+>> Invalid type in function 'expect_typed' in base 'RefCounted (typed_dictionary_pass_basic_to_typed.gd)'. The dictionary of argument 1 (Dictionary) does not have the same element type as the expected typed dictionary argument.
diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_differently_to_typed.gd b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_differently_to_typed.gd
new file mode 100644
index 0000000000..978a9fdfee
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_differently_to_typed.gd
@@ -0,0 +1,7 @@
+func expect_typed(typed: Dictionary[int, int]):
+ print(typed.size())
+
+func test():
+ var differently: Variant = { 1.0: 0.0 } as Dictionary[float, float]
+ expect_typed(differently)
+ print('not ok')
diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_differently_to_typed.out b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_differently_to_typed.out
new file mode 100644
index 0000000000..4036a1bf01
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_differently_to_typed.out
@@ -0,0 +1,6 @@
+GDTEST_RUNTIME_ERROR
+>> SCRIPT ERROR
+>> on function: test()
+>> runtime/errors/typed_dictionary_pass_differently_to_typed.gd
+>> 6
+>> Invalid type in function 'expect_typed' in base 'RefCounted (typed_dictionary_pass_differently_to_typed.gd)'. The dictionary of argument 1 (Dictionary[float, float]) does not have the same element type as the expected typed dictionary argument.
diff --git a/modules/gdscript/tests/scripts/runtime/features/chain_assignment_works.out b/modules/gdscript/tests/scripts/runtime/features/chain_assignment_works.out
index 22929bf636..04b0773991 100644
--- a/modules/gdscript/tests/scripts/runtime/features/chain_assignment_works.out
+++ b/modules/gdscript/tests/scripts/runtime/features/chain_assignment_works.out
@@ -1,7 +1,7 @@
GDTEST_OK
-{ 1: (2, 0) }
-{ 3: (4, 0) }
-[[(5, 0)]]
-[[(6, 0)]]
-[[(7, 0)]]
-[X: (8, 9, 7), Y: (0, 1, 0), Z: (0, 0, 1), O: (0, 0, 0)]
+{ 1: (2.0, 0.0) }
+{ 3: (4.0, 0.0) }
+[[(5.0, 0.0)]]
+[[(6.0, 0.0)]]
+[[(7.0, 0.0)]]
+[X: (8.0, 9.0, 7.0), Y: (0.0, 1.0, 0.0), Z: (0.0, 0.0, 1.0), O: (0.0, 0.0, 0.0)]
diff --git a/modules/gdscript/tests/scripts/runtime/features/conversion_for_default_parameter.out b/modules/gdscript/tests/scripts/runtime/features/conversion_for_default_parameter.out
index a9ef4919cf..78ea2a2d80 100644
--- a/modules/gdscript/tests/scripts/runtime/features/conversion_for_default_parameter.out
+++ b/modules/gdscript/tests/scripts/runtime/features/conversion_for_default_parameter.out
@@ -1,8 +1,8 @@
GDTEST_OK
-x is 1
+x is 1.0
typeof x is 3
-x is 2
+x is 2.0
typeof x is 3
-x is 3
+x is 3.0
typeof x is 3
ok
diff --git a/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd
index bc899a3a6f..393500bd9b 100644
--- a/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd
@@ -28,13 +28,18 @@ func test():
prints(var_to_str(e), var_to_str(elem))
print("Test String-keys dictionary.")
- var d1 := {a = 1, b = 2, c = 3}
+ var d1 := { a = 1, b = 2, c = 3 }
for k: StringName in d1:
var key := k
prints(var_to_str(k), var_to_str(key))
print("Test RefCounted-keys dictionary.")
- var d2 := {RefCounted.new(): 1, Resource.new(): 2, ConfigFile.new(): 3}
+ var d2 := { RefCounted.new(): 1, Resource.new(): 2, ConfigFile.new(): 3 }
for k: RefCounted in d2:
var key := k
prints(k.get_class(), key.get_class())
+
+ print("Test implicitly typed dictionary literal.")
+ for k: StringName in { x = 123, y = 456, z = 789 }:
+ var key := k
+ prints(var_to_str(k), var_to_str(key))
diff --git a/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out
index eeebdc4be5..89cc1b76fe 100644
--- a/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out
+++ b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out
@@ -27,3 +27,7 @@ Test RefCounted-keys dictionary.
RefCounted RefCounted
Resource Resource
ConfigFile ConfigFile
+Test implicitly typed dictionary literal.
+&"x" &"x"
+&"y" &"y"
+&"z" &"z"
diff --git a/modules/gdscript/tests/scripts/runtime/features/gdscript_utility_implicit_conversion.gd b/modules/gdscript/tests/scripts/runtime/features/gdscript_utility_implicit_conversion.gd
new file mode 100644
index 0000000000..59bdb6eceb
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/gdscript_utility_implicit_conversion.gd
@@ -0,0 +1,12 @@
+func test():
+ const COLOR = Color8(255, 0.0, false)
+ var false_value := false
+ @warning_ignore("narrowing_conversion")
+ var color = Color8(255, 0.0, false_value)
+ print(var_to_str(COLOR))
+ print(var_to_str(color))
+
+ var string := "Node"
+ var string_name := &"Node"
+ print(type_exists(string))
+ print(type_exists(string_name))
diff --git a/modules/gdscript/tests/scripts/runtime/features/gdscript_utility_implicit_conversion.out b/modules/gdscript/tests/scripts/runtime/features/gdscript_utility_implicit_conversion.out
new file mode 100644
index 0000000000..00913faa49
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/gdscript_utility_implicit_conversion.out
@@ -0,0 +1,5 @@
+GDTEST_OK
+Color(1, 0, 0, 1)
+Color(1, 0, 0, 1)
+true
+true
diff --git a/modules/gdscript/tests/scripts/runtime/features/member_info.gd b/modules/gdscript/tests/scripts/runtime/features/member_info.gd
index 91d5a501c8..4ce53aa395 100644
--- a/modules/gdscript/tests/scripts/runtime/features/member_info.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/member_info.gd
@@ -31,6 +31,16 @@ var test_var_hard_array_my_enum: Array[MyEnum]
var test_var_hard_array_resource: Array[Resource]
var test_var_hard_array_this: Array[TestMemberInfo]
var test_var_hard_array_my_class: Array[MyClass]
+var test_var_hard_dictionary: Dictionary
+var test_var_hard_dictionary_int_variant: Dictionary[int, Variant]
+var test_var_hard_dictionary_variant_int: Dictionary[Variant, int]
+var test_var_hard_dictionary_int_int: Dictionary[int, int]
+var test_var_hard_dictionary_variant_type: Dictionary[Variant.Type, Variant.Type]
+var test_var_hard_dictionary_node_process_mode: Dictionary[Node.ProcessMode, Node.ProcessMode]
+var test_var_hard_dictionary_my_enum: Dictionary[MyEnum, MyEnum]
+var test_var_hard_dictionary_resource: Dictionary[Resource, Resource]
+var test_var_hard_dictionary_this: Dictionary[TestMemberInfo, TestMemberInfo]
+var test_var_hard_dictionary_my_class: Dictionary[MyClass, MyClass]
var test_var_hard_resource: Resource
var test_var_hard_this: TestMemberInfo
var test_var_hard_my_class: MyClass
@@ -43,17 +53,17 @@ func test_func_weak_null(): return null
func test_func_weak_int(): return 1
func test_func_hard_variant() -> Variant: return null
func test_func_hard_int() -> int: return 1
-func test_func_args_1(_a: int, _b: Array[int], _c: int = 1, _d = 2): pass
+func test_func_args_1(_a: int, _b: Array[int], _c: Dictionary[int, int], _d: int = 1, _e = 2): pass
func test_func_args_2(_a = 1, _b = _a, _c = [2], _d = 3): pass
signal test_signal_1()
signal test_signal_2(a: Variant, b)
-signal test_signal_3(a: int, b: Array[int])
-signal test_signal_4(a: Variant.Type, b: Array[Variant.Type])
-signal test_signal_5(a: MyEnum, b: Array[MyEnum])
-signal test_signal_6(a: Resource, b: Array[Resource])
-signal test_signal_7(a: TestMemberInfo, b: Array[TestMemberInfo])
-signal test_signal_8(a: MyClass, b: Array[MyClass])
+signal test_signal_3(a: int, b: Array[int], c: Dictionary[int, int])
+signal test_signal_4(a: Variant.Type, b: Array[Variant.Type], c: Dictionary[Variant.Type, Variant.Type])
+signal test_signal_5(a: MyEnum, b: Array[MyEnum], c: Dictionary[MyEnum, MyEnum])
+signal test_signal_6(a: Resource, b: Array[Resource], c: Dictionary[Resource, Resource])
+signal test_signal_7(a: TestMemberInfo, b: Array[TestMemberInfo], c: Dictionary[TestMemberInfo, TestMemberInfo])
+signal test_signal_8(a: MyClass, b: Array[MyClass], c: Dictionary[MyClass, MyClass])
func no_exec():
test_signal_1.emit()
diff --git a/modules/gdscript/tests/scripts/runtime/features/member_info.out b/modules/gdscript/tests/scripts/runtime/features/member_info.out
index 7c826ac05a..2baf451aa5 100644
--- a/modules/gdscript/tests/scripts/runtime/features/member_info.out
+++ b/modules/gdscript/tests/scripts/runtime/features/member_info.out
@@ -23,6 +23,16 @@ var test_var_hard_array_my_enum: Array[TestMemberInfo.MyEnum]
var test_var_hard_array_resource: Array[Resource]
var test_var_hard_array_this: Array[TestMemberInfo]
var test_var_hard_array_my_class: Array[RefCounted]
+var test_var_hard_dictionary: Dictionary
+var test_var_hard_dictionary_int_variant: Dictionary[int, Variant]
+var test_var_hard_dictionary_variant_int: Dictionary[Variant, int]
+var test_var_hard_dictionary_int_int: Dictionary[int, int]
+var test_var_hard_dictionary_variant_type: Dictionary[Variant.Type, Variant.Type]
+var test_var_hard_dictionary_node_process_mode: Dictionary[Node.ProcessMode, Node.ProcessMode]
+var test_var_hard_dictionary_my_enum: Dictionary[TestMemberInfo.MyEnum, TestMemberInfo.MyEnum]
+var test_var_hard_dictionary_resource: Dictionary[Resource, Resource]
+var test_var_hard_dictionary_this: Dictionary[TestMemberInfo, TestMemberInfo]
+var test_var_hard_dictionary_my_class: Dictionary[RefCounted, RefCounted]
var test_var_hard_resource: Resource
var test_var_hard_this: TestMemberInfo
var test_var_hard_my_class: RefCounted
@@ -33,13 +43,13 @@ func test_func_weak_null() -> Variant
func test_func_weak_int() -> Variant
func test_func_hard_variant() -> Variant
func test_func_hard_int() -> int
-func test_func_args_1(_a: int, _b: Array[int], _c: int = 1, _d: Variant = 2) -> void
+func test_func_args_1(_a: int, _b: Array[int], _c: Dictionary[int, int], _d: int = 1, _e: Variant = 2) -> void
func test_func_args_2(_a: Variant = 1, _b: Variant = null, _c: Variant = null, _d: Variant = 3) -> void
signal test_signal_1()
signal test_signal_2(a: Variant, b: Variant)
-signal test_signal_3(a: int, b: Array[int])
-signal test_signal_4(a: Variant.Type, b: Array[Variant.Type])
-signal test_signal_5(a: TestMemberInfo.MyEnum, b: Array[TestMemberInfo.MyEnum])
-signal test_signal_6(a: Resource, b: Array[Resource])
-signal test_signal_7(a: TestMemberInfo, b: Array[TestMemberInfo])
-signal test_signal_8(a: RefCounted, b: Array[RefCounted])
+signal test_signal_3(a: int, b: Array[int], c: Dictionary[int, int])
+signal test_signal_4(a: Variant.Type, b: Array[Variant.Type], c: Dictionary[Variant.Type, Variant.Type])
+signal test_signal_5(a: TestMemberInfo.MyEnum, b: Array[TestMemberInfo.MyEnum], c: Dictionary[TestMemberInfo.MyEnum, TestMemberInfo.MyEnum])
+signal test_signal_6(a: Resource, b: Array[Resource], c: Dictionary[Resource, Resource])
+signal test_signal_7(a: TestMemberInfo, b: Array[TestMemberInfo], c: Dictionary[TestMemberInfo, TestMemberInfo])
+signal test_signal_8(a: RefCounted, b: Array[RefCounted], c: Dictionary[RefCounted, RefCounted])
diff --git a/modules/gdscript/tests/scripts/runtime/features/native_static_method_as_callable.gd b/modules/gdscript/tests/scripts/runtime/features/native_static_method_as_callable.gd
new file mode 100644
index 0000000000..63d5935d1e
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/native_static_method_as_callable.gd
@@ -0,0 +1,8 @@
+func get_parse_string(t: Variant):
+ return t.parse_string
+
+func test():
+ var a: Callable = JSON.parse_string
+ var b: Callable = get_parse_string(JSON)
+ prints(a.call("{\"test\": \"a\"}"), a.is_valid())
+ prints(b.call("{\"test\": \"b\"}"), b.is_valid())
diff --git a/modules/gdscript/tests/scripts/runtime/features/native_static_method_as_callable.out b/modules/gdscript/tests/scripts/runtime/features/native_static_method_as_callable.out
new file mode 100644
index 0000000000..a2cb4b9a07
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/native_static_method_as_callable.out
@@ -0,0 +1,3 @@
+GDTEST_OK
+{ "test": "a" } false
+{ "test": "b" } false
diff --git a/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out b/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out
index 5b981bc8bb..1650acadb5 100644
--- a/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out
+++ b/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out
@@ -2,16 +2,16 @@ GDTEST_OK
>> WARNING
>> Line: 5
>> SHADOWED_VARIABLE
->> The local function parameter "a" is shadowing an already-declared variable at line 3.
+>> The local function parameter "a" is shadowing an already-declared variable at line 3 in the current class.
>> WARNING
>> Line: 15
>> SHADOWED_VARIABLE
->> The local function parameter "v" is shadowing an already-declared variable at line 13.
+>> The local function parameter "v" is shadowing an already-declared variable at line 13 in the current class.
a
1
b
1
-(1, 1)
-(0, 0)
-(6, 1)
-(0, 0)
+(1.0, 1.0)
+(0.0, 0.0)
+(6.0, 1.0)
+(0.0, 0.0)
diff --git a/modules/gdscript/tests/scripts/runtime/features/self_destruction.gd b/modules/gdscript/tests/scripts/runtime/features/self_destruction.gd
new file mode 100644
index 0000000000..442335faeb
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/self_destruction.gd
@@ -0,0 +1,50 @@
+# https://github.com/godotengine/godot/issues/75658
+
+class MyObj:
+ var callable: Callable
+
+ func run():
+ callable.call()
+
+ var prop:
+ set(value):
+ callable.call()
+ get:
+ callable.call()
+ return 0
+
+ func _on_some_signal():
+ callable.call()
+
+ func _init(p_callable: Callable):
+ self.callable = p_callable
+
+signal some_signal
+
+var obj: MyObj
+
+func test():
+ # Call.
+ obj = MyObj.new(nullify_obj)
+ obj.run()
+ print(obj)
+
+ # Get.
+ obj = MyObj.new(nullify_obj)
+ var _aux = obj.prop
+ print(obj)
+
+ # Set.
+ obj = MyObj.new(nullify_obj)
+ obj.prop = 1
+ print(obj)
+
+ # Signal handling.
+ obj = MyObj.new(nullify_obj)
+ @warning_ignore("return_value_discarded")
+ some_signal.connect(obj._on_some_signal)
+ some_signal.emit()
+ print(obj)
+
+func nullify_obj():
+ obj = null
diff --git a/modules/gdscript/tests/scripts/runtime/features/self_destruction.out b/modules/gdscript/tests/scripts/runtime/features/self_destruction.out
new file mode 100644
index 0000000000..ee4024a524
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/self_destruction.out
@@ -0,0 +1,5 @@
+GDTEST_OK
+<null>
+<null>
+<null>
+<null>
diff --git a/modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.out b/modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.out
index c51759f481..e82e31bbed 100644
--- a/modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.out
+++ b/modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.out
@@ -1,26 +1,26 @@
GDTEST_OK
===
-prop1 setter (0, 0)
-prop1 setter (1, 0)
+prop1 setter (0.0, 0.0)
+prop1 setter (1.0, 0.0)
---
prop1 setter <Inner>
subprop getter
-subprop setter (1, 0)
+subprop setter (1.0, 0.0)
===
prop2 setter <Inner>
subprop getter
-subprop setter (1, 0)
+subprop setter (1.0, 0.0)
===
-prop3 setter (0, 0)
+prop3 setter (0.0, 0.0)
prop3 getter
-prop3 setter (1, 0)
+prop3 setter (1.0, 0.0)
---
prop3 setter <Inner>
prop3 getter
subprop getter
-subprop setter (1, 0)
+subprop setter (1.0, 0.0)
===
prop4 setter <Inner>
prop4 getter
subprop getter
-subprop setter (1, 0)
+subprop setter (1.0, 0.0)
diff --git a/modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.out b/modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.out
index 31b3b3a3a8..8617a65c33 100644
--- a/modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.out
+++ b/modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.out
@@ -1,4 +1,4 @@
GDTEST_OK
-setting vec from (0, 0) to (2, 0)
-setting vec from (0, 0) to (0, 2)
-vec is (0, 0)
+setting vec from (0.0, 0.0) to (2.0, 0.0)
+setting vec from (0.0, 0.0) to (0.0, 2.0)
+vec is (0.0, 0.0)
diff --git a/modules/gdscript/tests/scripts/runtime/features/stringify.out b/modules/gdscript/tests/scripts/runtime/features/stringify.out
index 7833b6e213..2463d70ef4 100644
--- a/modules/gdscript/tests/scripts/runtime/features/stringify.out
+++ b/modules/gdscript/tests/scripts/runtime/features/stringify.out
@@ -9,13 +9,13 @@ hello world
[P: (0, 0), S: (0, 0)]
(0.25, 0.25, 0.25)
(0, 0, 0)
-[X: (1, 0), Y: (0, 1), O: (0, 0)]
-[N: (1, 2, 3), D: 4]
-(1, 2, 3, 4)
-[P: (0, 0, 0), S: (1, 1, 1)]
-[X: (1, 0, 0), Y: (0, 1, 0), Z: (0, 0, 1)]
-[X: (1, 0, 0), Y: (0, 1, 0), Z: (0, 0, 1), O: (0, 0, 0)]
+[X: (1.0, 0.0), Y: (0.0, 1.0), O: (0.0, 0.0)]
+[N: (1.0, 2.0, 3.0), D: 4]
(1, 2, 3, 4)
+[P: (0.0, 0.0, 0.0), S: (1.0, 1.0, 1.0)]
+[X: (1.0, 0.0, 0.0), Y: (0.0, 1.0, 0.0), Z: (0.0, 0.0, 1.0)]
+[X: (1.0, 0.0, 0.0), Y: (0.0, 1.0, 0.0), Z: (0.0, 0.0, 1.0), O: (0.0, 0.0, 0.0)]
+(1.0, 2.0, 3.0, 4.0)
hello
hello/world
RID(0)
@@ -26,10 +26,10 @@ Node::[signal]property_list_changed
[255, 0, 1]
[-1, 0, 1]
[-1, 0, 1]
-[-1, 0, 1]
-[-1, 0, 1]
+[-1.0, 0.0, 1.0]
+[-1.0, 0.0, 1.0]
["hello", "world"]
-[(1, 1), (0, 0)]
-[(1, 1, 1), (0, 0, 0)]
-[(1, 0, 0, 1), (0, 0, 1, 1), (0, 1, 0, 1)]
+[(1.0, 1.0), (0.0, 0.0)]
+[(1.0, 1.0, 1.0), (0.0, 0.0, 0.0)]
+[(1.0, 0.0, 0.0, 1.0), (0.0, 0.0, 1.0, 1.0), (0.0, 1.0, 0.0, 1.0)]
[(1, 1, 1, 1), (0, 0, 0, 0)]
diff --git a/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_implicit_cast_param.gd b/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_implicit_cast_param.gd
new file mode 100644
index 0000000000..0371ee5630
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_implicit_cast_param.gd
@@ -0,0 +1,6 @@
+func test_param(dictionary: Dictionary[int, String]) -> void:
+ print(dictionary.get_typed_key_builtin() == TYPE_INT)
+ print(dictionary.get_typed_value_builtin() == TYPE_STRING)
+
+func test() -> void:
+ test_param({ 123: "some_string" })
diff --git a/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_implicit_cast_param.out b/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_implicit_cast_param.out
new file mode 100644
index 0000000000..9d111a8322
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_implicit_cast_param.out
@@ -0,0 +1,3 @@
+GDTEST_OK
+true
+true
diff --git a/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_init_with_untyped_in_literal.gd b/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_init_with_untyped_in_literal.gd
new file mode 100644
index 0000000000..ee51440d80
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_init_with_untyped_in_literal.gd
@@ -0,0 +1,7 @@
+func test():
+ var untyped: Variant = 32
+ var typed: Dictionary[int, int] = { untyped: untyped }
+ Utils.check(typed.get_typed_key_builtin() == TYPE_INT)
+ Utils.check(typed.get_typed_value_builtin() == TYPE_INT)
+ Utils.check(str(typed) == '{ 32: 32 }')
+ print('ok')
diff --git a/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_init_with_untyped_in_literal.out b/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_init_with_untyped_in_literal.out
new file mode 100644
index 0000000000..1b47ed10dc
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_init_with_untyped_in_literal.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+ok
diff --git a/modules/gdscript/tests/scripts/utils.notest.gd b/modules/gdscript/tests/scripts/utils.notest.gd
index 5d615d8557..225bcb3008 100644
--- a/modules/gdscript/tests/scripts/utils.notest.gd
+++ b/modules/gdscript/tests/scripts/utils.notest.gd
@@ -1,6 +1,5 @@
class_name Utils
-
# `assert()` is not evaluated in non-debug builds. Do not use `assert()`
# for anything other than testing the `assert()` itself.
static func check(condition: Variant) -> void:
@@ -24,6 +23,11 @@ static func get_type(property: Dictionary, is_return: bool = false) -> String:
if str(property.hint_string).is_empty():
return "Array[<unknown type>]"
return "Array[%s]" % property.hint_string
+ TYPE_DICTIONARY:
+ if property.hint == PROPERTY_HINT_DICTIONARY_TYPE:
+ if str(property.hint_string).is_empty():
+ return "Dictionary[<unknown type>, <unknown type>]"
+ return "Dictionary[%s]" % str(property.hint_string).replace(";", ", ")
TYPE_OBJECT:
if not str(property.class_name).is_empty():
return property.class_name
@@ -188,6 +192,8 @@ static func get_property_hint_name(hint: PropertyHint) -> String:
return "PROPERTY_HINT_INT_IS_POINTER"
PROPERTY_HINT_ARRAY_TYPE:
return "PROPERTY_HINT_ARRAY_TYPE"
+ PROPERTY_HINT_DICTIONARY_TYPE:
+ return "PROPERTY_HINT_DICTIONARY_TYPE"
PROPERTY_HINT_LOCALE_ID:
return "PROPERTY_HINT_LOCALE_ID"
PROPERTY_HINT_LOCALIZABLE_STRING:
@@ -198,6 +204,9 @@ static func get_property_hint_name(hint: PropertyHint) -> String:
return "PROPERTY_HINT_HIDE_QUATERNION_EDIT"
PROPERTY_HINT_PASSWORD:
return "PROPERTY_HINT_PASSWORD"
+ PROPERTY_HINT_TOOL_BUTTON:
+ return "PROPERTY_HINT_TOOL_BUTTON"
+
printerr("Argument `hint` is invalid. Use `PROPERTY_HINT_*` constants.")
return "<invalid hint>"