Repository: python/mypy
Pull request: Fix getargs argument passing
Fixes https://github.com/mypyc/mypyc/issues/1078

Introduced in https://github.com/python/mypy/pull/17930

See the first commit to see the bug (wrong condition)

Problem statement:
1.14.0 - regression bug: confusion of **kwargs with other parameters
Python 3.13
MacOS

Works in MyPy 1.13.0
Fails in MyPy 1.14.0

```python
class X:
    def __init__(self, msg: str, **variables: int) -> None:
         pass

X('test1', b=2)

# when executing with the generated .so library of MyPyC:
TypeError: argument for __init__() given by name ('msg') and position (1)
```

Hints from issue discussion:
Hmm, I can't reproduce the issue with mypy 1.14
I also can't reproduce this using mypy v1.14. Are you able to reproduce this in a clean environment?
I only removed the .mypy_cache / .build dirs and the .so files. am I missing any other dirty file?
Then, in the same env, I installed 1.13 and it worked fine.
What does running this bash script get you:
```
set -ex
rm -rf tmp
mkdir tmp
cd tmp
echo "
class X:
    def __init__(self, msg: str, **variables: int) -> None:
         pass

X('test1', b=2)
print('okay')
" > test.py

python --version
python -m venv env
env/bin/python -m pip install 'mypy>=1.14'
env/bin/python -m mypyc test.py
env/bin/python -c 'import test; print(test.__file__)'
```
Thanks!

Apple M1 Max
MacOS Sonoma 14.2

1. Output
> Python 3.13.0

2. Output
> from setuptools import setup
> ModuleNotFoundError: No module named 'setuptools'

3. Added
`env/bin/python -m pip install setuptools`

4. Output
`okay`

5. Then, I started the shell
`env/bin/python`

```python
from test import X
X('test1', b=2)
```

<img width="612" alt="Screenshot 2024-12-27 at 20 09 03" src="https://github.com/user-attachments/assets/3f83484c-ac94-4d3f-b17f-9b6f0edbbe82" />

##  Hypothesis:

- Maybe is a shell related problem instead of kwargs? 
- or something related to import method (import test from X)?

## Kwargs Hypothesis

Look that using all arguments as keywords it works fine.

<img width="594" alt="Screenshot 2024-12-27 at 20 19 14" src="https://github.com/user-attachments/assets/6a79b3bf-ec37-4ef8-b608-af130e3e137b" />

## With MyPy 1.13

`env/bin/python -m pip install 'mypy==1.13'`

<img width="829" alt="Screenshot 2024-12-27 at 20 24 50" src="https://github.com/user-attachments/assets/443041a9-8a40-455c-8657-0833da7dbfc6" />


Okay, great, I can now repro! My guess is the triggering code needs to be interpreted. E.g. this should be a one liner: `rm -rf build; mypyc test.py; python -c "import test; test.X('test1', b=2)"`

Bisects to https://github.com/python/mypy/pull/17930

Commit message: Fix getargs argument passing

Fixes https://github.com/mypyc/mypyc/issues/1078

See https://github.com/python/mypy/pull/17930

Patch to review:
diff --git a/mypyc/lib-rt/getargs.c b/mypyc/lib-rt/getargs.c
index 4f2f8aa0be83..9b6ab2f5a3f2 100644
--- a/mypyc/lib-rt/getargs.c
+++ b/mypyc/lib-rt/getargs.c
@@ -374,8 +374,6 @@ vgetargskeywords(PyObject *args, PyObject *kwargs, const char *format,
             int res = PyDict_GetItemStringRef(kwargs, kwlist[i], &current_arg);
             if (res == 1) {
                 Py_DECREF(current_arg);
-            }
-            else if (unlikely(res == 0)) {
                 /* arg present in tuple and in dict */
                 PyErr_Format(PyExc_TypeError,
                              "argument for %.200s%s given by name ('%s') "
diff --git a/mypyc/test-data/run-classes.test b/mypyc/test-data/run-classes.test
index cf30bddbef64..f7aa72d0c6fd 100644
--- a/mypyc/test-data/run-classes.test
+++ b/mypyc/test-data/run-classes.test
@@ -467,6 +467,14 @@ a = A(10)
 assert a.foo() == 11
 assert foo() == 21
 
+[case testClassKwargs]
+class X:
+    def __init__(self, msg: str, **variables: int) -> None:
+         pass
+[file driver.py]
+from native import X
+X('hello', a=0)
+
 [case testGenericClass]
 from typing import TypeVar, Generic, Sequence
 T = TypeVar('T')


Code review comments:
diff_hunk: @@ -374,8 +374,6 @@ vgetargskeywords(PyObject *args, PyObject *kwargs, const char *format,
             int res = PyDict_GetItemStringRef(kwargs, kwlist[i], &current_arg);
             if (res == 1) {
path: mypyc/lib-rt/getargs.c
text: @user1:
```suggestion
            if (unlikely(res == 1)) {
```
To fully restore the behavior prior to #17930.