Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions core/object/script_language.h
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,10 @@ class Script : public Resource {
virtual Ref<Script> get_base_script() const = 0; //< For script inheritance
virtual StringName get_global_name() const = 0;
virtual bool inherits_script(const Ref<Script> &p_script) const = 0;
/// Returns whether this script defines a trait.
virtual bool is_trait() const { return false; }
/// Returns whether instances of this script implement the named trait.
virtual bool has_trait(const StringName &p_trait) const { return false; }

virtual StringName get_instance_base_type() const = 0; ///< This may not work in all scripts, will return empty if so
virtual ScriptInstance *instance_create(Object *p_this) = 0;
Expand Down
5 changes: 2 additions & 3 deletions core/variant/array.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -233,9 +233,8 @@ void Array::assign(const Array &p_array) {
const Variant *source = p_array._p->array.ptr();
int size = p_array._p->array.size();

if ((source_typed.type == Variant::NIL && typed.type == Variant::OBJECT) || (source_typed.type == Variant::OBJECT && source_typed.can_reference(typed))) {
// from variants to objects or
// from base classes to subclasses
if ((source_typed.type == Variant::NIL && typed.type == Variant::OBJECT) || (source_typed.type == Variant::OBJECT && typed.type == Variant::OBJECT)) {
// From variants or another object type, validate each element against the destination type.
for (int i = 0; i < size; i++) {
const Variant &element = source[i];
if (element.get_type() != Variant::NIL && (element.get_type() != Variant::OBJECT || !typed.validate_object(element, "assign"))) {
Expand Down
6 changes: 5 additions & 1 deletion core/variant/container_type_validate.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,12 @@ struct ContainerTypeValidate {

const StringName &obj_class = object->get_class_name();
if (obj_class != class_name && !ClassDB::is_parent_class(obj_class, class_name)) {
Ref<Script> object_script = object->get_script();
if (object_script.is_valid() && object_script->has_trait(class_name)) {
return true;
}
if (p_output_errors) {
ERR_FAIL_V_MSG(false, vformat("Attempted to %s an object of type '%s' into a %s, which does not inherit from '%s'.", String(p_operation), object->get_class(), where, String(class_name)));
ERR_FAIL_V_MSG(false, vformat("Attempted to %s an object of type '%s' into a %s, which does not inherit from or use '%s'.", String(p_operation), object->get_class(), where, String(class_name)));
} else {
return false;
}
Expand Down
55 changes: 43 additions & 12 deletions core/variant/variant_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1354,10 +1354,11 @@ Error VariantParser::parse_value(Token &token, Variant &value, Stream *p_stream,
}

get_token(p_stream, token, line, r_err_str);
if (token.type != TK_IDENTIFIER) {
r_err_str = "Expected type identifier for key";
if (token.type != TK_IDENTIFIER && token.type != TK_STRING) {
r_err_str = "Expected type identifier or string for key";
return ERR_PARSE_ERROR;
}
const bool quoted_key_class_name = token.type == TK_STRING;

static HashMap<StringName, Variant::Type> builtin_types;
if (builtin_types.is_empty()) {
Expand All @@ -1371,7 +1372,10 @@ Error VariantParser::parse_value(Token &token, Variant &value, Stream *p_stream,
StringName key_class_name;
Variant key_script;
bool got_comma_token = false;
if (builtin_types.has(token.value)) {
if (quoted_key_class_name) {
key_type = Variant::OBJECT;
key_class_name = token.value;
} else if (builtin_types.has(token.value)) {
key_type = builtin_types.get(token.value);
} else if (token.value == "Resource" || token.value == "SubResource" || token.value == "ExtResource") {
Variant resource;
Expand All @@ -1397,6 +1401,12 @@ Error VariantParser::parse_value(Token &token, Variant &value, Stream *p_stream,
} else if (ClassDB::class_exists(token.value)) {
key_type = Variant::OBJECT;
key_class_name = token.value;
} else if (ScriptServer::is_global_class(token.value)) {
Ref<Script> trait_script = ResourceLoader::load(ScriptServer::get_global_class_path(token.value));
if (trait_script.is_valid() && trait_script->is_trait()) {
key_type = Variant::OBJECT;
key_class_name = token.value;
}
}

if (!got_comma_token) {
Expand All @@ -1408,16 +1418,20 @@ Error VariantParser::parse_value(Token &token, Variant &value, Stream *p_stream,
}

get_token(p_stream, token, line, r_err_str);
if (token.type != TK_IDENTIFIER) {
r_err_str = "Expected type identifier for value";
if (token.type != TK_IDENTIFIER && token.type != TK_STRING) {
r_err_str = "Expected type identifier or string for value";
return ERR_PARSE_ERROR;
}
const bool quoted_value_class_name = token.type == TK_STRING;

Variant::Type value_type = Variant::NIL;
StringName value_class_name;
Variant value_script;
bool got_bracket_token = false;
if (builtin_types.has(token.value)) {
if (quoted_value_class_name) {
value_type = Variant::OBJECT;
value_class_name = token.value;
} else if (builtin_types.has(token.value)) {
value_type = builtin_types.get(token.value);
} else if (token.value == "Resource" || token.value == "SubResource" || token.value == "ExtResource") {
Variant resource;
Expand All @@ -1443,6 +1457,12 @@ Error VariantParser::parse_value(Token &token, Variant &value, Stream *p_stream,
} else if (ClassDB::class_exists(token.value)) {
value_type = Variant::OBJECT;
value_class_name = token.value;
} else if (ScriptServer::is_global_class(token.value)) {
Ref<Script> trait_script = ResourceLoader::load(ScriptServer::get_global_class_path(token.value));
if (trait_script.is_valid() && trait_script->is_trait()) {
value_type = Variant::OBJECT;
value_class_name = token.value;
}
}

if (key_type != Variant::NIL || value_type != Variant::NIL) {
Expand Down Expand Up @@ -1494,10 +1514,11 @@ Error VariantParser::parse_value(Token &token, Variant &value, Stream *p_stream,
}

get_token(p_stream, token, line, r_err_str);
if (token.type != TK_IDENTIFIER) {
r_err_str = "Expected type identifier";
if (token.type != TK_IDENTIFIER && token.type != TK_STRING) {
r_err_str = "Expected type identifier or string";
return ERR_PARSE_ERROR;
}
const bool quoted_class_name = token.type == TK_STRING;

static HashMap<String, Variant::Type> builtin_types;
if (builtin_types.is_empty()) {
Expand All @@ -1508,7 +1529,9 @@ Error VariantParser::parse_value(Token &token, Variant &value, Stream *p_stream,

Array array = Array();
bool got_bracket_token = false;
if (builtin_types.has(token.value)) {
if (quoted_class_name) {
array.set_typed(Variant::OBJECT, token.value, Variant());
} else if (builtin_types.has(token.value)) {
array.set_typed(builtin_types.get(token.value), StringName(), Variant());
} else if (token.value == "Resource" || token.value == "SubResource" || token.value == "ExtResource") {
if (!p_allow_objects) {
Expand All @@ -1535,6 +1558,11 @@ Error VariantParser::parse_value(Token &token, Variant &value, Stream *p_stream,
}
} else if (ClassDB::class_exists(token.value)) {
array.set_typed(Variant::OBJECT, token.value, Variant());
} else if (ScriptServer::is_global_class(token.value)) {
Ref<Script> script = ResourceLoader::load(ScriptServer::get_global_class_path(token.value));
if (script.is_valid() && script->is_trait()) {
array.set_typed(Variant::OBJECT, token.value, Variant());
}
}

if (!got_bracket_token) {
Expand Down Expand Up @@ -2446,7 +2474,8 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str
p_store_string_func(p_store_string_ud, key_class_name);
}
} else if (key_class_name != StringName()) {
p_store_string_func(p_store_string_ud, key_class_name);
String type_name = key_class_name;
p_store_string_func(p_store_string_ud, type_name.is_valid_identifier() ? type_name : "\"" + type_name.c_escape() + "\"");
} else if (key_builtin_type == Variant::NIL) {
p_store_string_func(p_store_string_ud, "Variant");
} else {
Expand Down Expand Up @@ -2475,7 +2504,8 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str
p_store_string_func(p_store_string_ud, value_class_name);
}
} else if (value_class_name != StringName()) {
p_store_string_func(p_store_string_ud, value_class_name);
String type_name = value_class_name;
p_store_string_func(p_store_string_ud, type_name.is_valid_identifier() ? type_name : "\"" + type_name.c_escape() + "\"");
} else if (value_builtin_type == Variant::NIL) {
p_store_string_func(p_store_string_ud, "Variant");
} else {
Expand Down Expand Up @@ -2590,7 +2620,8 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str
p_store_string_func(p_store_string_ud, class_name);
}
} else if (class_name != StringName()) {
p_store_string_func(p_store_string_ud, class_name);
String type_name = class_name;
p_store_string_func(p_store_string_ud, type_name.is_valid_identifier() ? type_name : "\"" + type_name.c_escape() + "\"");
} else {
p_store_string_func(p_store_string_ud, Variant::get_type_name(builtin_type));
}
Expand Down
3 changes: 3 additions & 0 deletions editor/editor_node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5350,6 +5350,9 @@ bool EditorNode::is_object_of_custom_type(const Object *p_object, const StringNa
}

if (scr.is_valid()) {
if (scr->has_trait(p_class)) {
return true;
}
Ref<Script> base_script = scr;
while (base_script.is_valid()) {
StringName name = EditorNode::get_editor_data().script_class_get_name(base_script->get_path());
Expand Down
43 changes: 35 additions & 8 deletions editor/inspector/editor_properties_array_dict.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@

#include "core/input/input.h"
#include "core/io/marshalls.h"
#include "core/io/resource_loader.h"
#include "core/object/script_language.h"
#include "core/variant/struct.h"
#include "core/variant/struct_info.h"
#include "editor/docks/inspector_dock.h"
Expand Down Expand Up @@ -243,15 +245,40 @@ String EditorPropertyDictionaryObject::get_label_for_index(int p_index) {

///////////////////// ARRAY ///////////////////////////

static void _resolve_typed_object_hint(const String &p_hint_string, StringName &r_class, Ref<Script> &r_script) {
if (p_hint_string.is_empty()) {
return;
}
if (ClassDB::class_exists(p_hint_string)) {
r_class = p_hint_string;
return;
}

String script_path = p_hint_string;
if (ScriptServer::is_global_class(p_hint_string)) {
script_path = ScriptServer::get_global_class_path(p_hint_string);
} else if (script_path.contains("::")) {
script_path = script_path.get_slice("::", 0);
}
Ref<Script> hint_script;
if (script_path.is_resource_file()) {
hint_script = ResourceLoader::load(script_path);
}
if (hint_script.is_valid() && (hint_script->is_trait() || p_hint_string.contains("::"))) {
r_class = p_hint_string;
} else if (hint_script.is_valid()) {
r_class = hint_script->get_instance_base_type();
r_script = hint_script;
}
}

void EditorPropertyArray::initialize_array(Variant &p_array) {
if (array_type == Variant::ARRAY && subtype != Variant::NIL) {
Array array;
StringName subtype_class;
Ref<Script> subtype_script;
if (subtype == Variant::OBJECT && !subtype_hint_string.is_empty()) {
if (ClassDB::class_exists(subtype_hint_string)) {
subtype_class = subtype_hint_string;
}
if (subtype == Variant::OBJECT) {
_resolve_typed_object_hint(subtype_hint_string, subtype_class, subtype_script);
}
array.set_typed(subtype, subtype_class, subtype_script);
p_array = array;
Expand Down Expand Up @@ -993,13 +1020,13 @@ void EditorPropertyDictionary::initialize_dictionary(Variant &p_dictionary) {
Dictionary dict;
StringName key_subtype_class;
Ref<Script> key_subtype_script;
if (key_subtype == Variant::OBJECT && !key_subtype_hint_string.is_empty() && ClassDB::class_exists(key_subtype_hint_string)) {
key_subtype_class = key_subtype_hint_string;
if (key_subtype == Variant::OBJECT) {
_resolve_typed_object_hint(key_subtype_hint_string, key_subtype_class, key_subtype_script);
}
StringName value_subtype_class;
Ref<Script> value_subtype_script;
if (value_subtype == Variant::OBJECT && !value_subtype_hint_string.is_empty() && ClassDB::class_exists(value_subtype_hint_string)) {
value_subtype_class = value_subtype_hint_string;
if (value_subtype == Variant::OBJECT) {
_resolve_typed_object_hint(value_subtype_hint_string, value_subtype_class, value_subtype_script);
}
dict.set_typed(key_subtype, key_subtype_class, key_subtype_script, value_subtype, value_subtype_class, value_subtype_script);
p_dictionary = dict;
Expand Down
3 changes: 2 additions & 1 deletion modules/gdscript/gdscript.h
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,8 @@ class GDScript : public Script {
}

_FORCE_INLINE_ StringName get_local_name() const { return local_name; }
_FORCE_INLINE_ bool has_trait(const StringName &p_trait) const { return traits_fqtn.has(p_trait); }
_FORCE_INLINE_ bool has_trait(const StringName &p_trait) const override { return traits_fqtn.has(p_trait); }
_FORCE_INLINE_ bool is_trait() const override { return file_trait; }

void clear(GDScript::ClearData *p_clear_data = nullptr);

Expand Down
Loading
Loading