From 590a8cba02716f5ea7a1f63ee4997e294ba69e51 Mon Sep 17 00:00:00 2001 From: zjin <184326668+zjin123@users.noreply.github.com> Date: Sat, 19 Oct 2024 13:05:39 +0800 Subject: [PATCH] GDScript: support variable definitions in if-statement --- .../gdscript_translation_parser_plugin.cpp | 11 +- modules/gdscript/gdscript_analyzer.cpp | 11 +- modules/gdscript/gdscript_byte_codegen.cpp | 9 +- modules/gdscript/gdscript_byte_codegen.h | 2 +- modules/gdscript/gdscript_codegen.h | 2 +- modules/gdscript/gdscript_compiler.cpp | 165 ++++++++++++------ modules/gdscript/gdscript_compiler.h | 2 + modules/gdscript/gdscript_editor.cpp | 42 +++-- modules/gdscript/gdscript_parser.cpp | 114 ++++++++++-- modules/gdscript/gdscript_parser.h | 5 +- .../assignment_in_var_if_and_use_in_elif.gd | 5 + .../assignment_in_var_if_and_use_in_elif.out | 2 + .../assignment_in_var_if_and_use_in_else.gd | 5 + .../assignment_in_var_if_and_use_in_else.out | 2 + .../assignment_in_var_if_and_use_outside.gd | 5 + .../assignment_in_var_if_and_use_outside.out | 2 + .../parser/errors/assignment_in_var_if.gd | 4 - .../parser/errors/assignment_in_var_if.out | 2 - ...ignment_in_var_if_duplicated_definition.gd | 3 + ...gnment_in_var_if_duplicated_definition.out | 2 + ...assignment_in_var_if_missing_init_value.gd | 3 + ...ssignment_in_var_if_missing_init_value.out | 2 + .../scripts/runtime/assignment_in_var_if.gd | 53 ++++++ .../scripts/runtime/assignment_in_var_if.out | 43 +++++ 24 files changed, 394 insertions(+), 102 deletions(-) create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/assignment_in_var_if_and_use_in_elif.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/assignment_in_var_if_and_use_in_elif.out create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/assignment_in_var_if_and_use_in_else.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/assignment_in_var_if_and_use_in_else.out create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/assignment_in_var_if_and_use_outside.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/assignment_in_var_if_and_use_outside.out delete mode 100644 modules/gdscript/tests/scripts/parser/errors/assignment_in_var_if.gd delete mode 100644 modules/gdscript/tests/scripts/parser/errors/assignment_in_var_if.out create mode 100644 modules/gdscript/tests/scripts/parser/errors/assignment_in_var_if_duplicated_definition.gd create mode 100644 modules/gdscript/tests/scripts/parser/errors/assignment_in_var_if_duplicated_definition.out create mode 100644 modules/gdscript/tests/scripts/parser/errors/assignment_in_var_if_missing_init_value.gd create mode 100644 modules/gdscript/tests/scripts/parser/errors/assignment_in_var_if_missing_init_value.out create mode 100644 modules/gdscript/tests/scripts/runtime/assignment_in_var_if.gd create mode 100644 modules/gdscript/tests/scripts/runtime/assignment_in_var_if.out diff --git a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp index b31ae878cef..554208236b2 100644 --- a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp +++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp @@ -135,7 +135,16 @@ void GDScriptEditorTranslationParserPlugin::_traverse_block(const GDScriptParser } break; case GDScriptParser::Node::IF: { const GDScriptParser::IfNode *if_node = static_cast(statement); - _assess_expression(if_node->condition); + const List::Element *E = if_node->conditions.front(); + while (E) { + const GDScriptParser::Node *node = E->get(); + if (node->is_expression()) { + _assess_expression(static_cast(node)); + } else if (node->type == GDScriptParser::Node::VARIABLE) { + _assess_expression(static_cast(node)->initializer); + } + E = E->next(); + } _traverse_block(if_node->true_block); _traverse_block(if_node->false_block); } break; diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 4a3a3a4b615..8f074e955b0 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -2096,7 +2096,16 @@ void GDScriptAnalyzer::resolve_parameter(GDScriptParser::ParameterNode *p_parame } void GDScriptAnalyzer::resolve_if(GDScriptParser::IfNode *p_if) { - reduce_expression(p_if->condition); + List::Element *E = p_if->conditions.front(); + while (E) { + GDScriptParser::Node *condition = E->get(); + if (condition->is_expression()) { + reduce_expression(static_cast(condition)); + } else if (condition->type == GDScriptParser::Node::VARIABLE) { + resolve_variable(static_cast(condition), true); + } + E = E->next(); + } resolve_suite(p_if->true_block); p_if->set_datatype(p_if->true_block->get_datatype()); diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp index b77c641eb55..15832256ade 100644 --- a/modules/gdscript/gdscript_byte_codegen.cpp +++ b/modules/gdscript/gdscript_byte_codegen.cpp @@ -1501,13 +1501,16 @@ void GDScriptByteCodeGenerator::write_if(const Address &p_condition) { append(0); // Jump destination, will be patched. } -void GDScriptByteCodeGenerator::write_else() { +void GDScriptByteCodeGenerator::write_else(int count) { append_opcode(GDScriptFunction::OPCODE_JUMP); // Jump from true if block; int else_jmp_addr = opcodes.size(); append(0); // Jump destination, will be patched. - patch_jump(if_jmp_addrs.back()->get()); - if_jmp_addrs.pop_back(); + for (int i = 0; i < count; i++) { + patch_jump(if_jmp_addrs.back()->get()); + if_jmp_addrs.pop_back(); + } + if_jmp_addrs.push_back(else_jmp_addr); } diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h index 6303db71fd7..cb724d89523 100644 --- a/modules/gdscript/gdscript_byte_codegen.h +++ b/modules/gdscript/gdscript_byte_codegen.h @@ -532,7 +532,7 @@ public: virtual void write_construct_typed_dictionary(const Address &p_target, const GDScriptDataType &p_key_type, const GDScriptDataType &p_value_type, const Vector
&p_arguments) override; virtual void write_await(const Address &p_target, const Address &p_operand) override; virtual void write_if(const Address &p_condition) override; - virtual void write_else() override; + virtual void write_else(int count) override; virtual void write_endif() override; virtual void write_jump_if_shared(const Address &p_value) override; virtual void write_end_jump_if_shared() override; diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h index f3c4acf1c34..a30e5db9e04 100644 --- a/modules/gdscript/gdscript_codegen.h +++ b/modules/gdscript/gdscript_codegen.h @@ -145,7 +145,7 @@ public: virtual void write_construct_typed_dictionary(const Address &p_target, const GDScriptDataType &p_key_type, const GDScriptDataType &p_value_type, const Vector
&p_arguments) = 0; virtual void write_await(const Address &p_target, const Address &p_operand) = 0; virtual void write_if(const Address &p_condition) = 0; - virtual void write_else() = 0; + virtual void write_else(int count) = 0; virtual void write_endif() = 0; virtual void write_jump_if_shared(const Address &p_value) = 0; virtual void write_end_jump_if_shared() = 0; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index f4f445e0966..0c928aa186f 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -251,6 +251,45 @@ static bool _can_use_validate_call(const MethodBind *p_method, const Vectoridentifier->name]; + GDScriptDataType local_type = _gdtype_from_datatype(p_variable->get_datatype(), codegen.script); + + Error err = OK; + + bool initialized = false; + if (p_variable->initializer != nullptr) { + GDScriptCodeGenerator::Address src_address = _parse_expression(codegen, err, p_variable->initializer); + if (err) { + return err; + } + if (p_variable->use_conversion_assign) { + gen->write_assign_with_conversion(local, src_address); + } else { + gen->write_assign(local, src_address); + } + if (src_address.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); + } + initialized = true; + } else if ((local_type.has_type && local_type.kind == GDScriptDataType::BUILTIN) || codegen.generator->is_local_dirty(local)) { + // Initialize with default for the type. Built-in types must always be cleared (they cannot be `null`). + // Objects and untyped variables are assigned to `null` only if the stack address has been re-used and not cleared. + codegen.generator->clear_address(local); + initialized = true; + } + + // Don't check `is_local_dirty()` since the variable must be assigned to `null` **on each iteration**. + if (!initialized && p_block->is_in_loop) { + codegen.generator->clear_address(local); + } + + return err; +} + GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression, bool p_root, bool p_initializer) { if (p_expression->is_constant && !(p_expression->get_datatype().is_meta_type && p_expression->get_datatype().kind == GDScriptParser::DataType::CLASS)) { return codegen.add_constant(p_expression->reduced_value); @@ -1878,6 +1917,72 @@ void GDScriptCompiler::_clear_block_locals(CodeGen &codegen, const List block_locals; + + gen->clear_temporaries(); + codegen.start_block(); + block_locals = _add_block_locals(codegen, if_n->condition_block); + + int count = 0; + const List::Element *E = if_n->conditions.front(); + while (E) { + const GDScriptParser::Node *condition_n = E->get(); + if (condition_n->is_expression()) { + const GDScriptParser::ExpressionNode *expression_n = static_cast(condition_n); + + GDScriptCodeGenerator::Address condition = _parse_expression(codegen, err, expression_n); + if (err) { + return err; + } + gen->write_if(condition); + + if (condition.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); + } + + count++; + } else if (condition_n->type == GDScriptParser::Node::VARIABLE) { + const GDScriptParser::VariableNode *variable_n = static_cast(condition_n); + + err = _parse_variable(codegen, variable_n, if_n->condition_block); + if (err) { + return err; + } + + gen->clear_temporaries(); + } + E = E->next(); + } + + err = _parse_block(codegen, if_n->true_block); + if (err) { + return err; + } + + _clear_block_locals(codegen, block_locals); + codegen.end_block(); + + if (if_n->false_block) { + gen->write_else(count); + + err = _parse_block(codegen, if_n->false_block); + if (err) { + return err; + } + + gen->write_endif(); + } else { + for (int i = 0; i < count; i++) { + gen->write_endif(); + } + } + + return OK; +} + Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block, bool p_add_locals, bool p_clear_locals) { Error err = OK; GDScriptCodeGenerator *gen = codegen.generator; @@ -1935,7 +2040,7 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui for (int j = 0; j < match->branches.size(); j++) { if (j > 0) { // Use `else` to not check the next branch after matching. - gen->write_else(); + gen->write_else(1); } const GDScriptParser::MatchBranchNode *branch = match->branches[j]; @@ -2004,32 +2109,10 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui } break; case GDScriptParser::Node::IF: { const GDScriptParser::IfNode *if_n = static_cast(s); - GDScriptCodeGenerator::Address condition = _parse_expression(codegen, err, if_n->condition); + err = _parse_if(codegen, if_n); if (err) { return err; } - - gen->write_if(condition); - - if (condition.mode == GDScriptCodeGenerator::Address::TEMPORARY) { - codegen.generator->pop_temporary(); - } - - err = _parse_block(codegen, if_n->true_block); - if (err) { - return err; - } - - if (if_n->false_block) { - gen->write_else(); - - err = _parse_block(codegen, if_n->false_block); - if (err) { - return err; - } - } - - gen->write_endif(); } break; case GDScriptParser::Node::FOR: { const GDScriptParser::ForNode *for_n = static_cast(s); @@ -2166,36 +2249,10 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui #endif } break; case GDScriptParser::Node::VARIABLE: { - const GDScriptParser::VariableNode *lv = static_cast(s); - // Should be already in stack when the block began. - GDScriptCodeGenerator::Address local = codegen.locals[lv->identifier->name]; - GDScriptDataType local_type = _gdtype_from_datatype(lv->get_datatype(), codegen.script); - - bool initialized = false; - if (lv->initializer != nullptr) { - GDScriptCodeGenerator::Address src_address = _parse_expression(codegen, err, lv->initializer); - if (err) { - return err; - } - if (lv->use_conversion_assign) { - gen->write_assign_with_conversion(local, src_address); - } else { - gen->write_assign(local, src_address); - } - if (src_address.mode == GDScriptCodeGenerator::Address::TEMPORARY) { - codegen.generator->pop_temporary(); - } - initialized = true; - } else if ((local_type.has_type && local_type.kind == GDScriptDataType::BUILTIN) || codegen.generator->is_local_dirty(local)) { - // Initialize with default for the type. Built-in types must always be cleared (they cannot be `null`). - // Objects and untyped variables are assigned to `null` only if the stack address has been re-used and not cleared. - codegen.generator->clear_address(local); - initialized = true; - } - - // Don't check `is_local_dirty()` since the variable must be assigned to `null` **on each iteration**. - if (!initialized && p_block->is_in_loop) { - codegen.generator->clear_address(local); + const GDScriptParser::VariableNode *variable_n = static_cast(s); + err = _parse_variable(codegen, variable_n, p_block); + if (err) { + return err; } } break; case GDScriptParser::Node::CONSTANT: { diff --git a/modules/gdscript/gdscript_compiler.h b/modules/gdscript/gdscript_compiler.h index 45f0f9e19b9..8fbe44585ad 100644 --- a/modules/gdscript/gdscript_compiler.h +++ b/modules/gdscript/gdscript_compiler.h @@ -155,6 +155,8 @@ class GDScriptCompiler { GDScriptCodeGenerator::Address _parse_match_pattern(CodeGen &codegen, Error &r_error, const GDScriptParser::PatternNode *p_pattern, const GDScriptCodeGenerator::Address &p_value_addr, const GDScriptCodeGenerator::Address &p_type_addr, const GDScriptCodeGenerator::Address &p_previous_test, bool p_is_first, bool p_is_nested); List _add_block_locals(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block); void _clear_block_locals(CodeGen &codegen, const List &p_locals); + Error _parse_variable(CodeGen &codegen, const GDScriptParser::VariableNode *p_variable, const GDScriptParser::SuiteNode *p_block); + Error _parse_if(CodeGen &codegen, const GDScriptParser::IfNode *if_n); Error _parse_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block, bool p_add_locals = true, bool p_clear_locals = true); GDScriptFunction *_parse_function(Error &r_error, GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready = false, bool p_for_lambda = false); GDScriptFunction *_make_static_initializer(Error &r_error, GDScript *p_script, const GDScriptParser::ClassNode *p_class); diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 3de1decc180..60c23807581 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -2205,25 +2205,33 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, } } - if (suite->parent_if && suite->parent_if->condition && suite->parent_if->condition->type == GDScriptParser::Node::TYPE_TEST) { - // Operator `is` used, check if identifier is in there! this helps resolve in blocks that are (if (identifier is value)): which are very common.. - // Super dirty hack, but very useful. - // Credit: Zylann. - // TODO: this could be hacked to detect ANDed conditions too... - const GDScriptParser::TypeTestNode *type_test = static_cast(suite->parent_if->condition); - if (type_test->operand && type_test->test_type && type_test->operand->type == GDScriptParser::Node::IDENTIFIER && static_cast(type_test->operand)->name == p_identifier->name && static_cast(type_test->operand)->source == p_identifier->source) { - // Bingo. - GDScriptParser::CompletionContext c = 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 = type_test->test_datatype; - if (last_assign_line < c.current_line) { - // Override last assignment. - last_assign_line = c.current_line; - last_assigned_expression = nullptr; + if (suite->parent_if) { + List::Element *E = suite->parent_if->conditions.front(); + while (E) { + GDScriptParser::Node *condition = E->get(); + if (condition->is_expression() && condition->type == GDScriptParser::Node::TYPE_TEST) { + // Operator `is` used, check if identifier is in there! this helps resolve in blocks that are (if (identifier is value)): which are very common.. + // Super dirty hack, but very useful. + // Credit: Zylann. + // TODO: this could be hacked to detect ANDed conditions too... + const GDScriptParser::TypeTestNode *type_test = static_cast(condition); + if (type_test->operand && type_test->test_type && type_test->operand->type == GDScriptParser::Node::IDENTIFIER && static_cast(type_test->operand)->name == p_identifier->name && static_cast(type_test->operand)->source == p_identifier->source) { + // Bingo. + GDScriptParser::CompletionContext c = 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 = type_test->test_datatype; + if (last_assign_line < c.current_line) { + // Override last assignment. + last_assign_line = c.current_line; + last_assigned_expression = nullptr; + } + } + break; } } + E = E->next(); } } diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 111a39d7302..9fc99284c33 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -1076,7 +1076,7 @@ GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_is_static) { return parse_variable(p_is_static, true); } -GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_is_static, bool p_allow_property) { +GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_is_static, bool p_allow_property, bool p_is_if_condition) { VariableNode *variable = alloc_node(); if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected variable name after "var".)")) { @@ -1124,6 +1124,8 @@ GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_is_static, b push_error(R"(Expected expression for variable initial value after "=".)"); } variable->assignments++; + } else if (p_is_if_condition) { + push_error(R"(Expected expression for initial value after variable declaration.)"); } if (p_allow_property && match(GDScriptTokenizer::Token::COLON)) { @@ -1135,7 +1137,10 @@ GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_is_static, b } complete_extents(variable); - end_statement("variable declaration"); + + if (p_is_if_condition == false) { + end_statement("variable declaration"); + } return variable; } @@ -2085,21 +2090,70 @@ GDScriptParser::ForNode *GDScriptParser::parse_for() { } GDScriptParser::IfNode *GDScriptParser::parse_if(const String &p_token) { - IfNode *n_if = alloc_node(); + SuiteNode *condition_block = alloc_node(); + condition_block->parent_block = current_suite; + condition_block->parent_function = current_function; + SuiteNode *saved_suite = current_suite; + current_suite = condition_block; - n_if->condition = parse_expression(false); - if (n_if->condition == nullptr) { - push_error(vformat(R"(Expected conditional expression after "%s".)", p_token)); - } + List conditions; + do { + if (match(GDScriptTokenizer::Token::VAR)) { + // Variable declaration + VariableNode *variable = parse_variable(false, false, true); + if (variable == nullptr) { + push_error(vformat(R"(Expected variable definition after "%s".)", p_token)); + break; + } else { + const SuiteNode::Local &local = current_suite->get_local(variable->identifier->name); + if (local.type != SuiteNode::Local::UNDEFINED) { + push_error(vformat(R"(There is already a %s named "%s" declared in this scope.)", local.get_name(), variable->identifier->name), variable->identifier); + break; + } + condition_block->add_local(variable, current_function); + + IdentifierNode *identifier = alloc_node(); + identifier->name = variable->identifier->name; + identifier->suite = condition_block; + identifier->source = IdentifierNode::Source::LOCAL_VARIABLE; + const SuiteNode::Local &declaration = condition_block->get_local(identifier->name); + identifier->variable_source = declaration.variable; + declaration.variable->usages++; + complete_extents(identifier); + + condition_block->statements.push_back(variable); + + conditions.push_back(variable); + conditions.push_back(identifier); + } + } else { + // Expression + ExpressionNode *expression = parse_expression(false); + if (expression == nullptr) { + push_error(vformat(R"(Expected conditional expression after "%s".)", p_token)); + break; + } else { + conditions.push_back(expression); + } + } + } while (match(GDScriptTokenizer::Token::COMMA)); consume(GDScriptTokenizer::Token::COLON, vformat(R"(Expected ":" after "%s" condition.)", p_token)); - n_if->true_block = parse_suite(vformat(R"("%s" block)", p_token)); - n_if->true_block->parent_if = n_if; + SuiteNode *true_block = parse_suite(vformat(R"("%s" block)", p_token)); - if (n_if->true_block->has_continue) { - current_suite->has_continue = true; - } + complete_extents(condition_block); + current_suite = saved_suite; + + IfNode *n_if = alloc_node(); + + true_block->parent_function = current_function; + true_block->parent_block = condition_block; + true_block->parent_if = n_if; + + n_if->conditions = conditions; + n_if->condition_block = condition_block; + n_if->true_block = true_block; if (match(GDScriptTokenizer::Token::ELIF)) { SuiteNode *else_block = alloc_node(); @@ -2121,10 +2175,10 @@ GDScriptParser::IfNode *GDScriptParser::parse_if(const String &p_token) { } complete_extents(n_if); - if (n_if->false_block != nullptr && n_if->false_block->has_return && n_if->true_block->has_return) { + if ((n_if->false_block != nullptr && n_if->false_block->has_return) && n_if->true_block->has_return) { current_suite->has_return = true; } - if (n_if->false_block != nullptr && n_if->false_block->has_continue) { + if ((n_if->false_block != nullptr && n_if->false_block->has_continue) || n_if->true_block->has_continue) { current_suite->has_continue = true; } @@ -4785,11 +4839,19 @@ bool GDScriptParser::warning_annotations(AnnotationNode *p_annotation, Node *p_t // Contain bodies. SIMPLE_CASE(Node::FOR, ForNode, list) - SIMPLE_CASE(Node::IF, IfNode, condition) SIMPLE_CASE(Node::MATCH, MatchNode, test) SIMPLE_CASE(Node::WHILE, WhileNode, condition) #undef SIMPLE_CASE + case Node::IF: { + IfNode *node = static_cast(p_target); + if (node->conditions.is_empty()) { + end_line = node->start_line; + } else { + end_line = node->conditions.back()->get()->end_line; + } + } break; + case Node::CLASS: { end_line = p_target->start_line; for (const AnnotationNode *annotation : p_target->annotations) { @@ -5742,7 +5804,27 @@ void GDScriptParser::TreePrinter::print_if(IfNode *p_if, bool p_is_elif) { } else { push_text("If "); } - print_expression(p_if->condition); + List::Element *E = p_if->conditions.front(); + bool first = true; + while (E) { + if (first) { + first = false; + } else { + push_text(", "); + } + Node *node = E->get(); + if (node->is_expression()) { + print_expression(static_cast(node)); + } else if (node->type == Node::VARIABLE) { + print_variable(static_cast(node)); + // Skip next identifier condition + E = E->next(); + } else { + ERR_PRINT("BUG: invalid condition"); + } + E = E->next(); + } + push_line(" :"); increase_indent(); diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 7f64ae902b0..5aa50fc8965 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -918,7 +918,8 @@ public: }; struct IfNode : public Node { - ExpressionNode *condition = nullptr; + List conditions; + SuiteNode *condition_block = nullptr; SuiteNode *true_block = nullptr; SuiteNode *false_block = nullptr; @@ -1516,7 +1517,7 @@ private: // Statements. Node *parse_statement(); VariableNode *parse_variable(bool p_is_static); - VariableNode *parse_variable(bool p_is_static, bool p_allow_property); + VariableNode *parse_variable(bool p_is_static, bool p_allow_property, bool p_is_if_condition = false); VariableNode *parse_property(VariableNode *p_variable, bool p_need_indent); void parse_property_getter(VariableNode *p_variable); void parse_property_setter(VariableNode *p_variable); diff --git a/modules/gdscript/tests/scripts/analyzer/errors/assignment_in_var_if_and_use_in_elif.gd b/modules/gdscript/tests/scripts/analyzer/errors/assignment_in_var_if_and_use_in_elif.gd new file mode 100644 index 00000000000..947fed09969 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/assignment_in_var_if_and_use_in_elif.gd @@ -0,0 +1,5 @@ +func test(): + if var x = 100: + print("t") + elif x > 0: + print("f") diff --git a/modules/gdscript/tests/scripts/analyzer/errors/assignment_in_var_if_and_use_in_elif.out b/modules/gdscript/tests/scripts/analyzer/errors/assignment_in_var_if_and_use_in_elif.out new file mode 100644 index 00000000000..ef7c66078e5 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/assignment_in_var_if_and_use_in_elif.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Identifier "x" not declared in the current scope. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/assignment_in_var_if_and_use_in_else.gd b/modules/gdscript/tests/scripts/analyzer/errors/assignment_in_var_if_and_use_in_else.gd new file mode 100644 index 00000000000..6ad28103377 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/assignment_in_var_if_and_use_in_else.gd @@ -0,0 +1,5 @@ +func test(): + if var x = 100: + print("t") + else: + print(x) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/assignment_in_var_if_and_use_in_else.out b/modules/gdscript/tests/scripts/analyzer/errors/assignment_in_var_if_and_use_in_else.out new file mode 100644 index 00000000000..ef7c66078e5 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/assignment_in_var_if_and_use_in_else.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Identifier "x" not declared in the current scope. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/assignment_in_var_if_and_use_outside.gd b/modules/gdscript/tests/scripts/analyzer/errors/assignment_in_var_if_and_use_outside.gd new file mode 100644 index 00000000000..3afec0d3673 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/assignment_in_var_if_and_use_outside.gd @@ -0,0 +1,5 @@ +func test(): + if var x = 100: + print("t") + + print(x) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/assignment_in_var_if_and_use_outside.out b/modules/gdscript/tests/scripts/analyzer/errors/assignment_in_var_if_and_use_outside.out new file mode 100644 index 00000000000..ef7c66078e5 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/assignment_in_var_if_and_use_outside.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Identifier "x" not declared in the current scope. diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_in_var_if.gd b/modules/gdscript/tests/scripts/parser/errors/assignment_in_var_if.gd deleted file mode 100644 index a99557fa3c5..00000000000 --- a/modules/gdscript/tests/scripts/parser/errors/assignment_in_var_if.gd +++ /dev/null @@ -1,4 +0,0 @@ -func test(): - # Error here. - if var foo = 25: - print(foo) diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_in_var_if.out b/modules/gdscript/tests/scripts/parser/errors/assignment_in_var_if.out deleted file mode 100644 index e84f4652acd..00000000000 --- a/modules/gdscript/tests/scripts/parser/errors/assignment_in_var_if.out +++ /dev/null @@ -1,2 +0,0 @@ -GDTEST_PARSER_ERROR -Expected conditional expression after "if". diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_in_var_if_duplicated_definition.gd b/modules/gdscript/tests/scripts/parser/errors/assignment_in_var_if_duplicated_definition.gd new file mode 100644 index 00000000000..7f005adb5d2 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/assignment_in_var_if_duplicated_definition.gd @@ -0,0 +1,3 @@ +func test(): + if var x := 1, var x := 2: + print("t") diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_in_var_if_duplicated_definition.out b/modules/gdscript/tests/scripts/parser/errors/assignment_in_var_if_duplicated_definition.out new file mode 100644 index 00000000000..4d606f9faa1 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/assignment_in_var_if_duplicated_definition.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +There is already a variable named "x" declared in this scope. diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_in_var_if_missing_init_value.gd b/modules/gdscript/tests/scripts/parser/errors/assignment_in_var_if_missing_init_value.gd new file mode 100644 index 00000000000..c44987ad7bc --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/assignment_in_var_if_missing_init_value.gd @@ -0,0 +1,3 @@ +func test(): + if var x, true: + pass diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_in_var_if_missing_init_value.out b/modules/gdscript/tests/scripts/parser/errors/assignment_in_var_if_missing_init_value.out new file mode 100644 index 00000000000..aa0b59cf03b --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/assignment_in_var_if_missing_init_value.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected expression for initial value after variable declaration. diff --git a/modules/gdscript/tests/scripts/runtime/assignment_in_var_if.gd b/modules/gdscript/tests/scripts/runtime/assignment_in_var_if.gd new file mode 100644 index 00000000000..5668d3b670d --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/assignment_in_var_if.gd @@ -0,0 +1,53 @@ +func foo(n: int) -> bool: + print("foo") + return n > 0 + +func bar(n: int) -> int: + print("bar") + return n + +func add(a: int, b: int) -> int: + print("add") + return a + b + +func test_if(f: bool, a: int, b: int) -> void: + print("--") + if f, foo(a), var n := bar(b): + print("t:%s" % n) + elif var n = add(a, b), n >= 1: + print("tt:%s" % n) + else: + print("f") + +func test(): + if var x = 25: + print(x) + + if var x = 25: + print(x) + else: + print("ff") + + if var x = 0: + print(x) + + if var x = 0: + print(x) + else: + print("fff") + + if var x := 100: + if x >= 1, var y := "ttt": + print(y) + print(100 + signi(x)) + + test_if(false, 0, 0); + test_if(false, 0, 1); + test_if(false, 1, 0); + test_if(false, 1, 1); + test_if(true, 0, 0); + test_if(true, 0, 1); + test_if(true, 0, 2); + test_if(true, 1, 0); + test_if(true, 1, 1); + test_if(true, 1, 2); diff --git a/modules/gdscript/tests/scripts/runtime/assignment_in_var_if.out b/modules/gdscript/tests/scripts/runtime/assignment_in_var_if.out new file mode 100644 index 00000000000..688e6631a29 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/assignment_in_var_if.out @@ -0,0 +1,43 @@ +GDTEST_OK +25 +25 +fff +ttt +101 +-- +add +f +-- +add +tt:1 +-- +add +tt:1 +-- +add +tt:2 +-- +foo +add +f +-- +foo +add +tt:1 +-- +foo +add +tt:2 +-- +foo +bar +add +tt:1 +-- +foo +bar +t:1 +-- +foo +bar +t:2