From 171c522290101aa3f30f4442dd5a078820b64d74 Mon Sep 17 00:00:00 2001 From: HolonProduction Date: Fri, 30 Aug 2024 13:05:26 +0200 Subject: [PATCH] Autocompletion: reintroduce enum options on assignment --- modules/gdscript/gdscript_editor.cpp | 22 ++++++++-------- modules/gdscript/gdscript_parser.cpp | 26 ++++++++++++++----- modules/gdscript/gdscript_parser.h | 7 +++-- .../assignment_options/enum_attribute.cfg | 19 ++++++++++++++ .../assignment_options/enum_attribute.gd | 7 +++++ .../enum_attribute_identifier.cfg | 19 ++++++++++++++ .../enum_attribute_identifier.gd | 7 +++++ .../assignment_options/enum_identifier.cfg | 12 +++++++++ .../assignment_options/enum_identifier.gd | 7 +++++ .../assignment_options/enum_no_identifier.cfg | 12 +++++++++ .../assignment_options/enum_no_identifier.gd | 7 +++++ 11 files changed, 126 insertions(+), 19 deletions(-) create mode 100644 modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute.cfg create mode 100644 modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute.gd create mode 100644 modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute_identifier.cfg create mode 100644 modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute_identifier.gd create mode 100644 modules/gdscript/tests/scripts/completion/assignment_options/enum_identifier.cfg create mode 100644 modules/gdscript/tests/scripts/completion/assignment_options/enum_identifier.gd create mode 100644 modules/gdscript/tests/scripts/completion/assignment_options/enum_no_identifier.cfg create mode 100644 modules/gdscript/tests/scripts/completion/assignment_options/enum_no_identifier.gd diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 636339ef1db..342ff073af3 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -2112,7 +2112,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, // Look in blocks first. int last_assign_line = -1; const GDScriptParser::ExpressionNode *last_assigned_expression = nullptr; - GDScriptParser::DataType id_type; + GDScriptCompletionIdentifier id_type; GDScriptParser::SuiteNode *suite = p_context.current_suite; bool is_function_parameter = false; @@ -2134,7 +2134,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, if (can_be_local && suite && suite->has_local(p_identifier->name)) { const GDScriptParser::SuiteNode::Local &local = suite->get_local(p_identifier->name); - id_type = local.get_datatype(); + id_type.type = local.get_datatype(); // Check initializer as the first assignment. switch (local.type) { @@ -2172,7 +2172,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, base.type.is_meta_type = p_context.current_function && p_context.current_function->is_static; if (_guess_identifier_type_from_base(p_context, base, p_identifier->name, base_identifier)) { - id_type = base_identifier.type; + id_type = base_identifier; } } } @@ -2212,7 +2212,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, c.current_line = type_test->operand->start_line; c.current_suite = suite; if (type_test->test_datatype.is_hard_type()) { - id_type = type_test->test_datatype; + id_type.type = type_test->test_datatype; if (last_assign_line < c.current_line) { // Override last assignment. last_assign_line = c.current_line; @@ -2230,10 +2230,10 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, c.current_line = last_assign_line; GDScriptCompletionIdentifier assigned_type; if (_guess_expression_type(c, last_assigned_expression, assigned_type)) { - if (id_type.is_set() && assigned_type.type.is_set() && !GDScriptAnalyzer::check_type_compatibility(id_type, assigned_type.type)) { + if (id_type.type.is_set() && assigned_type.type.is_set() && !GDScriptAnalyzer::check_type_compatibility(id_type.type, assigned_type.type)) { // The assigned type is incompatible. The annotated type takes priority. + r_type = id_type; r_type.assigned_expression = last_assigned_expression; - r_type.type = id_type; } else { r_type = assigned_type; } @@ -2251,8 +2251,8 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, GDScriptParser::FunctionNode *parent_function = base_type.class_type->get_member(p_context.current_function->identifier->name).function; if (parent_function->parameters_indices.has(p_identifier->name)) { const GDScriptParser::ParameterNode *parameter = parent_function->parameters[parent_function->parameters_indices[p_identifier->name]]; - if ((!id_type.is_set() || id_type.is_variant()) && parameter->get_datatype().is_hard_type()) { - id_type = parameter->get_datatype(); + if ((!id_type.type.is_set() || id_type.type.is_variant()) && parameter->get_datatype().is_hard_type()) { + id_type.type = parameter->get_datatype(); } if (parameter->initializer) { GDScriptParser::CompletionContext c = p_context; @@ -2268,7 +2268,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, base_type = base_type.class_type->base_type; break; case GDScriptParser::DataType::NATIVE: { - if (id_type.is_set() && !id_type.is_variant()) { + if (id_type.type.is_set() && !id_type.type.is_variant()) { base_type = GDScriptParser::DataType(); break; } @@ -2289,8 +2289,8 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, } } - if (id_type.is_set() && !id_type.is_variant()) { - r_type.type = id_type; + if (id_type.type.is_set() && !id_type.type.is_variant()) { + r_type = id_type; return true; } diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 582305d9007..a185eff4d96 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -249,7 +249,7 @@ void GDScriptParser::override_completion_context(const Node *p_for_node, Complet if (!for_completion) { return; } - if (completion_context.node != p_for_node) { + if (p_for_node == nullptr || completion_context.node != p_for_node) { return; } CompletionContext context; @@ -264,8 +264,8 @@ void GDScriptParser::override_completion_context(const Node *p_for_node, Complet completion_context = context; } -void GDScriptParser::make_completion_context(CompletionType p_type, Node *p_node, int p_argument) { - if (!for_completion) { +void GDScriptParser::make_completion_context(CompletionType p_type, Node *p_node, int p_argument, bool p_force) { + if (!for_completion || (!p_force && completion_context.type != COMPLETION_NONE)) { return; } if (previous.cursor_place != GDScriptTokenizerText::CURSOR_MIDDLE && previous.cursor_place != GDScriptTokenizerText::CURSOR_END && current.cursor_place == GDScriptTokenizerText::CURSOR_NONE) { @@ -283,8 +283,8 @@ void GDScriptParser::make_completion_context(CompletionType p_type, Node *p_node completion_context = context; } -void GDScriptParser::make_completion_context(CompletionType p_type, Variant::Type p_builtin_type) { - if (!for_completion) { +void GDScriptParser::make_completion_context(CompletionType p_type, Variant::Type p_builtin_type, bool p_force) { + if (!for_completion || (!p_force && completion_context.type != COMPLETION_NONE)) { return; } if (previous.cursor_place != GDScriptTokenizerText::CURSOR_MIDDLE && previous.cursor_place != GDScriptTokenizerText::CURSOR_END && current.cursor_place == GDScriptTokenizerText::CURSOR_NONE) { @@ -2471,7 +2471,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_precedence(Precedence p_pr } // Completion can appear whenever an expression is expected. - make_completion_context(COMPLETION_IDENTIFIER, nullptr); + make_completion_context(COMPLETION_IDENTIFIER, nullptr, -1, false); GDScriptTokenizer::Token token = current; GDScriptTokenizer::Token::Type token_type = token.type; @@ -2488,8 +2488,17 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_precedence(Precedence p_pr advance(); // Only consume the token if there's a valid rule. + // After a token was consumed, update the completion context regardless of a previously set context. + ExpressionNode *previous_operand = (this->*prefix_rule)(nullptr, p_can_assign); +#ifdef TOOLS_ENABLED + // HACK: We can't create a context in parse_identifier since it is used in places were we don't want completion. + if (previous_operand != nullptr && previous_operand->type == GDScriptParser::Node::IDENTIFIER && prefix_rule == static_cast(&GDScriptParser::parse_identifier)) { + make_completion_context(COMPLETION_IDENTIFIER, previous_operand); + } +#endif + while (p_precedence <= get_rule(current.type)->precedence) { if (previous_operand == nullptr || (p_stop_on_assign && current.type == GDScriptTokenizer::Token::EQUAL) || lambda_ended) { return previous_operand; @@ -2924,6 +2933,11 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode } assignment->assignee = p_previous_operand; assignment->assigned_value = parse_expression(false); +#ifdef TOOLS_ENABLED + if (assignment->assigned_value != nullptr && assignment->assigned_value->type == GDScriptParser::Node::IDENTIFIER) { + override_completion_context(assignment->assigned_value, COMPLETION_ASSIGN, assignment); + } +#endif if (assignment->assigned_value == nullptr) { push_error(R"(Expected an expression after "=".)"); } diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 43c5a48fa7c..2c7e730772b 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -1455,8 +1455,11 @@ private: } void apply_pending_warnings(); #endif - void make_completion_context(CompletionType p_type, Node *p_node, int p_argument = -1); - void make_completion_context(CompletionType p_type, Variant::Type p_builtin_type); + // Setting p_force to false will prevent the completion context from being update if a context was already set before. + // This should only be done when we push context before we consumed any tokens for the corresponding structure. + // See parse_precedence for an example. + void make_completion_context(CompletionType p_type, Node *p_node, int p_argument = -1, bool p_force = true); + void make_completion_context(CompletionType p_type, Variant::Type p_builtin_type, bool p_force = true); // In some cases it might become necessary to alter the completion context after parsing a subexpression. // For example to not override COMPLETE_CALL_ARGUMENTS with COMPLETION_NONE from string literals. void override_completion_context(const Node *p_for_node, CompletionType p_type, Node *p_node, int p_argument = -1); diff --git a/modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute.cfg b/modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute.cfg new file mode 100644 index 00000000000..e4759ac76b8 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute.cfg @@ -0,0 +1,19 @@ +[output] +include=[ + {"display": "new"}, + {"display": "SIZE_EXPAND"}, + {"display": "SIZE_EXPAND_FILL"}, + {"display": "SIZE_FILL"}, + {"display": "SIZE_SHRINK_BEGIN"}, + {"display": "SIZE_SHRINK_CENTER"}, + {"display": "SIZE_SHRINK_END"}, +] +exclude=[ + {"display": "Control.SIZE_EXPAND"}, + {"display": "Control.SIZE_EXPAND_FILL"}, + {"display": "Control.SIZE_FILL"}, + {"display": "Control.SIZE_SHRINK_BEGIN"}, + {"display": "Control.SIZE_SHRINK_CENTER"}, + {"display": "Control.SIZE_SHRINK_END"}, + {"display": "contro_var"} +] diff --git a/modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute.gd b/modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute.gd new file mode 100644 index 00000000000..4aeafb2e0a4 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute.gd @@ -0,0 +1,7 @@ +extends Control + +var contro_var + +func _ready(): + size_flags_horizontal = Control.➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute_identifier.cfg b/modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute_identifier.cfg new file mode 100644 index 00000000000..e4759ac76b8 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute_identifier.cfg @@ -0,0 +1,19 @@ +[output] +include=[ + {"display": "new"}, + {"display": "SIZE_EXPAND"}, + {"display": "SIZE_EXPAND_FILL"}, + {"display": "SIZE_FILL"}, + {"display": "SIZE_SHRINK_BEGIN"}, + {"display": "SIZE_SHRINK_CENTER"}, + {"display": "SIZE_SHRINK_END"}, +] +exclude=[ + {"display": "Control.SIZE_EXPAND"}, + {"display": "Control.SIZE_EXPAND_FILL"}, + {"display": "Control.SIZE_FILL"}, + {"display": "Control.SIZE_SHRINK_BEGIN"}, + {"display": "Control.SIZE_SHRINK_CENTER"}, + {"display": "Control.SIZE_SHRINK_END"}, + {"display": "contro_var"} +] diff --git a/modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute_identifier.gd b/modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute_identifier.gd new file mode 100644 index 00000000000..47e9bd5a67a --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute_identifier.gd @@ -0,0 +1,7 @@ +extends Control + +var contro_var + +func _ready(): + size_flags_horizontal = Control.SIZE➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/assignment_options/enum_identifier.cfg b/modules/gdscript/tests/scripts/completion/assignment_options/enum_identifier.cfg new file mode 100644 index 00000000000..5cc4ec5fd3e --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/assignment_options/enum_identifier.cfg @@ -0,0 +1,12 @@ +[output] +include=[ + {"display": "Control.SIZE_EXPAND"}, + {"display": "Control.SIZE_EXPAND_FILL"}, + {"display": "Control.SIZE_FILL"}, + {"display": "Control.SIZE_SHRINK_BEGIN"}, + {"display": "Control.SIZE_SHRINK_CENTER"}, + {"display": "Control.SIZE_SHRINK_END"}, +] +exclude=[ + {"display": "contro_var"} +] diff --git a/modules/gdscript/tests/scripts/completion/assignment_options/enum_identifier.gd b/modules/gdscript/tests/scripts/completion/assignment_options/enum_identifier.gd new file mode 100644 index 00000000000..5c96720bd38 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/assignment_options/enum_identifier.gd @@ -0,0 +1,7 @@ +extends Control + +var contro_var + +func _ready(): + size_flags_horizontal = Con➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/assignment_options/enum_no_identifier.cfg b/modules/gdscript/tests/scripts/completion/assignment_options/enum_no_identifier.cfg new file mode 100644 index 00000000000..5cc4ec5fd3e --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/assignment_options/enum_no_identifier.cfg @@ -0,0 +1,12 @@ +[output] +include=[ + {"display": "Control.SIZE_EXPAND"}, + {"display": "Control.SIZE_EXPAND_FILL"}, + {"display": "Control.SIZE_FILL"}, + {"display": "Control.SIZE_SHRINK_BEGIN"}, + {"display": "Control.SIZE_SHRINK_CENTER"}, + {"display": "Control.SIZE_SHRINK_END"}, +] +exclude=[ + {"display": "contro_var"} +] diff --git a/modules/gdscript/tests/scripts/completion/assignment_options/enum_no_identifier.gd b/modules/gdscript/tests/scripts/completion/assignment_options/enum_no_identifier.gd new file mode 100644 index 00000000000..d8bf13e51dc --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/assignment_options/enum_no_identifier.gd @@ -0,0 +1,7 @@ +extends Control + +var contro_var + +func _ready(): + size_flags_horizontal = ➡ + pass