Update string nodes for implicit concatenation (#7927)

## Summary

This PR updates the string nodes (`ExprStringLiteral`,
`ExprBytesLiteral`, and `ExprFString`) to account for implicit string
concatenation.

### Motivation

In Python, implicit string concatenation are joined while parsing
because the interpreter doesn't require the information for each part.
While that's feasible for an interpreter, it falls short for a static
analysis tool where having such information is more useful. Currently,
various parts of the code uses the lexer to get the individual string
parts.

One of the main challenge this solves is that of string formatting.
Currently, the formatter relies on the lexer to get the individual
string parts, and formats them including the comments accordingly. But,
with PEP 701, f-string can also contain comments. Without this change,
it becomes very difficult to add support for f-string formatting.

### Implementation

The initial proposal was made in this discussion:
https://github.com/astral-sh/ruff/discussions/6183#discussioncomment-6591993.
There were various AST designs which were explored for this task which
are available in the linked internal document[^1].

The selected variant was the one where the nodes were kept as it is
except that the `implicit_concatenated` field was removed and instead a
new struct was added to the `Expr*` struct. This would be a private
struct would contain the actual implementation of how the AST is
designed for both single and implicitly concatenated strings.

This implementation is achieved through an enum with two variants:
`Single` and `Concatenated` to avoid allocating a vector even for single
strings. There are various public methods available on the value struct
to query certain information regarding the node.

The nodes are structured in the following way:

```
ExprStringLiteral - "foo" "bar"
|- StringLiteral - "foo"
|- StringLiteral - "bar"

ExprBytesLiteral - b"foo" b"bar"
|- BytesLiteral - b"foo"
|- BytesLiteral - b"bar"

ExprFString - "foo" f"bar {x}"
|- FStringPart::Literal - "foo"
|- FStringPart::FString - f"bar {x}"
  |- StringLiteral - "bar "
  |- FormattedValue - "x"
```

[^1]: Internal document:
https://www.notion.so/astral-sh/Implicit-String-Concatenation-e036345dc48943f89e416c087bf6f6d9?pvs=4

#### Visitor

The way the nodes are structured is that the entire string, including
all the parts that are implicitly concatenation, is a single node
containing individual nodes for the parts. The previous section has a
representation of that tree for all the string nodes. This means that
new visitor methods are added to visit the individual parts of string,
bytes, and f-strings for `Visitor`, `PreorderVisitor`, and
`Transformer`.

## Test Plan

- `cargo insta test --workspace --all-features --unreferenced reject`
- Verify that the ecosystem results are unchanged
This commit is contained in:
Dhruv Manilawala 2023-11-24 17:55:41 -06:00 committed by GitHub
parent 2590aa30ae
commit 017e829115
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
121 changed files with 27666 additions and 25501 deletions

View file

@ -529,6 +529,91 @@ impl<'a> From<&'a ast::ElifElseClause> for ComparableElifElseClause<'a> {
}
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub enum ComparableLiteral<'a> {
None,
Ellipsis,
Bool(&'a bool),
Str(Vec<ComparableStringLiteral<'a>>),
Bytes(Vec<ComparableBytesLiteral<'a>>),
Number(ComparableNumber<'a>),
}
impl<'a> From<ast::LiteralExpressionRef<'a>> for ComparableLiteral<'a> {
fn from(literal: ast::LiteralExpressionRef<'a>) -> Self {
match literal {
ast::LiteralExpressionRef::NoneLiteral(_) => Self::None,
ast::LiteralExpressionRef::EllipsisLiteral(_) => Self::Ellipsis,
ast::LiteralExpressionRef::BooleanLiteral(ast::ExprBooleanLiteral {
value, ..
}) => Self::Bool(value),
ast::LiteralExpressionRef::StringLiteral(ast::ExprStringLiteral { value, .. }) => {
Self::Str(value.parts().map(Into::into).collect())
}
ast::LiteralExpressionRef::BytesLiteral(ast::ExprBytesLiteral { value, .. }) => {
Self::Bytes(value.parts().map(Into::into).collect())
}
ast::LiteralExpressionRef::NumberLiteral(ast::ExprNumberLiteral { value, .. }) => {
Self::Number(value.into())
}
}
}
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct ComparableFString<'a> {
values: Vec<ComparableExpr<'a>>,
}
impl<'a> From<&'a ast::FString> for ComparableFString<'a> {
fn from(fstring: &'a ast::FString) -> Self {
Self {
values: fstring.values.iter().map(Into::into).collect(),
}
}
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub enum ComparableFStringPart<'a> {
Literal(ComparableStringLiteral<'a>),
FString(ComparableFString<'a>),
}
impl<'a> From<&'a ast::FStringPart> for ComparableFStringPart<'a> {
fn from(f_string_part: &'a ast::FStringPart) -> Self {
match f_string_part {
ast::FStringPart::Literal(string_literal) => Self::Literal(string_literal.into()),
ast::FStringPart::FString(f_string) => Self::FString(f_string.into()),
}
}
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct ComparableStringLiteral<'a> {
value: &'a str,
}
impl<'a> From<&'a ast::StringLiteral> for ComparableStringLiteral<'a> {
fn from(string_literal: &'a ast::StringLiteral) -> Self {
Self {
value: string_literal.value.as_str(),
}
}
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct ComparableBytesLiteral<'a> {
value: &'a [u8],
}
impl<'a> From<&'a ast::BytesLiteral> for ComparableBytesLiteral<'a> {
fn from(bytes_literal: &'a ast::BytesLiteral) -> Self {
Self {
value: bytes_literal.value.as_slice(),
}
}
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct ExprBoolOp<'a> {
op: ComparableBoolOp,
@ -641,48 +726,17 @@ pub struct ExprFormattedValue<'a> {
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct ExprFString<'a> {
values: Vec<ComparableExpr<'a>>,
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub enum ComparableLiteral<'a> {
None,
Ellipsis,
Bool(&'a bool),
Str(&'a str),
Bytes(&'a [u8]),
Number(ComparableNumber<'a>),
}
impl<'a> From<ast::LiteralExpressionRef<'a>> for ComparableLiteral<'a> {
fn from(literal: ast::LiteralExpressionRef<'a>) -> Self {
match literal {
ast::LiteralExpressionRef::NoneLiteral(_) => Self::None,
ast::LiteralExpressionRef::EllipsisLiteral(_) => Self::Ellipsis,
ast::LiteralExpressionRef::BooleanLiteral(ast::ExprBooleanLiteral {
value, ..
}) => Self::Bool(value),
ast::LiteralExpressionRef::StringLiteral(ast::ExprStringLiteral { value, .. }) => {
Self::Str(value)
}
ast::LiteralExpressionRef::BytesLiteral(ast::ExprBytesLiteral { value, .. }) => {
Self::Bytes(value)
}
ast::LiteralExpressionRef::NumberLiteral(ast::ExprNumberLiteral { value, .. }) => {
Self::Number(value.into())
}
}
}
parts: Vec<ComparableFStringPart<'a>>,
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct ExprStringLiteral<'a> {
value: &'a str,
parts: Vec<ComparableStringLiteral<'a>>,
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct ExprBytesLiteral<'a> {
value: &'a [u8],
parts: Vec<ComparableBytesLiteral<'a>>,
}
#[derive(Debug, PartialEq, Eq, Hash)]
@ -933,28 +987,21 @@ impl<'a> From<&'a ast::Expr> for ComparableExpr<'a> {
debug_text: debug_text.as_ref(),
format_spec: format_spec.as_ref().map(Into::into),
}),
ast::Expr::FString(ast::ExprFString {
values,
implicit_concatenated: _,
range: _,
}) => Self::FString(ExprFString {
values: values.iter().map(Into::into).collect(),
}),
ast::Expr::StringLiteral(ast::ExprStringLiteral {
value,
// Compare strings based on resolved value, not representation (i.e., ignore whether
// the string was implicitly concatenated).
implicit_concatenated: _,
unicode: _,
range: _,
}) => Self::StringLiteral(ExprStringLiteral { value }),
ast::Expr::BytesLiteral(ast::ExprBytesLiteral {
value,
// Compare bytes based on resolved value, not representation (i.e., ignore whether
// the bytes was implicitly concatenated).
implicit_concatenated: _,
range: _,
}) => Self::BytesLiteral(ExprBytesLiteral { value }),
ast::Expr::FString(ast::ExprFString { value, range: _ }) => {
Self::FString(ExprFString {
parts: value.parts().map(Into::into).collect(),
})
}
ast::Expr::StringLiteral(ast::ExprStringLiteral { value, range: _ }) => {
Self::StringLiteral(ExprStringLiteral {
parts: value.parts().map(Into::into).collect(),
})
}
ast::Expr::BytesLiteral(ast::ExprBytesLiteral { value, range: _ }) => {
Self::BytesLiteral(ExprBytesLiteral {
parts: value.parts().map(Into::into).collect(),
})
}
ast::Expr::NumberLiteral(ast::ExprNumberLiteral { value, range: _ }) => {
Self::NumberLiteral(ExprNumberLiteral {
value: value.into(),

View file

@ -361,3 +361,44 @@ impl Ranged for LiteralExpressionRef<'_> {
}
}
}
impl<'a> From<LiteralExpressionRef<'a>> for AnyNodeRef<'a> {
fn from(value: LiteralExpressionRef<'a>) -> Self {
match value {
LiteralExpressionRef::StringLiteral(expression) => {
AnyNodeRef::ExprStringLiteral(expression)
}
LiteralExpressionRef::BytesLiteral(expression) => {
AnyNodeRef::ExprBytesLiteral(expression)
}
LiteralExpressionRef::NumberLiteral(expression) => {
AnyNodeRef::ExprNumberLiteral(expression)
}
LiteralExpressionRef::BooleanLiteral(expression) => {
AnyNodeRef::ExprBooleanLiteral(expression)
}
LiteralExpressionRef::NoneLiteral(expression) => {
AnyNodeRef::ExprNoneLiteral(expression)
}
LiteralExpressionRef::EllipsisLiteral(expression) => {
AnyNodeRef::ExprEllipsisLiteral(expression)
}
}
}
}
impl LiteralExpressionRef<'_> {
/// Returns `true` if the literal is either a string or bytes literal that
/// is implicitly concatenated.
pub fn is_implicit_concatenated(&self) -> bool {
match self {
LiteralExpressionRef::StringLiteral(expression) => {
expression.value.is_implicit_concatenated()
}
LiteralExpressionRef::BytesLiteral(expression) => {
expression.value.is_implicit_concatenated()
}
_ => false,
}
}
}

