Skip to content

Commit 2a47a01

Browse files
committed
GDScript: support variable definition as condition in if-statement (godotengine/godot-proposals#2727)
1 parent e25776e commit 2a47a01

18 files changed

+241
-75
lines changed

modules/gdscript/editor/gdscript_translation_parser_plugin.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,9 @@ void GDScriptEditorTranslationParserPlugin::_traverse_block(const GDScriptParser
135135
} break;
136136
case GDScriptParser::Node::IF: {
137137
const GDScriptParser::IfNode *if_node = static_cast<const GDScriptParser::IfNode *>(statement);
138+
if (if_node->variable) {
139+
_assess_expression(if_node->variable->initializer);
140+
}
138141
_assess_expression(if_node->condition);
139142
_traverse_block(if_node->true_block);
140143
_traverse_block(if_node->false_block);

modules/gdscript/gdscript_analyzer.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2096,6 +2096,9 @@ void GDScriptAnalyzer::resolve_parameter(GDScriptParser::ParameterNode *p_parame
20962096
}
20972097

20982098
void GDScriptAnalyzer::resolve_if(GDScriptParser::IfNode *p_if) {
2099+
if (p_if->variable) {
2100+
resolve_variable(p_if->variable, true);
2101+
}
20992102
reduce_expression(p_if->condition);
21002103

21012104
resolve_suite(p_if->true_block);

modules/gdscript/gdscript_compiler.cpp

Lines changed: 94 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,45 @@ static bool _can_use_validate_call(const MethodBind *p_method, const Vector<GDSc
251251
return true;
252252
}
253253

254+
Error GDScriptCompiler::_parse_variable(CodeGen &codegen, const GDScriptParser::VariableNode *p_variable, const GDScriptParser::SuiteNode *p_block) {
255+
GDScriptCodeGenerator *gen = codegen.generator;
256+
257+
// Should be already in stack when the block began.
258+
GDScriptCodeGenerator::Address local = codegen.locals[p_variable->identifier->name];
259+
GDScriptDataType local_type = _gdtype_from_datatype(p_variable->get_datatype(), codegen.script);
260+
261+
Error err = OK;
262+
263+
bool initialized = false;
264+
if (p_variable->initializer != nullptr) {
265+
GDScriptCodeGenerator::Address src_address = _parse_expression(codegen, err, p_variable->initializer);
266+
if (err) {
267+
return err;
268+
}
269+
if (p_variable->use_conversion_assign) {
270+
gen->write_assign_with_conversion(local, src_address);
271+
} else {
272+
gen->write_assign(local, src_address);
273+
}
274+
if (src_address.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
275+
codegen.generator->pop_temporary();
276+
}
277+
initialized = true;
278+
} else if ((local_type.has_type && local_type.kind == GDScriptDataType::BUILTIN) || codegen.generator->is_local_dirty(local)) {
279+
// Initialize with default for the type. Built-in types must always be cleared (they cannot be `null`).
280+
// Objects and untyped variables are assigned to `null` only if the stack address has been re-used and not cleared.
281+
codegen.generator->clear_address(local);
282+
initialized = true;
283+
}
284+
285+
// Don't check `is_local_dirty()` since the variable must be assigned to `null` **on each iteration**.
286+
if (!initialized && p_block->is_in_loop) {
287+
codegen.generator->clear_address(local);
288+
}
289+
290+
return err;
291+
}
292+
254293
GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression, bool p_root, bool p_initializer) {
255294
if (p_expression->is_constant && !(p_expression->get_datatype().is_meta_type && p_expression->get_datatype().kind == GDScriptParser::DataType::CLASS)) {
256295
return codegen.add_constant(p_expression->reduced_value);
@@ -1878,6 +1917,56 @@ void GDScriptCompiler::_clear_block_locals(CodeGen &codegen, const List<GDScript
18781917
}
18791918
}
18801919

1920+
Error GDScriptCompiler::_parse_if(CodeGen &codegen, const GDScriptParser::IfNode *if_n) {
1921+
Error err = OK;
1922+
GDScriptCodeGenerator *gen = codegen.generator;
1923+
List<GDScriptCodeGenerator::Address> block_locals;
1924+
1925+
gen->clear_temporaries();
1926+
codegen.start_block();
1927+
block_locals = _add_block_locals(codegen, if_n->condition_block);
1928+
1929+
if (if_n->variable) {
1930+
err = _parse_variable(codegen, if_n->variable, if_n->condition_block);
1931+
if (err) {
1932+
return err;
1933+
}
1934+
1935+
gen->clear_temporaries();
1936+
}
1937+
1938+
GDScriptCodeGenerator::Address condition = _parse_expression(codegen, err, if_n->condition);
1939+
if (err) {
1940+
return err;
1941+
}
1942+
gen->write_if(condition);
1943+
1944+
if (condition.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
1945+
codegen.generator->pop_temporary();
1946+
}
1947+
1948+
err = _parse_block(codegen, if_n->true_block);
1949+
if (err) {
1950+
return err;
1951+
}
1952+
1953+
_clear_block_locals(codegen, block_locals);
1954+
codegen.end_block();
1955+
1956+
if (if_n->false_block) {
1957+
gen->write_else();
1958+
1959+
err = _parse_block(codegen, if_n->false_block);
1960+
if (err) {
1961+
return err;
1962+
}
1963+
}
1964+
1965+
gen->write_endif();
1966+
1967+
return OK;
1968+
}
1969+
18811970
Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block, bool p_add_locals, bool p_clear_locals) {
18821971
Error err = OK;
18831972
GDScriptCodeGenerator *gen = codegen.generator;
@@ -2004,32 +2093,10 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
20042093
} break;
20052094
case GDScriptParser::Node::IF: {
20062095
const GDScriptParser::IfNode *if_n = static_cast<const GDScriptParser::IfNode *>(s);
2007-
GDScriptCodeGenerator::Address condition = _parse_expression(codegen, err, if_n->condition);
2096+
err = _parse_if(codegen, if_n);
20082097
if (err) {
20092098
return err;
20102099
}
2011-
2012-
gen->write_if(condition);
2013-
2014-
if (condition.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
2015-
codegen.generator->pop_temporary();
2016-
}
2017-
2018-
err = _parse_block(codegen, if_n->true_block);
2019-
if (err) {
2020-
return err;
2021-
}
2022-
2023-
if (if_n->false_block) {
2024-
gen->write_else();
2025-
2026-
err = _parse_block(codegen, if_n->false_block);
2027-
if (err) {
2028-
return err;
2029-
}
2030-
}
2031-
2032-
gen->write_endif();
20332100
} break;
20342101
case GDScriptParser::Node::FOR: {
20352102
const GDScriptParser::ForNode *for_n = static_cast<const GDScriptParser::ForNode *>(s);
@@ -2166,36 +2233,10 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
21662233
#endif
21672234
} break;
21682235
case GDScriptParser::Node::VARIABLE: {
2169-
const GDScriptParser::VariableNode *lv = static_cast<const GDScriptParser::VariableNode *>(s);
2170-
// Should be already in stack when the block began.
2171-
GDScriptCodeGenerator::Address local = codegen.locals[lv->identifier->name];
2172-
GDScriptDataType local_type = _gdtype_from_datatype(lv->get_datatype(), codegen.script);
2173-
2174-
bool initialized = false;
2175-
if (lv->initializer != nullptr) {
2176-
GDScriptCodeGenerator::Address src_address = _parse_expression(codegen, err, lv->initializer);
2177-
if (err) {
2178-
return err;
2179-
}
2180-
if (lv->use_conversion_assign) {
2181-
gen->write_assign_with_conversion(local, src_address);
2182-
} else {
2183-
gen->write_assign(local, src_address);
2184-
}
2185-
if (src_address.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
2186-
codegen.generator->pop_temporary();
2187-
}
2188-
initialized = true;
2189-
} else if ((local_type.has_type && local_type.kind == GDScriptDataType::BUILTIN) || codegen.generator->is_local_dirty(local)) {
2190-
// Initialize with default for the type. Built-in types must always be cleared (they cannot be `null`).
2191-
// Objects and untyped variables are assigned to `null` only if the stack address has been re-used and not cleared.
2192-
codegen.generator->clear_address(local);
2193-
initialized = true;
2194-
}
2195-
2196-
// Don't check `is_local_dirty()` since the variable must be assigned to `null` **on each iteration**.
2197-
if (!initialized && p_block->is_in_loop) {
2198-
codegen.generator->clear_address(local);
2236+
const GDScriptParser::VariableNode *variable_n = static_cast<const GDScriptParser::VariableNode *>(s);
2237+
err = _parse_variable(codegen, variable_n, p_block);
2238+
if (err) {
2239+
return err;
21992240
}
22002241
} break;
22012242
case GDScriptParser::Node::CONSTANT: {

modules/gdscript/gdscript_compiler.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,8 @@ class GDScriptCompiler {
155155
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);
156156
List<GDScriptCodeGenerator::Address> _add_block_locals(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block);
157157
void _clear_block_locals(CodeGen &codegen, const List<GDScriptCodeGenerator::Address> &p_locals);
158+
Error _parse_variable(CodeGen &codegen, const GDScriptParser::VariableNode *p_variable, const GDScriptParser::SuiteNode *p_block);
159+
Error _parse_if(CodeGen &codegen, const GDScriptParser::IfNode *if_n);
158160
Error _parse_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block, bool p_add_locals = true, bool p_clear_locals = true);
159161
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);
160162
GDScriptFunction *_make_static_initializer(Error &r_error, GDScript *p_script, const GDScriptParser::ClassNode *p_class);

modules/gdscript/gdscript_parser.cpp

Lines changed: 83 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1076,7 +1076,7 @@ GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_is_static) {
10761076
return parse_variable(p_is_static, true);
10771077
}
10781078

1079-
GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_is_static, bool p_allow_property) {
1079+
GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_is_static, bool p_allow_property, bool p_is_if_condition) {
10801080
VariableNode *variable = alloc_node<VariableNode>();
10811081

10821082
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected variable name after "var".)")) {
@@ -1135,7 +1135,10 @@ GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_is_static, b
11351135
}
11361136

