module SimpleCov::StaticCoverageExtractor::ValuePositions

def call(root)

Prism nodes Coverage treats as being in value position.
An identity set (a `compare_by_identity` Hash used as a set) of the
def call(root)
  positions = {} #: Hash[untyped, bool]
  positions.compare_by_identity
  mark(root, true, positions)
  positions
end

def else_clause(node)

def else_clause(node)
  node.public_send(ELSE_CLAUSE_METHOD)
end

def mark(node, in_value, positions)

def mark(node, in_value, positions)
  return unless node.is_a?(::Prism::Node)
  positions[node] = true if in_value
  children = tail_children(node, in_value)
  node.compact_child_nodes.each do |child|
    mark(child, children.any? { |c| c.equal?(child) }, positions)
  end
end

def subsequent(node)

`in` arms and `else` both discard tail position.
`case/in` (CaseMatchNode) is intentionally not a tail construct: its
of a case, under whichever accessor this Prism version exposes.
The `else`/`elsif` clause of an if-like node, and the `else` clause
def subsequent(node)
  node.is_a?(::Prism::IfNode) ? node.public_send(IF_NODE_SUBSEQUENT_METHOD) : else_clause(node)
end

def tail_children(node, in_value)

is included regardless of `in_value`.
itself is not (the method still returns its last expression), so it
void default. A method body is a tail context even when the `def`
The children of `node` that inherit its tail position; empty for the
def tail_children(node, in_value)
  # A method body is a tail context even when the `def` is not.
  return [node.body] if node.is_a?(::Prism::DefNode)
  return [] unless in_value
  case node
  when ::Prism::StatementsNode then [node.body.last]
  when ::Prism::IfNode, ::Prism::UnlessNode then [node.statements, subsequent(node)]
  when ::Prism::CaseNode then [*node.conditions, else_clause(node)]
  when ::Prism::ElseNode, ::Prism::WhenNode, ::Prism::BeginNode, ::Prism::ProgramNode then [node.statements]
  else []
  end
end