Module: SimpleCov::StaticCoverageExtractor::ConditionFolding
- Included in:
- Visitor
- Defined in:
- lib/simplecov/static_coverage_extractor/condition_folding.rb
Overview
Detects the if / unless / ternary conditions CRuby folds away.
When a condition is a statically-known-truthy/falsy literal the
compiler eliminates the dead arm and Coverage emits NO branch, so the
extractor must not synthesize one either — otherwise the arm is a
phantom that no loaded run can ever hit, the same unmergeable-tuple
failure mode as #1226 / #1233.
Constant Summary collapse
- FOLDS_SOURCE_FILE =
CRuby 3.4 rebuilt the fold on the Prism compiler, and the parse.y-based fold it replaced differed in three observable ways, each pinned by the runtime tuple equivalence battery on CI:
__FILE__folded on 3.2/3.3 but no longer does; parentheses were transparent for every literal on 3.2 (opacity starts at 3.3); and on 3.2 the dead arm's branch table entries survive the fold — parse.y instrumented branches before eliminating dead code — while itsdefs still never register. Gem::Version.new(RUBY_VERSION) < Gem::Version.new("3.4")
- PARENS_ALWAYS_TRANSPARENT =
Gem::Version.new(RUBY_VERSION) < Gem::Version.new("3.3")
- DEAD_ARM_BRANCHES_SURVIVE =
PARENS_ALWAYS_TRANSPARENT- CONTAINER_CONTENTS_NEED_STATIC_LITERALS =
Container literals in discarded position are eliminated from 3.3 on, but the contents rule differs: 3.3's compile.c elides a container whose contents are merely effect-free (
[x],[self]), while the Prism compiler (3.4+) demands fully static literals ([1]goes,[x]stays). Seestatic_container_literal?. !FOLDS_SOURCE_FILE
- STATIC_CONDITION_TYPES =
Prism node types for the literals that fold.
while/untildo NOT fold (while trueis a real branch), so only the if-like visitors consult this. Regexp and Range literals are excluded on purpose: as conditions they mean=~ $_/ flip-flop, which Coverage does branch on.[],{}, and interpolated strings do not fold either, and->folds while alambdacall does not — the compiler only folds what it can prove at compile time, and a method namedlambdaproves nothing. simplecov:disable branch — which arm runs is fixed by the running Ruby's version [ ::Prism::IntegerNode, ::Prism::FloatNode, ::Prism::RationalNode, ::Prism::ImaginaryNode, ::Prism::SymbolNode, ::Prism::StringNode, ::Prism::TrueNode, ::Prism::FalseNode, ::Prism::NilNode, ::Prism::SourceLineNode, ::Prism::SourceEncodingNode, ::Prism::LambdaNode, *(::Prism::SourceFileNode if FOLDS_SOURCE_FILE) ].freeze
- FALSY_CONDITION_TYPES =
The literals whose fold eliminates the then side:
if false/if nilkeep only the else arm. Every other folded literal is truthy and keeps only the then arm. [::Prism::FalseNode, ::Prism::NilNode].freeze
- PAREN_OPAQUE_TYPES =
The literals whose fold does NOT see through parentheses: CRuby folds
if nil,if "x", andif -> {}but keeps a real branch forif (nil),if ("x"), andif (-> {})— verified against Coverage, and pinned by the runtime tuple equivalence battery — while every other literal folds parenthesized or not.__FILE__is opaque like other strings on the Rubies that fold it at all. Consulted only when PARENS_ALWAYS_TRANSPARENT is false. [ ::Prism::NilNode, ::Prism::StringNode, ::Prism::LambdaNode, ::Prism::SourceFileNode ].freeze
- STATIC_LITERAL_LEAF_TYPES =
The scalar literals the compiler treats as fully static: a multi-statement paren condition (
if (1; 2)) folds by its last expression only when every leading statement is eliminated when discarded, and these — bare or composing an Array/Hash/Range — always are. Pinned against real Coverage on 3.2 through 4.0. [ ::Prism::IntegerNode, ::Prism::FloatNode, ::Prism::RationalNode, ::Prism::ImaginaryNode, ::Prism::StringNode, ::Prism::SymbolNode, ::Prism::TrueNode, ::Prism::FalseNode, ::Prism::NilNode, ::Prism::SourceLineNode, ::Prism::SourceFileNode, ::Prism::SourceEncodingNode, ::Prism::RegularExpressionNode ].freeze
- PRISM_ERA_ELIMINABLE_READS =
Non-literal reads that are also eliminated when discarded.
selfis eliminated by every supported compiler; local/ivar/ defined? elimination arrived with the Prism-era compilers. Anything that can raise or run hooks (constants, globals, calls, writes) is never eliminated and keeps the branch real. [ ::Prism::LocalVariableReadNode, ::Prism::InstanceVariableReadNode, ::Prism::DefinedNode ].freeze
- ELIMINABLE_READ_TYPES =
simplecov:disable branch — which arm runs is fixed by the running Ruby's version
[ ::Prism::SelfNode, *(PRISM_ERA_ELIMINABLE_READS unless PARENS_ALWAYS_TRANSPARENT) ].freeze