View file

@ -133,10 +133,12 @@ pub fn any_over_expr(expr: &Expr, func: &dyn Fn(&Expr) -> bool) -> bool {
return true;
}
match expr {
Expr::BoolOp(ast::ExprBoolOp { values, .. })
| Expr::FString(ast::ExprFString { values, .. }) => {
Expr::BoolOp(ast::ExprBoolOp { values, .. }) => {
values.iter().any(|expr| any_over_expr(expr, func))
}
Expr::FString(ast::ExprFString { value, .. }) => {
value.elements().any(|expr| any_over_expr(expr, func))
}
Expr::NamedExpr(ast::ExprNamedExpr {
target,
value,
@ -1139,11 +1141,14 @@ impl Truthiness {
}
Expr::NoneLiteral(_) => Self::Falsey,
Expr::EllipsisLiteral(_) => Self::Truthy,
Expr::FString(ast::ExprFString { values, .. }) => {
if values.is_empty() {
Expr::FString(ast::ExprFString { value, .. }) => {
if value.parts().all(|part| match part {
ast::FStringPart::Literal(string_literal) => string_literal.is_empty(),
ast::FStringPart::FString(f_string) => f_string.values.is_empty(),
}) {
Self::Falsey
} else if values.iter().any(|value| {
if let Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) = &value {
} else if value.elements().any(|expr| {
if let Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) = &expr {
!value.is_empty()
} else {
false

View file

@ -113,6 +113,9 @@ pub enum AnyNode {
TypeParamTypeVar(TypeParamTypeVar),
TypeParamTypeVarTuple(TypeParamTypeVarTuple),
TypeParamParamSpec(TypeParamParamSpec),
FString(ast::FString),
StringLiteral(ast::StringLiteral),
BytesLiteral(ast::BytesLiteral),
}
impl AnyNode {
@ -204,6 +207,9 @@ impl AnyNode {
| AnyNode::TypeParamTypeVar(_)
| AnyNode::TypeParamTypeVarTuple(_)
| AnyNode::TypeParamParamSpec(_)
| AnyNode::FString(_)
| AnyNode::StringLiteral(_)
| AnyNode::BytesLiteral(_)
| AnyNode::ElifElseClause(_) => None,
}
}
@ -296,6 +302,9 @@ impl AnyNode {
| AnyNode::TypeParamTypeVar(_)
| AnyNode::TypeParamTypeVarTuple(_)
| AnyNode::TypeParamParamSpec(_)
| AnyNode::FString(_)
| AnyNode::StringLiteral(_)
| AnyNode::BytesLiteral(_)
| AnyNode::ElifElseClause(_) => None,
}
}
@ -388,6 +397,9 @@ impl AnyNode {
| AnyNode::TypeParamTypeVar(_)
| AnyNode::TypeParamTypeVarTuple(_)
| AnyNode::TypeParamParamSpec(_)
| AnyNode::FString(_)
| AnyNode::StringLiteral(_)
| AnyNode::BytesLiteral(_)
| AnyNode::ElifElseClause(_) => None,
}
}
@ -480,6 +492,9 @@ impl AnyNode {
| AnyNode::TypeParamTypeVar(_)
| AnyNode::TypeParamTypeVarTuple(_)
| AnyNode::TypeParamParamSpec(_)
| AnyNode::FString(_)
| AnyNode::StringLiteral(_)
| AnyNode::BytesLiteral(_)
| AnyNode::ElifElseClause(_) => None,
}
}
@ -572,6 +587,9 @@ impl AnyNode {
| AnyNode::TypeParamTypeVar(_)
| AnyNode::TypeParamTypeVarTuple(_)
| AnyNode::TypeParamParamSpec(_)
| AnyNode::FString(_)
| AnyNode::StringLiteral(_)
| AnyNode::BytesLiteral(_)
| AnyNode::ElifElseClause(_) => None,
}
}
@ -683,6 +701,9 @@ impl AnyNode {
Self::TypeParamTypeVar(node) => AnyNodeRef::TypeParamTypeVar(node),
Self::TypeParamTypeVarTuple(node) => AnyNodeRef::TypeParamTypeVarTuple(node),
Self::TypeParamParamSpec(node) => AnyNodeRef::TypeParamParamSpec(node),
Self::FString(node) => AnyNodeRef::FString(node),
Self::StringLiteral(node) => AnyNodeRef::StringLiteral(node),
Self::BytesLiteral(node) => AnyNodeRef::BytesLiteral(node),
Self::ElifElseClause(node) => AnyNodeRef::ElifElseClause(node),
}
}
@ -2674,14 +2695,17 @@ impl AstNode for ast::ExprFString {
where
V: PreorderVisitor<'a> + ?Sized,
{
let ast::ExprFString {
values,
implicit_concatenated: _,
range: _,
} = self;
let ast::ExprFString { value, range: _ } = self;
for expr in values {
visitor.visit_expr(expr);
for f_string_part in value.parts() {
match f_string_part {
ast::FStringPart::Literal(string_literal) => {
visitor.visit_string_literal(string_literal);
}
ast::FStringPart::FString(f_string) => {
visitor.visit_f_string(f_string);
}
}
}
}
}
@ -2713,10 +2737,15 @@ impl AstNode for ast::ExprStringLiteral {
AnyNode::from(self)
}
fn visit_preorder<'a, V>(&'a self, _visitor: &mut V)
fn visit_preorder<'a, V>(&'a self, visitor: &mut V)
where
V: PreorderVisitor<'a> + ?Sized,
{
let ast::ExprStringLiteral { value, range: _ } = self;
for string_literal in value.parts() {
visitor.visit_string_literal(string_literal);
}
}
}
impl AstNode for ast::ExprBytesLiteral {
@ -2747,10 +2776,15 @@ impl AstNode for ast::ExprBytesLiteral {
AnyNode::from(self)
}
fn visit_preorder<'a, V>(&'a self, _visitor: &mut V)
fn visit_preorder<'a, V>(&'a self, visitor: &mut V)
where
V: PreorderVisitor<'a> + ?Sized,
{
let ast::ExprBytesLiteral { value, range: _ } = self;
for bytes_literal in value.parts() {
visitor.visit_bytes_literal(bytes_literal);
}
}
}
impl AstNode for ast::ExprNumberLiteral {
@ -4273,6 +4307,114 @@ impl AstNode for ast::TypeParamParamSpec {
let ast::TypeParamParamSpec { range: _, name: _ } = self;
}
}
impl AstNode for ast::FString {
fn cast(kind: AnyNode) -> Option<Self>
where
Self: Sized,
{
if let AnyNode::FString(node) = kind {
Some(node)
} else {
None
}
}
fn cast_ref(kind: AnyNodeRef) -> Option<&Self> {
if let AnyNodeRef::FString(node) = kind {
Some(node)
} else {
None
}
}
fn as_any_node_ref(&self) -> AnyNodeRef {
AnyNodeRef::from(self)
}
fn into_any_node(self) -> AnyNode {
AnyNode::from(self)
}
fn visit_preorder<'a, V>(&'a self, visitor: &mut V)
where
V: PreorderVisitor<'a> + ?Sized,
{
let ast::FString { values, range: _ } = self;
for expr in values {
visitor.visit_expr(expr);
}
}
}
impl AstNode for ast::StringLiteral {
fn cast(kind: AnyNode) -> Option<Self>
where
Self: Sized,
{
if let AnyNode::StringLiteral(node) = kind {
Some(node)
} else {
None
}
}
fn cast_ref(kind: AnyNodeRef) -> Option<&Self> {
if let AnyNodeRef::StringLiteral(node) = kind {
Some(node)
} else {
None
}
}
fn as_any_node_ref(&self) -> AnyNodeRef {
AnyNodeRef::from(self)
}
fn into_any_node(self) -> AnyNode {
AnyNode::from(self)
}
fn visit_preorder<'a, V>(&'a self, _visitor: &mut V)
where
V: PreorderVisitor<'a> + ?Sized,
{
}
}
impl AstNode for ast::BytesLiteral {
fn cast(kind: AnyNode) -> Option<Self>
where
Self: Sized,
{
if let AnyNode::BytesLiteral(node) = kind {
Some(node)
} else {
None
}
}
fn cast_ref(kind: AnyNodeRef) -> Option<&Self> {
if let AnyNodeRef::BytesLiteral(node) = kind {
Some(node)
} else {
None
}
}
fn as_any_node_ref(&self) -> AnyNodeRef {
AnyNodeRef::from(self)
}
fn into_any_node(self) -> AnyNode {
AnyNode::from(self)
}
fn visit_preorder<'a, V>(&'a self, _visitor: &mut V)
where
V: PreorderVisitor<'a> + ?Sized,
{
}
}
impl From<Stmt> for AnyNode {
fn from(stmt: Stmt) -> Self {
match stmt {
@ -4882,6 +5024,24 @@ impl From<TypeParamParamSpec> for AnyNode {
}
}
impl From<ast::FString> for AnyNode {
fn from(node: ast::FString) -> Self {
AnyNode::FString(node)
}
}
impl From<ast::StringLiteral> for AnyNode {
fn from(node: ast::StringLiteral) -> Self {
AnyNode::StringLiteral(node)
}
}
impl From<ast::BytesLiteral> for AnyNode {
fn from(node: ast::BytesLiteral) -> Self {
AnyNode::BytesLiteral(node)
}
}
impl Ranged for AnyNode {
fn range(&self) -> TextRange {
match self {
@ -4970,6 +5130,9 @@ impl Ranged for AnyNode {
AnyNode::TypeParamTypeVar(node) => node.range(),
AnyNode::TypeParamTypeVarTuple(node) => node.range(),
AnyNode::TypeParamParamSpec(node) => node.range(),
AnyNode::FString(node) => node.range(),
AnyNode::StringLiteral(node) => node.range(),
AnyNode::BytesLiteral(node) => node.range(),
AnyNode::ElifElseClause(node) => node.range(),
}
}
@ -5062,6 +5225,9 @@ pub enum AnyNodeRef<'a> {
TypeParamTypeVar(&'a TypeParamTypeVar),
TypeParamTypeVarTuple(&'a TypeParamTypeVarTuple),
TypeParamParamSpec(&'a TypeParamParamSpec),
FString(&'a ast::FString),
StringLiteral(&'a ast::StringLiteral),
BytesLiteral(&'a ast::BytesLiteral),
ElifElseClause(&'a ast::ElifElseClause),
}
@ -5153,6 +5319,9 @@ impl<'a> AnyNodeRef<'a> {
AnyNodeRef::TypeParamTypeVar(node) => NonNull::from(*node).cast(),
AnyNodeRef::TypeParamTypeVarTuple(node) => NonNull::from(*node).cast(),
AnyNodeRef::TypeParamParamSpec(node) => NonNull::from(*node).cast(),
AnyNodeRef::FString(node) => NonNull::from(*node).cast(),
AnyNodeRef::StringLiteral(node) => NonNull::from(*node).cast(),
AnyNodeRef::BytesLiteral(node) => NonNull::from(*node).cast(),
AnyNodeRef::ElifElseClause(node) => NonNull::from(*node).cast(),
}
}
@ -5250,6 +5419,9 @@ impl<'a> AnyNodeRef<'a> {
AnyNodeRef::TypeParamTypeVar(_) => NodeKind::TypeParamTypeVar,
AnyNodeRef::TypeParamTypeVarTuple(_) => NodeKind::TypeParamTypeVarTuple,
AnyNodeRef::TypeParamParamSpec(_) => NodeKind::TypeParamParamSpec,
AnyNodeRef::FString(_) => NodeKind::FString,
AnyNodeRef::StringLiteral(_) => NodeKind::StringLiteral,
AnyNodeRef::BytesLiteral(_) => NodeKind::BytesLiteral,
AnyNodeRef::ElifElseClause(_) => NodeKind::ElifElseClause,
}
}
@ -5342,6 +5514,9 @@ impl<'a> AnyNodeRef<'a> {
| AnyNodeRef::TypeParamTypeVar(_)
| AnyNodeRef::TypeParamTypeVarTuple(_)
| AnyNodeRef::TypeParamParamSpec(_)
| AnyNodeRef::FString(_)
| AnyNodeRef::StringLiteral(_)
| AnyNodeRef::BytesLiteral(_)
| AnyNodeRef::ElifElseClause(_) => false,
}
}
@ -5434,6 +5609,9 @@ impl<'a> AnyNodeRef<'a> {
| AnyNodeRef::TypeParamTypeVar(_)
| AnyNodeRef::TypeParamTypeVarTuple(_)
| AnyNodeRef::TypeParamParamSpec(_)
| AnyNodeRef::FString(_)
| AnyNodeRef::StringLiteral(_)
| AnyNodeRef::BytesLiteral(_)
| AnyNodeRef::ElifElseClause(_) => false,
}
}
@ -5525,6 +5703,9 @@ impl<'a> AnyNodeRef<'a> {
| AnyNodeRef::TypeParamTypeVar(_)
| AnyNodeRef::TypeParamTypeVarTuple(_)
| AnyNodeRef::TypeParamParamSpec(_)
| AnyNodeRef::FString(_)
| AnyNodeRef::StringLiteral(_)
| AnyNodeRef::BytesLiteral(_)
| AnyNodeRef::ElifElseClause(_) => false,
}
}
@ -5617,6 +5798,9 @@ impl<'a> AnyNodeRef<'a> {
| AnyNodeRef::TypeParamTypeVar(_)
| AnyNodeRef::TypeParamTypeVarTuple(_)
| AnyNodeRef::TypeParamParamSpec(_)
| AnyNodeRef::FString(_)
| AnyNodeRef::StringLiteral(_)
| AnyNodeRef::BytesLiteral(_)
| AnyNodeRef::ElifElseClause(_) => false,
}
}
@ -5709,6 +5893,9 @@ impl<'a> AnyNodeRef<'a> {
| AnyNodeRef::TypeParamTypeVar(_)
| AnyNodeRef::TypeParamTypeVarTuple(_)
| AnyNodeRef::TypeParamParamSpec(_)
| AnyNodeRef::FString(_)
| AnyNodeRef::StringLiteral(_)
| AnyNodeRef::BytesLiteral(_)
| AnyNodeRef::ElifElseClause(_) => false,
}
}
@ -5829,6 +6016,9 @@ impl<'a> AnyNodeRef<'a> {
AnyNodeRef::TypeParamTypeVar(node) => node.visit_preorder(visitor),
AnyNodeRef::TypeParamTypeVarTuple(node) => node.visit_preorder(visitor),
AnyNodeRef::TypeParamParamSpec(node) => node.visit_preorder(visitor),
AnyNodeRef::FString(node) => node.visit_preorder(visitor),
AnyNodeRef::StringLiteral(node) => node.visit_preorder(visitor),
AnyNodeRef::BytesLiteral(node) => node.visit_preorder(visitor),
AnyNodeRef::ElifElseClause(node) => node.visit_preorder(visitor),
}
}
@ -6355,6 +6545,24 @@ impl<'a> From<&'a TypeParamParamSpec> for AnyNodeRef<'a> {
}
}
impl<'a> From<&'a ast::FString> for AnyNodeRef<'a> {
fn from(node: &'a ast::FString) -> Self {
AnyNodeRef::FString(node)
}
}
impl<'a> From<&'a ast::StringLiteral> for AnyNodeRef<'a> {
fn from(node: &'a ast::StringLiteral) -> Self {
AnyNodeRef::StringLiteral(node)
}
}
impl<'a> From<&'a ast::BytesLiteral> for AnyNodeRef<'a> {
fn from(node: &'a ast::BytesLiteral) -> Self {
AnyNodeRef::BytesLiteral(node)
}
}
impl<'a> From<&'a Stmt> for AnyNodeRef<'a> {
fn from(stmt: &'a Stmt) -> Self {
match stmt {
@ -6606,6 +6814,9 @@ impl Ranged for AnyNodeRef<'_> {
AnyNodeRef::TypeParamTypeVar(node) => node.range(),
AnyNodeRef::TypeParamTypeVarTuple(node) => node.range(),
AnyNodeRef::TypeParamParamSpec(node) => node.range(),
AnyNodeRef::FString(node) => node.range(),
AnyNodeRef::StringLiteral(node) => node.range(),
AnyNodeRef::BytesLiteral(node) => node.range(),
}
}
}
@ -6701,4 +6912,7 @@ pub enum NodeKind {
TypeParamTypeVar,
TypeParamTypeVarTuple,
TypeParamParamSpec,
FString,
StringLiteral,
BytesLiteral,
}

