GDScript: Allow void functions to return calls to other void functions
This commit is contained in:
parent
e1648b3327
commit
a47d4d57ca
8 changed files with 78 additions and 10 deletions
|
@ -474,6 +474,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>
|
||||||
|
|
|
@ -1972,17 +1972,40 @@ void GDScriptAnalyzer::resolve_return(GDScriptParser::ReturnNode *p_return) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (p_return->return_value != nullptr) {
|
if (p_return->return_value != nullptr) {
|
||||||
|
bool is_void_function = has_expected_type && expected_type.is_hard_type() && expected_type.kind == GDScriptParser::DataType::BUILTIN && expected_type.builtin_type == Variant::NIL;
|
||||||
|
bool is_call = p_return->return_value->type == GDScriptParser::Node::CALL;
|
||||||
|
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);
|
reduce_expression(p_return->return_value);
|
||||||
|
}
|
||||||
|
if (is_void_function) {
|
||||||
|
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()) {
|
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());
|
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() && expected_type.kind == GDScriptParser::DataType::BUILTIN && expected_type.builtin_type == Variant::NIL) {
|
|
||||||
push_error("A void function cannot return a value.", p_return);
|
|
||||||
}
|
|
||||||
if (has_expected_type && expected_type.is_hard_type() && p_return->return_value->is_constant) {
|
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");
|
update_const_expression_builtin_type(p_return->return_value, expected_type, "return");
|
||||||
}
|
}
|
||||||
result = p_return->return_value->get_datatype();
|
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;
|
||||||
|
|
|
@ -1859,7 +1859,12 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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] + "'.";
|
||||||
|
@ -218,6 +222,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",
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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")
|
|
@ -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
|
Loading…
Reference in a new issue