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:
Dhruv Manilawala 2023-10-30 12:13:23 +05:30 committed by GitHub
parent 78bbf6d403
commit 230c9ce236
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
268 changed files with 6663 additions and 6741 deletions

View file

@ -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);