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

xd009642 / tarpaulin / #692

24 Oct 2025 07:10PM UTC coverage: 84.27% (+8.9%) from 75.356%
#692

push

xd009642
Release 0.34.1

44 of 47 new or added lines in 4 files covered. (93.62%)

34 existing lines in 7 files now uncovered.

4602 of 5461 relevant lines covered (84.27%)

252235.65 hits per line

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

75.45
/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,074✔
6
        let res = match expr {
2,148✔
7
            Expr::Macro(m) => self.visit_macro_call(&m.mac, ctx),
185✔
8
            Expr::Struct(s) => self.visit_struct_expr(s, ctx),
70✔
9
            Expr::Unsafe(u) => self.visit_unsafe_block(u, ctx),
50✔
10
            Expr::Call(c) => self.visit_callable(c, ctx),
640✔
11
            Expr::MethodCall(m) => self.visit_methodcall(m, ctx),
475✔
12
            Expr::Match(m) => self.visit_match(m, ctx),
100✔
13
            Expr::Block(b) => self.visit_expr_block(b, ctx),
185✔
14
            Expr::If(i) => self.visit_if(i, ctx),
395✔
15
            Expr::While(w) => self.visit_while(w, ctx),
100✔
16
            Expr::ForLoop(f) => self.visit_for(f, ctx),
135✔
17
            Expr::Loop(l) => self.visit_loop(l, ctx),
140✔
18
            Expr::Return(r) => self.visit_return(r, ctx),
75✔
19
            Expr::Closure(c) => self.visit_closure(c, ctx),
×
20
            Expr::Path(p) => self.visit_path(p, ctx),
1,250✔
21
            Expr::Let(l) => self.visit_let(l, ctx),
5✔
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) => {
1✔
26
                self.process_expr(&t.expr, ctx);
4✔
27
                SubResult::Definite
1✔
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,
312✔
35
        };
36
        if res.is_unreachable() {
2,169✔
37
            let analysis = self.get_line_analysis(ctx.file.to_path_buf());
126✔
38
            analysis.ignore_tokens(expr);
42✔
39
        }
40
        res
1,074✔
41
    }
42

