• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

xd009642 / tarpaulin / #746

23 Apr 2026 05:30AM UTC coverage: 85.719% (+0.3%) from 85.46%
#746

push

web-flow
Bump rand from 0.8.5 to 0.8.6 in /tests/data/kill_proc (#1845)

Bumps [rand](https://github.com/rust-random/rand) from 0.8.5 to 0.8.6.
- [Release notes](https://github.com/rust-random/rand/releases)
- [Changelog](https://github.com/rust-random/rand/blob/0.8.6/CHANGELOG.md)
- [Commits](https://github.com/rust-random/rand/compare/0.8.5...0.8.6)

---
updated-dependencies:
- dependency-name: rand
  dependency-version: 0.8.6
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

4898 of 5714 relevant lines covered (85.72%)

250732.41 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

77.44
/src/source_analysis/expressions.rs
1
use crate::source_analysis::prelude::*;
2
use syn::{punctuated::Pair, punctuated::Punctuated, token::Comma, *};
3

4
impl SourceAnalysis {
5
    pub(crate) fn process_expr(&mut self, expr: &Expr, ctx: &Context) -> SubResult {
1,730✔
6
        let res = match expr {
3,460✔
7
            Expr::Macro(m) => self.visit_macro_call(&m.mac, ctx),
300✔
8
            Expr::Struct(s) => self.visit_struct_expr(s, ctx),
80✔
9
            Expr::Unsafe(u) => self.visit_unsafe_block(u, ctx),
60✔
10
            Expr::Call(c) => self.visit_callable(c, ctx),
1,090✔
11
            Expr::MethodCall(m) => self.visit_methodcall(m, ctx),
1,540✔
12
            Expr::Match(m) => self.visit_match(m, ctx),
290✔
13
            Expr::Block(b) => self.visit_expr_block(b, ctx),
400✔
14
            Expr::If(i) => self.visit_if(i, ctx),
430✔
15
            Expr::While(w) => self.visit_while(w, ctx),
120✔
16
            Expr::ForLoop(f) => self.visit_for(f, ctx),
150✔
17
            Expr::Loop(l) => self.visit_loop(l, ctx),
160✔
18
            Expr::Return(r) => self.visit_return(r, ctx),
100✔
19
            Expr::Closure(c) => self.visit_closure(c, ctx),
×
20
            Expr::Path(p) => self.visit_path(p, ctx),
1,820✔
21
            Expr::Let(l) => self.visit_let(l, ctx),
10✔
22
            Expr::Group(g) => self.process_expr(&g.expr, ctx),
×
23
            Expr::Await(a) => self.process_expr(&a.base, ctx),
×
24
            Expr::Async(a) => self.visit_block(&a.block, ctx),
×
25
            Expr::Try(t) => {
26✔
26
                self.process_expr(&t.expr, ctx);
104✔
27
                SubResult::Definite
26✔
28
            }
29
            Expr::TryBlock(t) => {
×
30
                self.visit_block(&t.block, ctx);
×
31
                SubResult::Definite
×
32
            }
33
            // don't try to compute unreachability on other things
34
            _ => SubResult::Ok,
394✔
35
        };
36
        if res.is_unreachable() {
3,496✔
37
            let analysis = self.get_line_analysis(ctx.file.to_path_buf());
216✔
38
            analysis.ignore_tokens(expr);
72✔
39
        }
40
        res
1,730✔
41
    }
42

43
    fn visit_let(&mut self, let_expr: &ExprLet, ctx: &Context) -> SubResult {
2✔
44
        let check_cover = self.check_attr_list(&let_expr.attrs, ctx);
10✔
45
        let analysis = self.get_line_analysis(ctx.file.to_path_buf());
10✔
46
        let mut res = SubResult::Ok;
4✔
47
        if check_cover {
2✔
48
            for a in &let_expr.attrs {
2✔
49
                analysis.ignore_tokens(a);
×
50
            }
51
            let spn = let_expr.span();
6✔
52
            let base_line = let_expr.let_token.span().start().line;
4✔
53
            if base_line != spn.end().line {
4✔
54
                // Now check the other lines
55
                let lhs = let_expr.pat.span();
×
56
                if lhs.start().line != base_line {
×
57
                    analysis.logical_lines.insert(lhs.start().line, base_line);
×
58
                }
59
                let eq = let_expr.eq_token.span();
×
60
                if eq.start().line != base_line {
×
61
                    analysis.logical_lines.insert(eq.start().line, base_line);
×
62
                }
63
                if let_expr.expr.span().start().line != base_line {
×
64
                    analysis
×
65
                        .logical_lines
×
66
                        .insert(let_expr.expr.span().start().line, base_line);
×
67
                }
68
                res += self.process_expr(&let_expr.expr, ctx);
×
69
            }
70
        } else {
71
            analysis.ignore_tokens(let_expr);
×
72
        }
73
        res
2✔
74
    }
75

76
    fn visit_path(&mut self, path: &ExprPath, ctx: &Context) -> SubResult {
364✔
77
        if let Some(PathSegment {
78
            ref ident,
364✔
79
            arguments: _,
80
        }) = path.path.segments.last()
364✔
81
        {
82
            if ident == "unreachable_unchecked" {
364✔
83
                let analysis = self.get_line_analysis(ctx.file.to_path_buf());
10✔
84
                analysis.ignore_tokens(path);
6✔
85
                return SubResult::Unreachable;
2✔
86
            }
87
        }
88
        SubResult::Ok
362✔
89
    }
90

91
    fn visit_return(&mut self, ret: &ExprReturn, ctx: &Context) -> SubResult {
20✔
92
        let check_cover = self.check_attr_list(&ret.attrs, ctx);
100✔
93
        let analysis = self.get_line_analysis(ctx.file.to_path_buf());
100✔
94
        if check_cover {
20✔
95
            for a in &ret.attrs {
20✔
96
                analysis.ignore_tokens(a);
×
97
            }
98
        } else {
99
            analysis.ignore_tokens(ret);
×
100
        }
101
        SubResult::Definite
20✔
102
    }
103

104
    fn visit_expr_block(&mut self, block: &ExprBlock, ctx: &Context) -> SubResult {
80✔
105
        if self.check_attr_list(&block.attrs, ctx) {
320✔
106
            self.visit_block(&block.block, ctx)
320✔
107
        } else {
108
            let analysis = self.get_line_analysis(ctx.file.to_path_buf());
×
109
            analysis.ignore_tokens(block);
×
110
            SubResult::Ok
×
111
        }
112
    }
113

114
    fn visit_block(&mut self, block: &Block, ctx: &Context) -> SubResult {
252✔
115
        let reachable = self.process_statements(&block.stmts, ctx);
1,260✔
116
        if reachable.is_unreachable() {
522✔
117
            let analysis = self.get_line_analysis(ctx.file.to_path_buf());
108✔
118
            analysis.ignore_tokens(block);
36✔
119
        }
120
        reachable
252✔
121
    }
122

123
    fn visit_closure(&mut self, closure: &ExprClosure, ctx: &Context) -> SubResult {
×
124
        let res = self.process_expr(&closure.body, ctx);
×
125
        // Even if a closure is "unreachable" it might be part of a chained method
126
        // call and I don't want that propagating up.
127
        if res.is_unreachable() {
×
128
            SubResult::Ok
×
129
        } else {
130
            res
×
131
        }
132
    }
133

134
    fn visit_match(&mut self, mat: &ExprMatch, ctx: &Context) -> SubResult {
58✔
135
        // a match with some arms is unreachable iff all its arms are unreachable
136
        let mut result = None;
116✔
137
        for (arm_idx, arm) in mat.arms.iter().enumerate() {
356✔
138
            if self.check_attr_list(&arm.attrs, ctx) {
480✔
139
                let reachable = self.process_expr(&arm.body, ctx);
590✔
140
                if reachable.is_reachable() {
236✔
141
                    let analysis = self.get_line_analysis(ctx.file.to_path_buf());
530✔
142
                    let span = arm.pat.span();
318✔
143
                    for line in span.start().line..span.end().line {
228✔
144
                        analysis.logical_lines.insert(line + 1, span.start().line);
32✔
145
                    }
146
                    result = result.map(|x| x + reachable).or(Some(reachable));
532✔
147
                }
148
                self.maybe_ignore_inert_match_arm(arm, arm_idx, &mat.arms, ctx);
708✔
149
            } else {
150
                let analysis = self.get_line_analysis(ctx.file.to_path_buf());
12✔
151
                analysis.ignore_tokens(arm);
4✔
152
            }
153
        }
154
        if let Some(result) = result {
110✔
155
            result
52✔
156
        } else {
157
            let analysis = self.get_line_analysis(ctx.file.to_path_buf());
30✔
158
            analysis.ignore_tokens(mat);
18✔
159
            SubResult::Unreachable
6✔
160
        }
161
    }
162

163
    // LLVM doesn't always assign a coverage region to match arm patterns that are
164
    // inert (i.e., static patterns that don't bind any names).
165
    // In force-covered functions, this leads them to be reported as uncovered,
166
    // so we mark such patterns as ignored. Bindings (`a =>`, `Some(x) =>`) and
167
    // pattern guards do get a region, so they're left alone.
168
    fn maybe_ignore_inert_match_arm(
118✔
169
        &mut self,
170
        arm: &Arm,
171
        arm_idx: usize,
172
        all_arms: &[Arm],
173
        ctx: &Context,
174
    ) {
175
        if !pattern_is_inert(&arm.pat) {
118✔
176
            return;
18✔
177
        }
178
        let pat_line = arm.pat.span().start().line;
200✔
179
        let end_line = match &arm.guard {
138✔
180
            Some((_, guard)) => guard.span().start().line,
8✔
181
            None => {
182
                let Expr::Block(b) = &*arm.body else {
130✔
183
                    return;
62✔
184
                };
185
                let Some(first_stmt) = b.block.stmts.first() else {
68✔
186
                    return;
×
187
                };
188
                first_stmt.span().start().line
34✔
189
            }
190
        };
191
        if end_line <= pat_line {
38✔
192
            return;
4✔
193
        }
194
        // Bail out if any sibling arm has content in the range we'd ignore —
195
        // swallowing those rows would hide a sibling's coverage.
196
        for (i, other) in all_arms.iter().enumerate() {
226✔
197
            if i == arm_idx {
62✔
198
                continue;
32✔
199
            }
200
            let s = other.pat.span().start().line;
60✔
201
            let e = other.body.span().end().line;
60✔
202
            if s < end_line && e >= pat_line {
42✔
203
                return;
2✔
204
            }
205
        }
206
        let analysis = self.get_line_analysis(ctx.file.to_path_buf());
160✔
207
        if !analysis.is_force_covered(pat_line) {
64✔
208
            return;
6✔
209
        }
210
        analysis.add_to_ignore(pat_line..end_line);
78✔
211
    }
212

213
    fn visit_if(&mut self, if_block: &ExprIf, ctx: &Context) -> SubResult {
86✔
214
        // an if expression is unreachable iff both its branches are unreachable
215

216
        let mut reachable = self.process_expr(&if_block.cond, ctx);
430✔
217
        reachable += self.visit_block(&if_block.then_branch, ctx);
344✔
218
        if let Some((_, ref else_block)) = if_block.else_branch {
162✔
219
            reachable += self.process_expr(else_block, ctx);
152✔
220
        } else {
221
            // an empty else branch is reachable
222
            reachable += SubResult::Ok;
48✔
223
        }
224
        if reachable.is_unreachable() {
172✔
225
            let analysis = self.get_line_analysis(ctx.file.to_path_buf());
30✔
226
            analysis.ignore_tokens(if_block);
18✔
227
            SubResult::Unreachable
6✔
228
        } else {
229
            reachable
80✔
230
        }
231
    }
232

233
    fn visit_while(&mut self, whl: &ExprWhile, ctx: &Context) -> SubResult {
24✔
234
        if self.check_attr_list(&whl.attrs, ctx) {
96✔
235
            // a while block is unreachable iff its body is
236
            if self.visit_block(&whl.body, ctx).is_unreachable() {
96✔
237
                let analysis = self.get_line_analysis(ctx.file.to_path_buf());
20✔
238
                analysis.ignore_tokens(whl);
12✔
239
                SubResult::Unreachable
4✔
240
            } else {
241
                SubResult::Definite
20✔
242
            }
243
        } else {
244
            let analysis = self.get_line_analysis(ctx.file.to_path_buf());
×
245
            analysis.ignore_tokens(whl);
×
246
            SubResult::Definite
×
247
        }
248
    }
249

250
    fn visit_for(&mut self, for_loop: &ExprForLoop, ctx: &Context) -> SubResult {
30✔
251
        if self.check_attr_list(&for_loop.attrs, ctx) {
120✔
252
            // a for block is unreachable iff its body is
253
            if self.visit_block(&for_loop.body, ctx).is_unreachable() {
120✔
254
                let analysis = self.get_line_analysis(ctx.file.to_path_buf());
10✔
255
                analysis.ignore_tokens(for_loop);
6✔
256
                SubResult::Unreachable
2✔
257
            } else {
258
                SubResult::Definite
28✔
259
            }
260
        } else {
261
            let analysis = self.get_line_analysis(ctx.file.to_path_buf());
×
262
            analysis.ignore_tokens(for_loop);
×
263
            SubResult::Definite
×
264
        }
265
    }
266

267
    fn visit_loop(&mut self, loopex: &ExprLoop, ctx: &Context) -> SubResult {
32✔
268
        if self.check_attr_list(&loopex.attrs, ctx) {
128✔
269
            // a loop block is unreachable iff its body is
270
            // given we can't reason if a loop terminates we should make it as definite as
271
            // it may last forever
272
            if self.visit_block(&loopex.body, ctx).is_unreachable() {
128✔
273
                let analysis = self.get_line_analysis(ctx.file.to_path_buf());
20✔
274
                analysis.ignore_tokens(loopex);
12✔
275
                SubResult::Unreachable
4✔
276
            } else {
277
                SubResult::Definite
28✔
278
            }
279
        } else {
280
            let analysis = self.get_line_analysis(ctx.file.to_path_buf());
×
281
            analysis.ignore_tokens(loopex);
×
282
            SubResult::Definite
×
283
        }
284
    }
285

286
    fn visit_callable(&mut self, call: &ExprCall, ctx: &Context) -> SubResult {
218✔
287
        if self.check_attr_list(&call.attrs, ctx) {
872✔
288
            if !call.args.is_empty() && call.span().start().line != call.span().end().line {
494✔
289
                let lines = get_coverable_args(&call.args);
12✔
290
                let lines = get_line_range(call).filter(|x| !lines.contains(x));
52✔
291
                let analysis = self.get_line_analysis(ctx.file.to_path_buf());
20✔
292
                analysis.add_to_ignore(lines);
12✔
293
            }
294
            self.process_expr(&call.func, ctx);
872✔
295
        } else {
296
            let analysis = self.get_line_analysis(ctx.file.to_path_buf());
×
297
            analysis.ignore_tokens(call);
×
298
        }
299
        // We can't guess if a callable would actually be unreachable
300
        SubResult::Ok
218✔
301
    }
302

303
    fn visit_methodcall(&mut self, meth: &ExprMethodCall, ctx: &Context) -> SubResult {
308✔
304
        if self.check_attr_list(&meth.attrs, ctx) {
1,232✔
305
            self.process_expr(&meth.receiver, ctx);
1,232✔
306
            let start = meth.receiver.span().end().line + 1;
616✔
307
            let range = get_line_range(meth);
924✔
308
            let lines = get_coverable_args(&meth.args);
924✔
309
            let lines = (start..range.end).filter(|x| !lines.contains(x));
1,674✔
310
            let analysis = self.get_line_analysis(ctx.file.to_path_buf());
1,540✔
311
            analysis.add_to_ignore(lines);
924✔
312
        } else {
313
            let analysis = self.get_line_analysis(ctx.file.to_path_buf());
×
314
            analysis.ignore_tokens(meth);
×
315
        }
316
        // We can't guess if a method would actually be unreachable
317
        SubResult::Ok
308✔
318
    }
319

320
    fn visit_unsafe_block(&mut self, unsafe_expr: &ExprUnsafe, ctx: &Context) -> SubResult {
12✔
321
        let u_line = unsafe_expr.unsafe_token.span().start().line;
24✔
322
        let mut res = SubResult::Ok;
24✔
323
        let blk = &unsafe_expr.block;
24✔
324
        if u_line != blk.brace_token.span.join().start().line || blk.stmts.is_empty() {
48✔
325
            let analysis = self.get_line_analysis(ctx.file.to_path_buf());
×
326
            analysis.ignore_tokens(unsafe_expr.unsafe_token);
×
327
        } else if let Some(first_stmt) = blk.stmts.first() {
24✔
328
            let s = match first_stmt {
24✔
329
                Stmt::Local(l) => l.span(),
×
330
                Stmt::Item(i) => i.span(),
×
331
                Stmt::Expr(e, _) => e.span(),
24✔
332
                Stmt::Macro(m) => m.span(),
12✔
333
            };
334
            if u_line != s.start().line {
32✔
335
                let analysis = self.get_line_analysis(ctx.file.to_path_buf());
48✔
336
                analysis.ignore_tokens(unsafe_expr.unsafe_token);
16✔
337
            }
338
            let reachable = self.process_statements(&blk.stmts, ctx);
60✔
339
            if reachable.is_unreachable() {
24✔
340
                let analysis = self.get_line_analysis(ctx.file.to_path_buf());
×
341
                analysis.ignore_tokens(unsafe_expr);
×
342
                return SubResult::Unreachable;
×
343
            }
344
            res += reachable;
12✔
345
        } else {
346
            let analysis = self.get_line_analysis(ctx.file.to_path_buf());
×
347
            analysis.ignore_tokens(unsafe_expr.unsafe_token);
×
348
            analysis.ignore_span(blk.span());
×
349
        }
350
        res
12✔
351
    }
352

353
    fn visit_struct_expr(&mut self, structure: &ExprStruct, ctx: &Context) -> SubResult {
16✔
354
        let mut cover: HashSet<usize> = HashSet::new();
48✔
355
        for field in structure.fields.pairs() {
66✔
356
            let first = match field {
68✔
357
                Pair::Punctuated(t, _) => t,
52✔
358
                Pair::End(t) => t,
16✔
359
            };
360
            let span = match first.member {
68✔
361
                Member::Named(ref i) => i.span(),
102✔
362
                Member::Unnamed(ref i) => i.span,
×
363
            };
364
            match first.expr {
34✔
365
                Expr::Lit(_) | Expr::Path(_) => {}
18✔
366
                _ => {
16✔
367
                    cover.insert(span.start().line);
48✔
368
                }
369
            }
370
        }
371
        let x = get_line_range(structure).filter(|x| !cover.contains(x));
310✔
372
        let analysis = self.get_line_analysis(ctx.file.to_path_buf());
80✔
373
        analysis.add_to_ignore(x);
48✔
374
        // struct expressions are never unreachable by themselves
375
        SubResult::Ok
16✔
376
    }
377
}
378

379
// A pattern is "inert" when matching it binds no name, i.e. it contains
380
// only literals, existing idents, consts and passive syntactic structures.
381
//
382
// Syn can't distinguish `Pat::Ident` bindings from unit variants or constants
383
// without name resolution. Use Rust's naming convention as a heuristic:
384
// idents starting with uppercase are paths (types, variants, or
385
// SCREAMING_SNAKE constants) and are treated as inert. Lowercase idents are
386
// treated as bindings. The `non_snake_case` lint keeps the false-positive
387
// risk negligible.
388
fn pattern_is_inert(pat: &Pat) -> bool {
164✔
389
    match pat {
164✔
390
        Pat::Wild(_)
391
        | Pat::Lit(_)
392
        | Pat::Path(_)
393
        | Pat::Const(_)
394
        | Pat::Range(_)
395
        | Pat::Rest(_) => true,
112✔
396
        Pat::Or(o) => o.cases.iter().all(pattern_is_inert),
30✔
397
        Pat::Paren(p) => pattern_is_inert(&p.pat),
×
398
        Pat::Tuple(t) => t.elems.iter().all(pattern_is_inert),
18✔
399
        Pat::TupleStruct(ts) => ts.elems.iter().all(pattern_is_inert),
24✔
400
        Pat::Struct(s) => s.fields.iter().all(|f| pattern_is_inert(&f.pat)),
×
401
        Pat::Reference(r) => pattern_is_inert(&r.pat),
×
402
        Pat::Slice(s) => s.elems.iter().all(pattern_is_inert),
×
403
        Pat::Ident(i) => pat_ident_is_path_by_convention(i),
84✔
404
        _ => false,
×
405
    }
406
}
407

408
fn pat_ident_is_path_by_convention(i: &PatIdent) -> bool {
28✔
409
    i.by_ref.is_none()
56✔
410
        && i.mutability.is_none()
56✔
411
        && i.subpat.is_none()
56✔
412
        && i.ident
28✔
413
            .to_string()
28✔
414
            .chars()
28✔
415
            .next()
28✔
416
            .is_some_and(|c| c.is_ascii_uppercase())
84✔
417
}
418

419
fn get_coverable_args(args: &Punctuated<Expr, Comma>) -> HashSet<usize> {
312✔
420
    let mut lines: HashSet<usize> = HashSet::new();
936✔
421
    for a in args.iter() {
716✔
422
        match *a {
92✔
423
            Expr::Lit(_) => {}
8✔
424
            _ => {
425
                for i in get_line_range(a) {
368✔
426
                    lines.insert(i);
200✔
427
                }
428
            }
429
        }
430
    }
431
    lines
312✔
432
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc