Parenthesize numbers during attribute accesses (#3189)

This commit is contained in:
Charlie Marsh 2023-02-23 14:57:23 -05:00 committed by GitHub
parent 32d165b7ad
commit bda2a0007a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 60 additions and 153 deletions

View file

@ -23,9 +23,17 @@ pub fn trailing_quote(content: &str) -> Option<&&str> {
.find(|&pattern| content.ends_with(pattern))
}
pub fn is_radix_literal(content: &str) -> bool {
content.starts_with("0b")
|| content.starts_with("0o")
|| content.starts_with("0x")
|| content.starts_with("0B")
|| content.starts_with("0O")
|| content.starts_with("0X")
}
#[cfg(test)]
mod tests {
#[test]
fn test_prefixes() {
let prefixes = ruff_python::str::TRIPLE_QUOTE_PREFIXES

View file

@ -22,6 +22,9 @@ pub mod shared_traits;
pub mod trivia;
pub fn fmt(contents: &str) -> Result<Formatted<ASTFormatContext>> {
// Create a reusable locator.
let locator = Locator::new(contents);
// Tokenize once.
let tokens: Vec<LexResult> = ruff_rustpython::tokenize(contents);
@ -37,7 +40,7 @@ pub fn fmt(contents: &str) -> Result<Formatted<ASTFormatContext>> {
// Attach trivia.
attach(&mut python_cst, trivia);
normalize_newlines(&mut python_cst);
normalize_parentheses(&mut python_cst);
normalize_parentheses(&mut python_cst, &locator);
format!(
ASTFormatContext::new(
@ -45,7 +48,7 @@ pub fn fmt(contents: &str) -> Result<Formatted<ASTFormatContext>> {
indent_style: IndentStyle::Space(4),
line_width: 88.try_into().unwrap(),
},
Locator::new(contents)
locator,
),
[format::builders::block(&python_cst)]
)

View file

@ -1,7 +1,11 @@
use crate::core::helpers::is_radix_literal;
use crate::core::locator::Locator;
use crate::core::types::Range;
use crate::core::visitor;
use crate::core::visitor::Visitor;
use crate::cst::{Expr, ExprKind, Stmt, StmtKind};
use crate::trivia::{Parenthesize, TriviaKind};
use rustpython_parser::ast::Constant;
/// Modify an [`Expr`] to infer parentheses, rather than respecting any user-provided trivia.
fn use_inferred_parens(expr: &mut Expr) {
@ -22,9 +26,11 @@ fn use_inferred_parens(expr: &mut Expr) {
}
}
struct ParenthesesNormalizer {}
struct ParenthesesNormalizer<'a> {
locator: &'a Locator<'a>,
}
impl<'a> Visitor<'a> for ParenthesesNormalizer {
impl<'a> Visitor<'a> for ParenthesesNormalizer<'_> {
fn visit_stmt(&mut self, stmt: &'a mut Stmt) {
// Always remove parentheses around statements, unless it's an expression statement,
// in which case, remove parentheses around the expression.
@ -134,7 +140,29 @@ impl<'a> Visitor<'a> for ParenthesesNormalizer {
ExprKind::FormattedValue { .. } => {}
ExprKind::JoinedStr { .. } => {}
ExprKind::Constant { .. } => {}
ExprKind::Attribute { .. } => {}
ExprKind::Attribute { value, .. } => {
if matches!(
value.node,
ExprKind::Constant {
value: Constant::Float(..),
..
},
) {
value.parentheses = Parenthesize::Always;
} else if matches!(
value.node,
ExprKind::Constant {
value: Constant::Int(..),
..
},
) {
let (source, start, end) = self.locator.slice(Range::from_located(value));
// TODO(charlie): Encode this in the AST via separate node types.
if !is_radix_literal(&source[start..end]) {
value.parentheses = Parenthesize::Always;
}
}
}
ExprKind::Subscript { value, slice, .. } => {
// If the slice isn't manually parenthesized, ensure that we _never_ parenthesize
// the value.
@ -166,7 +194,7 @@ impl<'a> Visitor<'a> for ParenthesesNormalizer {
///
/// TODO(charlie): It's weird that we have both `TriviaKind::Parentheses` (which aren't used
/// during formatting) and `Parenthesize` (which are used during formatting).
pub fn normalize_parentheses(python_cst: &mut [Stmt]) {
let mut normalizer = ParenthesesNormalizer {};
pub fn normalize_parentheses(python_cst: &mut [Stmt], locator: &Locator) {
let mut normalizer = ParenthesesNormalizer { locator };
normalizer.visit_body(python_cst);
}

View file

@ -1,128 +0,0 @@
---
source: crates/ruff_python_formatter/src/lib.rs
expression: snapshot
input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/attribute_access_on_number_literals.py
---
## Input
```py
x = 123456789 .bit_count()
x = (123456).__abs__()
x = .1.is_integer()
x = 1. .imag
x = 1E+1.imag
x = 1E-1.real
x = 123456789.123456789.hex()
x = 123456789.123456789E123456789 .real
x = 123456789E123456789 .conjugate()
x = 123456789J.real
x = 123456789.123456789J.__add__(0b1011.bit_length())
x = 0XB1ACC.conjugate()
x = 0B1011 .conjugate()
x = 0O777 .real
x = 0.000000006 .hex()
x = -100.0000J
if 10 .real:
...
y = 100[no]
y = 100(no)
```
## Black Differences
```diff
--- Black
+++ Ruff
@@ -1,22 +1,22 @@
-x = (123456789).bit_count()
+x = 123456789.bit_count()
x = (123456).__abs__()
-x = (0.1).is_integer()
-x = (1.0).imag
-x = (1e1).imag
-x = (1e-1).real
-x = (123456789.123456789).hex()
-x = (123456789.123456789e123456789).real
-x = (123456789e123456789).conjugate()
+x = 0.1.is_integer()
+x = 1.0.imag
+x = 1e1.imag
+x = 1e-1.real
+x = 123456789.123456789.hex()
+x = 123456789.123456789e123456789.real
+x = 123456789e123456789.conjugate()
x = 123456789j.real
x = 123456789.123456789j.__add__(0b1011.bit_length())
x = 0xB1ACC.conjugate()
x = 0b1011.conjugate()
x = 0o777.real
-x = (0.000000006).hex()
+x = 0.000000006.hex()
x = -100.0000j
-if (10).real:
+if 10.real:
...
y = 100[no]
-y = 100(no)
+y = 100((no))
```
## Ruff Output
```py
x = 123456789.bit_count()
x = (123456).__abs__()
x = 0.1.is_integer()
x = 1.0.imag
x = 1e1.imag
x = 1e-1.real
x = 123456789.123456789.hex()
x = 123456789.123456789e123456789.real
x = 123456789e123456789.conjugate()
x = 123456789j.real
x = 123456789.123456789j.__add__(0b1011.bit_length())
x = 0xB1ACC.conjugate()
x = 0b1011.conjugate()
x = 0o777.real
x = 0.000000006.hex()
x = -100.0000j
if 10.real:
...
y = 100[no]
y = 100((no))
```
## Black Output
```py
x = (123456789).bit_count()
x = (123456).__abs__()
x = (0.1).is_integer()
x = (1.0).imag
x = (1e1).imag
x = (1e-1).real
x = (123456789.123456789).hex()
x = (123456789.123456789e123456789).real
x = (123456789e123456789).conjugate()
x = 123456789j.real
x = 123456789.123456789j.__add__(0b1011.bit_length())
x = 0xB1ACC.conjugate()
x = 0b1011.conjugate()
x = 0o777.real
x = (0.000000006).hex()
x = -100.0000j
if (10).real:
...
y = 100[no]
y = 100(no)
```

View file

@ -292,17 +292,6 @@ last_call()
}
Python3 > Python2 > COBOL
Life is Life
@@ -122,8 +125,8 @@
call(b, **self.screen_kwargs)
lukasz.langa.pl
call.me(maybe)
-(1).real
-(1.0).real
+1.real
+1.0.real
....__class__
list[str]
dict[str, int]
@@ -138,33 +141,33 @@
very_long_variable_name_filters: t.List[
t.Tuple[str, t.Union[str, t.List[t.Optional[str]]]],
@ -310,14 +299,14 @@ last_call()
-xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod( # type: ignore
- sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)
+xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = (
+ classmethod(sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)) # type: ignore
+)
+xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = (
+ classmethod(sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)) # type: ignore
)
-xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod( # type: ignore
- sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)
+xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = (
+ classmethod(sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)) # type: ignore
+)
+xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = (
+ classmethod(sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__))
)
-xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod(
@ -583,8 +572,8 @@ call(**self.screen_kwargs)
call(b, **self.screen_kwargs)
lukasz.langa.pl
call.me(maybe)
1.real
1.0.real
(1).real
(1.0).real
....__class__
list[str]
dict[str, int]

View file

@ -183,7 +183,14 @@ pub fn extract_trivia_tokens(lxr: &[LexResult]) -> Vec<TriviaToken> {
if matches!(tok, Tok::Lpar) {
if prev_tok.map_or(true, |(_, prev_tok, _)| {
!matches!(prev_tok, Tok::Name { .. })
!matches!(
prev_tok,
Tok::Name { .. }
| Tok::Int { .. }
| Tok::Float { .. }
| Tok::Complex { .. }
| Tok::String { .. }
)
}) {
parens.push((start, true));
} else {