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(): + ...