Skip to content

Commit

Permalink
Detect literals with unary operators (UP018)
Browse files Browse the repository at this point in the history
  • Loading branch information
sanxiyn committed Feb 20, 2024
1 parent 0d363ab commit 4aec62d
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 16 deletions.
Original file line number Diff line number Diff line change
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)
33 changes: 23 additions & 10 deletions crates/ruff_linter/src/rules/pyupgrade/rules/native_literals.rs
Original file line number Diff line number Diff line change
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,7 +198,20 @@ pub(crate) fn native_literals(
checker.diagnostics.push(diagnostic);
}
Some(arg) => {
let Some(literal_expr) = arg.as_literal_expr() else {
let (literal_expr, unary) = if let Some(literal_expr) = arg.as_literal_expr() {
(literal_expr, false)
} else if let Expr::UnaryOp(ast::ExprUnaryOp {
op: UnaryOp::UAdd | UnaryOp::USub,
operand,
..
}) = arg
{
if let Some(literal_expr) = operand.as_literal_expr() {
(literal_expr, true)
} else {
return;
}
} else {
return;
};

Expand All @@ -215,20 +228,20 @@ pub(crate) fn native_literals(
return;
}

if unary {
if !matches!(literal_type, LiteralType::Int | LiteralType::Float) {
return;
}
}

let arg_code = checker.locator().slice(arg);

// Attribute access on an integer requires the integer to be parenthesized to disambiguate from a float
// 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
Original file line number Diff line number Diff line change
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 4aec62d

Please sign in to comment.