diff --git a/crates/ruff_python_ast/src/nodes.rs b/crates/ruff_python_ast/src/nodes.rs index c91689e3c6b46b..d7f5c2f8faf940 100644 --- a/crates/ruff_python_ast/src/nodes.rs +++ b/crates/ruff_python_ast/src/nodes.rs @@ -116,7 +116,7 @@ impl ExprRef<'_> { } pub fn precedence(&self) -> OperatorPrecedence { - OperatorPrecedence::from(self) + OperatorPrecedence::from(*self) } } diff --git a/crates/ruff_python_ast/src/operator_precedence.rs b/crates/ruff_python_ast/src/operator_precedence.rs index 6b652847e9a574..8fba7184af6055 100644 --- a/crates/ruff_python_ast/src/operator_precedence.rs +++ b/crates/ruff_python_ast/src/operator_precedence.rs @@ -54,7 +54,7 @@ pub enum OperatorPrecedence { } impl OperatorPrecedence { - pub fn from_expr_ref(expr: &ExprRef) -> Self { + pub fn from_expr_ref(expr: ExprRef) -> Self { match expr { // Binding or parenthesized expression, list display, dictionary display, set display ExprRef::Tuple(_) @@ -130,7 +130,7 @@ impl OperatorPrecedence { } pub fn from_expr(expr: &Expr) -> Self { - Self::from(&ExprRef::from(expr)) + Self::from(ExprRef::from(expr)) } /// Returns `true` if the precedence is right-associative i.e., the operations are evaluated @@ -146,8 +146,8 @@ impl From<&Expr> for OperatorPrecedence { } } -impl<'a> From<&ExprRef<'a>> for OperatorPrecedence { - fn from(expr_ref: &ExprRef<'a>) -> Self { +impl<'a> From> for OperatorPrecedence { + fn from(expr_ref: ExprRef<'a>) -> Self { Self::from_expr_ref(expr_ref) } } diff --git a/crates/ty_python_semantic/resources/mdtest/directives/cast.md b/crates/ty_python_semantic/resources/mdtest/directives/cast.md index f88e39a08cebb5..797026a1e86bae 100644 --- a/crates/ty_python_semantic/resources/mdtest/directives/cast.md +++ b/crates/ty_python_semantic/resources/mdtest/directives/cast.md @@ -114,6 +114,15 @@ warning[redundant-cast]: Value is already of type `int` 5 | cast(int, secrets.randbelow(10)) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | +help: Remove the redundant `cast` +2 | from typing import cast +3 | +4 | # snapshot: redundant-cast + - cast(int, secrets.randbelow(10)) +5 + secrets.randbelow(10) +6 | # snapshot: redundant-cast +7 | cast(val=secrets.randbelow(10), typ=int) +8 | def f(x: int, y: int, z: int) -> int: ``` ```py @@ -128,4 +137,82 @@ warning[redundant-cast]: Value is already of type `int` 7 | cast(val=secrets.randbelow(10), typ=int) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | +help: Remove the redundant `cast` +4 | # snapshot: redundant-cast +5 | cast(int, secrets.randbelow(10)) +6 | # snapshot: redundant-cast + - cast(val=secrets.randbelow(10), typ=int) +7 + secrets.randbelow(10) +8 | def f(x: int, y: int, z: int) -> int: +9 | # snapshot: redundant-cast +10 | return cast(int, x + y) * z +``` + +```py +def f(x: int, y: int, z: int) -> int: + # snapshot: redundant-cast + return cast(int, x + y) * z +``` + +```snapshot +warning[redundant-cast]: Value is already of type `int` + --> src/mdtest_snippet.py:10:12 + | +10 | return cast(int, x + y) * z + | ^^^^^^^^^^^^^^^^ + | +help: Remove the redundant `cast` +7 | cast(val=secrets.randbelow(10), typ=int) +8 | def f(x: int, y: int, z: int) -> int: +9 | # snapshot: redundant-cast + - return cast(int, x + y) * z +10 + return (x + y) * z +11 | def g(x: int, y: int) -> int: +12 | # snapshot: redundant-cast +13 | return -cast(int, x + y) +``` + +```py +def g(x: int, y: int) -> int: + # snapshot: redundant-cast + return -cast(int, x + y) +``` + +```snapshot +warning[redundant-cast]: Value is already of type `int` + --> src/mdtest_snippet.py:13:13 + | +13 | return -cast(int, x + y) + | ^^^^^^^^^^^^^^^^ + | +help: Remove the redundant `cast` +10 | return cast(int, x + y) * z +11 | def g(x: int, y: int) -> int: +12 | # snapshot: redundant-cast + - return -cast(int, x + y) +13 + return -(x + y) +14 | def h(x: int, y: int) -> None: +15 | # snapshot: redundant-cast +16 | print(cast(int, x + y)) +``` + +```py +def h(x: int, y: int) -> None: + # snapshot: redundant-cast + print(cast(int, x + y)) +``` + +```snapshot +warning[redundant-cast]: Value is already of type `int` + --> src/mdtest_snippet.py:16:11 + | +16 | print(cast(int, x + y)) + | ^^^^^^^^^^^^^^^^ + | +help: Remove the redundant `cast` +13 | return -cast(int, x + y) +14 | def h(x: int, y: int) -> None: +15 | # snapshot: redundant-cast + - print(cast(int, x + y)) +16 + print(x + y) ``` diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/typed_dict.md_-_`TypedDict`_-_Redundant_cast_warni\342\200\246_(75ac240a2d1f7108).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/typed_dict.md_-_`TypedDict`_-_Redundant_cast_warni\342\200\246_(75ac240a2d1f7108).snap" index 4efaf32b8aef6f..4606501edfaf6f 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/typed_dict.md_-_`TypedDict`_-_Redundant_cast_warni\342\200\246_(75ac240a2d1f7108).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/typed_dict.md_-_`TypedDict`_-_Redundant_cast_warni\342\200\246_(75ac240a2d1f7108).snap" @@ -1,5 +1,5 @@ --- -source: crates/ty_test/src/lib.rs +source: crates/mdtest/src/lib.rs expression: snapshot --- @@ -35,6 +35,13 @@ warning[redundant-cast]: Value is already of type `Foo2` 10 | _ = cast(Foo2, foo) # error: [redundant-cast] | ^^^^^^^^^^^^^^^ | +help: Remove the redundant `cast` +7 | x: int +8 | +9 | foo: Foo2 = {"x": 1} + - _ = cast(Foo2, foo) # error: [redundant-cast] +10 + _ = foo # error: [redundant-cast] +11 | _ = cast(Bar2, foo) # error: [redundant-cast] ``` @@ -46,5 +53,11 @@ warning[redundant-cast]: Value is already of type `Bar2` | ^^^^^^^^^^^^^^^ | info: `Bar2` is equivalent to `Foo2` +help: Remove the redundant `cast` +8 | +9 | foo: Foo2 = {"x": 1} +10 | _ = cast(Foo2, foo) # error: [redundant-cast] + - _ = cast(Bar2, foo) # error: [redundant-cast] +11 + _ = foo # error: [redundant-cast] ``` diff --git a/crates/ty_python_semantic/src/types/function.rs b/crates/ty_python_semantic/src/types/function.rs index d8214d6e511c5f..7807fab3bd1d2d 100644 --- a/crates/ty_python_semantic/src/types/function.rs +++ b/crates/ty_python_semantic/src/types/function.rs @@ -55,7 +55,10 @@ use bitflags::bitflags; use ruff_db::diagnostic::{Annotation, DiagnosticId, Severity, Span}; use ruff_db::files::{File, FileRange}; use ruff_db::parsed::{ParsedModuleRef, parsed_module}; -use ruff_python_ast::{self as ast, ParameterWithDefault}; +use ruff_db::source::source_text; +use ruff_diagnostics::{Edit, Fix}; +use ruff_python_ast::find_node::covering_node; +use ruff_python_ast::{self as ast, OperatorPrecedence, ParameterWithDefault}; use ruff_text_size::Ranged; use salsa::plumbing::AsId; use ty_module_resolver::{KnownModule, ModuleName, file_to_module, resolve_module}; @@ -2232,6 +2235,31 @@ impl KnownFunction { "`{casted_display}` is equivalent to `{source_display}`", )); } + if let Some(value) = call_expression.arguments.find_argument_value("val", 1) + { + let covering = covering_node( + context.module().syntax().into(), + call_expression.range(), + ); + let needs_parens = covering + .parent() + .and_then(ast::AnyNodeRef::as_expr_ref) + .is_some_and(|parent| { + let value_precedence = OperatorPrecedence::from_expr(value); + OperatorPrecedence::from_expr_ref(parent) >= value_precedence + }); + let value_text = &source_text(db, file)[value.range()]; + let replacement = if needs_parens { + format!("({value_text})") + } else { + value_text.to_string() + }; + diagnostic.help("Remove the redundant `cast`"); + diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( + replacement, + call_expression.range(), + ))); + } } } }