Open Zalathar opened 7 months ago
Based on my investigations so far:
There's also some discussion at https://github.com/rust-lang/rust/issues/79649#issuecomment-2061486639 about whether the right-hand-side of a lazy boolean expression (&&
/||
) should be treated as a “branch” condition, even when it's not part of an if
condition.
My view is that “branch coverage” should not be in the business of instrumenting things that clearly aren't branches at the language level, at least not by default. There are valid use-cases for that behaviour, but if it exists then it should require some additional level of opt-in beyond just enabling branch coverage.
(If people want to discuss this point further, I would suggest creating a separate issue for that discussion.)
Is there a draft folk for if-let
pattern?
I have tried a bit and found a possibly feasible way to insert block markers, see a naive draft.
Because if let
would be taken as match
guard this method may solve them together (not sure, I just check it could find the true/false blocks for code like if let (En::A(Some(a)) | En::B(a), En::C(b)) = _
.
By this then BranchSpan
might need to change its true_marker
and false_marker
to Vec<BlockMarkerId>
.
I managed to get my match-arm idea working, so I'll try to tie up the loose ends and post it as a PR.
match
arms for branch coverage.I now have draft PRs for if-let, let-else, and match arms (without or-patterns).
Noticed the match arms pay attention to occasion like comment at first line of make_expression
in counters.rs, I think it may be better put a note here rather reviews of the current two prs.
Many redundant expressions like this may be introduced by match arms.
The root cause is rustc may generate several test blocks for certain pattern. For example, code (A | B, C | D)
will generate CFG like code
if match A {
if match C {
// Assume here is BcbCounter 1
} else if match D {
// ...
}
} else if match B {
if match C {
// Assume here is BcbCounter 2
} else if match D {
// ...
}
}
The true_term
of C
apparently should be BcbCounter 1 + BcbCounter 2
. Pattern (A | B, C | D, E | F)
even make terms of E
more sophisticated. Actually they are just composition of several BcbCounter::Counter
thus there probably many of intermediate expressions can be optimized out.
However, given that Expressions
does not have runtime overhead the main side effect of these redundant expressions might only make people who read mir directly distressed. Further if we wanted to minimize the number of expressions, probably do it in llvm is better.
The
true_term
ofC
apparently should beBcbCounter 1 + BcbCounter 2
. Pattern(A | B, C | D, E | F)
even make terms ofE
more sophisticated. Actually they are just composition of severalBcbCounter::Counter
thus there probably many of intermediate expressions can be optimized out.
This might not be "apparent". We also can view C
as several branches sharing one span. By this view most work could be simplified but users would be confused with many branches in llvm-cov reports.
I've rewritten the issue description to give a more up-to-date summary of the status quo.
Branch coverage is known to support these branching language constructs:
if
andwhile
if let
and let-chains||
and&&
as separate brancheswhile
desugars toif
before MIR building, so we support it as a consequence of supportingif
||
and&&
expressions||
and&&
are treated as separate brancheslet
-else
statementsif
expressions before MIR buildingif let
guardsBranch coverage is known to not yet support these branching language constructs:
match
expressions124154
?
operator (which conditionally returns)match
expression, so once we support those, supporting?
should mostly be a matter of expansion-span bookkeeping.await
expressions (which implicitly “return” when the enclosing future is dropped)?
Relevant PR links:
122322
124223
124154
(Rewritten on 2024-07-11; see edit history for the old text.)