Skip to content
Merged
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
4 changes: 4 additions & 0 deletions docs/reference/api/expressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@

::: griffe.ExprIfExp

::: griffe.ExprInterpolation

::: griffe.ExprJoinedStr

::: griffe.ExprKeyword
Expand Down Expand Up @@ -88,6 +90,8 @@

::: griffe.ExprSubscript

::: griffe.ExprTemplateStr

::: griffe.ExprTuple

::: griffe.ExprUnaryOp
Expand Down
4 changes: 4 additions & 0 deletions packages/griffelib/src/griffe/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,7 @@
ExprFormatted,
ExprGeneratorExp,
ExprIfExp,
ExprInterpolation,
ExprJoinedStr,
ExprKeyword,
ExprLambda,
Expand All @@ -301,6 +302,7 @@
ExprSetComp,
ExprSlice,
ExprSubscript,
ExprTemplateStr,
ExprTuple,
ExprUnaryOp,
ExprVarKeyword,
Expand Down Expand Up @@ -443,6 +445,7 @@
"ExprFormatted",
"ExprGeneratorExp",
"ExprIfExp",
"ExprInterpolation",
"ExprJoinedStr",
"ExprKeyword",
"ExprLambda",
Expand All @@ -455,6 +458,7 @@
"ExprSetComp",
"ExprSlice",
"ExprSubscript",
"ExprTemplateStr",
"ExprTuple",
"ExprUnaryOp",
"ExprVarKeyword",
Expand Down
47 changes: 47 additions & 0 deletions packages/griffelib/src/griffe/_internal/expressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from __future__ import annotations

import ast
import sys
from dataclasses import dataclass
from dataclasses import fields as getfields
from enum import IntEnum, auto
Expand Down Expand Up @@ -532,6 +533,20 @@ def iterate(self, *, flat: bool = True) -> Iterator[str | Expr]:
yield from _yield(self.orelse, flat=flat, outer_precedence=precedence, is_left=False)


@dataclass(eq=True, slots=True)
class ExprInterpolation(Expr):
"""Template string interpolation like `{name}`."""

value: str | Expr
"""Interpolated value."""

def iterate(self, *, flat: bool = True) -> Iterator[str | Expr]:
yield "{"
# Prevent parentheses from being added, avoiding `{(1 + 1)}`
yield from _yield(self.value, flat=flat, outer_precedence=_OperatorPrecedence.NONE)
yield "}"


@dataclass(eq=True, slots=True)
class ExprJoinedStr(Expr):
"""Joined strings like `f"a {b} c"`."""
Expand Down Expand Up @@ -915,6 +930,19 @@ def canonical_path(self) -> str:
return self.left.canonical_path


@dataclass(eq=True, slots=True)
class ExprTemplateStr(Expr):
"""Template strings like `t"a {name}"`."""

values: Sequence[str | Expr]
"""Joined values."""

def iterate(self, *, flat: bool = True) -> Iterator[str | Expr]:
yield "t'"
yield from _join(self.values, "", flat=flat)
yield "'"
Comment on lines +940 to +943
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.

Are we sure that using hardcoded single (or double) quotes will work in every case? 🤔

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I referred to the implementation of ExprJoinedStr for this. As far as I can tell, this iterate method is only used when round-tripping in tests or dumping expressions by __str__ - it doesn't seem like it's very critical? Please correct me if I misunderstand.

I imagine this round-tripping could break when strings contain certain types of quotes - for example, if input value is t"a'b", converting this to t'a'b' would invalidate syntax. However, if we have the same problem with ExprJoinedStr and ExprTemplateStr, I propose creating a new project issue to improve quote handling.

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.

It's critical: it must yield correct code. This code is then parsed to be highlighted, formatted etc. by Black/Ruff/Pygments/else in downstream tools like mkdocstrings-python.

However, if we have the same problem with ExprJoinedStr and ExprTemplateStr, I propose creating a new project issue to improve quote handling.

You make a good point. Lets handle that in a new issue then 🙂



@dataclass(eq=True, slots=True)
class ExprTuple(Expr):
"""Tuples like `(0, 1, 2)`."""
Expand Down Expand Up @@ -1369,6 +1397,25 @@ def __call__(self, node: Any, parent: Module | Class, **kwargs: Any) -> Expr: ..
ast.YieldFrom: _build_yield_from,
}

if sys.version_info >= (3, 14):
Comment thread
pawamoy marked this conversation as resolved.

def _build_interpolation(node: ast.Interpolation, parent: Module | Class, **kwargs: Any) -> Expr:
return ExprInterpolation(_build(node.value, parent, **kwargs))

def _build_templatestr(
node: ast.TemplateStr,
parent: Module | Class,
**kwargs: Any,
) -> Expr:
return ExprTemplateStr([_build(value, parent, in_joined_str=True, **kwargs) for value in node.values])

_node_map.update(
{
ast.Interpolation: _build_interpolation,
ast.TemplateStr: _build_templatestr,
},
)


def _build(node: ast.AST, parent: Module | Class, /, **kwargs: Any) -> Expr:
return _node_map[type(node)](node, parent, **kwargs)
Expand Down
3 changes: 3 additions & 0 deletions tests/test_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from __future__ import annotations

import logging
import sys
from ast import PyCF_ONLY_AST

import pytest
Expand Down Expand Up @@ -51,6 +52,8 @@
"call(something=something)",
# Strings.
"f'a {round(key, 2)} {z}'",
# YORE: EOL 3.13: Replace line with `"t'a {round(key, 2)} {z}'",`.
*(["t'a {round(key, 2)} {z}'"] if sys.version_info >= (3, 14) else []),
Comment thread
1borgy marked this conversation as resolved.
# Slices.
"o[x]",
"o[x, y]",
Expand Down
Loading