Skip to content

Commit 2315f76

Browse files
committed
Actually check lifetimes in trivially_copy_pass_by_ref
1 parent eaa03ea commit 2315f76

File tree

5 files changed

+117
-41
lines changed

5 files changed

+117
-41
lines changed

clippy_lints/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#![feature(let_chains)]
88
#![feature(let_else)]
99
#![feature(lint_reasons)]
10+
#![feature(never_type)]
1011
#![feature(once_cell)]
1112
#![feature(rustc_private)]
1213
#![feature(stmt_expr_attributes)]

clippy_lints/src/pass_by_ref_or_value.rs

Lines changed: 53 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,19 @@ use std::iter;
33

44
use clippy_utils::diagnostics::span_lint_and_sugg;
55
use clippy_utils::source::snippet;
6-
use clippy_utils::ty::is_copy;
6+
use clippy_utils::ty::{for_each_top_level_late_bound_region, is_copy};
77
use clippy_utils::{is_self, is_self_ty};
8+
use core::ops::ControlFlow;
89
use if_chain::if_chain;
910
use rustc_ast::attr;
11+
use rustc_data_structures::fx::FxHashSet;
1012
use rustc_errors::Applicability;
1113
use rustc_hir as hir;
1214
use rustc_hir::intravisit::FnKind;
1315
use rustc_hir::{BindingAnnotation, Body, FnDecl, HirId, Impl, ItemKind, MutTy, Mutability, Node, PatKind};
1416
use rustc_lint::{LateContext, LateLintPass};
15-
use rustc_middle::ty;
1617
use rustc_middle::ty::layout::LayoutOf;
18+
use rustc_middle::ty::{self, RegionKind};
1719
use rustc_session::{declare_tool_lint, impl_lint_pass};
1820
use rustc_span::def_id::LocalDefId;
1921
use rustc_span::{sym, Span};
@@ -141,50 +143,62 @@ impl<'tcx> PassByRefOrValue {
141143
}
142144

143145
let fn_sig = cx.tcx.fn_sig(def_id);
144-
let fn_sig = cx.tcx.erase_late_bound_regions(fn_sig);
145-
146146
let fn_body = cx.enclosing_body.map(|id| cx.tcx.hir().body(id));
147147

148-
for (index, (input, &ty)) in iter::zip(decl.inputs, fn_sig.inputs()).enumerate() {
148+
// Gather all the lifetimes found in the output type which may affect whether
149+
// `TRIVIALLY_COPY_PASS_BY_REF` should be linted.
150+
let mut output_regions = FxHashSet::default();
151+
for_each_top_level_late_bound_region(fn_sig.skip_binder().output(), |region| -> ControlFlow<!> {
152+
output_regions.insert(region);
153+
ControlFlow::Continue(())
154+
});
155+
156+
for (index, (input, ty)) in iter::zip(
157+
decl.inputs,
158+
fn_sig.skip_binder().inputs().iter().map(|&ty| fn_sig.rebind(ty)),
159+
)
160+
.enumerate()
161+
{
149162
// All spans generated from a proc-macro invocation are the same...
150163
match span {
151-
Some(s) if s == input.span => return,
164+
Some(s) if s == input.span => continue,
152165
_ => (),
153166
}
154167

155-
match ty.kind() {
156-
ty::Ref(input_lt, ty, Mutability::Not) => {
157-
// Use lifetimes to determine if we're returning a reference to the
158-
// argument. In that case we can't switch to pass-by-value as the
159-
// argument will not live long enough.
160-
let output_lts = match *fn_sig.output().kind() {
161-
ty::Ref(output_lt, _, _) => vec![output_lt],
162-
ty::Adt(_, substs) => substs.regions().collect(),
163-
_ => vec![],
164-
};
168+
match *ty.skip_binder().kind() {
169+
ty::Ref(lt, ty, Mutability::Not) => {
170+
match lt.kind() {
171+
RegionKind::ReLateBound(index, region)
172+
if index.as_u32() == 0 && output_regions.contains(&region) =>
173+
{
174+
continue;
175+
},
176+
// Early bound regions on functions are either from the containing item, are bounded by another
177+
// lifetime, or are used as a bound for a type or lifetime.
178+
RegionKind::ReEarlyBound(..) => continue,
179+
_ => (),
180+
}
165181

166-
if_chain! {
167-
if !output_lts.contains(input_lt);
168-
if is_copy(cx, *ty);
169-
if let Some(size) = cx.layout_of(*ty).ok().map(|l| l.size.bytes());
170-
if size <= self.ref_min_size;
171-
if let hir::TyKind::Rptr(_, MutTy { ty: decl_ty, .. }) = input.kind;
172-
then {
173-
let value_type = if fn_body.and_then(|body| body.params.get(index)).map_or(false, is_self) {
174-
"self".into()
175-
} else {
176-
snippet(cx, decl_ty.span, "_").into()
177-
};
178-
span_lint_and_sugg(
179-
cx,
180-
TRIVIALLY_COPY_PASS_BY_REF,
181-
input.span,
182-
&format!("this argument ({} byte) is passed by reference, but would be more efficient if passed by value (limit: {} byte)", size, self.ref_min_size),
183-
"consider passing by value instead",
184-
value_type,
185-
Applicability::Unspecified,
186-
);
187-
}
182+
let ty = cx.tcx.erase_late_bound_regions(fn_sig.rebind(ty));
183+
if is_copy(cx, ty)
184+
&& let Some(size) = cx.layout_of(ty).ok().map(|l| l.size.bytes())
185+
&& size <= self.ref_min_size
186+
&& let hir::TyKind::Rptr(_, MutTy { ty: decl_ty, .. }) = input.kind
187+
{
188+
let value_type = if fn_body.and_then(|body| body.params.get(index)).map_or(false, is_self) {
189+
"self".into()
190+
} else {
191+
snippet(cx, decl_ty.span, "_").into()
192+
};
193+
span_lint_and_sugg(
194+
cx,
195+
TRIVIALLY_COPY_PASS_BY_REF,
196+
input.span,
197+
&format!("this argument ({} byte) is passed by reference, but would be more efficient if passed by value (limit: {} byte)", size, self.ref_min_size),
198+
"consider passing by value instead",
199+
value_type,
200+
Applicability::Unspecified,
201+
);
188202
}
189203
},
190204

@@ -196,6 +210,7 @@ impl<'tcx> PassByRefOrValue {
196210
_ => continue,
197211
}
198212
}
213+
let ty = cx.tcx.erase_late_bound_regions(ty);
199214

200215
if_chain! {
201216
if is_copy(cx, ty);

clippy_utils/src/ty.rs

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
33
#![allow(clippy::module_name_repetitions)]
44

5+
use core::ops::ControlFlow;
56
use rustc_ast::ast::Mutability;
67
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
78
use rustc_hir as hir;
@@ -13,8 +14,8 @@ use rustc_lint::LateContext;
1314
use rustc_middle::mir::interpret::{ConstValue, Scalar};
1415
use rustc_middle::ty::subst::{GenericArg, GenericArgKind, Subst};
1516
use rustc_middle::ty::{
16-
self, AdtDef, Binder, FnSig, IntTy, ParamEnv, Predicate, PredicateKind, Ty, TyCtxt, TypeFoldable, UintTy,
17-
VariantDiscr,
17+
self, AdtDef, Binder, BoundRegion, FnSig, IntTy, ParamEnv, Predicate, PredicateKind, Region, RegionKind, Ty,
18+
TyCtxt, TypeFoldable, TypeSuperFoldable, TypeVisitor, UintTy, VariantDiscr,
1819
};
1920
use rustc_span::symbol::Ident;
2021
use rustc_span::{sym, Span, Symbol, DUMMY_SP};
@@ -667,3 +668,30 @@ pub fn is_c_void(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
667668
false
668669
}
669670
}
671+
672+
pub fn for_each_top_level_late_bound_region<B>(
673+
ty: Ty<'_>,
674+
f: impl FnMut(BoundRegion) -> ControlFlow<B>,
675+
) -> ControlFlow<B> {
676+
struct V<F> {
677+
index: u32,
678+
f: F,
679+
}
680+
impl<'tcx, B, F: FnMut(BoundRegion) -> ControlFlow<B>> TypeVisitor<'tcx> for V<F> {
681+
type BreakTy = B;
682+
fn visit_region(&mut self, r: Region<'tcx>) -> ControlFlow<Self::BreakTy> {
683+
if let RegionKind::ReLateBound(idx, bound) = r.kind() && idx.as_u32() == self.index {
684+
(self.f)(bound)
685+
} else {
686+
ControlFlow::Continue(())
687+
}
688+
}
689+
fn visit_binder<T: TypeFoldable<'tcx>>(&mut self, t: &Binder<'tcx, T>) -> ControlFlow<Self::BreakTy> {
690+
self.index += 1;
691+
let res = t.super_visit_with(self);
692+
self.index -= 1;
693+
res
694+
}
695+
}
696+
ty.visit_with(&mut V { index: 0, f })
697+
}

tests/ui/trivially_copy_pass_by_ref.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,32 @@ mod issue5876 {
113113
}
114114
}
115115

116+
fn _ref_to_opt_ref_implicit(x: &u32) -> Option<&u32> {
117+
Some(x)
118+
}
119+
120+
#[allow(clippy::needless_lifetimes)]
121+
fn _ref_to_opt_ref_explicit<'a>(x: &'a u32) -> Option<&'a u32> {
122+
Some(x)
123+
}
124+
125+
fn _with_constraint<'a, 'b: 'a>(x: &'b u32, y: &'a u32) -> &'a u32 {
126+
if true { x } else { y }
127+
}
128+
129+
async fn _async_implicit(x: &u32) -> &u32 {
130+
x
131+
}
132+
133+
#[allow(clippy::needless_lifetimes)]
134+
async fn _async_explicit<'a>(x: &'a u32) -> &'a u32 {
135+
x
136+
}
137+
138+
fn _unrelated_lifetimes<'a, 'b>(_x: &'a u32, y: &'b u32) -> &'b u32 {
139+
y
140+
}
141+
116142
fn main() {
117143
let (mut foo, bar) = (Foo(0), Bar([0; 24]));
118144
let (mut a, b, c, x, y, z) = (0, 0, Bar([0; 24]), 0, Foo(0), 0);

tests/ui/trivially_copy_pass_by_ref.stderr

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,5 +106,11 @@ error: this argument (N byte) is passed by reference, but would be more efficien
106106
LL | fn foo(x: &i32) {
107107
| ^^^^ help: consider passing by value instead: `i32`
108108

109-
error: aborting due to 17 previous errors
109+
error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte)
110+
--> $DIR/trivially_copy_pass_by_ref.rs:138:37
111+
|
112+
LL | fn _unrelated_lifetimes<'a, 'b>(_x: &'a u32, y: &'b u32) -> &'b u32 {
113+
| ^^^^^^^ help: consider passing by value instead: `u32`
114+
115+
error: aborting due to 18 previous errors
110116

0 commit comments

Comments
 (0)