summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/gdscript/gdscript.cpp77
-rw-r--r--modules/gdscript/gdscript.h14
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp416
-rw-r--r--modules/gdscript/gdscript_analyzer.h7
-rw-r--r--modules/gdscript/gdscript_byte_codegen.cpp52
-rw-r--r--modules/gdscript/gdscript_byte_codegen.h2
-rw-r--r--modules/gdscript/gdscript_compiler.cpp48
-rw-r--r--modules/gdscript/gdscript_editor.cpp23
-rw-r--r--modules/gdscript/gdscript_parser.cpp32
-rw-r--r--modules/gdscript/gdscript_parser.h4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/assign_enum.gd3
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/assign_enum.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/assign_named_enum.gd3
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/assign_named_enum.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_enum.gd5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_enum.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_int.gd4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_int.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/constant_array_index_assign.gd5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/constant_array_index_assign.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/constant_dictionary_index_assign.gd5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/constant_dictionary_index_assign.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/constant_subscript_type.gd5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/constant_subscript_type.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_bad_method.gd4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_bad_method.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_bad_value.gd4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_bad_value.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_duplicate_bad_method.gd5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_duplicate_bad_method.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_function_parameter_wrong_type.gd8
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_function_parameter_wrong_type.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_function_return_wrong_type.gd8
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_function_return_wrong_type.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_outer_with_wrong_enum_type.gd10
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_outer_with_wrong_enum_type.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_native_bad_value.gd2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_native_bad_value.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_preload_unnamed_assign_to_named.gd6
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_preload_unnamed_assign_to_named.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_shadows_base_enum.gd8
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_shadows_base_enum.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_shadows_outer_enum.gd7
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_shadows_outer_enum.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_unnamed_assign_to_named.gd7
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_unnamed_assign_to_named.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/native_type_errors.gd2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/native_type_errors.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants.gd8
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants.out6
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants_as_variant.gd9
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants_as_variant.out6
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants.gd8
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants.out6
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants_as_variant.gd9
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants_as_variant.out6
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/preload_enum_error.gd6
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/preload_enum_error.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/property_inline_set_type_error.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/enum_access_types.gd29
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/enum_access_types.out13
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/enum_duplicate_into_dict.gd13
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/enum_duplicate_into_dict.out7
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/enum_from_base.gd13
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/enum_from_base.out (renamed from modules/gdscript/tests/scripts/analyzer/features/enum_from_parent.out)0
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/enum_from_outer.gd (renamed from modules/gdscript/tests/scripts/analyzer/features/enum_from_parent.gd)2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/enum_from_outer.out4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/enum_function_typecheck.gd112
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/enum_function_typecheck.out55
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/enum_named_no_shadow.gd16
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/enum_named_no_shadow.out4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/enum_native_access_types.gd19
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/enum_native_access_types.out9
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/enum_typecheck_inner_class.gd86
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/enum_typecheck_inner_class.out19
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/lookup_class.gd50
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/lookup_class.out8
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/lookup_class_external.notest.gd15
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/lookup_signal.gd41
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/lookup_signal.out8
-rw-r--r--modules/gdscript/tests/scripts/parser/features/multiline_assert.gd24
-rw-r--r--modules/gdscript/tests/scripts/parser/features/multiline_assert.out2
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/constant_array_is_deep.gd6
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/constant_array_is_deep.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/constant_array_push_back.gd4
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/constant_array_push_back.out7
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_erase.gd4
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_erase.out7
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_is_deep.gd6
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_is_deep.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/await_on_void.gd7
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/await_on_void.out2
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd45
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.out2
-rw-r--r--modules/gridmap/grid_map.cpp10
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs44
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs14
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs4
-rw-r--r--modules/mono/editor/bindings_generator.cpp2
-rw-r--r--modules/multiplayer/doc_classes/MultiplayerSpawner.xml16
-rw-r--r--modules/multiplayer/doc_classes/SceneMultiplayer.xml1
-rw-r--r--modules/multiplayer/multiplayer_spawner.cpp30
-rw-r--r--modules/multiplayer/multiplayer_spawner.h5
-rw-r--r--modules/multiplayer/scene_multiplayer.cpp1
-rw-r--r--modules/navigation/godot_navigation_server.cpp116
-rw-r--r--modules/navigation/godot_navigation_server.h26
-rw-r--r--modules/openxr/extensions/openxr_extension_wrapper.h2
111 files changed, 1389 insertions, 423 deletions
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index 8dc47cbfd5..7b79c9cf7e 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -1307,7 +1307,7 @@ GDScript *GDScript::_get_gdscript_from_variant(const Variant &p_variant) {
}
void GDScript::_get_dependencies(RBSet<GDScript *> &p_dependencies, const GDScript *p_except) {
- if (skip_dependencies || p_dependencies.has(this)) {
+ if (p_dependencies.has(this)) {
return;
}
p_dependencies.insert(this);
@@ -1365,7 +1365,7 @@ GDScript::GDScript() :
}
}
-void GDScript::_save_orphaned_subclasses() {
+void GDScript::_save_orphaned_subclasses(GDScript::ClearData *p_clear_data) {
struct ClassRefWithName {
ObjectID id;
String fully_qualified_name;
@@ -1381,8 +1381,17 @@ void GDScript::_save_orphaned_subclasses() {
}
// clear subclasses to allow unused subclasses to be deleted
+ for (KeyValue<StringName, Ref<GDScript>> &E : subclasses) {
+ p_clear_data->scripts.insert(E.value);
+ }
subclasses.clear();
// subclasses are also held by constants, clear those as well
+ for (KeyValue<StringName, Variant> &E : constants) {
+ GDScript *gdscr = _get_gdscript_from_variant(E.value);
+ if (gdscr != nullptr) {
+ p_clear_data->scripts.insert(gdscr);
+ }
+ }
constants.clear();
// keep orphan subclass only for subclasses that are still in use
@@ -1413,60 +1422,50 @@ void GDScript::_init_rpc_methods_properties() {
}
}
-void GDScript::clear() {
+void GDScript::clear(GDScript::ClearData *p_clear_data) {
if (clearing) {
return;
}
clearing = true;
- RBSet<GDScript *> must_clear_dependencies = get_must_clear_dependencies();
- HashMap<GDScript *, ObjectID> must_clear_dependencies_objectids;
+ GDScript::ClearData data;
+ GDScript::ClearData *clear_data = p_clear_data;
+ bool is_root = false;
- // Log the objectids before clearing, as a cascade of clear could
- // remove instances that are still in the clear loop
- for (GDScript *E : must_clear_dependencies) {
- must_clear_dependencies_objectids.insert(E, E->get_instance_id());
+ // If `clear_data` is `nullptr`, it means that it's the root.
+ // The root is in charge to clear functions and scripts of itself and its dependencies
+ if (clear_data == nullptr) {
+ clear_data = &data;
+ is_root = true;
}
+ RBSet<GDScript *> must_clear_dependencies = get_must_clear_dependencies();
for (GDScript *E : must_clear_dependencies) {
- Object *obj = ObjectDB::get_instance(must_clear_dependencies_objectids[E]);
- if (obj == nullptr) {
- continue;
- }
-
- E->skip_dependencies = true;
- E->clear();
- E->skip_dependencies = false;
- GDScriptCache::remove_script(E->get_path());
+ clear_data->scripts.insert(E);
+ E->clear(clear_data);
}
- RBSet<StringName> member_function_names;
for (const KeyValue<StringName, GDScriptFunction *> &E : member_functions) {
- member_function_names.insert(E.key);
- }
- for (const StringName &E : member_function_names) {
- if (member_functions.has(E)) {
- memdelete(member_functions[E]);
- }
+ clear_data->functions.insert(E.value);
}
- member_function_names.clear();
member_functions.clear();
for (KeyValue<StringName, GDScript::MemberInfo> &E : member_indices) {
+ clear_data->scripts.insert(E.value.data_type.script_type_ref);
E.value.data_type.script_type_ref = Ref<Script>();
}
if (implicit_initializer) {
- memdelete(implicit_initializer);
+ clear_data->functions.insert(implicit_initializer);
+ implicit_initializer = nullptr;
}
- implicit_initializer = nullptr;
if (implicit_ready) {
- memdelete(implicit_ready);
+ clear_data->functions.insert(implicit_ready);
+ implicit_ready = nullptr;
}
- implicit_ready = nullptr;
- _save_orphaned_subclasses();
+ _save_orphaned_subclasses(clear_data);
#ifdef TOOLS_ENABLED
// Clearing inner class doc, script doc only cleared when the script source deleted.
@@ -1474,7 +1473,21 @@ void GDScript::clear() {
_clear_doc();
}
#endif
- clearing = false;
+
+ // If it's not the root, skip clearing the data
+ if (is_root) {
+ // All dependencies have been accounted for
+ for (GDScriptFunction *E : clear_data->functions) {
+ memdelete(E);
+ }
+ for (Ref<Script> &E : clear_data->scripts) {
+ Ref<GDScript> gdscr = E;
+ if (gdscr.is_valid()) {
+ GDScriptCache::remove_script(gdscr->get_path());
+ }
+ }
+ clear_data->clear();
+ }
}
GDScript::~GDScript() {
diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h
index 24ef67ddaf..a53785a98d 100644
--- a/modules/gdscript/gdscript.h
+++ b/modules/gdscript/gdscript.h
@@ -62,7 +62,6 @@ class GDScript : public Script {
bool tool = false;
bool valid = false;
bool reloading = false;
- bool skip_dependencies = false;
struct MemberInfo {
int index = 0;
@@ -71,6 +70,15 @@ class GDScript : public Script {
GDScriptDataType data_type;
};
+ struct ClearData {
+ RBSet<GDScriptFunction *> functions;
+ RBSet<Ref<Script>> scripts;
+ void clear() {
+ functions.clear();
+ scripts.clear();
+ }
+ };
+
friend class GDScriptInstance;
friend class GDScriptFunction;
friend class GDScriptAnalyzer;
@@ -157,7 +165,7 @@ class GDScript : public Script {
bool _update_exports(bool *r_err = nullptr, bool p_recursive_call = false, PlaceHolderScriptInstance *p_instance_to_update = nullptr);
- void _save_orphaned_subclasses();
+ void _save_orphaned_subclasses(GDScript::ClearData *p_clear_data);
void _init_rpc_methods_properties();
void _get_script_property_list(List<PropertyInfo> *r_list, bool p_include_base) const;
@@ -180,7 +188,7 @@ protected:
static void _bind_methods();
public:
- void clear();
+ void clear(GDScript::ClearData *p_clear_data = nullptr);
virtual bool is_valid() const override { return valid; }
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index 8a07d509a1..e54e5e4c48 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -42,6 +42,9 @@
#include "gdscript_utility_functions.h"
#include "scene/resources/packed_scene.h"
+#define UNNAMED_ENUM "<anonymous enum>"
+#define ENUM_SEPARATOR "::"
+
static MethodInfo info_from_utility_func(const StringName &p_function) {
ERR_FAIL_COND_V(!Variant::has_utility_function(p_function), MethodInfo());
@@ -106,13 +109,26 @@ static GDScriptParser::DataType make_native_meta_type(const StringName &p_class_
return type;
}
-static GDScriptParser::DataType make_native_enum_type(const StringName &p_native_class, const StringName &p_enum_name) {
+// In enum types, native_type is used to store the class (native or otherwise) that the enum belongs to.
+// This disambiguates between similarly named enums in base classes or outer classes
+static GDScriptParser::DataType make_enum_type(const StringName &p_enum_name, const String &p_base_name, const bool p_meta = false) {
GDScriptParser::DataType type;
type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
type.kind = GDScriptParser::DataType::ENUM;
- type.builtin_type = Variant::INT;
+ type.builtin_type = p_meta ? Variant::DICTIONARY : Variant::INT;
+ type.enum_type = p_enum_name;
type.is_constant = true;
- type.is_meta_type = true;
+ type.is_meta_type = p_meta;
+
+ // For enums, native_type is only used to check compatibility in is_type_compatible()
+ // We can set anything readable here for error messages, as long as it uniquely identifies the type of the enum
+ type.native_type = p_base_name + ENUM_SEPARATOR + p_enum_name;
+
+ return type;
+}
+
+static GDScriptParser::DataType make_native_enum_type(const StringName &p_enum_name, const StringName &p_native_class, const bool p_meta = true) {
+ GDScriptParser::DataType type = make_enum_type(p_enum_name, p_native_class, p_meta);
List<StringName> enum_values;
ClassDB::get_enum_constants(p_native_class, p_enum_name, &enum_values);
@@ -134,6 +150,19 @@ static GDScriptParser::DataType make_builtin_meta_type(Variant::Type p_type) {
return type;
}
+static StringName enum_get_value_name(const GDScriptParser::DataType p_type, int64_t p_val) {
+ // Check that an enum has a given value, not key.
+ // Make sure that implicit conversion to int64_t is sensible before calling!
+ HashMap<StringName, int64_t>::ConstIterator i = p_type.enum_values.begin();
+ while (i) {
+ if (i->value == p_val) {
+ return i->key;
+ }
+ ++i;
+ }
+ return StringName();
+}
+
bool GDScriptAnalyzer::has_member_name_conflict_in_script_class(const StringName &p_member_name, const GDScriptParser::ClassNode *p_class, const GDScriptParser::Node *p_member) {
if (p_class->members_indices.has(p_member_name)) {
int index = p_class->members_indices[p_member_name];
@@ -192,6 +221,7 @@ Error GDScriptAnalyzer::check_native_member_name_conflict(const StringName &p_me
}
Error GDScriptAnalyzer::check_class_member_name_conflict(const GDScriptParser::ClassNode *p_class_node, const StringName &p_member_name, const GDScriptParser::Node *p_member_node) {
+ // TODO check outer classes for static members only
const GDScriptParser::DataType *current_data_type = &p_class_node->base_type;
while (current_data_type && current_data_type->kind == GDScriptParser::DataType::Kind::CLASS) {
GDScriptParser::ClassNode *current_class_node = current_data_type->class_type;
@@ -220,9 +250,13 @@ Error GDScriptAnalyzer::check_class_member_name_conflict(const GDScriptParser::C
}
void GDScriptAnalyzer::get_class_node_current_scope_classes(GDScriptParser::ClassNode *p_node, List<GDScriptParser::ClassNode *> *p_list) {
+ ERR_FAIL_NULL(p_node);
+ ERR_FAIL_NULL(p_list);
+
if (p_list->find(p_node) != nullptr) {
return;
}
+
p_list->push_back(p_node);
// TODO: Try to solve class inheritance if not yet resolving.
@@ -591,12 +625,17 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
result = ref->get_parser()->head->get_datatype();
} else if (ClassDB::has_enum(parser->current_class->base_type.native_type, first)) {
// Native enum in current class.
- result = make_native_enum_type(parser->current_class->base_type.native_type, first);
+ result = make_native_enum_type(first, parser->current_class->base_type.native_type);
} else {
// Classes in current scope.
List<GDScriptParser::ClassNode *> script_classes;
+ bool found = false;
get_class_node_current_scope_classes(parser->current_class, &script_classes);
for (GDScriptParser::ClassNode *script_class : script_classes) {
+ if (found) {
+ break;
+ }
+
if (script_class->identifier && script_class->identifier->name == first) {
result = script_class->get_datatype();
break;
@@ -608,14 +647,17 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
switch (member.type) {
case GDScriptParser::ClassNode::Member::CLASS:
result = member.get_datatype();
+ found = true;
break;
case GDScriptParser::ClassNode::Member::ENUM:
result = member.get_datatype();
+ found = true;
break;
case GDScriptParser::ClassNode::Member::CONSTANT:
if (member.get_datatype().is_meta_type) {
result = member.get_datatype();
result.is_meta_type = false;
+ found = true;
break;
} else if (Ref<Script>(member.constant->initializer->reduced_value).is_valid()) {
Ref<GDScript> gdscript = member.constant->initializer->reduced_value;
@@ -636,6 +678,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
result.native_type = script->get_instance_base_type();
result.is_meta_type = false;
}
+ found = true;
break;
}
[[fallthrough]];
@@ -667,15 +710,17 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
}
} else if (result.kind == GDScriptParser::DataType::NATIVE) {
// Only enums allowed for native.
- if (!ClassDB::has_enum(result.native_type, p_type->type_chain[1]->name)) {
- push_error(vformat(R"(Could not find nested type "%s" under base "%s".)", p_type->type_chain[1]->name, result.to_string()), p_type->type_chain[1]);
- return bad_type;
- }
- if (p_type->type_chain.size() > 2) {
- push_error(R"(Enums cannot contain nested types.)", p_type->type_chain[2]);
+ if (ClassDB::has_enum(result.native_type, p_type->type_chain[1]->name)) {
+ if (p_type->type_chain.size() > 2) {
+ push_error(R"(Enums cannot contain nested types.)", p_type->type_chain[2]);
+ return bad_type;
+ } else {
+ result = make_native_enum_type(p_type->type_chain[1]->name, result.native_type);
+ }
+ } else {
+ push_error(vformat(R"(Could not find type "%s" in "%s".)", p_type->type_chain[1]->name, first), p_type->type_chain[1]);
return bad_type;
}
- result = make_native_enum_type(result.native_type, p_type->type_chain[1]->name);
} else {
push_error(vformat(R"(Could not find nested type "%s" under base "%s".)", p_type->type_chain[1]->name, result.to_string()), p_type->type_chain[1]);
return bad_type;
@@ -804,15 +849,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;
- enum_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
- enum_type.kind = GDScriptParser::DataType::ENUM;
- enum_type.builtin_type = Variant::DICTIONARY;
- enum_type.enum_type = member.m_enum->identifier->name;
- enum_type.native_type = p_class->fqcn + "." + member.m_enum->identifier->name;
- enum_type.is_meta_type = true;
- enum_type.is_constant = true;
+ GDScriptParser::DataType enum_type = make_enum_type(member.m_enum->identifier->name, p_class->fqcn, true);
const GDScriptParser::EnumNode *prev_enum = current_enum;
current_enum = member.m_enum;
@@ -846,6 +883,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
current_enum = prev_enum;
+ dictionary.set_read_only(true);
member.m_enum->set_datatype(enum_type);
member.m_enum->dictionary = dictionary;
@@ -892,11 +930,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);
- GDScriptParser::DataType datatype;
- datatype.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
- datatype.kind = GDScriptParser::DataType::BUILTIN;
- datatype.builtin_type = Variant::INT;
- member.enum_value.identifier->set_datatype(datatype);
+ member.enum_value.identifier->set_datatype(make_enum_type(UNNAMED_ENUM, p_class->fqcn, false));
} break;
case GDScriptParser::ClassNode::Member::CLASS:
check_class_member_name_conflict(p_class, member.m_class->identifier->name, member.m_class);
@@ -1330,7 +1364,7 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *
}
} else {
if (p_function->return_type != nullptr) {
- p_function->set_datatype(resolve_datatype(p_function->return_type));
+ p_function->set_datatype(type_from_metatype(resolve_datatype(p_function->return_type)));
} else {
// In case the function is not typed, we can safely assume it's a Variant, so it's okay to mark as "inferred" here.
// It's not "undetected" to not mix up with unknown functions.
@@ -1364,7 +1398,7 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *
if (!valid) {
// Compute parent signature as a string to show in the error message.
- String parent_signature = function_name.operator String() + "(";
+ String parent_signature = String(function_name) + "(";
int j = 0;
for (const GDScriptParser::DataType &par_type : parameters_types) {
if (j > 0) {
@@ -1507,9 +1541,9 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi
if (is_constant) {
if (p_assignable->initializer->type == GDScriptParser::Node::ARRAY) {
- const_fold_array(static_cast<GDScriptParser::ArrayNode *>(p_assignable->initializer));
+ const_fold_array(static_cast<GDScriptParser::ArrayNode *>(p_assignable->initializer), true);
} else if (p_assignable->initializer->type == GDScriptParser::Node::DICTIONARY) {
- const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(p_assignable->initializer));
+ const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(p_assignable->initializer), true);
}
if (!p_assignable->initializer->is_constant) {
push_error(vformat(R"(Assigned value for %s "%s" isn't a constant expression.)", p_kind, p_assignable->identifier->name), p_assignable->initializer);
@@ -2063,6 +2097,10 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
GDScriptParser::DataType assignee_type = p_assignment->assignee->get_datatype();
+ if (assignee_type.is_constant || (p_assignment->assignee->type == GDScriptParser::Node::SUBSCRIPT && static_cast<GDScriptParser::SubscriptNode *>(p_assignment->assignee)->base->is_constant)) {
+ push_error("Cannot assign a new value to a constant.", p_assignment->assignee);
+ }
+
// Check if assigned value is an array literal, so we can make it a typed array too if appropriate.
if (assignee_type.has_container_element_type() && p_assignment->assigned_value->type == GDScriptParser::Node::ARRAY) {
update_array_literal_element_type(assignee_type, static_cast<GDScriptParser::ArrayNode *>(p_assignment->assigned_value));
@@ -2070,10 +2108,6 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
GDScriptParser::DataType assigned_value_type = p_assignment->assigned_value->get_datatype();
- if (assignee_type.is_constant) {
- push_error("Cannot assign a new value to a constant.", p_assignment->assignee);
- }
-
bool compatible = true;
GDScriptParser::DataType op_type = assigned_value_type;
if (p_assignment->operation != GDScriptParser::AssignmentNode::OP_NONE) {
@@ -2081,13 +2115,15 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
}
p_assignment->set_datatype(op_type);
+ // If Assignee is a variant, then you can assign anything
+ // When the assigned value has a known type, further checks are possible.
if (assignee_type.is_hard_type() && !assignee_type.is_variant() && op_type.is_hard_type()) {
if (compatible) {
compatible = is_type_compatible(assignee_type, op_type, true, p_assignment->assigned_value);
if (!compatible) {
// Try reverse test since it can be a masked subtype.
if (!is_type_compatible(op_type, assignee_type, true)) {
- push_error(vformat(R"(Cannot assign a value of type "%s" to a target of type "%s".)", assigned_value_type.to_string(), assignee_type.to_string()), p_assignment->assigned_value);
+ push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", assigned_value_type.to_string(), assignee_type.to_string()), p_assignment->assigned_value);
} else {
// TODO: Add warning.
mark_node_unsafe(p_assignment);
@@ -2141,7 +2177,7 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
if (!id_type.is_hard_type()) {
id_type.kind = GDScriptParser::DataType::VARIANT;
id_type.type_source = GDScriptParser::DataType::UNDETECTED;
- identifier->variable_source->set_datatype(id_type);
+ identifier->bind_source->set_datatype(id_type);
}
} break;
default:
@@ -2368,7 +2404,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
switch (err.error) {
case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT:
- push_error(vformat(R"(Invalid argument for %s constructor: argument %d should be %s but is %s.)", Variant::get_type_name(builtin_type), err.argument + 1,
+ push_error(vformat(R"(Invalid argument for %s constructor: argument %d should be "%s" but is "%s".)", Variant::get_type_name(builtin_type), err.argument + 1,
Variant::get_type_name(Variant::Type(err.expected)), p_call->arguments[err.argument]->get_datatype().to_string()),
p_call->arguments[err.argument]);
break;
@@ -2466,7 +2502,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
} else if (GDScriptUtilityFunctions::function_exists(function_name)) {
MethodInfo function_info = GDScriptUtilityFunctions::get_function_info(function_name);
- if (!p_is_root && function_info.return_val.type == Variant::NIL && ((function_info.return_val.usage & PROPERTY_USAGE_NIL_IS_VARIANT) == 0)) {
+ if (!p_is_root && !p_is_await && function_info.return_val.type == Variant::NIL && ((function_info.return_val.usage & PROPERTY_USAGE_NIL_IS_VARIANT) == 0)) {
push_error(vformat(R"*(Cannot get return value of call to "%s()" because it returns "void".)*", function_name), p_call);
}
@@ -2484,7 +2520,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
switch (err.error) {
case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: {
PropertyInfo wrong_arg = function_info.arguments[err.argument];
- push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be %s but is %s.)*", function_name, err.argument + 1,
+ push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be "%s" but is "%s".)*", function_name, err.argument + 1,
type_from_property(wrong_arg).to_string(), p_call->arguments[err.argument]->get_datatype().to_string()),
p_call->arguments[err.argument]);
} break;
@@ -2513,7 +2549,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
} else if (Variant::has_utility_function(function_name)) {
MethodInfo function_info = info_from_utility_func(function_name);
- if (!p_is_root && function_info.return_val.type == Variant::NIL && ((function_info.return_val.usage & PROPERTY_USAGE_NIL_IS_VARIANT) == 0)) {
+ if (!p_is_root && !p_is_await && function_info.return_val.type == Variant::NIL && ((function_info.return_val.usage & PROPERTY_USAGE_NIL_IS_VARIANT) == 0)) {
push_error(vformat(R"*(Cannot get return value of call to "%s()" because it returns "void".)*", function_name), p_call);
}
@@ -2537,7 +2573,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
expected_type_name = Variant::get_type_name((Variant::Type)err.expected);
}
- push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be %s but is %s.)*", function_name, err.argument + 1,
+ push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be "%s" but is "%s".)*", function_name, err.argument + 1,
expected_type_name, p_call->arguments[err.argument]->get_datatype().to_string()),
p_call->arguments[err.argument]);
} break;
@@ -2659,7 +2695,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
mark_lambda_use_self();
}
- if (!p_is_root && return_type.is_hard_type() && return_type.kind == GDScriptParser::DataType::BUILTIN && return_type.builtin_type == Variant::NIL) {
+ if (!p_is_root && !p_is_await && return_type.is_hard_type() && return_type.kind == GDScriptParser::DataType::BUILTIN && return_type.builtin_type == Variant::NIL) {
push_error(vformat(R"*(Cannot get return value of call to "%s()" because it returns "void".)*", p_call->function_name), p_call);
}
@@ -2683,8 +2719,10 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
} else {
bool found = false;
- // Check if the name exists as something else.
- if (!p_call->is_super && callee_type != GDScriptParser::Node::NONE) {
+ // Enums do not have functions other than the built-in dictionary ones.
+ if (base_type.kind == GDScriptParser::DataType::ENUM && base_type.is_meta_type) {
+ push_error(vformat(R"*(Enums only have Dictionary built-in methods. Function "%s()" does not exist for enum "%s".)*", p_call->function_name, base_type.enum_type), p_call->callee);
+ } else if (!p_call->is_super && callee_type != GDScriptParser::Node::NONE) { // Check if the name exists as something else.
GDScriptParser::IdentifierNode *callee_id;
if (callee_type == GDScriptParser::Node::IDENTIFIER) {
callee_id = static_cast<GDScriptParser::IdentifierNode *>(p_call->callee);
@@ -2714,7 +2752,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
String base_name = is_self && !p_call->is_super ? "self" : base_type.to_string();
push_error(vformat(R"*(Function "%s()" not found in base %s.)*", p_call->function_name, base_name), p_call->is_super ? p_call : p_call->callee);
} else if (!found && (!p_call->is_super && base_type.is_hard_type() && base_type.kind == GDScriptParser::DataType::NATIVE && base_type.is_meta_type)) {
- push_error(vformat(R"*(Static function "%s()" not found in base "%s".)*", p_call->function_name, base_type.native_type.operator String()), p_call);
+ push_error(vformat(R"*(Static function "%s()" not found in base "%s".)*", p_call->function_name, base_type.native_type), p_call);
}
}
@@ -2742,19 +2780,48 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) {
GDScriptParser::DataType op_type = p_cast->operand->get_datatype();
if (!op_type.is_variant()) {
bool valid = false;
+ bool more_informative_error = false;
if (op_type.kind == GDScriptParser::DataType::ENUM && cast_type.kind == GDScriptParser::DataType::ENUM) {
- // Enum types are compatible between each other, so it's a safe cast.
- valid = true;
+ // Enum casts are compatible when value from operand exists in target enum
+ if (p_cast->operand->is_constant && p_cast->operand->reduced) {
+ if (enum_get_value_name(cast_type, p_cast->operand->reduced_value) != StringName()) {
+ valid = true;
+ } else {
+ valid = false;
+ more_informative_error = true;
+ push_error(vformat(R"(Invalid cast. Enum "%s" does not have value corresponding to "%s.%s" (%d).)",
+ cast_type.to_string(), op_type.enum_type,
+ enum_get_value_name(op_type, p_cast->operand->reduced_value), // Can never be null
+ p_cast->operand->reduced_value.operator uint64_t()),
+ p_cast->cast_type);
+ }
+ } else {
+ // Can't statically tell whether int has a corresponding enum value. Valid but dangerous!
+ mark_node_unsafe(p_cast);
+ valid = true;
+ }
} else if (op_type.kind == GDScriptParser::DataType::BUILTIN && op_type.builtin_type == Variant::INT && cast_type.kind == GDScriptParser::DataType::ENUM) {
- // Convertint int to enum is always valid.
- valid = true;
+ // Int assignment to enum not valid when exact int assigned is known but is not an enum value
+ if (p_cast->operand->is_constant && p_cast->operand->reduced) {
+ if (enum_get_value_name(cast_type, p_cast->operand->reduced_value) != StringName()) {
+ valid = true;
+ } else {
+ valid = false;
+ more_informative_error = true;
+ push_error(vformat(R"(Invalid cast. Enum "%s" does not have enum value %d.)", cast_type.to_string(), p_cast->operand->reduced_value.operator uint64_t()), p_cast->cast_type);
+ }
+ } else {
+ // Can't statically tell whether int has a corresponding enum value. Valid but dangerous!
+ mark_node_unsafe(p_cast);
+ valid = true;
+ }
} else if (op_type.kind == GDScriptParser::DataType::BUILTIN && cast_type.kind == GDScriptParser::DataType::BUILTIN) {
valid = Variant::can_convert(op_type.builtin_type, cast_type.builtin_type);
} else if (op_type.kind != GDScriptParser::DataType::BUILTIN && cast_type.kind != GDScriptParser::DataType::BUILTIN) {
valid = is_type_compatible(cast_type, op_type) || is_type_compatible(op_type, cast_type);
}
- if (!valid) {
+ if (!valid && !more_informative_error) {
push_error(vformat(R"(Invalid cast. Cannot convert from "%s" to "%s".)", op_type.to_string(), cast_type.to_string()), p_cast->cast_type);
}
}
@@ -2852,6 +2919,18 @@ GDScriptParser::DataType GDScriptAnalyzer::make_global_class_meta_type(const Str
}
}
+void GDScriptAnalyzer::reduce_identifier_from_base_set_class(GDScriptParser::IdentifierNode *p_identifier, GDScriptParser::DataType p_identifier_datatype) {
+ ERR_FAIL_NULL(p_identifier);
+
+ p_identifier->set_datatype(p_identifier_datatype);
+ Error err = OK;
+ GDScript *scr = GDScriptCache::get_full_script(p_identifier_datatype.script_path, err).ptr();
+ ERR_FAIL_COND_MSG(err != OK, "Error while getting full script.");
+ scr = scr->find_class(p_identifier_datatype.class_type->fqcn);
+ p_identifier->reduced_value = scr;
+ p_identifier->is_constant = true;
+}
+
void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNode *p_identifier, GDScriptParser::DataType *p_base) {
if (!p_identifier->get_datatype().has_no_type()) {
return;
@@ -2869,26 +2948,14 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
if (base.kind == GDScriptParser::DataType::ENUM) {
if (base.is_meta_type) {
if (base.enum_values.has(name)) {
+ p_identifier->set_datatype(type_from_metatype(base));
p_identifier->is_constant = true;
p_identifier->reduced_value = base.enum_values[name];
-
- GDScriptParser::DataType result;
- result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
- result.kind = GDScriptParser::DataType::ENUM;
- result.is_constant = true;
- result.builtin_type = Variant::INT;
- result.native_type = base.native_type;
- result.enum_type = base.enum_type;
- result.enum_values = base.enum_values;
- p_identifier->set_datatype(result);
return;
- } else {
- // Consider as a Dictionary, so it can be anything.
- // This will be evaluated in the next if block.
- base.kind = GDScriptParser::DataType::BUILTIN;
- base.builtin_type = Variant::DICTIONARY;
- base.is_meta_type = false;
}
+
+ // Enum does not have this value, return.
+ return;
} else {
push_error(R"(Cannot get property from enum value.)", p_identifier);
return;
@@ -2942,102 +3009,91 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
}
GDScriptParser::ClassNode *base_class = base.class_type;
+ List<GDScriptParser::ClassNode *> script_classes;
+ bool is_base = true;
- // TODO: Switch current class/function/suite here to avoid misrepresenting identifiers (in recursive reduce calls).
- while (base_class != nullptr) {
- if (base_class->identifier && base_class->identifier->name == name) {
- p_identifier->set_datatype(base_class->get_datatype());
+ if (base_class != nullptr) {
+ get_class_node_current_scope_classes(base_class, &script_classes);
+ }
+
+ for (GDScriptParser::ClassNode *script_class : script_classes) {
+ if (p_base == nullptr && script_class->identifier && script_class->identifier->name == name) {
+ reduce_identifier_from_base_set_class(p_identifier, script_class->get_datatype());
return;
}
- if (base_class->has_member(name)) {
- resolve_class_member(base_class, name, p_identifier);
+ if (script_class->has_member(name)) {
+ resolve_class_member(script_class, name, p_identifier);
- GDScriptParser::ClassNode::Member member = base_class->get_member(name);
- p_identifier->set_datatype(member.get_datatype());
+ GDScriptParser::ClassNode::Member member = script_class->get_member(name);
switch (member.type) {
- case GDScriptParser::ClassNode::Member::CONSTANT:
+ case GDScriptParser::ClassNode::Member::CONSTANT: {
+ p_identifier->set_datatype(member.get_datatype());
p_identifier->is_constant = true;
p_identifier->reduced_value = member.constant->initializer->reduced_value;
p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CONSTANT;
p_identifier->constant_source = member.constant;
- break;
- case GDScriptParser::ClassNode::Member::ENUM_VALUE:
+ return;
+ }
+
+ case GDScriptParser::ClassNode::Member::ENUM_VALUE: {
+ p_identifier->set_datatype(member.get_datatype());
p_identifier->is_constant = true;
p_identifier->reduced_value = member.enum_value.value;
p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CONSTANT;
- break;
- case GDScriptParser::ClassNode::Member::ENUM:
+ return;
+ }
+
+ case GDScriptParser::ClassNode::Member::ENUM: {
+ p_identifier->set_datatype(member.get_datatype());
p_identifier->is_constant = true;
p_identifier->reduced_value = member.m_enum->dictionary;
p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CONSTANT;
- break;
- case GDScriptParser::ClassNode::Member::VARIABLE:
- p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_VARIABLE;
- p_identifier->variable_source = member.variable;
- member.variable->usages += 1;
- break;
- case GDScriptParser::ClassNode::Member::SIGNAL:
- p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_SIGNAL;
- break;
- case GDScriptParser::ClassNode::Member::FUNCTION:
- p_identifier->set_datatype(make_callable_type(member.function->info));
- break;
- case GDScriptParser::ClassNode::Member::CLASS:
- if (p_base != nullptr && p_base->is_constant) {
- p_identifier->is_constant = true;
- p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CONSTANT;
-
- Error err = OK;
- GDScript *scr = GDScriptCache::get_full_script(base.script_path, err).ptr();
- ERR_FAIL_COND_MSG(err != OK, "Error while getting subscript full script.");
- scr = scr->find_class(p_identifier->get_datatype().class_type->fqcn);
- p_identifier->reduced_value = scr;
+ return;
+ }
+
+ case GDScriptParser::ClassNode::Member::VARIABLE: {
+ if (is_base && !base.is_meta_type) {
+ p_identifier->set_datatype(member.get_datatype());
+ p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_VARIABLE;
+ p_identifier->variable_source = member.variable;
+ member.variable->usages += 1;
+ return;
}
- break;
- default:
- break; // Type already set.
- }
- return;
- }
- // Check outer constants.
- // TODO: Allow outer static functions.
- if (base_class->outer != nullptr) {
- List<GDScriptParser::ClassNode *> script_classes;
- get_class_node_current_scope_classes(base_class->outer, &script_classes);
- for (GDScriptParser::ClassNode *script_class : script_classes) {
- if (script_class->has_member(name)) {
- resolve_class_member(script_class, name, p_identifier);
-
- GDScriptParser::ClassNode::Member member = script_class->get_member(name);
- switch (member.type) {
- case GDScriptParser::ClassNode::Member::CONSTANT:
- // TODO: Make sure loops won't cause problem. And make special error message for those.
- p_identifier->set_datatype(member.get_datatype());
- p_identifier->is_constant = true;
- p_identifier->reduced_value = member.constant->initializer->reduced_value;
- return;
- case GDScriptParser::ClassNode::Member::ENUM_VALUE:
- p_identifier->set_datatype(member.get_datatype());
- p_identifier->is_constant = true;
- p_identifier->reduced_value = member.enum_value.value;
- return;
- case GDScriptParser::ClassNode::Member::ENUM:
- p_identifier->set_datatype(member.get_datatype());
- p_identifier->is_constant = true;
- p_identifier->reduced_value = member.m_enum->dictionary;
- return;
- case GDScriptParser::ClassNode::Member::CLASS:
- p_identifier->set_datatype(member.get_datatype());
- return;
- default:
- break;
+ } break;
+
+ case GDScriptParser::ClassNode::Member::SIGNAL: {
+ if (is_base && !base.is_meta_type) {
+ p_identifier->set_datatype(member.get_datatype());
+ p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_SIGNAL;
+ return;
+ }
+ } break;
+
+ case GDScriptParser::ClassNode::Member::FUNCTION: {
+ if (is_base && !base.is_meta_type) {
+ p_identifier->set_datatype(make_callable_type(member.function->info));
+ return;
}
+ } break;
+
+ case GDScriptParser::ClassNode::Member::CLASS: {
+ reduce_identifier_from_base_set_class(p_identifier, member.get_datatype());
+ return;
+ }
+
+ default: {
+ // Do nothing
}
}
}
- base_class = base_class->base_type.class_type;
+ if (is_base) {
+ is_base = script_class->base_type.class_type != nullptr;
+ if (!is_base && p_base != nullptr) {
+ break;
+ }
+ }
}
// Check native members. No need for native class recursion because Node exposes all Object's properties.
@@ -3067,35 +3123,39 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
return;
}
if (ClassDB::has_enum(native, name)) {
- p_identifier->set_datatype(make_native_enum_type(native, name));
+ p_identifier->set_datatype(make_native_enum_type(name, native));
p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CONSTANT;
return;
}
bool valid = false;
+
int64_t int_constant = ClassDB::get_integer_constant(native, name, &valid);
if (valid) {
p_identifier->is_constant = true;
p_identifier->reduced_value = int_constant;
- p_identifier->set_datatype(type_from_variant(int_constant, p_identifier));
p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CONSTANT;
- return;
+
+ // Check whether this constant, which exists, belongs to an enum
+ StringName enum_name = ClassDB::get_integer_constant_enum(native, name);
+ if (enum_name != StringName()) {
+ p_identifier->set_datatype(make_native_enum_type(enum_name, native, false));
+ } else {
+ p_identifier->set_datatype(type_from_variant(int_constant, p_identifier));
+ }
}
}
}
void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_identifier, bool can_be_builtin) {
- // TODO: This is opportunity to further infer types.
+ // TODO: This is an opportunity to further infer types.
- // Check if we are inside and enum. This allows enum values to access other elements of the same enum.
+ // Check if we are inside an enum. This allows enum values to access other elements of the same enum.
if (current_enum) {
for (int i = 0; i < current_enum->values.size(); i++) {
const GDScriptParser::EnumNode::Value &element = current_enum->values[i];
if (element.identifier->name == p_identifier->name) {
- GDScriptParser::DataType type;
- type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
- type.kind = element.parent_enum->identifier ? GDScriptParser::DataType::ENUM : GDScriptParser::DataType::BUILTIN;
- type.builtin_type = Variant::INT;
- type.is_constant = true;
+ StringName enum_name = current_enum->identifier->name ? current_enum->identifier->name : UNNAMED_ENUM;
+ GDScriptParser::DataType type = make_enum_type(enum_name, parser->current_class->fqcn, false);
if (element.parent_enum->identifier) {
type.enum_type = element.parent_enum->identifier->name;
}
@@ -3164,18 +3224,20 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
}
if (found_source) {
- if ((p_identifier->source == GDScriptParser::IdentifierNode::MEMBER_VARIABLE || p_identifier->source == GDScriptParser::IdentifierNode::INHERITED_VARIABLE) && parser->current_function && parser->current_function->is_static) {
+ bool source_is_variable = p_identifier->source == GDScriptParser::IdentifierNode::MEMBER_VARIABLE || p_identifier->source == GDScriptParser::IdentifierNode::INHERITED_VARIABLE;
+ bool source_is_signal = p_identifier->source == GDScriptParser::IdentifierNode::MEMBER_SIGNAL;
+ if ((source_is_variable || source_is_signal) && parser->current_function && parser->current_function->is_static) {
// Get the parent function above any lambda.
GDScriptParser::FunctionNode *parent_function = parser->current_function;
while (parent_function->source_lambda) {
parent_function = parent_function->source_lambda->parent_function;
}
- push_error(vformat(R"*(Cannot access instance variable "%s" from the static function "%s()".)*", p_identifier->name, parent_function->identifier->name), p_identifier);
+ push_error(vformat(R"*(Cannot access %s "%s" from the static function "%s()".)*", source_is_signal ? "signal" : "instance variable", p_identifier->name, parent_function->identifier->name), p_identifier);
}
if (!lambda_stack.is_empty()) {
- // If the identifier is a member variable (including the native class properties), we consider the lambda to be using `self`, so we keep a reference to the current instance.
- if (p_identifier->source == GDScriptParser::IdentifierNode::MEMBER_VARIABLE || p_identifier->source == GDScriptParser::IdentifierNode::INHERITED_VARIABLE) {
+ // If the identifier is a member variable (including the native class properties) or a signal, we consider the lambda to be using `self`, so we keep a reference to the current instance.
+ if (source_is_variable || source_is_signal) {
mark_lambda_use_self();
return; // No need to capture.
}
@@ -3411,9 +3473,9 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
reduce_expression(p_subscript->base);
if (p_subscript->base->type == GDScriptParser::Node::ARRAY) {
- const_fold_array(static_cast<GDScriptParser::ArrayNode *>(p_subscript->base));
+ const_fold_array(static_cast<GDScriptParser::ArrayNode *>(p_subscript->base), false);
} else if (p_subscript->base->type == GDScriptParser::Node::DICTIONARY) {
- const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(p_subscript->base));
+ const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(p_subscript->base), false);
}
}
@@ -3473,12 +3535,12 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
Variant value = p_subscript->base->reduced_value.get(p_subscript->index->reduced_value, &valid);
if (!valid) {
push_error(vformat(R"(Cannot get index "%s" from "%s".)", p_subscript->index->reduced_value, p_subscript->base->reduced_value), p_subscript->index);
+ result_type.kind = GDScriptParser::DataType::VARIANT;
} else {
p_subscript->is_constant = true;
p_subscript->reduced_value = value;
result_type = type_from_variant(value, p_subscript);
}
- result_type.kind = GDScriptParser::DataType::VARIANT;
} else {
GDScriptParser::DataType base_type = p_subscript->base->get_datatype();
GDScriptParser::DataType index_type = p_subscript->index->get_datatype();
@@ -3738,20 +3800,17 @@ void GDScriptAnalyzer::reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op)
p_unary_op->set_datatype(result);
}
-void GDScriptAnalyzer::const_fold_array(GDScriptParser::ArrayNode *p_array) {
- bool all_is_constant = true;
-
+void GDScriptAnalyzer::const_fold_array(GDScriptParser::ArrayNode *p_array, bool p_is_const) {
for (int i = 0; i < p_array->elements.size(); i++) {
GDScriptParser::ExpressionNode *element = p_array->elements[i];
if (element->type == GDScriptParser::Node::ARRAY) {
- const_fold_array(static_cast<GDScriptParser::ArrayNode *>(element));
+ const_fold_array(static_cast<GDScriptParser::ArrayNode *>(element), p_is_const);
} else if (element->type == GDScriptParser::Node::DICTIONARY) {
- const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(element));
+ const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(element), p_is_const);
}
- all_is_constant = all_is_constant && element->is_constant;
- if (!all_is_constant) {
+ if (!element->is_constant) {
return;
}
}
@@ -3761,24 +3820,24 @@ void GDScriptAnalyzer::const_fold_array(GDScriptParser::ArrayNode *p_array) {
for (int i = 0; i < p_array->elements.size(); i++) {
array[i] = p_array->elements[i]->reduced_value;
}
+ if (p_is_const) {
+ array.set_read_only(true);
+ }
p_array->is_constant = true;
p_array->reduced_value = array;
}
-void GDScriptAnalyzer::const_fold_dictionary(GDScriptParser::DictionaryNode *p_dictionary) {
- bool all_is_constant = true;
-
+void GDScriptAnalyzer::const_fold_dictionary(GDScriptParser::DictionaryNode *p_dictionary, bool p_is_const) {
for (int i = 0; i < p_dictionary->elements.size(); i++) {
const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i];
if (element.value->type == GDScriptParser::Node::ARRAY) {
- const_fold_array(static_cast<GDScriptParser::ArrayNode *>(element.value));
+ const_fold_array(static_cast<GDScriptParser::ArrayNode *>(element.value), p_is_const);
} else if (element.value->type == GDScriptParser::Node::DICTIONARY) {
- const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(element.value));
+ const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(element.value), p_is_const);
}
- all_is_constant = all_is_constant && element.key->is_constant && element.value->is_constant;
- if (!all_is_constant) {
+ if (!element.key->is_constant || !element.value->is_constant) {
return;
}
}
@@ -3788,6 +3847,9 @@ void GDScriptAnalyzer::const_fold_dictionary(GDScriptParser::DictionaryNode *p_d
const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i];
dict[element.key->reduced_value] = element.value->reduced_value;
}
+ if (p_is_const) {
+ dict.set_read_only(true);
+ }
p_dictionary->is_constant = true;
p_dictionary->reduced_value = dict;
}
@@ -3865,12 +3927,13 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_va
return result;
}
-GDScriptParser::DataType GDScriptAnalyzer::type_from_metatype(const GDScriptParser::DataType &p_meta_type) const {
+GDScriptParser::DataType GDScriptAnalyzer::type_from_metatype(const GDScriptParser::DataType &p_meta_type) {
GDScriptParser::DataType result = p_meta_type;
result.is_meta_type = false;
- result.is_constant = false;
if (p_meta_type.kind == GDScriptParser::DataType::ENUM) {
result.builtin_type = Variant::INT;
+ } else {
+ result.is_constant = false;
}
return result;
}
@@ -3928,11 +3991,12 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bo
r_default_arg_count = 0;
StringName function_name = p_function;
+ bool was_enum = false;
if (p_base_type.kind == GDScriptParser::DataType::ENUM) {
+ was_enum = true;
if (p_base_type.is_meta_type) {
// Enum type can be treated as a dictionary value.
p_base_type.kind = GDScriptParser::DataType::BUILTIN;
- p_base_type.builtin_type = Variant::DICTIONARY;
p_base_type.is_meta_type = false;
} else {
push_error("Cannot call function on enum value.", p_source);
@@ -3955,6 +4019,10 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bo
if (E.name == p_function) {
function_signature_from_info(E, r_return_type, r_par_types, r_default_arg_count, r_static, r_vararg);
r_static = Variant::is_builtin_method_static(p_base_type.builtin_type, function_name);
+ // Cannot use non-const methods on enums.
+ if (!r_static && was_enum && !(E.flags & METHOD_FLAG_CONST)) {
+ push_error(vformat(R"*(Cannot call non-const Dictionary function "%s()" on enum "%s".)*", p_function, p_base_type.enum_type), p_source);
+ }
return true;
}
}
@@ -4102,7 +4170,7 @@ bool GDScriptAnalyzer::validate_call_arg(const List<GDScriptParser::DataType> &p
// Supertypes are acceptable for dynamic compliance, but it's unsafe.
mark_node_unsafe(p_call);
if (!is_type_compatible(arg_type, par_type)) {
- push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be %s but is %s.)*",
+ push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be "%s" but is "%s".)*",
p_call->function_name, i + 1, par_type.to_string(), arg_type.to_string()),
p_call->arguments[i]);
valid = false;
diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h
index a7f8e3b556..ecae0b4629 100644
--- a/modules/gdscript/gdscript_analyzer.h
+++ b/modules/gdscript/gdscript_analyzer.h
@@ -102,12 +102,12 @@ class GDScriptAnalyzer {
void reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternary_op);
void reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op);
- void const_fold_array(GDScriptParser::ArrayNode *p_array);
- void const_fold_dictionary(GDScriptParser::DictionaryNode *p_dictionary);
+ void const_fold_array(GDScriptParser::ArrayNode *p_array, bool p_is_const);
+ void const_fold_dictionary(GDScriptParser::DictionaryNode *p_dictionary, bool p_is_const);
// Helpers.
GDScriptParser::DataType type_from_variant(const Variant &p_value, const GDScriptParser::Node *p_source);
- GDScriptParser::DataType type_from_metatype(const GDScriptParser::DataType &p_meta_type) const;
+ static GDScriptParser::DataType type_from_metatype(const GDScriptParser::DataType &p_meta_type);
GDScriptParser::DataType type_from_property(const PropertyInfo &p_property) const;
GDScriptParser::DataType make_global_class_meta_type(const StringName &p_class_name, const GDScriptParser::Node *p_source);
bool get_function_signature(GDScriptParser::Node *p_source, bool p_is_constructor, GDScriptParser::DataType base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg);
@@ -123,6 +123,7 @@ class GDScriptAnalyzer {
void mark_lambda_use_self();
bool class_exists(const StringName &p_class) const;
Ref<GDScriptParserRef> get_parser_for(const String &p_path);
+ static void reduce_identifier_from_base_set_class(GDScriptParser::IdentifierNode *p_identifier, GDScriptParser::DataType p_identifier_datatype);
#ifdef DEBUG_ENABLED
bool is_shadowing(GDScriptParser::IdentifierNode *p_local, const String &p_context);
#endif
diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp
index 8b3ae17e5f..15a17edb65 100644
--- a/modules/gdscript/gdscript_byte_codegen.cpp
+++ b/modules/gdscript/gdscript_byte_codegen.cpp
@@ -920,13 +920,23 @@ void GDScriptByteCodeGenerator::write_cast(const Address &p_target, const Addres
append(index);
}
+GDScriptCodeGenerator::Address GDScriptByteCodeGenerator::get_call_target(const GDScriptCodeGenerator::Address &p_target) {
+ if (p_target.mode == Address::NIL) {
+ uint32_t addr = add_temporary(p_target.type);
+ pop_temporary();
+ return Address(Address::TEMPORARY, addr, p_target.type);
+ } else {
+ return p_target;
+ }
+}
+
void GDScriptByteCodeGenerator::write_call(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) {
append_opcode_and_argcount(p_target.mode == Address::NIL ? GDScriptFunction::OPCODE_CALL : GDScriptFunction::OPCODE_CALL_RETURN, 2 + p_arguments.size());
for (int i = 0; i < p_arguments.size(); i++) {
append(p_arguments[i]);
}
append(p_base);
- append(p_target);
+ append(get_call_target(p_target));
append(p_arguments.size());
append(p_function_name);
}
@@ -936,7 +946,7 @@ void GDScriptByteCodeGenerator::write_super_call(const Address &p_target, const
for (int i = 0; i < p_arguments.size(); i++) {
append(p_arguments[i]);
}
- append(p_target);
+ append(get_call_target(p_target));
append(p_arguments.size());
append(p_function_name);
}
@@ -947,7 +957,7 @@ void GDScriptByteCodeGenerator::write_call_async(const Address &p_target, const
append(p_arguments[i]);
}
append(p_base);
- append(p_target);
+ append(get_call_target(p_target));
append(p_arguments.size());
append(p_function_name);
}
@@ -957,7 +967,7 @@ void GDScriptByteCodeGenerator::write_call_gdscript_utility(const Address &p_tar
for (int i = 0; i < p_arguments.size(); i++) {
append(p_arguments[i]);
}
- append(p_target);
+ append(get_call_target(p_target));
append(p_arguments.size());
append(p_function);
}
@@ -983,7 +993,7 @@ void GDScriptByteCodeGenerator::write_call_utility(const Address &p_target, cons
for (int i = 0; i < p_arguments.size(); i++) {
append(p_arguments[i]);
}
- append(p_target);
+ append(get_call_target(p_target));
append(p_arguments.size());
append(Variant::get_validated_utility_function(p_function));
} else {
@@ -991,7 +1001,7 @@ void GDScriptByteCodeGenerator::write_call_utility(const Address &p_target, cons
for (int i = 0; i < p_arguments.size(); i++) {
append(p_arguments[i]);
}
- append(p_target);
+ append(get_call_target(p_target));
append(p_arguments.size());
append(p_function);
}
@@ -1035,7 +1045,7 @@ void GDScriptByteCodeGenerator::write_call_builtin_type(const Address &p_target,
append(p_arguments[i]);
}
append(p_base);
- append(p_target);
+ append(get_call_target(p_target));
append(p_arguments.size());
append(Variant::get_validated_builtin_method(p_type, p_method));
}
@@ -1064,7 +1074,7 @@ void GDScriptByteCodeGenerator::write_call_builtin_type_static(const Address &p_
for (int i = 0; i < p_arguments.size(); i++) {
append(p_arguments[i]);
}
- append(p_target);
+ append(get_call_target(p_target));
append(p_type);
append(p_method);
append(p_arguments.size());
@@ -1085,7 +1095,7 @@ void GDScriptByteCodeGenerator::write_call_builtin_type_static(const Address &p_
append(p_arguments[i]);
}
append(Address()); // No base since it's static.
- append(p_target);
+ append(get_call_target(p_target));
append(p_arguments.size());
append(Variant::get_validated_builtin_method(p_type, p_method));
}
@@ -1101,7 +1111,7 @@ void GDScriptByteCodeGenerator::write_call_native_static(const Address &p_target
for (int i = 0; i < p_arguments.size(); i++) {
append(p_arguments[i]);
}
- append(p_target);
+ append(get_call_target(p_target));
append(method);
append(p_arguments.size());
return;
@@ -1114,7 +1124,7 @@ void GDScriptByteCodeGenerator::write_call_method_bind(const Address &p_target,
append(p_arguments[i]);
}
append(p_base);
- append(p_target);
+ append(get_call_target(p_target));
append(p_arguments.size());
append(p_method);
}
@@ -1178,7 +1188,7 @@ void GDScriptByteCodeGenerator::write_call_ptrcall(const Address &p_target, cons
append(p_arguments[i]);
}
append(p_base);
- append(p_target);
+ append(get_call_target(p_target));
append(p_arguments.size());
append(p_method);
if (is_ptrcall) {
@@ -1194,7 +1204,7 @@ void GDScriptByteCodeGenerator::write_call_self(const Address &p_target, const S
append(p_arguments[i]);
}
append(GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS);
- append(p_target);
+ append(get_call_target(p_target));
append(p_arguments.size());
append(p_function_name);
}
@@ -1205,7 +1215,7 @@ void GDScriptByteCodeGenerator::write_call_self_async(const Address &p_target, c
append(p_arguments[i]);
}
append(GDScriptFunction::ADDR_SELF);
- append(p_target);
+ append(get_call_target(p_target));
append(p_arguments.size());
append(p_function_name);
}
@@ -1216,7 +1226,7 @@ void GDScriptByteCodeGenerator::write_call_script_function(const Address &p_targ
append(p_arguments[i]);
}
append(p_base);
- append(p_target);
+ append(get_call_target(p_target));
append(p_arguments.size());
append(p_function_name);
}
@@ -1227,7 +1237,7 @@ void GDScriptByteCodeGenerator::write_lambda(const Address &p_target, GDScriptFu
append(p_captures[i]);
}
- append(p_target);
+ append(get_call_target(p_target));
append(p_captures.size());
append(p_function);
}
@@ -1266,7 +1276,7 @@ void GDScriptByteCodeGenerator::write_construct(const Address &p_target, Variant
for (int i = 0; i < p_arguments.size(); i++) {
append(p_arguments[i]);
}
- append(p_target);
+ append(get_call_target(p_target));
append(p_arguments.size());
append(Variant::get_validated_constructor(p_type, valid_constructor));
return;
@@ -1277,7 +1287,7 @@ void GDScriptByteCodeGenerator::write_construct(const Address &p_target, Variant
for (int i = 0; i < p_arguments.size(); i++) {
append(p_arguments[i]);
}
- append(p_target);
+ append(get_call_target(p_target));
append(p_arguments.size());
append(p_type);
}
@@ -1287,7 +1297,7 @@ void GDScriptByteCodeGenerator::write_construct_array(const Address &p_target, c
for (int i = 0; i < p_arguments.size(); i++) {
append(p_arguments[i]);
}
- append(p_target);
+ append(get_call_target(p_target));
append(p_arguments.size());
}
@@ -1296,7 +1306,7 @@ void GDScriptByteCodeGenerator::write_construct_typed_array(const Address &p_tar
for (int i = 0; i < p_arguments.size(); i++) {
append(p_arguments[i]);
}
- append(p_target);
+ append(get_call_target(p_target));
if (p_element_type.script_type) {
Variant script_type = Ref<Script>(p_element_type.script_type);
int addr = get_constant_pos(script_type);
@@ -1315,7 +1325,7 @@ void GDScriptByteCodeGenerator::write_construct_dictionary(const Address &p_targ
for (int i = 0; i < p_arguments.size(); i++) {
append(p_arguments[i]);
}
- append(p_target);
+ append(get_call_target(p_target));
append(p_arguments.size() / 2); // This is number of key-value pairs, so only half of actual arguments.
}
diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h
index ba4847813f..258b9fb0c2 100644
--- a/modules/gdscript/gdscript_byte_codegen.h
+++ b/modules/gdscript/gdscript_byte_codegen.h
@@ -309,6 +309,8 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
}
}
+ Address get_call_target(const Address &p_target);
+
int address_of(const Address &p_address) {
switch (p_address.mode) {
case Address::SELF:
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index 2d36692c3c..beed6e90d2 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -213,7 +213,7 @@ static bool _have_exact_arguments(const MethodBind *p_method, const Vector<GDScr
}
GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression, bool p_root, bool p_initializer, const GDScriptCodeGenerator::Address &p_index_addr) {
- if (p_expression->is_constant) {
+ if (p_expression->is_constant && !p_expression->get_datatype().is_meta_type) {
return codegen.add_constant(p_expression->reduced_value);
}
@@ -520,10 +520,12 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
case GDScriptParser::Node::CALL: {
const GDScriptParser::CallNode *call = static_cast<const GDScriptParser::CallNode *>(p_expression);
GDScriptDataType type = _gdtype_from_datatype(call->get_datatype(), codegen.script);
- GDScriptCodeGenerator::Address result = codegen.add_temporary(type);
- GDScriptCodeGenerator::Address nil = GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::NIL);
-
- GDScriptCodeGenerator::Address return_addr = p_root ? nil : result;
+ GDScriptCodeGenerator::Address result;
+ if (p_root) {
+ result = GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::NIL);
+ } else {
+ result = codegen.add_temporary(type);
+ }
Vector<GDScriptCodeGenerator::Address> arguments;
for (int i = 0; i < call->arguments.size(); i++) {
@@ -538,20 +540,20 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
// Construct a built-in type.
Variant::Type vtype = GDScriptParser::get_builtin_type(static_cast<GDScriptParser::IdentifierNode *>(call->callee)->name);
- gen->write_construct(return_addr, vtype, arguments);
+ gen->write_construct(result, vtype, arguments);
} else if (!call->is_super && call->callee->type == GDScriptParser::Node::IDENTIFIER && Variant::has_utility_function(call->function_name)) {
// Variant utility function.
- gen->write_call_utility(return_addr, call->function_name, arguments);
+ gen->write_call_utility(result, call->function_name, arguments);
} else if (!call->is_super && call->callee->type == GDScriptParser::Node::IDENTIFIER && GDScriptUtilityFunctions::function_exists(call->function_name)) {
// GDScript utility function.
- gen->write_call_gdscript_utility(return_addr, GDScriptUtilityFunctions::get_function(call->function_name), arguments);
+ gen->write_call_gdscript_utility(result, GDScriptUtilityFunctions::get_function(call->function_name), arguments);
} else {
// Regular function.
const GDScriptParser::ExpressionNode *callee = call->callee;
if (call->is_super) {
// Super call.
- gen->write_super_call(return_addr, call->function_name, arguments);
+ gen->write_super_call(result, call->function_name, arguments);
} else {
if (callee->type == GDScriptParser::Node::IDENTIFIER) {
// Self function call.
@@ -563,24 +565,24 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
if (_have_exact_arguments(method, arguments)) {
// Exact arguments, use ptrcall.
- gen->write_call_ptrcall(return_addr, self, method, arguments);
+ gen->write_call_ptrcall(result, self, method, arguments);
} else {
// Not exact arguments, but still can use method bind call.
- gen->write_call_method_bind(return_addr, self, method, arguments);
+ gen->write_call_method_bind(result, self, method, arguments);
}
} else if ((codegen.function_node && codegen.function_node->is_static) || call->function_name == "new") {
GDScriptCodeGenerator::Address self;
self.mode = GDScriptCodeGenerator::Address::CLASS;
if (within_await) {
- gen->write_call_async(return_addr, self, call->function_name, arguments);
+ gen->write_call_async(result, self, call->function_name, arguments);
} else {
- gen->write_call(return_addr, self, call->function_name, arguments);
+ gen->write_call(result, self, call->function_name, arguments);
}
} else {
if (within_await) {
- gen->write_call_self_async(return_addr, call->function_name, arguments);
+ gen->write_call_self_async(result, call->function_name, arguments);
} else {
- gen->write_call_self(return_addr, call->function_name, arguments);
+ gen->write_call_self(result, call->function_name, arguments);
}
}
} else if (callee->type == GDScriptParser::Node::SUBSCRIPT) {
@@ -589,18 +591,18 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
if (subscript->is_attribute) {
// May be static built-in method call.
if (!call->is_super && subscript->base->type == GDScriptParser::Node::IDENTIFIER && GDScriptParser::get_builtin_type(static_cast<GDScriptParser::IdentifierNode *>(subscript->base)->name) < Variant::VARIANT_MAX) {
- gen->write_call_builtin_type_static(return_addr, GDScriptParser::get_builtin_type(static_cast<GDScriptParser::IdentifierNode *>(subscript->base)->name), subscript->attribute->name, arguments);
+ gen->write_call_builtin_type_static(result, GDScriptParser::get_builtin_type(static_cast<GDScriptParser::IdentifierNode *>(subscript->base)->name), subscript->attribute->name, arguments);
} else if (!call->is_super && subscript->base->type == GDScriptParser::Node::IDENTIFIER && call->function_name != SNAME("new") &&
ClassDB::class_exists(static_cast<GDScriptParser::IdentifierNode *>(subscript->base)->name) && !Engine::get_singleton()->has_singleton(static_cast<GDScriptParser::IdentifierNode *>(subscript->base)->name)) {
// It's a static native method call.
- gen->write_call_native_static(return_addr, static_cast<GDScriptParser::IdentifierNode *>(subscript->base)->name, subscript->attribute->name, arguments);
+ gen->write_call_native_static(result, static_cast<GDScriptParser::IdentifierNode *>(subscript->base)->name, subscript->attribute->name, arguments);
} else {
GDScriptCodeGenerator::Address base = _parse_expression(codegen, r_error, subscript->base);
if (r_error) {
return GDScriptCodeGenerator::Address();
}
if (within_await) {
- gen->write_call_async(return_addr, base, call->function_name, arguments);
+ gen->write_call_async(result, base, call->function_name, arguments);
} else if (base.type.has_type && base.type.kind != GDScriptDataType::BUILTIN) {
// Native method, use faster path.
StringName class_name;
@@ -613,18 +615,18 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
MethodBind *method = ClassDB::get_method(class_name, call->function_name);
if (_have_exact_arguments(method, arguments)) {
// Exact arguments, use ptrcall.
- gen->write_call_ptrcall(return_addr, base, method, arguments);
+ gen->write_call_ptrcall(result, base, method, arguments);
} else {
// Not exact arguments, but still can use method bind call.
- gen->write_call_method_bind(return_addr, base, method, arguments);
+ gen->write_call_method_bind(result, base, method, arguments);
}
} else {
- gen->write_call(return_addr, base, call->function_name, arguments);
+ gen->write_call(result, base, call->function_name, arguments);
}
} else if (base.type.has_type && base.type.kind == GDScriptDataType::BUILTIN) {
- gen->write_call_builtin_type(return_addr, base, base.type.builtin_type, call->function_name, arguments);
+ gen->write_call_builtin_type(result, base, base.type.builtin_type, call->function_name, arguments);
} else {
- gen->write_call(return_addr, base, call->function_name, arguments);
+ gen->write_call(result, base, call->function_name, arguments);
}
if (base.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
gen->pop_temporary();
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index 6f8b90bd06..0a1ae46927 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -995,9 +995,8 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base
ERR_FAIL_COND(p_recursion_depth > COMPLETION_RECURSION_LIMIT);
GDScriptParser::DataType base_type = p_base.type;
- bool _static = base_type.is_meta_type;
- if (_static && base_type.kind != GDScriptParser::DataType::BUILTIN) {
+ if (base_type.is_meta_type && base_type.kind != GDScriptParser::DataType::BUILTIN && base_type.kind != GDScriptParser::DataType::ENUM) {
ScriptLanguage::CodeCompletionOption option("new", ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION, ScriptLanguage::LOCATION_LOCAL);
option.insert_text += "(";
r_result.insert(option.display, option);
@@ -1006,7 +1005,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base
while (!base_type.has_no_type()) {
switch (base_type.kind) {
case GDScriptParser::DataType::CLASS: {
- _find_identifiers_in_class(base_type.class_type, p_only_functions, _static, false, r_result, p_recursion_depth + 1);
+ _find_identifiers_in_class(base_type.class_type, p_only_functions, base_type.is_meta_type, false, r_result, p_recursion_depth + 1);
// This already finds all parent identifiers, so we are done.
base_type = GDScriptParser::DataType();
} break;
@@ -1014,7 +1013,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base
Ref<Script> scr = base_type.script_type;
if (scr.is_valid()) {
if (!p_only_functions) {
- if (!_static) {
+ if (!base_type.is_meta_type) {
List<PropertyInfo> members;
scr->get_script_property_list(&members);
for (const PropertyInfo &E : members) {
@@ -1090,7 +1089,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base
r_result.insert(option.display, option);
}
- if (!_static || Engine::get_singleton()->has_singleton(type)) {
+ if (!base_type.is_meta_type || Engine::get_singleton()->has_singleton(type)) {
List<PropertyInfo> pinfo;
ClassDB::get_property_list(type, &pinfo);
for (const PropertyInfo &E : pinfo) {
@@ -1107,7 +1106,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base
}
}
- bool only_static = _static && !Engine::get_singleton()->has_singleton(type);
+ bool only_static = base_type.is_meta_type && !Engine::get_singleton()->has_singleton(type);
List<MethodInfo> methods;
ClassDB::get_method_list(type, &methods, false, true);
@@ -1129,6 +1128,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base
}
return;
} break;
+ case GDScriptParser::DataType::ENUM:
case GDScriptParser::DataType::BUILTIN: {
Callable::CallError err;
Variant tmp;
@@ -1156,6 +1156,10 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base
List<MethodInfo> methods;
tmp.get_method_list(&methods);
for (const MethodInfo &E : methods) {
+ if (base_type.kind == GDScriptParser::DataType::ENUM && base_type.is_meta_type && !(E.flags & METHOD_FLAG_CONST)) {
+ // Enum types are static and cannot change, therefore we skip non-const dictionary methods.
+ continue;
+ }
ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION);
if (E.arguments.size()) {
option.insert_text += "(";
@@ -1364,6 +1368,9 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context,
if (p_expression->is_constant) {
// Already has a value, so just use that.
r_type = _type_from_variant(p_expression->reduced_value);
+ if (p_expression->get_datatype().kind == GDScriptParser::DataType::ENUM) {
+ r_type.type = p_expression->get_datatype();
+ }
found = true;
} else {
switch (p_expression->type) {
@@ -2977,7 +2984,9 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
// The path needs quotes if it's not a valid identifier (with an exception
// for "/" as path separator, which also doesn't require quotes).
if (!opt.replace("/", "_").is_valid_identifier()) {
- opt = opt.quote(quote_style); // Handle user preference.
+ // Ignore quote_style and just use double quotes for paths with apostrophes.
+ // Double quotes don't need to be checked because they're not valid in node and property names.
+ opt = opt.quote(opt.contains("'") ? "\"" : quote_style); // Handle user preference.
}
ScriptLanguage::CodeCompletionOption option(opt, ScriptLanguage::CODE_COMPLETION_KIND_NODE_PATH);
options.insert(option.display, option);
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index 6107bb37c8..a6b8537074 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -1299,16 +1299,18 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() {
EnumNode::Value item;
GDScriptParser::IdentifierNode *identifier = parse_identifier();
#ifdef DEBUG_ENABLED
- for (MethodInfo &info : gdscript_funcs) {
- if (info.name == identifier->name) {
+ if (!named) { // Named enum identifiers do not shadow anything since you can only access them with NamedEnum.ENUM_VALUE
+ for (MethodInfo &info : gdscript_funcs) {
+ if (info.name == identifier->name) {
+ push_warning(identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, "enum member", identifier->name, "built-in function");
+ }
+ }
+ if (Variant::has_utility_function(identifier->name)) {
push_warning(identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, "enum member", identifier->name, "built-in function");
+ } else if (ClassDB::class_exists(identifier->name)) {
+ push_warning(identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, "enum member", identifier->name, "global class");
}
}
- if (Variant::has_utility_function(identifier->name)) {
- push_warning(identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, "enum member", identifier->name, "built-in function");
- } else if (ClassDB::class_exists(identifier->name)) {
- push_warning(identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, "enum member", identifier->name, "global class");
- }
#endif
item.identifier = identifier;
item.parent_enum = enum_node;
@@ -1799,24 +1801,29 @@ GDScriptParser::AssertNode *GDScriptParser::parse_assert() {
// TODO: Add assert message.
AssertNode *assert = alloc_node<AssertNode>();
+ push_multiline(true);
consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected "(" after "assert".)");
+
assert->condition = parse_expression(false);
if (assert->condition == nullptr) {
push_error("Expected expression to assert.");
+ pop_multiline();
complete_extents(assert);
return nullptr;
}
- if (match(GDScriptTokenizer::Token::COMMA)) {
- // Error message.
+ if (match(GDScriptTokenizer::Token::COMMA) && !check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) {
assert->message = parse_expression(false);
if (assert->message == nullptr) {
push_error(R"(Expected error message for assert after ",".)");
+ pop_multiline();
complete_extents(assert);
return nullptr;
}
+ match(GDScriptTokenizer::Token::COMMA);
}
+ pop_multiline();
consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after assert expression.)*");
complete_extents(assert);
@@ -4087,8 +4094,11 @@ String GDScriptParser::DataType::to_string() const {
}
return native_type.operator String();
}
- case ENUM:
- return enum_type.operator String() + " (enum)";
+ case ENUM: {
+ // native_type contains either the native class defining the enum
+ // or the fully qualified class name of the script defining the enum
+ return String(native_type).get_file(); // Remove path, keep filename
+ }
case RESOLVING:
case UNRESOLVED:
return "<unresolved type>";
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index 65eace8088..4bb02c4ea3 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -185,8 +185,8 @@ public:
case BUILTIN:
return builtin_type == p_other.builtin_type;
case NATIVE:
- case ENUM:
- return native_type == p_other.native_type && enum_type == p_other.enum_type;
+ case ENUM: // Enums use native_type to identify the enum and its base class.
+ return native_type == p_other.native_type;
case SCRIPT:
return script_type == p_other.script_type;
case CLASS:
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/assign_enum.gd b/modules/gdscript/tests/scripts/analyzer/errors/assign_enum.gd
new file mode 100644
index 0000000000..8123fc53d9
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/assign_enum.gd
@@ -0,0 +1,3 @@
+enum { V }
+func test():
+ V = 1
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/assign_enum.out b/modules/gdscript/tests/scripts/analyzer/errors/assign_enum.out
new file mode 100644
index 0000000000..5275183da2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/assign_enum.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot assign a new value to a constant.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/assign_named_enum.gd b/modules/gdscript/tests/scripts/analyzer/errors/assign_named_enum.gd
new file mode 100644
index 0000000000..da2b13d690
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/assign_named_enum.gd
@@ -0,0 +1,3 @@
+enum NamedEnum { V }
+func test():
+ NamedEnum.V = 1
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/assign_named_enum.out b/modules/gdscript/tests/scripts/analyzer/errors/assign_named_enum.out
new file mode 100644
index 0000000000..5275183da2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/assign_named_enum.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot assign a new value to a constant.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_enum.gd b/modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_enum.gd
new file mode 100644
index 0000000000..71616ea3af
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_enum.gd
@@ -0,0 +1,5 @@
+enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 }
+enum MyOtherEnum { OTHER_ENUM_VALUE_1, OTHER_ENUM_VALUE_2, OTHER_ENUM_VALUE_3 }
+
+func test():
+ print(MyOtherEnum.OTHER_ENUM_VALUE_3 as MyEnum)
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_enum.out b/modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_enum.out
new file mode 100644
index 0000000000..3a8d2a205a
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_enum.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Invalid cast. Enum "cast_enum_bad_enum.gd::MyEnum" does not have value corresponding to "MyOtherEnum.OTHER_ENUM_VALUE_3" (2).
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_int.gd b/modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_int.gd
new file mode 100644
index 0000000000..60a31fb318
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_int.gd
@@ -0,0 +1,4 @@
+enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 }
+
+func test():
+ print(2 as MyEnum)
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_int.out b/modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_int.out
new file mode 100644
index 0000000000..bc0d8b7834
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_int.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Invalid cast. Enum "cast_enum_bad_int.gd::MyEnum" does not have enum value 2.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/constant_array_index_assign.gd b/modules/gdscript/tests/scripts/analyzer/errors/constant_array_index_assign.gd
new file mode 100644
index 0000000000..b8603dd4ca
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/constant_array_index_assign.gd
@@ -0,0 +1,5 @@
+const array: Array = [0]
+
+func test():
+ var key: int = 0
+ array[key] = 0
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/constant_array_index_assign.out b/modules/gdscript/tests/scripts/analyzer/errors/constant_array_index_assign.out
new file mode 100644
index 0000000000..5275183da2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/constant_array_index_assign.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot assign a new value to a constant.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/constant_dictionary_index_assign.gd b/modules/gdscript/tests/scripts/analyzer/errors/constant_dictionary_index_assign.gd
new file mode 100644
index 0000000000..9b5112b788
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/constant_dictionary_index_assign.gd
@@ -0,0 +1,5 @@
+const dictionary := {}
+
+func test():
+ var key: int = 0
+ dictionary[key] = 0
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/constant_dictionary_index_assign.out b/modules/gdscript/tests/scripts/analyzer/errors/constant_dictionary_index_assign.out
new file mode 100644
index 0000000000..5275183da2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/constant_dictionary_index_assign.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot assign a new value to a constant.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/constant_subscript_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/constant_subscript_type.gd
new file mode 100644
index 0000000000..87fbe1229c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/constant_subscript_type.gd
@@ -0,0 +1,5 @@
+const base := [0]
+
+func test():
+ var sub := base[0]
+ if sub is String: pass
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/constant_subscript_type.out b/modules/gdscript/tests/scripts/analyzer/errors/constant_subscript_type.out
new file mode 100644
index 0000000000..54c190cf8a
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/constant_subscript_type.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Expression is of type "int" so it can't be of type "String".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_bad_method.gd b/modules/gdscript/tests/scripts/analyzer/errors/enum_bad_method.gd
new file mode 100644
index 0000000000..2940c03515
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_bad_method.gd
@@ -0,0 +1,4 @@
+enum Enum {V1, V2}
+
+func test():
+ Enum.clear()
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_bad_method.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_bad_method.out
new file mode 100644
index 0000000000..9ca86eca9c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_bad_method.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot call non-const Dictionary function "clear()" on enum "Enum".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_bad_value.gd b/modules/gdscript/tests/scripts/analyzer/errors/enum_bad_value.gd
new file mode 100644
index 0000000000..a66e2714d9
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_bad_value.gd
@@ -0,0 +1,4 @@
+enum Enum {V1, V2}
+
+func test():
+ var bad = Enum.V3
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_bad_value.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_bad_value.out
new file mode 100644
index 0000000000..ddbdc17a42
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_bad_value.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot find member "V3" in base "enum_bad_value.gd::Enum".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.out
index fde7e92f8c..02c4633586 100644
--- a/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.out
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.out
@@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
-Cannot assign a value of type "MyOtherEnum (enum)" to a target of type "MyEnum (enum)".
+Value of type "enum_class_var_assign_with_wrong_enum_type.gd::MyOtherEnum" cannot be assigned to a variable of type "enum_class_var_assign_with_wrong_enum_type.gd::MyEnum".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.out
index 6fa2682d0a..441cccbf7b 100644
--- a/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.out
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.out
@@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
-Cannot assign a value of type MyOtherEnum (enum) to variable "class_var" with specified type MyEnum (enum).
+Cannot assign a value of type enum_class_var_init_with_wrong_enum_type.gd::MyOtherEnum to variable "class_var" with specified type enum_class_var_init_with_wrong_enum_type.gd::MyEnum.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_duplicate_bad_method.gd b/modules/gdscript/tests/scripts/analyzer/errors/enum_duplicate_bad_method.gd
new file mode 100644
index 0000000000..2c7dfafd06
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_duplicate_bad_method.gd
@@ -0,0 +1,5 @@
+enum Enum {V1, V2}
+
+func test():
+ var Enum2 = Enum
+ Enum2.clear()
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_duplicate_bad_method.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_duplicate_bad_method.out
new file mode 100644
index 0000000000..9ca86eca9c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_duplicate_bad_method.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot call non-const Dictionary function "clear()" on enum "Enum".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_function_parameter_wrong_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/enum_function_parameter_wrong_type.gd
new file mode 100644
index 0000000000..62ac1c3108
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_function_parameter_wrong_type.gd
@@ -0,0 +1,8 @@
+enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 }
+enum MyOtherEnum { OTHER_ENUM_VALUE_1, OTHER_ENUM_VALUE_2 }
+
+func enum_func(e : MyEnum) -> void:
+ print(e)
+
+func test():
+ enum_func(MyOtherEnum.OTHER_ENUM_VALUE_1)
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_function_parameter_wrong_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_function_parameter_wrong_type.out
new file mode 100644
index 0000000000..e85f7d6f9f
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_function_parameter_wrong_type.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Invalid argument for "enum_func()" function: argument 1 should be "enum_function_parameter_wrong_type.gd::MyEnum" but is "enum_function_parameter_wrong_type.gd::MyOtherEnum".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_function_return_wrong_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/enum_function_return_wrong_type.gd
new file mode 100644
index 0000000000..18b3ffb0fc
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_function_return_wrong_type.gd
@@ -0,0 +1,8 @@
+enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 }
+enum MyOtherEnum { OTHER_ENUM_VALUE_1, OTHER_ENUM_VALUE_2 }
+
+func enum_func() -> MyEnum:
+ return MyOtherEnum.OTHER_ENUM_VALUE_1
+
+func test():
+ print(enum_func())
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_function_return_wrong_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_function_return_wrong_type.out
new file mode 100644
index 0000000000..f7ea3267fa
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_function_return_wrong_type.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot return value of type "enum_function_return_wrong_type.gd::MyOtherEnum" because the function return type is "enum_function_return_wrong_type.gd::MyEnum".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_outer_with_wrong_enum_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_outer_with_wrong_enum_type.gd
new file mode 100644
index 0000000000..2b006f1f69
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_outer_with_wrong_enum_type.gd
@@ -0,0 +1,10 @@
+enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 }
+
+class InnerClass:
+ enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 }
+
+func test():
+ var local_var: MyEnum = MyEnum.ENUM_VALUE_1
+ print(local_var)
+ local_var = InnerClass.MyEnum.ENUM_VALUE_2
+ print(local_var)
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_outer_with_wrong_enum_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_outer_with_wrong_enum_type.out
new file mode 100644
index 0000000000..38df5a0cd8
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_outer_with_wrong_enum_type.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Value of type "enum_local_var_assign_outer_with_wrong_enum_type.gd::InnerClass::MyEnum" cannot be assigned to a variable of type "enum_local_var_assign_outer_with_wrong_enum_type.gd::MyEnum".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.out
index fde7e92f8c..2adcbd9edf 100644
--- a/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.out
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.out
@@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
-Cannot assign a value of type "MyOtherEnum (enum)" to a target of type "MyEnum (enum)".
+Value of type "enum_local_var_assign_with_wrong_enum_type.gd::MyOtherEnum" cannot be assigned to a variable of type "enum_local_var_assign_with_wrong_enum_type.gd::MyEnum".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.out
index 07fb19f1ff..331113dd30 100644
--- a/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.out
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.out
@@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
-Cannot assign a value of type MyOtherEnum (enum) to variable "local_var" with specified type MyEnum (enum).
+Cannot assign a value of type enum_local_var_init_with_wrong_enum_type.gd::MyOtherEnum to variable "local_var" with specified type enum_local_var_init_with_wrong_enum_type.gd::MyEnum.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_native_bad_value.gd b/modules/gdscript/tests/scripts/analyzer/errors/enum_native_bad_value.gd
new file mode 100644
index 0000000000..744c2e47ce
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_native_bad_value.gd
@@ -0,0 +1,2 @@
+func test():
+ var _bad = TileSet.TileShape.THIS_DOES_NOT_EXIST
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_native_bad_value.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_native_bad_value.out
new file mode 100644
index 0000000000..49f041a2dd
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_native_bad_value.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot find member "THIS_DOES_NOT_EXIST" in base "TileSet::TileShape".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_preload_unnamed_assign_to_named.gd b/modules/gdscript/tests/scripts/analyzer/errors/enum_preload_unnamed_assign_to_named.gd
new file mode 100644
index 0000000000..98f1f3ec2d
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_preload_unnamed_assign_to_named.gd
@@ -0,0 +1,6 @@
+enum MyEnum { VALUE_A, VALUE_B, VALUE_C = 42 }
+func test():
+ const P = preload("../features/enum_value_from_parent.gd")
+ var local_var: MyEnum
+ local_var = P.VALUE_B
+ print(local_var)
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_preload_unnamed_assign_to_named.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_preload_unnamed_assign_to_named.out
new file mode 100644
index 0000000000..6298c026b4
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_preload_unnamed_assign_to_named.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Value of type "enum_value_from_parent.gd::<anonymous enum>" cannot be assigned to a variable of type "enum_preload_unnamed_assign_to_named.gd::MyEnum".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_shadows_base_enum.gd b/modules/gdscript/tests/scripts/analyzer/errors/enum_shadows_base_enum.gd
new file mode 100644
index 0000000000..96904c297a
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_shadows_base_enum.gd
@@ -0,0 +1,8 @@
+class A:
+ enum { V }
+
+class B extends A:
+ enum { V }
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_shadows_base_enum.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_shadows_base_enum.out
new file mode 100644
index 0000000000..7961a1a481
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_shadows_base_enum.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+The member "V" already exists in parent class A.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_shadows_outer_enum.gd b/modules/gdscript/tests/scripts/analyzer/errors/enum_shadows_outer_enum.gd
new file mode 100644
index 0000000000..f3f3b5ffeb
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_shadows_outer_enum.gd
@@ -0,0 +1,7 @@
+enum { V }
+
+class InnerClass:
+ enum { V }
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_shadows_outer_enum.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_shadows_outer_enum.out
new file mode 100644
index 0000000000..c9706003e1
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_shadows_outer_enum.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Name "V" is already used as a class enum value.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_unnamed_assign_to_named.gd b/modules/gdscript/tests/scripts/analyzer/errors/enum_unnamed_assign_to_named.gd
new file mode 100644
index 0000000000..7e749db6b5
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_unnamed_assign_to_named.gd
@@ -0,0 +1,7 @@
+enum { ENUM_VALUE_1, ENUM_VALUE_2 }
+
+enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 }
+
+func test():
+ var local_var: MyEnum = ENUM_VALUE_1
+ print(local_var)
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_unnamed_assign_to_named.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_unnamed_assign_to_named.out
new file mode 100644
index 0000000000..b70121ed81
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_unnamed_assign_to_named.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot assign a value of type enum_unnamed_assign_to_named.gd::<anonymous enum> to variable "local_var" with specified type enum_unnamed_assign_to_named.gd::MyEnum.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/native_type_errors.gd b/modules/gdscript/tests/scripts/analyzer/errors/native_type_errors.gd
new file mode 100644
index 0000000000..e1bed94406
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/native_type_errors.gd
@@ -0,0 +1,2 @@
+func test():
+ TileSet.this_does_not_exist # Does not exist
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/native_type_errors.out b/modules/gdscript/tests/scripts/analyzer/errors/native_type_errors.out
new file mode 100644
index 0000000000..06180c3a55
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/native_type_errors.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot find member "this_does_not_exist" in base "TileSet".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants.gd b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants.gd
new file mode 100644
index 0000000000..1cf3870a8e
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants.gd
@@ -0,0 +1,8 @@
+class Outer:
+ const OUTER_CONST: = 0
+ class Inner:
+ pass
+
+func test() -> void:
+ var type: = Outer.Inner
+ print(type.OUTER_CONST)
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants.out b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants.out
new file mode 100644
index 0000000000..73a54d7820
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants.out
@@ -0,0 +1,6 @@
+GDTEST_RUNTIME_ERROR
+>> SCRIPT ERROR
+>> on function: test()
+>> analyzer/errors/outer_class_constants.gd
+>> 8
+>> Invalid get index 'OUTER_CONST' (on base: 'GDScript').
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants_as_variant.gd b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants_as_variant.gd
new file mode 100644
index 0000000000..c1074df915
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants_as_variant.gd
@@ -0,0 +1,9 @@
+class Outer:
+ const OUTER_CONST: = 0
+ class Inner:
+ pass
+
+func test() -> void:
+ var type: = Outer.Inner
+ var type_v: Variant = type
+ print(type_v.OUTER_CONST)
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants_as_variant.out b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants_as_variant.out
new file mode 100644
index 0000000000..92e7b9316e
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants_as_variant.out
@@ -0,0 +1,6 @@
+GDTEST_RUNTIME_ERROR
+>> SCRIPT ERROR
+>> on function: test()
+>> analyzer/errors/outer_class_constants_as_variant.gd
+>> 9
+>> Invalid get index 'OUTER_CONST' (on base: 'GDScript').
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants.gd b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants.gd
new file mode 100644
index 0000000000..2631c3c500
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants.gd
@@ -0,0 +1,8 @@
+class Outer:
+ const OUTER_CONST: = 0
+ class Inner:
+ pass
+
+func test() -> void:
+ var instance: = Outer.Inner.new()
+ print(instance.OUTER_CONST)
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants.out b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants.out
new file mode 100644
index 0000000000..892f8e2c3f
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants.out
@@ -0,0 +1,6 @@
+GDTEST_RUNTIME_ERROR
+>> SCRIPT ERROR
+>> on function: test()
+>> analyzer/errors/outer_class_instance_constants.gd
+>> 8
+>> Invalid get index 'OUTER_CONST' (on base: 'RefCounted (Inner)').
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants_as_variant.gd b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants_as_variant.gd
new file mode 100644
index 0000000000..cba788381e
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants_as_variant.gd
@@ -0,0 +1,9 @@
+class Outer:
+ const OUTER_CONST: = 0
+ class Inner:
+ pass
+
+func test() -> void:
+ var instance: = Outer.Inner.new()
+ var instance_v: Variant = instance
+ print(instance_v.OUTER_CONST)
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants_as_variant.out b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants_as_variant.out
new file mode 100644
index 0000000000..8257e74f57
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants_as_variant.out
@@ -0,0 +1,6 @@
+GDTEST_RUNTIME_ERROR
+>> SCRIPT ERROR
+>> on function: test()
+>> analyzer/errors/outer_class_instance_constants_as_variant.gd
+>> 9
+>> Invalid get index 'OUTER_CONST' (on base: 'RefCounted (Inner)').
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/preload_enum_error.gd b/modules/gdscript/tests/scripts/analyzer/errors/preload_enum_error.gd
new file mode 100644
index 0000000000..4e75ded96a
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/preload_enum_error.gd
@@ -0,0 +1,6 @@
+enum LocalNamed { VALUE_A, VALUE_B, VALUE_C = 42 }
+
+func test():
+ const P = preload("../features/enum_from_outer.gd")
+ var x : LocalNamed
+ x = P.Named.VALUE_A
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/preload_enum_error.out b/modules/gdscript/tests/scripts/analyzer/errors/preload_enum_error.out
new file mode 100644
index 0000000000..5e3c446bf6
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/preload_enum_error.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Value of type "enum_from_outer.gd::Named" cannot be assigned to a variable of type "preload_enum_error.gd::LocalNamed".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/property_inline_set_type_error.out b/modules/gdscript/tests/scripts/analyzer/errors/property_inline_set_type_error.out
index bbadf1ce27..bf776029b9 100644
--- a/modules/gdscript/tests/scripts/analyzer/errors/property_inline_set_type_error.out
+++ b/modules/gdscript/tests/scripts/analyzer/errors/property_inline_set_type_error.out
@@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
-Cannot assign a value of type "String" to a target of type "int".
+Value of type "String" cannot be assigned to a variable of type "int".
diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_access_types.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_access_types.gd
new file mode 100644
index 0000000000..9bc08f2dc5
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/enum_access_types.gd
@@ -0,0 +1,29 @@
+class_name EnumAccessOuterClass
+
+class InnerClass:
+ enum MyEnum { V0, V2, V1 }
+
+ static func print_enums():
+ print("Inner - Inner")
+ print(MyEnum.V0, MyEnum.V1, MyEnum.V2)
+ print(InnerClass.MyEnum.V0, InnerClass.MyEnum.V1, InnerClass.MyEnum.V2)
+ print(EnumAccessOuterClass.InnerClass.MyEnum.V0, EnumAccessOuterClass.InnerClass.MyEnum.V1, EnumAccessOuterClass.InnerClass.MyEnum.V2)
+
+ print("Inner - Outer")
+ print(EnumAccessOuterClass.MyEnum.V0, EnumAccessOuterClass.MyEnum.V1, EnumAccessOuterClass.MyEnum.V2)
+
+
+enum MyEnum { V0, V1, V2 }
+
+func print_enums():
+ print("Outer - Outer")
+ print(MyEnum.V0, MyEnum.V1, MyEnum.V2)
+ print(EnumAccessOuterClass.MyEnum.V0, EnumAccessOuterClass.MyEnum.V1, EnumAccessOuterClass.MyEnum.V2)
+
+ print("Outer - Inner")
+ print(InnerClass.MyEnum.V0, InnerClass.MyEnum.V1, InnerClass.MyEnum.V2)
+ print(EnumAccessOuterClass.InnerClass.MyEnum.V0, EnumAccessOuterClass.InnerClass.MyEnum.V1, EnumAccessOuterClass.InnerClass.MyEnum.V2)
+
+func test():
+ print_enums()
+ InnerClass.print_enums()
diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_access_types.out b/modules/gdscript/tests/scripts/analyzer/features/enum_access_types.out
new file mode 100644
index 0000000000..02e2e2b396
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/enum_access_types.out
@@ -0,0 +1,13 @@
+GDTEST_OK
+Outer - Outer
+012
+012
+Outer - Inner
+021
+021
+Inner - Inner
+021
+021
+021
+Inner - Outer
+012
diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_duplicate_into_dict.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_duplicate_into_dict.gd
new file mode 100644
index 0000000000..3076e7069f
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/enum_duplicate_into_dict.gd
@@ -0,0 +1,13 @@
+enum Enum {V1, V2}
+
+func test():
+ var enumAsDict : Dictionary = Enum.duplicate()
+ var enumAsVariant = Enum.duplicate()
+ print(Enum.has("V1"))
+ print(enumAsDict.has("V1"))
+ print(enumAsVariant.has("V1"))
+ enumAsDict.clear()
+ enumAsVariant.clear()
+ print(Enum.has("V1"))
+ print(enumAsDict.has("V1"))
+ print(enumAsVariant.has("V1"))
diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_duplicate_into_dict.out b/modules/gdscript/tests/scripts/analyzer/features/enum_duplicate_into_dict.out
new file mode 100644
index 0000000000..a41924d0c9
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/enum_duplicate_into_dict.out
@@ -0,0 +1,7 @@
+GDTEST_OK
+true
+true
+true
+true
+false
+false
diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_from_base.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_from_base.gd
new file mode 100644
index 0000000000..b3f9941903
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/enum_from_base.gd
@@ -0,0 +1,13 @@
+class A:
+ enum Named { VALUE_A, VALUE_B, VALUE_C = 42 }
+
+class B extends A:
+ var a = Named.VALUE_A
+ var b = Named.VALUE_B
+ var c = Named.VALUE_C
+
+func test():
+ var test_instance = B.new()
+ prints("a", test_instance.a, test_instance.a == A.Named.VALUE_A)
+ prints("b", test_instance.b, test_instance.b == A.Named.VALUE_B)
+ prints("c", test_instance.c, test_instance.c == B.Named.VALUE_C)
diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_from_parent.out b/modules/gdscript/tests/scripts/analyzer/features/enum_from_base.out
index c160839da3..c160839da3 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/enum_from_parent.out
+++ b/modules/gdscript/tests/scripts/analyzer/features/enum_from_base.out
diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_from_parent.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_from_outer.gd
index 5f57c5b8c2..4d6852a9be 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/enum_from_parent.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/enum_from_outer.gd
@@ -1,5 +1,3 @@
-extends Node
-
enum Named { VALUE_A, VALUE_B, VALUE_C = 42 }
class Test:
diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_from_outer.out b/modules/gdscript/tests/scripts/analyzer/features/enum_from_outer.out
new file mode 100644
index 0000000000..c160839da3
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/enum_from_outer.out
@@ -0,0 +1,4 @@
+GDTEST_OK
+a 0 true
+b 1 true
+c 42 true
diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_function_typecheck.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_function_typecheck.gd
new file mode 100644
index 0000000000..8a4e89d0d6
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/enum_function_typecheck.gd
@@ -0,0 +1,112 @@
+class_name EnumFunctionTypecheckOuterClass
+
+enum MyEnum { V0, V1, V2 }
+
+class InnerClass:
+ enum MyEnum { V0, V2, V1 }
+
+ func inner_inner_no_class(e : MyEnum) -> MyEnum:
+ print(e)
+ return e
+
+ func inner_inner_class(e : InnerClass.MyEnum) -> InnerClass.MyEnum:
+ print(e)
+ return e
+
+ func inner_inner_class_class(e : EnumFunctionTypecheckOuterClass.InnerClass.MyEnum) -> EnumFunctionTypecheckOuterClass.InnerClass.MyEnum:
+ print(e)
+ return e
+
+ func inner_outer(e : EnumFunctionTypecheckOuterClass.MyEnum) -> EnumFunctionTypecheckOuterClass.MyEnum:
+ print(e)
+ return e
+
+ func test():
+ var _d
+ print("Inner")
+
+ var o := EnumFunctionTypecheckOuterClass.new()
+
+ _d = o.outer_outer_no_class(EnumFunctionTypecheckOuterClass.MyEnum.V1)
+ print()
+ _d = o.outer_outer_class(EnumFunctionTypecheckOuterClass.MyEnum.V1)
+ print()
+ _d = o.outer_inner_class(MyEnum.V1)
+ _d = o.outer_inner_class(InnerClass.MyEnum.V1)
+ _d = o.outer_inner_class(EnumFunctionTypecheckOuterClass.InnerClass.MyEnum.V1)
+ print()
+ _d = o.outer_inner_class_class(MyEnum.V1)
+ _d = o.outer_inner_class_class(InnerClass.MyEnum.V1)
+ _d = o.outer_inner_class_class(EnumFunctionTypecheckOuterClass.InnerClass.MyEnum.V1)
+ print()
+ print()
+
+
+ _d = inner_inner_no_class(MyEnum.V1)
+ _d = inner_inner_no_class(InnerClass.MyEnum.V1)
+ _d = inner_inner_no_class(EnumFunctionTypecheckOuterClass.InnerClass.MyEnum.V1)
+ print()
+ _d = inner_inner_class(MyEnum.V1)
+ _d = inner_inner_class(InnerClass.MyEnum.V1)
+ _d = inner_inner_class(EnumFunctionTypecheckOuterClass.InnerClass.MyEnum.V1)
+ print()
+ _d = inner_inner_class_class(MyEnum.V1)
+ _d = inner_inner_class_class(InnerClass.MyEnum.V1)
+ _d = inner_inner_class_class(EnumFunctionTypecheckOuterClass.InnerClass.MyEnum.V1)
+ print()
+ _d = inner_outer(EnumFunctionTypecheckOuterClass.MyEnum.V1)
+ print()
+ print()
+
+
+func outer_outer_no_class(e : MyEnum) -> MyEnum:
+ print(e)
+ return e
+
+func outer_outer_class(e : EnumFunctionTypecheckOuterClass.MyEnum) -> EnumFunctionTypecheckOuterClass.MyEnum:
+ print(e)
+ return e
+
+func outer_inner_class(e : InnerClass.MyEnum) -> InnerClass.MyEnum:
+ print(e)
+ return e
+
+func outer_inner_class_class(e : EnumFunctionTypecheckOuterClass.InnerClass.MyEnum) -> EnumFunctionTypecheckOuterClass.InnerClass.MyEnum:
+ print(e)
+ return e
+
+func test():
+ var _d
+ print("Outer")
+
+ _d = outer_outer_no_class(MyEnum.V1)
+ _d = outer_outer_no_class(EnumFunctionTypecheckOuterClass.MyEnum.V1)
+ print()
+ _d = outer_outer_class(MyEnum.V1)
+ _d = outer_outer_class(EnumFunctionTypecheckOuterClass.MyEnum.V1)
+ print()
+ _d = outer_inner_class(InnerClass.MyEnum.V1)
+ _d = outer_inner_class(EnumFunctionTypecheckOuterClass.InnerClass.MyEnum.V1)
+ print()
+ _d = outer_inner_class_class(InnerClass.MyEnum.V1)
+ _d = outer_inner_class_class(EnumFunctionTypecheckOuterClass.InnerClass.MyEnum.V1)
+ print()
+ print()
+
+ var i := EnumFunctionTypecheckOuterClass.InnerClass.new()
+
+ _d = i.inner_inner_no_class(InnerClass.MyEnum.V1)
+ _d = i.inner_inner_no_class(EnumFunctionTypecheckOuterClass.InnerClass.MyEnum.V1)
+ print()
+ _d = i.inner_inner_class(InnerClass.MyEnum.V1)
+ _d = i.inner_inner_class(EnumFunctionTypecheckOuterClass.InnerClass.MyEnum.V1)
+ print()
+ _d = i.inner_inner_class_class(InnerClass.MyEnum.V1)
+ _d = i.inner_inner_class_class(EnumFunctionTypecheckOuterClass.InnerClass.MyEnum.V1)
+ print()
+ _d = i.inner_outer(MyEnum.V1)
+ _d = i.inner_outer(EnumFunctionTypecheckOuterClass.MyEnum.V1)
+ print()
+ print()
+
+ i.test()
diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_function_typecheck.out b/modules/gdscript/tests/scripts/analyzer/features/enum_function_typecheck.out
new file mode 100644
index 0000000000..2e3ce1aa10
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/enum_function_typecheck.out
@@ -0,0 +1,55 @@
+GDTEST_OK
+Outer
+1
+1
+
+1
+1
+
+2
+2
+
+2
+2
+
+
+2
+2
+
+2
+2
+
+2
+2
+
+1
+1
+
+
+Inner
+1
+
+1
+
+2
+2
+2
+
+2
+2
+2
+
+
+2
+2
+2
+
+2
+2
+2
+
+2
+2
+2
+
+1
diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_named_no_shadow.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_named_no_shadow.gd
new file mode 100644
index 0000000000..b97d9bbb6b
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/enum_named_no_shadow.gd
@@ -0,0 +1,16 @@
+const A := 1
+enum { B }
+enum NamedEnum { C }
+
+class Parent:
+ const D := 2
+ enum { E }
+ enum NamedEnum2 { F }
+
+class Child extends Parent:
+ enum TestEnum { A, B, C, D, E, F, Node, Object, Child, Parent}
+
+func test():
+ print(A, B, NamedEnum.C, Parent.D, Parent.E, Parent.NamedEnum2.F)
+ print(Child.TestEnum.A, Child.TestEnum.B, Child.TestEnum.C, Child.TestEnum.D, Child.TestEnum.E, Child.TestEnum.F)
+ print(Child.TestEnum.Node, Child.TestEnum.Object, Child.TestEnum.Child, Child.TestEnum.Parent)
diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_named_no_shadow.out b/modules/gdscript/tests/scripts/analyzer/features/enum_named_no_shadow.out
new file mode 100644
index 0000000000..864ba2a549
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/enum_named_no_shadow.out
@@ -0,0 +1,4 @@
+GDTEST_OK
+100200
+012345
+6789
diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_native_access_types.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_native_access_types.gd
new file mode 100644
index 0000000000..6a0a1e1969
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/enum_native_access_types.gd
@@ -0,0 +1,19 @@
+func print_enum(e : TileSet.TileShape) -> TileSet.TileShape:
+ print(e)
+ return e
+
+func test():
+ var v : TileSet.TileShape
+ v = TileSet.TILE_SHAPE_SQUARE
+ v = print_enum(v)
+ v = print_enum(TileSet.TILE_SHAPE_SQUARE)
+ v = TileSet.TileShape.TILE_SHAPE_SQUARE
+ v = print_enum(v)
+ v = print_enum(TileSet.TileShape.TILE_SHAPE_SQUARE)
+
+ v = TileSet.TILE_SHAPE_ISOMETRIC
+ v = print_enum(v)
+ v = print_enum(TileSet.TILE_SHAPE_ISOMETRIC)
+ v = TileSet.TileShape.TILE_SHAPE_ISOMETRIC
+ v = print_enum(v)
+ v = print_enum(TileSet.TileShape.TILE_SHAPE_ISOMETRIC)
diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_native_access_types.out b/modules/gdscript/tests/scripts/analyzer/features/enum_native_access_types.out
new file mode 100644
index 0000000000..1126dcc6ec
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/enum_native_access_types.out
@@ -0,0 +1,9 @@
+GDTEST_OK
+0
+0
+0
+0
+1
+1
+1
+1
diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_typecheck_inner_class.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_typecheck_inner_class.gd
new file mode 100644
index 0000000000..b05ae82048
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/enum_typecheck_inner_class.gd
@@ -0,0 +1,86 @@
+class_name EnumTypecheckOuterClass
+
+enum MyEnum { V0, V1, V2 }
+
+class InnerClass:
+ enum MyEnum { V0, V2, V1 }
+
+ static func test_inner_from_inner():
+ print("Inner - Inner")
+ var e1 : MyEnum
+ var e2 : InnerClass.MyEnum
+ var e3 : EnumTypecheckOuterClass.InnerClass.MyEnum
+
+ print("Self ", e1, e2, e3)
+ e1 = MyEnum.V1
+ e2 = MyEnum.V1
+ e3 = MyEnum.V1
+ print("MyEnum ", e1, e2, e3)
+ e1 = InnerClass.MyEnum.V1
+ e2 = InnerClass.MyEnum.V1
+ e3 = InnerClass.MyEnum.V1
+ print("Inner.MyEnum ", e1, e2, e3)
+ e1 = EnumTypecheckOuterClass.InnerClass.MyEnum.V1
+ e2 = EnumTypecheckOuterClass.InnerClass.MyEnum.V1
+ e3 = EnumTypecheckOuterClass.InnerClass.MyEnum.V1
+ print("Outer.Inner.MyEnum ", e1, e2, e3)
+
+ e1 = e2
+ e1 = e3
+ e2 = e1
+ e2 = e3
+ e3 = e1
+ e3 = e2
+
+ print()
+
+ static func test_outer_from_inner():
+ print("Inner - Outer")
+ var e : EnumTypecheckOuterClass.MyEnum
+
+ e = EnumTypecheckOuterClass.MyEnum.V1
+ print("Outer.MyEnum ", e)
+
+ print()
+
+func test_outer_from_outer():
+ print("Outer - Outer")
+ var e1 : MyEnum
+ var e2 : EnumTypecheckOuterClass.MyEnum
+
+ print("Self ", e1, e2)
+ e1 = MyEnum.V1
+ e2 = MyEnum.V1
+ print("Outer ", e1, e2)
+ e1 = EnumTypecheckOuterClass.MyEnum.V1
+ e2 = EnumTypecheckOuterClass.MyEnum.V1
+ print("Outer.MyEnum ", e1, e2)
+
+ e1 = e2
+ e2 = e1
+
+ print()
+
+func test_inner_from_outer():
+ print("Outer - Inner")
+ var e1 : InnerClass.MyEnum
+ var e2 : EnumTypecheckOuterClass.InnerClass.MyEnum
+
+ print("Inner ", e1, e2)
+ e1 = InnerClass.MyEnum.V1
+ e2 = InnerClass.MyEnum.V1
+ print("Outer.Inner ", e1, e2)
+ e1 = EnumTypecheckOuterClass.InnerClass.MyEnum.V1
+ e2 = EnumTypecheckOuterClass.InnerClass.MyEnum.V1
+ print("Outer.Inner.MyEnum ", e1, e2)
+
+ e1 = e2
+ e2 = e1
+
+ print()
+
+func test():
+ test_outer_from_outer()
+ test_inner_from_outer()
+ InnerClass.test_outer_from_inner()
+ InnerClass.test_inner_from_inner()
diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_typecheck_inner_class.out b/modules/gdscript/tests/scripts/analyzer/features/enum_typecheck_inner_class.out
new file mode 100644
index 0000000000..3b2dcade26
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/enum_typecheck_inner_class.out
@@ -0,0 +1,19 @@
+GDTEST_OK
+Outer - Outer
+Self 00
+Outer 11
+Outer.MyEnum 11
+
+Outer - Inner
+Inner 00
+Outer.Inner 22
+Outer.Inner.MyEnum 22
+
+Inner - Outer
+Outer.MyEnum 1
+
+Inner - Inner
+Self 000
+MyEnum 222
+Inner.MyEnum 222
+Outer.Inner.MyEnum 222
diff --git a/modules/gdscript/tests/scripts/analyzer/features/lookup_class.gd b/modules/gdscript/tests/scripts/analyzer/features/lookup_class.gd
new file mode 100644
index 0000000000..4a2ab7bcba
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/lookup_class.gd
@@ -0,0 +1,50 @@
+# Inner-outer class lookup
+class A:
+ const Q: = "right one"
+
+class X:
+ const Q: = "wrong one"
+
+class Y extends X:
+ class B extends A:
+ static func check() -> void:
+ print(Q)
+
+# External class lookup
+const External: = preload("lookup_class_external.notest.gd")
+
+class Internal extends External.A:
+ static func check() -> void:
+ print(TARGET)
+
+ class E extends External.E:
+ static func check() -> void:
+ print(TARGET)
+ print(WAITING)
+
+# Variable lookup
+class C:
+ var Q := 'right one'
+
+class D:
+ const Q := 'wrong one'
+
+class E extends D:
+ class F extends C:
+ func check() -> void:
+ print(Q)
+
+# Test
+func test() -> void:
+ # Inner-outer class lookup
+ Y.B.check()
+ print("---")
+
+ # External class lookup
+ Internal.check()
+ Internal.E.check()
+ print("---")
+
+ # Variable lookup
+ var f: = E.F.new()
+ f.check()
diff --git a/modules/gdscript/tests/scripts/analyzer/features/lookup_class.out b/modules/gdscript/tests/scripts/analyzer/features/lookup_class.out
new file mode 100644
index 0000000000..a0983c1438
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/lookup_class.out
@@ -0,0 +1,8 @@
+GDTEST_OK
+right one
+---
+wrong
+right
+godot
+---
+right one
diff --git a/modules/gdscript/tests/scripts/analyzer/features/lookup_class_external.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/lookup_class_external.notest.gd
new file mode 100644
index 0000000000..a2904e20a8
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/lookup_class_external.notest.gd
@@ -0,0 +1,15 @@
+class A:
+ const TARGET: = "wrong"
+
+ class B:
+ const TARGET: = "wrong"
+ const WAITING: = "godot"
+
+ class D extends C:
+ pass
+
+class C:
+ const TARGET: = "right"
+
+class E extends A.B.D:
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/features/lookup_signal.gd b/modules/gdscript/tests/scripts/analyzer/features/lookup_signal.gd
new file mode 100644
index 0000000000..b19d5ee4f9
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/lookup_signal.gd
@@ -0,0 +1,41 @@
+signal hello
+
+func get_signal() -> Signal:
+ return hello
+
+class A:
+ signal hello
+
+ func get_signal() -> Signal:
+ return hello
+
+ class B:
+ signal hello
+
+ func get_signal() -> Signal:
+ return hello
+
+class C extends A.B:
+ func get_signal() -> Signal:
+ return hello
+
+func test():
+ var a: = A.new()
+ var b: = A.B.new()
+ var c: = C.new()
+
+ var hello_a_result: = hello == a.get_signal()
+ var hello_b_result: = hello == b.get_signal()
+ var hello_c_result: = hello == c.get_signal()
+ var a_b_result: = a.get_signal() == b.get_signal()
+ var a_c_result: = a.get_signal() == c.get_signal()
+ var b_c_result: = b.get_signal() == c.get_signal()
+ var c_c_result: = c.get_signal() == c.get_signal()
+
+ print("hello == A.hello? %s" % hello_a_result)
+ print("hello == A.B.hello? %s" % hello_b_result)
+ print("hello == C.hello? %s" % hello_c_result)
+ print("A.hello == A.B.hello? %s" % a_b_result)
+ print("A.hello == C.hello? %s" % a_c_result)
+ print("A.B.hello == C.hello? %s" % b_c_result)
+ print("C.hello == C.hello? %s" % c_c_result)
diff --git a/modules/gdscript/tests/scripts/analyzer/features/lookup_signal.out b/modules/gdscript/tests/scripts/analyzer/features/lookup_signal.out
new file mode 100644
index 0000000000..6b0d32eaf8
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/lookup_signal.out
@@ -0,0 +1,8 @@
+GDTEST_OK
+hello == A.hello? false
+hello == A.B.hello? false
+hello == C.hello? false
+A.hello == A.B.hello? false
+A.hello == C.hello? false
+A.B.hello == C.hello? false
+C.hello == C.hello? true
diff --git a/modules/gdscript/tests/scripts/parser/features/multiline_assert.gd b/modules/gdscript/tests/scripts/parser/features/multiline_assert.gd
new file mode 100644
index 0000000000..8c699c604d
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/multiline_assert.gd
@@ -0,0 +1,24 @@
+func test():
+ var x := 5
+
+ assert(x > 0)
+ assert(x > 0,)
+ assert(x > 0, 'message')
+ assert(x > 0, 'message',)
+
+ assert(
+ x > 0
+ )
+ assert(
+ x > 0,
+ )
+ assert(
+ x > 0,
+ 'message'
+ )
+ assert(
+ x > 0,
+ 'message',
+ )
+
+ print('OK')
diff --git a/modules/gdscript/tests/scripts/parser/features/multiline_assert.out b/modules/gdscript/tests/scripts/parser/features/multiline_assert.out
new file mode 100644
index 0000000000..1ccb591560
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/multiline_assert.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+OK
diff --git a/modules/gdscript/tests/scripts/runtime/errors/constant_array_is_deep.gd b/modules/gdscript/tests/scripts/runtime/errors/constant_array_is_deep.gd
new file mode 100644
index 0000000000..a5ecaba38d
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/constant_array_is_deep.gd
@@ -0,0 +1,6 @@
+const array: Array = [{}]
+
+func test():
+ var dictionary := array[0]
+ var key: int = 0
+ dictionary[key] = 0
diff --git a/modules/gdscript/tests/scripts/runtime/errors/constant_array_is_deep.out b/modules/gdscript/tests/scripts/runtime/errors/constant_array_is_deep.out
new file mode 100644
index 0000000000..2a97eaea44
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/constant_array_is_deep.out
@@ -0,0 +1,6 @@
+GDTEST_RUNTIME_ERROR
+>> SCRIPT ERROR
+>> on function: test()
+>> runtime/errors/constant_array_is_deep.gd
+>> 6
+>> Invalid set index '0' (on base: 'Dictionary') with value of type 'int'
diff --git a/modules/gdscript/tests/scripts/runtime/errors/constant_array_push_back.gd b/modules/gdscript/tests/scripts/runtime/errors/constant_array_push_back.gd
new file mode 100644
index 0000000000..3e71cd0518
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/constant_array_push_back.gd
@@ -0,0 +1,4 @@
+const array: Array = [0]
+
+func test():
+ array.push_back(0)
diff --git a/modules/gdscript/tests/scripts/runtime/errors/constant_array_push_back.out b/modules/gdscript/tests/scripts/runtime/errors/constant_array_push_back.out
new file mode 100644
index 0000000000..ba3e1c46c6
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/constant_array_push_back.out
@@ -0,0 +1,7 @@
+GDTEST_RUNTIME_ERROR
+>> ERROR
+>> on function: push_back()
+>> core/variant/array.cpp
+>> 253
+>> Condition "_p->read_only" is true.
+>> Array is in read-only state.
diff --git a/modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_erase.gd b/modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_erase.gd
new file mode 100644
index 0000000000..935fb773dc
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_erase.gd
@@ -0,0 +1,4 @@
+const dictionary := {}
+
+func test():
+ dictionary.erase(0)
diff --git a/modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_erase.out b/modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_erase.out
new file mode 100644
index 0000000000..3e7ca11a4f
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_erase.out
@@ -0,0 +1,7 @@
+GDTEST_RUNTIME_ERROR
+>> ERROR
+>> on function: erase()
+>> core/variant/dictionary.cpp
+>> 177
+>> Condition "_p->read_only" is true. Returning: false
+>> Dictionary is in read-only state.
diff --git a/modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_is_deep.gd b/modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_is_deep.gd
new file mode 100644
index 0000000000..4763210a7f
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_is_deep.gd
@@ -0,0 +1,6 @@
+const dictionary := {0: [0]}
+
+func test():
+ var array := dictionary[0]
+ var key: int = 0
+ array[key] = 0
diff --git a/modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_is_deep.out b/modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_is_deep.out
new file mode 100644
index 0000000000..c807db6b0c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_is_deep.out
@@ -0,0 +1,6 @@
+GDTEST_RUNTIME_ERROR
+>> SCRIPT ERROR
+>> on function: test()
+>> runtime/errors/constant_dictionary_is_deep.gd
+>> 6
+>> Invalid set index '0' (on base: 'Array') with value of type 'int'
diff --git a/modules/gdscript/tests/scripts/runtime/features/await_on_void.gd b/modules/gdscript/tests/scripts/runtime/features/await_on_void.gd
new file mode 100644
index 0000000000..46b9fbc951
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/await_on_void.gd
@@ -0,0 +1,7 @@
+func wait() -> void:
+ pass
+
+func test():
+ @warning_ignore(redundant_await)
+ await wait()
+ print("end")
diff --git a/modules/gdscript/tests/scripts/runtime/features/await_on_void.out b/modules/gdscript/tests/scripts/runtime/features/await_on_void.out
new file mode 100644
index 0000000000..5bc3dcf2db
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/await_on_void.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+end
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
new file mode 100644
index 0000000000..cc34e71b01
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd
@@ -0,0 +1,45 @@
+# https://github.com/godotengine/godot/issues/70964
+
+func test():
+ test_construct(0, false)
+ test_utility(0, false)
+ test_builtin_call(Vector2.UP, false)
+ test_builtin_call_validated(Vector2.UP, false)
+ test_object_call(RefCounted.new(), false)
+ test_object_call_method_bind(Resource.new(), false)
+ test_object_call_ptrcall(RefCounted.new(), false)
+
+ print("end")
+
+func test_construct(v, f):
+ Vector2(v, v) # Built-in type construct.
+ assert(not f) # Test unary operator reading from `nil`.
+
+func test_utility(v, f):
+ abs(v) # Utility function.
+ assert(not f) # Test unary operator reading from `nil`.
+
+func test_builtin_call(v, f):
+ @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)
+ 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)
+ 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)
+ 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)
+ 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/standalone-calls-do-not-write-to-nil.out b/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.out
new file mode 100644
index 0000000000..5bc3dcf2db
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+end
diff --git a/modules/gridmap/grid_map.cpp b/modules/gridmap/grid_map.cpp
index faee36fb82..3c0bd56e86 100644
--- a/modules/gridmap/grid_map.cpp
+++ b/modules/gridmap/grid_map.cpp
@@ -1310,8 +1310,8 @@ RID GridMap::get_bake_mesh_instance(int p_idx) {
GridMap::GridMap() {
set_notify_transform(true);
#ifdef DEBUG_ENABLED
- NavigationServer3D::get_singleton_mut()->connect("map_changed", callable_mp(this, &GridMap::_navigation_map_changed));
- NavigationServer3D::get_singleton_mut()->connect("navigation_debug_changed", callable_mp(this, &GridMap::_update_navigation_debug_edge_connections));
+ NavigationServer3D::get_singleton()->connect("map_changed", callable_mp(this, &GridMap::_navigation_map_changed));
+ NavigationServer3D::get_singleton()->connect("navigation_debug_changed", callable_mp(this, &GridMap::_update_navigation_debug_edge_connections));
#endif // DEBUG_ENABLED
}
@@ -1338,8 +1338,8 @@ GridMap::~GridMap() {
clear();
#ifdef DEBUG_ENABLED
- NavigationServer3D::get_singleton_mut()->disconnect("map_changed", callable_mp(this, &GridMap::_navigation_map_changed));
- NavigationServer3D::get_singleton_mut()->disconnect("navigation_debug_changed", callable_mp(this, &GridMap::_update_navigation_debug_edge_connections));
+ NavigationServer3D::get_singleton()->disconnect("map_changed", callable_mp(this, &GridMap::_navigation_map_changed));
+ NavigationServer3D::get_singleton()->disconnect("navigation_debug_changed", callable_mp(this, &GridMap::_update_navigation_debug_edge_connections));
#endif // DEBUG_ENABLED
}
@@ -1421,7 +1421,7 @@ void GridMap::_update_octant_navigation_debug_edge_connections_mesh(const Octant
return;
}
- Ref<StandardMaterial3D> edge_connections_material = NavigationServer3D::get_singleton_mut()->get_debug_navigation_edge_connections_material();
+ Ref<StandardMaterial3D> edge_connections_material = NavigationServer3D::get_singleton()->get_debug_navigation_edge_connections_material();
Array mesh_array;
mesh_array.resize(Mesh::ARRAY_MAX);
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs
index 7f1231dbcb..5fb29b86da 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs
@@ -74,27 +74,19 @@ namespace Godot.SourceGenerators
ObjectId = 22,
TypeString = 23,
NodePathToEditedNode = 24,
- MethodOfVariantType = 25,
- MethodOfBaseType = 26,
- MethodOfInstance = 27,
- MethodOfScript = 28,
- PropertyOfVariantType = 29,
- PropertyOfBaseType = 30,
- PropertyOfInstance = 31,
- PropertyOfScript = 32,
- ObjectTooBig = 33,
- NodePathValidTypes = 34,
- SaveFile = 35,
- GlobalSaveFile = 36,
- IntIsObjectid = 37,
- IntIsPointer = 38,
- ArrayType = 39,
- LocaleId = 40,
- LocalizableString = 41,
- NodeType = 42,
- HideQuaternionEdit = 43,
- Password = 44,
- Max = 45
+ ObjectTooBig = 25,
+ NodePathValidTypes = 26,
+ SaveFile = 27,
+ GlobalSaveFile = 28,
+ IntIsObjectid = 29,
+ IntIsPointer = 30,
+ ArrayType = 31,
+ LocaleId = 32,
+ LocalizableString = 33,
+ NodeType = 34,
+ HideQuaternionEdit = 35,
+ Password = 36,
+ Max = 37
}
[Flags]
@@ -103,9 +95,9 @@ namespace Godot.SourceGenerators
None = 0,
Storage = 2,
Editor = 4,
- Checkable = 8,
- Checked = 16,
- Internationalized = 32,
+ Internal = 8,
+ Checkable = 16,
+ Checked = 32,
Group = 64,
Category = 128,
Subgroup = 256,
@@ -118,7 +110,7 @@ namespace Godot.SourceGenerators
ScriptDefaultValue = 32768,
ClassIsEnum = 65536,
NilIsVariant = 131072,
- Internal = 262144,
+ Array = 262144,
DoNotShareOnDuplicate = 524288,
HighEndGfx = 1048576,
NodePathFromSceneRoot = 2097152,
@@ -128,9 +120,7 @@ namespace Godot.SourceGenerators
EditorInstantiateObject = 33554432,
EditorBasicSetting = 67108864,
ReadOnly = 134217728,
- Array = 268435456,
Default = 6,
- DefaultIntl = 38,
NoEditor = 2
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs
index 4d40724a83..e439822666 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs
@@ -356,19 +356,19 @@ namespace GodotTools.Build
{
base._Ready();
- SizeFlagsVertical = (int)SizeFlags.ExpandFill;
+ SizeFlagsVertical = SizeFlags.ExpandFill;
var hsc = new HSplitContainer
{
- SizeFlagsHorizontal = (int)SizeFlags.ExpandFill,
- SizeFlagsVertical = (int)SizeFlags.ExpandFill
+ SizeFlagsHorizontal = SizeFlags.ExpandFill,
+ SizeFlagsVertical = SizeFlags.ExpandFill
};
AddChild(hsc);
_issuesList = new ItemList
{
- SizeFlagsVertical = (int)SizeFlags.ExpandFill,
- SizeFlagsHorizontal = (int)SizeFlags.ExpandFill // Avoid being squashed by the build log
+ SizeFlagsVertical = SizeFlags.ExpandFill,
+ SizeFlagsHorizontal = SizeFlags.ExpandFill // Avoid being squashed by the build log
};
_issuesList.ItemActivated += IssueActivated;
_issuesList.AllowRmbSelect = true;
@@ -382,8 +382,8 @@ namespace GodotTools.Build
_buildLog = new TextEdit
{
Editable = false,
- SizeFlagsVertical = (int)SizeFlags.ExpandFill,
- SizeFlagsHorizontal = (int)SizeFlags.ExpandFill // Avoid being squashed by the issues list
+ SizeFlagsVertical = SizeFlags.ExpandFill,
+ SizeFlagsHorizontal = SizeFlags.ExpandFill // Avoid being squashed by the issues list
};
hsc.AddChild(_buildLog);
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs
index 237ac85267..2e438f3f8f 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs
@@ -123,9 +123,9 @@ namespace GodotTools.Build
base._Ready();
CustomMinimumSize = new Vector2i(0, (int)(228 * EditorScale));
- SizeFlagsVertical = (int)SizeFlags.ExpandFill;
+ SizeFlagsVertical = SizeFlags.ExpandFill;
- var toolBarHBox = new HBoxContainer { SizeFlagsHorizontal = (int)SizeFlags.ExpandFill };
+ var toolBarHBox = new HBoxContainer { SizeFlagsHorizontal = SizeFlags.ExpandFill };
AddChild(toolBarHBox);
_buildMenuBtn = new MenuButton { Text = "Build", Icon = GetThemeIcon("Play", "EditorIcons") };
diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp
index e40b17cecb..c0d88553ad 100644
--- a/modules/mono/editor/bindings_generator.cpp
+++ b/modules/mono/editor/bindings_generator.cpp
@@ -3806,7 +3806,7 @@ void BindingsGenerator::_populate_global_constants() {
if (enum_name != StringName()) {
EnumInterface ienum(enum_name);
- // TODO: ienum.is_flags is always false for core constants since they don't seem to support bitfield enums
+ ienum.is_flags = CoreConstants::is_global_constant_bitfield(i);
List<EnumInterface>::Element *enum_match = global_enums.find(ienum);
if (enum_match) {
enum_match->get().constants.push_back(iconstant);
diff --git a/modules/multiplayer/doc_classes/MultiplayerSpawner.xml b/modules/multiplayer/doc_classes/MultiplayerSpawner.xml
index a3ca2d6486..b6a31cf542 100644
--- a/modules/multiplayer/doc_classes/MultiplayerSpawner.xml
+++ b/modules/multiplayer/doc_classes/MultiplayerSpawner.xml
@@ -5,20 +5,12 @@
</brief_description>
<description>
Spawnable scenes can be configured in the editor or through code (see [method add_spawnable_scene]).
- Also supports custom node spawns through [method spawn], calling [method _spawn_custom] on all peers.
+ Also supports custom node spawns through [method spawn], calling [member spawn_function] on all peers.
Internally, [MultiplayerSpawner] uses [method MultiplayerAPI.object_configuration_add] to notify spawns passing the spawned node as the [code]object[/code] and itself as the [code]configuration[/code], and [method MultiplayerAPI.object_configuration_remove] to notify despawns in a similar way.
</description>
<tutorials>
</tutorials>
<methods>
- <method name="_spawn_custom" qualifiers="virtual">
- <return type="Node" />
- <param index="0" name="data" type="Variant" />
- <description>
- Method called on all peers when a custom spawn was requested by the authority using [method spawn]. Should return a [Node] that is not in the scene tree.
- [b]Note:[/b] Spawned nodes should [b]not[/b] be added to the scene with [method Node.add_child]. This is done automatically.
- </description>
- </method>
<method name="add_spawnable_scene">
<return type="void" />
<param index="0" name="path" type="String" />
@@ -49,12 +41,16 @@
<return type="Node" />
<param index="0" name="data" type="Variant" default="null" />
<description>
- Requests a custom spawn, with [code]data[/code] passed to [method _spawn_custom] on all peers. Returns the locally spawned node instance already inside the scene tree, and added as a child of the node pointed by [member spawn_path].
+ Requests a custom spawn, with [code]data[/code] passed to [member spawn_function] on all peers. Returns the locally spawned node instance already inside the scene tree, and added as a child of the node pointed by [member spawn_path].
[b]Note:[/b] Spawnable scenes are spawned automatically. [method spawn] is only needed for custom spawns.
</description>
</method>
</methods>
<members>
+ <member name="spawn_function" type="Callable" setter="set_spawn_function" getter="get_spawn_function">
+ Method called on all peers when for every custom [method spawn] requested by the authority. Will receive the [code]data[/code] parameter, and should return a [Node] that is not in the scene tree.
+ [b]Note:[/b] The returned node should [b]not[/b] be added to the scene with [method Node.add_child]. This is done automatically.
+ </member>
<member name="spawn_limit" type="int" setter="set_spawn_limit" getter="get_spawn_limit" default="0">
Maximum nodes that is allowed to be spawned by this spawner. Includes both spawnable scenes and custom spawns.
When set to [code]0[/code] (the default), there is no limit.
diff --git a/modules/multiplayer/doc_classes/SceneMultiplayer.xml b/modules/multiplayer/doc_classes/SceneMultiplayer.xml
index e4e2b4f631..a688c5fd79 100644
--- a/modules/multiplayer/doc_classes/SceneMultiplayer.xml
+++ b/modules/multiplayer/doc_classes/SceneMultiplayer.xml
@@ -79,6 +79,7 @@
</member>
<member name="server_relay" type="bool" setter="set_server_relay_enabled" getter="is_server_relay_enabled" default="true">
Enable or disable the server feature that notifies clients of other peers' connection/disconnection, and relays messages between them. When this option is [code]false[/code], clients won't be automatically notified of other peers and won't be able to send them packets through the server.
+ [b]Note:[/b] Changing this option while other peers are connected may lead to unexpected behaviors.
[b]Note:[/b] Support for this feature may depend on the current [MultiplayerPeer] configuration. See [method MultiplayerPeer.is_server_relay_supported].
</member>
</members>
diff --git a/modules/multiplayer/multiplayer_spawner.cpp b/modules/multiplayer/multiplayer_spawner.cpp
index 52b874d280..7ed69a84d0 100644
--- a/modules/multiplayer/multiplayer_spawner.cpp
+++ b/modules/multiplayer/multiplayer_spawner.cpp
@@ -93,13 +93,6 @@ PackedStringArray MultiplayerSpawner::get_configuration_warnings() const {
if (spawn_path.is_empty() || !has_node(spawn_path)) {
warnings.push_back(RTR("A valid NodePath must be set in the \"Spawn Path\" property in order for MultiplayerSpawner to be able to spawn Nodes."));
}
- bool has_scenes = get_spawnable_scene_count() > 0;
- // Can't check if method is overridden in placeholder scripts.
- bool has_placeholder_script = get_script_instance() && get_script_instance()->is_placeholder();
- if (!has_scenes && !GDVIRTUAL_IS_OVERRIDDEN(_spawn_custom) && !has_placeholder_script) {
- warnings.push_back(RTR("A list of PackedScenes must be set in the \"Auto Spawn List\" property in order for MultiplayerSpawner to automatically spawn them remotely when added as child of \"spawn_path\"."));
- warnings.push_back(RTR("Alternatively, a Script implementing the function \"_spawn_custom\" must be set for this MultiplayerSpawner, and \"spawn\" must be called explicitly in code."));
- }
return warnings;
}
@@ -162,7 +155,9 @@ void MultiplayerSpawner::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_spawn_limit", "limit"), &MultiplayerSpawner::set_spawn_limit);
ADD_PROPERTY(PropertyInfo(Variant::INT, "spawn_limit", PROPERTY_HINT_RANGE, "0,1024,1,or_greater"), "set_spawn_limit", "get_spawn_limit");
- GDVIRTUAL_BIND(_spawn_custom, "data");
+ ClassDB::bind_method(D_METHOD("get_spawn_function"), &MultiplayerSpawner::get_spawn_function);
+ ClassDB::bind_method(D_METHOD("set_spawn_function", "spawn_function"), &MultiplayerSpawner::set_spawn_function);
+ ADD_PROPERTY(PropertyInfo(Variant::CALLABLE, "spawn_function", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_spawn_function", "get_spawn_function");
ADD_SIGNAL(MethodInfo("despawned", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node")));
ADD_SIGNAL(MethodInfo("spawned", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node")));
@@ -183,7 +178,7 @@ void MultiplayerSpawner::_update_spawn_node() {
Node *node = spawn_path.is_empty() && is_inside_tree() ? nullptr : get_node_or_null(spawn_path);
if (node) {
spawn_node = node->get_instance_id();
- if (get_spawnable_scene_count() && !GDVIRTUAL_IS_OVERRIDDEN(_spawn_custom)) {
+ if (get_spawnable_scene_count()) {
node->connect("child_entered_tree", callable_mp(this, &MultiplayerSpawner::_node_added));
}
} else {
@@ -298,23 +293,26 @@ Node *MultiplayerSpawner::instantiate_scene(int p_id) {
Node *MultiplayerSpawner::instantiate_custom(const Variant &p_data) {
ERR_FAIL_COND_V_MSG(spawn_limit && spawn_limit <= tracked_nodes.size(), nullptr, "Spawn limit reached!");
- Node *node = nullptr;
- if (GDVIRTUAL_CALL(_spawn_custom, p_data, node)) {
- return node;
- }
- ERR_FAIL_V_MSG(nullptr, "Method '_spawn_custom' is not implemented on this peer.");
+ ERR_FAIL_COND_V_MSG(!spawn_function.is_valid(), nullptr, "Custom spawn requires a valid 'spawn_function'.");
+ const Variant *argv[1] = { &p_data };
+ Variant ret;
+ Callable::CallError ce;
+ spawn_function.callp(argv, 1, ret, ce);
+ ERR_FAIL_COND_V_MSG(ce.error != Callable::CallError::CALL_OK, nullptr, "Failed to call spawn function.");
+ ERR_FAIL_COND_V_MSG(ret.get_type() != Variant::OBJECT, nullptr, "The spawn function must return a Node.");
+ return Object::cast_to<Node>(ret.operator Object *());
}
Node *MultiplayerSpawner::spawn(const Variant &p_data) {
ERR_FAIL_COND_V(!is_inside_tree() || !get_multiplayer()->has_multiplayer_peer() || !is_multiplayer_authority(), nullptr);
ERR_FAIL_COND_V_MSG(spawn_limit && spawn_limit <= tracked_nodes.size(), nullptr, "Spawn limit reached!");
- ERR_FAIL_COND_V_MSG(!GDVIRTUAL_IS_OVERRIDDEN(_spawn_custom), nullptr, "Custom spawn requires the '_spawn_custom' virtual method to be implemented via script.");
+ ERR_FAIL_COND_V_MSG(!spawn_function.is_valid(), nullptr, "Custom spawn requires the 'spawn_function' property to be a valid callable.");
Node *parent = get_spawn_node();
ERR_FAIL_COND_V_MSG(!parent, nullptr, "Cannot find spawn node.");
Node *node = instantiate_custom(p_data);
- ERR_FAIL_COND_V_MSG(!node, nullptr, "The '_spawn_custom' implementation must return a valid Node.");
+ ERR_FAIL_COND_V_MSG(!node, nullptr, "The 'spawn_function' callable must return a valid node.");
_track(node, p_data);
parent->add_child(node, true);
diff --git a/modules/multiplayer/multiplayer_spawner.h b/modules/multiplayer/multiplayer_spawner.h
index 3793c2d111..8d401a6818 100644
--- a/modules/multiplayer/multiplayer_spawner.h
+++ b/modules/multiplayer/multiplayer_spawner.h
@@ -71,6 +71,7 @@ private:
ObjectID spawn_node;
HashMap<ObjectID, SpawnInfo> tracked_nodes;
uint32_t spawn_limit = 0;
+ Callable spawn_function;
void _update_spawn_node();
void _track(Node *p_node, const Variant &p_argument, int p_scene_id = INVALID_ID);
@@ -106,6 +107,8 @@ public:
void set_spawn_path(const NodePath &p_path);
uint32_t get_spawn_limit() const { return spawn_limit; }
void set_spawn_limit(uint32_t p_limit) { spawn_limit = p_limit; }
+ void set_spawn_function(Callable p_spawn_function) { spawn_function = p_spawn_function; }
+ Callable get_spawn_function() const { return spawn_function; }
const Variant get_spawn_argument(const ObjectID &p_id) const;
int find_spawnable_scene_index_from_object(const ObjectID &p_id) const;
@@ -114,8 +117,6 @@ public:
Node *instantiate_custom(const Variant &p_data);
Node *instantiate_scene(int p_idx);
- GDVIRTUAL1R(Node *, _spawn_custom, const Variant &);
-
MultiplayerSpawner() {}
};
diff --git a/modules/multiplayer/scene_multiplayer.cpp b/modules/multiplayer/scene_multiplayer.cpp
index 0a580f782f..01fc1b5275 100644
--- a/modules/multiplayer/scene_multiplayer.cpp
+++ b/modules/multiplayer/scene_multiplayer.cpp
@@ -610,7 +610,6 @@ Error SceneMultiplayer::object_configuration_remove(Object *p_obj, Variant p_con
}
void SceneMultiplayer::set_server_relay_enabled(bool p_enabled) {
- ERR_FAIL_COND_MSG(multiplayer_peer.is_valid() && multiplayer_peer->get_connection_status() != MultiplayerPeer::CONNECTION_DISCONNECTED, "Cannot change the server relay option while the multiplayer peer is active.");
server_relay = p_enabled;
}
diff --git a/modules/navigation/godot_navigation_server.cpp b/modules/navigation/godot_navigation_server.cpp
index 4532ea9391..b9b92b77c9 100644
--- a/modules/navigation/godot_navigation_server.cpp
+++ b/modules/navigation/godot_navigation_server.cpp
@@ -52,7 +52,7 @@ using namespace NavigationUtilities;
server->MERGE(_cmd_, F_NAME)(d_0); \
} \
}; \
- void GodotNavigationServer::F_NAME(T_0 D_0) const { \
+ void GodotNavigationServer::F_NAME(T_0 D_0) { \
auto cmd = memnew(MERGE(F_NAME, _command)( \
D_0)); \
add_command(cmd); \
@@ -73,7 +73,7 @@ using namespace NavigationUtilities;
server->MERGE(_cmd_, F_NAME)(d_0, d_1); \
} \
}; \
- void GodotNavigationServer::F_NAME(T_0 D_0, T_1 D_1) const { \
+ void GodotNavigationServer::F_NAME(T_0 D_0, T_1 D_1) { \
auto cmd = memnew(MERGE(F_NAME, _command)( \
D_0, \
D_1)); \
@@ -81,34 +81,34 @@ using namespace NavigationUtilities;
} \
void GodotNavigationServer::MERGE(_cmd_, F_NAME)(T_0 D_0, T_1 D_1)
-#define COMMAND_4(F_NAME, T_0, D_0, T_1, D_1, T_2, D_2, T_3, D_3) \
- struct MERGE(F_NAME, _command) : public SetCommand { \
- T_0 d_0; \
- T_1 d_1; \
- T_2 d_2; \
- T_3 d_3; \
- MERGE(F_NAME, _command) \
- ( \
- T_0 p_d_0, \
- T_1 p_d_1, \
- T_2 p_d_2, \
- T_3 p_d_3) : \
- d_0(p_d_0), \
- d_1(p_d_1), \
- d_2(p_d_2), \
- d_3(p_d_3) {} \
- virtual void exec(GodotNavigationServer *server) override { \
- server->MERGE(_cmd_, F_NAME)(d_0, d_1, d_2, d_3); \
- } \
- }; \
- void GodotNavigationServer::F_NAME(T_0 D_0, T_1 D_1, T_2 D_2, T_3 D_3) const { \
- auto cmd = memnew(MERGE(F_NAME, _command)( \
- D_0, \
- D_1, \
- D_2, \
- D_3)); \
- add_command(cmd); \
- } \
+#define COMMAND_4(F_NAME, T_0, D_0, T_1, D_1, T_2, D_2, T_3, D_3) \
+ struct MERGE(F_NAME, _command) : public SetCommand { \
+ T_0 d_0; \
+ T_1 d_1; \
+ T_2 d_2; \
+ T_3 d_3; \
+ MERGE(F_NAME, _command) \
+ ( \
+ T_0 p_d_0, \
+ T_1 p_d_1, \
+ T_2 p_d_2, \
+ T_3 p_d_3) : \
+ d_0(p_d_0), \
+ d_1(p_d_1), \
+ d_2(p_d_2), \
+ d_3(p_d_3) {} \
+ virtual void exec(GodotNavigationServer *server) override { \
+ server->MERGE(_cmd_, F_NAME)(d_0, d_1, d_2, d_3); \
+ } \
+ }; \
+ void GodotNavigationServer::F_NAME(T_0 D_0, T_1 D_1, T_2 D_2, T_3 D_3) { \
+ auto cmd = memnew(MERGE(F_NAME, _command)( \
+ D_0, \
+ D_1, \
+ D_2, \
+ D_3)); \
+ add_command(cmd); \
+ } \
void GodotNavigationServer::MERGE(_cmd_, F_NAME)(T_0 D_0, T_1 D_1, T_2 D_2, T_3 D_3)
GodotNavigationServer::GodotNavigationServer() {}
@@ -117,12 +117,10 @@ GodotNavigationServer::~GodotNavigationServer() {
flush_queries();
}
-void GodotNavigationServer::add_command(SetCommand *command) const {
- GodotNavigationServer *mut_this = const_cast<GodotNavigationServer *>(this);
- {
- MutexLock lock(commands_mutex);
- mut_this->commands.push_back(command);
- }
+void GodotNavigationServer::add_command(SetCommand *command) {
+ MutexLock lock(commands_mutex);
+
+ commands.push_back(command);
}
TypedArray<RID> GodotNavigationServer::get_maps() const {
@@ -137,12 +135,12 @@ TypedArray<RID> GodotNavigationServer::get_maps() const {
return all_map_rids;
}
-RID GodotNavigationServer::map_create() const {
- GodotNavigationServer *mut_this = const_cast<GodotNavigationServer *>(this);
- MutexLock lock(mut_this->operations_mutex);
+RID GodotNavigationServer::map_create() {
+ MutexLock lock(operations_mutex);
+
RID rid = map_owner.make_rid();
- NavMap *space = map_owner.get_or_null(rid);
- space->set_self(rid);
+ NavMap *map = map_owner.get_or_null(rid);
+ map->set_self(rid);
return rid;
}
@@ -279,8 +277,10 @@ TypedArray<RID> GodotNavigationServer::map_get_regions(RID p_map) const {
TypedArray<RID> regions_rids;
const NavMap *map = map_owner.get_or_null(p_map);
ERR_FAIL_COND_V(map == nullptr, regions_rids);
+
const LocalVector<NavRegion *> regions = map->get_regions();
regions_rids.resize(regions.size());
+
for (uint32_t i = 0; i < regions.size(); i++) {
regions_rids[i] = regions[i]->get_self();
}
@@ -291,8 +291,10 @@ TypedArray<RID> GodotNavigationServer::map_get_agents(RID p_map) const {
TypedArray<RID> agents_rids;
const NavMap *map = map_owner.get_or_null(p_map);
ERR_FAIL_COND_V(map == nullptr, agents_rids);
+
const LocalVector<RvoAgent *> agents = map->get_agents();
agents_rids.resize(agents.size());
+
for (uint32_t i = 0; i < agents.size(); i++) {
agents_rids[i] = agents[i]->get_self();
}
@@ -302,6 +304,7 @@ TypedArray<RID> GodotNavigationServer::map_get_agents(RID p_map) const {
RID GodotNavigationServer::region_get_map(RID p_region) const {
NavRegion *region = region_owner.get_or_null(p_region);
ERR_FAIL_COND_V(region == nullptr, RID());
+
if (region->get_map()) {
return region->get_map()->get_self();
}
@@ -311,15 +314,16 @@ RID GodotNavigationServer::region_get_map(RID p_region) const {
RID GodotNavigationServer::agent_get_map(RID p_agent) const {
RvoAgent *agent = agent_owner.get_or_null(p_agent);
ERR_FAIL_COND_V(agent == nullptr, RID());
+
if (agent->get_map()) {
return agent->get_map()->get_self();
}
return RID();
}
-RID GodotNavigationServer::region_create() const {
- GodotNavigationServer *mut_this = const_cast<GodotNavigationServer *>(this);
- MutexLock lock(mut_this->operations_mutex);
+RID GodotNavigationServer::region_create() {
+ MutexLock lock(operations_mutex);
+
RID rid = region_owner.make_rid();
NavRegion *reg = region_owner.get_or_null(rid);
reg->set_self(rid);
@@ -402,6 +406,7 @@ ObjectID GodotNavigationServer::region_get_owner_id(RID p_region) const {
bool GodotNavigationServer::region_owns_point(RID p_region, const Vector3 &p_point) const {
const NavRegion *region = region_owner.get_or_null(p_region);
ERR_FAIL_COND_V(region == nullptr, false);
+
if (region->get_map()) {
RID closest_point_owner = map_get_closest_point_owner(region->get_map()->get_self(), p_point);
return closest_point_owner == region->get_self();
@@ -430,7 +435,7 @@ COMMAND_2(region_set_navigation_mesh, RID, p_region, Ref<NavigationMesh>, p_navi
region->set_mesh(p_navigation_mesh);
}
-void GodotNavigationServer::region_bake_navigation_mesh(Ref<NavigationMesh> p_navigation_mesh, Node *p_root_node) const {
+void GodotNavigationServer::region_bake_navigation_mesh(Ref<NavigationMesh> p_navigation_mesh, Node *p_root_node) {
ERR_FAIL_COND(p_navigation_mesh.is_null());
ERR_FAIL_COND(p_root_node == nullptr);
@@ -461,9 +466,9 @@ Vector3 GodotNavigationServer::region_get_connection_pathway_end(RID p_region, i
return region->get_connection_pathway_end(p_connection_id);
}
-RID GodotNavigationServer::link_create() const {
- GodotNavigationServer *mut_this = const_cast<GodotNavigationServer *>(this);
- MutexLock lock(mut_this->operations_mutex);
+RID GodotNavigationServer::link_create() {
+ MutexLock lock(operations_mutex);
+
RID rid = link_owner.make_rid();
NavLink *link = link_owner.get_or_null(rid);
link->set_self(rid);
@@ -600,9 +605,9 @@ ObjectID GodotNavigationServer::link_get_owner_id(RID p_link) const {
return link->get_owner_id();
}
-RID GodotNavigationServer::agent_create() const {
- GodotNavigationServer *mut_this = const_cast<GodotNavigationServer *>(this);
- MutexLock lock(mut_this->operations_mutex);
+RID GodotNavigationServer::agent_create() {
+ MutexLock lock(operations_mutex);
+
RID rid = agent_owner.make_rid();
RvoAgent *agent = agent_owner.get_or_null(rid);
agent->set_self(rid);
@@ -789,10 +794,10 @@ COMMAND_1(free, RID, p_object) {
}
}
-void GodotNavigationServer::set_active(bool p_active) const {
- GodotNavigationServer *mut_this = const_cast<GodotNavigationServer *>(this);
- MutexLock lock(mut_this->operations_mutex);
- mut_this->active = p_active;
+void GodotNavigationServer::set_active(bool p_active) {
+ MutexLock lock(operations_mutex);
+
+ active = p_active;
}
void GodotNavigationServer::flush_queries() {
@@ -800,6 +805,7 @@ void GodotNavigationServer::flush_queries() {
// even with mutable functions.
MutexLock lock(commands_mutex);
MutexLock lock2(operations_mutex);
+
for (size_t i(0); i < commands.size(); i++) {
commands[i]->exec(this);
memdelete(commands[i]);
diff --git a/modules/navigation/godot_navigation_server.h b/modules/navigation/godot_navigation_server.h
index 519034119d..7a6e5bb208 100644
--- a/modules/navigation/godot_navigation_server.h
+++ b/modules/navigation/godot_navigation_server.h
@@ -46,16 +46,16 @@
#define MERGE_INTERNAL(A, B) A##B
#define MERGE(A, B) MERGE_INTERNAL(A, B)
-#define COMMAND_1(F_NAME, T_0, D_0) \
- virtual void F_NAME(T_0 D_0) const override; \
+#define COMMAND_1(F_NAME, T_0, D_0) \
+ virtual void F_NAME(T_0 D_0) override; \
void MERGE(_cmd_, F_NAME)(T_0 D_0)
-#define COMMAND_2(F_NAME, T_0, D_0, T_1, D_1) \
- virtual void F_NAME(T_0 D_0, T_1 D_1) const override; \
+#define COMMAND_2(F_NAME, T_0, D_0, T_1, D_1) \
+ virtual void F_NAME(T_0 D_0, T_1 D_1) override; \
void MERGE(_cmd_, F_NAME)(T_0 D_0, T_1 D_1)
-#define COMMAND_4_DEF(F_NAME, T_0, D_0, T_1, D_1, T_2, D_2, T_3, D_3, D_3_DEF) \
- virtual void F_NAME(T_0 D_0, T_1 D_1, T_2 D_2, T_3 D_3 = D_3_DEF) const override; \
+#define COMMAND_4_DEF(F_NAME, T_0, D_0, T_1, D_1, T_2, D_2, T_3, D_3, D_3_DEF) \
+ virtual void F_NAME(T_0 D_0, T_1 D_1, T_2 D_2, T_3 D_3 = D_3_DEF) override; \
void MERGE(_cmd_, F_NAME)(T_0 D_0, T_1 D_1, T_2 D_2, T_3 D_3)
class GodotNavigationServer;
@@ -85,11 +85,11 @@ public:
GodotNavigationServer();
virtual ~GodotNavigationServer();
- void add_command(SetCommand *command) const;
+ void add_command(SetCommand *command);
virtual TypedArray<RID> get_maps() const override;
- virtual RID map_create() const override;
+ virtual RID map_create() override;
COMMAND_2(map_set_active, RID, p_map, bool, p_active);
virtual bool map_is_active(RID p_map) const override;
@@ -118,7 +118,7 @@ public:
virtual void map_force_update(RID p_map) override;
- virtual RID region_create() const override;
+ virtual RID region_create() override;
COMMAND_2(region_set_enter_cost, RID, p_region, real_t, p_enter_cost);
virtual real_t region_get_enter_cost(RID p_region) const override;
@@ -136,12 +136,12 @@ public:
virtual uint32_t region_get_navigation_layers(RID p_region) const override;
COMMAND_2(region_set_transform, RID, p_region, Transform3D, p_transform);
COMMAND_2(region_set_navigation_mesh, RID, p_region, Ref<NavigationMesh>, p_navigation_mesh);
- virtual void region_bake_navigation_mesh(Ref<NavigationMesh> p_navigation_mesh, Node *p_root_node) const override;
+ virtual void region_bake_navigation_mesh(Ref<NavigationMesh> p_navigation_mesh, Node *p_root_node) override;
virtual int region_get_connections_count(RID p_region) const override;
virtual Vector3 region_get_connection_pathway_start(RID p_region, int p_connection_id) const override;
virtual Vector3 region_get_connection_pathway_end(RID p_region, int p_connection_id) const override;
- virtual RID link_create() const override;
+ virtual RID link_create() override;
COMMAND_2(link_set_map, RID, p_link, RID, p_map);
virtual RID link_get_map(RID p_link) const override;
COMMAND_2(link_set_bidirectional, RID, p_link, bool, p_bidirectional);
@@ -159,7 +159,7 @@ public:
COMMAND_2(link_set_owner_id, RID, p_link, ObjectID, p_owner_id);
virtual ObjectID link_get_owner_id(RID p_link) const override;
- virtual RID agent_create() const override;
+ virtual RID agent_create() override;
COMMAND_2(agent_set_map, RID, p_agent, RID, p_map);
virtual RID agent_get_map(RID p_agent) const override;
COMMAND_2(agent_set_neighbor_distance, RID, p_agent, real_t, p_distance);
@@ -176,7 +176,7 @@ public:
COMMAND_1(free, RID, p_object);
- virtual void set_active(bool p_active) const override;
+ virtual void set_active(bool p_active) override;
void flush_queries();
virtual void process(real_t p_delta_time) override;
diff --git a/modules/openxr/extensions/openxr_extension_wrapper.h b/modules/openxr/extensions/openxr_extension_wrapper.h
index 51eb06c9df..84279635b5 100644
--- a/modules/openxr/extensions/openxr_extension_wrapper.h
+++ b/modules/openxr/extensions/openxr_extension_wrapper.h
@@ -73,7 +73,7 @@ public:
virtual void on_before_instance_created() {} // `on_before_instance_created` is called before we create our OpenXR instance.
virtual void on_instance_created(const XrInstance p_instance) {} // `on_instance_created` is called right after we've successfully created our OpenXR instance.
virtual void on_instance_destroyed() {} // `on_instance_destroyed` is called right before we destroy our OpenXR instance.
- virtual void on_session_created(const XrSession p_instance) {} // `on_session_created` is called right after we've successsfully created our OpenXR session.
+ virtual void on_session_created(const XrSession p_instance) {} // `on_session_created` is called right after we've successfully created our OpenXR session.
virtual void on_session_destroyed() {} // `on_session_destroyed` is called right before we destroy our OpenXR session.
// `on_process` is called as part of our OpenXR process handling,