@@ -211,59 +211,64 @@ impl<'a> ContractVisitor<'a> {
211211 // branch ID as we do.
212212 self . branch_id += 1 ;
213213
214- // The relevant source range for the true branch is the `if(...)` statement itself
215- // and the true body of the if statement. The false body of the
216- // statement (if any) is processed as its own thing. If this source
217- // range is not processed like this, it is virtually impossible to
218- // correctly map instructions back to branches that include more
219- // complex logic like conditional logic.
220- let true_branch_loc = & ast:: LowFidelitySourceLocation {
221- start : node. src . start ,
222- length : true_body
223- . src
224- . length
225- . map ( |length| true_body. src . start - node. src . start + length) ,
226- index : node. src . index ,
227- } ;
228-
229- // Add the coverage item for branch 0 (true body).
230- self . push_item ( CoverageItem {
231- kind : CoverageItemKind :: Branch { branch_id, path_id : 0 } ,
232- loc : self . source_location_for ( true_branch_loc) ,
233- hits : 0 ,
234- } ) ;
235-
236214 match node. attribute :: < Node > ( "falseBody" ) {
237215 // Both if/else statements.
238216 Some ( false_body) => {
239- // Add the coverage item for branch 1 (false body).
240- // The relevant source range for the false branch is the `else` statement
241- // itself and the false body of the else statement.
242- self . push_item ( CoverageItem {
243- kind : CoverageItemKind :: Branch { branch_id, path_id : 1 } ,
244- loc : self . source_location_for ( & ast:: LowFidelitySourceLocation {
245- start : node. src . start ,
246- length : false_body. src . length . map ( |length| {
247- false_body. src . start - true_body. src . start + length
217+ // Add branch coverage items only if one of true/branch bodies contains
218+ // statements.
219+ if has_statements ( & true_body) || has_statements ( & false_body) {
220+ // Add the coverage item for branch 0 (true body).
221+ // The relevant source range for the true branch is the `if(...)`
222+ // statement itself and the true body of the if statement.
223+ //
224+ // The false body of the statement is processed as its own thing.
225+ // If this source range is not processed like this, it is virtually
226+ // impossible to correctly map instructions back to branches that
227+ // include more complex logic like conditional logic.
228+ self . push_item ( CoverageItem {
229+ kind : CoverageItemKind :: Branch { branch_id, path_id : 0 } ,
230+ loc : self . source_location_for ( & ast:: LowFidelitySourceLocation {
231+ start : node. src . start ,
232+ length : true_body. src . length . map ( |length| {
233+ true_body. src . start - node. src . start + length
234+ } ) ,
235+ index : node. src . index ,
248236 } ) ,
249- index : node. src . index ,
250- } ) ,
251- hits : 0 ,
252- } ) ;
253- // Process the true body.
254- self . visit_block_or_statement ( & true_body) ?;
255- // Process the false body.
256- self . visit_block_or_statement ( & false_body) ?;
237+ hits : 0 ,
238+ } ) ;
239+ // Add the coverage item for branch 1 (false body).
240+ // The relevant source range for the false branch is the `else`
241+ // statement itself and the false body of the else statement.
242+ self . push_item ( CoverageItem {
243+ kind : CoverageItemKind :: Branch { branch_id, path_id : 1 } ,
244+ loc : self . source_location_for ( & ast:: LowFidelitySourceLocation {
245+ start : node. src . start ,
246+ length : false_body. src . length . map ( |length| {
247+ false_body. src . start - true_body. src . start + length
248+ } ) ,
249+ index : node. src . index ,
250+ } ) ,
251+ hits : 0 ,
252+ } ) ;
253+
254+ // Process the true body.
255+ self . visit_block_or_statement ( & true_body) ?;
256+ // Process the false body.
257+ self . visit_block_or_statement ( & false_body) ?;
258+ }
257259 }
258260 None => {
259- // Add the coverage item for branch 1 (same true body).
260- self . push_item ( CoverageItem {
261- kind : CoverageItemKind :: Branch { branch_id, path_id : 1 } ,
262- loc : self . source_location_for ( true_branch_loc) ,
263- hits : 0 ,
264- } ) ;
265- // Process the true body.
266- self . visit_block_or_statement ( & true_body) ?;
261+ // Add single branch coverage only if it contains statements.
262+ if has_statements ( & true_body) {
263+ // Add the coverage item for branch 0 (true body).
264+ self . push_item ( CoverageItem {
265+ kind : CoverageItemKind :: SinglePathBranch { branch_id } ,
266+ loc : self . source_location_for ( & true_body. src ) ,
267+ hits : 0 ,
268+ } ) ;
269+ // Process the true body.
270+ self . visit_block_or_statement ( & true_body) ?;
271+ }
267272 }
268273 }
269274
@@ -321,9 +326,7 @@ impl<'a> ContractVisitor<'a> {
321326
322327 // Add coverage for clause body only if it is not empty.
323328 if let Some ( block) = clause. attribute :: < Node > ( "block" ) {
324- let statements: Vec < Node > =
325- block. attribute ( "statements" ) . unwrap_or_default ( ) ;
326- if !statements. is_empty ( ) {
329+ if has_statements ( & block) {
327330 self . push_item ( CoverageItem {
328331 kind : CoverageItemKind :: Statement ,
329332 loc : self . source_location_for ( & block. src ) ,
@@ -502,8 +505,12 @@ impl<'a> ContractVisitor<'a> {
502505 let source_location = & item. loc ;
503506
504507 // Push a line item if we haven't already
505- if matches ! ( item. kind, CoverageItemKind :: Statement | CoverageItemKind :: Branch { .. } ) &&
506- self . last_line < source_location. line
508+ if matches ! (
509+ item. kind,
510+ CoverageItemKind :: Statement |
511+ CoverageItemKind :: Branch { .. } |
512+ CoverageItemKind :: SinglePathBranch { .. }
513+ ) && self . last_line < source_location. line
507514 {
508515 self . items . push ( CoverageItem {
509516 kind : CoverageItemKind :: Line ,
@@ -527,6 +534,12 @@ impl<'a> ContractVisitor<'a> {
527534 }
528535}
529536
537+ /// Helper function to check if a given node contains any statement.
538+ fn has_statements ( node : & Node ) -> bool {
539+ let statements: Vec < Node > = node. attribute ( "statements" ) . unwrap_or_default ( ) ;
540+ !statements. is_empty ( )
541+ }
542+
530543/// [`SourceAnalyzer`] result type.
531544#[ derive( Debug ) ]
532545pub struct SourceAnalysis {
0 commit comments