11371137
complete_extents(variable);
1138-
end_statement("variable declaration");
1138+
1139+
if (p_is_if_condition == false) {
1140+
end_statement("variable declaration");
1141+
}
11391142

11401143
return variable;
11411144
}
@@ -2087,21 +2090,68 @@ GDScriptParser::ForNode *GDScriptParser::parse_for() {
20872090
}
20882091

20892092
GDScriptParser::IfNode *GDScriptParser::parse_if(const String &p_token) {
2090-
IfNode *n_if = alloc_node<IfNode>();
2093+
SuiteNode *condition_block = alloc_node<SuiteNode>();
2094+
condition_block->parent_block = current_suite;
2095+
condition_block->parent_function = current_function;
2096+
SuiteNode *saved_suite = current_suite;
2097+
current_suite = condition_block;
2098+
2099+
VariableNode *variable = nullptr;
2100+
ExpressionNode *condition = nullptr;
2101+
if (match(GDScriptTokenizer::Token::VAR)) {
2102+
// Variable declaration
2103+
variable = parse_variable(false, false, true);
2104+
if (variable == nullptr) {
2105+
push_error(vformat(R"(Expected variable definition after "%s".)", p_token));
2106+
} else if (variable->initializer == nullptr) {
2107+
push_error(R"(Expected expression for variable initial value.)");
2108+
} else {
2109+
const SuiteNode::Local &local = current_suite->get_local(variable->identifier->name);
2110+
if (local.type != SuiteNode::Local::UNDEFINED) {
2111+
push_error(vformat(R"(There is already a %s named "%s" declared in this scope.)", local.get_name(), variable->identifier->name), variable->identifier);
2112+
}
2113+
condition_block->add_local(variable, current_function);
2114+
2115+
IdentifierNode *identifier = alloc_node<IdentifierNode>();
2116+
identifier->name = variable->identifier->name;
2117+
identifier->suite = condition_block;
2118+
identifier->source = IdentifierNode::Source::LOCAL_VARIABLE;
2119+
const SuiteNode::Local &declaration = condition_block->get_local(identifier->name);
2120+
identifier->variable_source = declaration.variable;
2121+
declaration.variable->usages++;
2122+
complete_extents(identifier);
2123+
2124+
condition_block->statements.push_back(variable);
20912125

2092-
n_if->condition = parse_expression(false);
2093-
if (n_if->condition == nullptr) {
2094-
push_error(vformat(R"(Expected conditional expression after "%s".)", p_token));
2126+
condition = identifier;
2127+
}
2128+
} else {
2129+
// Expression
2130+
ExpressionNode *expression = parse_expression(false);
2131+
if (expression == nullptr) {
2132+
push_error(vformat(R"(Expected conditional expression after "%s".)", p_token));
2133+
} else {
2134+
condition = expression;
2135+
}
20952136
}
20962137

20972138
consume(GDScriptTokenizer::Token::COLON, vformat(R"(Expected ":" after "%s" condition.)", p_token));
20982139

2099-
n_if->true_block = parse_suite(vformat(R"("%s" block)", p_token));
2100-
n_if->true_block->parent_if = n_if;
2140+
SuiteNode *true_block = parse_suite(vformat(R"("%s" block)", p_token));
21012141

2102-
if (n_if->true_block->has_continue) {
2103-
current_suite->has_continue = true;
2104-
}
2142+
complete_extents(condition_block);
2143+
current_suite = saved_suite;
2144+
2145+
IfNode *n_if = alloc_node<IfNode>();
2146+
2147+
true_block->parent_function = current_function;
2148+
true_block->parent_block = condition_block;
2149+
true_block->parent_if = n_if;
2150+
2151+
n_if->variable = variable;
2152+
n_if->condition = condition;
2153+
n_if->condition_block = condition_block;
2154+
n_if->true_block = true_block;
21052155

21062156
if (match(GDScriptTokenizer::Token::ELIF)) {
21072157
SuiteNode *else_block = alloc_node<SuiteNode>();
@@ -2123,10 +2173,10 @@ GDScriptParser::IfNode *GDScriptParser::parse_if(const String &p_token) {
21232173
}
21242174
complete_extents(n_if);
21252175

2126-
if (n_if->false_block != nullptr && n_if->false_block->has_return && n_if->true_block->has_return) {
2176+
if ((n_if->false_block != nullptr && n_if->false_block->has_return) && n_if->true_block->has_return) {
21272177
current_suite->has_return = true;
21282178
}
2129-
if (n_if->false_block != nullptr && n_if->false_block->has_continue) {
2179+
if ((n_if->false_block != nullptr && n_if->false_block->has_continue) || n_if->true_block->has_continue) {
21302180
current_suite->has_continue = true;
21312181
}
21322182

@@ -4787,11 +4837,25 @@ bool GDScriptParser::warning_annotations(AnnotationNode *p_annotation, Node *p_t
47874837

47884838
// Contain bodies.
47894839
SIMPLE_CASE(Node::FOR, ForNode, list)
4790-
SIMPLE_CASE(Node::IF, IfNode, condition)
47914840
SIMPLE_CASE(Node::MATCH, MatchNode, test)
47924841
SIMPLE_CASE(Node::WHILE, WhileNode, condition)
47934842
#undef SIMPLE_CASE
47944843

4844+
case Node::IF: {
4845+
IfNode *if_n = static_cast<IfNode *>(p_target);
4846+
if (if_n->variable) {
4847+
if (if_n->variable->initializer) {
4848+
end_line = if_n->variable->initializer->end_line;
4849+
} else {
4850+
end_line = if_n->start_line;
4851+
}
4852+
} else if (if_n->condition) {
4853+
end_line = if_n->condition->end_line;
4854+
} else {
4855+
end_line = if_n->start_line;
4856+
}
4857+
} break;
4858+
47954859
case Node::CLASS: {
47964860
end_line = p_target->start_line;
47974861
for (const AnnotationNode *annotation : p_target->annotations) {
@@ -5744,7 +5808,11 @@ void GDScriptParser::TreePrinter::print_if(IfNode *p_if, bool p_is_elif) {
57445808
} else {
57455809
push_text("If ");
57465810
}
5747-
print_expression(p_if->condition);
5811+
if (p_if->variable) {
5812+
print_variable(p_if->variable);
5813+
} else {
5814+
print_expression(p_if->condition);
5815+
}
57485816
push_line(" :");
57495817

57505818
increase_indent();

modules/gdscript/gdscript_parser.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -918,7 +918,9 @@ class GDScriptParser {
918918
};
919919

920920
struct IfNode : public Node {
921+
VariableNode *variable = nullptr;
921922
ExpressionNode *condition = nullptr;
923+
SuiteNode *condition_block = nullptr;
922924
SuiteNode *true_block = nullptr;
923925
SuiteNode *false_block = nullptr;
924926

@@ -1516,7 +1518,7 @@ class GDScriptParser {
15161518
// Statements.
15171519
Node *parse_statement();
15181520
VariableNode *parse_variable(bool p_is_static);
1519-
VariableNode *parse_variable(bool p_is_static, bool p_allow_property);
1521+
VariableNode *parse_variable(bool p_is_static, bool p_allow_property, bool p_is_if_condition = false);
15201522
VariableNode *parse_property(VariableNode *p_variable, bool p_need_indent);
15211523
void parse_property_getter(VariableNode *p_variable);
15221524
void parse_property_setter(VariableNode *p_variable);
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
func test():
2+
if var x = 100:
3+
print("t")
4+
elif x > 0:
5+
print("f")
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
GDTEST_ANALYZER_ERROR
2+
Identifier "x" not declared in the current scope.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
func test():
2+
if var x = 100:
3+
print("t")
4+
else:
5+
print(x)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
GDTEST_ANALYZER_ERROR
2+
Identifier "x" not declared in the current scope.

0 commit comments

Comments
 (0)