43
    fn visit_let(&mut self, let_expr: &ExprLet, ctx: &Context) -> SubResult {
1✔
44
        let check_cover = self.check_attr_list(&let_expr.attrs, ctx);
5✔
45
        let analysis = self.get_line_analysis(ctx.file.to_path_buf());
5✔
46
        let mut res = SubResult::Ok;
2✔
47
        if check_cover {
1✔
48
            for a in &let_expr.attrs {
1✔
UNCOV
49
                analysis.ignore_tokens(a);
×
50
            }
51
            let spn = let_expr.span();
3✔
52
            let base_line = let_expr.let_token.span().start().line;
2✔
53
            if base_line != spn.end().line {
2✔
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
1✔
74
    }
75

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

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

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

114
    fn visit_block(&mut self, block: &Block, ctx: &Context) -> SubResult {
191✔
115
        let reachable = self.process_statements(&block.stmts, ctx);
955✔
116
        if reachable.is_unreachable() {
391✔
117
            let analysis = self.get_line_analysis(ctx.file.to_path_buf());
54✔
118
            analysis.ignore_tokens(block);
18✔
119
        }
120
        reachable
191✔
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 {
20✔
135
        // a match with some arms is unreachable iff all its arms are unreachable
136
        let mut result = None;
40✔
137
        for arm in &mat.arms {
132✔
138
            if self.check_attr_list(&arm.attrs, ctx) {
224✔
139
                let reachable = self.process_expr(&arm.body, ctx);
275✔
140
                if reachable.is_reachable() {
110✔
141
                    let analysis = self.get_line_analysis(ctx.file.to_path_buf());
230✔
142
                    let span = arm.pat.span();
138✔
143
                    for line in span.start().line..span.end().line {
100✔
144
                        analysis.logical_lines.insert(line + 1, span.start().line);
16✔
145
                    }
146
                    result = result.map(|x| x + reachable).or(Some(reachable));
242✔
147
                }
148
            } else {
149
                let analysis = self.get_line_analysis(ctx.file.to_path_buf());
6✔
150
                analysis.ignore_tokens(arm);
2✔
151
            }
152
        }
153
        if let Some(result) = result {
37✔
154
            result
17✔
155
        } else {
156
            let analysis = self.get_line_analysis(ctx.file.to_path_buf());
15✔
157
            analysis.ignore_tokens(mat);
9✔
158
            SubResult::Unreachable
3✔
159
        }
160
    }
161

162
    fn visit_if(&mut self, if_block: &ExprIf, ctx: &Context) -> SubResult {
79✔
163
        // an if expression is unreachable iff both its branches are unreachable
164

165
        let mut reachable = self.process_expr(&if_block.cond, ctx);
395✔
166
        reachable += self.visit_block(&if_block.then_branch, ctx);
316✔
167
        if let Some((_, ref else_block)) = if_block.else_branch {
153✔
168
            reachable += self.process_expr(else_block, ctx);
148✔
169
        } else {
170
            // an empty else branch is reachable
171
            reachable += SubResult::Ok;
42✔
172
        }
173
        if reachable.is_unreachable() {
158✔
174
            let analysis = self.get_line_analysis(ctx.file.to_path_buf());
15✔
175
            analysis.ignore_tokens(if_block);
9✔
176
            SubResult::Unreachable
3✔
177
        } else {
178
            reachable
76✔
179
        }
180
    }
181

182
    fn visit_while(&mut self, whl: &ExprWhile, ctx: &Context) -> SubResult {
20✔
183
        if self.check_attr_list(&whl.attrs, ctx) {
80✔
184
            // a while block is unreachable iff its body is
185
            if self.visit_block(&whl.body, ctx).is_unreachable() {
80✔
186
                let analysis = self.get_line_analysis(ctx.file.to_path_buf());
10✔
187
                analysis.ignore_tokens(whl);
6✔
188
                SubResult::Unreachable
2✔
189
            } else {
190
                SubResult::Definite
18✔
191
            }
192
        } else {
193
            let analysis = self.get_line_analysis(ctx.file.to_path_buf());
×
UNCOV
194
            analysis.ignore_tokens(whl);
×
UNCOV
195
            SubResult::Definite
×
196
        }
197
    }
198

199
    fn visit_for(&mut self, for_loop: &ExprForLoop, ctx: &Context) -> SubResult {
27✔
200
        if self.check_attr_list(&for_loop.attrs, ctx) {
108✔
201
            // a for block is unreachable iff its body is
202
            if self.visit_block(&for_loop.body, ctx).is_unreachable() {
108✔
203
                let analysis = self.get_line_analysis(ctx.file.to_path_buf());
5✔
204
                analysis.ignore_tokens(for_loop);
3✔
205
                SubResult::Unreachable
1✔
206
            } else {
207
                SubResult::Definite
26✔
208
            }
209
        } else {
210
            let analysis = self.get_line_analysis(ctx.file.to_path_buf());
×
UNCOV
211
            analysis.ignore_tokens(for_loop);
×
UNCOV
212
            SubResult::Definite
×
213
        }
214
    }
215

216
    fn visit_loop(&mut self, loopex: &ExprLoop, ctx: &Context) -> SubResult {
28✔
217
        if self.check_attr_list(&loopex.attrs, ctx) {
112✔
218
            // a loop block is unreachable iff its body is
219
            // given we can't reason if a loop terminates we should make it as definite as
220
            // it may last forever
221
            if self.visit_block(&loopex.body, ctx).is_unreachable() {
112✔
222
                let analysis = self.get_line_analysis(ctx.file.to_path_buf());
10✔
223
                analysis.ignore_tokens(loopex);
6✔
224
                SubResult::Unreachable
2✔
225
            } else {
226
                SubResult::Definite
26✔
227
            }
228
        } else {
229
            let analysis = self.get_line_analysis(ctx.file.to_path_buf());
×
UNCOV
230
            analysis.ignore_tokens(loopex);
×
UNCOV
231
            SubResult::Definite
×
232
        }
233
    }
234

235
    fn visit_callable(&mut self, call: &ExprCall, ctx: &Context) -> SubResult {
128✔
236
        if self.check_attr_list(&call.attrs, ctx) {
512✔
237
            if !call.args.is_empty() && call.span().start().line != call.span().end().line {
304✔
238
                let lines = get_coverable_args(&call.args);
9✔
239
                let lines = get_line_range(call).filter(|x| !lines.contains(x));
39✔
240
                let analysis = self.get_line_analysis(ctx.file.to_path_buf());
15✔
241
                analysis.add_to_ignore(lines);
9✔
242
            }
243
            self.process_expr(&call.func, ctx);
512✔
244
        } else {
245
            let analysis = self.get_line_analysis(ctx.file.to_path_buf());
×
246
            analysis.ignore_tokens(call);
×
247
        }
248
        // We can't guess if a callable would actually be unreachable
249
        SubResult::Ok
128✔
250
    }
251

252
    fn visit_methodcall(&mut self, meth: &ExprMethodCall, ctx: &Context) -> SubResult {
95✔
253
        if self.check_attr_list(&meth.attrs, ctx) {
380✔
254
            self.process_expr(&meth.receiver, ctx);
380✔
255
            let start = meth.receiver.span().end().line + 1;
190✔
256
            let range = get_line_range(meth);
285✔
257
            let lines = get_coverable_args(&meth.args);
285✔
258
            let lines = (start..range.end).filter(|x| !lines.contains(x));
396✔
259
            let analysis = self.get_line_analysis(ctx.file.to_path_buf());
475✔
260
            analysis.add_to_ignore(lines);
285✔
261
        } else {
262
            let analysis = self.get_line_analysis(ctx.file.to_path_buf());
×
263
            analysis.ignore_tokens(meth);
×
264
        }
265
        // We can't guess if a method would actually be unreachable
266
        SubResult::Ok
95✔
267
    }
268

269
    fn visit_unsafe_block(&mut self, unsafe_expr: &ExprUnsafe, ctx: &Context) -> SubResult {
10✔
270
        let u_line = unsafe_expr.unsafe_token.span().start().line;
20✔
271
        let mut res = SubResult::Ok;
20✔
272
        let blk = &unsafe_expr.block;
20✔
273
        if u_line != blk.brace_token.span.join().start().line || blk.stmts.is_empty() {
40✔
274
            let analysis = self.get_line_analysis(ctx.file.to_path_buf());
×
275
            analysis.ignore_tokens(unsafe_expr.unsafe_token);
×
276
        } else if let Some(first_stmt) = blk.stmts.first() {
20✔
277
            let s = match first_stmt {
20✔
278
                Stmt::Local(l) => l.span(),
×
279
                Stmt::Item(i) => i.span(),
×
280
                Stmt::Expr(e, _) => e.span(),
24✔
281
                Stmt::Macro(m) => m.span(),
6✔
282
            };
283
            if u_line != s.start().line {
27✔
284
                let analysis = self.get_line_analysis(ctx.file.to_path_buf());
42✔
285
                analysis.ignore_tokens(unsafe_expr.unsafe_token);
14✔
286
            }
287
            let reachable = self.process_statements(&blk.stmts, ctx);
50✔
288
            if reachable.is_unreachable() {
20✔
289
                let analysis = self.get_line_analysis(ctx.file.to_path_buf());
×
290
                analysis.ignore_tokens(unsafe_expr);
×
291
                return SubResult::Unreachable;
×
292
            }
293
            res += reachable;
10✔
294
        } else {
295
            let analysis = self.get_line_analysis(ctx.file.to_path_buf());
×
296
            analysis.ignore_tokens(unsafe_expr.unsafe_token);
×
297
            analysis.ignore_span(blk.span());
×
298
        }
299
        res
10✔
300
    }
301

302
    fn visit_struct_expr(&mut self, structure: &ExprStruct, ctx: &Context) -> SubResult {
14✔
303
        let mut cover: HashSet<usize> = HashSet::new();
42✔
304
        for field in structure.fields.pairs() {
59✔
305
            let first = match field {
62✔
306
                Pair::Punctuated(t, _) => t,
46✔
307
                Pair::End(t) => t,
16✔
308
            };
309
            let span = match first.member {
62✔
310
                Member::Named(ref i) => i.span(),
93✔
311
                Member::Unnamed(ref i) => i.span,
×
312
            };
313
            match first.expr {
31✔
314
                Expr::Lit(_) | Expr::Path(_) => {}
17✔
315
                _ => {
14✔
316
                    cover.insert(span.start().line);
42✔
317
                }
318
            }
319
        }
320
        let x = get_line_range(structure).filter(|x| !cover.contains(x));
281✔
321
        let analysis = self.get_line_analysis(ctx.file.to_path_buf());
70✔
322
        analysis.add_to_ignore(x);
42✔
323
        // struct expressions are never unreachable by themselves
324
        SubResult::Ok
14✔
325
    }
326
}
327
fn get_coverable_args(args: &Punctuated<Expr, Comma>) -> HashSet<usize> {
98✔
328
    let mut lines: HashSet<usize> = HashSet::new();
294✔
329
    for a in args.iter() {
223✔
330
        match *a {
27✔
331
            Expr::Lit(_) => {}
2✔
332
            _ => {
333
                for i in get_line_range(a) {
116✔
334
                    lines.insert(i);
66✔
335
                }
336
            }
337
        }
338
    }
339
    lines
98✔
340
}
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