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

Fix manual_map with unsafe functions #7968

Merged
merged 3 commits into from
Nov 17, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 68 additions & 27 deletions clippy_lints/src/manual_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::{map_unit_fn::OPTION_MAP_UNIT_FN, matches::MATCH_AS_REF};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::higher::IfLetOrMatch;
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
use clippy_utils::ty::{is_type_diagnostic_item, peel_mid_ty_refs_is_mutable};
use clippy_utils::ty::{is_type_diagnostic_item, peel_mid_ty_refs_is_mutable, type_is_unsafe_function};
use clippy_utils::{
can_move_expr_to_closure, in_constant, is_else_clause, is_lang_ctor, is_lint_allowed, path_to_local_id,
peel_hir_expr_refs, peel_hir_expr_while, CaptureKind,
Expand All @@ -11,7 +11,8 @@ use rustc_ast::util::parser::PREC_POSTFIX;
use rustc_errors::Applicability;
use rustc_hir::LangItem::{OptionNone, OptionSome};
use rustc_hir::{
def::Res, Arm, BindingAnnotation, Block, Expr, ExprKind, HirId, Mutability, Pat, PatKind, Path, QPath,
def::Res, Arm, BindingAnnotation, Block, BlockCheckMode, Expr, ExprKind, HirId, Mutability, Pat, PatKind, Path,
QPath, UnsafeSource,
};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
Expand Down Expand Up @@ -93,20 +94,20 @@ impl LateLintPass<'_> for ManualMap {
return;
}

let some_expr = match get_some_expr(cx, some_expr, expr_ctxt) {
let some_expr = match get_some_expr(cx, some_expr, false, expr_ctxt) {
Some(expr) => expr,
None => return,
};

// These two lints will go back and forth with each other.
if cx.typeck_results().expr_ty(some_expr) == cx.tcx.types.unit
if cx.typeck_results().expr_ty(some_expr.expr) == cx.tcx.types.unit
&& !is_lint_allowed(cx, OPTION_MAP_UNIT_FN, expr.hir_id)
{
return;
}

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

Expand All @@ -120,7 +121,7 @@ impl LateLintPass<'_> for ManualMap {
None => "",
};

