Merge pull request #58835 from vnen/gdscript-check-override-signature

GDScript: Check if method signature matches the parent
This commit is contained in:
Rémi Verschelde 2022-03-06 22:31:25 +01:00 committed by GitHub
commit 9e3fd726d9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 141 additions and 9 deletions

View file

@ -1139,7 +1139,7 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *
#endif // TOOLS_ENABLED
}
if (p_function->identifier->name == "_init") {
if (p_function->identifier->name == GDScriptLanguage::get_singleton()->strings._init) {
// Constructor.
GDScriptParser::DataType return_type = parser->current_class->get_datatype();
return_type.is_meta_type = false;
@ -1153,6 +1153,57 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *
} else {
GDScriptParser::DataType return_type = resolve_datatype(p_function->return_type);
p_function->set_datatype(return_type);
#ifdef DEBUG_ENABLED
// Check if the function signature matches the parent. If not it's an error since it breaks polymorphism.
// Not for the constructor which can vary in signature.
GDScriptParser::DataType base_type = parser->current_class->base_type;
GDScriptParser::DataType parent_return_type;
List<GDScriptParser::DataType> parameters_types;
int default_par_count = 0;
bool is_static = false;
bool is_vararg = false;
if (get_function_signature(p_function, false, base_type, p_function->identifier->name, parent_return_type, parameters_types, default_par_count, is_static, is_vararg)) {
bool valid = p_function->is_static == is_static;
valid = valid && parent_return_type == p_function->get_datatype();
int par_count_diff = p_function->parameters.size() - parameters_types.size();
valid = valid && par_count_diff >= 0;
valid = valid && p_function->default_arg_values.size() >= default_par_count + par_count_diff;
int i = 0;
for (const GDScriptParser::DataType &par_type : parameters_types) {
valid = valid && par_type == p_function->parameters[i++]->get_datatype();
}
if (!valid) {
// Compute parent signature as a string to show in the error message.
String parent_signature = parent_return_type.is_hard_type() ? parent_return_type.to_string() : "Variant";
if (parent_signature == "null") {
parent_signature = "void";
}
parent_signature += " " + p_function->identifier->name.operator String() + "(";
int j = 0;
for (const GDScriptParser::DataType &par_type : parameters_types) {
if (j > 0) {
parent_signature += ", ";
}
String parameter = par_type.to_string();
if (parameter == "null") {
parameter = "Variant";
}
parent_signature += parameter;
if (j == parameters_types.size() - default_par_count) {
parent_signature += " = default";
}
j++;
}
parent_signature += ")";
push_error(vformat(R"(The function signature doesn't match the parent. Parent signature is "%s".)", parent_signature), p_function);
}
}
#endif
}
parser->current_function = previous_function;
@ -2416,7 +2467,9 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
GDScriptParser::DataType return_type;
List<GDScriptParser::DataType> par_types;
if (get_function_signature(p_call, base_type, p_call->function_name, return_type, par_types, default_arg_count, is_static, is_vararg)) {
bool is_constructor = (base_type.is_meta_type || (p_call->callee && p_call->callee->type == GDScriptParser::Node::IDENTIFIER)) && p_call->function_name == SNAME("new");
if (get_function_signature(p_call, is_constructor, base_type, p_call->function_name, return_type, par_types, default_arg_count, is_static, is_vararg)) {
// If the function require typed arrays we must make literals be typed.
for (const KeyValue<int, GDScriptParser::ArrayNode *> &E : arrays) {
int index = E.key;
@ -3576,7 +3629,7 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo
return result;
}
bool GDScriptAnalyzer::get_function_signature(GDScriptParser::CallNode *p_source, GDScriptParser::DataType p_base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg) {
bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bool p_is_constructor, GDScriptParser::DataType p_base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg) {
r_static = false;
r_vararg = false;
r_default_arg_count = 0;
@ -3616,8 +3669,7 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::CallNode *p_source
return false;
}
bool is_constructor = (p_base_type.is_meta_type || (p_source->callee && p_source->callee->type == GDScriptParser::Node::IDENTIFIER)) && p_function == StaticCString::create("new");
if (is_constructor) {
if (p_is_constructor) {
function_name = "_init";
r_static = true;
}
@ -3638,7 +3690,7 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::CallNode *p_source
}
if (found_function != nullptr) {
r_static = is_constructor || found_function->is_static;
r_static = p_is_constructor || found_function->is_static;
for (int i = 0; i < found_function->parameters.size(); i++) {
r_par_types.push_back(found_function->parameters[i]->get_datatype());
if (found_function->parameters[i]->default_value != nullptr) {
@ -3664,7 +3716,7 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::CallNode *p_source
}
// If the base is a script, it might be trying to access members of the Script class itself.
if (p_base_type.is_meta_type && !is_constructor && (p_base_type.kind == GDScriptParser::DataType::SCRIPT || p_base_type.kind == GDScriptParser::DataType::CLASS)) {
if (p_base_type.is_meta_type && !p_is_constructor && (p_base_type.kind == GDScriptParser::DataType::SCRIPT || p_base_type.kind == GDScriptParser::DataType::CLASS)) {
MethodInfo info;
StringName script_class = p_base_type.kind == GDScriptParser::DataType::SCRIPT ? p_base_type.script_type->get_class_name() : StringName(GDScript::get_class_static());
@ -3684,7 +3736,7 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::CallNode *p_source
}
#endif
if (is_constructor) {
if (p_is_constructor) {
// Native types always have a default constructor.
r_return_type = p_base_type;
r_return_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;

View file

@ -105,7 +105,7 @@ class GDScriptAnalyzer {
GDScriptParser::DataType type_from_metatype(const GDScriptParser::DataType &p_meta_type) const;
GDScriptParser::DataType type_from_property(const PropertyInfo &p_property) const;
GDScriptParser::DataType make_global_class_meta_type(const StringName &p_class_name, const GDScriptParser::Node *p_source);
bool get_function_signature(GDScriptParser::CallNode *p_source, GDScriptParser::DataType base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg);
bool get_function_signature(GDScriptParser::Node *p_source, bool p_is_constructor, GDScriptParser::DataType base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg);
bool function_signature_from_info(const MethodInfo &p_info, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg);
bool validate_call_arg(const List<GDScriptParser::DataType> &p_par_types, int p_default_args_count, bool p_is_vararg, const GDScriptParser::CallNode *p_call);
bool validate_call_arg(const MethodInfo &p_method, const GDScriptParser::CallNode *p_call);

View file

@ -0,0 +1,10 @@
func test():
print("Shouldn't reach this")
class Parent:
func my_function(_par1: int) -> int:
return 0
class Child extends Parent:
func my_function() -> int:
return 0

View file

@ -0,0 +1,2 @@
GDTEST_ANALYZER_ERROR
The function signature doesn't match the parent. Parent signature is "int my_function(int)".

View file

@ -0,0 +1,10 @@
func test():
print("Shouldn't reach this")
class Parent:
func my_function(_par1: int) -> int:
return 0
class Child extends Parent:
func my_function(_pary1: int, _par2: int) -> int:
return 0

View file

@ -0,0 +1,2 @@
GDTEST_ANALYZER_ERROR
The function signature doesn't match the parent. Parent signature is "int my_function(int)".

View file

@ -0,0 +1,10 @@
func test():
print("Shouldn't reach this")
class Parent:
func my_function(_par1: int = 0) -> int:
return 0
class Child extends Parent:
func my_function(_par1: int) -> int:
return 0

View file

@ -0,0 +1,2 @@
GDTEST_ANALYZER_ERROR
The function signature doesn't match the parent. Parent signature is "int my_function(int = default)".

View file

@ -0,0 +1,10 @@
func test():
print("Shouldn't reach this")
class Parent:
func my_function(_par1: int) -> int:
return 0
class Child extends Parent:
func my_function(_par1: Vector2) -> int:
return 0

View file

@ -0,0 +1,2 @@
GDTEST_ANALYZER_ERROR
The function signature doesn't match the parent. Parent signature is "int my_function(int)".

View file

@ -0,0 +1,10 @@
func test():
print("Shouldn't reach this")
class Parent:
func my_function() -> int:
return 0
class Child extends Parent:
func my_function() -> Vector2:
return Vector2()

View file

@ -0,0 +1,2 @@
GDTEST_ANALYZER_ERROR
The function signature doesn't match the parent. Parent signature is "int my_function()".

View file

@ -0,0 +1,17 @@
func test():
var instance := Parent.new()
var result := instance.my_function(1)
print(result)
assert(result == 1)
instance = Child.new()
result = instance.my_function(2)
print(result)
assert(result == 0)
class Parent:
func my_function(par1: int) -> int:
return par1
class Child extends Parent:
func my_function(_par1: int, par2: int = 0) -> int:
return par2