Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[mypyc] Support __pos__ and __abs__ dunders #13490

Merged
merged 1 commit into from
Sep 1, 2022

Conversation

ichard26
Copy link
Collaborator

@ichard26 ichard26 commented Aug 23, 2022

Description

Towards mypyc/mypyc#553.

Add supports for these dunders:

  • __pos__
  • __abs__

Calls to these dunders on native classes will be specialized to use a direct method call instead of using PyNumber_Absolute.

Also calls to abs() on any types have been optimized. They no longer involve a builtins dictionary lookup. It's probably possible to write a C helper function for abs(int) to avoid the C-API entirely for native integers, but I don't feel skilled enough to do that yet.

Test Plan

  • Added irbuild tests to verify the optimizations are working as intended
  • Added one run test to verify the dunders are actually visible to the Python C-API (and therefore other code)

Calls to these dunders on native classes will be specialized to use a
direct method call instead of using PyNumber_Absolute.

Also calls to abs() on any types have been optimized. They no longer
involve a builtins dictionary lookup. It's probably possible to write a
C helper function for abs(int) to avoid the C-API entirely for native
integers, but I don't feel skilled enough to do that yet.
Copy link
Collaborator

@JukkaL JukkaL left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR! Looks good, I have just one question.

arg_typ = builder.node_type(arg)
if isinstance(arg_typ, RInstance) and arg_typ.class_ir.has_method("__abs__"):
obj = builder.accept(arg)
return builder.gen_method_call(obj, "__abs__", [], None, expr.line)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if __abs__ has been defined incorrectly and, for example, accepts an argument? Maybe guard against this.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't that fail type checking as abs() is annotated to only accept an object that implements __abs__ in accordance to:

@runtime_checkable
class SupportsAbs(Protocol[_T_co]):
    @abstractmethod
    def __abs__(self) -> _T_co: ...
    
abs(__x: SupportsAbs[_T]) -> _T: ...

https://github.com/python/typeshed/blob/2c534dc2208191dc1ae1a6f51554fa5022cbfa0a/stdlib/typing.pyi#L313-L316
https://github.com/python/typeshed/blob/2c534dc2208191dc1ae1a6f51554fa5022cbfa0a/stdlib/builtins.pyi#L1199

class A:
    def __abs__(self, arg: int) -> int:
        return arg + 1


v = abs(A())
❯ mypyc t.py
t.py:6: error: Need type annotation for "v"
t.py:6: error: Argument 1 to "abs" has incompatible type "A"; expected "SupportsAbs[<nothing>]"

There's also a if guard that checks for one positional argument before attempting specialization so even if this somehow type checks, the specialization will fail and return None. The return type shouldn't matter as abs() doesn't check the return type of __abs__ at all.

Am I missing something?

Copy link
Collaborator Author

@ichard26 ichard26 Aug 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do see that mypyc doesn't complain if you don't ever call abs() on the native class with the invalid __abs__ dunder and simply generates bad code that isn't compatible with the C-API:

build/__native.c:39:20: error: initialization of ‘PyObject * (*)(PyObject *)’ {aka ‘struct _object * (*)(struct _object *)’} from incompatible pointer type ‘PyObject * (*)(PyObject *, PyObject *)’ {aka ‘struct _object * (*)(struct _object *, struct _object *)’} [-Werror=incompatible-pointer-types]
   39 |     .nb_absolute = CPyDunder___abs__A,
      |                    ^~~~~~~~~~~~~~~~~~
build/__native.c:39:20: note: (near initialization for ‘A_as_number.nb_absolute’)

... but I don't think checking for invalid dunders (and raising an error) should happen here since it applies to all dunders, not just __abs__. It would also miss the error if abs() is never used in compiled code.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this is something that should be checked elsewhere. I'll create an issue about this -- no need to fix it here.

@ichard26
Copy link
Collaborator Author

ichard26 commented Sep 1, 2022

Hi, I'd appreciate if this could be looked at again soon, thanks in advance!

arg_typ = builder.node_type(arg)
if isinstance(arg_typ, RInstance) and arg_typ.class_ir.has_method("__abs__"):
obj = builder.accept(arg)
return builder.gen_method_call(obj, "__abs__", [], None, expr.line)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this is something that should be checked elsewhere. I'll create an issue about this -- no need to fix it here.

@JukkaL JukkaL merged commit 3c7e216 into python:master Sep 1, 2022
@ichard26 ichard26 deleted the support-pos-and-abs-dunders branch September 1, 2022 18:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants