Skip to content

Commit 31b3bd1

Browse files
mladedavoppiliappan
andcommitted
Include async functions in the len_without_is_empty
Co-authored-by: Akshay <[email protected]>
1 parent 5b6795f commit 31b3bd1

File tree

3 files changed

+228
-20
lines changed

3 files changed

+228
-20
lines changed

clippy_lints/src/len_zero.rs

+126-19
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ use rustc_ast::ast::LitKind;
66
use rustc_errors::Applicability;
77
use rustc_hir::def_id::DefIdSet;
88
use rustc_hir::{
9-
def_id::DefId, AssocItemKind, BinOpKind, Expr, ExprKind, FnRetTy, ImplItem, ImplItemKind, ImplicitSelfKind, Item,
10-
ItemKind, Mutability, Node, TraitItemRef, TyKind, UnOp,
9+
def::Res, def_id::DefId, lang_items::LangItem, AssocItemKind, BinOpKind, Expr, ExprKind, FnRetTy, GenericArg,
10+
GenericBound, ImplItem, ImplItemKind, ImplicitSelfKind, Item, ItemKind, Mutability, Node, PathSegment, PrimTy,
11+
QPath, TraitItemRef, TyKind, TypeBindingKind, UnOp,
1112
};
1213
use rustc_lint::{LateContext, LateLintPass};
1314
use rustc_middle::ty::{self, AssocKind, FnSig, Ty};
@@ -251,33 +252,133 @@ fn check_trait_items(cx: &LateContext<'_>, visited_trait: &Item<'_>, trait_items
251252
}
252253

253254
#[derive(Debug, Clone, Copy)]
254-
enum LenOutput<'tcx> {
255+
enum LenOutput {
255256
Integral,
256257
Option(DefId),
257-
Result(DefId, Ty<'tcx>),
258+
Result(DefId),
258259
}
259-
fn parse_len_output<'tcx>(cx: &LateContext<'_>, sig: FnSig<'tcx>) -> Option<LenOutput<'tcx>> {
260+
261+
fn extract_future_output<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<&'tcx PathSegment<'tcx>> {
262+
if let ty::Alias(_, alias_ty) = ty.kind() {
263+
let def_id = alias_ty.def_id;
264+
let hir_map = cx.tcx.hir();
265+
let item = hir_map.get_if_local(def_id);
266+
let item = item.unwrap();
267+
268+
if let Node::Item(item) = item {
269+
if let ItemKind::OpaqueTy(opaque) = &item.kind {
270+
let bounds = opaque.bounds;
271+
if bounds.len() != 1 {
272+
return None;
273+
}
274+
let bound = &bounds[0];
275+
if let GenericBound::LangItemTrait(item, _, _, generic_args) = bound {
276+
if item != &LangItem::Future {
277+
return None;
278+
}
279+
280+
let bindings = generic_args.bindings;
281+
if bindings.len() != 1 {
282+
return None;
283+
}
284+
285+
let binding = &bindings[0];
286+
let kind = &binding.kind;
287+
288+
if let TypeBindingKind::Equality {
289+
term: rustc_hir::Term::Ty(term_ty),
290+
} = kind
291+
{
292+
if let TyKind::Path(QPath::Resolved(_, path)) = &term_ty.kind {
293+
let segments = &path.segments;
294+
if segments.len() != 1 {
295+
return None;
296+
}
297+
let segment = &segments[0];
298+
return Some(segment);
299+
}
300+
}
301+
}
302+
}
303+
}
304+
}
305+
306+
None
307+
}
308+
309+
fn is_first_generic_integral<'tcx>(segment: &'tcx PathSegment<'tcx>) -> bool {
310+
if let Some(generic_args) = segment.args {
311+
if generic_args.args.is_empty() {
312+
return false;
313+
}
314+
let arg = &generic_args.args[0];
315+
if let GenericArg::Type(rustc_hir::Ty {
316+
kind: TyKind::Path(QPath::Resolved(_, path)),
317+
..
318+
}) = arg
319+
{
320+
let segments = &path.segments;
321+
let segment = &segments[0];
322+
let res = &segment.res;
323+
if matches!(res, Res::PrimTy(PrimTy::Uint(_))) || matches!(res, Res::PrimTy(PrimTy::Int(_))) {
324+
return true;
325+
}
326+
}
327+
}
328+
329+
false
330+
}
331+
332+
fn parse_len_output<'tcx>(cx: &LateContext<'tcx>, sig: FnSig<'tcx>) -> Option<LenOutput> {
333+
if let Some(segment) = extract_future_output(cx, sig.output()) {
334+
let res = segment.res;
335+
336+
if matches!(res, Res::PrimTy(PrimTy::Uint(_))) || matches!(res, Res::PrimTy(PrimTy::Int(_))) {
337+
return Some(LenOutput::Integral);
338+
}
339+
340+
if let Res::Def(_, def_id) = res {
341+
if cx.tcx.is_diagnostic_item(sym::Option, def_id) {
342+
if is_first_generic_integral(segment) {
343+
return Some(LenOutput::Option(def_id));
344+
}
345+
346+
return Some(LenOutput::Option(def_id));
347+
} else if cx.tcx.is_diagnostic_item(sym::Result, def_id) && is_first_generic_integral(segment) {
348+
return Some(LenOutput::Option(def_id));
349+
}
350+
}
351+
352+
return None;
353+
}
354+
260355
match *sig.output().kind() {
261356
ty::Int(_) | ty::Uint(_) => Some(LenOutput::Integral),
262357
ty::Adt(adt, subs) if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) => {
263358
subs.type_at(0).is_integral().then(|| LenOutput::Option(adt.did()))
264359
},
265-
ty::Adt(adt, subs) if cx.tcx.is_diagnostic_item(sym::Result, adt.did()) => subs
266-
.type_at(0)
267-
.is_integral()
268-
.then(|| LenOutput::Result(adt.did(), subs.type_at(1))),
360+
ty::Adt(adt, subs) if cx.tcx.is_diagnostic_item(sym::Result, adt.did()) => {
361+
subs.type_at(0).is_integral().then(|| LenOutput::Result(adt.did()))
362+
},
269363
_ => None,
270364
}
271365
}
272366

273-
impl<'tcx> LenOutput<'tcx> {
274-
fn matches_is_empty_output(self, ty: Ty<'tcx>) -> bool {
367+
impl LenOutput {
368+
fn matches_is_empty_output<'tcx>(self, cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
369+
if let Some(segment) = extract_future_output(cx, ty) {
370+
return match (self, segment.res) {
371+
(_, Res::PrimTy(PrimTy::Bool)) => true,
372+
(Self::Option(_), Res::Def(_, def_id)) if cx.tcx.is_diagnostic_item(sym::Option, def_id) => true,
373+
(Self::Result(_), Res::Def(_, def_id)) if cx.tcx.is_diagnostic_item(sym::Result, def_id) => true,
374+
_ => false,
375+
};
376+
}
377+
275378
match (self, ty.kind()) {
276379
(_, &ty::Bool) => true,
277380
(Self::Option(id), &ty::Adt(adt, subs)) if id == adt.did() => subs.type_at(0).is_bool(),
278-
(Self::Result(id, err_ty), &ty::Adt(adt, subs)) if id == adt.did() => {
279-
subs.type_at(0).is_bool() && subs.type_at(1) == err_ty
280-
},
381+
(Self::Result(id), &ty::Adt(adt, subs)) if id == adt.did() => subs.type_at(0).is_bool(),
281382
_ => false,
282383
}
283384
}
@@ -301,9 +402,14 @@ impl<'tcx> LenOutput<'tcx> {
301402
}
302403

303404
/// 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 {
405+
fn check_is_empty_sig<'tcx>(
406+
cx: &LateContext<'tcx>,
407+
sig: FnSig<'tcx>,
408+
self_kind: ImplicitSelfKind,
409+
len_output: LenOutput,
410+
) -> bool {
305411
match &**sig.inputs_and_output {
306-
[arg, res] if len_output.matches_is_empty_output(*res) => {
412+
[arg, res] if len_output.matches_is_empty_output(cx, *res) => {
307413
matches!(
308414
(arg.kind(), self_kind),
309415
(ty::Ref(_, _, Mutability::Not), ImplicitSelfKind::ImmRef)
@@ -315,11 +421,11 @@ fn check_is_empty_sig<'tcx>(sig: FnSig<'tcx>, self_kind: ImplicitSelfKind, len_o
315421
}
316422

317423
/// Checks if the given type has an `is_empty` method with the appropriate signature.
318-
fn check_for_is_empty<'tcx>(
319-
cx: &LateContext<'tcx>,
424+
fn check_for_is_empty(
425+
cx: &LateContext<'_>,
320426
span: Span,
321427
self_kind: ImplicitSelfKind,
322-
output: LenOutput<'tcx>,
428+
output: LenOutput,
323429
impl_ty: DefId,
324430
item_name: Symbol,
325431
item_kind: &str,
@@ -352,6 +458,7 @@ fn check_for_is_empty<'tcx>(
352458
Some(is_empty)
353459
if !(is_empty.fn_has_self_parameter
354460
&& check_is_empty_sig(
461+
cx,
355462
cx.tcx.fn_sig(is_empty.def_id).subst_identity().skip_binder(),
356463
self_kind,
357464
output,

tests/ui/len_without_is_empty.rs

+83
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,78 @@ 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+
348+
// issue #9520
349+
pub struct NonStandardLen;
350+
impl NonStandardLen {
351+
// don't lint
352+
pub fn len(&self, something: usize) -> usize {
353+
something
354+
}
355+
}
356+
285357
// issue #9520
286358
pub struct NonStandardLenAndIsEmptySignature;
287359
impl NonStandardLenAndIsEmptySignature {
@@ -328,4 +400,15 @@ impl NonStandardSignatureWithGenerics {
328400
}
329401
}
330402

403+
pub struct DifferingErrors;
404+
impl DifferingErrors {
405+
pub fn len(&self) -> Result<usize, u8> {
406+
Ok(0)
407+
}
408+
409+
pub fn is_empty(&self) -> Result<bool, u16> {
410+
Ok(true)
411+
}
412+
}
413+
331414
fn main() {}

tests/ui/len_without_is_empty.stderr

+19-1
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)