diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml index 9d597d8e2385..b1c28cffda70 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -2676,7 +2676,7 @@ Hints that an [int] property is a bitmask with named bit flags. The hint string is a comma separated list of names such as [code]"Bit0,Bit1,Bit2,Bit3"[/code]. Whitespaces are [b]not[/b] removed from either end of a name. The first name in the list has value 1, the next 2, then 4, 8, 16 and so on. Explicit values can also be specified by appending [code]:integer[/code] to the name, e.g. [code]"A:4,B:8,C:16"[/code]. You can also combine several flags ([code]"A:4,B:8,AB:12,C:16"[/code]). - [b]Note:[/b] A flag value must be at least [code]1[/code] and at most [code]2 ** 32 - 1[/code]. + [b]Note:[/b] A flag value must be at least [code]1[/code] and at most [code]2 ** 32 - 1[/code]. This means you can use up to 32 different bit flags. [b]Note:[/b] Unlike [constant PROPERTY_HINT_ENUM], the previous explicit value is not taken into account. For the hint [code]"A:16,B,C"[/code], A is 16, B is 2, C is 4. @@ -2772,6 +2772,7 @@ hint_string = "%d:%d/%d:Texture2D" % [TYPE_ARRAY, TYPE_OBJECT, PROPERTY_HINT_RESOURCE_TYPE] # Two-dimensional array of textures. [/gdscript] [csharp] + hintString = $"{Variant.Type.Int:D}:"; // Array of integers. hintString = $"{Variant.Type.Int:D}/{PropertyHint.Range:D}:1,10,1"; // Array of integers (in range from 1 to 10). hintString = $"{Variant.Type.Int:D}/{PropertyHint.Enum:D}:Zero,One,Two"; // Array of integers (an enum). hintString = $"{Variant.Type.Int:D}/{PropertyHint.Enum:D}:Zero,One,Three:3,Six:6"; // Array of integers (an enum). diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index 0c300eade48c..018765f0f357 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -329,12 +329,17 @@ - Export an [int] or [String] property as an enumerated list of options. If the property is an [int], then the index of the value is stored, in the same order the values are provided. You can add explicit values using a colon. If the property is a [String], then the value is stored. - See also [constant PROPERTY_HINT_ENUM]. + Export an [int], [String], [StringName] or [Array] property as an enumerated list of options (or an array of options). If the property is an [int], then the value is stored. By default it's annotation argument index, starting from 0. You can specify explicit values using a colon. If the property is a [String] or [StringName], then the key is stored. If the property is an [Array] then the array of keys or values is stored (the array type must be [code]Array[/code], [code]Array[int][/code], [code]Array[String] or [code]Array[StringName][/code]). + See also [constant PROPERTY_HINT_ENUM] and [constant PROPERTY_HINT_TYPE_STRING]. [codeblock] @export_enum("Warrior", "Magician", "Thief") var character_class: int @export_enum("Slow:30", "Average:60", "Very Fast:200") var character_speed: int @export_enum("Rebecca", "Mary", "Leah") var character_name: String + @export_enum("red", "blue") var character_team: StringName + + @export_enum("Sword", "Spear", "Mace") var character_items: Array[int] + @export_enum("double_jump", "climb", "dash") var character_skills: Array[String] + @export_enum("fast", "nimble", "strong") var character_traits: Array[StringName] [/codeblock] If you want to set an initial value, you must specify it explicitly: [codeblock] @@ -344,7 +349,11 @@ [codeblock] enum CharacterName {REBECCA, MARY, LEAH} @export var character_name: CharacterName + + enum CharacterItem {SWORD, SPEAR, MACE} + @export var character_items: Array[CharacterItem] [/codeblock] + [b]Note:[/b] If the variable is untyped and uninitialized, the value will be stored as an integer. If the variable's type or initializer is an untyped array, then the value will be stored as an array of integers, regardless of the contents of the initializer array. @@ -391,7 +400,7 @@ @export_flags("Self:4", "Allies:8", "Self and Allies:12", "Foes:16") var spell_targets = 0 [/codeblock] - [b]Note:[/b] A flag value must be at least [code]1[/code] and at most [code]2 ** 32 - 1[/code]. + [b]Note:[/b] A flag value must be at least [code]1[/code] and at most [code]2 ** 32 - 1[/code]. This means you can use up to 32 different bit flags. [b]Note:[/b] Unlike [annotation @export_enum], the previous explicit value is not taken into account. In the following example, A is 16, B is 2, C is 4. [codeblock] @export_flags("A:16", "B", "C") var x diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index e17ee0668fda..e13f5da24b8d 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -4058,21 +4058,82 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node } } else if (p_annotation->name == SNAME("@export_enum")) { Variant::Type enum_type = Variant::INT; - - if (export_type.kind == DataType::BUILTIN && export_type.builtin_type == Variant::STRING) { - enum_type = Variant::STRING; + bool has_error = false; + + if (export_type.kind == DataType::BUILTIN) { + switch (export_type.builtin_type) { + case Variant::INT: + case Variant::STRING: + case Variant::STRING_NAME: + case Variant::ARRAY: + enum_type = export_type.builtin_type; + break; + default: + has_error = true; + break; + } } else if (export_type.is_variant() && variable->initializer != nullptr) { DataType initializer_type = variable->initializer->get_datatype(); - if (initializer_type.kind == DataType::BUILTIN && initializer_type.builtin_type == Variant::STRING) { - enum_type = Variant::STRING; + if (initializer_type.kind == DataType::BUILTIN) { + switch (initializer_type.builtin_type) { + case Variant::INT: + case Variant::STRING: + case Variant::STRING_NAME: + case Variant::ARRAY: + enum_type = initializer_type.builtin_type; + break; + default: + break; // No error since it's variant. + } } + } else if (!export_type.is_variant()) { + has_error = true; + } + + if (has_error) { + push_error(vformat(R"("@export_enum" annotation requires a variable of type "int", "String", "StringName" or "Array" but type "%s" was given instead.)", export_type.to_string()), variable); + return false; } variable->export_info.type = enum_type; - if (!export_type.is_variant() && (export_type.kind != DataType::BUILTIN || export_type.builtin_type != enum_type)) { - push_error(vformat(R"("@export_enum" annotation requires a variable of type "int" or "String" but type "%s" was given instead.)", export_type.to_string()), variable); - return false; + if (enum_type == Variant::ARRAY) { + Variant::Type enum_element_type = Variant::INT; + + if (export_type.has_container_element_type()) { + DataType element_type = export_type.get_container_element_type(); + switch (element_type.kind) { + case DataType::VARIANT: + break; // `Array[Variant]` is `Array`. + case DataType::BUILTIN: + switch (element_type.builtin_type) { + case Variant::INT: + case Variant::STRING: + case Variant::STRING_NAME: + enum_element_type = element_type.builtin_type; + break; + default: + has_error = true; + break; + } + break; + default: + has_error = true; + break; + } + } + + if (has_error) { + push_error(vformat(R"("@export_enum": The array type must be "Array", "Array[int]", "Array[String]" or "Array[StringName]" but type "%s" was given instead.)", export_type.to_string()), variable); + return false; + } + + String hint_prefix = itos(enum_element_type); + if (variable->export_info.hint) { + hint_prefix += "/" + itos(variable->export_info.hint); + } + variable->export_info.hint = PROPERTY_HINT_TYPE_STRING; + variable->export_info.hint_string = hint_prefix + ":" + variable->export_info.hint_string; } } else { // Validate variable type with export. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/export_enum_wrong_array_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/export_enum_wrong_array_type.gd new file mode 100644 index 000000000000..9f1a52856dd3 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/export_enum_wrong_array_type.gd @@ -0,0 +1,4 @@ +@export_enum("A", "B", "C") var x: Array[Color] + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/export_enum_wrong_array_type.out b/modules/gdscript/tests/scripts/analyzer/errors/export_enum_wrong_array_type.out new file mode 100644 index 000000000000..fe8b727a3cc3 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/export_enum_wrong_array_type.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +"@export_enum": The array type must be "Array", "Array[int]", "Array[String]" or "Array[StringName]" but type "Array[Color]" was given instead. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/export_enum_wrong_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/export_enum_wrong_type.gd new file mode 100644 index 000000000000..ec7d36f55e3e --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/export_enum_wrong_type.gd @@ -0,0 +1,4 @@ +@export_enum("A", "B", "C") var x: Color + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/export_enum_wrong_type.out b/modules/gdscript/tests/scripts/analyzer/errors/export_enum_wrong_type.out new file mode 100644 index 000000000000..0b695fd53321 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/export_enum_wrong_type.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +"@export_enum" annotation requires a variable of type "int", "String", "StringName" or "Array" but type "Color" was given instead. diff --git a/modules/gdscript/tests/scripts/parser/features/export_enum.gd b/modules/gdscript/tests/scripts/parser/features/export_enum.gd index 9b2c22dea13d..e49bc447e04d 100644 --- a/modules/gdscript/tests/scripts/parser/features/export_enum.gd +++ b/modules/gdscript/tests/scripts/parser/features/export_enum.gd @@ -1,15 +1,31 @@ -@export_enum("Red", "Green", "Blue") var untyped +@export_enum("Red:10", "Green:20", "Blue:30") var with_values + +var temp_array_variant: Array[Variant] +var temp_array_int: Array[int] +var temp_array_string: Array[String] +var temp_array_string_name: Array[StringName] +@export_enum("Red", "Green", "Blue") var weak_variant @export_enum("Red", "Green", "Blue") var weak_int = 0 @export_enum("Red", "Green", "Blue") var weak_string = "" +@export_enum("Red", "Green", "Blue") var weak_string_name = &"" +@export_enum("Red", "Green", "Blue") var weak_array = [] +@export_enum("Red", "Green", "Blue") var weak_array_variant = temp_array_variant +@export_enum("Red", "Green", "Blue") var weak_array_int = temp_array_int +@export_enum("Red", "Green", "Blue") var weak_array_string = temp_array_string +@export_enum("Red", "Green", "Blue") var weak_array_string_name = temp_array_string_name +@export_enum("Red", "Green", "Blue") var hard_variant: Variant @export_enum("Red", "Green", "Blue") var hard_int: int @export_enum("Red", "Green", "Blue") var hard_string: String - -@export_enum("Red:10", "Green:20", "Blue:30") var with_values +@export_enum("Red", "Green", "Blue") var hard_string_name: StringName +@export_enum("Red", "Green", "Blue") var hard_array: Array +@export_enum("Red", "Green", "Blue") var hard_array_variant: Array[Variant] +@export_enum("Red", "Green", "Blue") var hard_array_int: Array[int] +@export_enum("Red", "Green", "Blue") var hard_array_string: Array[String] +@export_enum("Red", "Green", "Blue") var hard_array_string_name: Array[StringName] func test(): for property in get_property_list(): - if property.name in ["untyped", "weak_int", "weak_string", "hard_int", - "hard_string", "with_values"]: + if property.usage & PROPERTY_USAGE_SCRIPT_VARIABLE and not str(property.name).begins_with("temp_"): prints(property.name, property.type, property.hint_string) diff --git a/modules/gdscript/tests/scripts/parser/features/export_enum.out b/modules/gdscript/tests/scripts/parser/features/export_enum.out index 330b7eaf01fb..e2db6b70253d 100644 --- a/modules/gdscript/tests/scripts/parser/features/export_enum.out +++ b/modules/gdscript/tests/scripts/parser/features/export_enum.out @@ -1,7 +1,20 @@ GDTEST_OK -untyped 2 Red,Green,Blue +with_values 2 Red:10,Green:20,Blue:30 +weak_variant 2 Red,Green,Blue weak_int 2 Red,Green,Blue weak_string 4 Red,Green,Blue +weak_string_name 21 Red,Green,Blue +weak_array 28 2/2:Red,Green,Blue +weak_array_variant 28 2/2:Red,Green,Blue +weak_array_int 28 2/2:Red,Green,Blue +weak_array_string 28 4/2:Red,Green,Blue +weak_array_string_name 28 21/2:Red,Green,Blue +hard_variant 2 Red,Green,Blue hard_int 2 Red,Green,Blue hard_string 4 Red,Green,Blue -with_values 2 Red:10,Green:20,Blue:30 +hard_string_name 21 Red,Green,Blue +hard_array 28 2/2:Red,Green,Blue +hard_array_variant 28 2/2:Red,Green,Blue +hard_array_int 28 2/2:Red,Green,Blue +hard_array_string 28 4/2:Red,Green,Blue +hard_array_string_name 28 21/2:Red,Green,Blue