Skip to content

Commit

Permalink
[pyupgrade] Detect literals with unary operators (UP018) (#10060)
Browse files Browse the repository at this point in the history
Fix #10029.
  • Loading branch information
sanxiyn committed Feb 20, 2024
1 parent 0f70c99 commit 7eafba2
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 21 deletions.
Expand Up @@ -33,7 +33,7 @@
bool(1.0)
int().denominator

# These become string or byte literals
# These become literals
str()
str("foo")
str("""
Expand All @@ -53,3 +53,9 @@

# These become a literal but retain parentheses
int(1).denominator

# These too are literals in spirit
int(+1)
int(-1)
float(+1.0)
float(-1.0)
40 changes: 25 additions & 15 deletions crates/ruff_linter/src/rules/pyupgrade/rules/native_literals.rs
Expand Up @@ -3,7 +3,7 @@ use std::str::FromStr;

use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{self as ast, Expr, LiteralExpressionRef};
use ruff_python_ast::{self as ast, Expr, LiteralExpressionRef, UnaryOp};
use ruff_text_size::{Ranged, TextRange};

use crate::checkers::ast::Checker;
Expand Down Expand Up @@ -198,15 +198,31 @@ pub(crate) fn native_literals(
checker.diagnostics.push(diagnostic);
}
Some(arg) => {
let Some(literal_expr) = arg.as_literal_expr() else {
let literal_expr = if let Some(literal_expr) = arg.as_literal_expr() {
// Skip implicit concatenated strings.
if literal_expr.is_implicit_concatenated() {
return;
}
literal_expr
} else if let Expr::UnaryOp(ast::ExprUnaryOp {
op: UnaryOp::UAdd | UnaryOp::USub,
operand,
..
}) = arg
{
if let Some(literal_expr) = operand
.as_literal_expr()
.filter(|expr| matches!(expr, LiteralExpressionRef::NumberLiteral(_)))
{
literal_expr
} else {
// Only allow unary operators for numbers.
return;
}
} else {
return;
};

// Skip implicit string concatenations.
if literal_expr.is_implicit_concatenated() {
return;
}

let Ok(arg_literal_type) = LiteralType::try_from(literal_expr) else {
return;
};
Expand All @@ -221,14 +237,8 @@ pub(crate) fn native_literals(
// Ex) `(7).denominator` is valid but `7.denominator` is not
// Note that floats do not have this problem
// Ex) `(1.0).real` is valid and `1.0.real` is too
let content = match (parent_expr, arg) {
(
Some(Expr::Attribute(_)),
Expr::NumberLiteral(ast::ExprNumberLiteral {
value: ast::Number::Int(_),
..
}),
) => format!("({arg_code})"),
let content = match (parent_expr, literal_type) {
(Some(Expr::Attribute(_)), LiteralType::Int) => format!("({arg_code})"),
_ => arg_code.to_string(),
};

Expand Down
Expand Up @@ -3,7 +3,7 @@ source: crates/ruff_linter/src/rules/pyupgrade/mod.rs
---
UP018.py:37:1: UP018 [*] Unnecessary `str` call (rewrite as a literal)
|
36 | # These become string or byte literals
36 | # These become literals
37 | str()
| ^^^^^ UP018
38 | str("foo")
Expand All @@ -14,7 +14,7 @@ UP018.py:37:1: UP018 [*] Unnecessary `str` call (rewrite as a literal)
Safe fix
34 34 | int().denominator
35 35 |
36 36 | # These become string or byte literals
36 36 | # These become literals
37 |-str()
37 |+""
38 38 | str("foo")
Expand All @@ -23,7 +23,7 @@ UP018.py:37:1: UP018 [*] Unnecessary `str` call (rewrite as a literal)

UP018.py:38:1: UP018 [*] Unnecessary `str` call (rewrite as a literal)
|
36 | # These become string or byte literals
36 | # These become literals
37 | str()
38 | str("foo")
| ^^^^^^^^^^ UP018
Expand All @@ -34,7 +34,7 @@ UP018.py:38:1: UP018 [*] Unnecessary `str` call (rewrite as a literal)

Safe fix
35 35 |
36 36 | # These become string or byte literals
36 36 | # These become literals
37 37 | str()
38 |-str("foo")
38 |+"foo"
Expand All @@ -55,7 +55,7 @@ UP018.py:39:1: UP018 [*] Unnecessary `str` call (rewrite as a literal)
= help: Replace with string literal

Safe fix
36 36 | # These become string or byte literals
36 36 | # These become literals
37 37 | str()
38 38 | str("foo")
39 |-str("""
Expand Down Expand Up @@ -304,6 +304,8 @@ UP018.py:55:1: UP018 [*] Unnecessary `int` call (rewrite as a literal)
54 | # These become a literal but retain parentheses
55 | int(1).denominator
| ^^^^^^ UP018
56 |
57 | # These too are literals in spirit
|
= help: Replace with integer literal

Expand All @@ -313,5 +315,82 @@ UP018.py:55:1: UP018 [*] Unnecessary `int` call (rewrite as a literal)
54 54 | # These become a literal but retain parentheses
55 |-int(1).denominator
55 |+(1).denominator
56 56 |
57 57 | # These too are literals in spirit
58 58 | int(+1)

UP018.py:58:1: UP018 [*] Unnecessary `int` call (rewrite as a literal)
|
57 | # These too are literals in spirit
58 | int(+1)
| ^^^^^^^ UP018
59 | int(-1)
60 | float(+1.0)
|
= help: Replace with integer literal

Safe fix
55 55 | int(1).denominator
56 56 |
57 57 | # These too are literals in spirit
58 |-int(+1)
58 |++1
59 59 | int(-1)
60 60 | float(+1.0)
61 61 | float(-1.0)

UP018.py:59:1: UP018 [*] Unnecessary `int` call (rewrite as a literal)
|
57 | # These too are literals in spirit
58 | int(+1)
59 | int(-1)
| ^^^^^^^ UP018
60 | float(+1.0)
61 | float(-1.0)
|
= help: Replace with integer literal

Safe fix
56 56 |
57 57 | # These too are literals in spirit
58 58 | int(+1)
59 |-int(-1)
59 |+-1
60 60 | float(+1.0)
61 61 | float(-1.0)

UP018.py:60:1: UP018 [*] Unnecessary `float` call (rewrite as a literal)
|
58 | int(+1)
59 | int(-1)
60 | float(+1.0)
| ^^^^^^^^^^^ UP018
61 | float(-1.0)
|
= help: Replace with float literal

Safe fix
57 57 | # These too are literals in spirit
58 58 | int(+1)
59 59 | int(-1)
60 |-float(+1.0)
60 |++1.0
61 61 | float(-1.0)

UP018.py:61:1: UP018 [*] Unnecessary `float` call (rewrite as a literal)
|
59 | int(-1)
60 | float(+1.0)
61 | float(-1.0)
| ^^^^^^^^^^^ UP018
|
= help: Replace with float literal

Safe fix
58 58 | int(+1)
59 59 | int(-1)
60 60 | float(+1.0)
61 |-float(-1.0)
61 |+-1.0


0 comments on commit 7eafba2

Please sign in to comment.