Skip to content
Open
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
8 changes: 7 additions & 1 deletion packages/griffelib/src/griffe/_internal/expressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,13 @@ class Expr:
"""Base class for expressions."""

def __str__(self) -> str:
return "".join(elem if isinstance(elem, str) else elem.name for elem in self.iterate(flat=True)) # ty:ignore[unresolved-attribute]
rendered = "".join(elem if isinstance(elem, str) else elem.name for elem in self.iterate(flat=True)) # ty:ignore[unresolved-attribute]
# A top-level named expression (walrus) is invalid Python without surrounding
# parentheses, e.g. as an attribute value or a parameter default. Nested occurrences
# are parenthesized by their enclosing context, but the top level has none.
if isinstance(self, ExprNamedExpr):
return f"({rendered})"
return rendered
Comment on lines +168 to +173

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm not a big fan of including code for a specific subclass in the main __str__ method. Can't we play with the precedence system? Or can't we at least override __str__ in ExprNamedExpr (calling super() then wrapping in parens)?


def __iter__(self) -> Iterator[str | Expr]:
"""Iterate on the expression syntax and elements."""
Expand Down
13 changes: 13 additions & 0 deletions packages/griffelib/tests/test_expressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ def test_await_expression(source: str, expected: str) -> None:
"f'{x:>{width}}'",
]
_expression_contexts = [
"%s",
"f(%s)",
"f(%s, 1)",
"f(a=%s)",
Expand Down Expand Up @@ -199,6 +200,18 @@ def test_length_one_tuple_as_string() -> None:
assert str(module["x"].value) == "('a',)"


def test_top_level_named_expression_keeps_parentheses() -> None:
"""A top-level named expression (walrus) must keep its surrounding parentheses.

Without them, the rendered string is invalid Python as an attribute value
or a parameter default (e.g. `x = n := 1` is a syntax error).
"""
code = "x = (n := 1)\ndef f(a=(b := compute())): ...\n"
with temporary_visited_module(code) as module:
assert str(module["x"].value) == "(n := 1)"
assert str(module["f"].parameters["a"].default) == "(b := compute())"


@pytest.mark.parametrize(
("annotation", "modernized"),
[
Expand Down
Loading