move expect_fun_call to its own module
This commit is contained in:
parent
f49349bf33
commit
c711de28ee
2 changed files with 204 additions and 201 deletions
199
clippy_lints/src/methods/expect_fun_call.rs
Normal file
199
clippy_lints/src/methods/expect_fun_call.rs
Normal file
|
@ -0,0 +1,199 @@
|
||||||
|
use crate::utils::{is_expn_of, is_type_diagnostic_item, snippet, snippet_with_applicability, span_lint_and_sugg};
|
||||||
|
use if_chain::if_chain;
|
||||||
|
use rustc_errors::Applicability;
|
||||||
|
use rustc_hir as hir;
|
||||||
|
use rustc_lint::LateContext;
|
||||||
|
use rustc_middle::ty;
|
||||||
|
use rustc_span::source_map::Span;
|
||||||
|
use rustc_span::symbol::sym;
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use super::EXPECT_FUN_CALL;
|
||||||
|
|
||||||
|
/// Checks for the `EXPECT_FUN_CALL` lint.
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
|
pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, method_span: Span, name: &str, args: &[hir::Expr<'_>]) {
|
||||||
|
// Strip `&`, `as_ref()` and `as_str()` off `arg` until we're left with either a `String` or
|
||||||
|
// `&str`
|
||||||
|
fn get_arg_root<'a>(cx: &LateContext<'_>, arg: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> {
|
||||||
|
let mut arg_root = arg;
|
||||||
|
loop {
|
||||||
|
arg_root = match &arg_root.kind {
|
||||||
|
hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, expr) => expr,
|
||||||
|
hir::ExprKind::MethodCall(method_name, _, call_args, _) => {
|
||||||
|
if call_args.len() == 1
|
||||||
|
&& (method_name.ident.name == sym::as_str || method_name.ident.name == sym!(as_ref))
|
||||||
|
&& {
|
||||||
|
let arg_type = cx.typeck_results().expr_ty(&call_args[0]);
|
||||||
|
let base_type = arg_type.peel_refs();
|
||||||
|
*base_type.kind() == ty::Str || is_type_diagnostic_item(cx, base_type, sym::string_type)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
&call_args[0]
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => break,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
arg_root
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only `&'static str` or `String` can be used directly in the `panic!`. Other types should be
|
||||||
|
// converted to string.
|
||||||
|
fn requires_to_string(cx: &LateContext<'_>, arg: &hir::Expr<'_>) -> bool {
|
||||||
|
let arg_ty = cx.typeck_results().expr_ty(arg);
|
||||||
|
if is_type_diagnostic_item(cx, arg_ty, sym::string_type) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if let ty::Ref(_, ty, ..) = arg_ty.kind() {
|
||||||
|
if *ty.kind() == ty::Str && can_be_static_str(cx, arg) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if an expression could have type `&'static str`, knowing that it
|
||||||
|
// has type `&str` for some lifetime.
|
||||||
|
fn can_be_static_str(cx: &LateContext<'_>, arg: &hir::Expr<'_>) -> bool {
|
||||||
|
match arg.kind {
|
||||||
|
hir::ExprKind::Lit(_) => true,
|
||||||
|
hir::ExprKind::Call(fun, _) => {
|
||||||
|
if let hir::ExprKind::Path(ref p) = fun.kind {
|
||||||
|
match cx.qpath_res(p, fun.hir_id) {
|
||||||
|
hir::def::Res::Def(hir::def::DefKind::Fn | hir::def::DefKind::AssocFn, def_id) => matches!(
|
||||||
|
cx.tcx.fn_sig(def_id).output().skip_binder().kind(),
|
||||||
|
ty::Ref(ty::ReStatic, ..)
|
||||||
|
),
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
hir::ExprKind::MethodCall(..) => {
|
||||||
|
cx.typeck_results()
|
||||||
|
.type_dependent_def_id(arg.hir_id)
|
||||||
|
.map_or(false, |method_id| {
|
||||||
|
matches!(
|
||||||
|
cx.tcx.fn_sig(method_id).output().skip_binder().kind(),
|
||||||
|
ty::Ref(ty::ReStatic, ..)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
hir::ExprKind::Path(ref p) => matches!(
|
||||||
|
cx.qpath_res(p, arg.hir_id),
|
||||||
|
hir::def::Res::Def(hir::def::DefKind::Const | hir::def::DefKind::Static, _)
|
||||||
|
),
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_format_arg_snippet(
|
||||||
|
cx: &LateContext<'_>,
|
||||||
|
a: &hir::Expr<'_>,
|
||||||
|
applicability: &mut Applicability,
|
||||||
|
) -> Vec<String> {
|
||||||
|
if_chain! {
|
||||||
|
if let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, ref format_arg) = a.kind;
|
||||||
|
if let hir::ExprKind::Match(ref format_arg_expr, _, _) = format_arg.kind;
|
||||||
|
if let hir::ExprKind::Tup(ref format_arg_expr_tup) = format_arg_expr.kind;
|
||||||
|
|
||||||
|
then {
|
||||||
|
format_arg_expr_tup
|
||||||
|
.iter()
|
||||||
|
.map(|a| snippet_with_applicability(cx, a.span, "..", applicability).into_owned())
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_call(node: &hir::ExprKind<'_>) -> bool {
|
||||||
|
match node {
|
||||||
|
hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, expr) => {
|
||||||
|
is_call(&expr.kind)
|
||||||
|
},
|
||||||
|
hir::ExprKind::Call(..)
|
||||||
|
| hir::ExprKind::MethodCall(..)
|
||||||
|
// These variants are debatable or require further examination
|
||||||
|
| hir::ExprKind::If(..)
|
||||||
|
| hir::ExprKind::Match(..)
|
||||||
|
| hir::ExprKind::Block{ .. } => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if args.len() != 2 || name != "expect" || !is_call(&args[1].kind) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let receiver_type = cx.typeck_results().expr_ty_adjusted(&args[0]);
|
||||||
|
let closure_args = if is_type_diagnostic_item(cx, receiver_type, sym::option_type) {
|
||||||
|
"||"
|
||||||
|
} else if is_type_diagnostic_item(cx, receiver_type, sym::result_type) {
|
||||||
|
"|_|"
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let arg_root = get_arg_root(cx, &args[1]);
|
||||||
|
|
||||||
|
let span_replace_word = method_span.with_hi(expr.span.hi());
|
||||||
|
|
||||||
|
let mut applicability = Applicability::MachineApplicable;
|
||||||
|
|
||||||
|
//Special handling for `format!` as arg_root
|
||||||
|
if_chain! {
|
||||||
|
if let hir::ExprKind::Block(block, None) = &arg_root.kind;
|
||||||
|
if block.stmts.len() == 1;
|
||||||
|
if let hir::StmtKind::Local(local) = &block.stmts[0].kind;
|
||||||
|
if let Some(arg_root) = &local.init;
|
||||||
|
if let hir::ExprKind::Call(ref inner_fun, ref inner_args) = arg_root.kind;
|
||||||
|
if is_expn_of(inner_fun.span, "format").is_some() && inner_args.len() == 1;
|
||||||
|
if let hir::ExprKind::Call(_, format_args) = &inner_args[0].kind;
|
||||||
|
then {
|
||||||
|
let fmt_spec = &format_args[0];
|
||||||
|
let fmt_args = &format_args[1];
|
||||||
|
|
||||||
|
let mut args = vec![snippet(cx, fmt_spec.span, "..").into_owned()];
|
||||||
|
|
||||||
|
args.extend(generate_format_arg_snippet(cx, fmt_args, &mut applicability));
|
||||||
|
|
||||||
|
let sugg = args.join(", ");
|
||||||
|
|
||||||
|
span_lint_and_sugg(
|
||||||
|
cx,
|
||||||
|
EXPECT_FUN_CALL,
|
||||||
|
span_replace_word,
|
||||||
|
&format!("use of `{}` followed by a function call", name),
|
||||||
|
"try this",
|
||||||
|
format!("unwrap_or_else({} panic!({}))", closure_args, sugg),
|
||||||
|
applicability,
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut arg_root_snippet: Cow<'_, _> = snippet_with_applicability(cx, arg_root.span, "..", &mut applicability);
|
||||||
|
if requires_to_string(cx, arg_root) {
|
||||||
|
arg_root_snippet.to_mut().push_str(".to_string()");
|
||||||
|
}
|
||||||
|
|
||||||
|
span_lint_and_sugg(
|
||||||
|
cx,
|
||||||
|
EXPECT_FUN_CALL,
|
||||||
|
span_replace_word,
|
||||||
|
&format!("use of `{}` followed by a function call", name),
|
||||||
|
"try this",
|
||||||
|
format!(
|
||||||
|
"unwrap_or_else({} {{ panic!(\"{{}}\", {}) }})",
|
||||||
|
closure_args, arg_root_snippet
|
||||||
|
),
|
||||||
|
applicability,
|
||||||
|
);
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ mod bind_instead_of_map;
|
||||||
mod bytes_nth;
|
mod bytes_nth;
|
||||||
mod clone_on_copy;
|
mod clone_on_copy;
|
||||||
mod clone_on_ref_ptr;
|
mod clone_on_ref_ptr;
|
||||||
|
mod expect_fun_call;
|
||||||
mod expect_used;
|
mod expect_used;
|
||||||
mod filetype_is_file;
|
mod filetype_is_file;
|
||||||
mod filter_flat_map;
|
mod filter_flat_map;
|
||||||
|
@ -50,8 +51,6 @@ mod useless_asref;
|
||||||
mod wrong_self_convention;
|
mod wrong_self_convention;
|
||||||
mod zst_offset;
|
mod zst_offset;
|
||||||
|
|
||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
use bind_instead_of_map::BindInsteadOfMap;
|
use bind_instead_of_map::BindInsteadOfMap;
|
||||||
use if_chain::if_chain;
|
use if_chain::if_chain;
|
||||||
use rustc_ast::ast;
|
use rustc_ast::ast;
|
||||||
|
@ -68,10 +67,9 @@ use rustc_span::symbol::{sym, Symbol, SymbolStr};
|
||||||
use rustc_typeck::hir_ty_to_ty;
|
use rustc_typeck::hir_ty_to_ty;
|
||||||
|
|
||||||
use crate::utils::{
|
use crate::utils::{
|
||||||
contains_return, contains_ty, get_trait_def_id, implements_trait, in_macro, is_copy, is_expn_of,
|
contains_return, contains_ty, get_trait_def_id, implements_trait, in_macro, is_copy, is_type_diagnostic_item,
|
||||||
is_type_diagnostic_item, iter_input_pats, match_def_path, match_qpath, method_calls, method_chain_args, paths,
|
iter_input_pats, match_def_path, match_qpath, method_calls, method_chain_args, paths, return_ty,
|
||||||
return_ty, single_segment_path, snippet, snippet_with_applicability, span_lint, span_lint_and_help,
|
single_segment_path, snippet_with_applicability, span_lint, span_lint_and_help, span_lint_and_sugg, SpanlessEq,
|
||||||
span_lint_and_sugg, SpanlessEq,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
declare_clippy_lint! {
|
declare_clippy_lint! {
|
||||||
|
@ -1779,7 +1777,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
|
||||||
},
|
},
|
||||||
hir::ExprKind::MethodCall(ref method_call, ref method_span, ref args, _) => {
|
hir::ExprKind::MethodCall(ref method_call, ref method_span, ref args, _) => {
|
||||||
or_fun_call::check(cx, expr, *method_span, &method_call.ident.as_str(), args);
|
or_fun_call::check(cx, expr, *method_span, &method_call.ident.as_str(), args);
|
||||||
lint_expect_fun_call(cx, expr, *method_span, &method_call.ident.as_str(), args);
|
expect_fun_call::check(cx, expr, *method_span, &method_call.ident.as_str(), args);
|
||||||
|
|
||||||
let self_ty = cx.typeck_results().expr_ty_adjusted(&args[0]);
|
let self_ty = cx.typeck_results().expr_ty_adjusted(&args[0]);
|
||||||
if args.len() == 1 && method_call.ident.name == sym::clone {
|
if args.len() == 1 && method_call.ident.name == sym::clone {
|
||||||
|
@ -1973,200 +1971,6 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
|
||||||
extract_msrv_attr!(LateContext);
|
extract_msrv_attr!(LateContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks for the `EXPECT_FUN_CALL` lint.
|
|
||||||
#[allow(clippy::too_many_lines)]
|
|
||||||
fn lint_expect_fun_call(
|
|
||||||
cx: &LateContext<'_>,
|
|
||||||
expr: &hir::Expr<'_>,
|
|
||||||
method_span: Span,
|
|
||||||
name: &str,
|
|
||||||
args: &[hir::Expr<'_>],
|
|
||||||
) {
|
|
||||||
// Strip `&`, `as_ref()` and `as_str()` off `arg` until we're left with either a `String` or
|
|
||||||
// `&str`
|
|
||||||
fn get_arg_root<'a>(cx: &LateContext<'_>, arg: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> {
|
|
||||||
let mut arg_root = arg;
|
|
||||||
loop {
|
|
||||||
arg_root = match &arg_root.kind {
|
|
||||||
hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, expr) => expr,
|
|
||||||
hir::ExprKind::MethodCall(method_name, _, call_args, _) => {
|
|
||||||
if call_args.len() == 1
|
|
||||||
&& (method_name.ident.name == sym::as_str || method_name.ident.name == sym!(as_ref))
|
|
||||||
&& {
|
|
||||||
let arg_type = cx.typeck_results().expr_ty(&call_args[0]);
|
|
||||||
let base_type = arg_type.peel_refs();
|
|
||||||
*base_type.kind() == ty::Str || is_type_diagnostic_item(cx, base_type, sym::string_type)
|
|
||||||
}
|
|
||||||
{
|
|
||||||
&call_args[0]
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => break,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
arg_root
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only `&'static str` or `String` can be used directly in the `panic!`. Other types should be
|
|
||||||
// converted to string.
|
|
||||||
fn requires_to_string(cx: &LateContext<'_>, arg: &hir::Expr<'_>) -> bool {
|
|
||||||
let arg_ty = cx.typeck_results().expr_ty(arg);
|
|
||||||
if is_type_diagnostic_item(cx, arg_ty, sym::string_type) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if let ty::Ref(_, ty, ..) = arg_ty.kind() {
|
|
||||||
if *ty.kind() == ty::Str && can_be_static_str(cx, arg) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if an expression could have type `&'static str`, knowing that it
|
|
||||||
// has type `&str` for some lifetime.
|
|
||||||
fn can_be_static_str(cx: &LateContext<'_>, arg: &hir::Expr<'_>) -> bool {
|
|
||||||
match arg.kind {
|
|
||||||
hir::ExprKind::Lit(_) => true,
|
|
||||||
hir::ExprKind::Call(fun, _) => {
|
|
||||||
if let hir::ExprKind::Path(ref p) = fun.kind {
|
|
||||||
match cx.qpath_res(p, fun.hir_id) {
|
|
||||||
hir::def::Res::Def(hir::def::DefKind::Fn | hir::def::DefKind::AssocFn, def_id) => matches!(
|
|
||||||
cx.tcx.fn_sig(def_id).output().skip_binder().kind(),
|
|
||||||
ty::Ref(ty::ReStatic, ..)
|
|
||||||
),
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
hir::ExprKind::MethodCall(..) => {
|
|
||||||
cx.typeck_results()
|
|
||||||
.type_dependent_def_id(arg.hir_id)
|
|
||||||
.map_or(false, |method_id| {
|
|
||||||
matches!(
|
|
||||||
cx.tcx.fn_sig(method_id).output().skip_binder().kind(),
|
|
||||||
ty::Ref(ty::ReStatic, ..)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
hir::ExprKind::Path(ref p) => matches!(
|
|
||||||
cx.qpath_res(p, arg.hir_id),
|
|
||||||
hir::def::Res::Def(hir::def::DefKind::Const | hir::def::DefKind::Static, _)
|
|
||||||
),
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_format_arg_snippet(
|
|
||||||
cx: &LateContext<'_>,
|
|
||||||
a: &hir::Expr<'_>,
|
|
||||||
applicability: &mut Applicability,
|
|
||||||
) -> Vec<String> {
|
|
||||||
if_chain! {
|
|
||||||
if let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, ref format_arg) = a.kind;
|
|
||||||
if let hir::ExprKind::Match(ref format_arg_expr, _, _) = format_arg.kind;
|
|
||||||
if let hir::ExprKind::Tup(ref format_arg_expr_tup) = format_arg_expr.kind;
|
|
||||||
|
|
||||||
then {
|
|
||||||
format_arg_expr_tup
|
|
||||||
.iter()
|
|
||||||
.map(|a| snippet_with_applicability(cx, a.span, "..", applicability).into_owned())
|
|
||||||
.collect()
|
|
||||||
} else {
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_call(node: &hir::ExprKind<'_>) -> bool {
|
|
||||||
match node {
|
|
||||||
hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, expr) => {
|
|
||||||
is_call(&expr.kind)
|
|
||||||
},
|
|
||||||
hir::ExprKind::Call(..)
|
|
||||||
| hir::ExprKind::MethodCall(..)
|
|
||||||
// These variants are debatable or require further examination
|
|
||||||
| hir::ExprKind::If(..)
|
|
||||||
| hir::ExprKind::Match(..)
|
|
||||||
| hir::ExprKind::Block{ .. } => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if args.len() != 2 || name != "expect" || !is_call(&args[1].kind) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let receiver_type = cx.typeck_results().expr_ty_adjusted(&args[0]);
|
|
||||||
let closure_args = if is_type_diagnostic_item(cx, receiver_type, sym::option_type) {
|
|
||||||
"||"
|
|
||||||
} else if is_type_diagnostic_item(cx, receiver_type, sym::result_type) {
|
|
||||||
"|_|"
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let arg_root = get_arg_root(cx, &args[1]);
|
|
||||||
|
|
||||||
let span_replace_word = method_span.with_hi(expr.span.hi());
|
|
||||||
|
|
||||||
let mut applicability = Applicability::MachineApplicable;
|
|
||||||
|
|
||||||
//Special handling for `format!` as arg_root
|
|
||||||
if_chain! {
|
|
||||||
if let hir::ExprKind::Block(block, None) = &arg_root.kind;
|
|
||||||
if block.stmts.len() == 1;
|
|
||||||
if let hir::StmtKind::Local(local) = &block.stmts[0].kind;
|
|
||||||
if let Some(arg_root) = &local.init;
|
|
||||||
if let hir::ExprKind::Call(ref inner_fun, ref inner_args) = arg_root.kind;
|
|
||||||
if is_expn_of(inner_fun.span, "format").is_some() && inner_args.len() == 1;
|
|
||||||
if let hir::ExprKind::Call(_, format_args) = &inner_args[0].kind;
|
|
||||||
then {
|
|
||||||
let fmt_spec = &format_args[0];
|
|
||||||
let fmt_args = &format_args[1];
|
|
||||||
|
|
||||||
let mut args = vec![snippet(cx, fmt_spec.span, "..").into_owned()];
|
|
||||||
|
|
||||||
args.extend(generate_format_arg_snippet(cx, fmt_args, &mut applicability));
|
|
||||||
|
|
||||||
let sugg = args.join(", ");
|
|
||||||
|
|
||||||
span_lint_and_sugg(
|
|
||||||
cx,
|
|
||||||
EXPECT_FUN_CALL,
|
|
||||||
span_replace_word,
|
|
||||||
&format!("use of `{}` followed by a function call", name),
|
|
||||||
"try this",
|
|
||||||
format!("unwrap_or_else({} panic!({}))", closure_args, sugg),
|
|
||||||
applicability,
|
|
||||||
);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut arg_root_snippet: Cow<'_, _> = snippet_with_applicability(cx, arg_root.span, "..", &mut applicability);
|
|
||||||
if requires_to_string(cx, arg_root) {
|
|
||||||
arg_root_snippet.to_mut().push_str(".to_string()");
|
|
||||||
}
|
|
||||||
|
|
||||||
span_lint_and_sugg(
|
|
||||||
cx,
|
|
||||||
EXPECT_FUN_CALL,
|
|
||||||
span_replace_word,
|
|
||||||
&format!("use of `{}` followed by a function call", name),
|
|
||||||
"try this",
|
|
||||||
format!(
|
|
||||||
"unwrap_or_else({} {{ panic!(\"{{}}\", {}) }})",
|
|
||||||
closure_args, arg_root_snippet
|
|
||||||
),
|
|
||||||
applicability,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn derefs_to_slice<'tcx>(
|
fn derefs_to_slice<'tcx>(
|
||||||
cx: &LateContext<'tcx>,
|
cx: &LateContext<'tcx>,
|
||||||
expr: &'tcx hir::Expr<'tcx>,
|
expr: &'tcx hir::Expr<'tcx>,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue