ruff/crates/ruff_python_parser/src/parser/helpers.rs
Dylan 9bbf4987e8
Implement template strings (#17851)
This PR implements template strings (t-strings) in the parser and
formatter for Ruff.

Minimal changes necessary to compile were made in other parts of the code (e.g. ty, the linter, etc.). These will be covered properly in follow-up PRs.
2025-05-30 15:00:56 -05:00

104 lines
4.3 KiB
Rust

use ruff_python_ast::{self as ast, CmpOp, Expr, ExprContext, Number};
use ruff_text_size::{Ranged, TextRange};
use crate::{TokenKind, error::RelaxedDecoratorError};
/// Set the `ctx` for `Expr::Id`, `Expr::Attribute`, `Expr::Subscript`, `Expr::Starred`,
/// `Expr::Tuple` and `Expr::List`. If `expr` is either `Expr::Tuple` or `Expr::List`,
/// recursively sets the `ctx` for their elements.
pub(super) fn set_expr_ctx(expr: &mut Expr, new_ctx: ExprContext) {
match expr {
Expr::Name(ast::ExprName { ctx, .. })
| Expr::Attribute(ast::ExprAttribute { ctx, .. })
| Expr::Subscript(ast::ExprSubscript { ctx, .. }) => *ctx = new_ctx,
Expr::Starred(ast::ExprStarred { value, ctx, .. }) => {
*ctx = new_ctx;
set_expr_ctx(value, new_ctx);
}
Expr::UnaryOp(ast::ExprUnaryOp { operand, .. }) => {
set_expr_ctx(operand, new_ctx);
}
Expr::List(ast::ExprList { elts, ctx, .. })
| Expr::Tuple(ast::ExprTuple { elts, ctx, .. }) => {
*ctx = new_ctx;
elts.iter_mut()
.for_each(|element| set_expr_ctx(element, new_ctx));
}
_ => {}
}
}
/// Converts a [`TokenKind`] array of size 2 to its correspondent [`CmpOp`].
pub(super) const fn token_kind_to_cmp_op(tokens: [TokenKind; 2]) -> Option<CmpOp> {
Some(match tokens {
[TokenKind::Is, TokenKind::Not] => CmpOp::IsNot,
[TokenKind::Is, _] => CmpOp::Is,
[TokenKind::Not, TokenKind::In] => CmpOp::NotIn,
[TokenKind::In, _] => CmpOp::In,
[TokenKind::EqEqual, _] => CmpOp::Eq,
[TokenKind::NotEqual, _] => CmpOp::NotEq,
[TokenKind::Less, _] => CmpOp::Lt,
[TokenKind::LessEqual, _] => CmpOp::LtE,
[TokenKind::Greater, _] => CmpOp::Gt,
[TokenKind::GreaterEqual, _] => CmpOp::GtE,
_ => return None,
})
}
/// Helper for `parse_decorators` to determine if `expr` is a [`dotted_name`] from the decorator
/// grammar before Python 3.9.
///
/// Returns `Some((error, range))` if `expr` is not a `dotted_name`, or `None` if it is a `dotted_name`.
///
/// [`dotted_name`]: https://docs.python.org/3.8/reference/compound_stmts.html#grammar-token-dotted-name
pub(super) fn detect_invalid_pre_py39_decorator_node(
expr: &Expr,
) -> Option<(RelaxedDecoratorError, TextRange)> {
let description = match expr {
Expr::Name(_) => return None,
Expr::Attribute(attribute) => {
return detect_invalid_pre_py39_decorator_node(&attribute.value);
}
Expr::Call(_) => return Some((RelaxedDecoratorError::CallExpression, expr.range())),
Expr::NumberLiteral(number) => match &number.value {
Number::Int(_) => "an int literal",
Number::Float(_) => "a float literal",
Number::Complex { .. } => "a complex literal",
},
Expr::BoolOp(_) => "boolean expression",
Expr::BinOp(_) => "binary-operation expression",
Expr::UnaryOp(_) => "unary-operation expression",
Expr::Await(_) => "`await` expression",
Expr::Lambda(_) => "lambda expression",
Expr::If(_) => "conditional expression",
Expr::Dict(_) => "a dict literal",
Expr::Set(_) => "a set literal",
Expr::List(_) => "a list literal",
Expr::Tuple(_) => "a tuple literal",
Expr::Starred(_) => "starred expression",
Expr::Slice(_) => "slice expression",
Expr::BytesLiteral(_) => "a bytes literal",
Expr::StringLiteral(_) => "a string literal",
Expr::EllipsisLiteral(_) => "an ellipsis literal",
Expr::NoneLiteral(_) => "a `None` literal",
Expr::BooleanLiteral(_) => "a boolean literal",
Expr::ListComp(_) => "a list comprehension",
Expr::SetComp(_) => "a set comprehension",
Expr::DictComp(_) => "a dict comprehension",
Expr::Generator(_) => "generator expression",
Expr::Yield(_) => "`yield` expression",
Expr::YieldFrom(_) => "`yield from` expression",
Expr::Compare(_) => "comparison expression",
Expr::FString(_) => "f-string",
Expr::TString(_) => "t-string",
Expr::Named(_) => "assignment expression",
Expr::Subscript(_) => "subscript expression",
Expr::IpyEscapeCommand(_) => "IPython escape command",
};
Some((RelaxedDecoratorError::Other(description), expr.range()))
}