GDScript: Improve PROPERTY_HINT_{ARRAY,DICTIONARY}_TYPE handling

This commit is contained in:
Danil Alexeev
2026-04-08 17:53:56 +03:00
parent 653fed37e0
commit 7db019d44d
6 changed files with 88 additions and 180 deletions
+34 -79
View File
@@ -5779,6 +5779,37 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_metatype(const GDScriptPars
return result;
}
GDScriptParser::DataType GDScriptAnalyzer::type_from_property_hint_string(const String &p_type_name) const {
GDScriptParser::DataType result;
result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
result.is_constant = false;
const Variant::Type builtin_type = GDScriptParser::get_builtin_type(p_type_name);
if (builtin_type < Variant::VARIANT_MAX) {
// Built-in type.
result.kind = GDScriptParser::DataType::BUILTIN;
result.builtin_type = builtin_type;
} else if (class_exists(p_type_name)) {
result.kind = GDScriptParser::DataType::NATIVE;
result.builtin_type = Variant::OBJECT;
result.native_type = p_type_name;
} else if (ScriptServer::is_global_class(p_type_name)) {
// Just load this as it shouldn't be a GDScript.
Ref<Script> script = ResourceLoader::load(ScriptServer::get_global_class_path(p_type_name));
result.kind = GDScriptParser::DataType::SCRIPT;
result.builtin_type = Variant::OBJECT;
result.native_type = script->get_instance_base_type();
result.script_type = script;
} else if (p_type_name == SNAME("Variant")) {
result.kind = GDScriptParser::DataType::VARIANT;
} else {
result.kind = GDScriptParser::DataType::VARIANT;
ERR_FAIL_V_MSG(result, "Could not find type from property hint string.");
}
return result;
}
GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo &p_property, bool p_is_arg, bool p_is_readonly) const {
GDScriptParser::DataType result;
result.is_read_only = p_is_readonly;
@@ -5807,86 +5838,10 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo
result.kind = GDScriptParser::DataType::BUILTIN;
result.builtin_type = p_property.type;
if (p_property.type == Variant::ARRAY && p_property.hint == PROPERTY_HINT_ARRAY_TYPE) {
// Check element type.
StringName elem_type_name = p_property.hint_string;
GDScriptParser::DataType elem_type;
elem_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
Variant::Type elem_builtin_type = GDScriptParser::get_builtin_type(elem_type_name);
if (elem_builtin_type < Variant::VARIANT_MAX) {
// Builtin type.
elem_type.kind = GDScriptParser::DataType::BUILTIN;
elem_type.builtin_type = elem_builtin_type;
} else if (class_exists(elem_type_name)) {
elem_type.kind = GDScriptParser::DataType::NATIVE;
elem_type.builtin_type = Variant::OBJECT;
elem_type.native_type = elem_type_name;
} else if (ScriptServer::is_global_class(elem_type_name)) {
// Just load this as it shouldn't be a GDScript.
Ref<Script> script = ResourceLoader::load(ScriptServer::get_global_class_path(elem_type_name));
elem_type.kind = GDScriptParser::DataType::SCRIPT;
elem_type.builtin_type = Variant::OBJECT;
elem_type.native_type = script->get_instance_base_type();
elem_type.script_type = script;
} else {
ERR_FAIL_V_MSG(result, "Could not find element type from property hint of a typed array.");
}
elem_type.is_constant = false;
result.set_container_element_type(0, elem_type);
result.set_container_element_type(0, type_from_property_hint_string(p_property.hint_string));
} else if (p_property.type == Variant::DICTIONARY && p_property.hint == PROPERTY_HINT_DICTIONARY_TYPE) {
// Check element type.
StringName key_elem_type_name = p_property.hint_string.get_slicec(';', 0);
GDScriptParser::DataType key_elem_type;
key_elem_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
Variant::Type key_elem_builtin_type = GDScriptParser::get_builtin_type(key_elem_type_name);
if (key_elem_builtin_type < Variant::VARIANT_MAX) {
// Builtin type.
key_elem_type.kind = GDScriptParser::DataType::BUILTIN;
key_elem_type.builtin_type = key_elem_builtin_type;
} else if (class_exists(key_elem_type_name)) {
key_elem_type.kind = GDScriptParser::DataType::NATIVE;
key_elem_type.builtin_type = Variant::OBJECT;
key_elem_type.native_type = key_elem_type_name;
} else if (ScriptServer::is_global_class(key_elem_type_name)) {
// Just load this as it shouldn't be a GDScript.
Ref<Script> script = ResourceLoader::load(ScriptServer::get_global_class_path(key_elem_type_name));
key_elem_type.kind = GDScriptParser::DataType::SCRIPT;
key_elem_type.builtin_type = Variant::OBJECT;
key_elem_type.native_type = script->get_instance_base_type();
key_elem_type.script_type = script;
} else {
ERR_FAIL_V_MSG(result, "Could not find element type from property hint of a typed dictionary.");
}
key_elem_type.is_constant = false;
StringName value_elem_type_name = p_property.hint_string.get_slicec(';', 1);
GDScriptParser::DataType value_elem_type;
value_elem_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
Variant::Type value_elem_builtin_type = GDScriptParser::get_builtin_type(value_elem_type_name);
if (value_elem_builtin_type < Variant::VARIANT_MAX) {
// Builtin type.
value_elem_type.kind = GDScriptParser::DataType::BUILTIN;
value_elem_type.builtin_type = value_elem_builtin_type;
} else if (class_exists(value_elem_type_name)) {
value_elem_type.kind = GDScriptParser::DataType::NATIVE;
value_elem_type.builtin_type = Variant::OBJECT;
value_elem_type.native_type = value_elem_type_name;
} else if (ScriptServer::is_global_class(value_elem_type_name)) {
// Just load this as it shouldn't be a GDScript.
Ref<Script> script = ResourceLoader::load(ScriptServer::get_global_class_path(value_elem_type_name));
value_elem_type.kind = GDScriptParser::DataType::SCRIPT;
value_elem_type.builtin_type = Variant::OBJECT;
value_elem_type.native_type = script->get_instance_base_type();
value_elem_type.script_type = script;
} else {
ERR_FAIL_V_MSG(result, "Could not find element type from property hint of a typed dictionary.");
}
value_elem_type.is_constant = false;
result.set_container_element_type(0, key_elem_type);
result.set_container_element_type(1, value_elem_type);
result.set_container_element_type(0, type_from_property_hint_string(p_property.hint_string.get_slicec(';', 0)));
result.set_container_element_type(1, type_from_property_hint_string(p_property.hint_string.get_slicec(';', 1)));
} else if (p_property.type == Variant::INT) {
// Check if it's enum.
if ((p_property.usage & PROPERTY_USAGE_CLASS_IS_ENUM) && p_property.class_name != StringName()) {
+1
View File
@@ -134,6 +134,7 @@ class GDScriptAnalyzer {
Dictionary make_dictionary_from_element_datatype(const GDScriptParser::DataType &p_key_element_datatype, const GDScriptParser::DataType &p_value_element_datatype, const GDScriptParser::Node *p_source_node = nullptr);
GDScriptParser::DataType type_from_script(const Ref<Script> &p_script, const GDScriptParser::Node *p_source, bool p_is_meta_type);
GDScriptParser::DataType type_from_variant(const Variant &p_value, const GDScriptParser::Node *p_source);
GDScriptParser::DataType type_from_property_hint_string(const String &p_type_name) const;
GDScriptParser::DataType type_from_property(const PropertyInfo &p_property, bool p_is_arg = false, bool p_is_readonly = false) const;
GDScriptParser::DataType make_global_class_meta_type(const StringName &p_class_name, const GDScriptParser::Node *p_source);
bool get_function_signature(GDScriptParser::Node *p_source, bool p_is_constructor, GDScriptParser::DataType base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, BitField<MethodFlags> &r_method_flags, StringName *r_native_class = nullptr);
+37 -93
View File
@@ -5385,6 +5385,35 @@ String GDScriptParser::DataType::to_string() const {
ERR_FAIL_V_MSG("<unresolved type>", "Kind set outside the enum range.");
}
String GDScriptParser::DataType::to_property_info_hint_string() const {
switch (kind) {
case BUILTIN:
return Variant::get_type_name(builtin_type);
case NATIVE:
return native_type;
case SCRIPT:
if (script_type.is_valid() && script_type->get_global_name() != StringName()) {
return script_type->get_global_name();
} else {
return native_type;
}
case CLASS:
if (class_type != nullptr && class_type->get_global_name() != StringName()) {
return class_type->get_global_name();
} else {
return native_type;
}
case ENUM:
return String(native_type).replace("::", ".");
case VARIANT:
return "Variant";
case RESOLVING:
case UNRESOLVED:
break;
}
ERR_FAIL_V_MSG("Variant", "GDScript bug: Unexpected type kind.");
}
PropertyInfo GDScriptParser::DataType::to_property_info(const String &p_name) const {
PropertyInfo result;
result.name = p_name;
@@ -5400,106 +5429,21 @@ PropertyInfo GDScriptParser::DataType::to_property_info(const String &p_name) co
result.type = builtin_type;
if (builtin_type == Variant::ARRAY && has_container_element_type(0)) {
const DataType elem_type = get_container_element_type(0);
switch (elem_type.kind) {
case BUILTIN:
result.hint = PROPERTY_HINT_ARRAY_TYPE;
result.hint_string = Variant::get_type_name(elem_type.builtin_type);
break;
case NATIVE:
result.hint = PROPERTY_HINT_ARRAY_TYPE;
result.hint_string = elem_type.native_type;
break;
case SCRIPT:
result.hint = PROPERTY_HINT_ARRAY_TYPE;
if (elem_type.script_type.is_valid() && elem_type.script_type->get_global_name() != StringName()) {
result.hint_string = elem_type.script_type->get_global_name();
} else {
result.hint_string = elem_type.native_type;
}
break;
case CLASS:
result.hint = PROPERTY_HINT_ARRAY_TYPE;
if (elem_type.class_type != nullptr && elem_type.class_type->get_global_name() != StringName()) {
result.hint_string = elem_type.class_type->get_global_name();
} else {
result.hint_string = elem_type.native_type;
}
break;
case ENUM:
result.hint = PROPERTY_HINT_ARRAY_TYPE;
result.hint_string = String(elem_type.native_type).replace("::", ".");
break;
case VARIANT:
case RESOLVING:
case UNRESOLVED:
break;
if (elem_type.is_variant()) {
break;
}
result.hint = PROPERTY_HINT_ARRAY_TYPE;
result.hint_string = elem_type.to_property_info_hint_string();
} else if (builtin_type == Variant::DICTIONARY && has_container_element_types()) {
const DataType key_type = get_container_element_type_or_variant(0);
const DataType value_type = get_container_element_type_or_variant(1);
if ((key_type.kind == VARIANT && value_type.kind == VARIANT) || key_type.kind == RESOLVING ||
key_type.kind == UNRESOLVED || value_type.kind == RESOLVING || value_type.kind == UNRESOLVED) {
if (key_type.is_variant() && value_type.is_variant()) {
break;
}
String key_hint, value_hint;
switch (key_type.kind) {
case BUILTIN:
key_hint = Variant::get_type_name(key_type.builtin_type);
break;
case NATIVE:
key_hint = key_type.native_type;
break;
case SCRIPT:
if (key_type.script_type.is_valid() && key_type.script_type->get_global_name() != StringName()) {
key_hint = key_type.script_type->get_global_name();
} else {
key_hint = key_type.native_type;
}
break;
case CLASS:
if (key_type.class_type != nullptr && key_type.class_type->get_global_name() != StringName()) {
key_hint = key_type.class_type->get_global_name();
} else {
key_hint = key_type.native_type;
}
break;
case ENUM:
key_hint = String(key_type.native_type).replace("::", ".");
break;
default:
key_hint = "Variant";
break;
}
switch (value_type.kind) {
case BUILTIN:
value_hint = Variant::get_type_name(value_type.builtin_type);
break;
case NATIVE:
value_hint = value_type.native_type;
break;
case SCRIPT:
if (value_type.script_type.is_valid() && value_type.script_type->get_global_name() != StringName()) {
value_hint = value_type.script_type->get_global_name();
} else {
value_hint = value_type.native_type;
}
break;
case CLASS:
if (value_type.class_type != nullptr && value_type.class_type->get_global_name() != StringName()) {
value_hint = value_type.class_type->get_global_name();
} else {
value_hint = value_type.native_type;
}
break;
case ENUM:
value_hint = String(value_type.native_type).replace("::", ".");
break;
default:
value_hint = "Variant";
break;
}
result.hint = PROPERTY_HINT_DICTIONARY_TYPE;
result.hint_string = key_hint + ";" + value_hint;
result.hint_string = key_type.to_property_info_hint_string() + ";" + value_type.to_property_info_hint_string();
}
break;
case NATIVE:
+2
View File
@@ -146,6 +146,8 @@ public:
String to_string() const;
_FORCE_INLINE_ String to_string_strict() const { return is_hard_type() ? to_string() : "Variant"; }
String to_property_info_hint_string() const;
PropertyInfo to_property_info(const String &p_name) const;
_FORCE_INLINE_ static DataType get_variant_type() { // Default DataType for container elements.
@@ -3,7 +3,7 @@ class_name TestMemberInfo
class MyClass:
pass
enum MyEnum {}
enum MyEnum { NONE }
static var test_static_var_untyped
static var test_static_var_weak_null = null
@@ -16,14 +16,19 @@ var test_var_weak_int = 1
@export var test_var_weak_int_exported = 1
var test_var_weak_variant_type = TYPE_NIL
@export var test_var_weak_variant_type_exported = TYPE_NIL
var test_var_hard_variant: Variant
var test_var_hard_int: int
var test_var_hard_variant_type: Variant.Type
@export var test_var_hard_variant_type_exported: Variant.Type
var test_var_hard_node_process_mode: Node.ProcessMode
@warning_ignore("enum_variable_without_default")
var test_var_hard_my_enum: MyEnum
var test_var_hard_resource: Resource
var test_var_hard_this: TestMemberInfo
var test_var_hard_my_class: MyClass
var test_var_hard_array: Array
var test_var_hard_array_variant: Array[Variant]
var test_var_hard_array_int: Array[int]
var test_var_hard_array_variant_type: Array[Variant.Type]
var test_var_hard_array_node_process_mode: Array[Node.ProcessMode]
@@ -31,7 +36,9 @@ var test_var_hard_array_my_enum: Array[MyEnum]
var test_var_hard_array_resource: Array[Resource]
var test_var_hard_array_this: Array[TestMemberInfo]
var test_var_hard_array_my_class: Array[MyClass]
var test_var_hard_dictionary: Dictionary
var test_var_hard_dictionary_variant_variant: Dictionary[Variant, Variant]
var test_var_hard_dictionary_int_variant: Dictionary[int, Variant]
var test_var_hard_dictionary_variant_int: Dictionary[Variant, int]
var test_var_hard_dictionary_int_int: Dictionary[int, int]
@@ -41,9 +48,6 @@ var test_var_hard_dictionary_my_enum: Dictionary[MyEnum, MyEnum]
var test_var_hard_dictionary_resource: Dictionary[Resource, Resource]
var test_var_hard_dictionary_this: Dictionary[TestMemberInfo, TestMemberInfo]
var test_var_hard_dictionary_my_class: Dictionary[MyClass, MyClass]
var test_var_hard_resource: Resource
var test_var_hard_this: TestMemberInfo
var test_var_hard_my_class: MyClass
static func test_static_func(): pass
@@ -15,7 +15,11 @@ var test_var_hard_variant_type: Variant.Type
var test_var_hard_variant_type_exported: Variant.Type
var test_var_hard_node_process_mode: Node.ProcessMode
var test_var_hard_my_enum: TestMemberInfo.MyEnum
var test_var_hard_resource: Resource
var test_var_hard_this: TestMemberInfo
var test_var_hard_my_class: RefCounted
var test_var_hard_array: Array
var test_var_hard_array_variant: Array
var test_var_hard_array_int: Array[int]
var test_var_hard_array_variant_type: Array[Variant.Type]
var test_var_hard_array_node_process_mode: Array[Node.ProcessMode]
@@ -24,6 +28,7 @@ var test_var_hard_array_resource: Array[Resource]
var test_var_hard_array_this: Array[TestMemberInfo]
var test_var_hard_array_my_class: Array[RefCounted]
var test_var_hard_dictionary: Dictionary
var test_var_hard_dictionary_variant_variant: Dictionary
var test_var_hard_dictionary_int_variant: Dictionary[int, Variant]
var test_var_hard_dictionary_variant_int: Dictionary[Variant, int]
var test_var_hard_dictionary_int_int: Dictionary[int, int]
@@ -33,9 +38,6 @@ var test_var_hard_dictionary_my_enum: Dictionary[TestMemberInfo.MyEnum, TestMemb
var test_var_hard_dictionary_resource: Dictionary[Resource, Resource]
var test_var_hard_dictionary_this: Dictionary[TestMemberInfo, TestMemberInfo]
var test_var_hard_dictionary_my_class: Dictionary[RefCounted, RefCounted]
var test_var_hard_resource: Resource
var test_var_hard_this: TestMemberInfo
var test_var_hard_my_class: RefCounted
static func test_static_func() -> void
func test_func_implicit_void() -> void
func test_func_explicit_void() -> void