Skip to content

Commit 6f96437

Browse files
committed
Include async functions in the len_without_is_empty
1 parent 5b6795f commit 6f96437

File tree

3 files changed

+190
-5
lines changed

3 files changed

+190
-5
lines changed

clippy_lints/src/len_zero.rs

Lines changed: 108 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ use rustc_ast::ast::LitKind;
66
use rustc_errors::Applicability;
77
use rustc_hir::def_id::DefIdSet;
88
use rustc_hir::{
9+
def::Res,
10+
PrimTy,
11+
QPath,
12+
TypeBindingKind,
13+
lang_items::LangItem,
14+
GenericBound,
915
def_id::DefId, AssocItemKind, BinOpKind, Expr, ExprKind, FnRetTy, ImplItem, ImplItemKind, ImplicitSelfKind, Item,
1016
ItemKind, Mutability, Node, TraitItemRef, TyKind, UnOp,
1117
};
@@ -256,7 +262,87 @@ enum LenOutput<'tcx> {
256262
Option(DefId),
257263
Result(DefId, Ty<'tcx>),
258264
}
259-
fn parse_len_output<'tcx>(cx: &LateContext<'_>, sig: FnSig<'tcx>) -> Option<LenOutput<'tcx>> {
265+
266+
fn extract_future_output<'tcx>(
267+
cx: &LateContext<'tcx>,
268+
ty: Ty<'tcx>,
269+
) -> Option<Res> {
270+
271+
if let ty::Alias(_, alias_ty) = ty.kind() {
272+
let def_id = alias_ty.def_id;
273+
let hir_map = cx.tcx.hir();
274+
let item = hir_map.get_if_local(def_id);
275+
let item = item.unwrap();
276+
277+
if let Node::Item(item) = item {
278+
if let ItemKind::OpaqueTy(opaque) = &item.kind {
279+
let bounds = opaque.bounds;
280+
let bound = &bounds[0];
281+
if let GenericBound::LangItemTrait(item, _, _, generic_args) = bound {
282+
if item != &LangItem::Future {
283+
return None;
284+
}
285+
286+
let bindings = generic_args.bindings;
287+
if bindings.len() != 1 {
288+
return None;
289+
}
290+
291+
let binding = &bindings[0];
292+
let kind = &binding.kind;
293+
294+
if let TypeBindingKind::Equality{term: rustc_hir::Term::Ty(term_ty) } = kind {
295+
if let TyKind::Path(QPath::Resolved(_, path)) = &term_ty.kind {
296+
let segments = &path.segments;
297+
let segment = &segments[0];
298+
let res = segment.res;
299+
300+
return Some(res);
301+
}
302+
}
303+
}
304+
}
305+
}
306+
}
307+
308+
return None;
309+
}
310+
311+
// fn future_output_ty<'tcx>(trait_ref: &'tcx TraitRef<'tcx>) -> Option<&'tcx Ty<'tcx>> {
312+
// if_chain! {
313+
// if let Some(segment) = trait_ref.path.segments.last();
314+
// if let Some(args) = segment.args;
315+
// if args.bindings.len() == 1;
316+
// let binding = &args.bindings[0];
317+
// if binding.ident.name == sym::Output;
318+
// if let TypeBindingKind::Equality { term: Term::Ty(output) } = binding.kind;
319+
// then {
320+
// return Some(output);
321+
// }
322+
// }
323+
324+
// None
325+
// }
326+
327+
fn parse_len_output<'tcx>(cx: &LateContext<'tcx>, sig: FnSig<'tcx>) -> Option<LenOutput<'tcx>> {
328+
329+
if let Some(res) = extract_future_output(cx, sig.output()) {
330+
331+
if matches!(res, Res::PrimTy(PrimTy::Uint(_))) || matches!(res, Res::PrimTy(PrimTy::Int(_))) {
332+
return Some(LenOutput::Integral);
333+
}
334+
335+
if let Res::Def(_, def_id) = res {
336+
if cx.tcx.is_diagnostic_item(sym::Option, def_id) {
337+
return Some(LenOutput::Option(def_id));
338+
} else if cx.tcx.is_diagnostic_item(sym::Result, def_id) {
339+
return Some(LenOutput::Result(def_id, cx.tcx.ty_error()));
340+
}
341+
}
342+
343+
panic!();
344+
}
345+
260346
match *sig.output().kind() {
261347
ty::Int(_) | ty::Uint(_) => Some(LenOutput::Integral),
262348
ty::Adt(adt, subs) if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) => {
@@ -271,7 +357,19 @@ fn parse_len_output<'tcx>(cx: &LateContext<'_>, sig: FnSig<'tcx>) -> Option<LenO
271357
}
272358

273359
impl<'tcx> LenOutput<'tcx> {
274-
fn matches_is_empty_output(self, ty: Ty<'tcx>) -> bool {
360+
fn matches_is_empty_output(self,
361+
cx: &LateContext<'tcx>,
362+
ty: Ty<'tcx>) -> bool {
363+
364+
if let Some(future_output) = extract_future_output(cx, ty) {
365+
return match (self, future_output) {
366+
(_, Res::PrimTy(PrimTy::Bool)) => true,
367+
(Self::Option(_), Res::Def(_, def_id)) if cx.tcx.is_diagnostic_item(sym::Option, def_id) => true,
368+
(Self::Result(_, _), Res::Def(_, def_id)) if cx.tcx.is_diagnostic_item(sym::Result, def_id) => true,
369+
_ => false,
370+
}
371+
}
372+
275373
match (self, ty.kind()) {
276374
(_, &ty::Bool) => true,
277375
(Self::Option(id), &ty::Adt(adt, subs)) if id == adt.did() => subs.type_at(0).is_bool(),
@@ -301,9 +399,14 @@ impl<'tcx> LenOutput<'tcx> {
301399
}
302400

303401
/// Checks if the given signature matches the expectations for `is_empty`
304-
fn check_is_empty_sig<'tcx>(sig: FnSig<'tcx>, self_kind: ImplicitSelfKind, len_output: LenOutput<'tcx>) -> bool {
402+
fn check_is_empty_sig<'tcx>(
403+
cx: &LateContext<'tcx>,
404+
sig: FnSig<'tcx>,
405+
self_kind: ImplicitSelfKind,
406+
len_output: LenOutput<'tcx>
407+
) -> bool {
305408
match &**sig.inputs_and_output {
306-
[arg, res] if len_output.matches_is_empty_output(*res) => {
409+
[arg, res] if len_output.matches_is_empty_output(cx, *res) => {
307410
matches!(
308411
(arg.kind(), self_kind),
309412
(ty::Ref(_, _, Mutability::Not), ImplicitSelfKind::ImmRef)
@@ -352,6 +455,7 @@ fn check_for_is_empty<'tcx>(
352455
Some(is_empty)
353456
if !(is_empty.fn_has_self_parameter
354457
&& check_is_empty_sig(
458+
cx,
355459
cx.tcx.fn_sig(is_empty.def_id).subst_identity().skip_binder(),
356460
self_kind,
357461
output,

tests/ui/len_without_is_empty.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,69 @@ impl AsyncLen {
282282
}
283283
}
284284

285+
// issue #7232
286+
pub struct AsyncLenWithoutIsEmpty;
287+
impl AsyncLenWithoutIsEmpty {
288+
async fn async_task(&self) -> bool {
289+
true
290+
}
291+
292+
pub async fn len(&self) -> usize {
293+
usize::from(!self.async_task().await)
294+
}
295+
}
296+
297+
// issue #7232
298+
pub struct AsyncOptionLenWithoutIsEmpty;
299+
impl AsyncOptionLenWithoutIsEmpty {
300+
async fn async_task(&self) -> bool {
301+
true
302+
}
303+
304+
pub async fn len(&self) -> Option<usize> {
305+
None
306+
}
307+
}
308+
309+
// issue #7232
310+
pub struct AsyncResultLenWithoutIsEmpty;
311+
impl AsyncResultLenWithoutIsEmpty {
312+
async fn async_task(&self) -> bool {
313+
true
314+
}
315+
316+
pub async fn len(&self) -> Result<usize, ()> {
317+
Err(())
318+
}
319+
}
320+
321+
// issue #7232
322+
pub struct AsyncOptionLen;
323+
impl AsyncOptionLen {
324+
async fn async_task(&self) -> bool {
325+
true
326+
}
327+
328+
pub async fn len(&self) -> Result<usize, ()> {
329+
Err(())
330+
}
331+
332+
pub async fn is_empty(&self) -> bool {
333+
true
334+
}
335+
}
336+
337+
pub struct AsyncLenSyncIsEmpty;
338+
impl AsyncLenSyncIsEmpty {
339+
pub async fn len(&self) -> u32 {
340+
0
341+
}
342+
343+
pub fn is_empty(&self) -> bool {
344+
true
345+
}
346+
}
347+
285348
// issue #9520
286349
pub struct NonStandardLenAndIsEmptySignature;
287350
impl NonStandardLenAndIsEmptySignature {

tests/ui/len_without_is_empty.stderr

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,5 +119,23 @@ LL | pub fn len(&self) -> Result<usize, ()> {
119119
|
120120
= help: use a custom `Error` type instead
121121

122-
error: aborting due to 12 previous errors
122+
error: struct `AsyncLenWithoutIsEmpty` has a public `len` method, but no `is_empty` method
123+
--> $DIR/len_without_is_empty.rs:292:5
124+
|
125+
LL | pub async fn len(&self) -> usize {
126+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
127+
128+
error: struct `AsyncOptionLenWithoutIsEmpty` has a public `len` method, but no `is_empty` method
129+
--> $DIR/len_without_is_empty.rs:304:5
130+
|
131+
LL | pub async fn len(&self) -> Option<usize> {
132+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
133+
134+
error: struct `AsyncResultLenWithoutIsEmpty` has a public `len` method, but no `is_empty` method
135+
--> $DIR/len_without_is_empty.rs:316:5
136+
|
137+
LL | pub async fn len(&self) -> Result<usize, ()> {
138+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
139+
140+
error: aborting due to 15 previous errors
123141

0 commit comments

Comments
 (0)