diff options
46 files changed, 494 insertions, 223 deletions
diff --git a/core/variant/variant.cpp b/core/variant/variant.cpp index ca42738b05..672b030806 100644 --- a/core/variant/variant.cpp +++ b/core/variant/variant.cpp @@ -3492,6 +3492,46 @@ bool Variant::hash_compare(const Variant &p_variant, int recursion_count) const } } +bool Variant::identity_compare(const Variant &p_variant) const { + if (type != p_variant.type) { + return false; + } + + switch (type) { + case OBJECT: { + return _get_obj().obj == p_variant._get_obj().obj; + } break; + + case DICTIONARY: { + const Dictionary &l = *(reinterpret_cast<const Dictionary *>(_data._mem)); + const Dictionary &r = *(reinterpret_cast<const Dictionary *>(p_variant._data._mem)); + return l.id() == r.id(); + } break; + + case ARRAY: { + const Array &l = *(reinterpret_cast<const Array *>(_data._mem)); + const Array &r = *(reinterpret_cast<const Array *>(p_variant._data._mem)); + return l.id() == r.id(); + } break; + + case PACKED_BYTE_ARRAY: + case PACKED_INT32_ARRAY: + case PACKED_INT64_ARRAY: + case PACKED_FLOAT32_ARRAY: + case PACKED_FLOAT64_ARRAY: + case PACKED_STRING_ARRAY: + case PACKED_VECTOR2_ARRAY: + case PACKED_VECTOR3_ARRAY: + case PACKED_COLOR_ARRAY: { + return _data.packed_array == p_variant._data.packed_array; + } break; + + default: { + return hash_compare(p_variant); + } + } +} + bool StringLikeVariantComparator::compare(const Variant &p_lhs, const Variant &p_rhs) { if (p_lhs.hash_compare(p_rhs)) { return true; diff --git a/core/variant/variant.h b/core/variant/variant.h index b9294de77d..b2f31a6d57 100644 --- a/core/variant/variant.h +++ b/core/variant/variant.h @@ -748,6 +748,7 @@ public: uint32_t recursive_hash(int recursion_count) const; bool hash_compare(const Variant &p_variant, int recursion_count = 0) const; + bool identity_compare(const Variant &p_variant) const; bool booleanize() const; String stringify(int recursion_count = 0) const; String to_json_string() const; diff --git a/core/variant/variant_utility.cpp b/core/variant/variant_utility.cpp index fe7150bca9..042ebe368a 100644 --- a/core/variant/variant_utility.cpp +++ b/core/variant/variant_utility.cpp @@ -1007,9 +1007,14 @@ struct VariantUtilityFunctions { static inline uint64_t rid_allocate_id() { return RID_AllocBase::_gen_id(); } + static inline RID rid_from_int64(uint64_t p_base) { return RID::from_uint64(p_base); } + + static inline bool is_same(const Variant &p_a, const Variant &p_b) { + return p_a.identity_compare(p_b); + } }; #ifdef DEBUG_METHODS_ENABLED @@ -1601,6 +1606,8 @@ void Variant::_register_variant_utility_functions() { FUNCBINDR(rid_allocate_id, Vector<String>(), Variant::UTILITY_FUNC_TYPE_GENERAL); FUNCBINDR(rid_from_int64, sarray("base"), Variant::UTILITY_FUNC_TYPE_GENERAL); + + FUNCBINDR(is_same, sarray("a", "b"), Variant::UTILITY_FUNC_TYPE_GENERAL); } void Variant::_unregister_variant_utility_functions() { diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml index 4fdb7d82c5..ecb6a83c8a 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -525,6 +525,31 @@ Returns [code]true[/code] if [param x] is a NaN ("Not a Number" or invalid) value. </description> </method> + <method name="is_same"> + <return type="bool" /> + <param index="0" name="a" type="Variant" /> + <param index="1" name="b" type="Variant" /> + <description> + Returns [code]true[/code], for value types, if [param a] and [param b] share the same value. Returns [code]true[/code], for reference types, if the references of [param a] and [param b] are the same. + [codeblock] + # Vector2 is a value type + var vec2_a = Vector2(0, 0) + var vec2_b = Vector2(0, 0) + var vec2_c = Vector2(1, 1) + is_same(vec2_a, vec2_a) # true + is_same(vec2_a, vec2_b) # true + is_same(vec2_a, vec2_c) # false + + # Array is a reference type + var arr_a = [] + var arr_b = [] + is_same(arr_a, arr_a) # true + is_same(arr_a, arr_b) # false + [/codeblock] + These are [Variant] value types: [code]null[/code], [bool], [int], [float], [String], [StringName], [Vector2], [Vector2i], [Vector3], [Vector3i], [Vector4], [Vector4i], [Rect2], [Rect2i], [Transform2D], [Transform3D], [Plane], [Quaternion], [AABB], [Basis], [Projection], [Color], [NodePath], [RID], [Callable] and [Signal]. + These are [Variant] reference types: [Object], [Dictionary], [Array], [PackedByteArray], [PackedInt32Array], [PackedInt64Array], [PackedFloat32Array], [PackedFloat64Array], [PackedStringArray], [PackedVector2Array], [PackedVector3Array] and [PackedColorArray]. + </description> + </method> <method name="is_zero_approx"> <return type="bool" /> <param index="0" name="x" type="float" /> diff --git a/doc/classes/Dictionary.xml b/doc/classes/Dictionary.xml index 5f99ba82b8..03e5b5d1d8 100644 --- a/doc/classes/Dictionary.xml +++ b/doc/classes/Dictionary.xml @@ -42,7 +42,7 @@ You can access a dictionary's value by referencing its corresponding key. In the above example, [code]points_dict["White"][/code] will return [code]50[/code]. You can also write [code]points_dict.White[/code], which is equivalent. However, you'll have to use the bracket syntax if the key you're accessing the dictionary with isn't a fixed string (such as a number or variable). [codeblocks] [gdscript] - @export(String, "White", "Yellow", "Orange") var my_color + @export_enum("White", "Yellow", "Orange") var my_color: String var points_dict = {"White": 50, "Yellow": 75, "Orange": 100} func _ready(): # We can't use dot syntax here as `my_color` is a variable. diff --git a/doc/classes/MultiplayerAPI.xml b/doc/classes/MultiplayerAPI.xml index 3ce6ce41b4..020ce4f468 100644 --- a/doc/classes/MultiplayerAPI.xml +++ b/doc/classes/MultiplayerAPI.xml @@ -138,10 +138,10 @@ Used with [method Node.rpc_config] to disable a method or property for all RPC calls, making it unavailable. Default for all methods. </constant> <constant name="RPC_MODE_ANY_PEER" value="1" enum="RPCMode"> - Used with [method Node.rpc_config] to set a method to be callable remotely by any peer. Analogous to the [code]@rpc(any)[/code] annotation. Calls are accepted from all remote peers, no matter if they are node's authority or not. + Used with [method Node.rpc_config] to set a method to be callable remotely by any peer. Analogous to the [code]@rpc("any")[/code] annotation. Calls are accepted from all remote peers, no matter if they are node's authority or not. </constant> <constant name="RPC_MODE_AUTHORITY" value="2" enum="RPCMode"> - Used with [method Node.rpc_config] to set a method to be callable remotely only by the current multiplayer authority (which is the server by default). Analogous to the [code]@rpc(authority)[/code] annotation. See [method Node.set_multiplayer_authority]. + Used with [method Node.rpc_config] to set a method to be callable remotely only by the current multiplayer authority (which is the server by default). Analogous to the [code]@rpc("authority")[/code] annotation. See [method Node.set_multiplayer_authority]. </constant> </constants> </class> diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml index 7b27f16d82..7c40c189c0 100644 --- a/doc/classes/Node.xml +++ b/doc/classes/Node.xml @@ -666,7 +666,7 @@ channel = 0, } [/codeblock] - See [enum MultiplayerAPI.RPCMode] and [enum MultiplayerPeer.TransferMode]. An alternative is annotating methods and properties with the corresponding annotation ([code]@rpc(any)[/code], [code]@rpc(authority)[/code]). By default, methods are not exposed to networking (and RPCs). + See [enum MultiplayerAPI.RPCMode] and [enum MultiplayerPeer.TransferMode]. An alternative is annotating methods and properties with the corresponding annotation ([code]@rpc("any")[/code], [code]@rpc("authority")[/code]). By default, methods are not exposed to networking (and RPCs). </description> </method> <method name="rpc_id" qualifiers="vararg"> diff --git a/editor/plugins/animation_state_machine_editor.cpp b/editor/plugins/animation_state_machine_editor.cpp index 7ede0bd68c..9632670658 100644 --- a/editor/plugins/animation_state_machine_editor.cpp +++ b/editor/plugins/animation_state_machine_editor.cpp @@ -1138,7 +1138,7 @@ void AnimationNodeStateMachineEditor::_connection_draw(const Vector2 &p_from, co accent.a *= 0.6; } - const Ref<Texture2D> icons[6] = { + const Ref<Texture2D> icons[] = { get_theme_icon(SNAME("TransitionImmediateBig"), SNAME("EditorIcons")), get_theme_icon(SNAME("TransitionSyncBig"), SNAME("EditorIcons")), get_theme_icon(SNAME("TransitionEndBig"), SNAME("EditorIcons")), @@ -1146,6 +1146,7 @@ void AnimationNodeStateMachineEditor::_connection_draw(const Vector2 &p_from, co get_theme_icon(SNAME("TransitionSyncAutoBig"), SNAME("EditorIcons")), get_theme_icon(SNAME("TransitionEndAutoBig"), SNAME("EditorIcons")) }; + const int ICON_COUNT = sizeof(icons) / sizeof(*icons); if (p_selected) { state_machine_draw->draw_line(p_from, p_to, accent, 6); @@ -1162,7 +1163,9 @@ void AnimationNodeStateMachineEditor::_connection_draw(const Vector2 &p_from, co fade_linecolor.set_hsv(1.0, fade_linecolor.get_s(), fade_linecolor.get_v()); state_machine_draw->draw_line(p_from, p_from.lerp(p_to, p_fade_ratio), fade_linecolor, 2); } - Ref<Texture2D> icon = icons[p_mode + (p_auto_advance ? 3 : 0)]; + int icon_index = p_mode + (p_auto_advance ? ICON_COUNT / 2 : 0); + ERR_FAIL_COND(icon_index >= ICON_COUNT); + Ref<Texture2D> icon = icons[icon_index]; Transform2D xf; xf.columns[0] = (p_to - p_from).normalized(); diff --git a/editor/plugins/animation_tree_editor_plugin.cpp b/editor/plugins/animation_tree_editor_plugin.cpp index 720deb0b92..ab46e8f04a 100644 --- a/editor/plugins/animation_tree_editor_plugin.cpp +++ b/editor/plugins/animation_tree_editor_plugin.cpp @@ -65,13 +65,14 @@ void AnimationTreeEditor::edit(AnimationTree *p_tree) { tree = p_tree; Vector<String> path; - if (tree && tree->has_meta("_tree_edit_path")) { - path = tree->get_meta("_tree_edit_path"); - } else { - current_root = ObjectID(); + if (tree) { + if (tree->has_meta("_tree_edit_path")) { + path = tree->get_meta("_tree_edit_path"); + } else { + current_root = ObjectID(); + } + edit_path(path); } - - edit_path(path); } void AnimationTreeEditor::_node_removed(Node *p_node) { diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index b719a2ce30..6b4e7184d9 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -252,12 +252,14 @@ void ScriptTextEditor::_warning_clicked(Variant p_line) { } else if (p_line.get_type() == Variant::DICTIONARY) { Dictionary meta = p_line.operator Dictionary(); const int line = meta["line"].operator int64_t() - 1; + const String code = meta["code"].operator String(); + const String quote_style = EDITOR_GET("text_editor/completion/use_single_quotes") ? "'" : "\""; CodeEdit *text_editor = code_editor->get_text_editor(); String prev_line = line > 0 ? text_editor->get_line(line - 1) : ""; if (prev_line.contains("@warning_ignore")) { const int closing_bracket_idx = prev_line.find(")"); - const String text_to_insert = ", " + meta["code"].operator String(); + const String text_to_insert = ", " + code.quote(quote_style); prev_line = prev_line.insert(closing_bracket_idx, text_to_insert); text_editor->set_line(line - 1, prev_line); } else { @@ -268,7 +270,7 @@ void ScriptTextEditor::_warning_clicked(Variant p_line) { } else { annotation_indent = String(" ").repeat(text_editor->get_indent_size() * indent); } - text_editor->insert_line_at(line, annotation_indent + "@warning_ignore(" + meta["code"].operator String() + ")"); + text_editor->insert_line_at(line, annotation_indent + "@warning_ignore(" + code.quote(quote_style) + ")"); } _validate_script(); diff --git a/editor/plugins/tiles/tile_map_editor.cpp b/editor/plugins/tiles/tile_map_editor.cpp index 3dc42b4481..d79bd8ced3 100644 --- a/editor/plugins/tiles/tile_map_editor.cpp +++ b/editor/plugins/tiles/tile_map_editor.cpp @@ -3997,7 +3997,7 @@ TileMapEditor::TileMapEditor() { tabs_bar->connect("tab_changed", callable_mp(this, &TileMapEditor::_tab_changed)); // --- TileMap toolbar --- - tile_map_toolbar = memnew(HBoxContainer); + tile_map_toolbar = memnew(HFlowContainer); tile_map_toolbar->set_h_size_flags(SIZE_EXPAND_FILL); add_child(tile_map_toolbar); @@ -4012,8 +4012,11 @@ TileMapEditor::TileMapEditor() { } } - // Wide empty separation control. - tile_map_toolbar->add_spacer(); + // Wide empty separation control. (like BoxContainer::add_spacer()) + Control *c = memnew(Control); + c->set_mouse_filter(MOUSE_FILTER_PASS); + c->set_h_size_flags(SIZE_EXPAND_FILL); + tile_map_toolbar->add_child(c); // Layer selector. layers_selection_button = memnew(OptionButton); diff --git a/editor/plugins/tiles/tile_map_editor.h b/editor/plugins/tiles/tile_map_editor.h index fb9c2f3689..1cab1d1500 100644 --- a/editor/plugins/tiles/tile_map_editor.h +++ b/editor/plugins/tiles/tile_map_editor.h @@ -38,6 +38,7 @@ #include "scene/2d/tile_map.h" #include "scene/gui/box_container.h" #include "scene/gui/check_box.h" +#include "scene/gui/flow_container.h" #include "scene/gui/item_list.h" #include "scene/gui/menu_button.h" #include "scene/gui/option_button.h" @@ -323,7 +324,7 @@ private: Vector<TileMapEditorPlugin *> tile_map_editor_plugins; // Toolbar. - HBoxContainer *tile_map_toolbar = nullptr; + HFlowContainer *tile_map_toolbar = nullptr; OptionButton *layers_selection_button = nullptr; Button *toggle_highlight_selected_layer_button = nullptr; diff --git a/editor/project_converter_3_to_4.cpp b/editor/project_converter_3_to_4.cpp index 88fb88de98..5800fcca91 100644 --- a/editor/project_converter_3_to_4.cpp +++ b/editor/project_converter_3_to_4.cpp @@ -2549,14 +2549,14 @@ bool ProjectConverter3To4::test_conversion(RegExContainer ®_container) { valid = valid && test_conversion_with_regex("tool", "@tool", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container); valid = valid && test_conversion_with_regex("\n tool", "\n tool", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container); valid = valid && test_conversion_with_regex("\n\ntool", "\n\n@tool", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container); - valid = valid && test_conversion_with_regex("\n\nremote func", "\n\n@rpc(any_peer) func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container); - valid = valid && test_conversion_with_regex("\n\nremotesync func", "\n\n@rpc(any_peer, call_local) func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container); - valid = valid && test_conversion_with_regex("\n\nsync func", "\n\n@rpc(any_peer, call_local) func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container); + valid = valid && test_conversion_with_regex("\n\nremote func", "\n\n@rpc(\"any_peer\") func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container); + valid = valid && test_conversion_with_regex("\n\nremotesync func", "\n\n@rpc(\"any_peer\", \"call_local\") func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container); + valid = valid && test_conversion_with_regex("\n\nsync func", "\n\n@rpc(\"any_peer\", \"call_local\") func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container); valid = valid && test_conversion_with_regex("\n\nslave func", "\n\n@rpc func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container); valid = valid && test_conversion_with_regex("\n\npuppet func", "\n\n@rpc func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container); - valid = valid && test_conversion_with_regex("\n\npuppetsync func", "\n\n@rpc(call_local) func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container); + valid = valid && test_conversion_with_regex("\n\npuppetsync func", "\n\n@rpc(\"call_local\") func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container); valid = valid && test_conversion_with_regex("\n\nmaster func", "\n\nThe master and mastersync rpc behavior is not officially supported anymore. Try using another keyword or making custom logic using get_multiplayer().get_remote_sender_id()\n@rpc func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container); - valid = valid && test_conversion_with_regex("\n\nmastersync func", "\n\nThe master and mastersync rpc behavior is not officially supported anymore. Try using another keyword or making custom logic using get_multiplayer().get_remote_sender_id()\n@rpc(call_local) func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container); + valid = valid && test_conversion_with_regex("\n\nmastersync func", "\n\nThe master and mastersync rpc behavior is not officially supported anymore. Try using another keyword or making custom logic using get_multiplayer().get_remote_sender_id()\n@rpc(\"call_local\") func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container); valid = valid && test_conversion_gdscript_builtin("var size : Vector2 = Vector2() setget set_function , get_function", "var size : Vector2 = Vector2() : get = get_function, set = set_function", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false); valid = valid && test_conversion_gdscript_builtin("var size : Vector2 = Vector2() setget set_function , ", "var size : Vector2 = Vector2() : set = set_function", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false); @@ -4087,13 +4087,13 @@ void ProjectConverter3To4::rename_gdscript_keywords(Vector<String> &lines, const line = reg_container.keyword_gdscript_onready.sub(line, "@onready", true); } if (line.contains("remote")) { - line = reg_container.keyword_gdscript_remote.sub(line, "@rpc(any_peer) func", true); + line = reg_container.keyword_gdscript_remote.sub(line, "@rpc(\"any_peer\") func", true); } if (line.contains("remote")) { - line = reg_container.keyword_gdscript_remotesync.sub(line, "@rpc(any_peer, call_local) func", true); + line = reg_container.keyword_gdscript_remotesync.sub(line, "@rpc(\"any_peer\", \"call_local\") func", true); } if (line.contains("sync")) { - line = reg_container.keyword_gdscript_sync.sub(line, "@rpc(any_peer, call_local) func", true); + line = reg_container.keyword_gdscript_sync.sub(line, "@rpc(\"any_peer\", \"call_local\") func", true); } if (line.contains("slave")) { line = reg_container.keyword_gdscript_slave.sub(line, "@rpc func", true); @@ -4102,13 +4102,13 @@ void ProjectConverter3To4::rename_gdscript_keywords(Vector<String> &lines, const line = reg_container.keyword_gdscript_puppet.sub(line, "@rpc func", true); } if (line.contains("puppet")) { - line = reg_container.keyword_gdscript_puppetsync.sub(line, "@rpc(call_local) func", true); + line = reg_container.keyword_gdscript_puppetsync.sub(line, "@rpc(\"call_local\") func", true); } if (line.contains("master")) { line = reg_container.keyword_gdscript_master.sub(line, error_message + "@rpc func", true); } if (line.contains("master")) { - line = reg_container.keyword_gdscript_mastersync.sub(line, error_message + "@rpc(call_local) func", true); + line = reg_container.keyword_gdscript_mastersync.sub(line, error_message + "@rpc(\"call_local\") func", true); } } } @@ -4156,25 +4156,25 @@ Vector<String> ProjectConverter3To4::check_for_rename_gdscript_keywords(Vector<S if (line.contains("remote")) { old = line; - line = reg_container.keyword_gdscript_remote.sub(line, "@rpc(any_peer) func", true); + line = reg_container.keyword_gdscript_remote.sub(line, "@rpc(\"any_peer\") func", true); if (old != line) { - found_renames.append(line_formatter(current_line, "remote func", "@rpc(any_peer) func", line)); + found_renames.append(line_formatter(current_line, "remote func", "@rpc(\"any_peer\") func", line)); } } if (line.contains("remote")) { old = line; - line = reg_container.keyword_gdscript_remotesync.sub(line, "@rpc(any_peer, call_local)) func", true); + line = reg_container.keyword_gdscript_remotesync.sub(line, "@rpc(\"any_peer\", \"call_local\")) func", true); if (old != line) { - found_renames.append(line_formatter(current_line, "remotesync func", "@rpc(any_peer, call_local)) func", line)); + found_renames.append(line_formatter(current_line, "remotesync func", "@rpc(\"any_peer\", \"call_local\")) func", line)); } } if (line.contains("sync")) { old = line; - line = reg_container.keyword_gdscript_sync.sub(line, "@rpc(any_peer, call_local)) func", true); + line = reg_container.keyword_gdscript_sync.sub(line, "@rpc(\"any_peer\", \"call_local\")) func", true); if (old != line) { - found_renames.append(line_formatter(current_line, "sync func", "@rpc(any_peer, call_local)) func", line)); + found_renames.append(line_formatter(current_line, "sync func", "@rpc(\"any_peer\", \"call_local\")) func", line)); } } @@ -4196,9 +4196,9 @@ Vector<String> ProjectConverter3To4::check_for_rename_gdscript_keywords(Vector<S if (line.contains("puppet")) { old = line; - line = reg_container.keyword_gdscript_puppetsync.sub(line, "@rpc(call_local) func", true); + line = reg_container.keyword_gdscript_puppetsync.sub(line, "@rpc(\"call_local\") func", true); if (old != line) { - found_renames.append(line_formatter(current_line, "puppetsync func", "@rpc(call_local) func", line)); + found_renames.append(line_formatter(current_line, "puppetsync func", "@rpc(\"call_local\") func", line)); } } @@ -4212,9 +4212,9 @@ Vector<String> ProjectConverter3To4::check_for_rename_gdscript_keywords(Vector<S if (line.contains("master")) { old = line; - line = reg_container.keyword_gdscript_master.sub(line, "@rpc(call_local) func", true); + line = reg_container.keyword_gdscript_master.sub(line, "@rpc(\"call_local\") func", true); if (old != line) { - found_renames.append(line_formatter(current_line, "mastersync func", "@rpc(call_local) func", line)); + found_renames.append(line_formatter(current_line, "mastersync func", "@rpc(\"call_local\") func", line)); } } } diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index 3fe741a582..5bed1b9da3 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -485,7 +485,7 @@ Export a [NodePath] property with a filter for allowed node types. See also [constant PROPERTY_HINT_NODE_PATH_VALID_TYPES]. [codeblock] - @export_node_path(Button, TouchScreenButton) var some_button + @export_node_path("Button", "TouchScreenButton") var some_button [/codeblock] </description> </annotation> diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 6b325d6451..4fc3929bbd 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -2448,7 +2448,6 @@ bool GDScriptLanguage::handles_global_class_type(const String &p_type) const { } String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_base_type, String *r_icon_path) const { - Vector<uint8_t> sourcef; Error err; Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ, &err); if (err) { @@ -2459,88 +2458,31 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b GDScriptParser parser; err = parser.parse(source, p_path, false); + if (err) { + return String(); + } - // TODO: Simplify this code by using the analyzer to get full inheritance. - if (err == OK) { - const GDScriptParser::ClassNode *c = parser.get_tree(); - if (r_icon_path) { - if (c->icon_path.is_empty() || c->icon_path.is_absolute_path()) { - *r_icon_path = c->icon_path; - } else if (c->icon_path.is_relative_path()) { - *r_icon_path = p_path.get_base_dir().path_join(c->icon_path).simplify_path(); - } - } - if (r_base_type) { - const GDScriptParser::ClassNode *subclass = c; - String path = p_path; - GDScriptParser subparser; - while (subclass) { - if (subclass->extends_used) { - if (!subclass->extends_path.is_empty()) { - if (subclass->extends.size() == 0) { - get_global_class_name(subclass->extends_path, r_base_type); - subclass = nullptr; - break; - } else { - Vector<StringName> extend_classes = subclass->extends; - - Ref<FileAccess> subfile = FileAccess::open(subclass->extends_path, FileAccess::READ); - if (subfile.is_null()) { - break; - } - String subsource = subfile->get_as_utf8_string(); - - if (subsource.is_empty()) { - break; - } - String subpath = subclass->extends_path; - if (subpath.is_relative_path()) { - subpath = path.get_base_dir().path_join(subpath).simplify_path(); - } - - if (OK != subparser.parse(subsource, subpath, false)) { - break; - } - path = subpath; - subclass = subparser.get_tree(); - - while (extend_classes.size() > 0) { - bool found = false; - for (int i = 0; i < subclass->members.size(); i++) { - if (subclass->members[i].type != GDScriptParser::ClassNode::Member::CLASS) { - continue; - } - - const GDScriptParser::ClassNode *inner_class = subclass->members[i].m_class; - if (inner_class->identifier->name == extend_classes[0]) { - extend_classes.remove_at(0); - found = true; - subclass = inner_class; - break; - } - } - if (!found) { - subclass = nullptr; - break; - } - } - } - } else if (subclass->extends.size() == 1) { - *r_base_type = subclass->extends[0]; - subclass = nullptr; - } else { - break; - } - } else { - *r_base_type = "RefCounted"; - subclass = nullptr; - } - } + GDScriptAnalyzer analyzer(&parser); + err = analyzer.resolve_inheritance(); + if (err) { + return String(); + } + + const GDScriptParser::ClassNode *c = parser.get_tree(); + + if (r_base_type) { + *r_base_type = c->get_datatype().native_type; + } + + if (r_icon_path) { + if (c->icon_path.is_empty() || c->icon_path.is_absolute_path()) { + *r_icon_path = c->icon_path.simplify_path(); + } else if (c->icon_path.is_relative_path()) { + *r_icon_path = p_path.get_base_dir().path_join(c->icon_path).simplify_path(); } - return c->identifier != nullptr ? String(c->identifier->name) : String(); } - return String(); + return c->identifier != nullptr ? String(c->identifier->name) : String(); } GDScriptLanguage::GDScriptLanguage() { diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index e04a962dcb..d7f6126207 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -802,6 +802,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, // Apply annotations. for (GDScriptParser::AnnotationNode *&E : member.variable->annotations) { + resolve_annotation(E); E->apply(parser, member.variable); } } break; @@ -812,6 +813,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, // Apply annotations. for (GDScriptParser::AnnotationNode *&E : member.constant->annotations) { + resolve_annotation(E); E->apply(parser, member.constant); } } break; @@ -835,6 +837,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, // Apply annotations. for (GDScriptParser::AnnotationNode *&E : member.signal->annotations) { + resolve_annotation(E); E->apply(parser, member.signal); } } break; @@ -882,6 +885,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, // Apply annotations. for (GDScriptParser::AnnotationNode *&E : member.m_enum->annotations) { + resolve_annotation(E); E->apply(parser, member.m_enum); } } break; @@ -1064,6 +1068,7 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co if (member.type == GDScriptParser::ClassNode::Member::FUNCTION) { // Apply annotations. for (GDScriptParser::AnnotationNode *&E : member.function->annotations) { + resolve_annotation(E); E->apply(parser, member.function); } @@ -1290,7 +1295,55 @@ void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node, bool p_is_root } void GDScriptAnalyzer::resolve_annotation(GDScriptParser::AnnotationNode *p_annotation) { - // TODO: Add second validation function for annotations, so they can use checked types. + ERR_FAIL_COND_MSG(!parser->valid_annotations.has(p_annotation->name), vformat(R"(Annotation "%s" not found to validate.)", p_annotation->name)); + + const MethodInfo &annotation_info = parser->valid_annotations[p_annotation->name].info; + + const List<PropertyInfo>::Element *E = annotation_info.arguments.front(); + for (int i = 0; i < p_annotation->arguments.size(); i++) { + GDScriptParser::ExpressionNode *argument = p_annotation->arguments[i]; + const PropertyInfo &argument_info = E->get(); + + if (E->next() != nullptr) { + E = E->next(); + } + + reduce_expression(argument); + + if (!argument->is_constant) { + push_error(vformat(R"(Argument %d of annotation "%s" isn't a constant expression.)", i + 1, p_annotation->name), argument); + return; + } + + Variant value = argument->reduced_value; + + if (value.get_type() != argument_info.type) { +#ifdef DEBUG_ENABLED + if (argument_info.type == Variant::INT && value.get_type() == Variant::FLOAT) { + parser->push_warning(argument, GDScriptWarning::NARROWING_CONVERSION); + } +#endif + + if (!Variant::can_convert_strict(value.get_type(), argument_info.type)) { + push_error(vformat(R"(Invalid argument for annotation "%s": argument %d should be "%s" but is "%s".)", p_annotation->name, i + 1, Variant::get_type_name(argument_info.type), argument->get_datatype().to_string()), argument); + return; + } + + Variant converted_to; + const Variant *converted_from = &value; + Callable::CallError call_error; + Variant::construct(argument_info.type, converted_to, &converted_from, 1, call_error); + + if (call_error.error != Callable::CallError::CALL_OK) { + push_error(vformat(R"(Cannot convert argument %d of annotation "%s" from "%s" to "%s".)", i + 1, p_annotation->name, Variant::get_type_name(value.get_type()), Variant::get_type_name(argument_info.type)), argument); + return; + } + + value = converted_to; + } + + p_annotation->resolved_arguments.push_back(value); + } } void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *p_function, const GDScriptParser::Node *p_source, bool p_is_lambda) { @@ -1486,8 +1539,10 @@ void GDScriptAnalyzer::decide_suite_type(GDScriptParser::Node *p_suite, GDScript void GDScriptAnalyzer::resolve_suite(GDScriptParser::SuiteNode *p_suite) { for (int i = 0; i < p_suite->statements.size(); i++) { GDScriptParser::Node *stmt = p_suite->statements[i]; - for (GDScriptParser::AnnotationNode *&annotation : stmt->annotations) { - annotation->apply(parser, stmt); + // Apply annotations. + for (GDScriptParser::AnnotationNode *&E : stmt->annotations) { + resolve_annotation(E); + E->apply(parser, stmt); } #ifdef DEBUG_ENABLED @@ -1544,10 +1599,8 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi GDScriptParser::DataType initializer_type = p_assignable->initializer->get_datatype(); if (p_assignable->infer_datatype) { - if (!initializer_type.is_set() || initializer_type.has_no_type()) { + if (!initializer_type.is_set() || initializer_type.has_no_type() || !initializer_type.is_hard_type()) { push_error(vformat(R"(Cannot infer the type of "%s" %s because the value doesn't have a set type.)", p_assignable->identifier->name, p_kind), p_assignable->initializer); - } else if (initializer_type.is_variant() && !initializer_type.is_hard_type()) { - push_error(vformat(R"(Cannot infer the type of "%s" %s because the value is Variant. Use explicit "Variant" type if this is intended.)", p_assignable->identifier->name, p_kind), p_assignable->initializer); } else if (initializer_type.kind == GDScriptParser::DataType::BUILTIN && initializer_type.builtin_type == Variant::NIL && !is_constant) { push_error(vformat(R"(Cannot infer the type of "%s" %s because the value is "null".)", p_assignable->identifier->name, p_kind), p_assignable->initializer); } @@ -2014,7 +2067,7 @@ void GDScriptAnalyzer::reduce_expression(GDScriptParser::ExpressionNode *p_expre reduce_subscript(static_cast<GDScriptParser::SubscriptNode *>(p_expression)); break; case GDScriptParser::Node::TERNARY_OPERATOR: - reduce_ternary_op(static_cast<GDScriptParser::TernaryOpNode *>(p_expression)); + reduce_ternary_op(static_cast<GDScriptParser::TernaryOpNode *>(p_expression), p_is_root); break; case GDScriptParser::Node::UNARY_OPERATOR: reduce_unary_op(static_cast<GDScriptParser::UnaryOpNode *>(p_expression)); @@ -3534,6 +3587,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri } #endif result_type.kind = GDScriptParser::DataType::VARIANT; + mark_node_unsafe(p_subscript); } } if (!valid) { @@ -3735,10 +3789,10 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri p_subscript->set_datatype(result_type); } -void GDScriptAnalyzer::reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternary_op) { +void GDScriptAnalyzer::reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternary_op, bool p_is_root) { reduce_expression(p_ternary_op->condition); - reduce_expression(p_ternary_op->true_expr); - reduce_expression(p_ternary_op->false_expr); + reduce_expression(p_ternary_op->true_expr, p_is_root); + reduce_expression(p_ternary_op->false_expr, p_is_root); GDScriptParser::DataType result; @@ -4552,6 +4606,11 @@ Ref<GDScriptParserRef> GDScriptAnalyzer::get_parser_for(const String &p_path) { } Error GDScriptAnalyzer::resolve_inheritance() { + // Apply annotations. + for (GDScriptParser::AnnotationNode *&E : parser->head->annotations) { + resolve_annotation(E); + E->apply(parser, parser->head); + } return resolve_class_inheritance(parser->head, true); } diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index b22d47982f..5397be33f0 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -99,7 +99,7 @@ class GDScriptAnalyzer { void reduce_preload(GDScriptParser::PreloadNode *p_preload); void reduce_self(GDScriptParser::SelfNode *p_self); void reduce_subscript(GDScriptParser::SubscriptNode *p_subscript); - void reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternary_op); + void reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternary_op, bool p_is_root = false); void reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op); void const_fold_array(GDScriptParser::ArrayNode *p_array, bool p_is_const); diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 3fc0924b4c..f88ac581ca 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -782,6 +782,7 @@ static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_a } } else if (p_annotation->name == SNAME("@export_node_path")) { ScriptLanguage::CodeCompletionOption node("Node", ScriptLanguage::CODE_COMPLETION_KIND_CLASS); + node.insert_text = node.display.quote(p_quote_style); r_result.insert(node.display, node); List<StringName> node_types; ClassDB::get_inheriters_from_class("Node", &node_types); @@ -790,11 +791,13 @@ static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_a continue; } ScriptLanguage::CodeCompletionOption option(E, ScriptLanguage::CODE_COMPLETION_KIND_CLASS); + option.insert_text = option.display.quote(p_quote_style); r_result.insert(option.display, option); } } else if (p_annotation->name == SNAME("@warning_ignore")) { for (int warning_code = 0; warning_code < GDScriptWarning::WARNING_MAX; warning_code++) { ScriptLanguage::CodeCompletionOption warning(GDScriptWarning::get_name_from_code((GDScriptWarning::Code)warning_code).to_lower(), ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); + warning.insert_text = warning.display.quote(p_quote_style); r_result.insert(warning.display, warning); } } diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 99b78a8326..1a744d59ba 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -529,7 +529,7 @@ void GDScriptParser::parse_program() { AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE | AnnotationInfo::CLASS_LEVEL); if (annotation != nullptr) { if (annotation->applies_to(AnnotationInfo::SCRIPT)) { - annotation->apply(this, head); + head->annotations.push_back(annotation); } else { annotation_stack.push_back(annotation); // This annotation must appear after script-level annotations @@ -771,7 +771,6 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)() return; } - // Apply annotations. for (AnnotationNode *&annotation : annotations) { member->annotations.push_back(annotation); } @@ -848,7 +847,7 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) { if (previous.type != GDScriptTokenizer::Token::NEWLINE) { push_error(R"(Expected newline after a standalone annotation.)"); } - annotation->apply(this, head); + head->annotations.push_back(annotation); } else { annotation_stack.push_back(annotation); } @@ -1470,7 +1469,7 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali match(GDScriptTokenizer::Token::NEWLINE); // Newline after annotation is optional. if (valid) { - valid = validate_annotation_arguments(annotation); + valid = validate_annotation_argument_count(annotation); } return valid ? annotation : nullptr; @@ -1701,6 +1700,7 @@ GDScriptParser::Node *GDScriptParser::parse_statement() { case Node::CALL: case Node::ASSIGNMENT: case Node::AWAIT: + case Node::TERNARY_OPERATOR: // Fine. break; case Node::LAMBDA: @@ -1716,7 +1716,6 @@ GDScriptParser::Node *GDScriptParser::parse_statement() { } } - // Apply annotations to statement. while (!is_annotation && result != nullptr && !annotation_stack.is_empty()) { AnnotationNode *last_annotation = annotation_stack.back()->get(); if (last_annotation->applies_to(AnnotationInfo::STATEMENT)) { @@ -3597,7 +3596,7 @@ bool GDScriptParser::AnnotationNode::applies_to(uint32_t p_target_kinds) const { return (info->target_kind & p_target_kinds) > 0; } -bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation) { +bool GDScriptParser::validate_annotation_argument_count(AnnotationNode *p_annotation) { ERR_FAIL_COND_V_MSG(!valid_annotations.has(p_annotation->name), false, vformat(R"(Annotation "%s" not found to validate.)", p_annotation->name)); const MethodInfo &info = valid_annotations[p_annotation->name].info; @@ -3612,62 +3611,6 @@ bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation) return false; } - const List<PropertyInfo>::Element *E = info.arguments.front(); - for (int i = 0; i < p_annotation->arguments.size(); i++) { - ExpressionNode *argument = p_annotation->arguments[i]; - const PropertyInfo ¶meter = E->get(); - - if (E->next() != nullptr) { - E = E->next(); - } - - switch (parameter.type) { - case Variant::STRING: - case Variant::STRING_NAME: - case Variant::NODE_PATH: - // Allow "quote-less strings", as long as they are recognized as identifiers. - if (argument->type == Node::IDENTIFIER) { - IdentifierNode *string = static_cast<IdentifierNode *>(argument); - Callable::CallError error; - Vector<Variant> args = varray(string->name); - const Variant *name = args.ptr(); - Variant r; - Variant::construct(parameter.type, r, &(name), 1, error); - p_annotation->resolved_arguments.push_back(r); - if (error.error != Callable::CallError::CALL_OK) { - push_error(vformat(R"(Expected %s as argument %d of annotation "%s".)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name)); - p_annotation->resolved_arguments.remove_at(p_annotation->resolved_arguments.size() - 1); - return false; - } - break; - } - [[fallthrough]]; - default: { - if (argument->type != Node::LITERAL) { - push_error(vformat(R"(Expected %s as argument %d of annotation "%s".)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name)); - return false; - } - - Variant value = static_cast<LiteralNode *>(argument)->value; - if (!Variant::can_convert_strict(value.get_type(), parameter.type)) { - push_error(vformat(R"(Expected %s as argument %d of annotation "%s".)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name)); - return false; - } - Callable::CallError error; - const Variant *args = &value; - Variant r; - Variant::construct(parameter.type, r, &(args), 1, error); - p_annotation->resolved_arguments.push_back(r); - if (error.error != Callable::CallError::CALL_OK) { - push_error(vformat(R"(Expected %s as argument %d of annotation "%s".)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name)); - p_annotation->resolved_arguments.remove_at(p_annotation->resolved_arguments.size() - 1); - return false; - } - break; - } - } - } - return true; } diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 5da709e8cd..74e12d0b5e 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -1401,7 +1401,7 @@ private: // Annotations AnnotationNode *parse_annotation(uint32_t p_valid_targets); bool register_annotation(const MethodInfo &p_info, uint32_t p_target_kinds, AnnotationAction p_apply, const Vector<Variant> &p_default_arguments = Vector<Variant>(), bool p_is_vararg = false); - bool validate_annotation_arguments(AnnotationNode *p_annotation); + bool validate_annotation_argument_count(AnnotationNode *p_annotation); void clear_unused_annotations(); bool tool_annotation(const AnnotationNode *p_annotation, Node *p_target); bool icon_annotation(const AnnotationNode *p_annotation, Node *p_target); diff --git a/modules/gdscript/tests/scripts/analyzer/errors/annotation_non_constant_parameter.gd b/modules/gdscript/tests/scripts/analyzer/errors/annotation_non_constant_parameter.gd new file mode 100644 index 0000000000..75524c32ae --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/annotation_non_constant_parameter.gd @@ -0,0 +1,6 @@ +var num := 1 + +@export_range(num, 10) var a + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/annotation_non_constant_parameter.out b/modules/gdscript/tests/scripts/analyzer/errors/annotation_non_constant_parameter.out new file mode 100644 index 0000000000..b4f0e79237 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/annotation_non_constant_parameter.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Argument 1 of annotation "@export_range" isn't a constant expression. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_local_variable.gd b/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_local_variable.gd new file mode 100644 index 0000000000..6014ee831c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_local_variable.gd @@ -0,0 +1,3 @@ +func test(): + var untyped = 1 + var inferred := untyped diff --git a/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_local_variable.out b/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_local_variable.out new file mode 100644 index 0000000000..b6dc6d0b01 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_local_variable.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot infer the type of "inferred" variable because the value doesn't have a set type. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_member_variable.gd b/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_member_variable.gd new file mode 100644 index 0000000000..040aa2e82a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_member_variable.gd @@ -0,0 +1,5 @@ +var untyped = 1 +var inferred := untyped + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_member_variable.out b/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_member_variable.out new file mode 100644 index 0000000000..b6dc6d0b01 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_member_variable.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot infer the type of "inferred" variable because the value doesn't have a set type. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_parameter.gd b/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_parameter.gd new file mode 100644 index 0000000000..80c676488e --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_parameter.gd @@ -0,0 +1,5 @@ +func check(untyped = 1, inferred := untyped): + pass + +func test(): + check() diff --git a/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_parameter.out b/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_parameter.out new file mode 100644 index 0000000000..8c9f0c13ae --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_parameter.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot infer the type of "inferred" parameter because the value doesn't have a set type. diff --git a/modules/gdscript/tests/scripts/analyzer/features/annotation_constant_expression_parameters.gd b/modules/gdscript/tests/scripts/analyzer/features/annotation_constant_expression_parameters.gd new file mode 100644 index 0000000000..272dce8bbe --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/annotation_constant_expression_parameters.gd @@ -0,0 +1,10 @@ +const BEFORE = 1 + +@export_range(-10, 10) var a = 0 +@export_range(1 + 2, absi(-10) + 1) var b = 5 +@export_range(BEFORE + 1, BEFORE + AFTER + 1) var c = 5 + +const AFTER = 10 + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/features/annotation_constant_expression_parameters.out b/modules/gdscript/tests/scripts/analyzer/features/annotation_constant_expression_parameters.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/annotation_constant_expression_parameters.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/analyzer/features/assignments_with_untyped.gd b/modules/gdscript/tests/scripts/analyzer/features/assignments_with_untyped.gd index 2d2c2bef19..595563541f 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/assignments_with_untyped.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/assignments_with_untyped.gd @@ -1,9 +1,4 @@ func test(): - var one_0 = 0 - one_0 = 1 - var one_1 := one_0 - print(one_1) - var two: Variant = 0 two += 2 print(two) diff --git a/modules/gdscript/tests/scripts/analyzer/features/assignments_with_untyped.out b/modules/gdscript/tests/scripts/analyzer/features/assignments_with_untyped.out index 7536c38490..0ddfa4b75f 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/assignments_with_untyped.out +++ b/modules/gdscript/tests/scripts/analyzer/features/assignments_with_untyped.out @@ -1,5 +1,4 @@ GDTEST_OK -1 2 3 4 diff --git a/modules/gdscript/tests/scripts/analyzer/features/warning_ignore_annotation.gd b/modules/gdscript/tests/scripts/analyzer/features/warning_ignore_annotation.gd index 877a4ea221..4c02fd4b0d 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/warning_ignore_annotation.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/warning_ignore_annotation.gd @@ -1,12 +1,12 @@ -@warning_ignore(unused_private_class_variable) +@warning_ignore("unused_private_class_variable") var _unused = 2 -@warning_ignore(unused_variable) +@warning_ignore("unused_variable") func test(): print("test") var unused = 3 - @warning_ignore(redundant_await) + @warning_ignore("redundant_await") print(await regular_func()) print("done") diff --git a/modules/gdscript/tests/scripts/parser/features/arrays_dictionaries_nested_const.gd b/modules/gdscript/tests/scripts/parser/features/arrays_dictionaries_nested_const.gd index cc78309ae4..a34cc26e67 100644 --- a/modules/gdscript/tests/scripts/parser/features/arrays_dictionaries_nested_const.gd +++ b/modules/gdscript/tests/scripts/parser/features/arrays_dictionaries_nested_const.gd @@ -1,6 +1,6 @@ # https://github.com/godotengine/godot/issues/50285 -@warning_ignore(unused_local_constant) +@warning_ignore("unused_local_constant") func test(): const CONST_INNER_DICTIONARY = { "key": true } const CONST_NESTED_DICTIONARY_OLD_WORKAROUND = { diff --git a/modules/gdscript/tests/scripts/parser/features/export_variable.gd b/modules/gdscript/tests/scripts/parser/features/export_variable.gd index 1e072728fc..acf9ff2e21 100644 --- a/modules/gdscript/tests/scripts/parser/features/export_variable.gd +++ b/modules/gdscript/tests/scripts/parser/features/export_variable.gd @@ -5,7 +5,7 @@ @export var color: Color @export_color_no_alpha var color_no_alpha: Color -@export_node_path(Sprite2D, Sprite3D, Control, Node) var nodepath := ^"hello" +@export_node_path("Sprite2D", "Sprite3D", "Control", "Node") var nodepath := ^"hello" func test(): diff --git a/modules/gdscript/tests/scripts/runtime/features/await_on_void.gd b/modules/gdscript/tests/scripts/runtime/features/await_on_void.gd index 46b9fbc951..1490a164c9 100644 --- a/modules/gdscript/tests/scripts/runtime/features/await_on_void.gd +++ b/modules/gdscript/tests/scripts/runtime/features/await_on_void.gd @@ -2,6 +2,6 @@ func wait() -> void: pass func test(): - @warning_ignore(redundant_await) + @warning_ignore("redundant_await") await wait() print("end") diff --git a/modules/gdscript/tests/scripts/runtime/features/does_not_override_temp_values.gd b/modules/gdscript/tests/scripts/runtime/features/does_not_override_temp_values.gd index 1d4b400d81..48af734317 100644 --- a/modules/gdscript/tests/scripts/runtime/features/does_not_override_temp_values.gd +++ b/modules/gdscript/tests/scripts/runtime/features/does_not_override_temp_values.gd @@ -7,11 +7,11 @@ func test(): func builtin_method(): var pba := PackedByteArray() - @warning_ignore(return_value_discarded) + @warning_ignore("return_value_discarded") pba.resize(1) # Built-in validated. func builtin_method_static(): var _pba := PackedByteArray() - @warning_ignore(return_value_discarded) + @warning_ignore("return_value_discarded") Vector2.from_angle(PI) # Static built-in validated. diff --git a/modules/gdscript/tests/scripts/runtime/features/gdscript.gd b/modules/gdscript/tests/scripts/runtime/features/gdscript.gd index f2368643de..e686cffc48 100644 --- a/modules/gdscript/tests/scripts/runtime/features/gdscript.gd +++ b/modules/gdscript/tests/scripts/runtime/features/gdscript.gd @@ -11,10 +11,10 @@ class InnerClass: func _init() -> void: prints("Inner") ''' - @warning_ignore(return_value_discarded) + @warning_ignore("return_value_discarded") gdscr.reload() var inst = gdscr.new() - @warning_ignore(unsafe_method_access) + @warning_ignore("unsafe_method_access") inst.test() diff --git a/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd b/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd index cc34e71b01..2f55059334 100644 --- a/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd +++ b/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd @@ -20,26 +20,26 @@ func test_utility(v, f): assert(not f) # Test unary operator reading from `nil`. func test_builtin_call(v, f): - @warning_ignore(unsafe_method_access) + @warning_ignore("unsafe_method_access") v.angle() # Built-in method call. assert(not f) # Test unary operator reading from `nil`. func test_builtin_call_validated(v: Vector2, f): - @warning_ignore(return_value_discarded) + @warning_ignore("return_value_discarded") v.abs() # Built-in method call validated. assert(not f) # Test unary operator reading from `nil`. func test_object_call(v, f): - @warning_ignore(unsafe_method_access) + @warning_ignore("unsafe_method_access") v.get_reference_count() # Native type method call. assert(not f) # Test unary operator reading from `nil`. func test_object_call_method_bind(v: Resource, f): - @warning_ignore(return_value_discarded) + @warning_ignore("return_value_discarded") v.duplicate() # Native type method call with MethodBind. assert(not f) # Test unary operator reading from `nil`. func test_object_call_ptrcall(v: RefCounted, f): - @warning_ignore(return_value_discarded) + @warning_ignore("return_value_discarded") v.get_reference_count() # Native type method call with ptrcall. assert(not f) # Test unary operator reading from `nil`. diff --git a/modules/gdscript/tests/scripts/runtime/features/use_conversion_assign_with_variant_value.gd b/modules/gdscript/tests/scripts/runtime/features/use_conversion_assign_with_variant_value.gd index af3f3cb941..efa8270526 100644 --- a/modules/gdscript/tests/scripts/runtime/features/use_conversion_assign_with_variant_value.gd +++ b/modules/gdscript/tests/scripts/runtime/features/use_conversion_assign_with_variant_value.gd @@ -1,7 +1,7 @@ # https://github.com/godotengine/godot/issues/71172 func test(): - @warning_ignore(narrowing_conversion) + @warning_ignore("narrowing_conversion") var foo: int = 0.0 print(typeof(foo) == TYPE_INT) var dict : Dictionary = {"a":0.0} diff --git a/modules/webxr/doc_classes/WebXRInterface.xml b/modules/webxr/doc_classes/WebXRInterface.xml index ba1750386f..0c18acbcb1 100644 --- a/modules/webxr/doc_classes/WebXRInterface.xml +++ b/modules/webxr/doc_classes/WebXRInterface.xml @@ -90,7 +90,7 @@ You can use both methods to allow your game or app to support a wider or narrower set of devices and input methods, or to allow more advanced interactions with more advanced devices. </description> <tutorials> - <link title="How to make a VR game for WebXR with Godot">https://www.snopekgames.com/blog/2020/how-make-vr-game-webxr-godot</link> + <link title="How to make a VR game for WebXR with Godot 4">https://www.snopekgames.com/tutorial/2023/how-make-vr-game-webxr-godot-4</link> </tutorials> <methods> <method name="get_input_source_target_ray_mode" qualifiers="const"> diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp index 472299b135..c26a00221a 100644 --- a/scene/gui/base_button.cpp +++ b/scene/gui/base_button.cpp @@ -479,14 +479,16 @@ void BaseButton::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disabled"), "set_disabled", "is_disabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "toggle_mode"), "set_toggle_mode", "is_toggle_mode"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shortcut_in_tooltip"), "set_shortcut_in_tooltip", "is_shortcut_in_tooltip_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "button_pressed"), "set_pressed", "is_pressed"); ADD_PROPERTY(PropertyInfo(Variant::INT, "action_mode", PROPERTY_HINT_ENUM, "Button Press,Button Release"), "set_action_mode", "get_action_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "button_mask", PROPERTY_HINT_FLAGS, "Mouse Left, Mouse Right, Mouse Middle"), "set_button_mask", "get_button_mask"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "keep_pressed_outside"), "set_keep_pressed_outside", "is_keep_pressed_outside"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "button_group", PROPERTY_HINT_RESOURCE_TYPE, "ButtonGroup"), "set_button_group", "get_button_group"); + + ADD_GROUP("Shortcut", ""); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shortcut", PROPERTY_HINT_RESOURCE_TYPE, "Shortcut"), "set_shortcut", "get_shortcut"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shortcut_feedback"), "set_shortcut_feedback", "is_shortcut_feedback"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "button_group", PROPERTY_HINT_RESOURCE_TYPE, "ButtonGroup"), "set_button_group", "get_button_group"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shortcut_in_tooltip"), "set_shortcut_in_tooltip", "is_shortcut_in_tooltip_enabled"); BIND_ENUM_CONSTANT(DRAW_NORMAL); BIND_ENUM_CONSTANT(DRAW_PRESSED); diff --git a/scene/gui/button.cpp b/scene/gui/button.cpp index 1e07a53642..2a8b1cd8c4 100644 --- a/scene/gui/button.cpp +++ b/scene/gui/button.cpp @@ -575,9 +575,13 @@ void Button::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT), "set_text", "get_text"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_button_icon", "get_button_icon"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flat"), "set_flat", "is_flat"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clip_text"), "set_clip_text", "get_clip_text"); + + ADD_GROUP("Text Behavior", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "alignment", PROPERTY_HINT_ENUM, "Left,Center,Right"), "set_text_alignment", "get_text_alignment"); ADD_PROPERTY(PropertyInfo(Variant::INT, "text_overrun_behavior", PROPERTY_HINT_ENUM, "Trim Nothing,Trim Characters,Trim Words,Ellipsis,Word Ellipsis"), "set_text_overrun_behavior", "get_text_overrun_behavior"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clip_text"), "set_clip_text", "get_clip_text"); + + ADD_GROUP("Icon Behavior", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "icon_alignment", PROPERTY_HINT_ENUM, "Left,Center,Right"), "set_icon_alignment", "get_icon_alignment"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "expand_icon"), "set_expand_icon", "is_expand_icon"); diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp index 25204f1abf..f951252111 100644 --- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp +++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp @@ -2237,6 +2237,9 @@ RenderGeometryInstance *RenderForwardMobile::geometry_instance_create(RID p_base ginstance->data->base = p_base; ginstance->data->base_type = type; + ginstance->data->dependency_tracker.userdata = ginstance; + ginstance->data->dependency_tracker.changed_callback = _geometry_instance_dependency_changed; + ginstance->data->dependency_tracker.deleted_callback = _geometry_instance_dependency_deleted; ginstance->_mark_dirty(); diff --git a/servers/rendering/shader_language.cpp b/servers/rendering/shader_language.cpp index ba7ffda00a..91e9879bae 100644 --- a/servers/rendering/shader_language.cpp +++ b/servers/rendering/shader_language.cpp @@ -90,8 +90,9 @@ const char *ShaderLanguage::token_names[TK_MAX] = { "IDENTIFIER", "TRUE", "FALSE", - "REAL_CONSTANT", + "FLOAT_CONSTANT", "INT_CONSTANT", + "UINT_CONSTANT", "TYPE_VOID", "TYPE_BOOL", "TYPE_BVEC2", @@ -126,6 +127,7 @@ const char *ShaderLanguage::token_names[TK_MAX] = { "INTERPOLATION_FLAT", "INTERPOLATION_SMOOTH", "CONST", + "STRUCT", "PRECISION_LOW", "PRECISION_MID", "PRECISION_HIGH", @@ -169,6 +171,7 @@ const char *ShaderLanguage::token_names[TK_MAX] = { "CF_DO", "CF_SWITCH", "CF_CASE", + "CF_DEFAULT", "CF_BREAK", "CF_CONTINUE", "CF_RETURN", @@ -185,19 +188,26 @@ const char *ShaderLanguage::token_names[TK_MAX] = { "SEMICOLON", "PERIOD", "UNIFORM", + "UNIFORM_GROUP", "INSTANCE", "GLOBAL", "VARYING", - "IN", - "OUT", - "INOUT", + "ARG_IN", + "ARG_OUT", + "ARG_INOUT", "RENDER_MODE", - "SOURCE_COLOR", "HINT_DEFAULT_WHITE_TEXTURE", "HINT_DEFAULT_BLACK_TEXTURE", "HINT_DEFAULT_TRANSPARENT_TEXTURE", "HINT_NORMAL_TEXTURE", + "HINT_ROUGHNESS_NORMAL_TEXTURE", + "HINT_ROUGHNESS_R", + "HINT_ROUGHNESS_G", + "HINT_ROUGHNESS_B", + "HINT_ROUGHNESS_A", + "HINT_ROUGHNESS_GRAY", "HINT_ANISOTROPY_TEXTURE", + "HINT_SOURCE_COLOR", "HINT_RANGE", "HINT_INSTANCE_INDEX", "HINT_SCREEN_TEXTURE", diff --git a/tests/core/variant/test_variant.h b/tests/core/variant/test_variant.h index 85b53264f2..dfbaa36af8 100644 --- a/tests/core/variant/test_variant.h +++ b/tests/core/variant/test_variant.h @@ -868,6 +868,196 @@ TEST_CASE("[Variant] Basic comparison") { CHECK_NE(Variant(Dictionary()), Variant()); } +TEST_CASE("[Variant] Identity comparison") { + // Value types are compared by value + Variant aabb = AABB(); + CHECK(aabb.identity_compare(aabb)); + CHECK(aabb.identity_compare(AABB())); + CHECK_FALSE(aabb.identity_compare(AABB(Vector3(1, 2, 3), Vector3(1, 2, 3)))); + + Variant basis = Basis(); + CHECK(basis.identity_compare(basis)); + CHECK(basis.identity_compare(Basis())); + CHECK_FALSE(basis.identity_compare(Basis(Quaternion(Vector3(1, 2, 3).normalized(), 45)))); + + Variant bool_var = true; + CHECK(bool_var.identity_compare(bool_var)); + CHECK(bool_var.identity_compare(true)); + CHECK_FALSE(bool_var.identity_compare(false)); + + Variant callable = Callable(); + CHECK(callable.identity_compare(callable)); + CHECK(callable.identity_compare(Callable())); + CHECK_FALSE(callable.identity_compare(Callable(ObjectID(), StringName("lambda")))); + + Variant color = Color(); + CHECK(color.identity_compare(color)); + CHECK(color.identity_compare(Color())); + CHECK_FALSE(color.identity_compare(Color(255, 0, 255))); + + Variant float_var = 1.0; + CHECK(float_var.identity_compare(float_var)); + CHECK(float_var.identity_compare(1.0)); + CHECK_FALSE(float_var.identity_compare(2.0)); + + Variant int_var = 1; + CHECK(int_var.identity_compare(int_var)); + CHECK(int_var.identity_compare(1)); + CHECK_FALSE(int_var.identity_compare(2)); + + Variant nil = Variant(); + CHECK(nil.identity_compare(nil)); + CHECK(nil.identity_compare(Variant())); + CHECK_FALSE(nil.identity_compare(true)); + + Variant node_path = NodePath("godot"); + CHECK(node_path.identity_compare(node_path)); + CHECK(node_path.identity_compare(NodePath("godot"))); + CHECK_FALSE(node_path.identity_compare(NodePath("waiting"))); + + Variant plane = Plane(); + CHECK(plane.identity_compare(plane)); + CHECK(plane.identity_compare(Plane())); + CHECK_FALSE(plane.identity_compare(Plane(Vector3(1, 2, 3), 42))); + + Variant projection = Projection(); + CHECK(projection.identity_compare(projection)); + CHECK(projection.identity_compare(Projection())); + CHECK_FALSE(projection.identity_compare(Projection(Transform3D(Basis(Vector3(1, 2, 3).normalized(), 45), Vector3(1, 2, 3))))); + + Variant quaternion = Quaternion(); + CHECK(quaternion.identity_compare(quaternion)); + CHECK(quaternion.identity_compare(Quaternion())); + CHECK_FALSE(quaternion.identity_compare(Quaternion(Vector3(1, 2, 3).normalized(), 45))); + + Variant rect2 = Rect2(); + CHECK(rect2.identity_compare(rect2)); + CHECK(rect2.identity_compare(Rect2())); + CHECK_FALSE(rect2.identity_compare(Rect2(Point2(Vector2(1, 2)), Size2(Vector2(1, 2))))); + + Variant rect2i = Rect2i(); + CHECK(rect2i.identity_compare(rect2i)); + CHECK(rect2i.identity_compare(Rect2i())); + CHECK_FALSE(rect2i.identity_compare(Rect2i(Point2i(Vector2i(1, 2)), Size2i(Vector2i(1, 2))))); + + Variant rid = RID(); + CHECK(rid.identity_compare(rid)); + CHECK(rid.identity_compare(RID())); + CHECK_FALSE(rid.identity_compare(RID::from_uint64(123))); + + Variant signal = Signal(); + CHECK(signal.identity_compare(signal)); + CHECK(signal.identity_compare(Signal())); + CHECK_FALSE(signal.identity_compare(Signal(ObjectID(), StringName("lambda")))); + + Variant str = "godot"; + CHECK(str.identity_compare(str)); + CHECK(str.identity_compare("godot")); + CHECK_FALSE(str.identity_compare("waiting")); + + Variant str_name = StringName("godot"); + CHECK(str_name.identity_compare(str_name)); + CHECK(str_name.identity_compare(StringName("godot"))); + CHECK_FALSE(str_name.identity_compare(StringName("waiting"))); + + Variant transform2d = Transform2D(); + CHECK(transform2d.identity_compare(transform2d)); + CHECK(transform2d.identity_compare(Transform2D())); + CHECK_FALSE(transform2d.identity_compare(Transform2D(45, Vector2(1, 2)))); + + Variant transform3d = Transform3D(); + CHECK(transform3d.identity_compare(transform3d)); + CHECK(transform3d.identity_compare(Transform3D())); + CHECK_FALSE(transform3d.identity_compare(Transform3D(Basis(Quaternion(Vector3(1, 2, 3).normalized(), 45)), Vector3(1, 2, 3)))); + + Variant vect2 = Vector2(); + CHECK(vect2.identity_compare(vect2)); + CHECK(vect2.identity_compare(Vector2())); + CHECK_FALSE(vect2.identity_compare(Vector2(1, 2))); + + Variant vect2i = Vector2i(); + CHECK(vect2i.identity_compare(vect2i)); + CHECK(vect2i.identity_compare(Vector2i())); + CHECK_FALSE(vect2i.identity_compare(Vector2i(1, 2))); + + Variant vect3 = Vector3(); + CHECK(vect3.identity_compare(vect3)); + CHECK(vect3.identity_compare(Vector3())); + CHECK_FALSE(vect3.identity_compare(Vector3(1, 2, 3))); + + Variant vect3i = Vector3i(); + CHECK(vect3i.identity_compare(vect3i)); + CHECK(vect3i.identity_compare(Vector3i())); + CHECK_FALSE(vect3i.identity_compare(Vector3i(1, 2, 3))); + + Variant vect4 = Vector4(); + CHECK(vect4.identity_compare(vect4)); + CHECK(vect4.identity_compare(Vector4())); + CHECK_FALSE(vect4.identity_compare(Vector4(1, 2, 3, 4))); + + Variant vect4i = Vector4i(); + CHECK(vect4i.identity_compare(vect4i)); + CHECK(vect4i.identity_compare(Vector4i())); + CHECK_FALSE(vect4i.identity_compare(Vector4i(1, 2, 3, 4))); + + // Reference types are compared by reference + Variant array = Array(); + CHECK(array.identity_compare(array)); + CHECK_FALSE(array.identity_compare(Array())); + + Variant dictionary = Dictionary(); + CHECK(dictionary.identity_compare(dictionary)); + CHECK_FALSE(dictionary.identity_compare(Dictionary())); + + Variant packed_byte_array = PackedByteArray(); + CHECK(packed_byte_array.identity_compare(packed_byte_array)); + CHECK_FALSE(packed_byte_array.identity_compare(PackedByteArray())); + + Variant packed_color_array = PackedColorArray(); + CHECK(packed_color_array.identity_compare(packed_color_array)); + CHECK_FALSE(packed_color_array.identity_compare(PackedColorArray())); + + Variant packed_float32_array = PackedFloat32Array(); + CHECK(packed_float32_array.identity_compare(packed_float32_array)); + CHECK_FALSE(packed_float32_array.identity_compare(PackedFloat32Array())); + + Variant packed_float64_array = PackedFloat64Array(); + CHECK(packed_float64_array.identity_compare(packed_float64_array)); + CHECK_FALSE(packed_float64_array.identity_compare(PackedFloat64Array())); + + Variant packed_int32_array = PackedInt32Array(); + CHECK(packed_int32_array.identity_compare(packed_int32_array)); + CHECK_FALSE(packed_int32_array.identity_compare(PackedInt32Array())); + + Variant packed_int64_array = PackedInt64Array(); + CHECK(packed_int64_array.identity_compare(packed_int64_array)); + CHECK_FALSE(packed_int64_array.identity_compare(PackedInt64Array())); + + Variant packed_string_array = PackedStringArray(); + CHECK(packed_string_array.identity_compare(packed_string_array)); + CHECK_FALSE(packed_string_array.identity_compare(PackedStringArray())); + + Variant packed_vector2_array = PackedVector2Array(); + CHECK(packed_vector2_array.identity_compare(packed_vector2_array)); + CHECK_FALSE(packed_vector2_array.identity_compare(PackedVector2Array())); + + Variant packed_vector3_array = PackedVector3Array(); + CHECK(packed_vector3_array.identity_compare(packed_vector3_array)); + CHECK_FALSE(packed_vector3_array.identity_compare(PackedVector3Array())); + + Object obj_one = Object(); + Variant obj_one_var = &obj_one; + Object obj_two = Object(); + Variant obj_two_var = &obj_two; + CHECK(obj_one_var.identity_compare(obj_one_var)); + CHECK_FALSE(obj_one_var.identity_compare(obj_two_var)); + + Variant obj_null_one_var = Variant((Object *)nullptr); + Variant obj_null_two_var = Variant((Object *)nullptr); + CHECK(obj_null_one_var.identity_compare(obj_null_one_var)); + CHECK(obj_null_one_var.identity_compare(obj_null_two_var)); +} + TEST_CASE("[Variant] Nested array comparison") { Array a1 = build_array(1, build_array(2, 3)); Array a2 = build_array(1, build_array(2, 3)); |