Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

make [manual_map] ignore types that contain dyn #12712

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
78 changes: 74 additions & 4 deletions clippy_lints/src/matches/manual_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,19 @@ use crate::matches::MATCH_AS_REF;
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
use clippy_utils::sugg::Sugg;
use clippy_utils::ty::{is_copy, is_type_diagnostic_item, peel_mid_ty_refs_is_mutable, type_is_unsafe_function};
use clippy_utils::visitors::for_each_expr;
use clippy_utils::{
can_move_expr_to_closure, is_else_clause, is_lint_allowed, is_res_lang_ctor, path_res, path_to_local_id,
peel_blocks, peel_hir_expr_refs, peel_hir_expr_while, CaptureKind,
can_move_expr_to_closure, is_else_clause, is_lint_allowed, is_res_lang_ctor, path_res, path_to_local,
path_to_local_id, peel_blocks, peel_hir_expr_refs, peel_hir_expr_while, CaptureKind,
};
use rustc_ast::util::parser::PREC_POSTFIX;
use rustc_errors::Applicability;
use rustc_hir::def::Res;
use rustc_hir::LangItem::{OptionNone, OptionSome};
use rustc_hir::{BindingMode, Expr, ExprKind, HirId, Mutability, Pat, PatKind, Path, QPath};
use rustc_hir::{BindingMode, Block, Expr, ExprKind, HirId, Mutability, Node, Pat, PatKind, Path, QPath};
use rustc_lint::LateContext;
use rustc_span::{sym, SyntaxContext};
use std::ops::ControlFlow;

#[expect(clippy::too_many_arguments)]
#[expect(clippy::too_many_lines)]
Expand Down Expand Up @@ -73,7 +75,7 @@ where
}

// `map` won't perform any adjustments.
if !cx.typeck_results().expr_adjustments(some_expr.expr).is_empty() {
if some_expr.expr_contains_ty_adjustments(cx) && expr_has_explicit_ty(cx, expr) {
return None;
}

Expand Down Expand Up @@ -238,6 +240,17 @@ impl<'tcx> SomeExpr<'tcx> {
let sugg = Sugg::hir_with_context(cx, self.expr, ctxt, "..", app);
if self.needs_negated { !sugg } else { sugg }
}

fn expr_contains_ty_adjustments(&self, cx: &LateContext<'tcx>) -> bool {
for_each_expr(self.expr, |ex| {
if cx.typeck_results().expr_adjustments(ex).is_empty() {
ControlFlow::Continue(())
} else {
ControlFlow::Break(true)
}
})
.unwrap_or_default()
}
}

// Try to parse into a recognized `Option` pattern.
Expand Down Expand Up @@ -274,3 +287,60 @@ pub(super) fn try_parse_pattern<'tcx>(
fn is_none_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
is_res_lang_ctor(cx, path_res(cx, peel_blocks(expr)), OptionNone)
}

/// Return `true` if the type of `match` or `if-let` is labeled explicitly.
///
/// Such as the init binding of:
///
/// ```ignore
/// let x: &u8 = match Some(0) { .. };
/// // ^^^^^^^^^^^^^^^^^^^^
/// ```
///
/// Or the return of a function with return ty:
///
/// ```ignore
/// fn foo() -> u8 {
/// match Some(0) { .. }
/// // ^^^^^^^^^^^^^^^^^^^^
/// }
/// ```
fn expr_has_explicit_ty(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
fn check_inner_(cx: &LateContext<'_>, hir_id: HirId) -> bool {
match cx.tcx.parent_hir_node(hir_id) {
Node::LetStmt(let_stmt) => let_stmt.ty.is_some(),
Node::Expr(Expr {
kind: ExprKind::Assign(assignee, ..),
..
}) => {
if let Some(local_id) = path_to_local(assignee)
&& let Node::LetStmt(let_stmt) = cx.tcx.parent_hir_node(local_id)
{
let_stmt.ty.is_some()
} else {
false
}
},
// Assuming `true` because this branch should only be reached from
// tracing the parent of a `Node::Block` that has return value,
// meaning this is either `Fn` or `Const`, that always (?) has a labeled type.
Node::Item(_) | Node::TraitItem(_) | Node::ImplItem(_) |
// If this expr is being returned, then it's type must've been `labeled` on its owner function.
Node::Expr(Expr {
kind: ExprKind::Ret(_), ..
}) => true,
// The implicit return of a block, check if its a fn body or some init block.
Node::Block(Block {
expr: Some(_), hir_id, ..
}) => check_inner_(cx, cx.tcx.parent_hir_id(*hir_id)),
_ => false,
}
}

// Sanity check
assert!(
matches!(expr.kind, ExprKind::Match(..) | ExprKind::If(..)),
"must used on `match` or `if-let` expressions"
);
check_inner_(cx, expr.hir_id)
}
75 changes: 74 additions & 1 deletion tests/ui/manual_map_option_2.fixed
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,14 @@ fn main() {
None => None,
};

