Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GDScript: Add @export_storage annotation #82122

Merged
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
11 changes: 11 additions & 0 deletions modules/gdscript/doc_classes/@GDScript.xml
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,17 @@
[/codeblock]
</description>
</annotation>
<annotation name="@export_storage">
<return type="void" />
<description>
Export a property with [constant PROPERTY_USAGE_STORAGE] flag. The property is not displayed in the editor, but it is serialized and stored in the scene or resource file. This can be useful for [annotation @tool] scripts. Also the property value is copied when [method Resource.duplicate] or [method Node.duplicate] is called, unlike non-exported variables.
[codeblock]
var a # Not stored in the file, not displayed in the editor.
@export_storage var b # Stored in the file, not displayed in the editor.
@export var c: int # Stored in the file, displayed in the editor.
[/codeblock]
</description>
</annotation>
<annotation name="@export_subgroup">
<return type="void" />
<param index="0" name="name" type="String" />
Expand Down
21 changes: 15 additions & 6 deletions modules/gdscript/gdscript_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ GDScriptParser::GDScriptParser() {
register_annotation(MethodInfo("@onready"), AnnotationInfo::VARIABLE, &GDScriptParser::onready_annotation);
// Export annotations.
register_annotation(MethodInfo("@export"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NONE, Variant::NIL>);
register_annotation(MethodInfo("@export_storage"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NONE, Variant::NIL>);
register_annotation(MethodInfo("@export_enum", PropertyInfo(Variant::STRING, "names")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_ENUM, Variant::NIL>, varray(), true);
register_annotation(MethodInfo("@export_file", PropertyInfo(Variant::STRING, "filter")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_FILE, Variant::STRING>, varray(""), true);
register_annotation(MethodInfo("@export_dir"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_DIR, Variant::STRING>);
Expand Down Expand Up @@ -3996,11 +3997,11 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
}
hint_string += arg_string;
}

variable->export_info.hint_string = hint_string;

// This is called after the analyzer is done finding the type, so this should be set here.
DataType export_type = variable->get_datatype();
bool use_default_variable_type_check = true;
YuriSizov marked this conversation as resolved.
Show resolved Hide resolved

if (p_annotation->name == SNAME("@export_range")) {
if (export_type.builtin_type == Variant::INT) {
Expand Down Expand Up @@ -4032,11 +4033,9 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node

return true;
}
}
} else if (p_annotation->name == SNAME("@export")) {
use_default_variable_type_check = false;

// WARNING: Do not merge with the previous `else if`! Otherwise `else` (default variable type check)
// will not work for the above annotations. `@export` and `@export_enum` validate the type separately.
if (p_annotation->name == SNAME("@export")) {
if (variable->datatype_specifier == nullptr && variable->initializer == nullptr) {
push_error(R"(Cannot use simple "@export" annotation with variable without type or initializer, since type can't be inferred.)", p_annotation);
return false;
Expand Down Expand Up @@ -4154,6 +4153,8 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
variable->export_info.type = Variant::ARRAY;
}
} else if (p_annotation->name == SNAME("@export_enum")) {
use_default_variable_type_check = false;

Variant::Type enum_type = Variant::INT;

if (export_type.kind == DataType::BUILTIN && export_type.builtin_type == Variant::STRING) {
Expand All @@ -4171,7 +4172,15 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
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;
}
} else {
} else if (p_annotation->name == SNAME("@export_storage")) {
use_default_variable_type_check = false; // Can be applied to a variable of any type.

// Save the info because the compiler uses export info for overwriting member info.
variable->export_info = export_type.to_property_info(variable->identifier->name);
variable->export_info.usage |= PROPERTY_USAGE_STORAGE;
}

if (use_default_variable_type_check) {
// Validate variable type with export.
if (!export_type.is_variant() && (export_type.kind != DataType::BUILTIN || export_type.builtin_type != t_type)) {
// Allow float/int conversion.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@ 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
@export var test_1 = MyEnum
@export var test_2 = MyEnum.A
@export var test_3 := MyEnum
@export var test_4 := MyEnum.A
@export var test_5: 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))
if str(property.name).begins_with("test_"):
Utils.print_property_extended_info(property)
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
GDTEST_OK
@export var x1: Dictionary
var test_1: Dictionary
hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE
@export var x2: TestExportEnumAsDictionary.MyEnum
var test_2: TestExportEnumAsDictionary.MyEnum
hint=ENUM hint_string="A:0,B:1,C:2" usage=DEFAULT|SCRIPT_VARIABLE|CLASS_IS_ENUM
@export var x3: Dictionary
var test_3: Dictionary
hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE
@export var x4: TestExportEnumAsDictionary.MyEnum
var test_4: TestExportEnumAsDictionary.MyEnum
hint=ENUM hint_string="A:0,B:1,C:2" usage=DEFAULT|SCRIPT_VARIABLE|CLASS_IS_ENUM
@export var x5: TestExportEnumAsDictionary.MyEnum
var test_5: TestExportEnumAsDictionary.MyEnum
hint=ENUM hint_string="A:0,B:1,C:2" usage=DEFAULT|SCRIPT_VARIABLE|CLASS_IS_ENUM
31 changes: 16 additions & 15 deletions modules/gdscript/tests/scripts/parser/features/annotations.gd
Original file line number Diff line number Diff line change
@@ -1,48 +1,49 @@
extends Node

@export_enum("A", "B", "C") var a0
@export_enum("A", "B", "C",) var a1
const Utils = preload("../../utils.notest.gd")

@export_enum("A", "B", "C") var test_1
@export_enum("A", "B", "C",) var test_2

@export_enum(
"A",
"B",
"C"
) var a2
) var test_3

@export_enum(
"A",
"B",
"C",
) var a3
) var test_4

@export
var a4: int
var test_5: int

@export()
var a5: int
var test_6: int

@export() var a6: int
@warning_ignore("onready_with_export") @onready @export var a7: int
@warning_ignore("onready_with_export") @onready() @export() var a8: int
@export() var test_7: int = 42
@warning_ignore("onready_with_export") @onready @export var test_8: int = 42
@warning_ignore("onready_with_export") @onready() @export() var test_9: int = 42

@warning_ignore("onready_with_export")
@onready
@export
var a9: int
var test_10: int = 42

@warning_ignore("onready_with_export")
@onready()
@export()
var a10: int
var test_11: int = 42

@warning_ignore("onready_with_export")
@onready()
@export()

var a11: int

var test_12: int = 42

func test():
for property in get_property_list():
if property.usage & PROPERTY_USAGE_SCRIPT_VARIABLE:
print(property)
if str(property.name).begins_with("test_"):
Utils.print_property_extended_info(property, self)
36 changes: 24 additions & 12 deletions modules/gdscript/tests/scripts/parser/features/annotations.out
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
GDTEST_OK
{ "name": "a0", "class_name": &"", "type": 2, "hint": 2, "hint_string": "A,B,C", "usage": 4102 }
{ "name": "a1", "class_name": &"", "type": 2, "hint": 2, "hint_string": "A,B,C", "usage": 4102 }
{ "name": "a2", "class_name": &"", "type": 2, "hint": 2, "hint_string": "A,B,C", "usage": 4102 }
{ "name": "a3", "class_name": &"", "type": 2, "hint": 2, "hint_string": "A,B,C", "usage": 4102 }
{ "name": "a4", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 }
{ "name": "a5", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 }
{ "name": "a6", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 }
{ "name": "a7", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 }
{ "name": "a8", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 }
{ "name": "a9", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 }
{ "name": "a10", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 }
{ "name": "a11", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 }
var test_1: int = null
hint=ENUM hint_string="A,B,C" usage=DEFAULT|SCRIPT_VARIABLE
var test_2: int = null
hint=ENUM hint_string="A,B,C" usage=DEFAULT|SCRIPT_VARIABLE
var test_3: int = null
hint=ENUM hint_string="A,B,C" usage=DEFAULT|SCRIPT_VARIABLE
var test_4: int = null
hint=ENUM hint_string="A,B,C" usage=DEFAULT|SCRIPT_VARIABLE
var test_5: int = 0
hint=NONE hint_string="int" usage=DEFAULT|SCRIPT_VARIABLE
var test_6: int = 0
hint=NONE hint_string="int" usage=DEFAULT|SCRIPT_VARIABLE
var test_7: int = 42
hint=NONE hint_string="int" usage=DEFAULT|SCRIPT_VARIABLE
var test_8: int = 0
hint=NONE hint_string="int" usage=DEFAULT|SCRIPT_VARIABLE
var test_9: int = 0
hint=NONE hint_string="int" usage=DEFAULT|SCRIPT_VARIABLE
var test_10: int = 0
hint=NONE hint_string="int" usage=DEFAULT|SCRIPT_VARIABLE
var test_11: int = 0
hint=NONE hint_string="int" usage=DEFAULT|SCRIPT_VARIABLE
var test_12: int = 0
hint=NONE hint_string="int" usage=DEFAULT|SCRIPT_VARIABLE
19 changes: 10 additions & 9 deletions modules/gdscript/tests/scripts/parser/features/export_enum.gd
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
@export_enum("Red", "Green", "Blue") var untyped
const Utils = preload("../../utils.notest.gd")

@export_enum("Red", "Green", "Blue") var weak_int = 0
@export_enum("Red", "Green", "Blue") var weak_string = ""
@export_enum("Red", "Green", "Blue") var test_untyped

@export_enum("Red", "Green", "Blue") var hard_int: int
@export_enum("Red", "Green", "Blue") var hard_string: String
@export_enum("Red", "Green", "Blue") var test_weak_int = 0
@export_enum("Red", "Green", "Blue") var test_weak_string = ""

@export_enum("Red:10", "Green:20", "Blue:30") var with_values
@export_enum("Red", "Green", "Blue") var test_hard_int: int
@export_enum("Red", "Green", "Blue") var test_hard_string: String

@export_enum("Red:10", "Green:20", "Blue:30") var test_with_values

func test():
for property in get_property_list():
if property.name in ["untyped", "weak_int", "weak_string", "hard_int",
"hard_string", "with_values"]:
prints(property.name, property.type, property.hint_string)
if str(property.name).begins_with("test_"):
Utils.print_property_extended_info(property, self)
18 changes: 12 additions & 6 deletions modules/gdscript/tests/scripts/parser/features/export_enum.out
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
GDTEST_OK
untyped 2 Red,Green,Blue
weak_int 2 Red,Green,Blue
weak_string 4 Red,Green,Blue
hard_int 2 Red,Green,Blue
hard_string 4 Red,Green,Blue
with_values 2 Red:10,Green:20,Blue:30
var test_untyped: int = null
hint=ENUM hint_string="Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE
var test_weak_int: int = 0
hint=ENUM hint_string="Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE
var test_weak_string: String = ""
hint=ENUM hint_string="Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE
var test_hard_int: int = 0
hint=ENUM hint_string="Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE
var test_hard_string: String = ""
hint=ENUM hint_string="Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE
var test_with_values: int = null
hint=ENUM hint_string="Red:10,Green:20,Blue:30" usage=DEFAULT|SCRIPT_VARIABLE
35 changes: 17 additions & 18 deletions modules/gdscript/tests/scripts/parser/features/export_variable.gd
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
extends Node

@export var example = 99
@export_range(0, 100) var example_range = 100
@export_range(0, 100, 1) var example_range_step = 101
@export_range(0, 100, 1, "or_greater") var example_range_step_or_greater = 102
const Utils = preload("../../utils.notest.gd")

@export var color: Color
@export_color_no_alpha var color_no_alpha: Color
@export_node_path("Sprite2D", "Sprite3D", "Control", "Node") var nodepath := ^"hello"
@export var node: Node
@export var node_array: Array[Node]
@export var test_weak_int = 1
@export var test_hard_int: int = 2
@export_storage var test_storage_untyped
@export_storage var test_storage_weak_int = 3 # Property info still `Variant`, unlike `@export`.
@export_storage var test_storage_hard_int: int = 4
@export_range(0, 100) var test_range = 100
@export_range(0, 100, 1) var test_range_step = 101
@export_range(0, 100, 1, "or_greater") var test_range_step_or_greater = 102
@export var test_color: Color
@export_color_no_alpha var test_color_no_alpha: Color
@export_node_path("Sprite2D", "Sprite3D", "Control", "Node") var test_node_path := ^"hello"
@export var test_node: Node
@export var test_node_array: Array[Node]

func test():
print(example)
print(example_range)
print(example_range_step)
print(example_range_step_or_greater)
print(color)
print(color_no_alpha)
print(nodepath)
print(node)
print(var_to_str(node_array))
for property in get_property_list():
if str(property.name).begins_with("test_"):
Utils.print_property_extended_info(property, self)
Original file line number Diff line number Diff line change
@@ -1,10 +1,27 @@
GDTEST_OK
99
100
101
102
(0, 0, 0, 1)
(0, 0, 0, 1)
hello
<null>
Array[Node]([])
var test_weak_int: int = 1
hint=NONE hint_string="int" usage=DEFAULT|SCRIPT_VARIABLE
var test_hard_int: int = 2
hint=NONE hint_string="int" usage=DEFAULT|SCRIPT_VARIABLE
var test_storage_untyped: Variant = null
hint=NONE hint_string="" usage=STORAGE|SCRIPT_VARIABLE|NIL_IS_VARIANT
var test_storage_weak_int: Variant = 3
hint=NONE hint_string="" usage=STORAGE|SCRIPT_VARIABLE|NIL_IS_VARIANT
var test_storage_hard_int: int = 4
hint=NONE hint_string="" usage=STORAGE|SCRIPT_VARIABLE
var test_range: int = 100
hint=RANGE hint_string="0,100" usage=DEFAULT|SCRIPT_VARIABLE
var test_range_step: int = 101
hint=RANGE hint_string="0,100,1" usage=DEFAULT|SCRIPT_VARIABLE
var test_range_step_or_greater: int = 102
hint=RANGE hint_string="0,100,1,or_greater" usage=DEFAULT|SCRIPT_VARIABLE
var test_color: Color = Color(0, 0, 0, 1)
hint=NONE hint_string="Color" usage=DEFAULT|SCRIPT_VARIABLE
var test_color_no_alpha: Color = Color(0, 0, 0, 1)
hint=COLOR_NO_ALPHA hint_string="" usage=DEFAULT|SCRIPT_VARIABLE
var test_node_path: NodePath = NodePath("hello")
hint=NODE_PATH_VALID_TYPES hint_string="Sprite2D,Sprite3D,Control,Node" usage=DEFAULT|SCRIPT_VARIABLE
var test_node: Node = null
hint=NODE_TYPE hint_string="Node" usage=DEFAULT|SCRIPT_VARIABLE
var test_node_array: Array = Array[Node]([])
hint=TYPE_STRING hint_string="24/34:Node" usage=DEFAULT|SCRIPT_VARIABLE
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
extends RefCounted # TODO: Fix standalone annotations parsing.
const Utils = preload("../../utils.notest.gd")

# GH-73843
@export_group("Resource")

# GH-78252
@export var prop_1: int
@export_category("prop_1")
@export var prop_2: int
@export var test_1: int
@export_category("test_1")
@export var test_2: int

func test():
var resource := Resource.new()
prints("Not shadowed:", resource.get_class())

for property in get_property_list():
if property.name in ["prop_1", "prop_2"]:
print(property)
if str(property.name).begins_with("test_"):
Utils.print_property_extended_info(property, self)
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
GDTEST_OK
Not shadowed: Resource
{ "name": "prop_1", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 }
{ "name": "prop_1", "class_name": &"", "type": 0, "hint": 0, "hint_string": "", "usage": 128 }
{ "name": "prop_2", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 }
var test_1: int = 0
hint=NONE hint_string="int" usage=DEFAULT|SCRIPT_VARIABLE
@export_category("test_1")
hint=NONE hint_string="" usage=CATEGORY
var test_2: int = 0
hint=NONE hint_string="int" usage=DEFAULT|SCRIPT_VARIABLE
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func test():
var script: Script = get_script()
for property in script.get_property_list():
if str(property.name).begins_with("test_"):
print(Utils.get_property_signature(property, true))
print(Utils.get_property_signature(property, null, true))
for property in get_property_list():
if str(property.name).begins_with("test_"):
print(Utils.get_property_signature(property))
Expand Down
Loading
Loading