Merge pull request #72206 from vnen/gdscript-allow-void-return-shorthand

GDScript: Allow void functions to return calls to other void functions
This commit is contained in:
Rémi Verschelde 2023-01-31 15:55:35 +01:00
commit 6d9c9d30b9
No known key found for this signature in database
GPG Key ID: C3336907360768E1
8 changed files with 78 additions and 10 deletions

View File

@ -477,6 +477,9 @@
<member name="debug/gdscript/warnings/unsafe_property_access" type="int" setter="" getter="" default="0"> <member name="debug/gdscript/warnings/unsafe_property_access" type="int" setter="" getter="" default="0">
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when accessing a property whose presence is not guaranteed at compile-time in the class. When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when accessing a property whose presence is not guaranteed at compile-time in the class.
</member> </member>
<member name="debug/gdscript/warnings/unsafe_void_return" type="int" setter="" getter="" default="0">
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when returning a call from a [code]void[/code] function when such call cannot be guaranteed to be also [code]void[/code].
</member>
<member name="debug/gdscript/warnings/unused_local_constant" type="int" setter="" getter="" default="1"> <member name="debug/gdscript/warnings/unused_local_constant" type="int" setter="" getter="" default="1">
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a local constant is never used. When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a local constant is never used.
</member> </member>

View File

@ -1972,17 +1972,40 @@ void GDScriptAnalyzer::resolve_return(GDScriptParser::ReturnNode *p_return) {
} }
if (p_return->return_value != nullptr) { if (p_return->return_value != nullptr) {
reduce_expression(p_return->return_value); bool is_void_function = has_expected_type && expected_type.is_hard_type() && expected_type.kind == GDScriptParser::DataType::BUILTIN && expected_type.builtin_type == Variant::NIL;
if (p_return->return_value->type == GDScriptParser::Node::ARRAY && has_expected_type && expected_type.has_container_element_type()) { bool is_call = p_return->return_value->type == GDScriptParser::Node::CALL;
update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_return->return_value), expected_type.get_container_element_type()); if (is_void_function && is_call) {
// Pretend the call is a root expression to allow those that are "void".
reduce_call(static_cast<GDScriptParser::CallNode *>(p_return->return_value), false, true);
} else {
reduce_expression(p_return->return_value);
} }
if (has_expected_type && expected_type.is_hard_type() && expected_type.kind == GDScriptParser::DataType::BUILTIN && expected_type.builtin_type == Variant::NIL) { if (is_void_function) {
push_error("A void function cannot return a value.", p_return); p_return->void_return = true;
const GDScriptParser::DataType &return_type = p_return->return_value->datatype;
if (is_call && !return_type.is_hard_type()) {
String function_name = parser->current_function->identifier ? parser->current_function->identifier->name.operator String() : String("<anonymous function>");
String called_function_name = static_cast<GDScriptParser::CallNode *>(p_return->return_value)->function_name.operator String();
#ifdef DEBUG_ENABLED
parser->push_warning(p_return, GDScriptWarning::UNSAFE_VOID_RETURN, function_name, called_function_name);
#endif
mark_node_unsafe(p_return);
} else if (!is_call) {
push_error("A void function cannot return a value.", p_return);
}
result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
result.kind = GDScriptParser::DataType::BUILTIN;
result.builtin_type = Variant::NIL;
result.is_constant = true;
} else {
if (p_return->return_value->type == GDScriptParser::Node::ARRAY && has_expected_type && expected_type.has_container_element_type()) {
update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_return->return_value), expected_type.get_container_element_type());
}
if (has_expected_type && expected_type.is_hard_type() && p_return->return_value->is_constant) {
update_const_expression_builtin_type(p_return->return_value, expected_type, "return");
}
result = p_return->return_value->get_datatype();
} }
if (has_expected_type && expected_type.is_hard_type() && p_return->return_value->is_constant) {
update_const_expression_builtin_type(p_return->return_value, expected_type, "return");
}
result = p_return->return_value->get_datatype();
} else { } else {
// Return type is null by default. // Return type is null by default.
result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;

View File

@ -1859,7 +1859,12 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
} }
} }
gen->write_return(return_value); if (return_n->void_return) {
// Always return "null", even if the expression is a call to a void function.
gen->write_return(codegen.add_constant(Variant()));
} else {
gen->write_return(return_value);
}
if (return_value.mode == GDScriptCodeGenerator::Address::TEMPORARY) { if (return_value.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
codegen.generator->pop_temporary(); codegen.generator->pop_temporary();
} }

