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

Refactoring of FnArg #4033

Merged
merged 7 commits into from
Apr 14, 2024
Merged

Refactoring of FnArg #4033

merged 7 commits into from
Apr 14, 2024

Conversation

Icxolu
Copy link
Contributor

@Icxolu Icxolu commented Apr 1, 2024

Following the discussion from #4002 (comment)

This reworks the FnArg type in our macro codegen to contain a FnArgKind enum instead of a bunch of booleans. This is done to make much clearer which branches there are, depending on the argument kind. It also makes weird combinations (like optional py/varargs, required kwargs, ...) unrepresentable. As a consequence the error checking for those cases are now at construction/parsing side instead of usage side, which also feels more appropriate.

I left a few FIXMEs, so I leave this as a draft for now, but feedback is welcome 😄

@Icxolu Icxolu added the CI-skip-changelog Skip checking changelog entry label Apr 1, 2024
Copy link
Member

@mejrs mejrs 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 is great. I'm not very familiar with this part of the code base but I have some thoughts:

arg: &FnArg<'_>,
from_py_with: syn::Ident,
arg_value: TokenStream, // expected type: Option<&'a Bound<'py, PyAny>>
holders: &mut Holders,
ctx: &Ctx,
) -> Result<TokenStream> {
) -> TokenStream {
// FIXME: Is there a better way to guarantee this in the type system?
Copy link
Member

Choose a reason for hiding this comment

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

You could write

pub(crate) fn impl_regular_arg_param(
    arg: &FnArg<'_>,
    from_py_with: syn::Ident,
    arg_value: TokenStream, // expected type: Option<&'a Bound<'py, PyAny>>
    holders: &mut Holders,
    default: Whatever,
    ty_opt: Whatever,
    ctx: &Ctx,
) -> TokenStream {
    let FnArg {
        name,
        ty,
        attrs,
        ..
    } = arg;

or you could make enum variant types:

pub struct Regular<'a> {
    pub name: &'a syn::Ident,
    pub ty: &'a syn::Type,
    pub attrs: PyFunctionArgPyO3Attributes,
    pub kind: FnArgKind<'a>,
    default: Option<syn::Expr>,
    ty_opt: Option<&'a syn::Type>,
}

pub struct VarArgs<'a> {
    pub name: &'a syn::Ident,
    pub ty: &'a syn::Type,
    pub attrs: PyFunctionArgPyO3Attributes,
    pub kind: FnArgKind<'a>,
}

pub enum FnArg<'a> {
    Regular(Regular<'a>)
    VarArgs(VarArgs<'a>),
    ...
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the feedback 👍, I though about using enum variant types, but initially decided against them because it might get very verbose. But maybe it is beneficial to have a separate type for each kind of argument. I think I'll give it a try and see how it turns out.

pyo3-macros-backend/src/method.rs Outdated Show resolved Hide resolved
Comment on lines 646 to 649
.map(|arg| match arg.kind {
FnArgKind::Py => quote!(py),
FnArgKind::CancelHandle { .. } => quote!(__cancel_handle),
_ => unreachable!(),
Copy link
Member

Choose a reason for hiding this comment

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

maybe some kind of

enum PyOrCancelHandle{
    Py,
    CancelHandle,
}

could get rid of this unreachable 🤔 Just some food for thoughts

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm, since this from the general parsed types, I don't think we can do a lot to avoid it. But since this is under CallingConvention::Noargs there really shouldn't be any "real" argument's (reaching Python), except for self which is handled separately. So I think unreachable!() is appropriate here, but an explanation comment might be good, I'll add that.

@Icxolu
Copy link
Contributor Author

Icxolu commented Apr 2, 2024

I actually like a lot how the enum variant types turned out. It's a bit more boilerplate for type declaration, but it allows to actually see and specify the type system which argument types its operates on or returns.

@Icxolu Icxolu marked this pull request as ready for review April 2, 2024 22:17
@wyfo
Copy link
Contributor

wyfo commented Apr 4, 2024

Why did you choose VarArgs(VarArg<'a>), over

    VarArgs {
        name: &'a syn::Ident,
        ty: &'a syn::Type,
    },

? I haven't seen VarArg type and the others being used in the code, except for constructor ofc, so a tuple variant could be enough, couldn't?

Also, I've compared both versions, and I prefer FnArgKind one, because I find it more natural with less boilerplate (but I'm not a reviewer, so my opinion doesn't count a lot).

@Icxolu
Copy link
Contributor Author

Icxolu commented Apr 4, 2024

Valid question 😄, opinions are always welcome

I did settle on this one, because it allows to specific argument types be handles in separate. It's true that no all of them are used currently this way, but for example impl_regular_arg_param only takes &RegularArg<'_> now. That one was also the origin of this whole refactoring process, so that the implementation could be reused for the #[setter] argument (see #3998 and #4002). That one felt a bit clunky with the FnArgKind approach, because I could not make sure it gets the correct kind at compile time. Another one is split_off_python_arg which now guarantees statically that the split off argument is indeed a PyArg (if any).

For the other variants I did it part of consistency and part to make future refactorings around argument kinds easier.

Personally I also find the current state looks nicer over the FnArgKind approach, but that's subjective I guess 😁

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.

Looks great to me, thanks! A couple of name suggestions and tiny edge cases, otherwise please feel free to merge 👍

pyo3-macros-backend/src/method.rs Outdated Show resolved Hide resolved
pyo3-macros-backend/src/method.rs Show resolved Hide resolved
pyo3-macros-backend/src/method.rs Show resolved Hide resolved
pyo3-macros-backend/src/params.rs Show resolved Hide resolved
pyo3-macros-backend/src/params.rs Outdated Show resolved Hide resolved
pyo3-macros-backend/src/pymethod.rs Outdated Show resolved Hide resolved
@davidhewitt davidhewitt added this pull request to the merge queue Apr 14, 2024
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to failed status checks Apr 14, 2024
@Icxolu Icxolu added this pull request to the merge queue Apr 14, 2024
Merged via the queue into PyO3:main with commit cc7e16f Apr 14, 2024
43 checks passed
@Icxolu Icxolu deleted the refactor-fnarg branch April 14, 2024 14:54
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.

4 participants