diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 077b30a7fed2..87ad6f4ceae4 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -4099,7 +4099,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri GDScriptParser::DataType base_type = p_subscript->base->get_datatype(); bool valid = false; // If the base is a metatype, use the analyzer instead. - if (p_subscript->base->is_constant && !base_type.is_meta_type) { + if (p_subscript->base->is_constant && !base_type.is_meta_type && base_type.kind != GDScriptParser::DataType::CLASS) { // Just try to get it. Variant value = p_subscript->base->reduced_value.get_named(p_subscript->attribute->name, valid); if (valid) { diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 52c1a5b141e2..662a36d96f41 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -4095,25 +4095,29 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node } } break; case GDScriptParser::DataType::ENUM: { - variable->export_info.type = Variant::INT; - variable->export_info.hint = PROPERTY_HINT_ENUM; - - String enum_hint_string; - bool first = true; - for (const KeyValue &E : export_type.enum_values) { - if (!first) { - enum_hint_string += ","; - } else { - first = false; + if (export_type.is_meta_type) { + variable->export_info.type = Variant::DICTIONARY; + } else { + variable->export_info.type = Variant::INT; + variable->export_info.hint = PROPERTY_HINT_ENUM; + + String enum_hint_string; + bool first = true; + for (const KeyValue &E : export_type.enum_values) { + if (!first) { + enum_hint_string += ","; + } else { + first = false; + } + enum_hint_string += E.key.operator String().capitalize().xml_escape(); + enum_hint_string += ":"; + enum_hint_string += String::num_int64(E.value).xml_escape(); } - enum_hint_string += E.key.operator String().capitalize().xml_escape(); - enum_hint_string += ":"; - enum_hint_string += String::num_int64(E.value).xml_escape(); - } - variable->export_info.hint_string = enum_hint_string; - variable->export_info.usage |= PROPERTY_USAGE_CLASS_IS_ENUM; - variable->export_info.class_name = String(export_type.native_type).replace("::", "."); + variable->export_info.hint_string = enum_hint_string; + variable->export_info.usage |= PROPERTY_USAGE_CLASS_IS_ENUM; + variable->export_info.class_name = String(export_type.native_type).replace("::", "."); + } } break; default: push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", variable); diff --git a/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd b/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd new file mode 100644 index 000000000000..dafd2ec0c818 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd @@ -0,0 +1,17 @@ +class_name TestExportEnumAsDictionary + +enum MyEnum {A, B, C} + +const Utils = preload("../../utils.notest.gd") + +@export var x1 = MyEnum +@export var x2 = MyEnum.A +@export var x3 := MyEnum +@export var x4 := MyEnum.A +@export var x5: MyEnum + +func test(): + for property in get_property_list(): + if property.usage & PROPERTY_USAGE_SCRIPT_VARIABLE: + print(Utils.get_property_signature(property)) + print(" ", Utils.get_property_additional_info(property)) diff --git a/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.out b/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.out new file mode 100644 index 000000000000..f1a13f10454e --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.out @@ -0,0 +1,11 @@ +GDTEST_OK +@export var x1: Dictionary + hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE +@export var x2: TestExportEnumAsDictionary.MyEnum + hint=ENUM hint_string="A:0,B:1,C:2" usage=DEFAULT|SCRIPT_VARIABLE|CLASS_IS_ENUM +@export var x3: Dictionary + hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE +@export var x4: TestExportEnumAsDictionary.MyEnum + hint=ENUM hint_string="A:0,B:1,C:2" usage=DEFAULT|SCRIPT_VARIABLE|CLASS_IS_ENUM +@export var x5: TestExportEnumAsDictionary.MyEnum + hint=ENUM hint_string="A:0,B:1,C:2" usage=DEFAULT|SCRIPT_VARIABLE|CLASS_IS_ENUM diff --git a/modules/gdscript/tests/scripts/utils.notest.gd b/modules/gdscript/tests/scripts/utils.notest.gd index 50444e62a1a9..fb208171174b 100644 --- a/modules/gdscript/tests/scripts/utils.notest.gd +++ b/modules/gdscript/tests/scripts/utils.notest.gd @@ -19,6 +19,7 @@ static func get_type(property: Dictionary, is_return: bool = false) -> String: return property.class_name return variant_get_type_name(property.type) + static func get_property_signature(property: Dictionary, is_static: bool = false) -> String: var result: String = "" if not (property.usage & PROPERTY_USAGE_SCRIPT_VARIABLE): @@ -30,6 +31,15 @@ static func get_property_signature(property: Dictionary, is_static: bool = false result += "var " + property.name + ": " + get_type(property) return result + +static func get_property_additional_info(property: Dictionary) -> String: + return 'hint=%s hint_string="%s" usage=%s' % [ + get_property_hint_name(property.hint).trim_prefix("PROPERTY_HINT_"), + str(property.hint_string).c_escape(), + get_property_usage_string(property.usage).replace("PROPERTY_USAGE_", ""), + ] + + static func get_method_signature(method: Dictionary, is_signal: bool = false) -> String: var result: String = "" if method.flags & METHOD_FLAG_STATIC: @@ -55,6 +65,7 @@ static func get_method_signature(method: Dictionary, is_signal: bool = false) -> result += " -> " + get_type(method.return, true) return result + static func variant_get_type_name(type: Variant.Type) -> String: match type: TYPE_NIL: @@ -135,3 +146,136 @@ static func variant_get_type_name(type: Variant.Type) -> String: return "PackedColorArray" push_error("Argument `type` is invalid. Use `TYPE_*` constants.") return "" + + +static func get_property_hint_name(hint: PropertyHint) -> String: + match hint: + PROPERTY_HINT_NONE: + return "PROPERTY_HINT_NONE" + PROPERTY_HINT_RANGE: + return "PROPERTY_HINT_RANGE" + PROPERTY_HINT_ENUM: + return "PROPERTY_HINT_ENUM" + PROPERTY_HINT_ENUM_SUGGESTION: + return "PROPERTY_HINT_ENUM_SUGGESTION" + PROPERTY_HINT_EXP_EASING: + return "PROPERTY_HINT_EXP_EASING" + PROPERTY_HINT_LINK: + return "PROPERTY_HINT_LINK" + PROPERTY_HINT_FLAGS: + return "PROPERTY_HINT_FLAGS" + PROPERTY_HINT_LAYERS_2D_RENDER: + return "PROPERTY_HINT_LAYERS_2D_RENDER" + PROPERTY_HINT_LAYERS_2D_PHYSICS: + return "PROPERTY_HINT_LAYERS_2D_PHYSICS" + PROPERTY_HINT_LAYERS_2D_NAVIGATION: + return "PROPERTY_HINT_LAYERS_2D_NAVIGATION" + PROPERTY_HINT_LAYERS_3D_RENDER: + return "PROPERTY_HINT_LAYERS_3D_RENDER" + PROPERTY_HINT_LAYERS_3D_PHYSICS: + return "PROPERTY_HINT_LAYERS_3D_PHYSICS" + PROPERTY_HINT_LAYERS_3D_NAVIGATION: + return "PROPERTY_HINT_LAYERS_3D_NAVIGATION" + PROPERTY_HINT_LAYERS_AVOIDANCE: + return "PROPERTY_HINT_LAYERS_AVOIDANCE" + PROPERTY_HINT_FILE: + return "PROPERTY_HINT_FILE" + PROPERTY_HINT_DIR: + return "PROPERTY_HINT_DIR" + PROPERTY_HINT_GLOBAL_FILE: + return "PROPERTY_HINT_GLOBAL_FILE" + PROPERTY_HINT_GLOBAL_DIR: + return "PROPERTY_HINT_GLOBAL_DIR" + PROPERTY_HINT_RESOURCE_TYPE: + return "PROPERTY_HINT_RESOURCE_TYPE" + PROPERTY_HINT_MULTILINE_TEXT: + return "PROPERTY_HINT_MULTILINE_TEXT" + PROPERTY_HINT_EXPRESSION: + return "PROPERTY_HINT_EXPRESSION" + PROPERTY_HINT_PLACEHOLDER_TEXT: + return "PROPERTY_HINT_PLACEHOLDER_TEXT" + PROPERTY_HINT_COLOR_NO_ALPHA: + return "PROPERTY_HINT_COLOR_NO_ALPHA" + PROPERTY_HINT_OBJECT_ID: + return "PROPERTY_HINT_OBJECT_ID" + PROPERTY_HINT_TYPE_STRING: + return "PROPERTY_HINT_TYPE_STRING" + PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE: + return "PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE" + PROPERTY_HINT_OBJECT_TOO_BIG: + return "PROPERTY_HINT_OBJECT_TOO_BIG" + PROPERTY_HINT_NODE_PATH_VALID_TYPES: + return "PROPERTY_HINT_NODE_PATH_VALID_TYPES" + PROPERTY_HINT_SAVE_FILE: + return "PROPERTY_HINT_SAVE_FILE" + PROPERTY_HINT_GLOBAL_SAVE_FILE: + return "PROPERTY_HINT_GLOBAL_SAVE_FILE" + PROPERTY_HINT_INT_IS_OBJECTID: + return "PROPERTY_HINT_INT_IS_OBJECTID" + PROPERTY_HINT_INT_IS_POINTER: + return "PROPERTY_HINT_INT_IS_POINTER" + PROPERTY_HINT_ARRAY_TYPE: + return "PROPERTY_HINT_ARRAY_TYPE" + PROPERTY_HINT_LOCALE_ID: + return "PROPERTY_HINT_LOCALE_ID" + PROPERTY_HINT_LOCALIZABLE_STRING: + return "PROPERTY_HINT_LOCALIZABLE_STRING" + PROPERTY_HINT_NODE_TYPE: + return "PROPERTY_HINT_NODE_TYPE" + PROPERTY_HINT_HIDE_QUATERNION_EDIT: + return "PROPERTY_HINT_HIDE_QUATERNION_EDIT" + PROPERTY_HINT_PASSWORD: + return "PROPERTY_HINT_PASSWORD" + push_error("Argument `hint` is invalid. Use `PROPERTY_HINT_*` constants.") + return "" + + +static func get_property_usage_string(usage: int) -> String: + if usage == PROPERTY_USAGE_NONE: + return "PROPERTY_USAGE_NONE" + + const FLAGS: Array[Array] = [ + [PROPERTY_USAGE_DEFAULT, "PROPERTY_USAGE_DEFAULT"], + [PROPERTY_USAGE_STORAGE, "PROPERTY_USAGE_STORAGE"], + [PROPERTY_USAGE_EDITOR, "PROPERTY_USAGE_EDITOR"], + [PROPERTY_USAGE_INTERNAL, "PROPERTY_USAGE_INTERNAL"], + [PROPERTY_USAGE_CHECKABLE, "PROPERTY_USAGE_CHECKABLE"], + [PROPERTY_USAGE_CHECKED, "PROPERTY_USAGE_CHECKED"], + [PROPERTY_USAGE_GROUP, "PROPERTY_USAGE_GROUP"], + [PROPERTY_USAGE_CATEGORY, "PROPERTY_USAGE_CATEGORY"], + [PROPERTY_USAGE_SUBGROUP, "PROPERTY_USAGE_SUBGROUP"], + [PROPERTY_USAGE_CLASS_IS_BITFIELD, "PROPERTY_USAGE_CLASS_IS_BITFIELD"], + [PROPERTY_USAGE_NO_INSTANCE_STATE, "PROPERTY_USAGE_NO_INSTANCE_STATE"], + [PROPERTY_USAGE_RESTART_IF_CHANGED, "PROPERTY_USAGE_RESTART_IF_CHANGED"], + [PROPERTY_USAGE_SCRIPT_VARIABLE, "PROPERTY_USAGE_SCRIPT_VARIABLE"], + [PROPERTY_USAGE_STORE_IF_NULL, "PROPERTY_USAGE_STORE_IF_NULL"], + [PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED, "PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED"], + [PROPERTY_USAGE_SCRIPT_DEFAULT_VALUE, "PROPERTY_USAGE_SCRIPT_DEFAULT_VALUE"], + [PROPERTY_USAGE_CLASS_IS_ENUM, "PROPERTY_USAGE_CLASS_IS_ENUM"], + [PROPERTY_USAGE_NIL_IS_VARIANT, "PROPERTY_USAGE_NIL_IS_VARIANT"], + [PROPERTY_USAGE_ARRAY, "PROPERTY_USAGE_ARRAY"], + [PROPERTY_USAGE_ALWAYS_DUPLICATE, "PROPERTY_USAGE_ALWAYS_DUPLICATE"], + [PROPERTY_USAGE_NEVER_DUPLICATE, "PROPERTY_USAGE_NEVER_DUPLICATE"], + [PROPERTY_USAGE_HIGH_END_GFX, "PROPERTY_USAGE_HIGH_END_GFX"], + [PROPERTY_USAGE_NODE_PATH_FROM_SCENE_ROOT, "PROPERTY_USAGE_NODE_PATH_FROM_SCENE_ROOT"], + [PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT, "PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT"], + [PROPERTY_USAGE_KEYING_INCREMENTS, "PROPERTY_USAGE_KEYING_INCREMENTS"], + [PROPERTY_USAGE_DEFERRED_SET_RESOURCE, "PROPERTY_USAGE_DEFERRED_SET_RESOURCE"], + [PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT, "PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT"], + [PROPERTY_USAGE_EDITOR_BASIC_SETTING, "PROPERTY_USAGE_EDITOR_BASIC_SETTING"], + [PROPERTY_USAGE_READ_ONLY, "PROPERTY_USAGE_READ_ONLY"], + [PROPERTY_USAGE_SECRET, "PROPERTY_USAGE_SECRET"], + ] + + var result: String = "" + + for flag in FLAGS: + if usage & flag[0]: + result += flag[1] + "|" + usage &= ~flag[0] + + if usage != PROPERTY_USAGE_NONE: + push_error("Argument `usage` is invalid. Use `PROPERTY_USAGE_*` constants.") + return "" + + return result.left(-1)