Skip to content

Commit

Permalink
Improve error diagnostic for exception-spec mismatch (#5547)
Browse files Browse the repository at this point in the history
It seems like the most common Cython 3 snag is the change in
function exception behaviour. This PR adds some extra diagnostics
to help people navigate this change, and a general mechanism
for adding this kind of diagnostics.

It needs some tests, but I wasn't to see what tests fail
to tell me where to put them.
  • Loading branch information
da-woods committed Jul 21, 2023
1 parent 0ad32ea commit 572b640
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 13 deletions.
6 changes: 5 additions & 1 deletion Cython/Compiler/ExprNodes.py
Expand Up @@ -1077,7 +1077,11 @@ def coerce_to(self, dst_type, env):
return src

def fail_assignment(self, dst_type):
error(self.pos, "Cannot assign type '%s' to '%s'" % (self.type, dst_type))
extra_diagnostics = dst_type.assignment_failure_extra_info(self.type)
if extra_diagnostics:
extra_diagnostics = ". " + extra_diagnostics
error(self.pos, "Cannot assign type '%s' to '%s'%s" % (
self.type, dst_type, extra_diagnostics))

def check_for_coercion_error(self, dst_type, env, fail=False, default=None):
if fail and not default:
Expand Down
21 changes: 21 additions & 0 deletions Cython/Compiler/PyrexTypes.py
Expand Up @@ -318,6 +318,11 @@ def assignable_from(self, src_type):
def assignable_from_resolved_type(self, src_type):
return self.same_as(src_type)

def assignment_failure_extra_info(self, src_type):
"""Override if you can useful provide extra
information about why an assignment didn't work."""
return ""

def as_argument_type(self):
return self

Expand Down Expand Up @@ -2860,6 +2865,22 @@ def assignable_from_resolved_type(self, other_type):
return self.base_type.is_void or self.base_type.same_as(other_type.base_type)
return 0

def assignment_failure_extra_info(self, src_type):
if self.base_type.is_cfunction and src_type.is_ptr:
src_type = src_type.base_type.resolve()
if self.base_type.is_cfunction and src_type.is_cfunction:
copied_src_type = copy.copy(src_type)
# make the exception values the same as us
copied_src_type.exception_check = self.base_type.exception_check
copied_src_type.exception_value = self.base_type.exception_value
if self.base_type.pointer_assignable_from_resolved_type(copied_src_type):
# the only reason we can't assign is because of exception incompatibility
msg = "Exception values are incompatible."
if not self.base_type.exception_check and not self.base_type.exception_value:
msg += " Suggest adding 'noexcept' to type '{}'.".format(src_type)
return msg
return super(CPtrType, self).assignment_failure_extra_info(src_type)

def specialize(self, values):
base_type = self.base_type.specialize(values)
if base_type == self.base_type:
Expand Down
20 changes: 10 additions & 10 deletions tests/errors/cfuncptr.pyx
Expand Up @@ -41,14 +41,14 @@ def fail_struct_pointer():


_ERRORS = """
13:13: Cannot assign type 'int (int) except? -2' to 'int (*)(int) except -2'
14:13: Cannot assign type 'int (int) except? -2' to 'int (*)(int) except -1'
15:13: Cannot assign type 'int (int) except? -2' to 'int (*)(int) except? -1'
29:13: Cannot assign type 'int (int) except *' to 'int (*)(int) noexcept'
30:13: Cannot assign type 'int (int) except *' to 'int (*)(int) except -1'
31:13: Cannot assign type 'int (int) except *' to 'int (*)(int) except? -1'
40:32: Cannot assign type 'int (*)(int) except? -1 nogil' to 'int (*)(int) noexcept nogil'
40:32: Cannot assign type 'int (*)(int) except? -1 nogil' to 'int (*)(int) noexcept nogil'
40:37: Cannot assign type 'void (*)(int) except * nogil' to 'void (*)(int) noexcept nogil'
40:37: Cannot assign type 'void (*)(int) except * nogil' to 'void (*)(int) noexcept nogil'
13:13: Cannot assign type 'int (int) except? -2' to 'int (*)(int) except -2'. Exception values are incompatible.
14:13: Cannot assign type 'int (int) except? -2' to 'int (*)(int) except -1'. Exception values are incompatible.
15:13: Cannot assign type 'int (int) except? -2' to 'int (*)(int) except? -1'. Exception values are incompatible.
29:13: Cannot assign type 'int (int) except *' to 'int (*)(int) noexcept'. Exception values are incompatible. Suggest adding 'noexcept' to type 'int (int) except *'.
30:13: Cannot assign type 'int (int) except *' to 'int (*)(int) except -1'. Exception values are incompatible.
31:13: Cannot assign type 'int (int) except *' to 'int (*)(int) except? -1'. Exception values are incompatible.
40:32: Cannot assign type 'int (*)(int) except? -1 nogil' to 'int (*)(int) noexcept nogil'. Exception values are incompatible. Suggest adding 'noexcept' to type 'int (int) except? -1 nogil'.
40:32: Cannot assign type 'int (*)(int) except? -1 nogil' to 'int (*)(int) noexcept nogil'. Exception values are incompatible. Suggest adding 'noexcept' to type 'int (int) except? -1 nogil'.
40:37: Cannot assign type 'void (*)(int) except * nogil' to 'void (*)(int) noexcept nogil'. Exception values are incompatible. Suggest adding 'noexcept' to type 'void (int) except * nogil'.
40:37: Cannot assign type 'void (*)(int) except * nogil' to 'void (*)(int) noexcept nogil'. Exception values are incompatible. Suggest adding 'noexcept' to type 'void (int) except * nogil'.
"""
4 changes: 2 additions & 2 deletions tests/errors/e_excvalfunctype.pyx
Expand Up @@ -11,6 +11,6 @@ spam = grail # type mismatch


_ERRORS = u"""
9:8: Cannot assign type 'spamfunc' to 'grailfunc'
10:7: Cannot assign type 'grailfunc' to 'spamfunc'
9:8: Cannot assign type 'spamfunc' to 'grailfunc'. Exception values are incompatible. Suggest adding 'noexcept' to type 'int (int, char *) except 42'.
10:7: Cannot assign type 'grailfunc' to 'spamfunc'. Exception values are incompatible.
"""

0 comments on commit 572b640

Please sign in to comment.