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): ...@@ -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 = PyrexTypes.CFuncType(PyrexTypes.c_bint_type, [], nogil = 1)
queue_is_empty_type.is_const_method = 1 queue_is_empty_type.is_const_method = 1
queue_is_empty_entry = queue_scope.declare("is_empty", "is_empty", 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_cfunction = 1
queue_is_empty_entry.is_variable = 1 queue_is_empty_entry.is_variable = 1
queue_scope.var_entries.append(queue_is_empty_entry) queue_scope.var_entries.append(queue_is_empty_entry)
......
...@@ -2958,18 +2958,17 @@ class CFuncType(CType): ...@@ -2958,18 +2958,17 @@ class CFuncType(CType):
return 0 return 0
return 1 return 1
def compatible_signature_with(self, other_type, as_cmethod = 0, ignore_return_type=0): def compatible_arguments_with(self, other_type, as_cmethod=0):
return self.compatible_signature_with_resolved_type(other_type.resolve(), as_cmethod, ignore_return_type) 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): def compatible_arguments_with_resolved_type(self, other_type, as_cmethod):
#print "CFuncType.same_c_signature_as_resolved_type:", \
# self, other_type, "as_cmethod =", as_cmethod ###
if other_type is error_type: if other_type is error_type:
return 1 return 1
if not other_type.is_cfunction: if not other_type.is_cfunction:
return 0 return 0
if not self.is_overridable and other_type.is_overridable: return self.__compatible_arguments_with_resolved_type(other_type, as_cmethod)
return 0
def __compatible_arguments_with_resolved_type(self, other_type, as_cmethod):
nargs = len(self.args) nargs = len(self.args)
if nargs - self.optional_arg_count != len(other_type.args) - other_type.optional_arg_count: if nargs - self.optional_arg_count != len(other_type.args) - other_type.optional_arg_count:
return 0 return 0
...@@ -2984,9 +2983,24 @@ class CFuncType(CType): ...@@ -2984,9 +2983,24 @@ class CFuncType(CType):
return 0 return 0
if self.has_varargs != other_type.has_varargs: if self.has_varargs != other_type.has_varargs:
return 0 return 0
if not ignore_return_type: return 1
if not self.return_type.subtype_of_resolved_type(other_type.return_type):
return 0 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): if not self.same_calling_convention_as(other_type):
return 0 return 0
if self.nogil != other_type.nogil: if self.nogil != other_type.nogil:
...@@ -3010,14 +3024,17 @@ class CFuncType(CType): ...@@ -3010,14 +3024,17 @@ class CFuncType(CType):
return 0 return 0
return 1 return 1
def narrower_c_signature_than(self, other_type, as_cmethod = 0): def narrower_arguments_than(self, other_type, as_cmethod = 0):
return self.narrower_c_signature_than_resolved_type(other_type.resolve(), as_cmethod) 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: if other_type is error_type:
return 1 return 1
if not other_type.is_cfunction: if not other_type.is_cfunction:
return 0 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) nargs = len(self.args)
if nargs != len(other_type.args): if nargs != len(other_type.args):
return 0 return 0
...@@ -3031,6 +3048,18 @@ class CFuncType(CType): ...@@ -3031,6 +3048,18 @@ class CFuncType(CType):
return 0 return 0
if self.optional_arg_count != other_type.optional_arg_count: if self.optional_arg_count != other_type.optional_arg_count:
return 0 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): if not self.return_type.subtype_of_resolved_type(other_type.return_type):
return 0 return 0
if not self.exception_check and other_type.exception_check: if not self.exception_check and other_type.exception_check:
...@@ -3966,6 +3995,9 @@ class CppClassType(CType): ...@@ -3966,6 +3995,9 @@ class CppClassType(CType):
base_code = public_decl(base_code, dll_linkage) base_code = public_decl(base_code, dll_linkage)
return self.base_declaration_code(base_code, entity_code) 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): def is_subclass(self, other_type):
if self.same_as_resolved_type(other_type): if self.same_as_resolved_type(other_type):
return 1 return 1
......
...@@ -537,11 +537,13 @@ class Scope(object): ...@@ -537,11 +537,13 @@ class Scope(object):
cpp_override_allowed = True cpp_override_allowed = True
for index, alt_entry in enumerate(old_entry.all_alternatives()): for index, alt_entry in enumerate(old_entry.all_alternatives()):
# in a cypclass, a method can hide a method inherited from a different class # in a cypclass, a method can hide a method inherited from a different class
# regardless of their return types # based only on their argument types
ignore_return_type = (self.is_cyp_class_scope if self.is_cyp_class_scope:
and alt_entry.is_inherited might_redeclare_or_override = type.compatible_arguments_with
and alt_entry.from_type is not from_type) else:
if type.compatible_signature_with(alt_entry.type, ignore_return_type=ignore_return_type): might_redeclare_or_override = type.compatible_signature_with
if might_redeclare_or_override(alt_entry.type):
cpp_override_allowed = False cpp_override_allowed = False
...@@ -555,10 +557,36 @@ class Scope(object): ...@@ -555,10 +557,36 @@ class Scope(object):
# Any inherited method is visible # Any inherited method is visible
# until overloaded by a method with the same signature # until overloaded by a method with the same signature
if alt_entry.is_inherited: 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) previous_alternative_indices.append(index)
cpp_override_allowed = True cpp_override_allowed = True
continue 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: if cpp_override_allowed:
# C++ function/method overrides with different signatures are ok. # C++ function/method overrides with different signatures are ok.
pass pass
......
...@@ -13,30 +13,29 @@ cdef cypclass A nolock: ...@@ -13,30 +13,29 @@ cdef cypclass A nolock:
cdef cypclass B(A) nolock: cdef cypclass B(A) nolock:
B __iadd__(self, A other): B __iadd__(self, A other):
self.val += (other.val + 10) self.val += (other.val + 10)
B __iadd__(self, B other): int is_inferred_as_B(self):
self.val += (other.val + 100) return 1
def test_inplace_operator_inference(): def test_inplace_operator_inference():
""" """
>>> test_inplace_operator_inference() >>> test_inplace_operator_inference()
111 (11, 1)
""" """
a = A(0) a = A(0)
b0 = B(0) b = B(0)
b1 = B(0)
# at this point, the types are unambiguous and the following assignments should not cause them to infer as another type. # 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 # since all cypclass methods are virtual, 'b' being erroneously inferred as type A would cause a compilation error
# 'b0 += b1' to call 'B __iadd__(self, A other)' instead of 'B __iadd__(self, B other)'. # 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