-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
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] Introduce ClassBuilder and add support for attrs classes #11328
Conversation
prior to this change dataclasses.field would only be specialized if called without the module, as field()
"name={self.name}, module_name={self.module_name}, " | ||
"is_trait={self.is_trait}, is_generated={self.is_generated}, " | ||
"is_abstract={self.is_abstract}, is_ext_class={self.is_ext_class}" | ||
")".format(self=self)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a convenience. This is the sort of thing dataclasses are great for!
mypyc/irbuild/expression.py
Outdated
elif isinstance(callee, SuperExpr): | ||
return translate_super_method_call(builder, expr, callee) | ||
else: | ||
return translate_call(builder, expr, callee) | ||
return get_specialization(builder, expr, callee) or \ | ||
translate_call(builder, expr, callee) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This now performs specialization for all CallExpr
types other than SuperExpr
which does not have a fullname
attribute. I could use get_specialization(builder, expr, callee, fullname=False)
for SuperExpr
if that's the right thing to do.
mypyc/irbuild/specialize.py
Outdated
def get_specialization(builder: 'IRBuilder', expr: CallExpr, callee: RefExpr, | ||
typ: Optional[RType] = None, fullname: bool = True) -> Optional[Value]: | ||
# TODO: Allow special cases to have default args or named args. Currently they don't since | ||
# they check that everything in arg_kinds is ARG_POS. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wasn't sure if this TODO applied to specialization in general, or if it was specific to the context where it was called. I copied it here from translate_refexpr_call
extension vs non-extension, plus various types of dataclasses
Thanks for the PR! Have you tried writing a micro-benchmark (or two) to see if this will improve performance?
Hmm if the compiled module won't work without (Not a full review, I will get back to this later.) |
To add a benchmark do I just add a new test case to
Since I made that comment I added the new |
You can write a throwaway microbenchmark and post the code and results here. We have some dataclass benchmarks which may be useful as examples: https://github.com/mypyc/mypyc-benchmarks/blob/master/microbenchmarks/dataclasses.py For more information about the benchmarks, see https://github.com/mypyc/mypyc-benchmarks. It would also be great if you can add some attrs microbenchmarks there, but that's optional. |
Ah, I see. I wasn't aware of that repo. Yeah, it should be trivial to add some new attrs benchmarks based on the existing dataclass code. I expect the the results to be similar. |
Here are the bench results. Note that the
I'll commit the benchmarks once this gets merged. |
I just went over this PR again and I think it is in pretty good shape and ready for review. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for doing some benchmarking! The results look great.
I did a first review pass and looks good overall, just some minor comments. I'll do a final pass next week (this is quite a big PR!).
mypyc/irbuild/specialize.py
Outdated
# they check that everything in arg_kinds is ARG_POS. | ||
|
||
if isinstance(callee, (MemberExpr, NameExpr)): | ||
name: Optional[str] = callee.fullname if callee.fullname else callee.name |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should only use callee.name
if it's a MemberExpr
and typ
is defined. I think that this logic is a bit too complicated and it would perhaps be better to have separate functions for get_function_specialization
and get_method_specialization
(only the latter would accept typ
or receiver_type
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
have a look and see if I did this right.
mypyc/irbuild/classdef.py
Outdated
|
||
|
||
class ClassBuilder: | ||
"""Create IR for a class definition.""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for refactoring these! This now looks cleaner.
Can you mention that this is effectively an abstract class in the docstring, even though there are no abstract members?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done. I also went ahead and marked the methods as abstract: even though they all return None a subclass would effectively be broken if it did not implement these (with the possible exception of finalize
)
Microbenchmarks for this PR: python/mypy#11328
Hi, just pinging on this one |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the abstraction of the ClassBuilder
, which makes the code structure clearer and is easy to maintain.
@@ -209,7 +209,8 @@ def transform_call_expr(builder: IRBuilder, expr: CallExpr) -> Value: | |||
callee = callee.analyzed.expr # Unwrap type application | |||
|
|||
if isinstance(callee, MemberExpr): | |||
return translate_method_call(builder, expr, callee) | |||
return apply_method_specialization(builder, expr, callee) or \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure whether it's fine to adding a specialization here, since it's slightly different from the original semantics.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm pretty sure this change is required for the feature to work. What can we do to test that it's safe?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then val = apply_method_specialization(builder, expr, callee, receiver_typ)
inside translate_method_call
might be redundant?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I confirmed that both need to be present in order to work. This second call is handling the case where receiver_typ
is present.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks again for your PR! Only some small suggestions on tests.
mypyc/irbuild/util.py
Outdated
@@ -13,6 +13,7 @@ | |||
'dataclasses.dataclass', | |||
'attr.s', | |||
'attr.attrs', | |||
'attr.define', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add tests for this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I ended up removing the attr.define
support. I did some tests and something about the way the mypyc support for attrs and dataclasses works is tripping up attrs auto-detection of annotations. I'm wary of delaying this PR further because I'm afraid I'll run out of steam, and we can always add attr.define
support later.
@@ -54,15 +54,11 @@ | |||
'run-dunders.test', | |||
'run-singledispatch.test', | |||
'run-attrs-non-auto.test', | |||
'run-attrs.test', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we could just use one test file for these cases?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
Ok, I think this is all ready to go! |
Thanks! |
…hon#11328) Create a class structure for building different types of classes, extension vs non-extension, plus various types of dataclasses. Specilize member expressions. Prior to this change dataclasses.field would only be specialized if called without the module, as field() Add support for attrs classes and build proper __annotations__ for dataclasses. Co-authored-by: Jingchen Ye <97littleleaf11@gmail.com>
Description
attrs
classes are very similar todataclasses
when used withauto_attribs=True
(which tells attrs use to type annotations), so it was fairly trivial to add support for them to mypyc.This MR also fixes a bug with specialization of functions like
dataclasses.field
that when they are accessed from their module rather than the globals. this was necessary because the idiomatic way to use attrs is@attr.s
andattr.ib
.I was wondering if I should skip the special treatment of attrs classes if
auto_attribs
is not enabled, but if the attrs class is not using annotations then the compiled module wouldn't work whether mypyc skipped it or not, so I'm not sure if it's worth the extra complexity and potential fragility of doing that check.Test Plan
I added tests for attrs that are based off of the dataclasses tests, and I also modified the dataclasses test to demonstrate the problem with specialization of
dataclasses.field
.