View File

@ -970,6 +970,7 @@ public:
struct ReturnNode : public Node { struct ReturnNode : public Node {
ExpressionNode *return_value = nullptr; ExpressionNode *return_value = nullptr;
bool void_return = false;
ReturnNode() { ReturnNode() {
type = RETURN; type = RETURN;

View File

@ -125,6 +125,10 @@ String GDScriptWarning::get_message() const {
CHECK_SYMBOLS(4); CHECK_SYMBOLS(4);
return "The argument '" + symbols[0] + "' of the function '" + symbols[1] + "' requires a the subtype '" + symbols[2] + "' but the supertype '" + symbols[3] + "' was provided"; return "The argument '" + symbols[0] + "' of the function '" + symbols[1] + "' requires a the subtype '" + symbols[2] + "' but the supertype '" + symbols[3] + "' was provided";
} break; } break;
case UNSAFE_VOID_RETURN: {
CHECK_SYMBOLS(2);
return "The method '" + symbols[0] + "()' returns 'void' but it's trying to return a call to '" + symbols[1] + "()' that can't be ensured to also be 'void'.";
} break;
case DEPRECATED_KEYWORD: { case DEPRECATED_KEYWORD: {
CHECK_SYMBOLS(2); CHECK_SYMBOLS(2);
return "The '" + symbols[0] + "' keyword is deprecated and will be removed in a future release, please replace its uses by '" + symbols[1] + "'."; return "The '" + symbols[0] + "' keyword is deprecated and will be removed in a future release, please replace its uses by '" + symbols[1] + "'.";
@ -224,6 +228,7 @@ String GDScriptWarning::get_name_from_code(Code p_code) {
"UNSAFE_METHOD_ACCESS", "UNSAFE_METHOD_ACCESS",
"UNSAFE_CAST", "UNSAFE_CAST",
"UNSAFE_CALL_ARGUMENT", "UNSAFE_CALL_ARGUMENT",
"UNSAFE_VOID_RETURN",
"DEPRECATED_KEYWORD", "DEPRECATED_KEYWORD",
"STANDALONE_TERNARY", "STANDALONE_TERNARY",
"ASSERT_ALWAYS_TRUE", "ASSERT_ALWAYS_TRUE",

View File

@ -69,6 +69,7 @@ public:
UNSAFE_METHOD_ACCESS, // Function not found in the detected type (but can be in subtypes). UNSAFE_METHOD_ACCESS, // Function not found in the detected type (but can be in subtypes).
UNSAFE_CAST, // Cast used in an unknown type. UNSAFE_CAST, // Cast used in an unknown type.
UNSAFE_CALL_ARGUMENT, // Function call argument is of a supertype of the require argument. UNSAFE_CALL_ARGUMENT, // Function call argument is of a supertype of the require argument.
UNSAFE_VOID_RETURN, // Function returns void but returned a call to a function that can't be type checked.
DEPRECATED_KEYWORD, // The keyword is deprecated and should be replaced. DEPRECATED_KEYWORD, // The keyword is deprecated and should be replaced.
STANDALONE_TERNARY, // Return value of ternary expression is discarded. STANDALONE_TERNARY, // Return value of ternary expression is discarded.
ASSERT_ALWAYS_TRUE, // Expression for assert argument is always true. ASSERT_ALWAYS_TRUE, // Expression for assert argument is always true.

View File

@ -0,0 +1,20 @@
func test():
return_call()
return_nothing()
return_side_effect()
var r = return_side_effect.call() # Untyped call to check return value.
prints(r, typeof(r) == TYPE_NIL)
print("end")
func side_effect(v):
print("effect")
return v
func return_call() -> void:
return print("hello")
func return_nothing() -> void:
return
func return_side_effect() -> void:
return side_effect("x")

View File

@ -0,0 +1,10 @@
GDTEST_OK
>> WARNING
>> Line: 20
>> UNSAFE_VOID_RETURN
>> The method 'return_side_effect()' returns 'void' but it's trying to return a call to 'side_effect()' that can't be ensured to also be 'void'.
hello
effect
effect
<null> true
end