summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/core_constants.cpp237
-rw-r--r--core/core_constants.h5
-rw-r--r--editor/inspector_dock.cpp2
-rw-r--r--modules/gdscript/doc_classes/@GDScript.xml7
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp221
-rw-r--r--modules/gdscript/gdscript_analyzer.h3
-rw-r--r--modules/gdscript/gdscript_byte_codegen.cpp50
-rw-r--r--modules/gdscript/gdscript_byte_codegen.h3
-rw-r--r--modules/gdscript/gdscript_codegen.h3
-rw-r--r--modules/gdscript/gdscript_compiler.cpp60
-rw-r--r--modules/gdscript/gdscript_disassembler.cpp49
-rw-r--r--modules/gdscript/gdscript_editor.cpp14
-rw-r--r--modules/gdscript/gdscript_function.h16
-rw-r--r--modules/gdscript/gdscript_parser.cpp33
-rw-r--r--modules/gdscript/gdscript_parser.h19
-rw-r--r--modules/gdscript/gdscript_utility_functions.cpp77
-rw-r--r--modules/gdscript/gdscript_vm.cpp134
-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_function_parameter_wrong_type.out2
-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.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.out2
-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_unnamed_assign_to_named.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/preload_enum_error.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/global_enums.gd30
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/global_enums.out9
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/type_test_usage.gd127
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/type_test_usage.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_enum.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_int.out2
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/typed_argument_is_null.gd27
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/typed_argument_is_null.out3
-rw-r--r--scene/3d/soft_body_3d.cpp14
-rw-r--r--scene/3d/soft_body_3d.h2
-rw-r--r--servers/physics_3d/godot_body_pair_3d.cpp80
40 files changed, 955 insertions, 300 deletions
diff --git a/core/core_constants.cpp b/core/core_constants.cpp
index b1f56539e5..d88dda6609 100644
--- a/core/core_constants.cpp
+++ b/core/core_constants.cpp
@@ -33,14 +33,15 @@
#include "core/input/input_event.h"
#include "core/object/class_db.h"
#include "core/os/keyboard.h"
+#include "core/templates/hash_set.h"
#include "core/variant/variant.h"
struct _CoreConstant {
#ifdef DEBUG_METHODS_ENABLED
- StringName enum_name;
bool ignore_value_in_docs = false;
bool is_bitfield = false;
#endif
+ StringName enum_name;
const char *name = nullptr;
int64_t value = 0;
@@ -48,14 +49,15 @@ struct _CoreConstant {
#ifdef DEBUG_METHODS_ENABLED
_CoreConstant(const StringName &p_enum_name, const char *p_name, int64_t p_value, bool p_ignore_value_in_docs = false, bool p_is_bitfield = false) :
- enum_name(p_enum_name),
ignore_value_in_docs(p_ignore_value_in_docs),
is_bitfield(p_is_bitfield),
+ enum_name(p_enum_name),
name(p_name),
value(p_value) {
}
#else
- _CoreConstant(const char *p_name, int64_t p_value) :
+ _CoreConstant(const StringName &p_enum_name, const char *p_name, int64_t p_value) :
+ enum_name(p_enum_name),
name(p_name),
value(p_value) {
}
@@ -63,84 +65,190 @@ struct _CoreConstant {
};
static Vector<_CoreConstant> _global_constants;
+static HashMap<StringName, int> _global_constants_map;
+static HashMap<StringName, Vector<_CoreConstant>> _global_enums;
#ifdef DEBUG_METHODS_ENABLED
-#define BIND_CORE_CONSTANT(m_constant) \
- _global_constants.push_back(_CoreConstant(StringName(), #m_constant, m_constant));
+#define BIND_CORE_CONSTANT(m_constant) \
+ _global_constants.push_back(_CoreConstant(StringName(), #m_constant, m_constant)); \
+ _global_constants_map[#m_constant] = _global_constants.size() - 1;
-#define BIND_CORE_ENUM_CONSTANT(m_constant) \
- _global_constants.push_back(_CoreConstant(__constant_get_enum_name(m_constant, #m_constant), #m_constant, m_constant));
+#define BIND_CORE_ENUM_CONSTANT(m_constant) \
+ { \
+ StringName enum_name = __constant_get_enum_name(m_constant, #m_constant); \
+ _global_constants.push_back(_CoreConstant(enum_name, #m_constant, m_constant)); \
+ _global_constants_map[#m_constant] = _global_constants.size() - 1; \
+ _global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \
+ }
-#define BIND_CORE_BITFIELD_FLAG(m_constant) \
- _global_constants.push_back(_CoreConstant(__constant_get_bitfield_name(m_constant, #m_constant), #m_constant, m_constant, false, true));
+#define BIND_CORE_BITFIELD_FLAG(m_constant) \
+ { \
+ StringName enum_name = __constant_get_bitfield_name(m_constant, #m_constant); \
+ _global_constants.push_back(_CoreConstant(enum_name, #m_constant, m_constant, false, true)); \
+ _global_constants_map[#m_constant] = _global_constants.size() - 1; \
+ _global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \
+ }
// This just binds enum classes as if they were regular enum constants.
-#define BIND_CORE_ENUM_CLASS_CONSTANT(m_enum, m_prefix, m_member) \
- _global_constants.push_back(_CoreConstant(__constant_get_enum_name(m_enum::m_member, #m_prefix "_" #m_member), #m_prefix "_" #m_member, (int64_t)m_enum::m_member));
+#define BIND_CORE_ENUM_CLASS_CONSTANT(m_enum, m_prefix, m_member) \
+ { \
+ StringName enum_name = __constant_get_enum_name(m_enum::m_member, #m_prefix "_" #m_member); \
+ _global_constants.push_back(_CoreConstant(enum_name, #m_prefix "_" #m_member, (int64_t)m_enum::m_member)); \
+ _global_constants_map[#m_prefix "_" #m_member] = _global_constants.size() - 1; \
+ _global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \
+ }
-#define BIND_CORE_BITFIELD_CLASS_FLAG(m_enum, m_prefix, m_member) \
- _global_constants.push_back(_CoreConstant(__constant_get_bitfield_name(m_enum::m_member, #m_prefix "_" #m_member), #m_prefix "_" #m_member, (int64_t)m_enum::m_member, false, true));
+#define BIND_CORE_BITFIELD_CLASS_FLAG(m_enum, m_prefix, m_member) \
+ { \
+ StringName enum_name = __constant_get_bitfield_name(m_enum::m_member, #m_prefix "_" #m_member); \
+ _global_constants.push_back(_CoreConstant(enum_name, #m_prefix "_" #m_member, (int64_t)m_enum::m_member, false, true)); \
+ _global_constants_map[#m_prefix "_" #m_member] = _global_constants.size() - 1; \
+ _global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \
+ }
-#define BIND_CORE_ENUM_CLASS_CONSTANT_CUSTOM(m_enum, m_name, m_member) \
- _global_constants.push_back(_CoreConstant(__constant_get_enum_name(m_enum::m_member, #m_name), #m_name, (int64_t)m_enum::m_member));
+#define BIND_CORE_ENUM_CLASS_CONSTANT_CUSTOM(m_enum, m_name, m_member) \
+ { \
+ StringName enum_name = __constant_get_enum_name(m_enum::m_member, #m_name); \
+ _global_constants.push_back(_CoreConstant(enum_name, #m_name, (int64_t)m_enum::m_member)); \
+ _global_constants_map[#m_name] = _global_constants.size() - 1; \
+ _global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \
+ }
-#define BIND_CORE_BITFIELD_CLASS_FLAG_CUSTOM(m_enum, m_name, m_member) \
- _global_constants.push_back(_CoreConstant(__constant_get_bitfield_name(m_enum::m_member, #m_name), #m_name, (int64_t)m_enum::m_member, false, true));
+#define BIND_CORE_BITFIELD_CLASS_FLAG_CUSTOM(m_enum, m_name, m_member) \
+ { \
+ StringName enum_name = __constant_get_bitfield_name(m_enum::m_member, #m_name); \
+ _global_constants.push_back(_CoreConstant(enum_name, #m_name, (int64_t)m_enum::m_member, false, true)); \
+ _global_constants_map[#m_name] = _global_constants.size() - 1; \
+ _global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \
+ }
-#define BIND_CORE_ENUM_CLASS_CONSTANT_NO_VAL(m_enum, m_prefix, m_member) \
- _global_constants.push_back(_CoreConstant(__constant_get_enum_name(m_enum::m_member, #m_prefix "_" #m_member), #m_prefix "_" #m_member, (int64_t)m_enum::m_member, true));
+#define BIND_CORE_ENUM_CLASS_CONSTANT_NO_VAL(m_enum, m_prefix, m_member) \
+ { \
+ StringName enum_name = __constant_get_enum_name(m_enum::m_member, #m_prefix "_" #m_member); \
+ _global_constants.push_back(_CoreConstant(enum_name, #m_prefix "_" #m_member, (int64_t)m_enum::m_member, true)); \
+ _global_constants_map[#m_prefix "_" #m_member] = _global_constants.size() - 1; \
+ _global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \
+ }
-#define BIND_CORE_ENUM_CONSTANT_CUSTOM(m_custom_name, m_constant) \
- _global_constants.push_back(_CoreConstant(__constant_get_enum_name(m_constant, #m_constant), m_custom_name, m_constant));
+#define BIND_CORE_ENUM_CONSTANT_CUSTOM(m_custom_name, m_constant) \
+ { \
+ StringName enum_name = __constant_get_enum_name(m_constant, #m_constant); \
+ _global_constants.push_back(_CoreConstant(enum_name, m_custom_name, m_constant)); \
+ _global_constants_map[m_custom_name] = _global_constants.size() - 1; \
+ _global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \
+ }
-#define BIND_CORE_CONSTANT_NO_VAL(m_constant) \
- _global_constants.push_back(_CoreConstant(StringName(), #m_constant, m_constant, true));
+#define BIND_CORE_CONSTANT_NO_VAL(m_constant) \
+ _global_constants.push_back(_CoreConstant(StringName(), #m_constant, m_constant, true)); \
+ _global_constants_map[#m_constant] = _global_constants.size() - 1;
-#define BIND_CORE_ENUM_CONSTANT_NO_VAL(m_constant) \
- _global_constants.push_back(_CoreConstant(__constant_get_enum_name(m_constant, #m_constant), #m_constant, m_constant, true));
+#define BIND_CORE_ENUM_CONSTANT_NO_VAL(m_constant) \
+ { \
+ StringName enum_name = __constant_get_enum_name(m_constant, #m_constant); \
+ _global_constants.push_back(_CoreConstant(enum_name, #m_constant, m_constant, true)); \
+ _global_constants_map[#m_constant] = _global_constants.size() - 1; \
+ _global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \
+ }
-#define BIND_CORE_ENUM_CONSTANT_CUSTOM_NO_VAL(m_custom_name, m_constant) \
- _global_constants.push_back(_CoreConstant(__constant_get_enum_name(m_constant, #m_constant), m_custom_name, m_constant, true));
+#define BIND_CORE_ENUM_CONSTANT_CUSTOM_NO_VAL(m_custom_name, m_constant) \
+ { \
+ StringName enum_name = __constant_get_enum_name(m_constant, #m_constant); \
+ _global_constants.push_back(_CoreConstant(enum_name, m_custom_name, m_constant, true)); \
+ _global_constants_map[m_custom_name] = _global_constants.size() - 1; \
+ _global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \
+ }
#else
-#define BIND_CORE_CONSTANT(m_constant) \
- _global_constants.push_back(_CoreConstant(#m_constant, m_constant));
+#define BIND_CORE_CONSTANT(m_constant) \
+ _global_constants.push_back(_CoreConstant(StringName(), #m_constant, m_constant)); \
+ _global_constants_map[#m_constant] = _global_constants.size() - 1;
-#define BIND_CORE_ENUM_CONSTANT(m_constant) \
- _global_constants.push_back(_CoreConstant(#m_constant, m_constant));
+#define BIND_CORE_ENUM_CONSTANT(m_constant) \
+ { \
+ StringName enum_name = __constant_get_enum_name(m_constant, #m_constant); \
+ _global_constants.push_back(_CoreConstant(enum_name, #m_constant, m_constant)); \
+ _global_constants_map[#m_constant] = _global_constants.size() - 1; \
+ _global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \
+ }
-#define BIND_CORE_BITFIELD_FLAG(m_constant) \
- _global_constants.push_back(_CoreConstant(#m_constant, m_constant));
+#define BIND_CORE_BITFIELD_FLAG(m_constant) \
+ { \
+ StringName enum_name = __constant_get_bitfield_name(m_constant, #m_constant); \
+ _global_constants.push_back(_CoreConstant(enum_name, #m_constant, m_constant)); \
+ _global_constants_map[#m_constant] = _global_constants.size() - 1; \
+ _global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \
+ }
// This just binds enum classes as if they were regular enum constants.
-#define BIND_CORE_ENUM_CLASS_CONSTANT(m_enum, m_prefix, m_member) \
- _global_constants.push_back(_CoreConstant(#m_prefix "_" #m_member, (int64_t)m_enum::m_member));
+#define BIND_CORE_ENUM_CLASS_CONSTANT(m_enum, m_prefix, m_member) \
+ { \
+ StringName enum_name = __constant_get_enum_name(m_enum::m_member, #m_prefix "_" #m_member); \
+ _global_constants.push_back(_CoreConstant(enum_name, #m_prefix "_" #m_member, (int64_t)m_enum::m_member)); \
+ _global_constants_map[#m_prefix "_" #m_member] = _global_constants.size() - 1; \
+ _global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \
+ }
-#define BIND_CORE_BITFIELD_CLASS_FLAG(m_enum, m_prefix, m_member) \
- _global_constants.push_back(_CoreConstant(#m_prefix "_" #m_member, (int64_t)m_enum::m_member));
+#define BIND_CORE_BITFIELD_CLASS_FLAG(m_enum, m_prefix, m_member) \
+ { \
+ StringName enum_name = __constant_get_bitfield_name(m_enum::m_member, #m_prefix "_" #m_member); \
+ _global_constants.push_back(_CoreConstant(enum_name, #m_prefix "_" #m_member, (int64_t)m_enum::m_member)); \
+ _global_constants_map[#m_prefix "_" #m_member] = _global_constants.size() - 1; \
+ _global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \
+ }
-#define BIND_CORE_ENUM_CLASS_CONSTANT_CUSTOM(m_enum, m_name, m_member) \
- _global_constants.push_back(_CoreConstant(#m_name, (int64_t)m_enum::m_member));
+#define BIND_CORE_ENUM_CLASS_CONSTANT_CUSTOM(m_enum, m_name, m_member) \
+ { \
+ StringName enum_name = __constant_get_enum_name(m_enum::m_member, #m_name); \
+ _global_constants.push_back(_CoreConstant(enum_name, #m_name, (int64_t)m_enum::m_member)); \
+ _global_constants_map[#m_name] = _global_constants.size() - 1; \
+ _global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \
+ }
-#define BIND_CORE_BITFIELD_CLASS_FLAG_CUSTOM(m_enum, m_name, m_member) \
- _global_constants.push_back(_CoreConstant(#m_name, (int64_t)m_enum::m_member));
+#define BIND_CORE_BITFIELD_CLASS_FLAG_CUSTOM(m_enum, m_name, m_member) \
+ { \
+ StringName enum_name = __constant_get_bitfield_name(m_enum::m_member, #m_name); \
+ _global_constants.push_back(_CoreConstant(enum_name, #m_name, (int64_t)m_enum::m_member)); \
+ _global_constants_map[#m_name] = _global_constants.size() - 1; \
+ _global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \
+ }
-#define BIND_CORE_ENUM_CLASS_CONSTANT_NO_VAL(m_enum, m_prefix, m_member) \
- _global_constants.push_back(_CoreConstant(#m_prefix "_" #m_member, (int64_t)m_enum::m_member));
+#define BIND_CORE_ENUM_CLASS_CONSTANT_NO_VAL(m_enum, m_prefix, m_member) \
+ { \
+ StringName enum_name = __constant_get_enum_name(m_enum::m_member, #m_prefix "_" #m_member); \
+ _global_constants.push_back(_CoreConstant(enum_name, #m_prefix "_" #m_member, (int64_t)m_enum::m_member)); \
+ _global_constants_map[#m_prefix "_" #m_member] = _global_constants.size() - 1; \
+ _global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \
+ }
-#define BIND_CORE_ENUM_CONSTANT_CUSTOM(m_custom_name, m_constant) \
- _global_constants.push_back(_CoreConstant(m_custom_name, m_constant));
+#define BIND_CORE_ENUM_CONSTANT_CUSTOM(m_custom_name, m_constant) \
+ { \
+ StringName enum_name = __constant_get_enum_name(m_constant, #m_constant); \
+ _global_constants.push_back(_CoreConstant(enum_name, m_custom_name, m_constant)); \
+ _global_constants_map[m_custom_name] = _global_constants.size() - 1; \
+ _global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \
+ }
-#define BIND_CORE_CONSTANT_NO_VAL(m_constant) \
- _global_constants.push_back(_CoreConstant(#m_constant, m_constant));
+#define BIND_CORE_CONSTANT_NO_VAL(m_constant) \
+ _global_constants.push_back(_CoreConstant(StringName(), #m_constant, m_constant)); \
+ _global_constants_map[#m_constant] = _global_constants.size() - 1;
-#define BIND_CORE_ENUM_CONSTANT_NO_VAL(m_constant) \
- _global_constants.push_back(_CoreConstant(#m_constant, m_constant));
+#define BIND_CORE_ENUM_CONSTANT_NO_VAL(m_constant) \
+ { \
+ StringName enum_name = __constant_get_enum_name(m_constant, #m_constant); \
+ _global_constants.push_back(_CoreConstant(enum_name, #m_constant, m_constant)); \
+ _global_constants_map[#m_constant] = _global_constants.size() - 1; \
+ _global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \
+ }
-#define BIND_CORE_ENUM_CONSTANT_CUSTOM_NO_VAL(m_custom_name, m_constant) \
- _global_constants.push_back(_CoreConstant(m_custom_name, m_constant));
+#define BIND_CORE_ENUM_CONSTANT_CUSTOM_NO_VAL(m_custom_name, m_constant) \
+ { \
+ StringName enum_name = __constant_get_enum_name(m_constant, #m_constant); \
+ _global_constants.push_back(_CoreConstant(enum_name, m_custom_name, m_constant)); \
+ _global_constants_map[m_custom_name] = _global_constants.size() - 1; \
+ _global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \
+ }
#endif
@@ -690,11 +798,11 @@ int CoreConstants::get_global_constant_count() {
return _global_constants.size();
}
-#ifdef DEBUG_METHODS_ENABLED
StringName CoreConstants::get_global_constant_enum(int p_idx) {
return _global_constants[p_idx].enum_name;
}
+#ifdef DEBUG_METHODS_ENABLED
bool CoreConstants::is_global_constant_bitfield(int p_idx) {
return _global_constants[p_idx].is_bitfield;
}
@@ -703,10 +811,6 @@ bool CoreConstants::get_ignore_value_in_docs(int p_idx) {
return _global_constants[p_idx].ignore_value_in_docs;
}
#else
-StringName CoreConstants::get_global_constant_enum(int p_idx) {
- return StringName();
-}
-
bool CoreConstants::is_global_constant_bitfield(int p_idx) {
return false;
}
@@ -723,3 +827,24 @@ const char *CoreConstants::get_global_constant_name(int p_idx) {
int64_t CoreConstants::get_global_constant_value(int p_idx) {
return _global_constants[p_idx].value;
}
+
+bool CoreConstants::is_global_constant(const StringName &p_name) {
+ return _global_constants_map.has(p_name);
+}
+
+int CoreConstants::get_global_constant_index(const StringName &p_name) {
+ ERR_FAIL_COND_V_MSG(!_global_constants_map.has(p_name), -1, "Trying to get index of non-existing constant.");
+ return _global_constants_map[p_name];
+}
+
+bool CoreConstants::is_global_enum(const StringName &p_enum) {
+ return _global_enums.has(p_enum);
+}
+
+void CoreConstants::get_enum_values(StringName p_enum, HashMap<StringName, int64_t> *p_values) {
+ ERR_FAIL_NULL_MSG(p_values, "Trying to get enum values with null map.");
+ ERR_FAIL_COND_MSG(!_global_enums.has(p_enum), "Trying to get values of non-existing enum.");
+ for (const _CoreConstant &constant : _global_enums[p_enum]) {
+ (*p_values)[constant.name] = constant.value;
+ }
+}
diff --git a/core/core_constants.h b/core/core_constants.h
index 5a5cd4394c..51842490c8 100644
--- a/core/core_constants.h
+++ b/core/core_constants.h
@@ -32,6 +32,7 @@
#define CORE_CONSTANTS_H
#include "core/string/string_name.h"
+#include "core/templates/hash_set.h"
class CoreConstants {
public:
@@ -41,6 +42,10 @@ public:
static bool get_ignore_value_in_docs(int p_idx);
static const char *get_global_constant_name(int p_idx);
static int64_t get_global_constant_value(int p_idx);
+ static bool is_global_constant(const StringName &p_name);
+ static int get_global_constant_index(const StringName &p_name);
+ static bool is_global_enum(const StringName &p_enum);
+ static void get_enum_values(StringName p_enum, HashMap<StringName, int64_t> *p_values);
};
#endif // CORE_CONSTANTS_H
diff --git a/editor/inspector_dock.cpp b/editor/inspector_dock.cpp
index 52482ecb16..ab061c4d5c 100644
--- a/editor/inspector_dock.cpp
+++ b/editor/inspector_dock.cpp
@@ -186,7 +186,7 @@ void InspectorDock::_menu_option_confirm(int p_option, bool p_confirmed) {
}
}
- int history_id = EditorUndoRedoManager::get_singleton()->get_history_for_object(current).id;
+ int history_id = EditorUndoRedoManager::get_singleton()->get_history_id_for_object(current);
EditorUndoRedoManager::get_singleton()->clear_history(true, history_id);
EditorNode::get_singleton()->edit_item(current, inspector);
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml
index 0b7e4e50e6..08c8763493 100644
--- a/modules/gdscript/doc_classes/@GDScript.xml
+++ b/modules/gdscript/doc_classes/@GDScript.xml
@@ -121,6 +121,13 @@
[/codeblock]
</description>
</method>
+ <method name="is_instance_of">
+ <return type="bool" />
+ <param index="0" name="value" type="Variant" />
+ <param index="1" name="type" type="Variant" />
+ <description>
+ </description>
+ </method>
<method name="len">
<return type="int" />
<param index="0" name="var" type="Variant" />
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index c8dfdbdd68..38f9163f70 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -32,6 +32,7 @@
#include "core/config/engine.h"
#include "core/config/project_settings.h"
+#include "core/core_constants.h"
#include "core/core_string_names.h"
#include "core/io/file_access.h"
#include "core/io/resource_loader.h"
@@ -48,7 +49,7 @@
#endif
#define UNNAMED_ENUM "<anonymous enum>"
-#define ENUM_SEPARATOR "::"
+#define ENUM_SEPARATOR "."
static MethodInfo info_from_utility_func(const StringName &p_function) {
ERR_FAIL_COND_V(!Variant::has_utility_function(p_function), MethodInfo());
@@ -137,12 +138,16 @@ static GDScriptParser::DataType make_enum_type(const StringName &p_enum_name, co
// 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;
+ if (p_base_name.is_empty()) {
+ type.native_type = p_enum_name;
+ } else {
+ 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) {
+static GDScriptParser::DataType make_native_enum_type(const StringName &p_enum_name, const StringName &p_native_class, bool p_meta = true) {
// Find out which base class declared the enum, so the name is always the same even when coming from other contexts.
StringName native_base = p_native_class;
while (true && native_base != StringName()) {
@@ -154,7 +159,7 @@ static GDScriptParser::DataType make_native_enum_type(const StringName &p_enum_n
GDScriptParser::DataType type = make_enum_type(p_enum_name, native_base, p_meta);
if (p_meta) {
- type.builtin_type = Variant::NIL; // Native enum types are not Dictionaries
+ type.builtin_type = Variant::NIL; // Native enum types are not Dictionaries.
}
List<StringName> enum_values;
@@ -167,6 +172,22 @@ static GDScriptParser::DataType make_native_enum_type(const StringName &p_enum_n
return type;
}
+static GDScriptParser::DataType make_global_enum_type(const StringName &p_enum_name, const StringName &p_base, bool p_meta = true) {
+ GDScriptParser::DataType type = make_enum_type(p_enum_name, p_base, p_meta);
+ if (p_meta) {
+ type.builtin_type = Variant::NIL; // Native enum types are not Dictionaries.
+ type.is_pseudo_type = true;
+ }
+
+ HashMap<StringName, int64_t> enum_values;
+ CoreConstants::get_enum_values(type.native_type, &enum_values);
+ for (const KeyValue<StringName, int64_t> &element : enum_values) {
+ type.enum_values[element.key] = element.value;
+ }
+
+ return type;
+}
+
static GDScriptParser::DataType make_builtin_meta_type(Variant::Type p_type) {
GDScriptParser::DataType type;
type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
@@ -562,7 +583,6 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
GDScriptParser::DataType result;
result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
- result.builtin_type = Variant::OBJECT;
if (p_type->type_chain.is_empty()) {
// void.
@@ -575,15 +595,26 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
StringName first = p_type->type_chain[0]->name;
if (first == SNAME("Variant")) {
- if (p_type->type_chain.size() > 1) {
- // TODO: Variant does actually have a nested Type though.
- push_error(R"(Variant doesn't contain nested types.)", p_type->type_chain[1]);
+ if (p_type->type_chain.size() == 2) {
+ // May be nested enum.
+ StringName enum_name = p_type->type_chain[1]->name;
+ StringName qualified_name = String(first) + ENUM_SEPARATOR + String(p_type->type_chain[1]->name);
+ if (CoreConstants::is_global_enum(qualified_name)) {
+ result = make_global_enum_type(enum_name, first, true);
+ return result;
+ } else {
+ push_error(vformat(R"(Name "%s" is not a nested type of "Variant".)", enum_name), p_type->type_chain[1]);
+ return bad_type;
+ }
+ } else if (p_type->type_chain.size() > 2) {
+ push_error(R"(Variant only contains enum types, which do not have nested types.)", p_type->type_chain[2]);
return bad_type;
}
result.kind = GDScriptParser::DataType::VARIANT;
} else if (first == SNAME("Object")) {
// Object is treated like a native type, not a built-in.
result.kind = GDScriptParser::DataType::NATIVE;
+ result.builtin_type = Variant::OBJECT;
result.native_type = SNAME("Object");
} else if (GDScriptParser::get_builtin_type(first) < Variant::VARIANT_MAX) {
// Built-in types.
@@ -604,6 +635,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
} else if (class_exists(first)) {
// Native engine classes.
result.kind = GDScriptParser::DataType::NATIVE;
+ result.builtin_type = Variant::OBJECT;
result.native_type = first;
} else if (ScriptServer::is_global_class(first)) {
if (parser->script_path == ScriptServer::get_global_class_path(first)) {
@@ -633,6 +665,12 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
} else if (ClassDB::has_enum(parser->current_class->base_type.native_type, first)) {
// Native enum in current class.
result = make_native_enum_type(first, parser->current_class->base_type.native_type);
+ } else if (CoreConstants::is_global_enum(first)) {
+ if (p_type->type_chain.size() > 1) {
+ push_error(R"(Enums cannot contain nested types.)", p_type->type_chain[1]);
+ return bad_type;
+ }
+ result = make_global_enum_type(first, StringName());
} else {
// Classes in current scope.
List<GDScriptParser::ClassNode *> script_classes;
@@ -1338,6 +1376,7 @@ void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node, bool p_is_root
case GDScriptParser::Node::SELF:
case GDScriptParser::Node::SUBSCRIPT:
case GDScriptParser::Node::TERNARY_OPERATOR:
+ case GDScriptParser::Node::TYPE_TEST:
case GDScriptParser::Node::UNARY_OPERATOR:
reduce_expression(static_cast<GDScriptParser::ExpressionNode *>(p_node), p_is_root);
break;
@@ -2196,6 +2235,9 @@ void GDScriptAnalyzer::reduce_expression(GDScriptParser::ExpressionNode *p_expre
case GDScriptParser::Node::TERNARY_OPERATOR:
reduce_ternary_op(static_cast<GDScriptParser::TernaryOpNode *>(p_expression), p_is_root);
break;
+ case GDScriptParser::Node::TYPE_TEST:
+ reduce_type_test(static_cast<GDScriptParser::TypeTestNode *>(p_expression));
+ break;
case GDScriptParser::Node::UNARY_OPERATOR:
reduce_unary_op(static_cast<GDScriptParser::UnaryOpNode *>(p_expression));
break;
@@ -2502,13 +2544,7 @@ void GDScriptAnalyzer::reduce_await(GDScriptParser::AwaitNode *p_await) {
void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_op) {
reduce_expression(p_binary_op->left_operand);
-
- if (p_binary_op->operation == GDScriptParser::BinaryOpNode::OP_TYPE_TEST && p_binary_op->right_operand && p_binary_op->right_operand->type == GDScriptParser::Node::IDENTIFIER) {
- reduce_identifier(static_cast<GDScriptParser::IdentifierNode *>(p_binary_op->right_operand), true);
- } else {
- reduce_expression(p_binary_op->right_operand);
- }
- // TODO: Right operand must be a valid type with the `is` operator. Need to check here.
+ reduce_expression(p_binary_op->right_operand);
GDScriptParser::DataType left_type;
if (p_binary_op->left_operand) {
@@ -2546,19 +2582,7 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o
}
}
} else {
- if (p_binary_op->operation == GDScriptParser::BinaryOpNode::OP_TYPE_TEST) {
- GDScriptParser::DataType test_type = right_type;
- test_type.is_meta_type = false;
-
- if (!is_type_compatible(test_type, left_type)) {
- push_error(vformat(R"(Expression is of type "%s" so it can't be of type "%s".)"), p_binary_op->left_operand);
- p_binary_op->reduced_value = false;
- } else {
- p_binary_op->reduced_value = true;
- }
- } else {
- ERR_PRINT("Parser bug: unknown binary operation.");
- }
+ ERR_PRINT("Parser bug: unknown binary operation.");
}
p_binary_op->set_datatype(type_from_variant(p_binary_op->reduced_value, p_binary_op));
@@ -2567,24 +2591,7 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o
GDScriptParser::DataType result;
- if (p_binary_op->operation == GDScriptParser::BinaryOpNode::OP_TYPE_TEST) {
- GDScriptParser::DataType test_type = right_type;
- test_type.is_meta_type = false;
-
- if (!is_type_compatible(test_type, left_type) && !is_type_compatible(left_type, test_type)) {
- if (left_type.is_hard_type()) {
- push_error(vformat(R"(Expression is of type "%s" so it can't be of type "%s".)", left_type.to_string(), test_type.to_string()), p_binary_op->left_operand);
- } else {
- // TODO: Warning.
- mark_node_unsafe(p_binary_op);
- }
- }
-
- // "is" operator is always a boolean anyway.
- result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
- result.kind = GDScriptParser::DataType::BUILTIN;
- result.builtin_type = Variant::BOOL;
- } else if ((p_binary_op->variant_op == Variant::OP_EQUAL || p_binary_op->variant_op == Variant::OP_NOT_EQUAL) &&
+ if ((p_binary_op->variant_op == Variant::OP_EQUAL || p_binary_op->variant_op == Variant::OP_NOT_EQUAL) &&
((left_type.kind == GDScriptParser::DataType::BUILTIN && left_type.builtin_type == Variant::NIL) || (right_type.kind == GDScriptParser::DataType::BUILTIN && right_type.builtin_type == Variant::NIL))) {
// "==" and "!=" operators always return a boolean when comparing to null.
result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
@@ -2599,6 +2606,8 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o
result = get_operation_type(p_binary_op->variant_op, left_type, right_type, valid, p_binary_op);
if (!valid) {
push_error(vformat(R"(Invalid operands "%s" and "%s" for "%s" operator.)", left_type.to_string(), right_type.to_string(), Variant::get_operator_name(p_binary_op->variant_op)), p_binary_op);
+ } else if (result.type_source != GDScriptParser::DataType::ANNOTATED_EXPLICIT) {
+ mark_node_unsafe(p_binary_op);
}
} else {
ERR_PRINT("Parser bug: unknown binary operation.");
@@ -3661,6 +3670,20 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
}
}
+ if (CoreConstants::is_global_constant(name)) {
+ int index = CoreConstants::get_global_constant_index(name);
+ StringName enum_name = CoreConstants::get_global_constant_enum(index);
+ int64_t value = CoreConstants::get_global_constant_value(index);
+ if (enum_name != StringName()) {
+ p_identifier->set_datatype(make_global_enum_type(enum_name, StringName(), false));
+ } else {
+ p_identifier->set_datatype(type_from_variant(value, p_identifier));
+ }
+ p_identifier->is_constant = true;
+ p_identifier->reduced_value = value;
+ return;
+ }
+
if (GDScriptLanguage::get_singleton()->has_any_global_constant(name)) {
Variant constant = GDScriptLanguage::get_singleton()->get_any_global_constant(name);
p_identifier->set_datatype(type_from_variant(constant, p_identifier));
@@ -3669,6 +3692,25 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
return;
}
+ if (CoreConstants::is_global_enum(name)) {
+ p_identifier->set_datatype(make_global_enum_type(name, StringName(), true));
+ if (!can_be_builtin) {
+ push_error(vformat(R"(Global enum "%s" cannot be used on its own.)", name), p_identifier);
+ }
+ return;
+ }
+
+ // Allow "Variant" here since it might be used for nested enums.
+ if (can_be_builtin && name == SNAME("Variant")) {
+ GDScriptParser::DataType variant;
+ variant.kind = GDScriptParser::DataType::VARIANT;
+ variant.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ variant.is_meta_type = true;
+ variant.is_pseudo_type = true;
+ p_identifier->set_datatype(variant);
+ return;
+ }
+
// Not found.
// Check if it's a builtin function.
if (GDScriptUtilityFunctions::function_exists(name)) {
@@ -3809,12 +3851,14 @@ void GDScriptAnalyzer::reduce_self(GDScriptParser::SelfNode *p_self) {
mark_lambda_use_self();
}
-void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscript) {
+void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscript, bool p_can_be_pseudo_type) {
if (p_subscript->base == nullptr) {
return;
}
if (p_subscript->base->type == GDScriptParser::Node::IDENTIFIER) {
reduce_identifier(static_cast<GDScriptParser::IdentifierNode *>(p_subscript->base), true);
+ } else if (p_subscript->base->type == GDScriptParser::Node::SUBSCRIPT) {
+ reduce_subscript(static_cast<GDScriptParser::SubscriptNode *>(p_subscript->base), true);
} else {
reduce_expression(p_subscript->base);
}
@@ -3838,14 +3882,28 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
result_type = type_from_variant(value, p_subscript);
}
} else if (base_type.is_variant() || !base_type.is_hard_type()) {
- valid = true;
+ valid = !base_type.is_pseudo_type || p_can_be_pseudo_type;
result_type.kind = GDScriptParser::DataType::VARIANT;
- mark_node_unsafe(p_subscript);
+ if (base_type.is_variant() && base_type.is_hard_type() && base_type.is_meta_type && base_type.is_pseudo_type) {
+ // Special case: it may be a global enum with pseudo base (e.g. Variant.Type).
+ String enum_name;
+ if (p_subscript->base->type == GDScriptParser::Node::IDENTIFIER) {
+ enum_name = String(static_cast<GDScriptParser::IdentifierNode *>(p_subscript->base)->name) + ENUM_SEPARATOR + String(p_subscript->attribute->name);
+ }
+ if (CoreConstants::is_global_enum(enum_name)) {
+ result_type = make_global_enum_type(enum_name, StringName());
+ } else {
+ valid = false;
+ mark_node_unsafe(p_subscript);
+ }
+ } else {
+ mark_node_unsafe(p_subscript);
+ }
} else {
reduce_identifier_from_base(p_subscript->attribute, &base_type);
GDScriptParser::DataType attr_type = p_subscript->attribute->get_datatype();
if (attr_type.is_set()) {
- valid = true;
+ valid = !attr_type.is_pseudo_type || p_can_be_pseudo_type;
result_type = attr_type;
p_subscript->is_constant = p_subscript->attribute->is_constant;
p_subscript->reduced_value = p_subscript->attribute->reduced_value;
@@ -3861,7 +3919,12 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
}
}
if (!valid) {
- push_error(vformat(R"(Cannot find member "%s" in base "%s".)", p_subscript->attribute->name, type_from_metatype(base_type).to_string()), p_subscript->attribute);
+ GDScriptParser::DataType attr_type = p_subscript->attribute->get_datatype();
+ if (!p_can_be_pseudo_type && (attr_type.is_pseudo_type || result_type.is_pseudo_type)) {
+ push_error(vformat(R"(Type "%s" in base "%s" cannot be used on its own.)", p_subscript->attribute->name, type_from_metatype(base_type).to_string()), p_subscript->attribute);
+ } else {
+ push_error(vformat(R"(Cannot find member "%s" in base "%s".)", p_subscript->attribute->name, type_from_metatype(base_type).to_string()), p_subscript->attribute);
+ }
result_type.kind = GDScriptParser::DataType::VARIANT;
}
} else {
@@ -4107,6 +4170,48 @@ void GDScriptAnalyzer::reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternar
p_ternary_op->set_datatype(result);
}
+void GDScriptAnalyzer::reduce_type_test(GDScriptParser::TypeTestNode *p_type_test) {
+ GDScriptParser::DataType result;
+ result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ result.kind = GDScriptParser::DataType::BUILTIN;
+ result.builtin_type = Variant::BOOL;
+ p_type_test->set_datatype(result);
+
+ if (!p_type_test->operand || !p_type_test->test_type) {
+ return;
+ }
+
+ reduce_expression(p_type_test->operand);
+ GDScriptParser::DataType operand_type = p_type_test->operand->get_datatype();
+ GDScriptParser::DataType test_type = type_from_metatype(resolve_datatype(p_type_test->test_type));
+ p_type_test->test_datatype = test_type;
+
+ if (!operand_type.is_set() || !test_type.is_set()) {
+ return;
+ }
+
+ if (p_type_test->operand->is_constant) {
+ p_type_test->is_constant = true;
+ p_type_test->reduced_value = false;
+
+ if (!is_type_compatible(test_type, operand_type)) {
+ push_error(vformat(R"(Expression is of type "%s" so it can't be of type "%s".)", operand_type.to_string(), test_type.to_string()), p_type_test->operand);
+ } else if (is_type_compatible(test_type, type_from_variant(p_type_test->operand->reduced_value, p_type_test->operand))) {
+ p_type_test->reduced_value = test_type.builtin_type != Variant::OBJECT || !p_type_test->operand->reduced_value.is_null();
+ }
+
+ return;
+ }
+
+ if (!is_type_compatible(test_type, operand_type) && !is_type_compatible(operand_type, test_type)) {
+ if (operand_type.is_hard_type()) {
+ push_error(vformat(R"(Expression is of type "%s" so it can't be of type "%s".)", operand_type.to_string(), test_type.to_string()), p_type_test->operand);
+ } else {
+ downgrade_node_type_source(p_type_test->operand);
+ }
+ }
+}
+
void GDScriptAnalyzer::reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op) {
reduce_expression(p_unary_op->operand);
@@ -4375,6 +4480,7 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_va
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_pseudo_type = false;
if (p_meta_type.kind == GDScriptParser::DataType::ENUM) {
result.builtin_type = Variant::INT;
} else {
@@ -4428,11 +4534,16 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo
result.set_container_element_type(elem_type);
} else if (p_property.type == Variant::INT) {
// Check if it's enum.
- if (p_property.class_name != StringName()) {
- Vector<String> names = String(p_property.class_name).split(".");
- if (names.size() == 2) {
- result = make_native_enum_type(names[1], names[0], false);
+ if ((p_property.usage & (PROPERTY_USAGE_CLASS_IS_ENUM | PROPERTY_USAGE_CLASS_IS_BITFIELD)) && p_property.class_name != StringName()) {
+ if (CoreConstants::is_global_enum(p_property.class_name)) {
+ result = make_global_enum_type(p_property.class_name, StringName(), false);
result.is_constant = false;
+ } else {
+ Vector<String> names = String(p_property.class_name).split(ENUM_SEPARATOR);
+ if (names.size() == 2) {
+ result = make_native_enum_type(names[1], names[0], false);
+ result.is_constant = false;
+ }
}
}
}
diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h
index cdeba374c7..7a50b32d4c 100644
--- a/modules/gdscript/gdscript_analyzer.h
+++ b/modules/gdscript/gdscript_analyzer.h
@@ -98,8 +98,9 @@ class GDScriptAnalyzer {
void reduce_literal(GDScriptParser::LiteralNode *p_literal);
void reduce_preload(GDScriptParser::PreloadNode *p_preload);
void reduce_self(GDScriptParser::SelfNode *p_self);
- void reduce_subscript(GDScriptParser::SubscriptNode *p_subscript);
+ void reduce_subscript(GDScriptParser::SubscriptNode *p_subscript, bool p_can_be_pseudo_type = false);
void reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternary_op, bool p_is_root = false);
+ void reduce_type_test(GDScriptParser::TypeTestNode *p_type_test);
void reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op);
Variant make_expression_reduced_value(GDScriptParser::ExpressionNode *p_expression, bool &is_reduced);
diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp
index 45008b0e87..a13bf8009f 100644
--- a/modules/gdscript/gdscript_byte_codegen.cpp
+++ b/modules/gdscript/gdscript_byte_codegen.cpp
@@ -612,18 +612,44 @@ void GDScriptByteCodeGenerator::write_binary_operator(const Address &p_target, V
append(p_operator);
}
-void GDScriptByteCodeGenerator::write_type_test(const Address &p_target, const Address &p_source, const Address &p_type) {
- append_opcode(GDScriptFunction::OPCODE_EXTENDS_TEST);
- append(p_source);
- append(p_type);
- append(p_target);
-}
-
-void GDScriptByteCodeGenerator::write_type_test_builtin(const Address &p_target, const Address &p_source, Variant::Type p_type) {
- append_opcode(GDScriptFunction::OPCODE_IS_BUILTIN);
- append(p_source);
- append(p_target);
- append(p_type);
+void GDScriptByteCodeGenerator::write_type_test(const Address &p_target, const Address &p_source, const GDScriptDataType &p_type) {
+ switch (p_type.kind) {
+ case GDScriptDataType::BUILTIN: {
+ if (p_type.builtin_type == Variant::ARRAY && p_type.has_container_element_type()) {
+ const GDScriptDataType &element_type = p_type.get_container_element_type();
+ append_opcode(GDScriptFunction::OPCODE_TYPE_TEST_ARRAY);
+ append(p_target);
+ append(p_source);
+ append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
+ append(element_type.builtin_type);
+ append(element_type.native_type);
+ } else {
+ append_opcode(GDScriptFunction::OPCODE_TYPE_TEST_BUILTIN);
+ append(p_target);
+ append(p_source);
+ append(p_type.builtin_type);
+ }
+ } break;
+ case GDScriptDataType::NATIVE: {
+ append_opcode(GDScriptFunction::OPCODE_TYPE_TEST_NATIVE);
+ append(p_target);
+ append(p_source);
+ append(p_type.native_type);
+ } break;
+ case GDScriptDataType::SCRIPT:
+ case GDScriptDataType::GDSCRIPT: {
+ const Variant &script = p_type.script_type;
+ append_opcode(GDScriptFunction::OPCODE_TYPE_TEST_SCRIPT);
+ append(p_target);
+ append(p_source);
+ append(get_constant_pos(script) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
+ } break;
+ default: {
+ ERR_PRINT("Compiler bug: unresolved type in type test.");
+ append_opcode(GDScriptFunction::OPCODE_ASSIGN_FALSE);
+ append(p_target);
+ }
+ }
}
void GDScriptByteCodeGenerator::write_and_left_operand(const Address &p_left_operand) {
diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h
index 1d1b22e196..dc05de9fc6 100644
--- a/modules/gdscript/gdscript_byte_codegen.h
+++ b/modules/gdscript/gdscript_byte_codegen.h
@@ -481,8 +481,7 @@ public:
virtual void write_type_adjust(const Address &p_target, Variant::Type p_new_type) override;
virtual void write_unary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand) override;
virtual void write_binary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand, const Address &p_right_operand) override;
- virtual void write_type_test(const Address &p_target, const Address &p_source, const Address &p_type) override;
- virtual void write_type_test_builtin(const Address &p_target, const Address &p_source, Variant::Type p_type) override;
+ virtual void write_type_test(const Address &p_target, const Address &p_source, const GDScriptDataType &p_type) override;
virtual void write_and_left_operand(const Address &p_left_operand) override;
virtual void write_and_right_operand(const Address &p_right_operand) override;
virtual void write_end_and(const Address &p_target) override;
diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h
index 6d42d152b9..7847ab28c7 100644
--- a/modules/gdscript/gdscript_codegen.h
+++ b/modules/gdscript/gdscript_codegen.h
@@ -90,8 +90,7 @@ public:
virtual void write_type_adjust(const Address &p_target, Variant::Type p_new_type) = 0;
virtual void write_unary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand) = 0;
virtual void write_binary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand, const Address &p_right_operand) = 0;
- virtual void write_type_test(const Address &p_target, const Address &p_source, const Address &p_type) = 0;
- virtual void write_type_test_builtin(const Address &p_target, const Address &p_source, Variant::Type p_type) = 0;
+ virtual void write_type_test(const Address &p_target, const Address &p_source, const GDScriptDataType &p_type) = 0;
virtual void write_and_left_operand(const Address &p_left_operand) = 0;
virtual void write_and_right_operand(const Address &p_right_operand) = 0;
virtual void write_end_and(const Address &p_target) = 0;
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index b34be11169..efa75528fc 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -148,13 +148,8 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D
}
} break;
case GDScriptParser::DataType::ENUM:
- result.has_type = true;
result.kind = GDScriptDataType::BUILTIN;
- if (p_datatype.is_meta_type) {
- result.builtin_type = Variant::DICTIONARY;
- } else {
- result.builtin_type = Variant::INT;
- }
+ result.builtin_type = p_datatype.builtin_type;
break;
case GDScriptParser::DataType::RESOLVING:
case GDScriptParser::DataType::UNRESOLVED: {
@@ -494,17 +489,10 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
} break;
case GDScriptParser::Node::CAST: {
const GDScriptParser::CastNode *cn = static_cast<const GDScriptParser::CastNode *>(p_expression);
- GDScriptParser::DataType og_cast_type = cn->get_datatype();
- GDScriptDataType cast_type = _gdtype_from_datatype(og_cast_type, codegen.script);
+ GDScriptDataType cast_type = _gdtype_from_datatype(cn->get_datatype(), codegen.script);
GDScriptCodeGenerator::Address result;
if (cast_type.has_type) {
- if (og_cast_type.kind == GDScriptParser::DataType::ENUM) {
- // Enum types are usually treated as dictionaries, but in this case we want to cast to an integer.
- cast_type.kind = GDScriptDataType::BUILTIN;
- cast_type.builtin_type = Variant::INT;
- }
-
// Create temporary for result first since it will be deleted last.
result = codegen.add_temporary(cast_type);
@@ -817,28 +805,6 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
gen->pop_temporary();
}
} break;
- case GDScriptParser::BinaryOpNode::OP_TYPE_TEST: {
- GDScriptCodeGenerator::Address operand = _parse_expression(codegen, r_error, binary->left_operand);
-
- if (binary->right_operand->type == GDScriptParser::Node::IDENTIFIER && GDScriptParser::get_builtin_type(static_cast<const GDScriptParser::IdentifierNode *>(binary->right_operand)->name) != Variant::VARIANT_MAX) {
- // `is` with builtin type)
- Variant::Type type = GDScriptParser::get_builtin_type(static_cast<const GDScriptParser::IdentifierNode *>(binary->right_operand)->name);
- gen->write_type_test_builtin(result, operand, type);
- } else {
- GDScriptCodeGenerator::Address type = _parse_expression(codegen, r_error, binary->right_operand);
- if (r_error) {
- return GDScriptCodeGenerator::Address();
- }
- gen->write_type_test(result, operand, type);
- if (type.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
- gen->pop_temporary();
- }
- }
-
- if (operand.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
- gen->pop_temporary();
- }
- } break;
default: {
GDScriptCodeGenerator::Address left_operand = _parse_expression(codegen, r_error, binary->left_operand);
GDScriptCodeGenerator::Address right_operand = _parse_expression(codegen, r_error, binary->right_operand);
@@ -894,6 +860,28 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
return result;
} break;
+ case GDScriptParser::Node::TYPE_TEST: {
+ const GDScriptParser::TypeTestNode *type_test = static_cast<const GDScriptParser::TypeTestNode *>(p_expression);
+ GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(type_test->get_datatype(), codegen.script));
+
+ GDScriptCodeGenerator::Address operand = _parse_expression(codegen, r_error, type_test->operand);
+ GDScriptDataType test_type = _gdtype_from_datatype(type_test->test_datatype, codegen.script);
+ if (r_error) {
+ return GDScriptCodeGenerator::Address();
+ }
+
+ if (test_type.has_type) {
+ gen->write_type_test(result, operand, test_type);
+ } else {
+ gen->write_assign_true(result);
+ }
+
+ if (operand.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ gen->pop_temporary();
+ }
+
+ return result;
+ } break;
case GDScriptParser::Node::ASSIGNMENT: {
const GDScriptParser::AssignmentNode *assignment = static_cast<const GDScriptParser::AssignmentNode *>(p_expression);
diff --git a/modules/gdscript/gdscript_disassembler.cpp b/modules/gdscript/gdscript_disassembler.cpp
index d4f4358ac1..0acc03be3d 100644
--- a/modules/gdscript/gdscript_disassembler.cpp
+++ b/modules/gdscript/gdscript_disassembler.cpp
@@ -135,23 +135,56 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
incr += 5;
} break;
- case OPCODE_EXTENDS_TEST: {
- text += "is object ";
- text += DADDR(3);
- text += " = ";
+ case OPCODE_TYPE_TEST_BUILTIN: {
+ text += "type test ";
text += DADDR(1);
- text += " is ";
+ text += " = ";
text += DADDR(2);
+ text += " is ";
+ text += Variant::get_type_name(Variant::Type(_code_ptr[ip + 3]));
incr += 4;
} break;
- case OPCODE_IS_BUILTIN: {
- text += "is builtin ";
+ case OPCODE_TYPE_TEST_ARRAY: {
+ text += "type test ";
+ text += DADDR(1);
+ text += " = ";
text += DADDR(2);
+ text += " is Array[";
+
+ Ref<Script> script_type = get_constant(_code_ptr[ip + 3] & GDScriptFunction::ADDR_MASK);
+ Variant::Type builtin_type = (Variant::Type)_code_ptr[ip + 4];
+ StringName native_type = get_global_name(_code_ptr[ip + 5]);
+
+ if (script_type.is_valid() && script_type->is_valid()) {
+ text += script_type->get_path();
+ } else if (native_type != StringName()) {
+ text += native_type;
+ } else {
+ text += Variant::get_type_name(builtin_type);
+ }
+
+ text += "]";
+
+ incr += 6;
+ } break;
+ case OPCODE_TYPE_TEST_NATIVE: {
+ text += "type test ";
+ text += DADDR(1);
text += " = ";
+ text += DADDR(2);
+ text += " is ";
+ text += get_global_name(_code_ptr[ip + 3]);
+
+ incr += 4;
+ } break;
+ case OPCODE_TYPE_TEST_SCRIPT: {
+ text += "type test ";
text += DADDR(1);
+ text += " = ";
+ text += DADDR(2);
text += " is ";
- text += Variant::get_type_name(Variant::Type(_code_ptr[ip + 3]));
+ text += DADDR(3);
incr += 4;
} break;
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index 8cfd48b52b..f79ba40e71 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -1918,21 +1918,19 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context,
}
}
- if (suite->parent_if && suite->parent_if->condition && suite->parent_if->condition->type == GDScriptParser::Node::BINARY_OPERATOR && static_cast<const GDScriptParser::BinaryOpNode *>(suite->parent_if->condition)->operation == GDScriptParser::BinaryOpNode::OP_TYPE_TEST) {
+ if (suite->parent_if && suite->parent_if->condition && suite->parent_if->condition->type == GDScriptParser::Node::TYPE_TEST) {
// Operator `is` used, check if identifier is in there! this helps resolve in blocks that are (if (identifier is value)): which are very common..
// Super dirty hack, but very useful.
// Credit: Zylann.
// TODO: this could be hacked to detect ANDed conditions too...
- const GDScriptParser::BinaryOpNode *op = static_cast<const GDScriptParser::BinaryOpNode *>(suite->parent_if->condition);
- if (op->left_operand && op->right_operand && op->left_operand->type == GDScriptParser::Node::IDENTIFIER && static_cast<const GDScriptParser::IdentifierNode *>(op->left_operand)->name == p_identifier) {
+ const GDScriptParser::TypeTestNode *type_test = static_cast<const GDScriptParser::TypeTestNode *>(suite->parent_if->condition);
+ if (type_test->operand && type_test->test_type && type_test->operand->type == GDScriptParser::Node::IDENTIFIER && static_cast<const GDScriptParser::IdentifierNode *>(type_test->operand)->name == p_identifier) {
// Bingo.
GDScriptParser::CompletionContext c = p_context;
- c.current_line = op->left_operand->start_line;
+ c.current_line = type_test->operand->start_line;
c.current_suite = suite;
- GDScriptCompletionIdentifier is_type;
- if (_guess_expression_type(c, op->right_operand, is_type)) {
- id_type = is_type.type;
- id_type.is_meta_type = false;
+ if ((!id_type.is_set() || id_type.is_variant()) && type_test->test_datatype.is_hard_type()) {
+ id_type = type_test->test_datatype;
if (last_assign_line < c.current_line) {
// Override last assignment.
last_assign_line = c.current_line;
diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h
index 2624fb8dd9..1a5e9eef53 100644
--- a/modules/gdscript/gdscript_function.h
+++ b/modules/gdscript/gdscript_function.h
@@ -105,9 +105,10 @@ public:
return false;
}
- Object *obj = p_variant.get_validated_object();
+ bool was_freed = false;
+ Object *obj = p_variant.get_validated_object_with_check(was_freed);
if (!obj) {
- return false;
+ return !was_freed;
}
if (!ClassDB::is_parent_class(obj->get_class_name(), native_type)) {
@@ -124,9 +125,10 @@ public:
return false;
}
- Object *obj = p_variant.get_validated_object();
+ bool was_freed = false;
+ Object *obj = p_variant.get_validated_object_with_check(was_freed);
if (!obj) {
- return false;
+ return !was_freed;
}
Ref<Script> base = obj && obj->get_script_instance() ? obj->get_script_instance()->get_script() : nullptr;
@@ -219,8 +221,10 @@ public:
enum Opcode {
OPCODE_OPERATOR,
OPCODE_OPERATOR_VALIDATED,
- OPCODE_EXTENDS_TEST,
- OPCODE_IS_BUILTIN,
+ OPCODE_TYPE_TEST_BUILTIN,
+ OPCODE_TYPE_TEST_ARRAY,
+ OPCODE_TYPE_TEST_NATIVE,
+ OPCODE_TYPE_TEST_SCRIPT,
OPCODE_SET_KEYED,
OPCODE_SET_KEYED_VALIDATED,
OPCODE_SET_INDEXED_VALIDATED,
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index b5cb5a4680..b32313dad4 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -2463,9 +2463,6 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_binary_operator(Expression
operation->operation = BinaryOpNode::OP_LOGIC_OR;
operation->variant_op = Variant::OP_OR;
break;
- case GDScriptTokenizer::Token::IS:
- operation->operation = BinaryOpNode::OP_TYPE_TEST;
- break;
case GDScriptTokenizer::Token::IN:
operation->operation = BinaryOpNode::OP_CONTENT_TEST;
operation->variant_op = Variant::OP_IN;
@@ -3161,6 +3158,22 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_lambda(ExpressionNode *p_p
return lambda;
}
+GDScriptParser::ExpressionNode *GDScriptParser::parse_type_test(ExpressionNode *p_previous_operand, bool p_can_assign) {
+ TypeTestNode *type_test = alloc_node<TypeTestNode>();
+ reset_extents(type_test, p_previous_operand);
+ update_extents(type_test);
+
+ type_test->operand = p_previous_operand;
+ type_test->test_type = parse_type();
+ complete_extents(type_test);
+
+ if (type_test->test_type == nullptr) {
+ push_error(R"(Expected type specifier after "is".)");
+ }
+
+ return type_test;
+}
+
GDScriptParser::ExpressionNode *GDScriptParser::parse_yield(ExpressionNode *p_previous_operand, bool p_can_assign) {
push_error(R"("yield" was removed in Godot 4.0. Use "await" instead.)");
return nullptr;
@@ -3529,7 +3542,7 @@ GDScriptParser::ParseRule *GDScriptParser::get_rule(GDScriptTokenizer::Token::Ty
{ nullptr, nullptr, PREC_NONE }, // EXTENDS,
{ &GDScriptParser::parse_lambda, nullptr, PREC_NONE }, // FUNC,
{ nullptr, &GDScriptParser::parse_binary_operator, PREC_CONTENT_TEST }, // IN,
- { nullptr, &GDScriptParser::parse_binary_operator, PREC_TYPE_TEST }, // IS,
+ { nullptr, &GDScriptParser::parse_type_test, PREC_TYPE_TEST }, // IS,
{ nullptr, nullptr, PREC_NONE }, // NAMESPACE,
{ &GDScriptParser::parse_preload, nullptr, PREC_NONE }, // PRELOAD,
{ &GDScriptParser::parse_self, nullptr, PREC_NONE }, // SELF,
@@ -4379,9 +4392,6 @@ void GDScriptParser::TreePrinter::print_binary_op(BinaryOpNode *p_binary_op) {
case BinaryOpNode::OP_LOGIC_OR:
push_text(" OR ");
break;
- case BinaryOpNode::OP_TYPE_TEST:
- push_text(" IS ");
- break;
case BinaryOpNode::OP_CONTENT_TEST:
push_text(" IN ");
break;
@@ -4584,6 +4594,9 @@ void GDScriptParser::TreePrinter::print_expression(ExpressionNode *p_expression)
case Node::TERNARY_OPERATOR:
print_ternary_op(static_cast<TernaryOpNode *>(p_expression));
break;
+ case Node::TYPE_TEST:
+ print_type_test(static_cast<TypeTestNode *>(p_expression));
+ break;
case Node::UNARY_OPERATOR:
print_unary_op(static_cast<UnaryOpNode *>(p_expression));
break;
@@ -4943,6 +4956,12 @@ void GDScriptParser::TreePrinter::print_type(TypeNode *p_type) {
}
}
+void GDScriptParser::TreePrinter::print_type_test(TypeTestNode *p_test) {
+ print_expression(p_test->operand);
+ push_text(" IS ");
+ print_type(p_test->test_type);
+}
+
void GDScriptParser::TreePrinter::print_unary_op(UnaryOpNode *p_unary_op) {
// Surround in parenthesis for disambiguation.
push_text("(");
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index 0ba0d5b6da..346c9bc45d 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -91,6 +91,7 @@ public:
struct SuiteNode;
struct TernaryOpNode;
struct TypeNode;
+ struct TypeTestNode;
struct UnaryOpNode;
struct VariableNode;
struct WhileNode;
@@ -124,6 +125,7 @@ public:
bool is_constant = false;
bool is_read_only = false;
bool is_meta_type = false;
+ bool is_pseudo_type = false; // For global names that can't be used standalone.
bool is_coroutine = false; // For function calls.
Variant::Type builtin_type = Variant::NIL;
@@ -210,6 +212,7 @@ public:
is_read_only = p_other.is_read_only;
is_constant = p_other.is_constant;
is_meta_type = p_other.is_meta_type;
+ is_pseudo_type = p_other.is_pseudo_type;
is_coroutine = p_other.is_coroutine;
builtin_type = p_other.builtin_type;
native_type = p_other.native_type;
@@ -288,6 +291,7 @@ public:
SUITE,
TERNARY_OPERATOR,
TYPE,
+ TYPE_TEST,
UNARY_OPERATOR,
VARIABLE,
WHILE,
@@ -426,7 +430,6 @@ public:
OP_BIT_XOR,
OP_LOGIC_AND,
OP_LOGIC_OR,
- OP_TYPE_TEST,
OP_CONTENT_TEST,
OP_COMP_EQUAL,
OP_COMP_NOT_EQUAL,
@@ -1150,6 +1153,16 @@ public:
}
};
+ struct TypeTestNode : public ExpressionNode {
+ ExpressionNode *operand = nullptr;
+ TypeNode *test_type = nullptr;
+ DataType test_datatype;
+
+ TypeTestNode() {
+ type = TYPE_TEST;
+ }
+ };
+
struct UnaryOpNode : public ExpressionNode {
enum OpType {
OP_POSITIVE,
@@ -1460,6 +1473,7 @@ private:
ExpressionNode *parse_attribute(ExpressionNode *p_previous_operand, bool p_can_assign);
ExpressionNode *parse_subscript(ExpressionNode *p_previous_operand, bool p_can_assign);
ExpressionNode *parse_lambda(ExpressionNode *p_previous_operand, bool p_can_assign);
+ ExpressionNode *parse_type_test(ExpressionNode *p_previous_operand, bool p_can_assign);
ExpressionNode *parse_yield(ExpressionNode *p_previous_operand, bool p_can_assign);
ExpressionNode *parse_invalid_token(ExpressionNode *p_previous_operand, bool p_can_assign);
TypeNode *parse_type(bool p_allow_void = false);
@@ -1541,8 +1555,9 @@ public:
void print_statement(Node *p_statement);
void print_subscript(SubscriptNode *p_subscript);
void print_suite(SuiteNode *p_suite);
- void print_type(TypeNode *p_type);
void print_ternary_op(TernaryOpNode *p_ternary_op);
+ void print_type(TypeNode *p_type);
+ void print_type_test(TypeTestNode *p_type_test);
void print_unary_op(UnaryOpNode *p_unary_op);
void print_variable(VariableNode *p_variable);
void print_while(WhileNode *p_while);
diff --git a/modules/gdscript/gdscript_utility_functions.cpp b/modules/gdscript/gdscript_utility_functions.cpp
index 758b61bb31..8862450121 100644
--- a/modules/gdscript/gdscript_utility_functions.cpp
+++ b/modules/gdscript/gdscript_utility_functions.cpp
@@ -523,6 +523,82 @@ struct GDScriptUtilityFunctionsDefinitions {
}
}
}
+
+ static inline void is_instance_of(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
+ VALIDATE_ARG_COUNT(2);
+
+ if (p_args[1]->get_type() == Variant::INT) {
+ int builtin_type = *p_args[1];
+ if (builtin_type < 0 || builtin_type >= Variant::VARIANT_MAX) {
+ *r_ret = RTR("Invalid type argument for is_instance_of(), use TYPE_* constants for built-in types.");
+ r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
+ r_error.argument = 1;
+ r_error.expected = Variant::NIL;
+ return;
+ }
+ *r_ret = p_args[0]->get_type() == builtin_type;
+ return;
+ }
+
+ bool was_type_freed = false;
+ Object *type_object = p_args[1]->get_validated_object_with_check(was_type_freed);
+ if (was_type_freed) {
+ *r_ret = RTR("Type argument is a previously freed instance.");
+ r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
+ r_error.argument = 1;
+ r_error.expected = Variant::NIL;
+ return;
+ }
+ if (!type_object) {
+ *r_ret = RTR("Invalid type argument for is_instance_of(), should be a TYPE_* constant, a class or a script.");
+ r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
+ r_error.argument = 1;
+ r_error.expected = Variant::NIL;
+ return;
+ }
+
+ bool was_value_freed = false;
+ Object *value_object = p_args[0]->get_validated_object_with_check(was_value_freed);
+ if (was_value_freed) {
+ *r_ret = RTR("Value argument is a previously freed instance.");
+ r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
+ r_error.argument = 0;
+ r_error.expected = Variant::NIL;
+ return;
+ }
+ if (!value_object) {
+ *r_ret = false;
+ return;
+ }
+
+ GDScriptNativeClass *native_type = Object::cast_to<GDScriptNativeClass>(type_object);
+ if (native_type) {
+ *r_ret = ClassDB::is_parent_class(value_object->get_class_name(), native_type->get_name());
+ return;
+ }
+
+ Script *script_type = Object::cast_to<Script>(type_object);
+ if (script_type) {
+ bool result = false;
+ if (value_object->get_script_instance()) {
+ Script *script_ptr = value_object->get_script_instance()->get_script().ptr();
+ while (script_ptr) {
+ if (script_ptr == script_type) {
+ result = true;
+ break;
+ }
+ script_ptr = script_ptr->get_base_script().ptr();
+ }
+ }
+ *r_ret = result;
+ return;
+ }
+
+ *r_ret = RTR("Invalid type argument for is_instance_of(), should be a TYPE_* constant, a class or a script.");
+ r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
+ r_error.argument = 1;
+ r_error.expected = Variant::NIL;
+ }
};
struct GDScriptUtilityFunctionInfo {
@@ -638,6 +714,7 @@ void GDScriptUtilityFunctions::register_functions() {
REGISTER_FUNC_NO_ARGS(print_stack, false, Variant::NIL);
REGISTER_FUNC_NO_ARGS(get_stack, false, Variant::ARRAY);
REGISTER_FUNC(len, true, Variant::INT, VARARG("var"));
+ REGISTER_FUNC(is_instance_of, true, Variant::BOOL, VARARG("value"), VARARG("type"));
}
void GDScriptUtilityFunctions::unregister_functions() {
diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp
index 9f7d27d841..ba400b8e15 100644
--- a/modules/gdscript/gdscript_vm.cpp
+++ b/modules/gdscript/gdscript_vm.cpp
@@ -201,8 +201,10 @@ void (*type_init_function_table[])(Variant *) = {
static const void *switch_table_ops[] = { \
&&OPCODE_OPERATOR, \
&&OPCODE_OPERATOR_VALIDATED, \
- &&OPCODE_EXTENDS_TEST, \
- &&OPCODE_IS_BUILTIN, \
+ &&OPCODE_TYPE_TEST_BUILTIN, \
+ &&OPCODE_TYPE_TEST_ARRAY, \
+ &&OPCODE_TYPE_TEST_NATIVE, \
+ &&OPCODE_TYPE_TEST_SCRIPT, \
&&OPCODE_SET_KEYED, \
&&OPCODE_SET_KEYED_VALIDATED, \
&&OPCODE_SET_INDEXED_VALIDATED, \
@@ -743,91 +745,95 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
}
DISPATCH_OPCODE;
- OPCODE(OPCODE_EXTENDS_TEST) {
+ OPCODE(OPCODE_TYPE_TEST_BUILTIN) {
CHECK_SPACE(4);
- GET_VARIANT_PTR(a, 0);
- GET_VARIANT_PTR(b, 1);
- GET_VARIANT_PTR(dst, 2);
-
-#ifdef DEBUG_ENABLED
- if (b->get_type() != Variant::OBJECT || b->operator Object *() == nullptr) {
- err_text = "Right operand of 'is' is not a class.";
- OPCODE_BREAK;
- }
-#endif
+ GET_VARIANT_PTR(dst, 0);
+ GET_VARIANT_PTR(value, 1);
- bool extends_ok = false;
- if (a->get_type() == Variant::OBJECT && a->operator Object *() != nullptr) {
-#ifdef DEBUG_ENABLED
- bool was_freed;
- Object *obj_A = a->get_validated_object_with_check(was_freed);
+ Variant::Type builtin_type = (Variant::Type)_code_ptr[ip + 3];
+ GD_ERR_BREAK(builtin_type < 0 || builtin_type >= Variant::VARIANT_MAX);
- if (was_freed) {
- err_text = "Left operand of 'is' is a previously freed instance.";
- OPCODE_BREAK;
- }
+ *dst = value->get_type() == builtin_type;
+ ip += 4;
+ }
+ DISPATCH_OPCODE;
- Object *obj_B = b->get_validated_object_with_check(was_freed);
+ OPCODE(OPCODE_TYPE_TEST_ARRAY) {
+ CHECK_SPACE(6);
- if (was_freed) {
- err_text = "Right operand of 'is' is a previously freed instance.";
- OPCODE_BREAK;
- }
-#else
+ GET_VARIANT_PTR(dst, 0);
+ GET_VARIANT_PTR(value, 1);
- Object *obj_A = *a;
- Object *obj_B = *b;
-#endif // DEBUG_ENABLED
+ GET_VARIANT_PTR(script_type, 2);
+ Variant::Type builtin_type = (Variant::Type)_code_ptr[ip + 4];
+ int native_type_idx = _code_ptr[ip + 5];
+ GD_ERR_BREAK(native_type_idx < 0 || native_type_idx >= _global_names_count);
+ const StringName native_type = _global_names_ptr[native_type_idx];
- GDScript *scr_B = Object::cast_to<GDScript>(obj_B);
+ bool result = false;
+ if (value->get_type() == Variant::ARRAY) {
+ Array *array = VariantInternal::get_array(value);
+ result = array->get_typed_builtin() == ((uint32_t)builtin_type) && array->get_typed_class_name() == native_type && array->get_typed_script() == *script_type && array->get_typed_class_name() == native_type;
+ }
- if (scr_B) {
- //if B is a script, the only valid condition is that A has an instance which inherits from the script
- //in other situation, this should return false.
+ *dst = result;
+ ip += 6;
+ }
+ DISPATCH_OPCODE;
- if (obj_A->get_script_instance() && obj_A->get_script_instance()->get_language() == GDScriptLanguage::get_singleton()) {
- GDScript *cmp = static_cast<GDScript *>(obj_A->get_script_instance()->get_script().ptr());
- //bool found=false;
- while (cmp) {
- if (cmp == scr_B) {
- //inherits from script, all ok
- extends_ok = true;
- break;
- }
+ OPCODE(OPCODE_TYPE_TEST_NATIVE) {
+ CHECK_SPACE(4);
- cmp = cmp->_base;
- }
- }
+ GET_VARIANT_PTR(dst, 0);
+ GET_VARIANT_PTR(value, 1);
- } else {
- GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(obj_B);
+ int native_type_idx = _code_ptr[ip + 3];
+ GD_ERR_BREAK(native_type_idx < 0 || native_type_idx >= _global_names_count);
+ const StringName native_type = _global_names_ptr[native_type_idx];
-#ifdef DEBUG_ENABLED
- if (!nc) {
- err_text = "Right operand of 'is' is not a class (type: '" + obj_B->get_class() + "').";
- OPCODE_BREAK;
- }
-#endif
- extends_ok = ClassDB::is_parent_class(obj_A->get_class_name(), nc->get_name());
- }
+ bool was_freed = false;
+ Object *object = value->get_validated_object_with_check(was_freed);
+ if (was_freed) {
+ err_text = "Left operand of 'is' is a previously freed instance.";
+ OPCODE_BREAK;
}
- *dst = extends_ok;
+ *dst = object && ClassDB::is_parent_class(object->get_class_name(), native_type);
ip += 4;
}
DISPATCH_OPCODE;
- OPCODE(OPCODE_IS_BUILTIN) {
+ OPCODE(OPCODE_TYPE_TEST_SCRIPT) {
CHECK_SPACE(4);
- GET_VARIANT_PTR(value, 0);
- GET_VARIANT_PTR(dst, 1);
- Variant::Type var_type = (Variant::Type)_code_ptr[ip + 3];
+ GET_VARIANT_PTR(dst, 0);
+ GET_VARIANT_PTR(value, 1);
- GD_ERR_BREAK(var_type < 0 || var_type >= Variant::VARIANT_MAX);
+ GET_VARIANT_PTR(type, 2);
+ Script *script_type = Object::cast_to<Script>(type->operator Object *());
+ GD_ERR_BREAK(!script_type);
+
+ bool was_freed = false;
+ Object *object = value->get_validated_object_with_check(was_freed);
+ if (was_freed) {
+ err_text = "Left operand of 'is' is a previously freed instance.";
+ OPCODE_BREAK;
+ }
+
+ bool result = false;
+ if (object && object->get_script_instance()) {
+ Script *script_ptr = object->get_script_instance()->get_script().ptr();
+ while (script_ptr) {
+ if (script_ptr == script_type) {
+ result = true;
+ break;
+ }
+ script_ptr = script_ptr->get_base_script().ptr();
+ }
+ }
- *dst = value->get_type() == var_type;
+ *dst = result;
ip += 4;
}
DISPATCH_OPCODE;
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_bad_value.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_bad_value.out
index ddbdc17a42..1b6e11f6f2 100644
--- a/modules/gdscript/tests/scripts/analyzer/errors/enum_bad_value.out
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_bad_value.out
@@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
-Cannot find member "V3" in base "enum_bad_value.gd::Enum".
+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 84958f1aa2..d401675bcf 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 "enum_class_var_assign_with_wrong_enum_type.gd::MyOtherEnum" as "enum_class_var_assign_with_wrong_enum_type.gd::MyEnum".
+Cannot assign a value of type "enum_class_var_assign_with_wrong_enum_type.gd.MyOtherEnum" as "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 e294f3496a..4b6b42b024 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 "enum_class_var_init_with_wrong_enum_type.gd::MyOtherEnum" as "enum_class_var_init_with_wrong_enum_type.gd::MyEnum".
+Cannot assign a value of type "enum_class_var_init_with_wrong_enum_type.gd.MyOtherEnum" as "enum_class_var_init_with_wrong_enum_type.gd.MyEnum".
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
index a91189e2dd..adde630a0b 100644
--- 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
@@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
-Cannot pass a value of type "enum_function_parameter_wrong_type.gd::MyOtherEnum" as "enum_function_parameter_wrong_type.gd::MyEnum".
+Cannot pass a value of type "enum_function_parameter_wrong_type.gd.MyOtherEnum" as "enum_function_parameter_wrong_type.gd.MyEnum".
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
index 6b4eba3740..9ee3fb7c06 100644
--- 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
@@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
-Cannot return a value of type "enum_function_return_wrong_type.gd::MyOtherEnum" as "enum_function_return_wrong_type.gd::MyEnum".
+Cannot return a value of type "enum_function_return_wrong_type.gd.MyOtherEnum" as "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.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_outer_with_wrong_enum_type.out
index 616358bb61..8de7bde50d 100644
--- 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
@@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
-Cannot assign a value of type "enum_local_var_assign_outer_with_wrong_enum_type.gd::InnerClass::MyEnum" as "enum_local_var_assign_outer_with_wrong_enum_type.gd::MyEnum".
+Cannot assign a value of type "enum_local_var_assign_outer_with_wrong_enum_type.gd::InnerClass.MyEnum" as "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 af3dde663f..e2139e75f6 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 "enum_local_var_assign_with_wrong_enum_type.gd::MyOtherEnum" as "enum_local_var_assign_with_wrong_enum_type.gd::MyEnum".
+Cannot assign a value of type "enum_local_var_assign_with_wrong_enum_type.gd.MyOtherEnum" as "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 781b0bc85f..46c0553c28 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 "enum_local_var_init_with_wrong_enum_type.gd::MyOtherEnum" as "enum_local_var_init_with_wrong_enum_type.gd::MyEnum".
+Cannot assign a value of type "enum_local_var_init_with_wrong_enum_type.gd.MyOtherEnum" as "enum_local_var_init_with_wrong_enum_type.gd.MyEnum".
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
index 49f041a2dd..4b1db77a35 100644
--- a/modules/gdscript/tests/scripts/analyzer/errors/enum_native_bad_value.out
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_native_bad_value.out
@@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
-Cannot find member "THIS_DOES_NOT_EXIST" in base "TileSet::TileShape".
+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.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_preload_unnamed_assign_to_named.out
index e8c7f86c4f..ddd26e7399 100644
--- 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
@@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
-Cannot assign a value of type "enum_value_from_parent.gd::<anonymous enum>" as "enum_preload_unnamed_assign_to_named.gd::MyEnum".
+Cannot assign a value of type "enum_value_from_parent.gd.<anonymous enum>" as "enum_preload_unnamed_assign_to_named.gd.MyEnum".
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
index fb18c94d0b..3a65978aa3 100644
--- 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
@@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
-Cannot assign a value of type "enum_unnamed_assign_to_named.gd::<anonymous enum>" as "enum_unnamed_assign_to_named.gd::MyEnum".
+Cannot assign a value of type "enum_unnamed_assign_to_named.gd.<anonymous enum>" as "enum_unnamed_assign_to_named.gd.MyEnum".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/preload_enum_error.out b/modules/gdscript/tests/scripts/analyzer/errors/preload_enum_error.out
index 08a973503f..21143f2ade 100644
--- a/modules/gdscript/tests/scripts/analyzer/errors/preload_enum_error.out
+++ b/modules/gdscript/tests/scripts/analyzer/errors/preload_enum_error.out
@@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
-Cannot assign a value of type "enum_from_outer.gd::Named" as "preload_enum_error.gd::LocalNamed".
+Cannot assign a value of type "enum_from_outer.gd.Named" as "preload_enum_error.gd.LocalNamed".
diff --git a/modules/gdscript/tests/scripts/analyzer/features/global_enums.gd b/modules/gdscript/tests/scripts/analyzer/features/global_enums.gd
new file mode 100644
index 0000000000..67d48817e8
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/global_enums.gd
@@ -0,0 +1,30 @@
+func test():
+ var type: Variant.Type
+ type = Variant.Type.TYPE_INT
+ print(type)
+ type = TYPE_FLOAT
+ print(type)
+
+ var direction: ClockDirection
+ direction = ClockDirection.CLOCKWISE
+ print(direction)
+ direction = COUNTERCLOCKWISE
+ print(direction)
+
+ var duper := Duper.new()
+ duper.set_type(Variant.Type.TYPE_INT)
+ duper.set_type(TYPE_FLOAT)
+ duper.set_direction(ClockDirection.CLOCKWISE)
+ duper.set_direction(COUNTERCLOCKWISE)
+
+class Super:
+ func set_type(type: Variant.Type) -> void:
+ print(type)
+ func set_direction(dir: ClockDirection) -> void:
+ print(dir)
+
+class Duper extends Super:
+ func set_type(type: Variant.Type) -> void:
+ print(type)
+ func set_direction(dir: ClockDirection) -> void:
+ print(dir)
diff --git a/modules/gdscript/tests/scripts/analyzer/features/global_enums.out b/modules/gdscript/tests/scripts/analyzer/features/global_enums.out
new file mode 100644
index 0000000000..b3b5bf57c1
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/global_enums.out
@@ -0,0 +1,9 @@
+GDTEST_OK
+2
+3
+0
+1
+2
+3
+0
+1
diff --git a/modules/gdscript/tests/scripts/analyzer/features/type_test_usage.gd b/modules/gdscript/tests/scripts/analyzer/features/type_test_usage.gd
new file mode 100644
index 0000000000..12dc0b93df
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/type_test_usage.gd
@@ -0,0 +1,127 @@
+class A extends RefCounted:
+ pass
+
+class B extends A:
+ pass
+
+@warning_ignore("assert_always_true")
+func test():
+ var builtin: Variant = 3
+ assert((builtin is Variant) == true)
+ assert((builtin is int) == true)
+ assert(is_instance_of(builtin, TYPE_INT) == true)
+ assert((builtin is float) == false)
+ assert(is_instance_of(builtin, TYPE_FLOAT) == false)
+
+ const const_builtin: Variant = 3
+ assert((const_builtin is Variant) == true)
+ assert((const_builtin is int) == true)
+ assert(is_instance_of(const_builtin, TYPE_INT) == true)
+ assert((const_builtin is float) == false)
+ assert(is_instance_of(const_builtin, TYPE_FLOAT) == false)
+
+ var int_array: Variant = [] as Array[int]
+ assert((int_array is Variant) == true)
+ assert((int_array is Array) == true)
+ assert(is_instance_of(int_array, TYPE_ARRAY) == true)
+ assert((int_array is Array[int]) == true)
+ assert((int_array is Array[float]) == false)
+ assert((int_array is int) == false)
+ assert(is_instance_of(int_array, TYPE_INT) == false)
+
+ var const_int_array: Variant = [] as Array[int]
+ assert((const_int_array is Variant) == true)
+ assert((const_int_array is Array) == true)
+ assert(is_instance_of(const_int_array, TYPE_ARRAY) == true)
+ assert((const_int_array is Array[int]) == true)
+ assert((const_int_array is Array[float]) == false)
+ assert((const_int_array is int) == false)
+ assert(is_instance_of(const_int_array, TYPE_INT) == false)
+
+ var b_array: Variant = [] as Array[B]
+ assert((b_array is Variant) == true)
+ assert((b_array is Array) == true)
+ assert(is_instance_of(b_array, TYPE_ARRAY) == true)
+ assert((b_array is Array[B]) == true)
+ assert((b_array is Array[A]) == false)
+ assert((b_array is Array[int]) == false)
+ assert((b_array is int) == false)
+ assert(is_instance_of(b_array, TYPE_INT) == false)
+
+ var const_b_array: Variant = [] as Array[B]
+ assert((const_b_array is Variant) == true)
+ assert((const_b_array is Array) == true)
+ assert(is_instance_of(const_b_array, TYPE_ARRAY) == true)
+ assert((const_b_array is Array[B]) == true)
+ assert((const_b_array is Array[A]) == false)
+ assert((const_b_array is Array[int]) == false)
+ assert((const_b_array is int) == false)
+ assert(is_instance_of(const_b_array, TYPE_INT) == false)
+
+ var native: Variant = RefCounted.new()
+ assert((native is Variant) == true)
+ assert((native is Object) == true)
+ assert(is_instance_of(native, TYPE_OBJECT) == true)
+ assert(is_instance_of(native, Object) == true)
+ assert((native is RefCounted) == true)
+ assert(is_instance_of(native, RefCounted) == true)
+ assert((native is Node) == false)
+ assert(is_instance_of(native, Node) == false)
+ assert((native is int) == false)
+ assert(is_instance_of(native, TYPE_INT) == false)
+
+ var a_script: Variant = A.new()
+ assert((a_script is Variant) == true)
+ assert((a_script is Object) == true)
+ assert(is_instance_of(a_script, TYPE_OBJECT) == true)
+ assert(is_instance_of(a_script, Object) == true)
+ assert((a_script is RefCounted) == true)
+ assert(is_instance_of(a_script, RefCounted) == true)
+ assert((a_script is A) == true)
+ assert(is_instance_of(a_script, A) == true)
+ assert((a_script is B) == false)
+ assert(is_instance_of(a_script, B) == false)
+ assert((a_script is Node) == false)
+ assert(is_instance_of(a_script, Node) == false)
+ assert((a_script is int) == false)
+ assert(is_instance_of(a_script, TYPE_INT) == false)
+
+ var b_script: Variant = B.new()
+ assert((b_script is Variant) == true)
+ assert((b_script is Object) == true)
+ assert(is_instance_of(b_script, TYPE_OBJECT) == true)
+ assert(is_instance_of(b_script, Object) == true)
+ assert((b_script is RefCounted) == true)
+ assert(is_instance_of(b_script, RefCounted) == true)
+ assert((b_script is A) == true)
+ assert(is_instance_of(b_script, A) == true)
+ assert((b_script is B) == true)
+ assert(is_instance_of(b_script, B) == true)
+ assert((b_script is Node) == false)
+ assert(is_instance_of(b_script, Node) == false)
+ assert((b_script is int) == false)
+ assert(is_instance_of(b_script, TYPE_INT) == false)
+
+ var var_null: Variant = null
+ assert((var_null is Variant) == true)
+ assert((var_null is int) == false)
+ assert(is_instance_of(var_null, TYPE_INT) == false)
+ assert((var_null is Object) == false)
+ assert(is_instance_of(var_null, TYPE_OBJECT) == false)
+ assert((var_null is RefCounted) == false)
+ assert(is_instance_of(var_null, RefCounted) == false)
+ assert((var_null is A) == false)
+ assert(is_instance_of(var_null, A) == false)
+
+ const const_null: Variant = null
+ assert((const_null is Variant) == true)
+ assert((const_null is int) == false)
+ assert(is_instance_of(const_null, TYPE_INT) == false)
+ assert((const_null is Object) == false)
+ assert(is_instance_of(const_null, TYPE_OBJECT) == false)
+ assert((const_null is RefCounted) == false)
+ assert(is_instance_of(const_null, RefCounted) == false)
+ assert((const_null is A) == false)
+ assert(is_instance_of(const_null, A) == false)
+
+ print('ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/features/type_test_usage.out b/modules/gdscript/tests/scripts/analyzer/features/type_test_usage.out
new file mode 100644
index 0000000000..1b47ed10dc
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/type_test_usage.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+ok
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_enum.out b/modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_enum.out
index 6e086a0918..6d8aeaf0b6 100644
--- a/modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_enum.out
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_enum.out
@@ -2,5 +2,5 @@ GDTEST_OK
>> WARNING
>> Line: 5
>> INT_AS_ENUM_WITHOUT_MATCH
->> Cannot cast 2 as Enum "cast_enum_bad_enum.gd::MyEnum": no enum member has matching value.
+>> Cannot cast 2 as Enum "cast_enum_bad_enum.gd.MyEnum": no enum member has matching value.
2
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_int.out b/modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_int.out
index c19d57f98e..b0e4af29a0 100644
--- a/modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_int.out
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_int.out
@@ -2,5 +2,5 @@ GDTEST_OK
>> WARNING
>> Line: 4
>> INT_AS_ENUM_WITHOUT_MATCH
->> Cannot cast 2 as Enum "cast_enum_bad_int.gd::MyEnum": no enum member has matching value.
+>> Cannot cast 2 as Enum "cast_enum_bad_int.gd.MyEnum": no enum member has matching value.
2
diff --git a/modules/gdscript/tests/scripts/runtime/features/typed_argument_is_null.gd b/modules/gdscript/tests/scripts/runtime/features/typed_argument_is_null.gd
new file mode 100644
index 0000000000..277242156d
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/typed_argument_is_null.gd
@@ -0,0 +1,27 @@
+# https://github.com/godotengine/godot/issues/72967
+
+class CustomNode:
+ extends Node
+
+ static func test_custom_node(n: CustomNode):
+ if not n:
+ print("null node")
+
+func test():
+ test_typed_argument_is_null()
+
+func get_custom_node() -> CustomNode:
+ return null
+
+func test_typed_argument_is_null():
+ var node: Node = Node.new()
+ print_node_name(node.get_parent())
+ node.free()
+ test_custom_node()
+
+func test_custom_node():
+ CustomNode.test_custom_node(get_custom_node())
+
+func print_node_name(n: Node):
+ if not n:
+ print("null node")
diff --git a/modules/gdscript/tests/scripts/runtime/features/typed_argument_is_null.out b/modules/gdscript/tests/scripts/runtime/features/typed_argument_is_null.out
new file mode 100644
index 0000000000..41560003d6
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/typed_argument_is_null.out
@@ -0,0 +1,3 @@
+GDTEST_OK
+null node
+null node
diff --git a/scene/3d/soft_body_3d.cpp b/scene/3d/soft_body_3d.cpp
index e7e3084037..9fa33e9d5c 100644
--- a/scene/3d/soft_body_3d.cpp
+++ b/scene/3d/soft_body_3d.cpp
@@ -217,8 +217,7 @@ bool SoftBody3D::_set_property_pinned_points_attachment(int p_item, const String
if ("spatial_attachment_path" == p_what) {
PinnedPoint *w = pinned_points.ptrw();
- pin_point(w[p_item].point_index, true, p_value);
- _make_cache_dirty();
+ callable_mp(this, &SoftBody3D::_pin_point_deferred).call_deferred(Variant(w[p_item].point_index), true, p_value);
} else if ("offset" == p_what) {
PinnedPoint *w = pinned_points.ptrw();
w[p_item].offset = p_value;
@@ -670,6 +669,11 @@ void SoftBody3D::pin_point(int p_point_index, bool pin, const NodePath &p_spatia
}
}
+void SoftBody3D::_pin_point_deferred(int p_point_index, bool pin, const NodePath p_spatial_attachment_path) {
+ pin_point(p_point_index, pin, p_spatial_attachment_path);
+ _make_cache_dirty();
+}
+
bool SoftBody3D::is_point_pinned(int p_point_index) const {
return -1 != _has_pinned_point(p_point_index);
}
@@ -741,7 +745,11 @@ void SoftBody3D::_add_pinned_point(int p_point_index, const NodePath &p_spatial_
pinned_point->spatial_attachment_path = p_spatial_attachment_path;
if (!p_spatial_attachment_path.is_empty() && has_node(p_spatial_attachment_path)) {
- pinned_point->spatial_attachment = Object::cast_to<Node3D>(get_node(p_spatial_attachment_path));
+ Node3D *attachment_node = Object::cast_to<Node3D>(get_node(p_spatial_attachment_path));
+
+ ERR_FAIL_NULL_MSG(attachment_node, "Attachment node path is invalid.");
+
+ pinned_point->spatial_attachment = attachment_node;
pinned_point->offset = (pinned_point->spatial_attachment->get_global_transform().affine_inverse() * get_global_transform()).xform(PhysicsServer3D::get_singleton()->soft_body_get_point_global_position(physics_rid, pinned_point->point_index));
}
}
diff --git a/scene/3d/soft_body_3d.h b/scene/3d/soft_body_3d.h
index d81011006b..0b75ae2cda 100644
--- a/scene/3d/soft_body_3d.h
+++ b/scene/3d/soft_body_3d.h
@@ -179,6 +179,8 @@ public:
void pin_point(int p_point_index, bool pin, const NodePath &p_spatial_attachment_path = NodePath());
bool is_point_pinned(int p_point_index) const;
+ void _pin_point_deferred(int p_point_index, bool pin, const NodePath p_spatial_attachment_path);
+
void set_ray_pickable(bool p_ray_pickable);
bool is_ray_pickable() const;
diff --git a/servers/physics_3d/godot_body_pair_3d.cpp b/servers/physics_3d/godot_body_pair_3d.cpp
index 619e6c00be..3a91e5e480 100644
--- a/servers/physics_3d/godot_body_pair_3d.cpp
+++ b/servers/physics_3d/godot_body_pair_3d.cpp
@@ -167,6 +167,9 @@ void GodotBodyPair3D::validate_contacts() {
// cast forward along motion vector to see if A is going to enter/pass B's collider next frame, only proceed if it does.
// adjust the velocity of A down so that it will just slightly intersect the collider instead of blowing right past it.
bool GodotBodyPair3D::_test_ccd(real_t p_step, GodotBody3D *p_A, int p_shape_A, const Transform3D &p_xform_A, GodotBody3D *p_B, int p_shape_B, const Transform3D &p_xform_B) {
+ GodotShape3D *shape_A_ptr = p_A->get_shape(p_shape_A);
+ GodotShape3D *shape_B_ptr = p_B->get_shape(p_shape_B);
+
Vector3 motion = p_A->get_linear_velocity() * p_step;
real_t mlen = motion.length();
if (mlen < CMP_EPSILON) {
@@ -176,7 +179,7 @@ bool GodotBodyPair3D::_test_ccd(real_t p_step, GodotBody3D *p_A, int p_shape_A,
Vector3 mnormal = motion / mlen;
real_t min = 0.0, max = 0.0;
- p_A->get_shape(p_shape_A)->project_range(mnormal, p_xform_A, min, max);
+ shape_A_ptr->project_range(mnormal, p_xform_A, min, max);
// Did it move enough in this direction to even attempt raycast?
// Let's say it should move more than 1/3 the size of the object in that axis.
@@ -187,33 +190,64 @@ bool GodotBodyPair3D::_test_ccd(real_t p_step, GodotBody3D *p_A, int p_shape_A,
// A is moving fast enough that tunneling might occur. See if it's really about to collide.
- // Cast a segment from support in motion normal, in the same direction of motion by motion length.
- // Support point will the farthest forward collision point along the movement vector.
- // i.e. the point that should hit B first if any collision does occur.
-
- // convert mnormal into body A's local xform because get_support requires (and returns) local coordinates.
- Vector3 s = p_A->get_shape(p_shape_A)->get_support(p_xform_A.basis.xform_inv(mnormal).normalized());
- Vector3 from = p_xform_A.xform(s);
- Vector3 to = from + motion;
-
- Transform3D from_inv = p_xform_B.affine_inverse();
-
- // Back up 10% of the per-frame motion behind the support point and use that as the beginning of our cast.
- // At high speeds, this may mean we're actually casting from well behind the body instead of inside it, which is odd. But it still works out.
- Vector3 local_from = from_inv.xform(from - motion * 0.1);
- Vector3 local_to = from_inv.xform(to);
+ // Support points are the farthest forward points on A in the direction of the motion vector.
+ // i.e. the candidate points of which one should hit B first if any collision does occur.
+ static const int max_supports = 16;
+ Vector3 supports_A[max_supports];
+ int support_count_A;
+ GodotShape3D::FeatureType support_type_A;
+ // Convert mnormal into body A's local xform because get_supports requires (and returns) local coordinates.
+ shape_A_ptr->get_supports(p_xform_A.basis.xform_inv(mnormal).normalized(), max_supports, supports_A, support_count_A, support_type_A);
+
+ // Cast a segment from each support point of A in the motion direction.
+ int segment_support_idx = -1;
+ float segment_hit_length = FLT_MAX;
+ Vector3 segment_hit_local;
+ for (int i = 0; i < support_count_A; i++) {
+ supports_A[i] = p_xform_A.xform(supports_A[i]);
+
+ Vector3 from = supports_A[i];
+ Vector3 to = from + motion;
+
+ Transform3D from_inv = p_xform_B.affine_inverse();
+
+ // Back up 10% of the per-frame motion behind the support point and use that as the beginning of our cast.
+ // At high speeds, this may mean we're actually casting from well behind the body instead of inside it, which is odd.
+ // But it still works out.
+ Vector3 local_from = from_inv.xform(from - motion * 0.1);
+ Vector3 local_to = from_inv.xform(to);
+
+ Vector3 rpos, rnorm;
+ if (shape_B_ptr->intersect_segment(local_from, local_to, rpos, rnorm, true)) {
+ float hit_length = local_from.distance_to(rpos);
+ if (hit_length < segment_hit_length) {
+ segment_support_idx = i;
+ segment_hit_length = hit_length;
+ segment_hit_local = rpos;
+ }
+ }
+ }
- Vector3 rpos, rnorm;
- if (!p_B->get_shape(p_shape_B)->intersect_segment(local_from, local_to, rpos, rnorm, true)) {
- // there was no hit. Since the segment is the length of per-frame motion, this means the bodies will not
+ if (segment_support_idx == -1) {
+ // There was no hit. Since the segment is the length of per-frame motion, this means the bodies will not
// actually collide yet on next frame. We'll probably check again next frame once they're closer.
return false;
}
- // Shorten the linear velocity so it will collide next frame.
- Vector3 hitpos = p_xform_B.xform(rpos);
-
- real_t newlen = hitpos.distance_to(from) + (max - min) * 0.01; // adding 1% of body length to the distance between collision and support point should cause body A's support point to arrive just within B's collider next frame.
+ Vector3 hitpos = p_xform_B.xform(segment_hit_local);
+
+ real_t newlen = hitpos.distance_to(supports_A[segment_support_idx]);
+ if (shape_B_ptr->is_concave()) {
+ // Subtracting 5% of body length from the distance between collision and support point
+ // should cause body A's support point to arrive just before a face of B next frame.
+ newlen = MAX(newlen - (max - min) * 0.05, 0.0);
+ // NOTE: This may stop body A completely, without a proper collision response.
+ // We consider this preferable to tunneling.
+ } else {
+ // Adding 1% of body length to the distance between collision and support point
+ // should cause body A's support point to arrive just within B's collider next frame.
+ newlen += (max - min) * 0.01;
+ }
p_A->set_linear_velocity((mnormal * newlen) / p_step);