summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanil Alexeev <danil@alexeev.xyz>2023-07-26 18:26:48 +0300
committerDanil Alexeev <danil@alexeev.xyz>2023-07-26 20:36:47 +0300
commit5b4403c9a5650e36feb11e45f91431eacfed4050 (patch)
treea8d22a9d90c599420ec3a3975cbab2a22e3db9b8
parent202e4b2c1e7f8b25738b93d0e4d5066453d3edf3 (diff)
downloadredot-engine-5b4403c9a5650e36feb11e45f91431eacfed4050.tar.gz
GDScript: Add validation for `@export_node_path` annotation arguments
Co-authored-by: George Marques <george@gmarqu.es>
-rw-r--r--modules/gdscript/doc_classes/@GDScript.xml1
-rw-r--r--modules/gdscript/gdscript_editor.cpp18
-rw-r--r--modules/gdscript/gdscript_parser.cpp18
3 files changed, 32 insertions, 5 deletions
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml
index 0c300eade4..9cc1fc0c31 100644
--- a/modules/gdscript/doc_classes/@GDScript.xml
+++ b/modules/gdscript/doc_classes/@GDScript.xml
@@ -533,6 +533,7 @@
[codeblock]
@export_node_path("Button", "TouchScreenButton") var some_button
[/codeblock]
+ [b]Note:[/b] The type must be a native class or a globally registered script (using the [code]class_name[/code] keyword) that inherits [Node].
</description>
</annotation>
<annotation name="@export_placeholder">
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index 2a7346940b..2a0a36a062 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -808,9 +808,10 @@ static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_a
ScriptLanguage::CodeCompletionOption node("Node", ScriptLanguage::CODE_COMPLETION_KIND_CLASS);
node.insert_text = node.display.quote(p_quote_style);
r_result.insert(node.display, node);
- List<StringName> node_types;
- ClassDB::get_inheriters_from_class("Node", &node_types);
- for (const StringName &E : node_types) {
+
+ List<StringName> native_classes;
+ ClassDB::get_inheriters_from_class("Node", &native_classes);
+ for (const StringName &E : native_classes) {
if (!ClassDB::is_class_exposed(E)) {
continue;
}
@@ -818,6 +819,17 @@ static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_a
option.insert_text = option.display.quote(p_quote_style);
r_result.insert(option.display, option);
}
+
+ List<StringName> global_script_classes;
+ ScriptServer::get_global_class_list(&global_script_classes);
+ for (const StringName &E : global_script_classes) {
+ if (!ClassDB::is_parent_class(ScriptServer::get_global_class_native_base(E), "Node")) {
+ continue;
+ }
+ ScriptLanguage::CodeCompletionOption option(E, ScriptLanguage::CODE_COMPLETION_KIND_CLASS);
+ option.insert_text = option.display.quote(p_quote_style);
+ r_result.insert(option.display, option);
+ }
} else if (p_annotation->name == SNAME("@warning_ignore")) {
for (int warning_code = 0; warning_code < GDScriptWarning::WARNING_MAX; warning_code++) {
ScriptLanguage::CodeCompletionOption warning(GDScriptWarning::get_name_from_code((GDScriptWarning::Code)warning_code).to_lower(), ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT);
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index 2b91ba8f86..6197cbdcf4 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -3867,6 +3867,7 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
}
}
+ // WARNING: Do not merge with the previous `if` because there `!=`, not `==`!
if (p_annotation->name == SNAME("@export_flags")) {
const int64_t max_flags = 32;
Vector<String> t = arg_string.split(":", true, 1);
@@ -3892,6 +3893,18 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
push_error(vformat(R"(Invalid argument %d of annotation "@export_flags": Starting from argument %d, the flag value must be specified explicitly.)", i + 1, max_flags + 1), p_annotation->arguments[i]);
return false;
}
+ } else if (p_annotation->name == SNAME("@export_node_path")) {
+ String native_class = arg_string;
+ if (ScriptServer::is_global_class(arg_string)) {
+ native_class = ScriptServer::get_global_class_native_base(arg_string);
+ }
+ if (!ClassDB::class_exists(native_class)) {
+ push_error(vformat(R"(Invalid argument %d of annotation "@export_node_path": The class "%s" was not found in the global scope.)", i + 1, arg_string), p_annotation->arguments[i]);
+ return false;
+ } else if (!ClassDB::is_parent_class(native_class, SNAME("Node"))) {
+ push_error(vformat(R"(Invalid argument %d of annotation "@export_node_path": The class "%s" does not inherit "Node".)", i + 1, arg_string), p_annotation->arguments[i]);
+ return false;
+ }
}
if (i > 0) {
@@ -3909,8 +3922,7 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
if (export_type.builtin_type == Variant::INT) {
variable->export_info.type = Variant::INT;
}
- }
- if (p_annotation->name == SNAME("@export_multiline")) {
+ } else if (p_annotation->name == SNAME("@export_multiline")) {
if (export_type.builtin_type == Variant::ARRAY && export_type.has_container_element_type()) {
DataType inner_type = export_type.get_container_element_type();
if (inner_type.builtin_type != Variant::STRING) {
@@ -3938,6 +3950,8 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
}
}
+ // 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);