View file

@ -1,14 +1,17 @@
#![allow(clippy::derive_partial_eq_without_eq)]
use itertools::Itertools;
use std::cell::OnceCell;
use std::fmt;
use std::fmt::Debug;
use std::ops::Deref;
use crate::{int, LiteralExpressionRef};
use itertools::Either::{Left, Right};
use itertools::Itertools;
use ruff_text_size::{Ranged, TextRange, TextSize};
use crate::{int, LiteralExpressionRef};
/// See also [mod](https://docs.python.org/3/library/ast.html#ast.mod)
#[derive(Clone, Debug, PartialEq, is_macro::Is)]
pub enum Mod {
@ -640,6 +643,7 @@ impl Expr {
)
}
/// Returns [`LiteralExpressionRef`] if the expression is a literal expression.
pub fn as_literal_expr(&self) -> Option<LiteralExpressionRef<'_>> {
match self {
Expr::StringLiteral(expr) => Some(LiteralExpressionRef::StringLiteral(expr)),
@ -651,24 +655,6 @@ impl Expr {
_ => None,
}
}
pub fn is_implicit_concatenated_string(&self) -> bool {
match self {
Expr::StringLiteral(ExprStringLiteral {
implicit_concatenated,
..
})
| Expr::BytesLiteral(ExprBytesLiteral {
implicit_concatenated,
..
})
| Expr::FString(ExprFString {
implicit_concatenated,
..
}) => *implicit_concatenated,
_ => false,
}
}
}
/// An AST node used to represent a IPython escape command at the expression level.
@ -984,13 +970,17 @@ pub struct DebugText {
pub trailing: String,
}
/// See also [JoinedStr](https://docs.python.org/3/library/ast.html#ast.JoinedStr)
/// An AST node used to represent an f-string.
///
/// This type differs from the original Python AST ([JoinedStr]) in that it
/// doesn't join the implicitly concatenated parts into a single string. Instead,
/// it keeps them separate and provide various methods to access the parts.
///
/// [JoinedStr]: https://docs.python.org/3/library/ast.html#ast.JoinedStr
#[derive(Clone, Debug, PartialEq)]
pub struct ExprFString {
pub range: TextRange,
pub values: Vec<Expr>,
/// Whether the f-string contains multiple string tokens that were implicitly concatenated.
pub implicit_concatenated: bool,
pub value: FStringValue,
}
impl From<ExprFString> for Expr {
@ -999,12 +989,155 @@ impl From<ExprFString> for Expr {
}
}
/// The value representing an [`ExprFString`].
#[derive(Clone, Debug, PartialEq)]
pub struct FStringValue {
inner: FStringValueInner,
}
impl FStringValue {
/// Creates a new f-string with the given value.
pub fn single(value: FString) -> Self {
Self {
inner: FStringValueInner::Single(FStringPart::FString(value)),
}
}
/// Creates a new f-string with the given values that represents an implicitly
/// concatenated f-string.
///
/// # Panics
///
/// Panics if `values` is less than 2. Use [`FStringValue::single`] instead.
pub fn concatenated(values: Vec<FStringPart>) -> Self {
assert!(values.len() > 1);
Self {
inner: FStringValueInner::Concatenated(values),
}
}
/// Returns `true` if the f-string is implicitly concatenated, `false` otherwise.
pub fn is_implicit_concatenated(&self) -> bool {
matches!(self.inner, FStringValueInner::Concatenated(_))
}
/// Returns an iterator over all the [`FStringPart`]s contained in this value.
pub fn parts(&self) -> impl Iterator<Item = &FStringPart> {
match &self.inner {
FStringValueInner::Single(part) => Left(std::iter::once(part)),
FStringValueInner::Concatenated(parts) => Right(parts.iter()),
}
}
/// Returns an iterator over all the [`FStringPart`]s contained in this value
/// that allows modification.
pub(crate) fn parts_mut(&mut self) -> impl Iterator<Item = &mut FStringPart> {
match &mut self.inner {
FStringValueInner::Single(part) => Left(std::iter::once(part)),
FStringValueInner::Concatenated(parts) => Right(parts.iter_mut()),
}
}
/// Returns an iterator over the [`StringLiteral`] parts contained in this value.
///
/// Note that this doesn't nest into the f-string parts. For example,
///
/// ```python
/// "foo" f"bar {x}" "baz" f"qux"
/// ```
///
/// Here, the string literal parts returned would be `"foo"` and `"baz"`.
pub fn literals(&self) -> impl Iterator<Item = &StringLiteral> {
self.parts().filter_map(|part| part.as_literal())
}
/// Returns an iterator over the [`FString`] parts contained in this value.
///
/// Note that this doesn't nest into the f-string parts. For example,
///
/// ```python
/// "foo" f"bar {x}" "baz" f"qux"
/// ```
///
/// Here, the f-string parts returned would be `f"bar {x}"` and `f"qux"`.
pub fn f_strings(&self) -> impl Iterator<Item = &FString> {
self.parts().filter_map(|part| part.as_f_string())
}
/// Returns an iterator over all the f-string elements contained in this value.
///
/// An f-string element is what makes up an [`FString`] i.e., it is either a
/// string literal or an expression. In the following example,
///
/// ```python
/// "foo" f"bar {x}" "baz" f"qux"
/// ```
///
/// The f-string elements returned would be string literal (`"bar "`),
/// expression (`x`) and string literal (`"qux"`).
pub fn elements(&self) -> impl Iterator<Item = &Expr> {
self.f_strings().flat_map(|fstring| fstring.values.iter())
}
}
/// An internal representation of [`FStringValue`].
#[derive(Clone, Debug, PartialEq)]
enum FStringValueInner {
/// A single f-string i.e., `f"foo"`.
///
/// This is always going to be `FStringPart::FString` variant which is
/// maintained by the `FStringValue::single` constructor.
Single(FStringPart),
/// An implicitly concatenated f-string i.e., `"foo" f"bar {x}"`.
Concatenated(Vec<FStringPart>),
}
/// An f-string part which is either a string literal or an f-string.
#[derive(Clone, Debug, PartialEq, is_macro::Is)]
pub enum FStringPart {
Literal(StringLiteral),
FString(FString),
}
impl Ranged for FStringPart {
fn range(&self) -> TextRange {
match self {
FStringPart::Literal(string_literal) => string_literal.range(),
FStringPart::FString(f_string) => f_string.range(),
}
}
}
/// An AST node that represents a single f-string which is part of an [`ExprFString`].
#[derive(Clone, Debug, PartialEq)]
pub struct FString {
pub range: TextRange,
pub values: Vec<Expr>,
}
impl Ranged for FString {
fn range(&self) -> TextRange {
self.range
}
}
impl From<FString> for Expr {
fn from(payload: FString) -> Self {
ExprFString {
range: payload.range,
value: FStringValue::single(payload),
}
.into()
}
}
/// An AST node that represents either a single string literal or an implicitly
/// concatenated string literals.
#[derive(Clone, Debug, Default, PartialEq)]
pub struct ExprStringLiteral {
pub range: TextRange,
pub value: String,
pub unicode: bool,
pub implicit_concatenated: bool,
pub value: StringLiteralValue,
}
impl From<ExprStringLiteral> for Expr {
@ -1019,7 +1152,134 @@ impl Ranged for ExprStringLiteral {
}
}
impl Deref for ExprStringLiteral {
/// The value representing a [`ExprStringLiteral`].
#[derive(Clone, Debug, Default, PartialEq)]
pub struct StringLiteralValue {
inner: StringLiteralValueInner,
}
impl StringLiteralValue {
/// Creates a new single string literal with the given value.
pub fn single(string: StringLiteral) -> Self {
Self {
inner: StringLiteralValueInner::Single(string),
}
}
/// Creates a new string literal with the given values that represents an
/// implicitly concatenated strings.
///
/// # Panics
///
/// Panics if `strings` is less than 2. Use [`StringLiteralValue::single`]
/// instead.
pub fn concatenated(strings: Vec<StringLiteral>) -> Self {
assert!(strings.len() > 1);
Self {
inner: StringLiteralValueInner::Concatenated(ConcatenatedStringLiteral {
strings,
value: OnceCell::new(),
}),
}
}
/// Returns `true` if the string literal is implicitly concatenated.
pub const fn is_implicit_concatenated(&self) -> bool {
matches!(self.inner, StringLiteralValueInner::Concatenated(_))
}
/// Returns `true` if the string literal is a unicode string.
///
/// For an implicitly concatenated string, it returns `true` only if the first
/// string literal is a unicode string.
pub fn is_unicode(&self) -> bool {
self.parts().next().map_or(false, |part| part.unicode)
}
/// Returns an iterator over all the [`StringLiteral`] parts contained in this value.
pub fn parts(&self) -> impl Iterator<Item = &StringLiteral> {
match &self.inner {
StringLiteralValueInner::Single(value) => Left(std::iter::once(value)),
StringLiteralValueInner::Concatenated(value) => Right(value.strings.iter()),
}
}
/// Returns an iterator over all the [`StringLiteral`] parts contained in this value
/// that allows modification.
pub(crate) fn parts_mut(&mut self) -> impl Iterator<Item = &mut StringLiteral> {
match &mut self.inner {
StringLiteralValueInner::Single(value) => Left(std::iter::once(value)),
StringLiteralValueInner::Concatenated(value) => Right(value.strings.iter_mut()),
}
}
/// Returns the concatenated string value as a [`str`].
pub fn as_str(&self) -> &str {
match &self.inner {
StringLiteralValueInner::Single(value) => value.as_str(),
StringLiteralValueInner::Concatenated(value) => value.as_str(),
}
}
}
impl PartialEq<str> for StringLiteralValue {
fn eq(&self, other: &str) -> bool {
self.as_str() == other
}
}
impl PartialEq<String> for StringLiteralValue {
fn eq(&self, other: &String) -> bool {
self.as_str() == other
}
}
impl Deref for StringLiteralValue {
type Target = str;
fn deref(&self) -> &Self::Target {
self.as_str()
}
}
impl fmt::Display for StringLiteralValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
/// An internal representation of [`StringLiteralValue`].
#[derive(Clone, Debug, PartialEq)]
enum StringLiteralValueInner {
/// A single string literal i.e., `"foo"`.
Single(StringLiteral),
/// An implicitly concatenated string literals i.e., `"foo" "bar"`.
Concatenated(ConcatenatedStringLiteral),
}
impl Default for StringLiteralValueInner {
fn default() -> Self {
Self::Single(StringLiteral::default())
}
}
/// An AST node that represents a single string literal which is part of an
/// [`ExprStringLiteral`].
#[derive(Clone, Debug, Default, PartialEq)]
pub struct StringLiteral {
pub range: TextRange,
pub value: String,
pub unicode: bool,
}
impl Ranged for StringLiteral {
fn range(&self) -> TextRange {
self.range
}
}
impl Deref for StringLiteral {
type Target = str;
fn deref(&self) -> &Self::Target {
@ -1027,11 +1287,69 @@ impl Deref for ExprStringLiteral {
}
}
impl StringLiteral {
/// Extracts a string slice containing the entire `String`.
pub fn as_str(&self) -> &str {
self
}
}
impl From<StringLiteral> for Expr {
fn from(payload: StringLiteral) -> Self {
ExprStringLiteral {
range: payload.range,
value: StringLiteralValue::single(payload),
}
.into()
}
}
/// An internal representation of [`StringLiteral`] that represents an
/// implicitly concatenated string.
#[derive(Clone)]
struct ConcatenatedStringLiteral {
/// Each string literal that makes up the concatenated string.
strings: Vec<StringLiteral>,
/// The concatenated string value.
value: OnceCell<String>,
}
impl ConcatenatedStringLiteral {
/// Extracts a string slice containing the entire concatenated string.
fn as_str(&self) -> &str {
self.value
.get_or_init(|| self.strings.iter().map(StringLiteral::as_str).collect())
}
}
impl PartialEq for ConcatenatedStringLiteral {
fn eq(&self, other: &Self) -> bool {
if self.strings.len() != other.strings.len() {
return false;
}
// The `zip` here is safe because we have checked the length of both parts.
self.strings
.iter()
.zip(other.strings.iter())
.all(|(s1, s2)| s1 == s2)
}
}
impl Debug for ConcatenatedStringLiteral {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ConcatenatedStringLiteral")
.field("strings", &self.strings)
.field("value", &self.as_str())
.finish()
}
}
/// An AST node that represents either a single bytes literal or an implicitly
/// concatenated bytes literals.
#[derive(Clone, Debug, Default, PartialEq)]
pub struct ExprBytesLiteral {
pub range: TextRange,
pub value: Vec<u8>,
pub implicit_concatenated: bool,
pub value: BytesLiteralValue,
}
impl From<ExprBytesLiteral> for Expr {
@ -1046,6 +1364,140 @@ impl Ranged for ExprBytesLiteral {
}
}
/// The value representing a [`ExprBytesLiteral`].
#[derive(Clone, Debug, Default, PartialEq)]
pub struct BytesLiteralValue {
inner: BytesLiteralValueInner,
}
impl BytesLiteralValue {
/// Creates a new single bytes literal with the given value.
pub fn single(value: BytesLiteral) -> Self {
Self {
inner: BytesLiteralValueInner::Single(value),
}
}
/// Creates a new bytes literal with the given values that represents an
/// implicitly concatenated bytes.
///
/// # Panics
///
/// Panics if `values` is less than 2. Use [`BytesLiteralValue::single`]
/// instead.
pub fn concatenated(values: Vec<BytesLiteral>) -> Self {
assert!(values.len() > 1);
Self {
inner: BytesLiteralValueInner::Concatenated(values),
}
}
/// Returns `true` if the bytes literal is implicitly concatenated.
pub const fn is_implicit_concatenated(&self) -> bool {
matches!(self.inner, BytesLiteralValueInner::Concatenated(_))
}
/// Returns an iterator over all the [`BytesLiteral`] parts contained in this value.
pub fn parts(&self) -> impl Iterator<Item = &BytesLiteral> {
match &self.inner {
BytesLiteralValueInner::Single(value) => Left(std::iter::once(value)),
BytesLiteralValueInner::Concatenated(values) => Right(values.iter()),
}
}
/// Returns an iterator over all the [`BytesLiteral`] parts contained in this value
/// that allows modification.
pub(crate) fn parts_mut(&mut self) -> impl Iterator<Item = &mut BytesLiteral> {
match &mut self.inner {
BytesLiteralValueInner::Single(value) => Left(std::iter::once(value)),
BytesLiteralValueInner::Concatenated(values) => Right(values.iter_mut()),
}
}
/// Returns `true` if the concatenated bytes has a length of zero.
pub fn is_empty(&self) -> bool {
self.parts().all(|part| part.is_empty())
}
/// Returns the length of the concatenated bytes.
pub fn len(&self) -> usize {
self.parts().map(|part| part.len()).sum()
}
/// Returns an iterator over the bytes of the concatenated bytes.
fn bytes(&self) -> impl Iterator<Item = u8> + '_ {
self.parts()
.flat_map(|part| part.as_slice().iter().copied())
}
}
impl PartialEq<[u8]> for BytesLiteralValue {
fn eq(&self, other: &[u8]) -> bool {
if self.len() != other.len() {
return false;
}
// The `zip` here is safe because we have checked the length of both parts.
self.bytes()
.zip(other.iter().copied())
.all(|(b1, b2)| b1 == b2)
}
}
/// An internal representation of [`BytesLiteralValue`].
#[derive(Clone, Debug, PartialEq)]
enum BytesLiteralValueInner {
/// A single bytes literal i.e., `b"foo"`.
Single(BytesLiteral),
/// An implicitly concatenated bytes literals i.e., `b"foo" b"bar"`.
Concatenated(Vec<BytesLiteral>),
}
impl Default for BytesLiteralValueInner {
fn default() -> Self {
Self::Single(BytesLiteral::default())
}
}
/// An AST node that represents a single bytes literal which is part of an
/// [`ExprBytesLiteral`].
#[derive(Clone, Debug, Default, PartialEq)]
pub struct BytesLiteral {
pub range: TextRange,
pub value: Vec<u8>,
}
impl Ranged for BytesLiteral {
fn range(&self) -> TextRange {
self.range
}
}
impl Deref for BytesLiteral {
type Target = [u8];
fn deref(&self) -> &Self::Target {
self.value.as_slice()
}
}
impl BytesLiteral {
/// Extracts a byte slice containing the entire [`BytesLiteral`].
pub fn as_slice(&self) -> &[u8] {
self
}
}
impl From<BytesLiteral> for Expr {
fn from(payload: BytesLiteral) -> Self {
ExprBytesLiteral {
range: payload.range,
value: BytesLiteralValue::single(payload),
}
.into()
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct ExprNumberLiteral {
pub range: TextRange,
@ -3088,12 +3540,12 @@ impl Ranged for crate::Expr {
Self::Call(node) => node.range(),
Self::FormattedValue(node) => node.range(),
Self::FString(node) => node.range(),
Expr::StringLiteral(node) => node.range(),
Expr::BytesLiteral(node) => node.range(),
Expr::NumberLiteral(node) => node.range(),
Expr::BooleanLiteral(node) => node.range(),
Expr::NoneLiteral(node) => node.range(),
Expr::EllipsisLiteral(node) => node.range(),
Self::StringLiteral(node) => node.range(),
Self::BytesLiteral(node) => node.range(),
Self::NumberLiteral(node) => node.range(),
Self::BooleanLiteral(node) => node.range(),
Self::NoneLiteral(node) => node.range(),
Self::EllipsisLiteral(node) => node.range(),
Self::Attribute(node) => node.range(),
Self::Subscript(node) => node.range(),
Self::Starred(node) => node.range(),
@ -3101,7 +3553,7 @@ impl Ranged for crate::Expr {
Self::List(node) => node.range(),
Self::Tuple(node) => node.range(),
Self::Slice(node) => node.range(),
Expr::IpyEscapeCommand(node) => node.range(),
Self::IpyEscapeCommand(node) => node.range(),
}
}
}

View file

@ -4,10 +4,10 @@ pub mod preorder;
pub mod transformer;
use crate::{
self as ast, Alias, Arguments, BoolOp, CmpOp, Comprehension, Decorator, ElifElseClause,
ExceptHandler, Expr, ExprContext, Keyword, MatchCase, Operator, Parameter, Parameters, Pattern,
PatternArguments, PatternKeyword, Stmt, TypeParam, TypeParamTypeVar, TypeParams, UnaryOp,
WithItem,
self as ast, Alias, Arguments, BoolOp, BytesLiteral, CmpOp, Comprehension, Decorator,
ElifElseClause, ExceptHandler, Expr, ExprContext, FString, FStringPart, Keyword, MatchCase,
Operator, Parameter, Parameters, Pattern, PatternArguments, PatternKeyword, Stmt,
StringLiteral, TypeParam, TypeParamTypeVar, TypeParams, UnaryOp, WithItem,
};
/// A trait for AST visitors. Visits all nodes in the AST recursively in evaluation-order.
@ -98,6 +98,15 @@ pub trait Visitor<'a> {
fn visit_elif_else_clause(&mut self, elif_else_clause: &'a ElifElseClause) {
walk_elif_else_clause(self, elif_else_clause);
}
fn visit_f_string(&mut self, f_string: &'a FString) {
walk_f_string(self, f_string);
}
fn visit_string_literal(&mut self, string_literal: &'a StringLiteral) {
walk_string_literal(self, string_literal);
}
fn visit_bytes_literal(&mut self, bytes_literal: &'a BytesLiteral) {
walk_bytes_literal(self, bytes_literal);
}
}
pub fn walk_body<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, body: &'a [Stmt]) {
@ -475,14 +484,27 @@ pub fn walk_expr<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, expr: &'a Expr) {
visitor.visit_format_spec(expr);
}
}
Expr::FString(ast::ExprFString { values, .. }) => {
for expr in values {
visitor.visit_expr(expr);
Expr::FString(ast::ExprFString { value, .. }) => {
for part in value.parts() {
match part {
FStringPart::Literal(string_literal) => {
visitor.visit_string_literal(string_literal);
}
FStringPart::FString(f_string) => visitor.visit_f_string(f_string),
}
}
}
Expr::StringLiteral(_)
| Expr::BytesLiteral(_)
| Expr::NumberLiteral(_)
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => {
for string_literal in value.parts() {
visitor.visit_string_literal(string_literal);
}
}
Expr::BytesLiteral(ast::ExprBytesLiteral { value, .. }) => {
for bytes_literal in value.parts() {
visitor.visit_bytes_literal(bytes_literal);
}
}
Expr::NumberLiteral(_)
| Expr::BooleanLiteral(_)
| Expr::NoneLiteral(_)
| Expr::EllipsisLiteral(_) => {}
@ -576,6 +598,12 @@ pub fn walk_except_handler<'a, V: Visitor<'a> + ?Sized>(
}
}
pub fn walk_f_string<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, f_string: &'a FString) {
for expr in &f_string.values {
visitor.visit_expr(expr);
}
}
pub fn walk_format_spec<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, format_spec: &'a Expr) {
visitor.visit_expr(format_spec);
}
@ -746,3 +774,17 @@ pub fn walk_cmp_op<'a, V: Visitor<'a> + ?Sized>(visitor: &V, cmp_op: &'a CmpOp)
#[allow(unused_variables)]
pub fn walk_alias<'a, V: Visitor<'a> + ?Sized>(visitor: &V, alias: &'a Alias) {}
#[allow(unused_variables)]
pub fn walk_string_literal<'a, V: Visitor<'a> + ?Sized>(
visitor: &V,
string_literal: &'a StringLiteral,
) {
}
#[allow(unused_variables)]
pub fn walk_bytes_literal<'a, V: Visitor<'a> + ?Sized>(
visitor: &V,
bytes_literal: &'a BytesLiteral,
) {
}

View file

@ -1,7 +1,8 @@
use crate::{
Alias, Arguments, BoolOp, CmpOp, Comprehension, Decorator, ElifElseClause, ExceptHandler, Expr,
Keyword, MatchCase, Mod, Operator, Parameter, ParameterWithDefault, Parameters, Pattern,
PatternArguments, PatternKeyword, Singleton, Stmt, TypeParam, TypeParams, UnaryOp, WithItem,
Alias, Arguments, BoolOp, BytesLiteral, CmpOp, Comprehension, Decorator, ElifElseClause,
ExceptHandler, Expr, FString, Keyword, MatchCase, Mod, Operator, Parameter,
ParameterWithDefault, Parameters, Pattern, PatternArguments, PatternKeyword, Singleton, Stmt,
StringLiteral, TypeParam, TypeParams, UnaryOp, WithItem,
};
use crate::{AnyNodeRef, AstNode};
@ -152,6 +153,21 @@ pub trait PreorderVisitor<'a> {
fn visit_elif_else_clause(&mut self, elif_else_clause: &'a ElifElseClause) {
walk_elif_else_clause(self, elif_else_clause);
}
#[inline]
fn visit_f_string(&mut self, f_string: &'a FString) {
walk_f_string(self, f_string);
}
#[inline]
fn visit_string_literal(&mut self, string_literal: &'a StringLiteral) {
walk_string_literal(self, string_literal);
}
#[inline]
fn visit_bytes_literal(&mut self, bytes_literal: &'a BytesLiteral) {
walk_bytes_literal(self, bytes_literal);
}
}
pub fn walk_module<'a, V>(visitor: &mut V, module: &'a Mod)
@ -530,6 +546,42 @@ where
{
}
#[inline]
pub fn walk_f_string<'a, V>(visitor: &mut V, f_string: &'a FString)
where
V: PreorderVisitor<'a> + ?Sized,
{
let node = AnyNodeRef::from(f_string);
if visitor.enter_node(node).is_traverse() {
f_string.visit_preorder(visitor);
}
visitor.leave_node(node);
}
#[inline]
pub fn walk_string_literal<'a, V>(visitor: &mut V, string_literal: &'a StringLiteral)
where
V: PreorderVisitor<'a> + ?Sized,
{
let node = AnyNodeRef::from(string_literal);
if visitor.enter_node(node).is_traverse() {
string_literal.visit_preorder(visitor);
}
visitor.leave_node(node);
}
#[inline]
pub fn walk_bytes_literal<'a, V>(visitor: &mut V, bytes_literal: &'a BytesLiteral)
where
V: PreorderVisitor<'a> + ?Sized,
{
let node = AnyNodeRef::from(bytes_literal);
if visitor.enter_node(node).is_traverse() {
bytes_literal.visit_preorder(visitor);
}
visitor.leave_node(node);
}
#[inline]
pub fn walk_alias<'a, V>(visitor: &mut V, alias: &'a Alias)
where

