Merge pull request #82477 from dalexeev/gds-covariance-and-contravariance
GDScript: Add return type covariance and parameter type contravariance
This commit is contained in:
commit
b25f1f9c79
19 changed files with 169 additions and 4 deletions
|
@ -1671,15 +1671,42 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *
|
||||||
StringName native_base;
|
StringName native_base;
|
||||||
if (!p_is_lambda && get_function_signature(p_function, false, base_type, function_name, parent_return_type, parameters_types, default_par_count, method_flags, &native_base)) {
|
if (!p_is_lambda && get_function_signature(p_function, false, base_type, function_name, parent_return_type, parameters_types, default_par_count, method_flags, &native_base)) {
|
||||||
bool valid = p_function->is_static == method_flags.has_flag(METHOD_FLAG_STATIC);
|
bool valid = p_function->is_static == method_flags.has_flag(METHOD_FLAG_STATIC);
|
||||||
valid = valid && parent_return_type == p_function->get_datatype();
|
|
||||||
|
if (p_function->return_type != nullptr) {
|
||||||
|
// Check return type covariance.
|
||||||
|
GDScriptParser::DataType return_type = p_function->get_datatype();
|
||||||
|
if (return_type.is_variant()) {
|
||||||
|
// `is_type_compatible()` returns `true` if one of the types is `Variant`.
|
||||||
|
// Don't allow an explicitly specified `Variant` if the parent return type is narrower.
|
||||||
|
valid = valid && parent_return_type.is_variant();
|
||||||
|
} else if (return_type.kind == GDScriptParser::DataType::BUILTIN && return_type.builtin_type == Variant::NIL) {
|
||||||
|
// `is_type_compatible()` returns `true` if target is an `Object` and source is `null`.
|
||||||
|
// Don't allow `void` if the parent return type is a hard non-`void` type.
|
||||||
|
if (parent_return_type.is_hard_type() && !(parent_return_type.kind == GDScriptParser::DataType::BUILTIN && parent_return_type.builtin_type == Variant::NIL)) {
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
valid = valid && is_type_compatible(parent_return_type, return_type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int par_count_diff = p_function->parameters.size() - parameters_types.size();
|
int par_count_diff = p_function->parameters.size() - parameters_types.size();
|
||||||
valid = valid && par_count_diff >= 0;
|
valid = valid && par_count_diff >= 0;
|
||||||
valid = valid && default_value_count >= default_par_count + par_count_diff;
|
valid = valid && default_value_count >= default_par_count + par_count_diff;
|
||||||
|
|
||||||
int i = 0;
|
if (valid) {
|
||||||
for (const GDScriptParser::DataType &par_type : parameters_types) {
|
int i = 0;
|
||||||
valid = valid && par_type == p_function->parameters[i++]->get_datatype();
|
for (const GDScriptParser::DataType &parent_par_type : parameters_types) {
|
||||||
|
// Check parameter type contravariance.
|
||||||
|
GDScriptParser::DataType current_par_type = p_function->parameters[i++]->get_datatype();
|
||||||
|
if (parent_par_type.is_variant() && parent_par_type.is_hard_type()) {
|
||||||
|
// `is_type_compatible()` returns `true` if one of the types is `Variant`.
|
||||||
|
// Don't allow narrowing a hard `Variant`.
|
||||||
|
valid = valid && current_par_type.is_variant();
|
||||||
|
} else {
|
||||||
|
valid = valid && is_type_compatible(current_par_type, parent_par_type);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
class A:
|
||||||
|
func f(_p: Object):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class B extends A:
|
||||||
|
func f(_p: Node):
|
||||||
|
pass
|
||||||
|
|
||||||
|
func test():
|
||||||
|
pass
|
|
@ -0,0 +1,2 @@
|
||||||
|
GDTEST_ANALYZER_ERROR
|
||||||
|
The function signature doesn't match the parent. Parent signature is "f(Object) -> Variant".
|
|
@ -0,0 +1,10 @@
|
||||||
|
class A:
|
||||||
|
func f(_p: Variant):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class B extends A:
|
||||||
|
func f(_p: Node): # No `is_type_compatible()` misuse.
|
||||||
|
pass
|
||||||
|
|
||||||
|
func test():
|
||||||
|
pass
|
|
@ -0,0 +1,2 @@
|
||||||
|
GDTEST_ANALYZER_ERROR
|
||||||
|
The function signature doesn't match the parent. Parent signature is "f(Variant) -> Variant".
|
|
@ -0,0 +1,10 @@
|
||||||
|
class A:
|
||||||
|
func f(_p: int):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class B extends A:
|
||||||
|
func f(_p: float): # No implicit conversion.
|
||||||
|
pass
|
||||||
|
|
||||||
|
func test():
|
||||||
|
pass
|
|
@ -0,0 +1,2 @@
|
||||||
|
GDTEST_ANALYZER_ERROR
|
||||||
|
The function signature doesn't match the parent. Parent signature is "f(int) -> Variant".
|
|
@ -0,0 +1,10 @@
|
||||||
|
class A:
|
||||||
|
func f() -> Node:
|
||||||
|
return null
|
||||||
|
|
||||||
|
class B extends A:
|
||||||
|
func f() -> Object:
|
||||||
|
return null
|
||||||
|
|
||||||
|
func test():
|
||||||
|
pass
|
|
@ -0,0 +1,2 @@
|
||||||
|
GDTEST_ANALYZER_ERROR
|
||||||
|
The function signature doesn't match the parent. Parent signature is "f() -> Node".
|
|
@ -0,0 +1,10 @@
|
||||||
|
class A:
|
||||||
|
func f() -> Node:
|
||||||
|
return null
|
||||||
|
|
||||||
|
class B extends A:
|
||||||
|
func f() -> Variant: # No `is_type_compatible()` misuse.
|
||||||
|
return null
|
||||||
|
|
||||||
|
func test():
|
||||||
|
pass
|
|
@ -0,0 +1,2 @@
|
||||||
|
GDTEST_ANALYZER_ERROR
|
||||||
|
The function signature doesn't match the parent. Parent signature is "f() -> Node".
|
|
@ -0,0 +1,10 @@
|
||||||
|
class A:
|
||||||
|
func f() -> Node:
|
||||||
|
return null
|
||||||
|
|
||||||
|
class B extends A:
|
||||||
|
func f() -> void: # No `is_type_compatible()` misuse.
|
||||||
|
return
|
||||||
|
|
||||||
|
func test():
|
||||||
|
pass
|
|
@ -0,0 +1,2 @@
|
||||||
|
GDTEST_ANALYZER_ERROR
|
||||||
|
The function signature doesn't match the parent. Parent signature is "f() -> Node".
|
|
@ -0,0 +1,10 @@
|
||||||
|
class A:
|
||||||
|
func f() -> float:
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
class B extends A:
|
||||||
|
func f() -> int: # No implicit conversion.
|
||||||
|
return 0
|
||||||
|
|
||||||
|
func test():
|
||||||
|
pass
|
|
@ -0,0 +1,2 @@
|
||||||
|
GDTEST_ANALYZER_ERROR
|
||||||
|
The function signature doesn't match the parent. Parent signature is "f() -> float".
|
|
@ -0,0 +1,20 @@
|
||||||
|
class A:
|
||||||
|
func int_to_variant(_p: int): pass
|
||||||
|
func node_to_variant(_p: Node): pass
|
||||||
|
func node_2d_to_node(_p: Node2D): pass
|
||||||
|
|
||||||
|
func variant_to_untyped(_p: Variant): pass
|
||||||
|
func int_to_untyped(_p: int): pass
|
||||||
|
func node_to_untyped(_p: Node): pass
|
||||||
|
|
||||||
|
class B extends A:
|
||||||
|
func int_to_variant(_p: Variant): pass
|
||||||
|
func node_to_variant(_p: Variant): pass
|
||||||
|
func node_2d_to_node(_p: Node): pass
|
||||||
|
|
||||||
|
func variant_to_untyped(_p): pass
|
||||||
|
func int_to_untyped(_p): pass
|
||||||
|
func node_to_untyped(_p): pass
|
||||||
|
|
||||||
|
func test():
|
||||||
|
pass
|
|
@ -0,0 +1 @@
|
||||||
|
GDTEST_OK
|
|
@ -0,0 +1,32 @@
|
||||||
|
class A:
|
||||||
|
func variant_to_int() -> Variant: return 0
|
||||||
|
func variant_to_node() -> Variant: return null
|
||||||
|
func node_to_node_2d() -> Node: return null
|
||||||
|
|
||||||
|
func untyped_to_void(): pass
|
||||||
|
func untyped_to_variant(): pass
|
||||||
|
func untyped_to_int(): pass
|
||||||
|
func untyped_to_node(): pass
|
||||||
|
|
||||||
|
func void_to_untyped() -> void: pass
|
||||||
|
func variant_to_untyped() -> Variant: return null
|
||||||
|
func int_to_untyped() -> int: return 0
|
||||||
|
func node_to_untyped() -> Node: return null
|
||||||
|
|
||||||
|
class B extends A:
|
||||||
|
func variant_to_int() -> int: return 0
|
||||||
|
func variant_to_node() -> Node: return null
|
||||||
|
func node_to_node_2d() -> Node2D: return null
|
||||||
|
|
||||||
|
func untyped_to_void() -> void: pass
|
||||||
|
func untyped_to_variant() -> Variant: return null
|
||||||
|
func untyped_to_int() -> int: return 0
|
||||||
|
func untyped_to_node() -> Node: return null
|
||||||
|
|
||||||
|
func void_to_untyped(): pass
|
||||||
|
func variant_to_untyped(): pass
|
||||||
|
func int_to_untyped(): pass
|
||||||
|
func node_to_untyped(): pass
|
||||||
|
|
||||||
|
func test():
|
||||||
|
pass
|
|
@ -0,0 +1 @@
|
||||||
|
GDTEST_OK
|
Loading…
Add table
Reference in a new issue