match can_move_expr_to_closure(cx, some_expr) {
match can_move_expr_to_closure(cx, some_expr.expr) {
Some(captures) => {
// Check if captures the closure will need conflict with borrows made in the scrutinee.
// TODO: check all the references made in the scrutinee expression. This will require interacting
Expand Down Expand Up @@ -158,12 +159,14 @@ impl LateLintPass<'_> for ManualMap {
};

let body_str = if let PatKind::Binding(annotation, id, some_binding, None) = some_pat.kind {
match can_pass_as_func(cx, id, some_expr) {
Some(func) if func.span.ctxt() == some_expr.span.ctxt() => {
if_chain! {
if !some_expr.needs_unsafe_block;
if let Some(func) = can_pass_as_func(cx, id, some_expr.expr);
if func.span.ctxt() == some_expr.expr.span.ctxt();
then {
snippet_with_applicability(cx, func.span, "..", &mut app).into_owned()
},
_ => {
if path_to_local_id(some_expr, id)
} else {
if path_to_local_id(some_expr.expr, id)
&& !is_lint_allowed(cx, MATCH_AS_REF, expr.hir_id)
&& binding_ref.is_some()
{
Expand All @@ -176,21 +179,38 @@ impl LateLintPass<'_> for ManualMap {
} else {
""
};
format!(
"|{}{}| {}",
annotation,
some_binding,
snippet_with_context(cx, some_expr.span, expr_ctxt, "..", &mut app).0
)
},
if some_expr.needs_unsafe_block {
format!(
"|{}{}| unsafe {{ {} }}",
annotation,
some_binding,
snippet_with_context(cx, some_expr.expr.span, expr_ctxt, "..", &mut app).0
)
} else {
format!(
"|{}{}| {}",
annotation,
some_binding,
snippet_with_context(cx, some_expr.expr.span, expr_ctxt, "..", &mut app).0
)
}
}
Jarcho marked this conversation as resolved.
Show resolved Hide resolved
}
} else if !is_wild_none && explicit_ref.is_none() {
// TODO: handle explicit reference annotations.
format!(
"|{}| {}",
snippet_with_context(cx, some_pat.span, expr_ctxt, "..", &mut app).0,
snippet_with_context(cx, some_expr.span, expr_ctxt, "..", &mut app).0
)
if some_expr.needs_unsafe_block {
format!(
"|{}| unsafe {{ {} }}",
snippet_with_context(cx, some_pat.span, expr_ctxt, "..", &mut app).0,
snippet_with_context(cx, some_expr.expr.span, expr_ctxt, "..", &mut app).0
)
} else {
format!(
"|{}| {}",
snippet_with_context(cx, some_pat.span, expr_ctxt, "..", &mut app).0,
snippet_with_context(cx, some_expr.expr.span, expr_ctxt, "..", &mut app).0
)
}
Jarcho marked this conversation as resolved.
Show resolved Hide resolved
} else {
// Refutable bindings and mixed reference annotations can't be handled by `map`.
return;
Expand All @@ -217,7 +237,9 @@ impl LateLintPass<'_> for ManualMap {
fn can_pass_as_func(cx: &LateContext<'tcx>, binding: HirId, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
match expr.kind {
ExprKind::Call(func, [arg])
if path_to_local_id(arg, binding) && cx.typeck_results().expr_adjustments(arg).is_empty() =>
if path_to_local_id(arg, binding)
&& cx.typeck_results().expr_adjustments(arg).is_empty()
&& !type_is_unsafe_function(cx, cx.typeck_results().expr_ty(func).peel_refs()) =>
{
Some(func)
},
Expand All @@ -237,6 +259,11 @@ enum OptionPat<'a> {
},
}

struct SomeExpr<'tcx> {
expr: &'tcx Expr<'tcx>,
needs_unsafe_block: bool,
}

// Try to parse into a recognized `Option` pattern.
// i.e. `_`, `None`, `Some(..)`, or a reference to any of those.
fn try_parse_pattern(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, ctxt: SyntaxContext) -> Option<OptionPat<'tcx>> {
Expand All @@ -257,7 +284,12 @@ fn try_parse_pattern(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, ctxt: SyntaxCon
}

// Checks for an expression wrapped by the `Some` constructor. Returns the contained expression.
fn get_some_expr(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, ctxt: SyntaxContext) -> Option<&'tcx Expr<'tcx>> {
fn get_some_expr(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'_>,
needs_unsafe_block: bool,
ctxt: SyntaxContext,
) -> Option<SomeExpr<'tcx>> {
// TODO: Allow more complex expressions.
match expr.kind {
ExprKind::Call(
Expand All @@ -266,15 +298,24 @@ fn get_some_expr(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, ctxt: SyntaxConte
..
},
[arg],
) if ctxt == expr.span.ctxt() && is_lang_ctor(cx, qpath, OptionSome) => Some(arg),
) if ctxt == expr.span.ctxt() && is_lang_ctor(cx, qpath, OptionSome) => Some(SomeExpr {
expr: arg,
needs_unsafe_block,
}),
ExprKind::Block(
Block {
stmts: [],
expr: Some(expr),
rules,
..
},
_,
) => get_some_expr(cx, expr, ctxt),
) => get_some_expr(
cx,
expr,
needs_unsafe_block || *rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided),
ctxt,
),
_ => None,
}
}
Expand Down
8 changes: 8 additions & 0 deletions tests/ui/manual_map_option_2.fixed
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,12 @@ fn main() {
let _ = s.as_ref().map(|x| {
if let Some(ref s) = s { (x.clone(), s) } else { panic!() }
});

unsafe fn f(x: u32) -> u32 {
x
}
unsafe {
let _ = Some(0).map(|x| f(x));
}
let _ = Some(0).map(|x| unsafe { f(x) });
}
14 changes: 14 additions & 0 deletions tests/ui/manual_map_option_2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,18 @@ fn main() {
}),
None => None,
};

unsafe fn f(x: u32) -> u32 {
x
}
unsafe {
let _ = match Some(0) {
Some(x) => Some(f(x)),
None => None,
};
}
let _ = match Some(0) {
Some(x) => unsafe { Some(f(x)) },
None => None,
};
}
Jarcho marked this conversation as resolved.
Show resolved Hide resolved
22 changes: 21 additions & 1 deletion tests/ui/manual_map_option_2.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,25 @@ LL + if let Some(ref s) = s { (x.clone(), s) } else { panic!() }
LL ~ });
|

error: aborting due to 2 previous errors
error: manual implementation of `Option::map`
--> $DIR/manual_map_option_2.rs:61:17
|
LL | let _ = match Some(0) {
| _________________^
LL | | Some(x) => Some(f(x)),
LL | | None => None,
LL | | };
| |_________^ help: try this: `Some(0).map(|x| f(x))`

error: manual implementation of `Option::map`
--> $DIR/manual_map_option_2.rs:66:13
|
LL | let _ = match Some(0) {
| _____________^
LL | | Some(x) => unsafe { Some(f(x)) },
LL | | None => None,
LL | | };
| |_____^ help: try this: `Some(0).map(|x| unsafe { f(x) })`

error: aborting due to 4 previous errors