summaryrefslogtreecommitdiff
path: root/modules/gdscript
diff options
context:
space:
mode:
Diffstat (limited to 'modules/gdscript')
-rw-r--r--modules/gdscript/doc_classes/@GDScript.xml4
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.cpp4
-rw-r--r--modules/gdscript/editor/gdscript_translation_parser_plugin.cpp376
-rw-r--r--modules/gdscript/editor/gdscript_translation_parser_plugin.h33
-rw-r--r--modules/gdscript/gdscript.cpp13
-rw-r--r--modules/gdscript/gdscript.h1
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp316
-rw-r--r--modules/gdscript/gdscript_analyzer.h4
-rw-r--r--modules/gdscript/gdscript_cache.cpp29
-rw-r--r--modules/gdscript/gdscript_cache.h6
-rw-r--r--modules/gdscript/gdscript_compiler.cpp29
-rw-r--r--modules/gdscript/gdscript_editor.cpp47
-rw-r--r--modules/gdscript/gdscript_parser.cpp137
-rw-r--r--modules/gdscript/gdscript_parser.h14
-rw-r--r--modules/gdscript/gdscript_tokenizer.cpp58
-rw-r--r--modules/gdscript/gdscript_tokenizer.h6
-rw-r--r--modules/gdscript/language_server/gdscript_extend_parser.cpp2
-rw-r--r--modules/gdscript/register_types.cpp4
18 files changed, 798 insertions, 285 deletions
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml
index 9e40a69712..e528fc6623 100644
--- a/modules/gdscript/doc_classes/@GDScript.xml
+++ b/modules/gdscript/doc_classes/@GDScript.xml
@@ -167,6 +167,7 @@
i = ceil(1.45) # i is 2
i = ceil(1.001) # i is 2
[/codeblock]
+ See also [method floor], [method round], and [method stepify].
</description>
</method>
<method name="char">
@@ -338,6 +339,7 @@
# a is -3.0
a = floor(-2.99)
[/codeblock]
+ See also [method ceil], [method round], and [method stepify].
[b]Note:[/b] This method returns a float. If you need an integer, you can use [code]int(s)[/code] directly.
</description>
</method>
@@ -1043,6 +1045,7 @@
[codeblock]
round(2.6) # Returns 3
[/codeblock]
+ See also [method floor], [method ceil], and [method stepify].
</description>
</method>
<method name="seed">
@@ -1161,6 +1164,7 @@
stepify(100, 32) # Returns 96
stepify(3.14159, 0.01) # Returns 3.14
[/codeblock]
+ See also [method ceil], [method floor], and [method round].
</description>
</method>
<method name="str" qualifiers="vararg">
diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp
index aba3e07134..ae1f2893f1 100644
--- a/modules/gdscript/editor/gdscript_highlighter.cpp
+++ b/modules/gdscript/editor/gdscript_highlighter.cpp
@@ -82,6 +82,10 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting(int p_line)
const String &str = text_edit->get_line(p_line);
const int line_length = str.length();
Color prev_color;
+
+ if (in_region != -1 && str.length() == 0) {
+ color_region_cache[p_line] = in_region;
+ }
for (int j = 0; j < str.length(); j++) {
Dictionary highlighter_info;
diff --git a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp
index 6d454e43f2..944ed859f5 100644
--- a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp
+++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp
@@ -37,9 +37,11 @@ void GDScriptEditorTranslationParserPlugin::get_recognized_extensions(List<Strin
GDScriptLanguage::get_singleton()->get_recognized_extensions(r_extensions);
}
-Error GDScriptEditorTranslationParserPlugin::parse_file(const String &p_path, Vector<String> *r_extracted_strings) {
- // Parse and match all GDScript function API that involves translation string.
- // E.g get_node("Label").text = "something", var test = tr("something"), "something" will be matched and collected.
+Error GDScriptEditorTranslationParserPlugin::parse_file(const String &p_path, Vector<String> *r_ids, Vector<Vector<String>> *r_ids_ctx_plural) {
+ // Extract all translatable strings using the parsed tree from GDSriptParser.
+ // The strategy is to find all ExpressionNode and AssignmentNode from the tree and extract strings if relevant, i.e
+ // Search strings in ExpressionNode -> CallNode -> tr(), set_text(), set_placeholder() etc.
+ // Search strings in AssignmentNode -> text = "__", hint_tooltip = "__" etc.
Error err;
RES loaded_res = ResourceLoader::load(p_path, "", false, &err);
@@ -48,108 +50,302 @@ Error GDScriptEditorTranslationParserPlugin::parse_file(const String &p_path, Ve
return err;
}
+ ids = r_ids;
+ ids_ctx_plural = r_ids_ctx_plural;
Ref<GDScript> gdscript = loaded_res;
String source_code = gdscript->get_source_code();
- Vector<String> parsed_strings;
-
- // Search translation strings with RegEx.
- regex.clear();
- regex.compile(String("|").join(patterns));
- Array results = regex.search_all(source_code);
- _get_captured_strings(results, &parsed_strings);
-
- // Special handling for FileDialog.
- Vector<String> temp;
- _parse_file_dialog(source_code, &temp);
- parsed_strings.append_array(temp);
-
- // Filter out / and +
- String filter = "(?:\\\\\\n|\"[\\s\\\\]*\\+\\s*\")";
- regex.clear();
- regex.compile(filter);
- for (int i = 0; i < parsed_strings.size(); i++) {
- parsed_strings.set(i, regex.sub(parsed_strings[i], "", true));
+
+ GDScriptParser parser;
+ err = parser.parse(source_code, p_path, false);
+ if (err != OK) {
+ ERR_PRINT("Failed to parse with GDScript with GDScriptParser.");
+ return err;
}
- r_extracted_strings->append_array(parsed_strings);
+ // Traverse through the parsed tree from GDScriptParser.
+ GDScriptParser::ClassNode *c = parser.get_tree();
+ _traverse_class(c);
return OK;
}
-void GDScriptEditorTranslationParserPlugin::_parse_file_dialog(const String &p_source_code, Vector<String> *r_output) {
- // FileDialog API has the form .filters = PackedStringArray(["*.png ; PNG Images","*.gd ; GDScript Files"]).
- // First filter: Get "*.png ; PNG Images", "*.gd ; GDScript Files" from PackedStringArray.
- regex.clear();
- regex.compile(String("|").join(file_dialog_patterns));
- Array results = regex.search_all(p_source_code);
-
- Vector<String> temp;
- _get_captured_strings(results, &temp);
- String captured_strings = String(",").join(temp);
-
- // Second filter: Get the texts after semicolon from "*.png ; PNG Images","*.gd ; GDScript Files".
- String second_filter = "\"[^;]+;" + text + "\"";
- regex.clear();
- regex.compile(second_filter);
- results = regex.search_all(captured_strings);
- _get_captured_strings(results, r_output);
- for (int i = 0; i < r_output->size(); i++) {
- r_output->set(i, r_output->get(i).strip_edges());
+void GDScriptEditorTranslationParserPlugin::_traverse_class(const GDScriptParser::ClassNode *p_class) {
+ for (int i = 0; i < p_class->members.size(); i++) {
+ const GDScriptParser::ClassNode::Member &m = p_class->members[i];
+ // There are 7 types of Member, but only class, function and variable can contain translatable strings.
+ switch (m.type) {
+ case GDScriptParser::ClassNode::Member::CLASS:
+ _traverse_class(m.m_class);
+ break;
+ case GDScriptParser::ClassNode::Member::FUNCTION:
+ _traverse_function(m.function);
+ break;
+ case GDScriptParser::ClassNode::Member::VARIABLE:
+ _read_variable(m.variable);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void GDScriptEditorTranslationParserPlugin::_traverse_function(const GDScriptParser::FunctionNode *p_func) {
+ _traverse_block(p_func->body);
+}
+
+void GDScriptEditorTranslationParserPlugin::_read_variable(const GDScriptParser::VariableNode *p_var) {
+ _assess_expression(p_var->initializer);
+}
+
+void GDScriptEditorTranslationParserPlugin::_traverse_block(const GDScriptParser::SuiteNode *p_suite) {
+ if (!p_suite) {
+ return;
+ }
+
+ const Vector<GDScriptParser::Node *> &statements = p_suite->statements;
+ for (int i = 0; i < statements.size(); i++) {
+ GDScriptParser::Node *statement = statements[i];
+
+ // Statements with Node type constant, break, continue, pass, breakpoint are skipped because they can't contain translatable strings.
+ switch (statement->type) {
+ case GDScriptParser::Node::VARIABLE:
+ _assess_expression(static_cast<GDScriptParser::VariableNode *>(statement)->initializer);
+ break;
+ case GDScriptParser::Node::IF: {
+ GDScriptParser::IfNode *if_node = static_cast<GDScriptParser::IfNode *>(statement);
+ _assess_expression(if_node->condition);
+ //FIXME : if the elif logic is changed in GDScriptParser, then this probably will have to change as well. See GDScriptParser::TreePrinter::print_if().
+ _traverse_block(if_node->true_block);
+ _traverse_block(if_node->false_block);
+ break;
+ }
+ case GDScriptParser::Node::FOR: {
+ GDScriptParser::ForNode *for_node = static_cast<GDScriptParser::ForNode *>(statement);
+ _assess_expression(for_node->list);
+ _traverse_block(for_node->loop);
+ break;
+ }
+ case GDScriptParser::Node::WHILE: {
+ GDScriptParser::WhileNode *while_node = static_cast<GDScriptParser::WhileNode *>(statement);
+ _assess_expression(while_node->condition);
+ _traverse_block(while_node->loop);
+ break;
+ }
+ case GDScriptParser::Node::MATCH: {
+ GDScriptParser::MatchNode *match_node = static_cast<GDScriptParser::MatchNode *>(statement);
+ _assess_expression(match_node->test);
+ for (int j = 0; j < match_node->branches.size(); j++) {
+ _traverse_block(match_node->branches[j]->block);
+ }
+ break;
+ }
+ case GDScriptParser::Node::RETURN:
+ _assess_expression(static_cast<GDScriptParser::ReturnNode *>(statement)->return_value);
+ break;
+ case GDScriptParser::Node::ASSERT:
+ _assess_expression((static_cast<GDScriptParser::AssertNode *>(statement))->condition);
+ break;
+ case GDScriptParser::Node::ASSIGNMENT:
+ _assess_assignment(static_cast<GDScriptParser::AssignmentNode *>(statement));
+ break;
+ default:
+ if (statement->is_expression()) {
+ _assess_expression(static_cast<GDScriptParser::ExpressionNode *>(statement));
+ }
+ break;
+ }
+ }
+}
+
+void GDScriptEditorTranslationParserPlugin::_assess_expression(GDScriptParser::ExpressionNode *p_expression) {
+ // Explore all ExpressionNodes to find CallNodes which contain translation strings, such as tr(), set_text() etc.
+ // tr() can be embedded quite deep within multiple ExpressionNodes so need to dig down to search through all ExpressionNodes.
+ if (!p_expression) {
+ return;
+ }
+
+ // ExpressionNode of type await, cast, get_node, identifier, literal, preload, self, subscript, unary are ignored as they can't be CallNode
+ // containing translation strings.
+ switch (p_expression->type) {
+ case GDScriptParser::Node::ARRAY: {
+ GDScriptParser::ArrayNode *array_node = static_cast<GDScriptParser::ArrayNode *>(p_expression);
+ for (int i = 0; i < array_node->elements.size(); i++) {
+ _assess_expression(array_node->elements[i]);
+ }
+ break;
+ }
+ case GDScriptParser::Node::ASSIGNMENT:
+ _assess_assignment(static_cast<GDScriptParser::AssignmentNode *>(p_expression));
+ break;
+ case GDScriptParser::Node::BINARY_OPERATOR: {
+ GDScriptParser::BinaryOpNode *binary_op_node = static_cast<GDScriptParser::BinaryOpNode *>(p_expression);
+ _assess_expression(binary_op_node->left_operand);
+ _assess_expression(binary_op_node->right_operand);
+ break;
+ }
+ case GDScriptParser::Node::CALL: {
+ GDScriptParser::CallNode *call_node = static_cast<GDScriptParser::CallNode *>(p_expression);
+ _extract_from_call(call_node);
+ for (int i = 0; i < call_node->arguments.size(); i++) {
+ _assess_expression(call_node->arguments[i]);
+ }
+ } break;
+ case GDScriptParser::Node::DICTIONARY: {
+ GDScriptParser::DictionaryNode *dict_node = static_cast<GDScriptParser::DictionaryNode *>(p_expression);
+ for (int i = 0; i < dict_node->elements.size(); i++) {
+ _assess_expression(dict_node->elements[i].key);
+ _assess_expression(dict_node->elements[i].value);
+ }
+ break;
+ }
+ case GDScriptParser::Node::TERNARY_OPERATOR: {
+ GDScriptParser::TernaryOpNode *ternary_op_node = static_cast<GDScriptParser::TernaryOpNode *>(p_expression);
+ _assess_expression(ternary_op_node->condition);
+ _assess_expression(ternary_op_node->true_expr);
+ _assess_expression(ternary_op_node->false_expr);
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+void GDScriptEditorTranslationParserPlugin::_assess_assignment(GDScriptParser::AssignmentNode *p_assignment) {
+ // Extract the translatable strings coming from assignments. For example, get_node("Label").text = "____"
+
+ StringName assignee_name;
+ if (p_assignment->assignee->type == GDScriptParser::Node::IDENTIFIER) {
+ assignee_name = static_cast<GDScriptParser::IdentifierNode *>(p_assignment->assignee)->name;
+ } else if (p_assignment->assignee->type == GDScriptParser::Node::SUBSCRIPT) {
+ assignee_name = static_cast<GDScriptParser::SubscriptNode *>(p_assignment->assignee)->attribute->name;
+ }
+
+ if (assignment_patterns.has(assignee_name) && p_assignment->assigned_value->type == GDScriptParser::Node::LITERAL) {
+ // If the assignment is towards one of the extract patterns (text, hint_tooltip etc.), and the value is a string literal, we collect the string.
+ ids->push_back(static_cast<GDScriptParser::LiteralNode *>(p_assignment->assigned_value)->value);
+ } else if (assignee_name == fd_filters && p_assignment->assigned_value->type == GDScriptParser::Node::CALL) {
+ // FileDialog.filters accepts assignment in the form of PackedStringArray. For example,
+ // get_node("FileDialog").filters = PackedStringArray(["*.png ; PNG Images","*.gd ; GDScript Files"]).
+
+ GDScriptParser::CallNode *call_node = static_cast<GDScriptParser::CallNode *>(p_assignment->assigned_value);
+ if (call_node->arguments[0]->type == GDScriptParser::Node::ARRAY) {
+ GDScriptParser::ArrayNode *array_node = static_cast<GDScriptParser::ArrayNode *>(call_node->arguments[0]);
+
+ // Extract the name in "extension ; name" of PackedStringArray.
+ for (int i = 0; i < array_node->elements.size(); i++) {
+ _extract_fd_literals(array_node->elements[i]);
+ }
+ }
+ } else {
+ // If the assignee is not in extract patterns or the assigned_value is not Literal type, try to see if the assigned_value contains tr().
+ _assess_expression(p_assignment->assigned_value);
}
}
-void GDScriptEditorTranslationParserPlugin::_get_captured_strings(const Array &p_results, Vector<String> *r_output) {
- Ref<RegExMatch> result;
- for (int i = 0; i < p_results.size(); i++) {
- result = p_results[i];
- for (int j = 0; j < result->get_group_count(); j++) {
- String s = result->get_string(j + 1);
- // Prevent reading text with only spaces.
- if (!s.strip_edges().empty()) {
- r_output->push_back(s);
+void GDScriptEditorTranslationParserPlugin::_extract_from_call(GDScriptParser::CallNode *p_call) {
+ // Extract the translatable strings coming from function calls. For example:
+ // tr("___"), get_node("Label").set_text("____"), get_node("LineEdit").set_placeholder("____").
+
+ StringName function_name = p_call->function_name;
+
+ // Variables for extracting tr() and tr_n().
+ Vector<String> id_ctx_plural;
+ id_ctx_plural.resize(3);
+ bool extract_id_ctx_plural = true;
+
+ if (function_name == tr_func) {
+ // Extract from tr(id, ctx).
+ for (int i = 0; i < p_call->arguments.size(); i++) {
+ if (p_call->arguments[i]->type == GDScriptParser::Node::LITERAL) {
+ id_ctx_plural.write[i] = static_cast<GDScriptParser::LiteralNode *>(p_call->arguments[i])->value;
+ } else {
+ // Avoid adding something like tr("Flying dragon", var_context_level_1). We want to extract both id and context together.
+ extract_id_ctx_plural = false;
+ }
+ }
+ if (extract_id_ctx_plural) {
+ ids_ctx_plural->push_back(id_ctx_plural);
+ }
+ } else if (function_name == trn_func) {
+ // Extract from tr_n(id, plural, n, ctx).
+ Vector<int> indices;
+ indices.push_back(0);
+ indices.push_back(3);
+ indices.push_back(1);
+ for (int i = 0; i < indices.size(); i++) {
+ if (indices[i] >= p_call->arguments.size()) {
+ continue;
+ }
+
+ if (p_call->arguments[indices[i]]->type == GDScriptParser::Node::LITERAL) {
+ id_ctx_plural.write[i] = static_cast<GDScriptParser::LiteralNode *>(p_call->arguments[indices[i]])->value;
+ } else {
+ extract_id_ctx_plural = false;
+ }
+ }
+ if (extract_id_ctx_plural) {
+ ids_ctx_plural->push_back(id_ctx_plural);
+ }
+ } else if (first_arg_patterns.has(function_name)) {
+ // Extracting argument with only string literals. In other words, not extracting something like set_text("hello " + some_var).
+ if (p_call->arguments[0]->type == GDScriptParser::Node::LITERAL) {
+ ids->push_back(static_cast<GDScriptParser::LiteralNode *>(p_call->arguments[0])->value);
+ }
+ } else if (second_arg_patterns.has(function_name)) {
+ if (p_call->arguments[1]->type == GDScriptParser::Node::LITERAL) {
+ ids->push_back(static_cast<GDScriptParser::LiteralNode *>(p_call->arguments[1])->value);
+ }
+ } else if (function_name == fd_add_filter) {
+ // Extract the 'JPE Images' in this example - get_node("FileDialog").add_filter("*.jpg; JPE Images").
+ _extract_fd_literals(p_call->arguments[0]);
+
+ } else if (function_name == fd_set_filter && p_call->arguments[0]->type == GDScriptParser::Node::CALL) {
+ // FileDialog.set_filters() accepts assignment in the form of PackedStringArray. For example,
+ // get_node("FileDialog").set_filters( PackedStringArray(["*.png ; PNG Images","*.gd ; GDScript Files"])).
+
+ GDScriptParser::CallNode *call_node = static_cast<GDScriptParser::CallNode *>(p_call->arguments[0]);
+ if (call_node->arguments[0]->type == GDScriptParser::Node::ARRAY) {
+ GDScriptParser::ArrayNode *array_node = static_cast<GDScriptParser::ArrayNode *>(call_node->arguments[0]);
+ for (int i = 0; i < array_node->elements.size(); i++) {
+ _extract_fd_literals(array_node->elements[i]);
}
}
}
}
+void GDScriptEditorTranslationParserPlugin::_extract_fd_literals(GDScriptParser::ExpressionNode *p_expression) {
+ // Extract the name in "extension ; name".
+
+ if (p_expression->type == GDScriptParser::Node::LITERAL) {
+ String arg_val = String(static_cast<GDScriptParser::LiteralNode *>(p_expression)->value);
+ PackedStringArray arr = arg_val.split(";", true);
+ if (arr.size() != 2) {
+ ERR_PRINT("Argument for setting FileDialog has bad format.");
+ return;
+ }
+ ids->push_back(arr[1].strip_edges());
+ }
+}
+
GDScriptEditorTranslationParserPlugin::GDScriptEditorTranslationParserPlugin() {
- // Regex search pattern templates.
- // The extra complication in the regex pattern is to ensure that the matching works when users write over multiple lines, use tabs etc.
- const String dot = "\\.[\\s\\\\]*";
- const String str_assign_template = "[\\s\\\\]*=[\\s\\\\]*\"" + text + "\"";
- const String first_arg_template = "[\\s\\\\]*\\([\\s\\\\]*\"" + text + "\"[\\s\\S]*?\\)";
- const String second_arg_template = "[\\s\\\\]*\\([\\s\\S]+?,[\\s\\\\]*\"" + text + "\"[\\s\\S]*?\\)";
-
- // Common patterns.
- patterns.push_back("tr" + first_arg_template);
- patterns.push_back(dot + "text" + str_assign_template);
- patterns.push_back(dot + "placeholder_text" + str_assign_template);
- patterns.push_back(dot + "hint_tooltip" + str_assign_template);
- patterns.push_back(dot + "set_text" + first_arg_template);
- patterns.push_back(dot + "set_tooltip" + first_arg_template);
- patterns.push_back(dot + "set_placeholder" + first_arg_template);
-
- // Tabs and TabContainer API.
- patterns.push_back(dot + "set_tab_title" + second_arg_template);
- patterns.push_back(dot + "add_tab" + first_arg_template);
-
- // PopupMenu API.
- patterns.push_back(dot + "add_check_item" + first_arg_template);
- patterns.push_back(dot + "add_icon_check_item" + second_arg_template);
- patterns.push_back(dot + "add_icon_item" + second_arg_template);
- patterns.push_back(dot + "add_icon_radio_check_item" + second_arg_template);
- patterns.push_back(dot + "add_item" + first_arg_template);
- patterns.push_back(dot + "add_multistate_item" + first_arg_template);
- patterns.push_back(dot + "add_radio_check_item" + first_arg_template);
- patterns.push_back(dot + "add_separator" + first_arg_template);
- patterns.push_back(dot + "add_submenu_item" + first_arg_template);
- patterns.push_back(dot + "set_item_text" + second_arg_template);
- //patterns.push_back(dot + "set_item_tooltip" + second_arg_template); //no tr() behind this function. might be bug.
-
- // FileDialog API - special case.
- const String fd_text = "((?:[\\s\\\\]*\"(?:[^\"\\\\]|\\\\[\\s\\S])*(?:\"[\\s\\\\]*\\+[\\s\\\\]*\"(?:[^\"\\\\]|\\\\[\\s\\S])*)*\"[\\s\\\\]*,?)*)";
- const String packed_string_array = "[\\s\\\\]*PackedStringArray[\\s\\\\]*\\([\\s\\\\]*\\[" + fd_text + "\\][\\s\\\\]*\\)";
- file_dialog_patterns.push_back(dot + "add_filter[\\s\\\\]*\\(" + fd_text + "[\\s\\\\]*\\)");
- file_dialog_patterns.push_back(dot + "filters[\\s\\\\]*=" + packed_string_array);
- file_dialog_patterns.push_back(dot + "set_filters[\\s\\\\]*\\(" + packed_string_array + "[\\s\\\\]*\\)");
+ assignment_patterns.insert("text");
+ assignment_patterns.insert("placeholder_text");
+ assignment_patterns.insert("hint_tooltip");
+
+ first_arg_patterns.insert("set_text");
+ first_arg_patterns.insert("set_tooltip");
+ first_arg_patterns.insert("set_placeholder");
+ first_arg_patterns.insert("add_tab");
+ first_arg_patterns.insert("add_check_item");
+ first_arg_patterns.insert("add_item");
+ first_arg_patterns.insert("add_multistate_item");
+ first_arg_patterns.insert("add_radio_check_item");
+ first_arg_patterns.insert("add_separator");
+ first_arg_patterns.insert("add_submenu_item");
+
+ second_arg_patterns.insert("set_tab_title");
+ second_arg_patterns.insert("add_icon_check_item");
+ second_arg_patterns.insert("add_icon_item");
+ second_arg_patterns.insert("add_icon_radio_check_item");
+ second_arg_patterns.insert("set_item_text");
}
diff --git a/modules/gdscript/editor/gdscript_translation_parser_plugin.h b/modules/gdscript/editor/gdscript_translation_parser_plugin.h
index 9fa4b69f01..5ea416d4cc 100644
--- a/modules/gdscript/editor/gdscript_translation_parser_plugin.h
+++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.h
@@ -31,23 +31,40 @@
#ifndef GDSCRIPT_TRANSLATION_PARSER_PLUGIN_H
#define GDSCRIPT_TRANSLATION_PARSER_PLUGIN_H
+#include "core/set.h"
#include "editor/editor_translation_parser.h"
+#include "modules/gdscript/gdscript_parser.h"
#include "modules/regex/regex.h"
class GDScriptEditorTranslationParserPlugin : public EditorTranslationParserPlugin {
GDCLASS(GDScriptEditorTranslationParserPlugin, EditorTranslationParserPlugin);
- // Regex and search patterns that are used to match translation strings.
- const String text = "((?:[^\"\\\\]|\\\\[\\s\\S])*(?:\"[\\s\\\\]*\\+[\\s\\\\]*\"(?:[^\"\\\\]|\\\\[\\s\\S])*)*)";
- RegEx regex;
- Vector<String> patterns;
- Vector<String> file_dialog_patterns;
+ Vector<String> *ids;
+ Vector<Vector<String>> *ids_ctx_plural;
- void _parse_file_dialog(const String &p_source_code, Vector<String> *r_output);
- void _get_captured_strings(const Array &p_results, Vector<String> *r_output);
+ // List of patterns used for extracting translation strings.
+ StringName tr_func = "tr";
+ StringName trn_func = "tr_n";
+ Set<StringName> assignment_patterns;
+ Set<StringName> first_arg_patterns;
+ Set<StringName> second_arg_patterns;
+ // FileDialog patterns.
+ StringName fd_add_filter = "add_filter";
+ StringName fd_set_filter = "set_filters";
+ StringName fd_filters = "filters";
+
+ void _traverse_class(const GDScriptParser::ClassNode *p_class);
+ void _traverse_function(const GDScriptParser::FunctionNode *p_func);
+ void _traverse_block(const GDScriptParser::SuiteNode *p_suite);
+
+ void _read_variable(const GDScriptParser::VariableNode *p_var);
+ void _assess_expression(GDScriptParser::ExpressionNode *p_expression);
+ void _assess_assignment(GDScriptParser::AssignmentNode *p_assignment);
+ void _extract_from_call(GDScriptParser::CallNode *p_call);
+ void _extract_fd_literals(GDScriptParser::ExpressionNode *p_expression);
public:
- virtual Error parse_file(const String &p_path, Vector<String> *r_extracted_strings) override;
+ virtual Error parse_file(const String &p_path, Vector<String> *r_ids, Vector<Vector<String>> *r_ids_ctx_plural) override;
virtual void get_recognized_extensions(List<String> *r_extensions) const override;
GDScriptEditorTranslationParserPlugin();
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index 9170255c02..0263e32c5b 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -596,6 +596,19 @@ Error GDScript::reload(bool p_keep_state) {
return OK;
}
+ {
+ String source_path = path;
+ if (source_path.empty()) {
+ source_path = get_path();
+ }
+ if (!source_path.empty()) {
+ MutexLock lock(GDScriptCache::singleton->lock);
+ if (!GDScriptCache::singleton->shallow_gdscript_cache.has(source_path)) {
+ GDScriptCache::singleton->shallow_gdscript_cache[source_path] = this;
+ }
+ }
+ }
+
valid = false;
GDScriptParser parser;
Error err = parser.parse(source, path, false);
diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h
index 9906b4014d..79317ff846 100644
--- a/modules/gdscript/gdscript.h
+++ b/modules/gdscript/gdscript.h
@@ -69,6 +69,7 @@ class GDScript : public Script {
friend class GDScriptInstance;
friend class GDScriptFunction;
+ friend class GDScriptAnalyzer;
friend class GDScriptCompiler;
friend class GDScriptFunctions;
friend class GDScriptLanguage;
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index 531666bec5..cabe096579 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -33,6 +33,7 @@
#include "core/class_db.h"
#include "core/hash_map.h"
#include "core/io/resource_loader.h"
+#include "core/os/file_access.h"
#include "core/project_settings.h"
#include "core/script_language.h"
#include "gdscript.h"
@@ -71,6 +72,10 @@ static StringName get_real_class_name(const StringName &p_source) {
return p_source;
}
+void GDScriptAnalyzer::cleanup() {
+ underscore_map.clear();
+}
+
static GDScriptParser::DataType make_callable_type(const MethodInfo &p_info) {
GDScriptParser::DataType type;
type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
@@ -177,7 +182,7 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class,
return ERR_PARSE_ERROR;
}
- Error err = parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED);
+ Error err = parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED);
if (err != OK) {
push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", p_class->extends_path), p_class);
return err;
@@ -203,7 +208,7 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class,
return ERR_PARSE_ERROR;
}
- Error err = parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED);
+ Error err = parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED);
if (err != OK) {
push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), p_class);
return err;
@@ -222,7 +227,7 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class,
return ERR_PARSE_ERROR;
}
- Error err = parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED);
+ Error err = parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED);
if (err != OK) {
push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), p_class);
return err;
@@ -297,6 +302,16 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class,
return ERR_PARSE_ERROR;
}
+ // Check for cyclic inheritance.
+ const GDScriptParser::ClassNode *base_class = result.class_type;
+ while (base_class) {
+ if (base_class->fqcn == p_class->fqcn) {
+ push_error("Cyclic inheritance.", p_class);
+ return ERR_PARSE_ERROR;
+ }
+ base_class = base_class->base_type.class_type;
+ }
+
p_class->base_type = result;
class_type.native_type = result.native_type;
p_class->set_datatype(class_type);
@@ -304,7 +319,10 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class,
if (p_recursive) {
for (int i = 0; i < p_class->members.size(); i++) {
if (p_class->members[i].type == GDScriptParser::ClassNode::Member::CLASS) {
- resolve_inheritance(p_class->members[i].m_class, true);
+ Error err = resolve_inheritance(p_class->members[i].m_class, true);
+ if (err) {
+ return err;
+ }
}
}
}
@@ -494,7 +512,13 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
if (member.variable->initializer != nullptr) {
if (!is_type_compatible(datatype, member.variable->initializer->get_datatype(), true)) {
- push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", member.variable->initializer->get_datatype().to_string(), datatype.to_string()), member.variable->initializer);
+ // Try reverse test since it can be a masked subtype.
+ if (!is_type_compatible(member.variable->initializer->get_datatype(), datatype, true)) {
+ push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", member.variable->initializer->get_datatype().to_string(), datatype.to_string()), member.variable->initializer);
+ } else {
+ // TODO: Add warning.
+ mark_node_unsafe(member.variable->initializer);
+ }
} else if (datatype.builtin_type == Variant::INT && member.variable->initializer->get_datatype().builtin_type == Variant::FLOAT) {
#ifdef DEBUG_ENABLED
parser->push_warning(member.variable->initializer, GDScriptWarning::NARROWING_CONVERSION);
@@ -529,6 +553,7 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
break;
case GDScriptParser::DataType::NATIVE:
if (ClassDB::is_parent_class(get_real_class_name(datatype.native_type), "Resource")) {
+ member.variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE;
member.variable->export_info.hint_string = get_real_class_name(datatype.native_type);
} else {
push_error(R"(Export type can only be built-in or a resource.)", member.variable);
@@ -592,17 +617,67 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
enum_type.is_meta_type = true;
enum_type.is_constant = true;
+ // Enums can't be nested, so we can safely override this.
+ current_enum = member.m_enum;
+
for (int j = 0; j < member.m_enum->values.size(); j++) {
- enum_type.enum_values[member.m_enum->values[j].identifier->name] = member.m_enum->values[j].value;
+ GDScriptParser::EnumNode::Value &element = member.m_enum->values.write[j];
+
+ if (element.custom_value) {
+ reduce_expression(element.custom_value);
+ if (!element.custom_value->is_constant) {
+ push_error(R"(Enum values must be constant.)", element.custom_value);
+ } else if (element.custom_value->reduced_value.get_type() != Variant::INT) {
+ push_error(R"(Enum values must be integers.)", element.custom_value);
+ } else {
+ element.value = element.custom_value->reduced_value;
+ element.resolved = true;
+ }
+ } else {
+ if (element.index > 0) {
+ element.value = element.parent_enum->values[element.index - 1].value + 1;
+ } else {
+ element.value = 0;
+ }
+ element.resolved = true;
+ }
+
+ enum_type.enum_values[element.identifier->name] = element.value;
}
+ current_enum = nullptr;
+
member.m_enum->set_datatype(enum_type);
} break;
case GDScriptParser::ClassNode::Member::FUNCTION:
resolve_function_signature(member.function);
break;
- case GDScriptParser::ClassNode::Member::ENUM_VALUE:
- break; // Nothing to do, type and value set in parser.
+ case GDScriptParser::ClassNode::Member::ENUM_VALUE: {
+ if (member.enum_value.custom_value) {
+ current_enum = member.enum_value.parent_enum;
+ reduce_expression(member.enum_value.custom_value);
+ current_enum = nullptr;
+
+ if (!member.enum_value.custom_value->is_constant) {
+ push_error(R"(Enum values must be constant.)", member.enum_value.custom_value);
+ } else if (member.enum_value.custom_value->reduced_value.get_type() != Variant::INT) {
+ push_error(R"(Enum values must be integers.)", member.enum_value.custom_value);
+ } else {
+ member.enum_value.value = member.enum_value.custom_value->reduced_value;
+ member.enum_value.resolved = true;
+ }
+ } else {
+ if (member.enum_value.index > 0) {
+ member.enum_value.value = member.enum_value.parent_enum->values[member.enum_value.index - 1].value + 1;
+ } else {
+ member.enum_value.value = 0;
+ }
+ member.enum_value.resolved = true;
+ }
+ // Also update the original references.
+ member.enum_value.parent_enum->values.write[member.enum_value.index] = member.enum_value;
+ p_class->members.write[i].enum_value = member.enum_value;
+ } break;
case GDScriptParser::ClassNode::Member::CLASS:
break; // Done later.
case GDScriptParser::ClassNode::Member::UNDEFINED:
@@ -872,7 +947,8 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) {
// Use int, Vector2, Vector3 instead, which also can be used as range iterators.
if (p_for->list && p_for->list->type == GDScriptParser::Node::CALL) {
GDScriptParser::CallNode *call = static_cast<GDScriptParser::CallNode *>(p_for->list);
- if (call->callee->type == GDScriptParser::Node::IDENTIFIER) {
+ GDScriptParser::Node::Type callee_type = call->get_callee_type();
+ if (callee_type == GDScriptParser::Node::IDENTIFIER) {
GDScriptParser::IdentifierNode *callee = static_cast<GDScriptParser::IdentifierNode *>(call->callee);
if (callee->name == "range") {
list_resolved = true;
@@ -989,7 +1065,13 @@ void GDScriptAnalyzer::resolve_variable(GDScriptParser::VariableNode *p_variable
if (p_variable->initializer != nullptr) {
if (!is_type_compatible(type, p_variable->initializer->get_datatype(), true)) {
- push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", p_variable->initializer->get_datatype().to_string(), type.to_string()), p_variable->initializer);
+ // Try reverse test since it can be a masked subtype.
+ if (!is_type_compatible(p_variable->initializer->get_datatype(), type, true)) {
+ push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", p_variable->initializer->get_datatype().to_string(), type.to_string()), p_variable->initializer);
+ } else {
+ // TODO: Add warning.
+ mark_node_unsafe(p_variable->initializer);
+ }
#ifdef DEBUG_ENABLED
} else if (type.builtin_type == Variant::INT && p_variable->initializer->get_datatype().builtin_type == Variant::FLOAT) {
parser->push_warning(p_variable->initializer, GDScriptWarning::NARROWING_CONVERSION);
@@ -1369,60 +1451,32 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
push_error("Cannot assign a new value to a constant.", p_assignment->assignee);
}
- Variant::Operator vop = Variant::Operator::OP_EQUAL;
- switch (p_assignment->operation) {
- case GDScriptParser::AssignmentNode::OP_NONE:
- vop = Variant::Operator::OP_EQUAL;
- break;
- case GDScriptParser::AssignmentNode::OP_ADDITION:
- vop = Variant::Operator::OP_ADD;
- break;
- case GDScriptParser::AssignmentNode::OP_SUBTRACTION:
- vop = Variant::Operator::OP_SUBTRACT;
- break;
- case GDScriptParser::AssignmentNode::OP_MULTIPLICATION:
- vop = Variant::Operator::OP_MULTIPLY;
- break;
- case GDScriptParser::AssignmentNode::OP_DIVISION:
- vop = Variant::Operator::OP_DIVIDE;
- break;
- case GDScriptParser::AssignmentNode::OP_MODULO:
- vop = Variant::Operator::OP_MODULE;
- break;
- case GDScriptParser::AssignmentNode::OP_BIT_SHIFT_LEFT:
- vop = Variant::Operator::OP_SHIFT_LEFT;
- break;
- case GDScriptParser::AssignmentNode::OP_BIT_SHIFT_RIGHT:
- vop = Variant::Operator::OP_SHIFT_RIGHT;
- break;
- case GDScriptParser::AssignmentNode::OP_BIT_AND:
- vop = Variant::Operator::OP_BIT_AND;
- break;
- case GDScriptParser::AssignmentNode::OP_BIT_OR:
- vop = Variant::Operator::OP_BIT_OR;
- break;
- case GDScriptParser::AssignmentNode::OP_BIT_XOR:
- vop = Variant::Operator::OP_BIT_XOR;
- break;
- }
-
- bool compatible = true;
- GDScriptParser::DataType op_type = p_assignment->assigned_value->get_datatype();
- if (vop != Variant::OP_EQUAL) {
- op_type = get_operation_type(vop, p_assignment->assignee->get_datatype(), p_assignment->assigned_value->get_datatype(), compatible);
- }
+ if (!p_assignment->assignee->get_datatype().is_variant() && !p_assignment->assigned_value->get_datatype().is_variant()) {
+ bool compatible = true;
+ GDScriptParser::DataType op_type = p_assignment->assigned_value->get_datatype();
+ if (p_assignment->operation != GDScriptParser::AssignmentNode::OP_NONE) {
+ op_type = get_operation_type(p_assignment->variant_op, p_assignment->assignee->get_datatype(), p_assignment->assigned_value->get_datatype(), compatible);
+ }
- if (compatible) {
- compatible = is_type_compatible(p_assignment->assignee->get_datatype(), op_type, true);
- if (!compatible) {
- if (p_assignment->assignee->get_datatype().is_hard_type()) {
- push_error(vformat(R"(Cannot assign a value of type "%s" to a target of type "%s".)", p_assignment->assigned_value->get_datatype().to_string(), p_assignment->assignee->get_datatype().to_string()), p_assignment->assigned_value);
- } else {
- // TODO: Warning in this case.
+ if (compatible) {
+ compatible = is_type_compatible(p_assignment->assignee->get_datatype(), op_type, true);
+ if (!compatible) {
+ if (p_assignment->assignee->get_datatype().is_hard_type()) {
+ // Try reverse test since it can be a masked subtype.
+ if (!is_type_compatible(op_type, p_assignment->assignee->get_datatype(), true)) {
+ push_error(vformat(R"(Cannot assign a value of type "%s" to a target of type "%s".)", p_assignment->assigned_value->get_datatype().to_string(), p_assignment->assignee->get_datatype().to_string()), p_assignment->assigned_value);
+ } else {
+ // TODO: Add warning.
+ mark_node_unsafe(p_assignment);
+ }
+ } else {
+ // TODO: Warning in this case.
+ mark_node_unsafe(p_assignment);
+ }
}
+ } else {
+ push_error(vformat(R"(Invalid operands "%s" and "%s" for assignment operator.)", p_assignment->assignee->get_datatype().to_string(), p_assignment->assigned_value->get_datatype().to_string()), p_assignment);
}
- } else {
- push_error(vformat(R"(Invalid operands "%s" and "%s" for assignment operator.)", p_assignment->assignee->get_datatype().to_string(), p_assignment->assigned_value->get_datatype().to_string()), p_assignment);
}
if (p_assignment->assignee->get_datatype().has_no_type() || p_assignment->assigned_value->get_datatype().is_variant()) {
@@ -1536,7 +1590,19 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o
if (p_binary_op->left_operand->is_constant && p_binary_op->right_operand->is_constant) {
p_binary_op->is_constant = true;
if (p_binary_op->variant_op < Variant::OP_MAX) {
- p_binary_op->reduced_value = Variant::evaluate(p_binary_op->variant_op, p_binary_op->left_operand->reduced_value, p_binary_op->right_operand->reduced_value);
+ bool valid = false;
+ Variant::evaluate(p_binary_op->variant_op, p_binary_op->left_operand->reduced_value, p_binary_op->right_operand->reduced_value, p_binary_op->reduced_value, valid);
+ if (!valid) {
+ if (p_binary_op->reduced_value.get_type() == Variant::STRING) {
+ push_error(vformat(R"(%s in operator %s.)", p_binary_op->reduced_value, Variant::get_operator_name(p_binary_op->variant_op)), p_binary_op);
+ } else {
+ push_error(vformat(R"(Invalid operands to operator %s, %s and %s.".)",
+ Variant::get_operator_name(p_binary_op->variant_op),
+ Variant::get_type_name(p_binary_op->left_operand->reduced_value.get_type()),
+ Variant::get_type_name(p_binary_op->right_operand->reduced_value.get_type())),
+ p_binary_op);
+ }
+ }
} else {
if (p_binary_op->operation == GDScriptParser::BinaryOpNode::OP_TYPE_TEST) {
GDScriptParser::DataType test_type = right_type;
@@ -1608,9 +1674,10 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa
all_is_constant = all_is_constant && p_call->arguments[i]->is_constant;
}
+ GDScriptParser::Node::Type callee_type = p_call->get_callee_type();
GDScriptParser::DataType call_type;
- if (!p_call->is_super && p_call->callee->type == GDScriptParser::Node::IDENTIFIER) {
+ if (!p_call->is_super && callee_type == GDScriptParser::Node::IDENTIFIER) {
// Call to name directly.
StringName function_name = p_call->function_name;
Variant::Type builtin_type = GDScriptParser::get_builtin_type(function_name);
@@ -1651,6 +1718,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa
}
signature += p_call->arguments[i]->get_datatype().to_string();
}
+ signature += ")";
push_error(vformat(R"(No constructor of "%s" matches the signature "%s".)", Variant::get_type_name(builtin_type), signature), p_call->callee);
} break;
case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS:
@@ -1701,7 +1769,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa
for (int i = 0; i < p_call->arguments.size(); i++) {
GDScriptParser::DataType par_type = type_from_property(info.arguments[i]);
- if (!is_type_compatible(par_type, p_call->arguments[i]->get_datatype())) {
+ if (!is_type_compatible(par_type, p_call->arguments[i]->get_datatype(), true)) {
types_match = false;
break;
#ifdef DEBUG_ENABLED
@@ -1728,6 +1796,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa
}
signature += p_call->arguments[i]->get_datatype().to_string();
}
+ signature += ")";
push_error(vformat(R"(No constructor of "%s" matches the signature "%s".)", Variant::get_type_name(builtin_type), signature), p_call);
}
}
@@ -1785,10 +1854,10 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa
if (p_call->is_super) {
base_type = parser->current_class->base_type;
is_self = true;
- } else if (p_call->callee->type == GDScriptParser::Node::IDENTIFIER) {
+ } else if (callee_type == GDScriptParser::Node::IDENTIFIER) {
base_type = parser->current_class->get_datatype();
is_self = true;
- } else if (p_call->callee->type == GDScriptParser::Node::SUBSCRIPT) {
+ } else if (callee_type == GDScriptParser::Node::SUBSCRIPT) {
GDScriptParser::SubscriptNode *subscript = static_cast<GDScriptParser::SubscriptNode *>(p_call->callee);
if (!subscript->is_attribute) {
// Invalid call. Error already sent in parser.
@@ -1825,9 +1894,9 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa
} else {
// Check if the name exists as something else.
bool found = false;
- if (!p_call->is_super) {
+ if (!p_call->is_super && callee_type != GDScriptParser::Node::NONE) {
GDScriptParser::IdentifierNode *callee_id;
- if (p_call->callee->type == GDScriptParser::Node::IDENTIFIER) {
+ if (callee_type == GDScriptParser::Node::IDENTIFIER) {
callee_id = static_cast<GDScriptParser::IdentifierNode *>(p_call->callee);
} else {
// Can only be attribute.
@@ -1835,13 +1904,13 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa
}
if (callee_id) {
reduce_identifier_from_base(callee_id, &base_type);
- GDScriptParser::DataType callee_type = callee_id->get_datatype();
- if (callee_type.is_set() && !callee_type.is_variant()) {
+ GDScriptParser::DataType callee_datatype = callee_id->get_datatype();
+ if (callee_datatype.is_set() && !callee_datatype.is_variant()) {
found = true;
- if (callee_type.builtin_type == Variant::CALLABLE) {
+ if (callee_datatype.builtin_type == Variant::CALLABLE) {
push_error(vformat(R"*(Name "%s" is a Callable. You can call it with "%s.call()" instead.)*", p_call->function_name, p_call->function_name), p_call->callee);
} else {
- push_error(vformat(R"*(Name "%s" called as a function but is a "%s".)*", p_call->function_name, callee_type.to_string()), p_call->callee);
+ push_error(vformat(R"*(Name "%s" called as a function but is a "%s".)*", p_call->function_name, callee_datatype.to_string()), p_call->callee);
}
#ifdef DEBUG_ENABLED
} else if (!is_self) {
@@ -1906,6 +1975,8 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) {
void GDScriptAnalyzer::reduce_dictionary(GDScriptParser::DictionaryNode *p_dictionary) {
bool all_is_constant = true;
+ HashMap<Variant, GDScriptParser::ExpressionNode *, VariantHasher, VariantComparator> elements;
+
for (int i = 0; i < p_dictionary->elements.size(); i++) {
const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i];
if (p_dictionary->style == GDScriptParser::DictionaryNode::PYTHON_DICT) {
@@ -1913,6 +1984,14 @@ void GDScriptAnalyzer::reduce_dictionary(GDScriptParser::DictionaryNode *p_dicti
}
reduce_expression(element.value);
all_is_constant = all_is_constant && element.key->is_constant && element.value->is_constant;
+
+ if (element.key->is_constant) {
+ if (elements.has(element.key->reduced_value)) {
+ push_error(vformat(R"(Key "%s" was already used in this dictionary (at line %d).)", element.key->reduced_value, elements[element.key->reduced_value]->start_line), element.key);
+ } else {
+ elements[element.key->reduced_value] = element.value;
+ }
+ }
}
if (all_is_constant) {
@@ -2121,6 +2200,33 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_identifier, bool can_be_builtin) {
// TODO: This is opportunity to further infer types.
+
+ // Check if we are inside and enum. This allows enum values to access other elements of the same enum.
+ if (current_enum) {
+ for (int i = 0; i < current_enum->values.size(); i++) {
+ const GDScriptParser::EnumNode::Value &element = current_enum->values[i];
+ if (element.identifier->name == p_identifier->name) {
+ GDScriptParser::DataType type;
+ type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ type.kind = element.parent_enum->identifier ? GDScriptParser::DataType::ENUM_VALUE : GDScriptParser::DataType::BUILTIN;
+ type.builtin_type = Variant::INT;
+ type.is_constant = true;
+ if (element.parent_enum->identifier) {
+ type.enum_type = element.parent_enum->identifier->name;
+ }
+ p_identifier->set_datatype(type);
+
+ if (element.resolved) {
+ p_identifier->is_constant = true;
+ p_identifier->reduced_value = element.value;
+ } else {
+ push_error(R"(Cannot use another enum element before it was declared.)", p_identifier);
+ }
+ return; // Found anyway.
+ }
+ }
+ }
+
// Check if identifier is local.
// If that's the case, the declaration already was solved before.
switch (p_identifier->source) {
@@ -2220,6 +2326,37 @@ void GDScriptAnalyzer::reduce_literal(GDScriptParser::LiteralNode *p_literal) {
}
void GDScriptAnalyzer::reduce_preload(GDScriptParser::PreloadNode *p_preload) {
+ if (!p_preload->path) {
+ return;
+ }
+
+ reduce_expression(p_preload->path);
+
+ if (!p_preload->path->is_constant) {
+ push_error("Preloaded path must be a constant string.", p_preload->path);
+ return;
+ }
+
+ if (p_preload->path->reduced_value.get_type() != Variant::STRING) {
+ push_error("Preloaded path must be a constant string.", p_preload->path);
+ } else {
+ p_preload->resolved_path = p_preload->path->reduced_value;
+ // TODO: Save this as script dependency.
+ if (p_preload->resolved_path.is_rel_path()) {
+ p_preload->resolved_path = parser->script_path.get_base_dir().plus_file(p_preload->resolved_path);
+ }
+ p_preload->resolved_path = p_preload->resolved_path.simplify_path();
+ if (!FileAccess::exists(p_preload->resolved_path)) {
+ push_error(vformat(R"(Preload file "%s" does not exist.)", p_preload->resolved_path), p_preload->path);
+ } else {
+ // TODO: Don't load if validating: use completion cache.
+ p_preload->resource = ResourceLoader::load(p_preload->resolved_path);
+ if (p_preload->resource.is_null()) {
+ push_error(vformat(R"(Could not p_preload resource file "%s".)", p_preload->resolved_path), p_preload->path);
+ }
+ }
+ }
+
p_preload->is_constant = true;
p_preload->reduced_value = p_preload->resource;
p_preload->set_datatype(type_from_variant(p_preload->reduced_value));
@@ -2395,7 +2532,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
// Check resulting type if possible.
result_type.builtin_type = Variant::NIL;
result_type.kind = GDScriptParser::DataType::BUILTIN;
- result_type.type_source = GDScriptParser::DataType::INFERRED;
+ result_type.type_source = base_type.is_hard_type() ? GDScriptParser::DataType::ANNOTATED_INFERRED : GDScriptParser::DataType::INFERRED;
switch (base_type.builtin_type) {
// Can't index at all.
@@ -2526,6 +2663,12 @@ void GDScriptAnalyzer::reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op)
GDScriptParser::DataType result;
+ if (p_unary_op->operand == nullptr) {
+ result.kind = GDScriptParser::DataType::VARIANT;
+ p_unary_op->set_datatype(result);
+ return;
+ }
+
if (p_unary_op->operand->is_constant) {
p_unary_op->is_constant = true;
p_unary_op->reduced_value = Variant::evaluate(p_unary_op->variant_op, p_unary_op->operand->reduced_value, Variant());
@@ -2572,9 +2715,27 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_va
Ref<GDScript> gds = scr;
if (gds.is_valid()) {
result.kind = GDScriptParser::DataType::CLASS;
- Ref<GDScriptParserRef> ref = get_parser_for(gds->get_path());
+ // This might be an inner class, so we want to get the parser for the root.
+ // But still get the inner class from that tree.
+ GDScript *current = gds.ptr();
+ List<StringName> class_chain;
+ while (current->_owner) {
+ // Push to front so it's in reverse.
+ class_chain.push_front(current->name);
+ current = current->_owner;
+ }
+
+ Ref<GDScriptParserRef> ref = get_parser_for(current->path);
ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED);
- result.class_type = ref->get_parser()->head;
+
+ GDScriptParser::ClassNode *found = ref->get_parser()->head;
+
+ // It should be okay to assume this exists, since we have a complete script already.
+ for (const List<StringName>::Element *E = class_chain.front(); E; E = E->next()) {
+ found = found->get_member(E->get()).m_class;
+ }
+
+ result.class_type = found;
result.script_path = ref->get_parser()->script_path;
} else {
result.kind = GDScriptParser::DataType::SCRIPT;
@@ -2900,7 +3061,7 @@ GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator
}
// Avoid error in formatting operator (%) where it doesn't find a placeholder.
- if (a_type == Variant::STRING) {
+ if (a_type == Variant::STRING && b_type != Variant::ARRAY) {
a = String("%s");
}
@@ -3106,6 +3267,9 @@ Error GDScriptAnalyzer::resolve_program() {
List<String> parser_keys;
depended_parsers.get_key_list(&parser_keys);
for (const List<String>::Element *E = parser_keys.front(); E != nullptr; E = E->next()) {
+ if (depended_parsers[E->get()].is_null()) {
+ return ERR_PARSE_ERROR;
+ }
depended_parsers[E->get()]->raise_status(GDScriptParserRef::FULLY_SOLVED);
}
depended_parsers.clear();
diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h
index 85183d3272..da767522ad 100644
--- a/modules/gdscript/gdscript_analyzer.h
+++ b/modules/gdscript/gdscript_analyzer.h
@@ -41,6 +41,8 @@ class GDScriptAnalyzer {
GDScriptParser *parser = nullptr;
HashMap<String, Ref<GDScriptParserRef>> depended_parsers;
+ const GDScriptParser::EnumNode *current_enum = nullptr;
+
Error resolve_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive = true);
GDScriptParser::DataType resolve_datatype(GDScriptParser::TypeNode *p_type);
@@ -113,6 +115,8 @@ public:
Error analyze();
GDScriptAnalyzer(GDScriptParser *p_parser);
+
+ static void cleanup();
};
#endif // GDSCRIPT_ANALYZER_H
diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp
index 583283ff46..992f8f4b58 100644
--- a/modules/gdscript/gdscript_cache.cpp
+++ b/modules/gdscript/gdscript_cache.cpp
@@ -85,6 +85,17 @@ Error GDScriptParserRef::raise_status(Status p_new_status) {
return result;
}
}
+ if (result != OK) {
+ if (parser != nullptr) {
+ memdelete(parser);
+ parser = nullptr;
+ }
+ if (analyzer != nullptr) {
+ memdelete(analyzer);
+ analyzer = nullptr;
+ }
+ return result;
+ }
}
return result;
@@ -116,14 +127,17 @@ Ref<GDScriptParserRef> GDScriptCache::get_parser(const String &p_path, GDScriptP
singleton->dependencies[p_owner].insert(p_path);
}
if (singleton->parser_map.has(p_path)) {
- ref = singleton->parser_map[p_path];
+ ref = Ref<GDScriptParserRef>(singleton->parser_map[p_path]);
} else {
+ if (!FileAccess::exists(p_path)) {
+ r_error = ERR_FILE_NOT_FOUND;
+ return ref;
+ }
GDScriptParser *parser = memnew(GDScriptParser);
ref.instance();
ref->parser = parser;
ref->path = p_path;
- singleton->parser_map[p_path] = ref;
- ref->unreference();
+ singleton->parser_map[p_path] = ref.ptr();
}
r_error = ref->raise_status(p_status);
@@ -171,10 +185,7 @@ Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, const Stri
script->set_script_path(p_path);
script->load_source_code(p_path);
- singleton->shallow_gdscript_cache[p_path] = script;
- // The one in cache is not a hard reference: if the script dies somewhere else it's fine.
- // Scripts remove themselves from cache when they die.
- script->unreference();
+ singleton->shallow_gdscript_cache[p_path] = script.ptr();
return script;
}
@@ -202,7 +213,7 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro
return script;
}
- singleton->full_gdscript_cache[p_path] = script;
+ singleton->full_gdscript_cache[p_path] = script.ptr();
singleton->shallow_gdscript_cache.erase(p_path);
return script;
@@ -211,7 +222,7 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro
Error GDScriptCache::finish_compiling(const String &p_owner) {
// Mark this as compiled.
Ref<GDScript> script = get_shallow_script(p_owner);
- singleton->full_gdscript_cache[p_owner] = script;
+ singleton->full_gdscript_cache[p_owner] = script.ptr();
singleton->shallow_gdscript_cache.erase(p_owner);
Set<String> depends = singleton->dependencies[p_owner];
diff --git a/modules/gdscript/gdscript_cache.h b/modules/gdscript/gdscript_cache.h
index 770704d6eb..865df34051 100644
--- a/modules/gdscript/gdscript_cache.h
+++ b/modules/gdscript/gdscript_cache.h
@@ -70,9 +70,9 @@ public:
class GDScriptCache {
// String key is full path.
- HashMap<String, Ref<GDScriptParserRef>> parser_map;
- HashMap<String, Ref<GDScript>> shallow_gdscript_cache;
- HashMap<String, Ref<GDScript>> full_gdscript_cache;
+ HashMap<String, GDScriptParserRef *> parser_map;
+ HashMap<String, GDScript *> shallow_gdscript_cache;
+ HashMap<String, GDScript *> full_gdscript_cache;
HashMap<String, Set<String>> dependencies;
friend class GDScript;
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index e34d87f5cc..67a894912f 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -2609,16 +2609,27 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar
p_script->_base = base.ptr();
if (p_class->base_type.kind == GDScriptParser::DataType::CLASS && p_class->base_type.class_type != nullptr) {
- if (!parsed_classes.has(p_script->_base)) {
- if (parsing_classes.has(p_script->_base)) {
- String class_name = p_class->identifier ? p_class->identifier->name : "<main>";
- _set_error("Cyclic class reference for '" + class_name + "'.", p_class);
- return ERR_PARSE_ERROR;
+ if (p_class->base_type.script_path == main_script->path) {
+ if (!parsed_classes.has(p_script->_base)) {
+ if (parsing_classes.has(p_script->_base)) {
+ String class_name = p_class->identifier ? p_class->identifier->name : "<main>";
+ _set_error("Cyclic class reference for '" + class_name + "'.", p_class);
+ return ERR_PARSE_ERROR;
+ }
+ Error err = _parse_class_level(p_script->_base, p_class->base_type.class_type, p_keep_state);
+ if (err) {
+ return err;
+ }
}
- Error err = _parse_class_level(p_script->_base, p_class->base_type.class_type, p_keep_state);
+ } else {
+ Error err = OK;
+ base = GDScriptCache::get_full_script(p_class->base_type.script_path, err, main_script->path);
if (err) {
return err;
}
+ if (base.is_null() && !base->is_valid()) {
+ return ERR_COMPILATION_FAILED;
+ }
}
}
@@ -2696,11 +2707,7 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar
const GDScriptParser::ConstantNode *constant = member.constant;
StringName name = constant->identifier->name;
- ERR_CONTINUE(constant->initializer->type != GDScriptParser::Node::LITERAL);
-
- const GDScriptParser::LiteralNode *literal = static_cast<const GDScriptParser::LiteralNode *>(constant->initializer);
-
- p_script->constants.insert(name, literal->value);
+ p_script->constants.insert(name, constant->initializer->reduced_value);
#ifdef TOOLS_ENABLED
p_script->member_lines[name] = constant->start_line;
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index 239015060e..2e372575da 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -483,6 +483,8 @@ String GDScriptLanguage::make_function(const String &p_class, const String &p_na
#ifdef TOOLS_ENABLED
+#define COMPLETION_RECURSION_LIMIT 200
+
struct GDScriptCompletionIdentifier {
GDScriptParser::DataType type;
String enumeration;
@@ -766,9 +768,11 @@ static void _find_identifiers_in_suite(const GDScriptParser::SuiteNode *p_suite,
}
}
-static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result);
+static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result, int p_recursion_depth);
+
+static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class, bool p_only_functions, bool p_static, bool p_parent_only, Map<String, ScriptCodeCompletionOption> &r_result, int p_recursion_depth) {
+ ERR_FAIL_COND(p_recursion_depth > COMPLETION_RECURSION_LIMIT);
-static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class, bool p_only_functions, bool p_static, bool p_parent_only, Map<String, ScriptCodeCompletionOption> &r_result) {
if (!p_parent_only) {
bool outer = false;
const GDScriptParser::ClassNode *clss = p_class;
@@ -820,7 +824,6 @@ static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class,
break;
case GDScriptParser::ClassNode::Member::SIGNAL:
if (p_only_functions || outer) {
- clss = clss->outer;
continue;
}
option = ScriptCodeCompletionOption(member.signal->identifier->name, ScriptCodeCompletionOption::KIND_SIGNAL);
@@ -840,10 +843,12 @@ static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class,
base_type.type = p_class->base_type;
base_type.type.is_meta_type = p_static;
- _find_identifiers_in_base(base_type, p_only_functions, r_result);
+ _find_identifiers_in_base(base_type, p_only_functions, r_result, p_recursion_depth + 1);
}
-static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result) {
+static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result, int p_recursion_depth) {
+ ERR_FAIL_COND(p_recursion_depth > COMPLETION_RECURSION_LIMIT);
+
GDScriptParser::DataType base_type = p_base.type;
bool _static = base_type.is_meta_type;
@@ -856,7 +861,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base
while (!base_type.has_no_type()) {
switch (base_type.kind) {
case GDScriptParser::DataType::CLASS: {
- _find_identifiers_in_class(base_type.class_type, p_only_functions, _static, false, r_result);
+ _find_identifiers_in_class(base_type.class_type, p_only_functions, _static, false, r_result, p_recursion_depth + 1);
// This already finds all parent identifiers, so we are done.
base_type = GDScriptParser::DataType();
} break;
@@ -1007,14 +1012,14 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base
}
}
-static void _find_identifiers(GDScriptParser::CompletionContext &p_context, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result) {
+static void _find_identifiers(GDScriptParser::CompletionContext &p_context, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result, int p_recursion_depth) {
if (!p_only_functions && p_context.current_suite) {
// This includes function parameters, since they are also locals.
_find_identifiers_in_suite(p_context.current_suite, r_result);
}
if (p_context.current_class) {
- _find_identifiers_in_class(p_context.current_class, p_only_functions, (!p_context.current_function || p_context.current_function->is_static), false, r_result);
+ _find_identifiers_in_class(p_context.current_class, p_only_functions, (!p_context.current_function || p_context.current_function->is_static), false, r_result, p_recursion_depth + 1);
}
for (int i = 0; i < GDScriptFunctions::FUNC_MAX; i++) {
@@ -1259,8 +1264,10 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context,
GDScriptParser::CompletionContext c = p_context;
c.current_line = call->start_line;
+ GDScriptParser::Node::Type callee_type = call->get_callee_type();
+
GDScriptCompletionIdentifier base;
- if (call->callee->type == GDScriptParser::Node::IDENTIFIER || call->is_super) {
+ if (callee_type == GDScriptParser::Node::IDENTIFIER || call->is_super) {
// Simple call, so base is 'self'.
if (p_context.current_class) {
base.type.kind = GDScriptParser::DataType::CLASS;
@@ -1271,7 +1278,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context,
} else {
break;
}
- } else if (call->callee->type == GDScriptParser::Node::SUBSCRIPT && static_cast<const GDScriptParser::SubscriptNode *>(call->callee)->is_attribute) {
+ } else if (callee_type == GDScriptParser::Node::SUBSCRIPT && static_cast<const GDScriptParser::SubscriptNode *>(call->callee)->is_attribute) {
if (!_guess_expression_type(c, static_cast<const GDScriptParser::SubscriptNode *>(call->callee)->base, base)) {
found = false;
break;
@@ -2290,6 +2297,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
GDScriptParser::DataType base_type;
bool _static = false;
const GDScriptParser::CallNode *call = static_cast<const GDScriptParser::CallNode *>(p_call);
+ GDScriptParser::Node::Type callee_type = GDScriptParser::Node::NONE;
GDScriptCompletionIdentifier connect_base;
@@ -2319,14 +2327,14 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
i++;
}
return;
- } else if (call->is_super || call->callee->type == GDScriptParser::Node::IDENTIFIER) {
+ } else if (call->is_super || callee_type == GDScriptParser::Node::IDENTIFIER) {
base = p_context.base;
if (p_context.current_class) {
base_type = p_context.current_class->get_datatype();
_static = !p_context.current_function || p_context.current_function->is_static;
}
- } else if (call->callee->type == GDScriptParser::Node::SUBSCRIPT) {
+ } else if (callee_type == GDScriptParser::Node::SUBSCRIPT) {
const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(call->callee);
if (subscript->is_attribute) {
@@ -2450,7 +2458,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path
break;
}
if (!_guess_expression_type(completion_context, static_cast<const GDScriptParser::AssignmentNode *>(completion_context.node)->assignee, type)) {
- _find_identifiers(completion_context, false, options);
+ _find_identifiers(completion_context, false, options, 0);
r_forced = true;
break;
}
@@ -2459,7 +2467,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path
_find_enumeration_candidates(completion_context, type.enumeration, options);
r_forced = options.size() > 0;
} else {
- _find_identifiers(completion_context, false, options);
+ _find_identifiers(completion_context, false, options, 0);
r_forced = true;
}
} break;
@@ -2467,7 +2475,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path
is_function = true;
[[fallthrough]];
case GDScriptParser::COMPLETION_IDENTIFIER: {
- _find_identifiers(completion_context, is_function, options);
+ _find_identifiers(completion_context, is_function, options, 0);
} break;
case GDScriptParser::COMPLETION_ATTRIBUTE_METHOD:
is_function = true;
@@ -2481,7 +2489,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path
break;
}
- _find_identifiers_in_base(base, is_function, options);
+ _find_identifiers_in_base(base, is_function, options, 0);
}
} break;
case GDScriptParser::COMPLETION_SUBSCRIPT: {
@@ -2501,7 +2509,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path
c.current_class = nullptr;
}
- _find_identifiers_in_base(base, false, options);
+ _find_identifiers_in_base(base, false, options, 0);
} break;
case GDScriptParser::COMPLETION_TYPE_ATTRIBUTE: {
if (!completion_context.current_class) {
@@ -2527,7 +2535,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path
// TODO: Improve this to only list types.
if (found) {
- _find_identifiers_in_base(base, false, options);
+ _find_identifiers_in_base(base, false, options, 0);
}
r_forced = true;
} break;
@@ -2648,7 +2656,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path
if (!completion_context.current_class) {
break;
}
- _find_identifiers_in_class(completion_context.current_class, true, false, true, options);
+ _find_identifiers_in_class(completion_context.current_class, true, false, true, options, 0);
} break;
}
@@ -2762,6 +2770,7 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
if (base_type.class_type->has_member(p_symbol)) {
r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION;
r_result.location = base_type.class_type->get_member(p_symbol).get_line();
+ return OK;
}
base_type = base_type.class_type->base_type;
}
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index af07457750..03da5e926b 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -94,6 +94,10 @@ Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) {
return Variant::VARIANT_MAX;
}
+void GDScriptParser::cleanup() {
+ builtin_types.clear();
+}
+
GDScriptFunctions::Function GDScriptParser::get_builtin_function(const StringName &p_name) {
for (int i = 0; i < GDScriptFunctions::FUNC_MAX; i++) {
if (p_name == GDScriptFunctions::get_func_name(GDScriptFunctions::Function(i))) {
@@ -462,44 +466,40 @@ void GDScriptParser::pop_multiline() {
}
bool GDScriptParser::is_statement_end() {
- return check(GDScriptTokenizer::Token::NEWLINE) || check(GDScriptTokenizer::Token::SEMICOLON);
+ return check(GDScriptTokenizer::Token::NEWLINE) || check(GDScriptTokenizer::Token::SEMICOLON) || check(GDScriptTokenizer::Token::TK_EOF);
}
void GDScriptParser::end_statement(const String &p_context) {
bool found = false;
- while (is_statement_end()) {
+ while (is_statement_end() && !is_at_end()) {
// Remove sequential newlines/semicolons.
found = true;
advance();
}
- if (!found) {
+ if (!found && !is_at_end()) {
push_error(vformat(R"(Expected end of statement after %s, found "%s" instead.)", p_context, current.get_name()));
}
}
void GDScriptParser::parse_program() {
- if (current.type == GDScriptTokenizer::Token::TK_EOF) {
- // Empty file.
- push_error("Source file is empty.");
- return;
- }
-
head = alloc_node<ClassNode>();
current_class = head;
if (match(GDScriptTokenizer::Token::ANNOTATION)) {
// Check for @tool annotation.
AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::CLASS_LEVEL);
- if (annotation->name == "@tool") {
- // TODO: don't allow @tool anywhere else. (Should all script annotations be the first thing?).
- _is_tool = true;
- if (previous.type != GDScriptTokenizer::Token::NEWLINE) {
- push_error(R"(Expected newline after "@tool" annotation.)");
- }
- // @tool annotation has no specific target.
- annotation->apply(this, nullptr);
- } else {
- annotation_stack.push_back(annotation);
+ if (annotation != nullptr) {
+ if (annotation->name == "@tool") {
+ // TODO: don't allow @tool anywhere else. (Should all script annotations be the first thing?).
+ _is_tool = true;
+ if (previous.type != GDScriptTokenizer::Token::NEWLINE) {
+ push_error(R"(Expected newline after "@tool" annotation.)");
+ }
+ // @tool annotation has no specific target.
+ annotation->apply(this, nullptr);
+ } else {
+ annotation_stack.push_back(annotation);
+ }
}
}
@@ -631,7 +631,6 @@ void GDScriptParser::parse_extends() {
current_class->extends_path = previous.literal;
if (!match(GDScriptTokenizer::Token::PERIOD)) {
- end_statement("superclass path");
return;
}
}
@@ -986,9 +985,15 @@ GDScriptParser::SignalNode *GDScriptParser::parse_signal() {
signal->identifier = parse_identifier();
if (match(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) {
- while (!check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE) && !is_at_end()) {
+ do {
+ if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) {
+ // Allow for trailing comma.
+ break;
+ }
+
ParameterNode *parameter = parse_parameter();
if (parameter == nullptr) {
+ push_error("Expected signal parameter name.");
break;
}
if (parameter->default_value != nullptr) {
@@ -1000,7 +1005,8 @@ GDScriptParser::SignalNode *GDScriptParser::parse_signal() {
signal->parameters_indices[parameter->identifier->name] = signal->parameters.size();
signal->parameters.push_back(parameter);
}
- }
+ } while (match(GDScriptTokenizer::Token::COMMA) && !is_at_end());
+
consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after signal parameters.)*");
}
@@ -1022,7 +1028,7 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() {
push_multiline(true);
consume(GDScriptTokenizer::Token::BRACE_OPEN, vformat(R"(Expected "{" after %s.)", named ? "enum name" : R"("enum")"));
- int current_value = 0;
+ HashMap<StringName, int> elements;
do {
if (check(GDScriptTokenizer::Token::BRACE_CLOSE)) {
@@ -1031,8 +1037,13 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() {
if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifer for enum key.)")) {
EnumNode::Value item;
item.identifier = parse_identifier();
+ item.parent_enum = enum_node;
+ item.line = previous.start_line;
+ item.leftmost_column = previous.leftmost_column;
- if (!named) {
+ if (elements.has(item.identifier->name)) {
+ push_error(vformat(R"(Name "%s" was already in this enum (at line %d).)", item.identifier->name, elements[item.identifier->name]), item.identifier);
+ } else if (!named) {
// TODO: Abstract this recursive member check.
ClassNode *parent = current_class;
while (parent != nullptr) {
@@ -1044,19 +1055,18 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() {
}
}
- if (match(GDScriptTokenizer::Token::EQUAL)) {
- if (consume(GDScriptTokenizer::Token::LITERAL, R"(Expected integer value after "=".)")) {
- item.custom_value = parse_literal();
+ elements[item.identifier->name] = item.line;
- if (item.custom_value->value.get_type() != Variant::INT) {
- push_error(R"(Expected integer value after "=".)");
- item.custom_value = nullptr;
- } else {
- current_value = item.custom_value->value;
- }
+ if (match(GDScriptTokenizer::Token::EQUAL)) {
+ ExpressionNode *value = parse_expression(false);
+ if (value == nullptr) {
+ push_error(R"(Expected expression value after "=".)");
}
+ item.custom_value = value;
}
- item.value = current_value++;
+ item.rightmost_column = previous.rightmost_column;
+
+ item.index = enum_node->values.size();
enum_node->values.push_back(item);
if (!named) {
// Add as member of current class.
@@ -1138,6 +1148,9 @@ GDScriptParser::FunctionNode *GDScriptParser::parse_function() {
if (match(GDScriptTokenizer::Token::FORWARD_ARROW)) {
make_completion_context(COMPLETION_TYPE_NAME_OR_VOID, function);
function->return_type = parse_type(true);
+ if (function->return_type == nullptr) {
+ push_error(R"(Expected return type or "void" after "->".)");
+ }
}
// TODO: Improve token consumption so it synchronizes to a statement boundary. This way we can get into the function body with unrecognized tokens.
@@ -1342,7 +1355,7 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
advance();
ReturnNode *n_return = alloc_node<ReturnNode>();
if (!is_statement_end()) {
- if (current_function->identifier->name == GDScriptLanguage::get_singleton()->strings._init) {
+ if (current_function && current_function->identifier->name == GDScriptLanguage::get_singleton()->strings._init) {
push_error(R"(Constructor cannot return a value.)");
}
n_return->return_value = parse_expression(false);
@@ -2005,7 +2018,6 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_binary_operator(Expression
push_error(vformat(R"(Expected expression after "%s" operator.")", op.get_name()));
}
- // TODO: Store the Variant operator here too (in the node).
// TODO: Also for unary, ternary, and assignment.
switch (op.type) {
case GDScriptTokenizer::Token::PLUS:
@@ -2167,39 +2179,50 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode
switch (previous.type) {
case GDScriptTokenizer::Token::EQUAL:
assignment->operation = AssignmentNode::OP_NONE;
+ assignment->variant_op = Variant::OP_MAX;
#ifdef DEBUG_ENABLED
has_operator = false;
#endif
break;
case GDScriptTokenizer::Token::PLUS_EQUAL:
assignment->operation = AssignmentNode::OP_ADDITION;
+ assignment->variant_op = Variant::OP_ADD;
break;
case GDScriptTokenizer::Token::MINUS_EQUAL:
assignment->operation = AssignmentNode::OP_SUBTRACTION;
+ assignment->variant_op = Variant::OP_SUBTRACT;
break;
case GDScriptTokenizer::Token::STAR_EQUAL:
assignment->operation = AssignmentNode::OP_MULTIPLICATION;
+ assignment->variant_op = Variant::OP_MULTIPLY;
break;
case GDScriptTokenizer::Token::SLASH_EQUAL:
assignment->operation = AssignmentNode::OP_DIVISION;
+ assignment->variant_op = Variant::OP_DIVIDE;
break;
case GDScriptTokenizer::Token::PERCENT_EQUAL:
assignment->operation = AssignmentNode::OP_MODULO;
+ assignment->variant_op = Variant::OP_MODULE;
break;
case GDScriptTokenizer::Token::LESS_LESS_EQUAL:
assignment->operation = AssignmentNode::OP_BIT_SHIFT_LEFT;
+ assignment->variant_op = Variant::OP_SHIFT_LEFT;
break;
case GDScriptTokenizer::Token::GREATER_GREATER_EQUAL:
assignment->operation = AssignmentNode::OP_BIT_SHIFT_RIGHT;
+ assignment->variant_op = Variant::OP_SHIFT_RIGHT;
break;
case GDScriptTokenizer::Token::AMPERSAND_EQUAL:
assignment->operation = AssignmentNode::OP_BIT_AND;
+ assignment->variant_op = Variant::OP_BIT_AND;
break;
case GDScriptTokenizer::Token::PIPE_EQUAL:
assignment->operation = AssignmentNode::OP_BIT_OR;
+ assignment->variant_op = Variant::OP_BIT_OR;
break;
case GDScriptTokenizer::Token::CARET_EQUAL:
assignment->operation = AssignmentNode::OP_BIT_XOR;
+ assignment->variant_op = Variant::OP_BIT_XOR;
break;
default:
break; // Unreachable.
@@ -2328,7 +2351,11 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_dictionary(ExpressionNode
GDScriptParser::ExpressionNode *GDScriptParser::parse_grouping(ExpressionNode *p_previous_operand, bool p_can_assign) {
ExpressionNode *grouped = parse_expression(false);
pop_multiline();
- consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after grouping expression.)*");
+ if (grouped == nullptr) {
+ push_error(R"(Expected grouping expression.)");
+ } else {
+ consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after grouping expression.)*");
+ }
return grouped;
}
@@ -2419,7 +2446,9 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre
} else {
call->callee = p_previous_operand;
- if (call->callee->type == Node::IDENTIFIER) {
+ if (call->callee == nullptr) {
+ push_error(R"*(Cannot call on an expression. Use ".call()" if it's a Callable.)*");
+ } else if (call->callee->type == Node::IDENTIFIER) {
call->function_name = static_cast<IdentifierNode *>(call->callee)->name;
make_completion_context(COMPLETION_METHOD, call->callee);
} else if (call->callee->type == Node::SUBSCRIPT) {
@@ -2475,15 +2504,18 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p
make_completion_context(COMPLETION_GET_NODE, get_node);
get_node->string = parse_literal();
return get_node;
- } else if (check(GDScriptTokenizer::Token::IDENTIFIER)) {
+ } else if (current.is_node_name()) {
GetNodeNode *get_node = alloc_node<GetNodeNode>();
int chain_position = 0;
do {
make_completion_context(COMPLETION_GET_NODE, get_node, chain_position++);
- if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expect node identifer after "/".)")) {
+ if (!current.is_node_name()) {
+ push_error(R"(Expect node path after "/".)");
return nullptr;
}
- IdentifierNode *identifier = parse_identifier();
+ advance();
+ IdentifierNode *identifier = alloc_node<IdentifierNode>();
+ identifier->name = previous.get_identifier();
get_node->chain.push_back(identifier);
} while (match(GDScriptTokenizer::Token::SLASH));
return get_node;
@@ -2507,29 +2539,6 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_preload(ExpressionNode *p_
if (preload->path == nullptr) {
push_error(R"(Expected resource path after "(".)");
- } else if (preload->path->type != Node::LITERAL) {
- push_error("Preloaded path must be a constant string.");
- } else {
- LiteralNode *path = static_cast<LiteralNode *>(preload->path);
- if (path->value.get_type() != Variant::STRING) {
- push_error("Preloaded path must be a constant string.");
- } else {
- preload->resolved_path = path->value;
- // TODO: Save this as script dependency.
- if (preload->resolved_path.is_rel_path()) {
- preload->resolved_path = script_path.get_base_dir().plus_file(preload->resolved_path);
- }
- preload->resolved_path = preload->resolved_path.simplify_path();
- if (!FileAccess::exists(preload->resolved_path)) {
- push_error(vformat(R"(Preload file "%s" does not exist.)", preload->resolved_path));
- } else {
- // TODO: Don't load if validating: use completion cache.
- preload->resource = ResourceLoader::load(preload->resolved_path);
- if (preload->resource.is_null()) {
- push_error(vformat(R"(Could not preload resource file "%s".)", preload->resolved_path));
- }
- }
- }
}
pop_completion_call();
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index a741ae0cc7..7d8ae7fc55 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -383,6 +383,14 @@ public:
CallNode() {
type = CALL;
}
+
+ Type get_callee_type() const {
+ if (callee == nullptr) {
+ return Type::NONE;
+ } else {
+ return callee->type;
+ }
+ }
};
struct CastNode : public ExpressionNode {
@@ -397,7 +405,10 @@ public:
struct EnumNode : public Node {
struct Value {
IdentifierNode *identifier = nullptr;
- LiteralNode *custom_value = nullptr;
+ ExpressionNode *custom_value = nullptr;
+ EnumNode *parent_enum = nullptr;
+ int index = -1;
+ bool resolved = false;
int value = 0;
int line = 0;
int leftmost_column = 0;
@@ -1335,6 +1346,7 @@ public:
void print_tree(const GDScriptParser &p_parser);
};
#endif // DEBUG_ENABLED
+ static void cleanup();
};
#endif // GDSCRIPT_PARSER_H
diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp
index 7a4bdd88ba..737e920693 100644
--- a/modules/gdscript/gdscript_tokenizer.cpp
+++ b/modules/gdscript/gdscript_tokenizer.cpp
@@ -156,6 +156,64 @@ const char *GDScriptTokenizer::Token::get_name() const {
return token_names[type];
}
+bool GDScriptTokenizer::Token::is_identifier() const {
+ // Note: Most keywords should not be recognized as identifiers.
+ // These are only exceptions for stuff that already is on the engine's API.
+ switch (type) {
+ case IDENTIFIER:
+ case MATCH: // Used in String.match().
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool GDScriptTokenizer::Token::is_node_name() const {
+ // This is meant to allow keywords with the $ notation, but not as general identifiers.
+ switch (type) {
+ case IDENTIFIER:
+ case AND:
+ case AS:
+ case ASSERT:
+ case AWAIT:
+ case BREAK:
+ case BREAKPOINT:
+ case CLASS_NAME:
+ case CLASS:
+ case CONST:
+ case CONTINUE:
+ case ELIF:
+ case ELSE:
+ case ENUM:
+ case EXTENDS:
+ case FOR:
+ case FUNC:
+ case IF:
+ case IN:
+ case IS:
+ case MATCH:
+ case NAMESPACE:
+ case NOT:
+ case OR:
+ case PASS:
+ case PRELOAD:
+ case RETURN:
+ case SELF:
+ case SIGNAL:
+ case STATIC:
+ case SUPER:
+ case TRAIT:
+ case UNDERSCORE:
+ case VAR:
+ case VOID:
+ case WHILE:
+ case YIELD:
+ return true;
+ default:
+ return false;
+ }
+}
+
String GDScriptTokenizer::get_token_name(Token::Type p_token_type) {
ERR_FAIL_INDEX_V_MSG(p_token_type, Token::TK_MAX, "<error>", "Using token type out of the enum.");
return token_names[p_token_type];
diff --git a/modules/gdscript/gdscript_tokenizer.h b/modules/gdscript/gdscript_tokenizer.h
index 059a226924..100ed3f132 100644
--- a/modules/gdscript/gdscript_tokenizer.h
+++ b/modules/gdscript/gdscript_tokenizer.h
@@ -168,9 +168,9 @@ public:
String source;
const char *get_name() const;
- // TODO: Allow some keywords as identifiers?
- bool is_identifier() const { return type == IDENTIFIER; }
- StringName get_identifier() const { return literal; }
+ bool is_identifier() const;
+ bool is_node_name() const;
+ StringName get_identifier() const { return source; }
Token(Type p_type) {
type = p_type;
diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp
index ae7898fdf2..4d79d9d395 100644
--- a/modules/gdscript/language_server/gdscript_extend_parser.cpp
+++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp
@@ -237,7 +237,7 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p
case ClassNode::Member::ENUM_VALUE: {
lsp::DocumentSymbol symbol;
- symbol.name = m.constant->identifier->name;
+ symbol.name = m.enum_value.identifier->name;
symbol.kind = lsp::SymbolKind::EnumMember;
symbol.deprecated = false;
symbol.range.start.line = LINE_NUMBER_TO_INDEX(m.enum_value.line);
diff --git a/modules/gdscript/register_types.cpp b/modules/gdscript/register_types.cpp
index 23c7f97b5a..c554cbac05 100644
--- a/modules/gdscript/register_types.cpp
+++ b/modules/gdscript/register_types.cpp
@@ -35,6 +35,7 @@
#include "core/os/dir_access.h"
#include "core/os/file_access.h"
#include "gdscript.h"
+#include "gdscript_analyzer.h"
#include "gdscript_cache.h"
#include "gdscript_tokenizer.h"
@@ -148,4 +149,7 @@ void unregister_gdscript_types() {
EditorTranslationParser::get_singleton()->remove_parser(gdscript_translation_parser_plugin, EditorTranslationParser::STANDARD);
gdscript_translation_parser_plugin.unref();
#endif // TOOLS_ENABLED
+
+ GDScriptParser::cleanup();
+ GDScriptAnalyzer::cleanup();
}