// Lint. `s` is captured by reference, so no lifetime issues.
let s = Some(String::new());
// Lint. `s` is captured by reference, so no lifetime issues.
let _ = s.as_ref().map(|x| { if let Some(ref s) = s { (x.clone(), s) } else { panic!() } });
// Don't lint this, type of `s` is coercioned from `&String` to `&str`
let x: Option<(String, &str)> = match &s {
Some(x) => Some({ if let Some(ref s) = s { (x.clone(), s) } else { panic!() } }),
None => None,
};

// Issue #7820
unsafe fn f(x: u32) -> u32 {
Expand All @@ -54,3 +59,71 @@ fn main() {
let _ = Some(0).map(|x| unsafe { f(x) });
let _ = Some(0).map(|x| unsafe { f(x) });
}

mod issue_12659 {
trait DummyTrait {}

fn foo<T: DummyTrait, F: Fn() -> Result<T, ()>>(f: F) {
// Don't lint
let _: Option<Result<Box<dyn DummyTrait>, ()>> = match Some(0) {
Some(_) => Some(match f() {
Ok(res) => Ok(Box::new(res)),
_ => Err(()),
}),
None => None,
};

let _: Option<Box<&[u8]>> = match Some(()) {
Some(_) => Some(Box::new(b"1234")),
None => None,
};

let x = String::new();
let _: Option<Box<&str>> = match Some(()) {
Some(_) => Some(Box::new(&x)),
None => None,
};

let _: Option<&str> = match Some(()) {
Some(_) => Some(&x),
None => None,
};

//~v ERROR: manual implementation of `Option::map`
let _ = Some(0).map(|_| match f() {
Ok(res) => Ok(Box::new(res)),
_ => Err(()),
});
}

fn with_fn_ret(s: &Option<String>) -> Option<(String, &str)> {
// Don't lint, `map` doesn't work as the return type is adjusted.
match s {
Some(x) => Some({ if let Some(ref s) = s { (x.clone(), s) } else { panic!() } }),
None => None,
}
}

fn with_fn_ret_2(s: &Option<String>) -> Option<(String, &str)> {
if true {
// Don't lint, `map` doesn't work as the return type is adjusted.
return match s {
Some(x) => Some({ if let Some(ref s) = s { (x.clone(), s) } else { panic!() } }),
None => None,
};
}
None
}

#[allow(clippy::needless_late_init)]
fn with_fn_ret_3<'a>(s: &'a Option<String>) -> Option<(String, &'a str)> {
let x: Option<(String, &'a str)>;
x = {
match s {
Some(x) => Some({ if let Some(ref s) = s { (x.clone(), s) } else { panic!() } }),
None => None,
}
};
x
}
}
78 changes: 77 additions & 1 deletion tests/ui/manual_map_option_2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,17 @@ fn main() {
None => None,
};

// Lint. `s` is captured by reference, so no lifetime issues.
let s = Some(String::new());
// Lint. `s` is captured by reference, so no lifetime issues.
let _ = match &s {
Some(x) => Some({ if let Some(ref s) = s { (x.clone(), s) } else { panic!() } }),
None => None,
};
// Don't lint this, type of `s` is coercioned from `&String` to `&str`
let x: Option<(String, &str)> = match &s {
Some(x) => Some({ if let Some(ref s) = s { (x.clone(), s) } else { panic!() } }),
None => None,
};

// Issue #7820
unsafe fn f(x: u32) -> u32 {
Expand All @@ -69,3 +74,74 @@ fn main() {
None => None,
};
}

mod issue_12659 {
trait DummyTrait {}

fn foo<T: DummyTrait, F: Fn() -> Result<T, ()>>(f: F) {
// Don't lint
let _: Option<Result<Box<dyn DummyTrait>, ()>> = match Some(0) {
Some(_) => Some(match f() {
Ok(res) => Ok(Box::new(res)),
_ => Err(()),
}),
None => None,
};
Comment on lines +81 to +89
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is that it doesn't catch the adjustment rather than the presence of a dyn, e.g. the same can occur with an unsized coercion

fn main() {
    let x: &[u8; 4] = b"1234";

    // ok
    let _: Option<Box<&[u8]>> = match Some(()) {
        Some(_) => Some(Box::new(x)),
        None => None,
    };

    // error[E0308]: mismatched types
    let _: Option<Box<&[u8]>> = Some(()).map(|_| Box::new(x));
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, you're right, I'll see what I can do with it


let _: Option<Box<&[u8]>> = match Some(()) {
Some(_) => Some(Box::new(b"1234")),
None => None,
};

let x = String::new();
let _: Option<Box<&str>> = match Some(()) {
Some(_) => Some(Box::new(&x)),
None => None,
};

let _: Option<&str> = match Some(()) {
Some(_) => Some(&x),
None => None,
};

//~v ERROR: manual implementation of `Option::map`
let _ = match Some(0) {
Some(_) => Some(match f() {
Ok(res) => Ok(Box::new(res)),
_ => Err(()),
}),
None => None,
};
}

fn with_fn_ret(s: &Option<String>) -> Option<(String, &str)> {
// Don't lint, `map` doesn't work as the return type is adjusted.
match s {
Some(x) => Some({ if let Some(ref s) = s { (x.clone(), s) } else { panic!() } }),
None => None,
}
}

fn with_fn_ret_2(s: &Option<String>) -> Option<(String, &str)> {
if true {
// Don't lint, `map` doesn't work as the return type is adjusted.
return match s {
Some(x) => Some({ if let Some(ref s) = s { (x.clone(), s) } else { panic!() } }),
None => None,
};
}
None
}

#[allow(clippy::needless_late_init)]
fn with_fn_ret_3<'a>(s: &'a Option<String>) -> Option<(String, &'a str)> {
let x: Option<(String, &'a str)>;
x = {
match s {
Some(x) => Some({ if let Some(ref s) = s { (x.clone(), s) } else { panic!() } }),
None => None,
}
};
x
}
}
29 changes: 25 additions & 4 deletions tests/ui/manual_map_option_2.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ LL | | };
| |_____^ help: try: `s.as_ref().map(|x| { if let Some(ref s) = s { (x.clone(), s) } else { panic!() } })`

error: manual implementation of `Option::map`
--> tests/ui/manual_map_option_2.rs:58:17
--> tests/ui/manual_map_option_2.rs:63:17
|
LL | let _ = match Some(0) {
| _________________^
Expand All @@ -42,7 +42,7 @@ LL | | };
| |_________^ help: try: `Some(0).map(|x| f(x))`

error: manual implementation of `Option::map`
--> tests/ui/manual_map_option_2.rs:63:13
--> tests/ui/manual_map_option_2.rs:68:13
|
LL | let _ = match Some(0) {
| _____________^
Expand All @@ -52,7 +52,7 @@ LL | | };
| |_____^ help: try: `Some(0).map(|x| unsafe { f(x) })`

error: manual implementation of `Option::map`
--> tests/ui/manual_map_option_2.rs:67:13
--> tests/ui/manual_map_option_2.rs:72:13
|
LL | let _ = match Some(0) {
| _____________^
Expand All @@ -61,5 +61,26 @@ LL | | None => None,
LL | | };
| |_____^ help: try: `Some(0).map(|x| unsafe { f(x) })`

error: aborting due to 5 previous errors
error: manual implementation of `Option::map`
--> tests/ui/manual_map_option_2.rs:108:17
|
LL | let _ = match Some(0) {
| _________________^
LL | | Some(_) => Some(match f() {
LL | | Ok(res) => Ok(Box::new(res)),
LL | | _ => Err(()),
LL | | }),
LL | | None => None,
LL | | };
| |_________^
|
help: try
|
LL ~ let _ = Some(0).map(|_| match f() {
LL + Ok(res) => Ok(Box::new(res)),
LL + _ => Err(()),
LL ~ });
|

error: aborting due to 6 previous errors