Commit 66a4a62e authored by Xavier Thompson's avatar Xavier Thompson

Enforce cypclass method overloading rules

parent e233afb0
......@@ -581,7 +581,7 @@ def inject_acthon_interfaces(self):
queue_is_empty_type = PyrexTypes.CFuncType(PyrexTypes.c_bint_type, [], nogil = 1)
queue_is_empty_type.is_const_method = 1
queue_is_empty_entry = queue_scope.declare("is_empty", "is_empty",
queue_activate_type, None, "extern")
queue_is_empty_type, None, "extern")
queue_is_empty_entry.is_cfunction = 1
queue_is_empty_entry.is_variable = 1
queue_scope.var_entries.append(queue_is_empty_entry)
......
......@@ -2958,18 +2958,17 @@ class CFuncType(CType):
return 0
return 1
def compatible_signature_with(self, other_type, as_cmethod = 0, ignore_return_type=0):
return self.compatible_signature_with_resolved_type(other_type.resolve(), as_cmethod, ignore_return_type)
def compatible_arguments_with(self, other_type, as_cmethod=0):
return self.compatible_arguments_with_resolved_type(other_type.resolve(), as_cmethod)
def compatible_signature_with_resolved_type(self, other_type, as_cmethod, ignore_return_type=0):
#print "CFuncType.same_c_signature_as_resolved_type:", \
# self, other_type, "as_cmethod =", as_cmethod ###
def compatible_arguments_with_resolved_type(self, other_type, as_cmethod):
if other_type is error_type:
return 1
if not other_type.is_cfunction:
return 0
if not self.is_overridable and other_type.is_overridable:
return 0
return self.__compatible_arguments_with_resolved_type(other_type, as_cmethod)
def __compatible_arguments_with_resolved_type(self, other_type, as_cmethod):
nargs = len(self.args)
if nargs - self.optional_arg_count != len(other_type.args) - other_type.optional_arg_count:
return 0
......@@ -2984,7 +2983,22 @@ class CFuncType(CType):
return 0
if self.has_varargs != other_type.has_varargs:
return 0
if not ignore_return_type:
return 1
def compatible_signature_with(self, other_type, as_cmethod = 0, skip_args=0, skip_return=0):
return self.compatible_signature_with_resolved_type(other_type.resolve(), as_cmethod, skip_args, skip_return)
def compatible_signature_with_resolved_type(self, other_type, as_cmethod, skip_args=0, skip_return=0):
#print "CFuncType.same_c_signature_as_resolved_type:", \
# self, other_type, "as_cmethod =", as_cmethod ###
if other_type is error_type:
return 1
if not other_type.is_cfunction:
return 0
if not self.is_overridable and other_type.is_overridable:
return 0
if not skip_args and not self.compatible_arguments_with_resolved_type(other_type, as_cmethod):
return 0
if not self.return_type.subtype_of_resolved_type(other_type.return_type):
return 0
if not self.same_calling_convention_as(other_type):
......@@ -3010,14 +3024,17 @@ class CFuncType(CType):
return 0
return 1
def narrower_c_signature_than(self, other_type, as_cmethod = 0):
return self.narrower_c_signature_than_resolved_type(other_type.resolve(), as_cmethod)
def narrower_arguments_than(self, other_type, as_cmethod = 0):
return self.narrower_arguments_than_resolved_type(other_type.resolve(), as_cmethod)
def narrower_c_signature_than_resolved_type(self, other_type, as_cmethod):
def narrower_arguments_than_resolved_type(self, other_type, as_cmethod):
if other_type is error_type:
return 1
if not other_type.is_cfunction:
return 0
return self.__narrower_arguments_than_resolved_type(other_type, as_cmethod)
def __narrower_arguments_than_resolved_type(self, other_type, as_cmethod):
nargs = len(self.args)
if nargs != len(other_type.args):
return 0
......@@ -3031,6 +3048,18 @@ class CFuncType(CType):
return 0
if self.optional_arg_count != other_type.optional_arg_count:
return 0
return 1
def narrower_c_signature_than(self, other_type, as_cmethod = 0, skip_args=0):
return self.narrower_c_signature_than_resolved_type(other_type.resolve(), as_cmethod)
def narrower_c_signature_than_resolved_type(self, other_type, as_cmethod, skip_args=0):
if other_type is error_type:
return 1
if not other_type.is_cfunction:
return 0
if not skip_args and not self.__narrower_arguments_than_resolved_type(other_type, as_cmethod):
return 0
if not self.return_type.subtype_of_resolved_type(other_type.return_type):
return 0
if not self.exception_check and other_type.exception_check:
......@@ -3966,6 +3995,9 @@ class CppClassType(CType):
base_code = public_decl(base_code, dll_linkage)
return self.base_declaration_code(base_code, entity_code)
def subtype_of_resolved_type(self, other_type):
return self.is_subclass(other_type)
def is_subclass(self, other_type):
if self.same_as_resolved_type(other_type):
return 1
......
......@@ -537,11 +537,13 @@ class Scope(object):
cpp_override_allowed = True
for index, alt_entry in enumerate(old_entry.all_alternatives()):
# in a cypclass, a method can hide a method inherited from a different class
# regardless of their return types
ignore_return_type = (self.is_cyp_class_scope
and alt_entry.is_inherited
and alt_entry.from_type is not from_type)
if type.compatible_signature_with(alt_entry.type, ignore_return_type=ignore_return_type):
# based only on their argument types
if self.is_cyp_class_scope:
might_redeclare_or_override = type.compatible_arguments_with
else:
might_redeclare_or_override = type.compatible_signature_with
if might_redeclare_or_override(alt_entry.type):
cpp_override_allowed = False
......@@ -555,10 +557,36 @@ class Scope(object):
# Any inherited method is visible
# until overloaded by a method with the same signature
if alt_entry.is_inherited:
# in a cypclass, if the arguments are compatible
# then the whole signature must also be compatible
if self.is_cyp_class_scope:
if not type.compatible_signature_with(alt_entry.type, skip_args=1):
error(pos, "Cypclass overriding method with incompatible return type")
if alt_entry.pos is not None:
error(alt_entry.pos, "Overriden method is defined here")
# 'CFuncType.compatible_signature_with' doesn't compare constness, so we're doing it here.
# TODO: Maybe it should.
elif (type.is_const_method and not alt_entry.type.is_const_method
or not type.is_const_method and alt_entry.type.is_const_method):
print(type.is_const_method)
print(alt_entry.type.is_const_method)
error(pos, "Cypclass overriding method with conflicting constness")
if alt_entry.pos is not None:
error(alt_entry.pos, "Overriden method is defined here")
previous_alternative_indices.append(index)
cpp_override_allowed = True
continue
elif self.is_cyp_class_scope:
if (type.narrower_arguments_than(alt_entry.type)
or alt_entry.type.narrower_arguments_than(type)):
error(pos, "Cypclass overloaded method with narrower arguments")
if alt_entry.pos is not None:
error(alt_entry.pos, "Conflicting method is defined here")
if cpp_override_allowed:
# C++ function/method overrides with different signatures are ok.
pass
......
......@@ -14,29 +14,28 @@ cdef cypclass B(A) nolock:
B __iadd__(self, A other):
self.val += (other.val + 10)
B __iadd__(self, B other):
self.val += (other.val + 100)
int is_inferred_as_B(self):
return 1
def test_inplace_operator_inference():
"""
>>> test_inplace_operator_inference()
111
(11, 1)
"""
a = A(0)
b0 = B(0)
b1 = B(0)
b = B(0)
# at this point, the types are unambiguous and the following assignments should not cause them to infer as another type.
a += b0 # should add 1
a += b # should add 1
# before it being fixed, 'b0 += a' where 'a' is of type A caused 'b0' to be inferred as type A instead of B.
# before it being fixed, 'b += a' where 'a' is of type A caused 'b' to be inferred as type A instead of B.
b0 += a # should add 10
b += a # should add 10
# since all cypclass methods are virtual, 'b0' being erroneously inferred as type A would cause
# 'b0 += b1' to call 'B __iadd__(self, A other)' instead of 'B __iadd__(self, B other)'.
# since all cypclass methods are virtual, 'b' being erroneously inferred as type A would cause a compilation error
# when calling 'b.is_inferred_as_B()'.
b0 += b1 # should add 100
r = b.is_inferred_as_B()
return b0.val
return b.val, r
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment