Repository: agronholm/typeguard

Issue:
Problem with := operator
### Things to check first

- [x] I have searched the existing issues and didn't find my bug already reported there

- [x] I have checked that my bug is still present in the latest release


### Typeguard version

4.4.1

### Python version

3.12.1

### What happened?

```
Traceback (most recent call last):
  File "test.py", line 3, in <module>
    import A
  File "<frozen importlib._bootstrap>", line 1360, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1331, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 935, in _load_unlocked
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/site-packages/typeguard/_importhook.py", line 98, in exec_module
    super().exec_module(module)
  File "A.py", line 7, in <module>
    f()
  File "A.py", line 3, in f
    if v := tuple():
       ^^^^^^^^^^^^
TypeError: check_variable_assignment() takes 3 positional arguments but 4 were given
```


### How can we reproduce the bug?

`test.py`:
```python3
from typeguard import install_import_hook
with install_import_hook(('A',)):
    import A
```
`A.py`:
```python3
def f() -> None:
    v: str
    if v := tuple():
        pass
    print("OK")

f()
```
```shell
$ python3 A.py
OK

$ python3 test.py
<crash>
```
The problem only occurs when `v` is a local variable.

Discussion:
@jolaf: For some reason `suppress_type_checks()` doesn't help as a workaround, which probably is another issue.

Solution patch:
diff --git a/docs/versionhistory.rst b/docs/versionhistory.rst
index 79a6e20..2d45feb 100644
--- a/docs/versionhistory.rst
+++ b/docs/versionhistory.rst
@@ -11,6 +11,8 @@ This library adheres to
   regression introduced in v4.4.1)
 - Fixed display of module name for forward references
   (`#492 <https://github.com/agronholm/typeguard/pull/492>`_; PR by @JelleZijlstra)
+- Fixed ``TypeError`` when using an assignment expression
+  (`#510 <https://github.com/agronholm/typeguard/issues/510>`_; PR by @JohannesK71083)
 
 **4.4.1** (2024-11-03)
 
diff --git a/src/typeguard/_transformer.py b/src/typeguard/_transformer.py
index 999dec0..a9f0aed 100644
--- a/src/typeguard/_transformer.py
+++ b/src/typeguard/_transformer.py
@@ -1138,8 +1138,20 @@ class TypeguardTransformer(NodeTransformer):
                 func_name,
                 [
                     node.value,
-                    Constant(node.target.id),
-                    annotation,
+                    List(
+                        [
+                            List(
+                                [
+                                    Tuple(
+                                        [Constant(node.target.id), annotation],
+                                        ctx=Load(),
+                                    )
+                                ],
+                                ctx=Load(),
+                            )
+                        ],
+                        ctx=Load(),
+                    ),
                     self._memo.get_memo_name(),
                 ],
                 [],


Test patch:
diff --git a/tests/mypy/test_type_annotations.py b/tests/mypy/test_type_annotations.py
index aacee7f..ac97826 100644
--- a/tests/mypy/test_type_annotations.py
+++ b/tests/mypy/test_type_annotations.py
@@ -31,7 +31,7 @@ def get_negative_mypy_output() -> str:
     )
     output = process.stdout.decode()
     assert output
-    return output
+    return re.sub(r"\n(?!\s|negative\.py)", " ", output.replace("\r", ""))
 
 
 def get_expected_errors() -> Dict[int, str]:
diff --git a/tests/test_transformer.py b/tests/test_transformer.py
index 2e18a5a..3fb0462 100644
--- a/tests/test_transformer.py
+++ b/tests/test_transformer.py
@@ -1475,7 +1475,7 @@ check_variable_assignment
                 def foo() -> None:
                     memo = TypeCheckMemo(globals(), locals())
                     x: int
-                    if (x := check_variable_assignment(otherfunc(), 'x', int, \
+                    if (x := check_variable_assignment(otherfunc(), [[('x', int)]], \
 memo)):
                         pass
                 """
@@ -1504,7 +1504,7 @@ check_variable_assignment
                 def foo(x: int) -> None:
                     memo = TypeCheckMemo(globals(), locals())
                     check_argument_types('foo', {'x': (x, int)}, memo)
-                    if (x := check_variable_assignment(otherfunc(), 'x', int, memo)):
+                    if (x := check_variable_assignment(otherfunc(), [[('x', int)]], memo)):
                         pass
                 """
             ).strip()