From 14d990647c6e86a3094d44f1d50416da5ba9df37 Mon Sep 17 00:00:00 2001
From: John Litborn <11260241+jakkdl@users.noreply.github.com>
Date: Mon, 18 Nov 2024 18:44:34 +0100
Subject: [PATCH] fix crash. Make identifier_to_string less footgunny. (#323)
* fix crash. Make identifier_to_string less footgunny.
* add changelog & bump version. Fix incorrect changelog placement.
---
docs/changelog.rst | 8 ++++++--
flake8_async/__init__.py | 2 +-
flake8_async/visitors/helpers.py | 23 ++++++++++++++---------
flake8_async/visitors/visitor91x.py | 15 +++++++--------
flake8_async/visitors/visitors.py | 7 +++----
tests/autofix_files/async100.py | 5 +++++
tests/eval_files/async100.py | 5 +++++
7 files changed, 41 insertions(+), 24 deletions(-)
diff --git a/docs/changelog.rst b/docs/changelog.rst
index ccfbaf4..dcfb1a1 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -4,20 +4,24 @@ Changelog
`CalVer, YY.month.patch `_
+24.11.2
+=======
+- Fix crash in ``Visitor91x`` on ``async with a().b():``.
+
24.11.1
=======
- :ref:`ASYNC100 ` now ignores :func:`trio.open_nursery` and :func:`anyio.create_task_group`
as cancellation sources, because they are :ref:`schedule points ` but not
:ref:`cancellation points `.
+- :ref:`ASYNC101 ` and :ref:`ASYNC119 ` are now silenced for decorators in :ref:`transform-async-generator-decorators`.
24.10.2
=======
-- :ref:`ASYNC101 ` and :ref:`ASYNC119 ` are now silenced for decorators in :ref:`transform-async-generator-decorators`
- :ref:`ASYNC102 ` now also warns about ``await()`` inside ``__aexit__``.
24.10.1
=======
-- Add :ref:`ASYNC123 ` bad-exception-group-flattening
+- Add :ref:`ASYNC123 ` bad-exception-group-flattening.
24.9.5
======
diff --git a/flake8_async/__init__.py b/flake8_async/__init__.py
index bbe6c35..8ffe4b0 100644
--- a/flake8_async/__init__.py
+++ b/flake8_async/__init__.py
@@ -38,7 +38,7 @@
# CalVer: YY.month.patch, e.g. first release of July 2022 == "22.7.1"
-__version__ = "24.11.1"
+__version__ = "24.11.2"
# taken from https://github.com/Zac-HD/shed
diff --git a/flake8_async/visitors/helpers.py b/flake8_async/visitors/helpers.py
index be39809..642c62b 100644
--- a/flake8_async/visitors/helpers.py
+++ b/flake8_async/visitors/helpers.py
@@ -337,15 +337,20 @@ def build_cst_matcher(attr: str) -> m.BaseExpression:
return m.Attribute(value=build_cst_matcher(body), attr=m.Name(value=tail))
-def identifier_to_string(attr: cst.Name | cst.Attribute) -> str | None:
- if isinstance(attr, cst.Name):
- return attr.value
- if not isinstance(attr.value, (cst.Attribute, cst.Name)):
- return None
- lhs = identifier_to_string(attr.value)
- if lhs is None:
- return None
- return lhs + "." + attr.attr.value
+def identifier_to_string(node: cst.CSTNode) -> str | None:
+ """Convert a simple identifier to a string.
+
+ If the node is composed of anything but cst.Name + cst.Attribute it returns None.
+ """
+ if isinstance(node, cst.Name):
+ return node.value
+ if (
+ isinstance(node, cst.Attribute)
+ and (lhs := identifier_to_string(node.value)) is not None
+ ):
+ return lhs + "." + node.attr.value
+
+ return None
def with_has_call(
diff --git a/flake8_async/visitors/visitor91x.py b/flake8_async/visitors/visitor91x.py
index 2698054..202e0a9 100644
--- a/flake8_async/visitors/visitor91x.py
+++ b/flake8_async/visitors/visitor91x.py
@@ -501,17 +501,16 @@ def _checkpoint_with(self, node: cst.With):
"""
if getattr(node, "asynchronous", None):
for item in node.items:
- if not isinstance(item.item, cst.Call) or not isinstance(
- item.item.func, (cst.Attribute, cst.Name)
+ if not (
+ isinstance(item.item, cst.Call)
+ and identifier_to_string(item.item.func)
+ in (
+ "trio.open_nursery",
+ "anyio.create_task_group",
+ )
):
self.checkpoint()
break
-
- func = identifier_to_string(item.item.func)
- assert func is not None
- if func not in ("trio.open_nursery", "anyio.create_task_group"):
- self.checkpoint()
- break
else:
self.uncheckpointed_statements = set()
diff --git a/flake8_async/visitors/visitors.py b/flake8_async/visitors/visitors.py
index bebd01d..54f14cf 100644
--- a/flake8_async/visitors/visitors.py
+++ b/flake8_async/visitors/visitors.py
@@ -5,8 +5,6 @@
import ast
from typing import TYPE_CHECKING, Any, cast
-import libcst as cst
-
from .flake8asyncvisitor import Flake8AsyncVisitor, Flake8AsyncVisitor_cst
from .helpers import (
disabled_by_default,
@@ -20,6 +18,8 @@
if TYPE_CHECKING:
from collections.abc import Mapping
+ import libcst as cst
+
LIBRARIES = ("trio", "anyio", "asyncio")
@@ -460,8 +460,7 @@ def visit_CompIf(self, node: cst.CSTNode):
def visit_Call(self, node: cst.Call):
if (
- isinstance(node.func, (cst.Name, cst.Attribute))
- and identifier_to_string(node.func) == "asyncio.create_task"
+ identifier_to_string(node.func) == "asyncio.create_task"
and not self.safe_to_create_task
):
self.error(node)
diff --git a/tests/autofix_files/async100.py b/tests/autofix_files/async100.py
index ecd6946..ccaf16b 100644
--- a/tests/autofix_files/async100.py
+++ b/tests/autofix_files/async100.py
@@ -141,3 +141,8 @@ async def nursery_no_cancel_point():
async def dont_crash_on_non_name_or_attr_call():
async with contextlib.asynccontextmanager(agen_fn)():
...
+
+
+async def another_weird_with_call():
+ async with a().b():
+ ...
diff --git a/tests/eval_files/async100.py b/tests/eval_files/async100.py
index 4dc100f..f862eea 100644
--- a/tests/eval_files/async100.py
+++ b/tests/eval_files/async100.py
@@ -141,3 +141,8 @@ async def nursery_no_cancel_point():
async def dont_crash_on_non_name_or_attr_call():
async with contextlib.asynccontextmanager(agen_fn)():
...
+
+
+async def another_weird_with_call():
+ async with a().b():
+ ...