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

try and use named arguments from caller for matching name #2294

Merged
merged 1 commit into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions mypy_django_plugin/lib/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from mypy.nodes import (
GDEF,
MDEF,
ArgKind,
AssignmentStmt,
Block,
ClassDef,
Expand Down Expand Up @@ -182,6 +183,12 @@ def get_call_argument_by_name(ctx: Union[FunctionContext, MethodContext], name:
Return the expression for the specific argument.
This helper should only be used with non-star arguments.
"""
# try and pull the named argument from the caller first
for kinds, argnames, args in zip(ctx.arg_kinds, ctx.arg_names, ctx.args):
for kind, argname, arg in zip(kinds, argnames, args):
if kind == ArgKind.ARG_NAMED and argname == name:
return arg
Copy link
Contributor Author

Choose a reason for hiding this comment

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

one could imagine a perverse class that does something like:

class FK(ForeignKey[_ST, _GT]):
   def __init__(self, *args, **kwargs):
       kwargs['null'] = not kwargs.get('null', False)
       super().__init__(*args, **kwargs)

but I'm thinking that's probably not going to happen and possibly ok to ignore?

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 guess they could even do that today and trick the current code with:

class FK(ForeignKey[_ST, _GT]):
    def __init__(self, *args, null: bool = False, **kwargs):
        null = not null
        super().__init__(*args, null=null, **kwargs)

so definitely not a problem imo :)

Copy link
Member

Choose a reason for hiding this comment

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

What do you think about reusing _get_argument from mypy.plugins.common? Would it solve this problem?

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 the current code is equivalent to that so it wouldn't help (_get_argument only looks at callee args)


if name not in ctx.callee_arg_names:
return None
idx = ctx.callee_arg_names.index(name)
Expand Down
25 changes: 25 additions & 0 deletions tests/typecheck/fields/test_related.yml
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,31 @@
class User(AbstractUser):
pass

- case: nullable_foreign_key_with_init_overridden
main: |
from myapp.models import A
reveal_type(A.objects.get().b) # N: Revealed type is "Union[myapp.models.B, None]"
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from typing import Any, TypeVar
from django.db import models

_ST = TypeVar("_ST", contravariant=True)
_GT = TypeVar("_GT", covariant=True)

class FK(models.ForeignKey[_ST, _GT]):
def __init__(self, *args: Any, **kwargs: Any) -> None:
kwargs.setdefault('on_delete', models.CASCADE)
super().__init__(*args, **kwargs)

class B(models.Model): ...

class A(models.Model):
b = FK(B, null=True, on_delete=models.CASCADE)

- case: related_manager_is_a_subclass_of_default_manager
main: |
Expand Down
Loading