Skip to content

Conversation

@Icxolu
Copy link
Contributor

@Icxolu Icxolu commented Aug 17, 2024

This extends the #[pyo3(get)] conversion specialization to include IntoPyObject. I implemented the following priority list (or at least tried to 🙃 )

  1. Py<T>
  2. IntoPyObject for &T
  3. Temp case: IntoPyObject + Clone for T (to prefer over IntoPy)
  4. Compat Fallback: ToPyObject for T
  5. Compat Fallback: IntoPy + Clone for T
  6. Base case: IntoPyObject + Clone for T (currently only used to emit the correct diagnostic if no conversion trait is implemented)

@Icxolu Icxolu added the CI-skip-changelog Skip checking changelog entry label Aug 17, 2024
@Icxolu
Copy link
Contributor Author

Icxolu commented Aug 17, 2024

Hmm, not sure if there is a way to rewrite PyO3GetFieldIntoPyObject without associated type bounds while still keeping the correct diagnostic... I tried

pub trait PyO3GetFieldIntoPyObject: for<'py> IntoPyObject<'py> + Clone
where
    for<'py> <Self as IntoPyObject<'py>>::Error: Into<PyErr>,
{
}

but this forces additional trait bounds on the usage site and causes the diagnostic of IntoPyObject to be emitted instead of this one.

Copy link
Member

@davidhewitt davidhewitt left a comment

Choose a reason for hiding this comment

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

Nice, thanks!

Hmm, not sure if there is a way to rewrite PyO3GetFieldIntoPyObject without associated type bounds

Hmm, I had a quick go just now but no obvious success, will continue to explore another rmoment.

Comment on lines 1549 to 1553
let _holder = unsafe {
BoundRef::ref_from_ptr(py, &obj)
.downcast_unchecked::<ClassT>()
.try_borrow()?
};
Copy link
Member

Choose a reason for hiding this comment

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

I think we now have a lot of repetition of this pattern, maybe refactor into a function? Possibly try of the offset operation just below also.

@mejrs
Copy link
Member

mejrs commented Aug 17, 2024

You can try using #[diagnostic::do_not_recommend] to suppress these helper traits from showing up in error messages. But I've not used it and I'm not sure if it has landed in stable.

@davidhewitt
Copy link
Member

this forces additional trait bounds on the usage site and causes the diagnostic of IntoPyObject to be emitted instead of this one.

Given that PyO3GetFieldIntoPyObject exists to emit a helpful diagnostic, maybe can just gate it behind a cfg so that we only try to use it on sufficiently new compilers?

@mejrs
Copy link
Member

mejrs commented Aug 17, 2024

Given that PyO3GetFieldIntoPyObject exists to emit a helpful diagnostic, maybe can just gate it behind a cfg so that we only try to use it on sufficiently new compilers?

I'm not particularly interested in putting in effort to prettify errors on old compilers. I would prefer having a simpler implementation.

@davidhewitt
Copy link
Member

Given that PyO3GetFieldIntoPyObject exists to emit a helpful diagnostic, maybe can just gate it behind a cfg so that we only try to use it on sufficiently new compilers?

I'm not particularly interested in putting in effort to prettify errors on old compilers. I would prefer having a simpler implementation.

I think we mean the same thing, though the problem is that the trait exists to emit a diagnostic specific to this case!

@Icxolu
Copy link
Contributor Author

Icxolu commented Aug 18, 2024

Given that PyO3GetFieldIntoPyObject exists to emit a helpful diagnostic, maybe can just gate it behind a cfg so that we only try to use it on sufficiently new compilers?

This could work, luckily associated type bounds were stabilized in 1.79 which is also the version we use for diagnostic_namespace

Copy link
Member

@davidhewitt davidhewitt left a comment

Choose a reason for hiding this comment

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

Overall this is looking good; just some final thoughts to refine this one off! 👀

where
ClassT: PyClass,
Offset: OffsetCalculator<ClassT, FieldT>,
FieldT: PyO3GetField,
Copy link
Member

Choose a reason for hiding this comment

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

I think there might be no need for this PyO3GetField trait any more, as it will never be used for a diagnostic? Might be able to simplify here.

Suggested change
FieldT: PyO3GetField,
FieldT: IntoPy<Py<PyAny>> + Clone

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It might show if only IntoPy<PyObject> (and not IntoPyObject) is implemented while Clone is missing. But this is probably such a specific case (and we want people to use IntoPyObject anyway) that we should probably just remove it.

// The bound goes here rather than on the block so that this impl is always available
// if no specialization is used instead
where
FieldT: PyO3GetFieldIntoPyObject,
Copy link
Member

Choose a reason for hiding this comment

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

Maybe if the other PyO3GetField trait can be removed, this one could be renamed to just PyO3GetField instead.

Comment on lines 1446 to 1450
PyMethodDefType::Getter(PyGetterDef {
name,
meth: pyo3_get_value_into_pyobject::<ClassT, FieldT, Offset>,
doc,
})
Copy link
Member

Choose a reason for hiding this comment

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

Because there is a separate IntoPyObject + Clone case, this bound can never be satisfied, right?

Suggested change
PyMethodDefType::Getter(PyGetterDef {
name,
meth: pyo3_get_value_into_pyobject::<ClassT, FieldT, Offset>,
doc,
})
unreachable!("exists purely to emit diagnostics on unimplemented traits")

If so, I guess the same applies to the other cfg-d variant just above too.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is true until we can remove the ToPyObject and IntoPy cases, at which point we can use this case, and remove the special case for IntoPyObject. If you prefer, we can put unreachable until then.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually I had to put panic since unreachable with a message is not allowed in const yet.

= note: implement `ToPyObject` or `IntoPy<PyObject> + Clone` for `PhantomData<i32>` to define the conversion
= note: required for `PhantomData<i32>` to implement `PyO3GetField`
note: required by a bound in `PyClassGetterGenerator::<ClassT, FieldT, Offset, false, false>::generate`
= help: the trait `for<'py> IntoPyObject<'py>` is not implemented for `PhantomData<i32>`, which is required by `PhantomData<i32>: PyO3GetFieldIntoPyObject`
Copy link
Member

Choose a reason for hiding this comment

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

A thought: from testing locally, if I change the definition of PyO3GetFieldIntoPyObject to have a lifetime 'py, i.e. PyO3GetFieldIntoPyObject<'py>: IntoPyObject<'py> + Clone.

... then (after adjusting other uses accordingly), the error message looks more like this:

Suggested change
= help: the trait `for<'py> IntoPyObject<'py>` is not implemented for `PhantomData<i32>`, which is required by `PhantomData<i32>: PyO3GetFieldIntoPyObject`
= help: the trait `IntoPyObject<'_>` is not implemented for `PhantomData<i32>`, which is required by `for<'py> PhantomData<i32>: PyO3GetFieldIntoPyObject<'py>`

Is that better? I think it might be slightly because it moves the HRTB later in the message. I think it's functionally equivalent otherwise?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it should be equivalent to put the HRTB in the method bound instead of the the super trait, yes.

Copy link
Member

@davidhewitt davidhewitt left a comment

Choose a reason for hiding this comment

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

Thanks, this one looks great to me now!

@davidhewitt davidhewitt added this pull request to the merge queue Aug 21, 2024
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to failed status checks Aug 21, 2024
@davidhewitt davidhewitt added this pull request to the merge queue Aug 21, 2024
Merged via the queue into PyO3:main with commit 7879d57 Aug 21, 2024
@Icxolu Icxolu deleted the pyclass-get branch August 22, 2024 16:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CI-skip-changelog Skip checking changelog entry

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants