Skip to content

Commit 8a74d33

Browse files
committed
Add explicit_auto_deref lint
1 parent c107c97 commit 8a74d33

21 files changed

+954
-53
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3400,6 +3400,7 @@ Released 2018-09-13
34003400
[`expect_fun_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#expect_fun_call
34013401
[`expect_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#expect_used
34023402
[`expl_impl_clone_on_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#expl_impl_clone_on_copy
3403+
[`explicit_auto_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_auto_deref
34033404
[`explicit_counter_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_counter_loop
34043405
[`explicit_deref_methods`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_deref_methods
34053406
[`explicit_into_iter_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_into_iter_loop

clippy_lints/src/dereference.rs

Lines changed: 303 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
22
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
33
use clippy_utils::sugg::has_enclosing_paren;
4-
use clippy_utils::ty::peel_mid_ty_refs;
5-
use clippy_utils::{get_parent_expr, get_parent_node, is_lint_allowed, path_to_local};
4+
use clippy_utils::ty::{expr_sig, peel_mid_ty_refs, variant_of_res};
5+
use clippy_utils::{
6+
get_parent_expr, get_parent_node, is_lint_allowed, path_to_local, peel_hir_ty_refs, walk_to_expr_usage,
7+
};
68
use rustc_ast::util::parser::{PREC_POSTFIX, PREC_PREFIX};
79
use rustc_data_structures::fx::FxIndexMap;
810
use rustc_errors::Applicability;
911
use rustc_hir::{
10-
BindingAnnotation, Body, BodyId, BorrowKind, Destination, Expr, ExprKind, HirId, MatchSource, Mutability, Node,
11-
Pat, PatKind, UnOp,
12+
self as hir, BindingAnnotation, Body, BodyId, BorrowKind, Destination, Expr, ExprKind, FnRetTy, GenericArg, HirId,
13+
ImplItem, ImplItemKind, Item, ItemKind, Local, MatchSource, Mutability, Node, Pat, PatKind, Path, QPath, TraitItem,
14+
TraitItemKind, TyKind, UnOp,
1215
};
1316
use rustc_lint::{LateContext, LateLintPass};
1417
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability};
15-
use rustc_middle::ty::{self, Ty, TyCtxt, TypeckResults};
18+
use rustc_middle::ty::{self, Ty, TyCtxt, TypeFoldable, TypeckResults};
1619
use rustc_session::{declare_tool_lint, impl_lint_pass};
1720
use rustc_span::{symbol::sym, Span};
1821

@@ -104,10 +107,34 @@ declare_clippy_lint! {
104107
"`ref` binding to a reference"
105108
}
106109

110+
declare_clippy_lint! {
111+
/// ### What it does
112+
/// Checks for dereferencing expressions which would be covered by auto-deref.
113+
///
114+
/// ### Why is this bad?
115+
/// This unnecessarily complicates the code.
116+
///
117+
/// ### Example
118+
/// ```rust
119+
/// let x = String::new();
120+
/// let y: &str = &*x;
121+
/// ```
122+
/// Use instead:
123+
/// ```rust
124+
/// let x = String::new();
125+
/// let y: &str = &x;
126+
/// ```
127+
#[clippy::version = "1.60.0"]
128+
pub EXPLICIT_AUTO_DEREF,
129+
complexity,
130+
"dereferencing when the compiler would automatically dereference"
131+
}
132+
107133
impl_lint_pass!(Dereferencing => [
108134
EXPLICIT_DEREF_METHODS,
109135
NEEDLESS_BORROW,
110136
REF_BINDING_TO_REFERENCE,
137+
EXPLICIT_AUTO_DEREF,
111138
]);
112139

113140
#[derive(Default)]
@@ -152,6 +179,11 @@ enum State {
152179
required_precedence: i8,
153180
msg: &'static str,
154181
},
182+
ExplicitDeref {
183+
deref_span: Span,
184+
deref_hir_id: HirId,
185+
},
186+
Borrow,
155187
}
156188

157189
// A reference operation considered by this lint pass
@@ -305,6 +337,14 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing {
305337
hir_id: expr.hir_id,
306338
},
307339
));
340+
} else if is_stable_auto_deref_position(cx, expr) {
341+
self.state = Some((
342+
State::Borrow,
343+
StateData {
344+
span: expr.span,
345+
hir_id: expr.hir_id,
346+
},
347+
));
308348
}
309349
},
310350
_ => (),
@@ -354,6 +394,18 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing {
354394
data,
355395
));
356396
},
397+
(Some((State::Borrow, data)), RefOp::Deref) => {
398+
self.state = Some((
399+
State::ExplicitDeref {
400+
deref_span: expr.span,
401+
deref_hir_id: expr.hir_id,
402+
},
403+
data,
404+
));
405+
},
406+
(state @ Some((State::ExplicitDeref { .. }, _)), RefOp::Deref) => {
407+
self.state = state;
408+
},
357409

358410
(Some((state, data)), _) => report(cx, expr, state, data),
359411
}
@@ -596,8 +648,230 @@ fn find_adjustments<'tcx>(
596648
}
597649
}
598650

651+
// Checks if the expression for the given id occurs in a position which auto dereferencing applies.
652+
// Note that the target type must not be inferred in a way that may cause auto-deref to select a
653+
// different type, nor may the position be the result of a macro expansion.
654+
//
655+
// e.g. the following should not linted
656+
// macro_rules! foo { ($e:expr) => { let x: &str = $e; }}
657+
// foo!(&*String::new());
658+
// fn foo<T>(_: &T) {}
659+
// foo(&*String::new())
660+
fn is_stable_auto_deref_position<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> bool {
661+
walk_to_expr_usage(cx, e, |node, child_id| match node {
662+
Node::Local(&Local { ty: Some(ty), .. }) => Some(is_binding_ty_auto_deref_stable(ty)),
663+
Node::Item(&Item {
664+
kind: ItemKind::Static(..) | ItemKind::Const(..),
665+
..
666+
})
667+
| Node::TraitItem(&TraitItem {
668+
kind: TraitItemKind::Const(..),
669+
..
670+
})
671+
| Node::ImplItem(&ImplItem {
672+
kind: ImplItemKind::Const(..),
673+
..
674+
}) => Some(true),
675+
676+
Node::Item(&Item {
677+
kind: ItemKind::Fn(..),
678+
def_id,
679+
..
680+
})
681+
| Node::TraitItem(&TraitItem {
682+
kind: TraitItemKind::Fn(..),
683+
def_id,
684+
..
685+
})
686+
| Node::ImplItem(&ImplItem {
687+
kind: ImplItemKind::Fn(..),
688+
def_id,
689+
..
690+
}) => {
691+
let output = cx.tcx.fn_sig(def_id.to_def_id()).skip_binder().output();
692+
Some(!(output.has_placeholders() || output.has_opaque_types()))
693+
},
694+
695+
Node::Expr(e) => match e.kind {
696+
ExprKind::Ret(_) => {
697+
let output = cx
698+
.tcx
699+
.fn_sig(cx.tcx.hir().body_owner_def_id(cx.enclosing_body.unwrap()))
700+
.skip_binder()
701+
.output();
702+
Some(!(output.has_placeholders() || output.has_opaque_types()))
703+
},
704+
ExprKind::Call(func, args) => Some(
705+
args.iter()
706+
.position(|arg| arg.hir_id == child_id)
707+
.zip(expr_sig(cx, func))
708+
.and_then(|(i, sig)| sig.input_with_hir(i))
709+
.map_or(false, |(hir_ty, ty)| match hir_ty {
710+
// Type inference for closures can depend on how they're called. Only go by the explicit
711+
// types here.
712+
Some(ty) => is_binding_ty_auto_deref_stable(ty),
713+
None => is_param_auto_deref_stable(ty.skip_binder()),
714+
}),
715+
),
716+
ExprKind::MethodCall(_, [_, args @ ..], _) => {
717+
let id = cx.typeck_results().type_dependent_def_id(e.hir_id).unwrap();
718+
Some(args.iter().position(|arg| arg.hir_id == child_id).map_or(false, |i| {
719+
let arg = cx.tcx.fn_sig(id).skip_binder().inputs()[i + 1];
720+
is_param_auto_deref_stable(arg)
721+
}))
722+
},
723+
ExprKind::Struct(path, fields, _) => {
724+
let variant = variant_of_res(cx, cx.qpath_res(path, e.hir_id));
725+
Some(
726+
fields
727+
.iter()
728+
.find(|f| f.expr.hir_id == child_id)
729+
.zip(variant)
730+
.and_then(|(field, variant)| variant.fields.iter().find(|f| f.name == field.ident.name))
731+
.map_or(false, |field| is_param_auto_deref_stable(cx.tcx.type_of(field.did))),
732+
)
733+
},
734+
_ => None,
735+
},
736+
_ => None,
737+
})
738+
.unwrap_or(false)
739+
}
740+
741+
// Checks whether auto-dereferencing any type into a binding of the given type will definitely
742+
// produce the same result.
743+
//
744+
// e.g.
745+
// let x = Box::new(Box::new(0u32));
746+
// let y1: &Box<_> = x.deref();
747+
// let y2: &Box<_> = &x;
748+
//
749+
// Here `y1` and `y2` would resolve to different types, so the type `&Box<_>` is not stable when
750+
// switching to auto-dereferencing.
751+
fn is_binding_ty_auto_deref_stable(ty: &hir::Ty<'_>) -> bool {
752+
let (ty, count) = peel_hir_ty_refs(ty);
753+
if count != 1 {
754+
return false;
755+
}
756+
757+
match &ty.kind {
758+
TyKind::Rptr(_, ty) => is_binding_ty_auto_deref_stable(ty.ty),
759+
&TyKind::Path(
760+
QPath::TypeRelative(_, path)
761+
| QPath::Resolved(
762+
_,
763+
Path {
764+
segments: [.., path], ..
765+
},
766+
),
767+
) => {
768+
if let Some(args) = path.args {
769+
args.args.iter().all(|arg| {
770+
if let GenericArg::Type(ty) = arg {
771+
!ty_contains_infer(ty)
772+
} else {
773+
true
774+
}
775+
})
776+
} else {
777+
true
778+
}
779+
},
780+
TyKind::Slice(_)
781+
| TyKind::Array(..)
782+
| TyKind::BareFn(_)
783+
| TyKind::Never
784+
| TyKind::Tup(_)
785+
| TyKind::Ptr(_)
786+
| TyKind::TraitObject(..)
787+
| TyKind::Path(_) => true,
788+
TyKind::OpaqueDef(..) | TyKind::Infer | TyKind::Typeof(..) | TyKind::Err => false,
789+
}
790+
}
791+
792+
// Checks whether a type is inferred at some point.
793+
// e.g. `_`, `Box<_>`, `[_]`
794+
fn ty_contains_infer(ty: &hir::Ty<'_>) -> bool {
795+
match &ty.kind {
796+
TyKind::Slice(ty) | TyKind::Array(ty, _) => ty_contains_infer(ty),
797+
TyKind::Ptr(ty) | TyKind::Rptr(_, ty) => ty_contains_infer(ty.ty),
798+
TyKind::Tup(tys) => tys.iter().any(ty_contains_infer),
799+
TyKind::BareFn(ty) => {
800+
if ty.decl.inputs.iter().any(ty_contains_infer) {
801+
return true;
802+
}
803+
if let FnRetTy::Return(ty) = &ty.decl.output {
804+
ty_contains_infer(ty)
805+
} else {
806+
false
807+
}
808+
},
809+
&TyKind::Path(
810+
QPath::TypeRelative(_, path)
811+
| QPath::Resolved(
812+
_,
813+
Path {
814+
segments: [.., path], ..
815+
},
816+
),
817+
) => {
818+
if let Some(args) = path.args {
819+
args.args.iter().any(|arg| {
820+
if let GenericArg::Type(ty) = arg {
821+
ty_contains_infer(ty)
822+
} else {
823+
false
824+
}
825+
})
826+
} else {
827+
false
828+
}
829+
},
830+
TyKind::Path(_) | TyKind::OpaqueDef(..) | TyKind::Infer | TyKind::Typeof(_) | TyKind::Err => true,
831+
TyKind::Never | TyKind::TraitObject(..) => false,
832+
}
833+
}
834+
835+
// Checks whether a type is stable when switching to auto dereferencing,
836+
fn is_param_auto_deref_stable(ty: Ty<'_>) -> bool {
837+
let (ty, count) = peel_mid_ty_refs(ty);
838+
if count != 1 {
839+
return false;
840+
}
841+
842+
match ty.kind() {
843+
ty::Bool
844+
| ty::Char
845+
| ty::Int(_)
846+
| ty::Uint(_)
847+
| ty::Float(_)
848+
| ty::Foreign(_)
849+
| ty::Str
850+
| ty::Array(..)
851+
| ty::Slice(..)
852+
| ty::RawPtr(..)
853+
| ty::FnDef(..)
854+
| ty::FnPtr(_)
855+
| ty::Closure(..)
856+
| ty::Generator(..)
857+
| ty::GeneratorWitness(..)
858+
| ty::Never
859+
| ty::Tuple(_)
860+
| ty::Ref(..)
861+
| ty::Projection(_) => true,
862+
ty::Infer(_)
863+
| ty::Error(_)
864+
| ty::Param(_)
865+
| ty::Bound(..)
866+
| ty::Opaque(..)
867+
| ty::Placeholder(_)
868+
| ty::Dynamic(..) => false,
869+
ty::Adt(..) => !(ty.has_placeholders() || ty.has_param_types_or_consts()),
870+
}
871+
}
872+
599873
#[expect(clippy::needless_pass_by_value)]
600-
fn report<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, state: State, data: StateData) {
874+
fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data: StateData) {
601875
match state {
602876
State::DerefMethod {
603877
ty_changed_count,
@@ -663,6 +937,29 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, state: State, data: S
663937
diag.span_suggestion(data.span, "change this to", sugg, app);
664938
});
665939
},
940+
State::ExplicitDeref {
941+
deref_span,
942+
deref_hir_id,
943+
} => {
944+
let (span, hir_id) = if cx.typeck_results().expr_ty(expr).is_ref() {
945+
(data.span, data.hir_id)
946+
} else {
947+
(deref_span, deref_hir_id)
948+
};
949+
span_lint_hir_and_then(
950+
cx,
951+
EXPLICIT_AUTO_DEREF,
952+
hir_id,
953+
span,
954+
"deref which would be done by auto-deref",
955+
|diag| {
956+
let mut app = Applicability::MachineApplicable;
957+
let snip = snippet_with_context(cx, expr.span, span.ctxt(), "..", &mut app).0;
958+
diag.span_suggestion(span, "try this", snip.into_owned(), app);
959+
},
960+
);
961+
},
962+
State::Borrow => (),
666963
}
667964
}
668965

clippy_lints/src/lib.register_all.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
4444
LintId::of(crate_in_macro_def::CRATE_IN_MACRO_DEF),
4545
LintId::of(default::FIELD_REASSIGN_WITH_DEFAULT),
4646
LintId::of(default_instead_of_iter_empty::DEFAULT_INSTEAD_OF_ITER_EMPTY),
47+
LintId::of(dereference::EXPLICIT_AUTO_DEREF),
4748
LintId::of(dereference::NEEDLESS_BORROW),
4849
LintId::of(derivable_impls::DERIVABLE_IMPLS),
4950
LintId::of(derive::DERIVE_HASH_XOR_EQ),

clippy_lints/src/lib.register_complexity.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec!
99
LintId::of(bytes_count_to_len::BYTES_COUNT_TO_LEN),
1010
LintId::of(casts::CHAR_LIT_AS_U8),
1111
LintId::of(casts::UNNECESSARY_CAST),
12+
LintId::of(dereference::EXPLICIT_AUTO_DEREF),
1213
LintId::of(derivable_impls::DERIVABLE_IMPLS),
1314
LintId::of(double_comparison::DOUBLE_COMPARISONS),
1415
LintId::of(double_parens::DOUBLE_PARENS),

0 commit comments

Comments
 (0)