View file

@ -1,8 +1,8 @@
use crate::{
self as ast, Alias, Arguments, BoolOp, CmpOp, Comprehension, Decorator, ElifElseClause,
ExceptHandler, Expr, ExprContext, Keyword, MatchCase, Operator, Parameter, Parameters, Pattern,
PatternArguments, PatternKeyword, Stmt, TypeParam, TypeParamTypeVar, TypeParams, UnaryOp,
WithItem,
self as ast, Alias, Arguments, BoolOp, BytesLiteral, CmpOp, Comprehension, Decorator,
ElifElseClause, ExceptHandler, Expr, ExprContext, FString, Keyword, MatchCase, Operator,
Parameter, Parameters, Pattern, PatternArguments, PatternKeyword, Stmt, StringLiteral,
TypeParam, TypeParamTypeVar, TypeParams, UnaryOp, WithItem,
};
/// A trait for transforming ASTs. Visits all nodes in the AST recursively in evaluation-order.
@ -85,6 +85,15 @@ pub trait Transformer {
fn visit_elif_else_clause(&self, elif_else_clause: &mut ElifElseClause) {
walk_elif_else_clause(self, elif_else_clause);
}
fn visit_f_string(&self, f_string: &mut FString) {
walk_f_string(self, f_string);
}
fn visit_string_literal(&self, string_literal: &mut StringLiteral) {
walk_string_literal(self, string_literal);
}
fn visit_bytes_literal(&self, bytes_literal: &mut BytesLiteral) {
walk_bytes_literal(self, bytes_literal);
}
}
pub fn walk_body<V: Transformer + ?Sized>(visitor: &V, body: &mut [Stmt]) {
@ -462,14 +471,29 @@ pub fn walk_expr<V: Transformer + ?Sized>(visitor: &V, expr: &mut Expr) {
visitor.visit_format_spec(expr);
}
}
Expr::FString(ast::ExprFString { values, .. }) => {
for expr in values {
visitor.visit_expr(expr);
Expr::FString(ast::ExprFString { value, .. }) => {
for f_string_part in value.parts_mut() {
match f_string_part {
ast::FStringPart::Literal(string_literal) => {
visitor.visit_string_literal(string_literal);
}
ast::FStringPart::FString(f_string) => {
visitor.visit_f_string(f_string);
}
}
}
}
Expr::StringLiteral(_)
| Expr::BytesLiteral(_)
| Expr::NumberLiteral(_)
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => {
for string_literal in value.parts_mut() {
visitor.visit_string_literal(string_literal);
}
}
Expr::BytesLiteral(ast::ExprBytesLiteral { value, .. }) => {
for bytes_literal in value.parts_mut() {
visitor.visit_bytes_literal(bytes_literal);
}
}
Expr::NumberLiteral(_)
| Expr::BooleanLiteral(_)
| Expr::NoneLiteral(_)
| Expr::EllipsisLiteral(_) => {}
@ -560,6 +584,12 @@ pub fn walk_except_handler<V: Transformer + ?Sized>(
}
}
pub fn walk_f_string<V: Transformer + ?Sized>(visitor: &V, f_string: &mut FString) {
for expr in &mut f_string.values {
visitor.visit_expr(expr);
}
}
pub fn walk_format_spec<V: Transformer + ?Sized>(visitor: &V, format_spec: &mut Expr) {
visitor.visit_expr(format_spec);
}
@ -730,3 +760,13 @@ pub fn walk_cmp_op<V: Transformer + ?Sized>(visitor: &V, cmp_op: &mut CmpOp) {}
#[allow(unused_variables)]
pub fn walk_alias<V: Transformer + ?Sized>(visitor: &V, alias: &mut Alias) {}
#[allow(unused_variables)]
pub fn walk_string_literal<V: Transformer + ?Sized>(
visitor: &V,
string_literal: &mut StringLiteral,
) {
}
#[allow(unused_variables)]
pub fn walk_bytes_literal<V: Transformer + ?Sized>(visitor: &V, bytes_literal: &mut BytesLiteral) {}