mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-03 18:28:56 +00:00
Split Constant
to individual literal nodes (#8064)
## Summary This PR splits the `Constant` enum as individual literal nodes. It introduces the following new nodes for each variant: * `ExprStringLiteral` * `ExprBytesLiteral` * `ExprNumberLiteral` * `ExprBooleanLiteral` * `ExprNoneLiteral` * `ExprEllipsisLiteral` The main motivation behind this refactor is to introduce the new AST node for implicit string concatenation in the coming PR. The elements of that node will be either a string literal, bytes literal or a f-string which can be implemented using an enum. This means that a string or bytes literal cannot be represented by `Constant::Str` / `Constant::Bytes` which creates an inconsistency. This PR avoids that inconsistency by splitting the constant nodes into it's own literal nodes, literal being the more appropriate naming convention from a static analysis tool perspective. This also makes working with literals in the linter and formatter much more ergonomic like, for example, if one would want to check if this is a string literal, it can be done easily using `Expr::is_string_literal_expr` or matching against `Expr::StringLiteral` as oppose to matching against the `ExprConstant` and enum `Constant`. A few AST helper methods can be simplified as well which will be done in a follow-up PR. This introduces a new `Expr::is_literal_expr` method which is the same as `Expr::is_constant_expr`. There are also intermediary changes related to implicit string concatenation which are quiet less. This is done so as to avoid having a huge PR which this already is. ## Test Plan 1. Verify and update all of the existing snapshots (parser, visitor) 2. Verify that the ecosystem check output remains **unchanged** for both the linter and formatter ### Formatter ecosystem check #### `main` | project | similarity index | total files | changed files | |----------------|------------------:|------------------:|------------------:| | cpython | 0.75803 | 1799 | 1647 | | django | 0.99983 | 2772 | 34 | | home-assistant | 0.99953 | 10596 | 186 | | poetry | 0.99891 | 317 | 17 | | transformers | 0.99966 | 2657 | 330 | | twine | 1.00000 | 33 | 0 | | typeshed | 0.99978 | 3669 | 20 | | warehouse | 0.99977 | 654 | 13 | | zulip | 0.99970 | 1459 | 22 | #### `dhruv/constant-to-literal` | project | similarity index | total files | changed files | |----------------|------------------:|------------------:|------------------:| | cpython | 0.75803 | 1799 | 1647 | | django | 0.99983 | 2772 | 34 | | home-assistant | 0.99953 | 10596 | 186 | | poetry | 0.99891 | 317 | 17 | | transformers | 0.99966 | 2657 | 330 | | twine | 1.00000 | 33 | 0 | | typeshed | 0.99978 | 3669 | 20 | | warehouse | 0.99977 | 654 | 13 | | zulip | 0.99970 | 1459 | 22 |
This commit is contained in:
parent
78bbf6d403
commit
230c9ce236
268 changed files with 6663 additions and 6741 deletions
|
@ -3,10 +3,10 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use ruff_python_ast::{
|
||||
self as ast, Alias, ArgOrKeyword, BoolOp, CmpOp, Comprehension, Constant, ConversionFlag,
|
||||
DebugText, ExceptHandler, Expr, Identifier, MatchCase, Operator, Parameter, Parameters,
|
||||
Pattern, Singleton, Stmt, Suite, TypeParam, TypeParamParamSpec, TypeParamTypeVar,
|
||||
TypeParamTypeVarTuple, WithItem,
|
||||
self as ast, Alias, ArgOrKeyword, BoolOp, CmpOp, Comprehension, ConversionFlag, DebugText,
|
||||
ExceptHandler, Expr, Identifier, MatchCase, Operator, Parameter, Parameters, Pattern,
|
||||
Singleton, Stmt, Suite, TypeParam, TypeParamParamSpec, TypeParamTypeVar, TypeParamTypeVarTuple,
|
||||
WithItem,
|
||||
};
|
||||
use ruff_python_ast::{ParameterWithDefault, TypeParams};
|
||||
use ruff_python_literal::escape::{AsciiEscape, Escape, UnicodeEscape};
|
||||
|
@ -115,12 +115,6 @@ impl<'a> Generator<'a> {
|
|||
self.generate()
|
||||
}
|
||||
|
||||
/// Generate source code from a [`Constant`].
|
||||
pub fn constant(mut self, constant: &Constant) -> String {
|
||||
self.unparse_constant(constant);
|
||||
self.generate()
|
||||
}
|
||||
|
||||
fn newline(&mut self) {
|
||||
if !self.initial {
|
||||
self.num_newlines = std::cmp::max(self.num_newlines, 1);
|
||||
|
@ -1090,12 +1084,56 @@ impl<'a> Generator<'a> {
|
|||
Expr::FString(ast::ExprFString { values, .. }) => {
|
||||
self.unparse_f_string(values, false);
|
||||
}
|
||||
Expr::Constant(ast::ExprConstant { value, range: _ }) => {
|
||||
self.unparse_constant(value);
|
||||
Expr::StringLiteral(ast::ExprStringLiteral { value, unicode, .. }) => {
|
||||
if *unicode {
|
||||
self.p("u");
|
||||
}
|
||||
self.p_str_repr(value);
|
||||
}
|
||||
Expr::BytesLiteral(ast::ExprBytesLiteral { value, .. }) => {
|
||||
self.p_bytes_repr(value);
|
||||
}
|
||||
Expr::NumberLiteral(ast::ExprNumberLiteral { value, .. }) => {
|
||||
static INF_STR: &str = "1e309";
|
||||
assert_eq!(f64::MAX_10_EXP, 308);
|
||||
|
||||
match value {
|
||||
ast::Number::Int(i) => {
|
||||
self.p(&format!("{i}"));
|
||||
}
|
||||
ast::Number::Float(fp) => {
|
||||
if fp.is_infinite() {
|
||||
self.p(INF_STR);
|
||||
} else {
|
||||
self.p(&ruff_python_literal::float::to_string(*fp));
|
||||
}
|
||||
}
|
||||
ast::Number::Complex { real, imag } => {
|
||||
let value = if *real == 0.0 {
|
||||
format!("{imag}j")
|
||||
} else {
|
||||
format!("({real}{imag:+}j)")
|
||||
};
|
||||
if real.is_infinite() || imag.is_infinite() {
|
||||
self.p(&value.replace("inf", INF_STR));
|
||||
} else {
|
||||
self.p(&value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Expr::BooleanLiteral(ast::ExprBooleanLiteral { value, .. }) => {
|
||||
self.p(if *value { "True" } else { "False" });
|
||||
}
|
||||
Expr::NoneLiteral(_) => {
|
||||
self.p("None");
|
||||
}
|
||||
Expr::EllipsisLiteral(_) => {
|
||||
self.p("...");
|
||||
}
|
||||
Expr::Attribute(ast::ExprAttribute { value, attr, .. }) => {
|
||||
if let Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Int(_),
|
||||
if let Expr::NumberLiteral(ast::ExprNumberLiteral {
|
||||
value: ast::Number::Int(_),
|
||||
..
|
||||
}) = value.as_ref()
|
||||
{
|
||||
|
@ -1174,45 +1212,6 @@ impl<'a> Generator<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn unparse_constant(&mut self, constant: &Constant) {
|
||||
assert_eq!(f64::MAX_10_EXP, 308);
|
||||
let inf_str = "1e309";
|
||||
match constant {
|
||||
Constant::Bytes(b) => {
|
||||
self.p_bytes_repr(b);
|
||||
}
|
||||
Constant::Str(ast::StringConstant { value, unicode, .. }) => {
|
||||
if *unicode {
|
||||
self.p("u");
|
||||
}
|
||||
self.p_str_repr(value);
|
||||
}
|
||||
Constant::None => self.p("None"),
|
||||
Constant::Bool(b) => self.p(if *b { "True" } else { "False" }),
|
||||
Constant::Int(i) => self.p(&format!("{i}")),
|
||||
Constant::Float(fp) => {
|
||||
if fp.is_infinite() {
|
||||
self.p(inf_str);
|
||||
} else {
|
||||
self.p(&ruff_python_literal::float::to_string(*fp));
|
||||
}
|
||||
}
|
||||
Constant::Complex { real, imag } => {
|
||||
let value = if *real == 0.0 {
|
||||
format!("{imag}j")
|
||||
} else {
|
||||
format!("({real}{imag:+}j)")
|
||||
};
|
||||
if real.is_infinite() || imag.is_infinite() {
|
||||
self.p(&value.replace("inf", inf_str));
|
||||
} else {
|
||||
self.p(&value);
|
||||
}
|
||||
}
|
||||
Constant::Ellipsis => self.p("..."),
|
||||
}
|
||||
}
|
||||
|
||||
fn unparse_parameters(&mut self, parameters: &Parameters) {
|
||||
let mut first = true;
|
||||
for (i, parameter_with_default) in parameters
|
||||
|
@ -1325,12 +1324,8 @@ impl<'a> Generator<'a> {
|
|||
|
||||
fn unparse_f_string_elem(&mut self, expr: &Expr, is_spec: bool) {
|
||||
match expr {
|
||||
Expr::Constant(ast::ExprConstant { value, .. }) => {
|
||||
if let Constant::Str(ast::StringConstant { value, .. }) = value {
|
||||
self.unparse_f_string_literal(value);
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => {
|
||||
self.unparse_f_string_literal(value);
|
||||
}
|
||||
Expr::FString(ast::ExprFString { values, .. }) => {
|
||||
self.unparse_f_string(values, is_spec);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue