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

Fallback to checking kwargs of function call when looking for arguments of a call #2044

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

md384
Copy link
Contributor

@md384 md384 commented Apr 5, 2024

I have made things!

Related issues

Closes #2043

@md384 md384 marked this pull request as ready for review April 5, 2024 19:56
Copy link
Member

@sobolevn sobolevn left a comment

Choose a reason for hiding this comment

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

We have several usages of this function: https://github.com/search?q=repo%3Atypeddjango%2Fdjango-stubs%20get_call_argument_by_name&type=code

Can you please also test the QuerySet one?

@md384 md384 requested a review from sobolevn April 6, 2024 20:34

class CustomQuerySet(QuerySet[_Model]):
def values_list(self, *args: Any, **kwargs: Any) -> QuerySet[_Model]:
return super().values_list(*args, **kwargs)
Copy link
Member

Choose a reason for hiding this comment

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

stupid question: what will happen if we do kwargs.pop('flat'); super().values_list(*args, flat=True, **kwargs)?

Copy link
Contributor Author

@md384 md384 Apr 7, 2024

Choose a reason for hiding this comment

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

The flat=True doesn't change the return type of the get call in the tests. This seems like a separate bug and is beyond my knowledge of mypy or mypy plugins -there doesn't seem to be anything usable in the context.

@md384 md384 requested a review from sobolevn April 7, 2024 00:17
@sobolevn sobolevn requested a review from flaeppe April 7, 2024 07:25
Copy link
Member

@flaeppe flaeppe left a comment

Choose a reason for hiding this comment

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

I'm suspecting there's more cases to cover for here.

return args[0]

# check for named arg in function call keyword args
if name in ctx.arg_names[1]:
Copy link
Member

Choose a reason for hiding this comment

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

I don't think usage of ctx.arg_names[1] will work like this here.

For example if I instead declare my field like:

class SecondField(fields.IntegerField[_ST, _GT]):
    def __init__(self, *args: Any, blank: bool = True, **kwargs: Any) -> None:
        super().__init__(*args, blank=blank, **kwargs)

And call it with: SecondField(blank=True, null=True)

ctx.arg_names could look like:

[[], ['blank'], ['null']]

Which results in a mismatch, resulting in the issue we're trying to solve.

With the above in mind. If I instead then do:

class ThirdField(fields.IntegerField[_ST, _GT]):
    def __init__(self) -> None:
        super().__init__()

And call it like: ThirdField() I get an IndexError crash on ctx.arg_names[1]

Copy link
Contributor Author

@md384 md384 Apr 7, 2024

Choose a reason for hiding this comment

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

I think we can do something along the lines of

    # check for named arg in function call keyword args
    for idx, arg_group in enumerate(ctx.arg_names):
        if name in arg_group:
            sub_idx = arg_group.index(name)
            return ctx.args[idx][sub_idx]

to handle these cases.

@md384 md384 requested a review from flaeppe April 8, 2024 16:25
@flaeppe
Copy link
Member

flaeppe commented Apr 8, 2024

Did you see my comment: #2043 (comment)?

@md384
Copy link
Contributor Author

md384 commented Apr 8, 2024

Did you see my comment: #2043 (comment)?

Yes, but that means the developer has to know about these "magic" kwargs in a number of functions.

IMO, the plugin should be able to handle the *args, **kwargs case since it is a common pattern and the presence/absence of some kwargs unexpectedly (at least for the typical django-stubs user) changes the return type.

@flaeppe
Copy link
Member

flaeppe commented Apr 8, 2024

I disagree with that the plugin should try to resolve *args: Any and **kwargs: Any here. In general the plugin should help express things that can't be statically declared, but this isn't such a case. There's a way to statically declare this and, in my eyes, introducing plugin code here just increases the risk of interference.

I can agree with that it might be tedious to declare any or all overridden arguments, especially since there are quite a few..

But we want the plugin to do as little as possible and fallback on mypy to do the rest.

At least that's my opinion. @intgr or @sobolevn might have different thoughts here.

Copy link
Collaborator

@intgr intgr left a comment

Choose a reason for hiding this comment

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

I'm torn on this.

From a typing perspective, *args: Any, **kwargs: Any is problematic, since it suppresses all typechecking of the call. You can write MyIntegerField(1, 2, 3, null=Ellipsis, no_such_arg=123) and mypy won't bat an eyelid. From this perspective it seems consistent to also reject inspecting null= if the function definition is vague.

On the other hand, this PR does seem like an improvement in making django-stubs less finnicky. The implementation makes sense and is self-contained in the helper function; other callers of this function may benefit from the changes as well.

If we can't think of any downsides of this approach, seems unfair to block it. So a weak vote for merging from me.

mypy_django_plugin/lib/helpers.py Show resolved Hide resolved
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

Custom fields with overwritten passthrough constructors don't change types based on attributes
4 participants