Skip to content

Derive macro for IntoPyObject#4495

Merged
davidhewitt merged 17 commits into
PyO3:mainfrom
Icxolu:derive-intopyobject
Oct 26, 2024
Merged

Derive macro for IntoPyObject#4495
davidhewitt merged 17 commits into
PyO3:mainfrom
Icxolu:derive-intopyobject

Conversation

@Icxolu

@Icxolu Icxolu commented Aug 26, 2024

Copy link
Copy Markdown
Member

Initial implementation of #[derive(IntoPyObject)]. #[derive(FromPyObject)] was used as a base/reference for this implementation.

This currently supports #[pyo3(transparent)] as the only real option, which just forwards to the inner IntoPyObject impl.

  • Newtype structs (#[pyo3(transparent)] named and single tuple): forward impl.
  • Named structs: PyDict with the field names as keys
  • Tuple structs: PyTuple in declaration order
  • Enums: matches on the variant and treats it as an anonymous struct with the rules above.
  • Lifetimes and Generics are supported, 'py is treated special and is used a the Python<'py> lifetime if given.

We still need lots of tests here, but I thought I'll put this up as a POC already. Hopefully this can smoothen the migration to IntoPyObject a bit.

One thing not needed initially, but may be a nice to have at some point is some like #[pyo3(into_py_with = "...")] analogous to #[pyo3(from_py_with = "...")]

Closes #4458

@Icxolu Icxolu force-pushed the derive-intopyobject branch from ffc4c81 to 68e57f4 Compare August 28, 2024 21:41

@davidhewitt davidhewitt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This looks brilliant, thank you so much! I am travelling with family for the next few days so it might be the weekend before I have a chance to do any reviewing... 🙏

@Icxolu

Icxolu commented Aug 28, 2024

Copy link
Copy Markdown
Member Author

No worries, enjoy your trip.

@Icxolu Icxolu marked this pull request as ready for review August 29, 2024 22:18

@davidhewitt davidhewitt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Overall this looks spot on, thanks very much. I have given this initial review from my phone so haven't read the implementation in full detail, looks great in general.

I have a bunch of various suggestions, mostly related to interactions with FromPyObject.

Comment thread guide/src/migration.md
body: TokenStream,
}

struct NamedStructField<'a> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should consider the interaction with FromPyObject's options like #[pyo3(item)] and #[pyo3(attribute)]. I am fine if we just parse them and reject cases where we don't want to support them for IntoPyObject (yet); all I'd like to avoid is that we don't silently ignore those options.

If there is an unsupported case then users can split their FromPyObject and IntoPyObject derives onto different structs, and they are then aware their conversions will not round-trip.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Hmm, we could definitely parse them, but I'm not sure what we would do with them in this case. Due to the PyDict return type this always uses set_item. So I think it makes only sense to parse them, if there is a way (I'm not aware of any) to create an anonymous object and set arbitrary attributes on it. I guess we could accept item to allow renaming and to make a round-trip possible if it is given.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Good point. Let's handle item round trips here and error on attribute? Users can always make two separate types if they need #[attribute] for their FromPyObject implementation for some reason.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I added parsing for #[pyo3(item)] and #[pyo3(attribute)] (like for FromPyObject), roundtrip tests in test_frompy_intopy_roundtrip.rs and ui tests for the unsupported cases.

Comment thread pyo3-macros-backend/src/intopyobject.rs Outdated
Comment thread tests/ui/invalid_intopy_derive.stderr Outdated
Comment thread tests/test_intopyobject.rs

@davidhewitt davidhewitt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Sorry for the long review cycle, this is looking great and I think we're nearly there! Main change I would like to see is just on the interaction with the FromPyObject attributes, where I think it makes our future development simpler if we handle that before releasing.

Comment thread guide/src/conversions/traits.md Outdated
Comment thread guide/src/migration.md Outdated
Crate(CrateAttribute),
}

impl Parse for ContainerPyO3Attribute {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Reading all these structures it feels like there's a ton of boilerplate which might be interesting to refactor into common macro_rules! patterns (maybe). Not for this PR, but maybe a non-urgent follow up to explore sometime.

Comment on lines +179 to +291
_ => bail_spanned!(
fields.span() => "cannot derive `IntoPyObject` for empty structs"
),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I wonder, does it make sense for it to be empty tuple and/or empty dict? Perhaps again we leave this for a follow up and consider FromPyObject at the same time.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yeah, I think revisiting this in a followup (perhaps after 0.23) makes sense.

body: TokenStream,
}

struct NamedStructField<'a> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Good point. Let's handle item round trips here and error on attribute? Users can always make two separate types if they need #[attribute] for their FromPyObject implementation for some reason.

@Icxolu

Icxolu commented Oct 20, 2024

Copy link
Copy Markdown
Member Author

No worries, thanks for the comments. I'll have a look in a few days.

@Icxolu Icxolu force-pushed the derive-intopyobject branch from 2ed88ed to 49d9ce3 Compare October 25, 2024 16:48
@Icxolu Icxolu force-pushed the derive-intopyobject branch from 49d9ce3 to 748bad3 Compare October 25, 2024 16:54

@davidhewitt davidhewitt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This looks perfect, thanks so much! I'm really optimistic this will help users have a good experience upgrading to 0.23! 🚀

@davidhewitt davidhewitt added this pull request to the merge queue Oct 26, 2024
Merged via the queue into PyO3:main with commit c8eb45b Oct 26, 2024
@Icxolu Icxolu deleted the derive-intopyobject branch October 26, 2024 08:24
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.

ENH: boiler plate for IntoPy<PyObject>

2 participants