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: Fix non-global class export #93384

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
2 changes: 1 addition & 1 deletion modules/gdscript/doc_classes/@GDScript.xml
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@
@export var image_array: Array[Image]
@export var node_array: Array[Node]
[/codeblock]
[b]Note:[/b] Custom resources and nodes must be registered as global classes using [code]class_name[/code].
[b]Note:[/b] Custom resources and nodes should be registered as global classes using [code]class_name[/code], since the Inspector currently only supports global classes. Otherwise, a less specific type will be exported instead.
[b]Note:[/b] Node export is only supported in [Node]-derived classes and has a number of other limitations.
</description>
</annotation>
Expand Down
110 changes: 60 additions & 50 deletions modules/gdscript/gdscript_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4160,6 +4160,64 @@ static String _get_annotation_error_string(const StringName &p_annotation_name,
return vformat(R"("%s" annotation requires a variable of type %s, but type "%s" was given instead.)", p_annotation_name, string, p_provided_type.to_string());
}

static StringName _find_narrowest_native_or_global_class(const GDScriptParser::DataType &p_type) {
akien-mga marked this conversation as resolved.
Show resolved Hide resolved
switch (p_type.kind) {
case GDScriptParser::DataType::NATIVE: {
if (p_type.is_meta_type) {
return Object::get_class_static(); // `GDScriptNativeClass` is not an exposed class.
}
return p_type.native_type;
} break;
case GDScriptParser::DataType::SCRIPT: {
Ref<Script> script;
if (p_type.script_type.is_valid()) {
script = p_type.script_type;
} else {
script = ResourceLoader::load(p_type.script_path, SNAME("Script"));
}

if (p_type.is_meta_type) {
return script.is_valid() ? script->get_class() : Script::get_class_static();
}
if (script.is_null()) {
return p_type.native_type;
}
if (script->get_global_name() != StringName()) {
return script->get_global_name();
}

Ref<Script> base_script = script->get_base_script();
if (base_script.is_null()) {
return script->get_instance_base_type();
}

GDScriptParser::DataType base_type;
base_type.kind = GDScriptParser::DataType::SCRIPT;
base_type.builtin_type = Variant::OBJECT;
base_type.native_type = base_script->get_instance_base_type();
base_type.script_type = base_script;
base_type.script_path = base_script->get_path();

return _find_narrowest_native_or_global_class(base_type);
} break;
case GDScriptParser::DataType::CLASS: {
if (p_type.is_meta_type) {
return GDScript::get_class_static();
}
if (p_type.class_type == nullptr) {
return p_type.native_type;
}
if (p_type.class_type->get_global_name() != StringName()) {
return p_type.class_type->get_global_name();
}
return _find_narrowest_native_or_global_class(p_type.class_type->base_type);
} break;
default: {
ERR_FAIL_V(StringName());
} break;
}
}

template <PropertyHint t_hint, Variant::Type t_type>
bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
ERR_FAIL_COND_V_MSG(p_target->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name));
Expand Down Expand Up @@ -4302,57 +4360,9 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
variable->export_info.hint_string = String();
break;
case GDScriptParser::DataType::NATIVE:
if (ClassDB::is_parent_class(export_type.native_type, SNAME("Resource"))) {
variable->export_info.type = Variant::OBJECT;
variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE;
variable->export_info.hint_string = export_type.native_type;
} else if (ClassDB::is_parent_class(export_type.native_type, SNAME("Node"))) {
variable->export_info.type = Variant::OBJECT;
variable->export_info.hint = PROPERTY_HINT_NODE_TYPE;
variable->export_info.hint_string = export_type.native_type;
} else {
push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", p_annotation);
return false;
}
break;
case GDScriptParser::DataType::SCRIPT:
case GDScriptParser::DataType::CLASS: {
StringName class_name;
if (export_type.class_type) {
class_name = export_type.class_type->get_global_name();
}
if (class_name == StringName()) {
push_error(R"(Script export type must be a global class.)", p_annotation);
return false;
}
if (ClassDB::is_parent_class(export_type.native_type, SNAME("Resource"))) {
variable->export_info.type = Variant::OBJECT;
variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE;
variable->export_info.hint_string = class_name;
} else if (ClassDB::is_parent_class(export_type.native_type, SNAME("Node"))) {
variable->export_info.type = Variant::OBJECT;
variable->export_info.hint = PROPERTY_HINT_NODE_TYPE;
variable->export_info.hint_string = class_name;
} else {
push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", p_annotation);
return false;
}
} break;

case GDScriptParser::DataType::SCRIPT: {
StringName class_name;
if (export_type.script_type.is_valid()) {
class_name = export_type.script_type->get_global_name();
}
if (class_name == StringName()) {
Ref<Script> script = ResourceLoader::load(export_type.script_path, SNAME("Script"));
if (script.is_valid()) {
class_name = script->get_global_name();
}
}
if (class_name == StringName()) {
push_error(R"(Script export type must be a global class.)", p_annotation);
return false;
}
const StringName class_name = _find_narrowest_native_or_global_class(export_type);
if (ClassDB::is_parent_class(export_type.native_type, SNAME("Resource"))) {
variable->export_info.type = Variant::OBJECT;
variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ class_name ExportVariableTest
extends Node

const Utils = preload("../../utils.notest.gd")
const PreloadedScript = preload("./export_variable.notest.gd")
const PreloadedGlobalClass = preload("./export_variable_global.notest.gd")
const PreloadedUnnamedClass = preload("./export_variable_unnamed.notest.gd")

# Built-in types.
@export var test_weak_int = 1
Expand All @@ -24,7 +25,8 @@ const PreloadedScript = preload("./export_variable.notest.gd")

# Global custom classes.
@export var test_global_class: ExportVariableTest
@export var test_preloaded_script: PreloadedScript
@export var test_preloaded_global_class: PreloadedGlobalClass
@export var test_preloaded_unnamed_class: PreloadedUnnamedClass # GH-93168

# Arrays.
@export var test_array: Array
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ var test_timer: Timer = null
hint=NODE_TYPE hint_string="Timer" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"Timer"
var test_global_class: ExportVariableTest = null
hint=NODE_TYPE hint_string="ExportVariableTest" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"ExportVariableTest"
var test_preloaded_script: ExportPreloadedClassTest = null
hint=NODE_TYPE hint_string="ExportPreloadedClassTest" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"ExportPreloadedClassTest"
var test_preloaded_global_class: ExportVariableTestGlobalClass = null
hint=NODE_TYPE hint_string="ExportVariableTestGlobalClass" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"ExportVariableTestGlobalClass"
var test_preloaded_unnamed_class: Node2D = null
hint=NODE_TYPE hint_string="Node2D" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"Node2D"
var test_array: Array = []
hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_array_bool: Array = Array[bool]([])
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class_name ExportVariableTestGlobalClass
extends Node2D
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
extends Node2D
Loading